From ddb0f8660489c06ce65d9f4b0256a8b60201920d Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 28 Jul 2023 16:00:13 +0100 Subject: [PATCH] Fix parsing of H.265 sequence parameter sets Fix short term reference picture list parsing. Before this change, `deltaPocS0` was derived by adding one to the value of the syntax element `delta_poc_s0_minus1`, but (maybe surprising) the specification actually says that `DeltaPocS0[stRpsIdx][i]` should be assigned the negation `-(delta_poc_s0_minus1[i] + 1)` on the first iteration, then that value added to the previous value on previous iterations. See equations (7-67) to (7-70) in the 2021-08 version of the H.265/HEVC specification. Also read the number of long term reference pictures once rather than on every loop iteration (subsection 7.3.2.2.1). PiperOrigin-RevId: 551852999 --- RELEASENOTES.md | 1 + .../media3/container/NalUnitUtil.java | 10 +++--- .../media3/container/NalUnitUtilTest.java | 35 +++++++++++++++++-- 3 files changed, 39 insertions(+), 7 deletions(-) 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;