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
(cherry picked from commit ddb0f86604)
This commit is contained in:
andrewlewis 2023-07-28 16:00:13 +01:00 committed by Tianyi Feng
parent ab2505d1a9
commit 50db5207cb
3 changed files with 40 additions and 7 deletions

View file

@ -18,6 +18,8 @@
* Audio Offload: * Audio Offload:
* Prepend Ogg ID Header and Comment Header Pages to bitstream for * Prepend Ogg ID Header and Comment Header Pages to bitstream for
offloaded Opus playback in accordance with RFC 7845. offloaded Opus playback in accordance with RFC 7845.
* Video:
* H.265/HEVC: Fix parsing SPS short and long term reference picture info.
* Text: * Text:
* CEA-608: Change cue truncation logic to only consider visible text. * CEA-608: Change cue truncation logic to only consider visible text.
Previously indent and tab offset were included when limiting the cue Previously indent and tab offset were included when limiting the cue

View file

@ -618,8 +618,8 @@ public final class NalUnitUtil {
} }
skipShortTermReferencePictureSets(data); skipShortTermReferencePictureSets(data);
if (data.readBit()) { // long_term_ref_pics_present_flag if (data.readBit()) { // long_term_ref_pics_present_flag
// num_long_term_ref_pics_sps int numLongTermRefPicsSps = data.readUnsignedExpGolombCodedInt();
for (int i = 0; i < data.readUnsignedExpGolombCodedInt(); i++) { for (int i = 0; i < numLongTermRefPicsSps; i++) {
int ltRefPicPocLsbSpsLength = log2MaxPicOrderCntLsbMinus4 + 4; int ltRefPicPocLsbSpsLength = log2MaxPicOrderCntLsbMinus4 + 4;
// lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i] // lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i]
data.skipBits(ltRefPicPocLsbSpsLength + 1); data.skipBits(ltRefPicPocLsbSpsLength + 1);
@ -941,12 +941,14 @@ public final class NalUnitUtil {
numPositivePics = bitArray.readUnsignedExpGolombCodedInt(); numPositivePics = bitArray.readUnsignedExpGolombCodedInt();
deltaPocS0 = new int[numNegativePics]; deltaPocS0 = new int[numNegativePics];
for (int i = 0; i < numNegativePics; i++) { 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] bitArray.skipBit(); // used_by_curr_pic_s0_flag[i]
} }
deltaPocS1 = new int[numPositivePics]; deltaPocS1 = new int[numPositivePics];
for (int i = 0; i < numPositivePics; i++) { 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] bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
} }
} }

View file

@ -202,11 +202,40 @@ public final class NalUnitUtilTest {
assertThat(spsData.colorTransfer).isEqualTo(6); 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() { private static byte[] buildTestData() {
byte[] data = new byte[20]; byte[] data = new byte[20];
for (int i = 0; i < data.length; i++) { Arrays.fill(data, (byte) 0xFF);
data[i] = (byte) 0xFF;
}
// Insert an incomplete NAL unit start code. // Insert an incomplete NAL unit start code.
data[TEST_PARTIAL_NAL_POSITION] = 0; data[TEST_PARTIAL_NAL_POSITION] = 0;
data[TEST_PARTIAL_NAL_POSITION + 1] = 0; data[TEST_PARTIAL_NAL_POSITION + 1] = 0;