diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c94249bb9e..317a7128db 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -92,6 +92,7 @@ * Video: * Allow `MediaCodecVideoRenderer` to use a custom `VideoFrameProcessor.Factory`. + * H.265/HEVC: Fix parsing SPS short and long term reference picture info. * Text: * CEA-608: Change cue truncation logic to only consider visible text. Previously indent and tab offset were included when limiting the cue diff --git a/libraries/container/src/main/java/androidx/media3/container/NalUnitUtil.java b/libraries/container/src/main/java/androidx/media3/container/NalUnitUtil.java index 18d4123c15..635b189c51 100644 --- a/libraries/container/src/main/java/androidx/media3/container/NalUnitUtil.java +++ b/libraries/container/src/main/java/androidx/media3/container/NalUnitUtil.java @@ -625,8 +625,8 @@ public final class NalUnitUtil { } skipShortTermReferencePictureSets(data); if (data.readBit()) { // long_term_ref_pics_present_flag - // num_long_term_ref_pics_sps - for (int i = 0; i < data.readUnsignedExpGolombCodedInt(); i++) { + int numLongTermRefPicsSps = data.readUnsignedExpGolombCodedInt(); + for (int i = 0; i < numLongTermRefPicsSps; i++) { int ltRefPicPocLsbSpsLength = log2MaxPicOrderCntLsbMinus4 + 4; // lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i] data.skipBits(ltRefPicPocLsbSpsLength + 1); @@ -948,12 +948,14 @@ public final class NalUnitUtil { numPositivePics = bitArray.readUnsignedExpGolombCodedInt(); deltaPocS0 = new int[numNegativePics]; for (int i = 0; i < numNegativePics; i++) { - deltaPocS0[i] = bitArray.readUnsignedExpGolombCodedInt() + 1; + deltaPocS0[i] = + (i > 0 ? deltaPocS0[i - 1] : 0) - (bitArray.readUnsignedExpGolombCodedInt() + 1); bitArray.skipBit(); // used_by_curr_pic_s0_flag[i] } deltaPocS1 = new int[numPositivePics]; for (int i = 0; i < numPositivePics; i++) { - deltaPocS1[i] = bitArray.readUnsignedExpGolombCodedInt() + 1; + deltaPocS1[i] = + (i > 0 ? deltaPocS1[i - 1] : 0) + (bitArray.readUnsignedExpGolombCodedInt() + 1); bitArray.skipBit(); // used_by_curr_pic_s1_flag[i] } } diff --git a/libraries/container/src/test/java/androidx/media3/container/NalUnitUtilTest.java b/libraries/container/src/test/java/androidx/media3/container/NalUnitUtilTest.java index 2ab0a7c7f1..a1a9977c05 100644 --- a/libraries/container/src/test/java/androidx/media3/container/NalUnitUtilTest.java +++ b/libraries/container/src/test/java/androidx/media3/container/NalUnitUtilTest.java @@ -202,11 +202,40 @@ public final class NalUnitUtilTest { assertThat(spsData.colorTransfer).isEqualTo(6); } + /** Regression test for [Internal: b/292170736]. */ + @Test + public void parseH265SpsNalUnitPayload_withShortTermRefPicSets() { + byte[] spsNalUnitPayload = + new byte[] { + 1, 2, 96, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, -106, -96, 2, 28, -128, 30, 4, -39, 111, + -110, 76, -114, -65, -7, -13, 101, 33, -51, 66, 68, 2, 65, 0, 0, 3, 0, 1, 0, 0, 3, 0, 29, + 8 + }; + + NalUnitUtil.H265SpsData spsData = + NalUnitUtil.parseH265SpsNalUnitPayload(spsNalUnitPayload, 0, spsNalUnitPayload.length); + + assertThat(spsData.constraintBytes).isEqualTo(new int[] {0, 0, 0, 0, 0, 0}); + assertThat(spsData.generalLevelIdc).isEqualTo(150); + assertThat(spsData.generalProfileCompatibilityFlags).isEqualTo(6); + assertThat(spsData.generalProfileIdc).isEqualTo(2); + assertThat(spsData.generalProfileSpace).isEqualTo(0); + assertThat(spsData.generalTierFlag).isFalse(); + assertThat(spsData.width).isEqualTo(1080); + assertThat(spsData.height).isEqualTo(1920); + assertThat(spsData.pixelWidthHeightRatio).isEqualTo(1); + assertThat(spsData.seqParameterSetId).isEqualTo(0); + assertThat(spsData.chromaFormatIdc).isEqualTo(1); + assertThat(spsData.bitDepthLumaMinus8).isEqualTo(2); + assertThat(spsData.bitDepthChromaMinus8).isEqualTo(2); + assertThat(spsData.colorSpace).isEqualTo(6); + assertThat(spsData.colorRange).isEqualTo(2); + assertThat(spsData.colorTransfer).isEqualTo(6); + } + private static byte[] buildTestData() { byte[] data = new byte[20]; - for (int i = 0; i < data.length; i++) { - data[i] = (byte) 0xFF; - } + Arrays.fill(data, (byte) 0xFF); // Insert an incomplete NAL unit start code. data[TEST_PARTIAL_NAL_POSITION] = 0; data[TEST_PARTIAL_NAL_POSITION + 1] = 0;