Fix parsing H265 short term reference picture sets
Issue: google/ExoPlayer#10316 PiperOrigin-RevId: 456084302
This commit is contained in:
parent
f5d8800d51
commit
6dc85dc241
@ -5,6 +5,8 @@
|
|||||||
* Extractors:
|
* Extractors:
|
||||||
* Add support for AVI
|
* Add support for AVI
|
||||||
([#2092](https://github.com/google/ExoPlayer/issues/2092)).
|
([#2092](https://github.com/google/ExoPlayer/issues/2092)).
|
||||||
|
* Fix parsing of H265 short term reference picture sets
|
||||||
|
([#10316](https://github.com/google/ExoPlayer/issues/10316)).
|
||||||
* RTSP:
|
* RTSP:
|
||||||
* Add RTP reader for H263
|
* Add RTP reader for H263
|
||||||
([#63](https://github.com/androidx/media/pull/63)).
|
([#63](https://github.com/androidx/media/pull/63)).
|
||||||
|
@ -18,6 +18,7 @@ package androidx.media3.extractor;
|
|||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
@ -786,40 +787,105 @@ public final class NalUnitUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips any short term reference picture sets contained in a SPS.
|
||||||
|
*
|
||||||
|
* <p>Note: The st_ref_pic_set parsing in this method is simplified for the case where they're
|
||||||
|
* contained in a SPS, and would need generalizing for use elsewhere.
|
||||||
|
*/
|
||||||
private static void skipShortTermReferencePictureSets(ParsableNalUnitBitArray bitArray) {
|
private static void skipShortTermReferencePictureSets(ParsableNalUnitBitArray bitArray) {
|
||||||
int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt();
|
int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt();
|
||||||
boolean interRefPicSetPredictionFlag = false;
|
// As this method applies in a SPS, each short term reference picture set only accesses data
|
||||||
int numNegativePics;
|
// from the previous one. This is because RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1), and
|
||||||
int numPositivePics;
|
// delta_idx_minus1 is always zero in a SPS. Hence we just keep track of variables from the
|
||||||
// As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous
|
// previous one as we iterate.
|
||||||
// one, so we just keep track of that rather than storing the whole array.
|
int previousNumNegativePics = C.INDEX_UNSET;
|
||||||
// RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS.
|
int previousNumPositivePics = C.INDEX_UNSET;
|
||||||
int previousNumDeltaPocs = 0;
|
int[] previousDeltaPocS0 = new int[0];
|
||||||
|
int[] previousDeltaPocS1 = new int[0];
|
||||||
for (int stRpsIdx = 0; stRpsIdx < numShortTermRefPicSets; stRpsIdx++) {
|
for (int stRpsIdx = 0; stRpsIdx < numShortTermRefPicSets; stRpsIdx++) {
|
||||||
if (stRpsIdx != 0) {
|
int numNegativePics;
|
||||||
interRefPicSetPredictionFlag = bitArray.readBit();
|
int numPositivePics;
|
||||||
}
|
int[] deltaPocS0;
|
||||||
|
int[] deltaPocS1;
|
||||||
|
|
||||||
|
boolean interRefPicSetPredictionFlag = stRpsIdx != 0 && bitArray.readBit();
|
||||||
if (interRefPicSetPredictionFlag) {
|
if (interRefPicSetPredictionFlag) {
|
||||||
bitArray.skipBit(); // delta_rps_sign
|
int previousNumDeltaPocs = previousNumNegativePics + previousNumPositivePics;
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
|
|
||||||
|
int deltaRpsSign = bitArray.readBit() ? 1 : 0;
|
||||||
|
int absDeltaRps = bitArray.readUnsignedExpGolombCodedInt() + 1;
|
||||||
|
int deltaRps = (1 - 2 * deltaRpsSign) * absDeltaRps;
|
||||||
|
|
||||||
|
boolean[] useDeltaFlags = new boolean[previousNumDeltaPocs + 1];
|
||||||
for (int j = 0; j <= previousNumDeltaPocs; j++) {
|
for (int j = 0; j <= previousNumDeltaPocs; j++) {
|
||||||
if (!bitArray.readBit()) { // used_by_curr_pic_flag[j]
|
if (!bitArray.readBit()) { // used_by_curr_pic_flag[j]
|
||||||
bitArray.skipBit(); // use_delta_flag[j]
|
useDeltaFlags[j] = bitArray.readBit();
|
||||||
|
} else {
|
||||||
|
// When use_delta_flag[j] is not present, its value is 1.
|
||||||
|
useDeltaFlags[j] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derive numNegativePics, numPositivePics, deltaPocS0 and deltaPocS1 as per Rec. ITU-T
|
||||||
|
// H.265 v6 (06/2019) Section 7.4.8
|
||||||
|
int i = 0;
|
||||||
|
deltaPocS0 = new int[previousNumDeltaPocs + 1];
|
||||||
|
deltaPocS1 = new int[previousNumDeltaPocs + 1];
|
||||||
|
for (int j = previousNumPositivePics - 1; j >= 0; j--) {
|
||||||
|
int dPoc = previousDeltaPocS1[j] + deltaRps;
|
||||||
|
if (dPoc < 0 && useDeltaFlags[previousNumNegativePics + j]) {
|
||||||
|
deltaPocS0[i++] = dPoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deltaRps < 0 && useDeltaFlags[previousNumDeltaPocs]) {
|
||||||
|
deltaPocS0[i++] = deltaRps;
|
||||||
|
}
|
||||||
|
for (int j = 0; j < previousNumNegativePics; j++) {
|
||||||
|
int dPoc = previousDeltaPocS0[j] + deltaRps;
|
||||||
|
if (dPoc < 0 && useDeltaFlags[j]) {
|
||||||
|
deltaPocS0[i++] = dPoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numNegativePics = i;
|
||||||
|
deltaPocS0 = Arrays.copyOf(deltaPocS0, numNegativePics);
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (int j = previousNumNegativePics - 1; j >= 0; j--) {
|
||||||
|
int dPoc = previousDeltaPocS0[j] + deltaRps;
|
||||||
|
if (dPoc > 0 && useDeltaFlags[j]) {
|
||||||
|
deltaPocS1[i++] = dPoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deltaRps > 0 && useDeltaFlags[previousNumDeltaPocs]) {
|
||||||
|
deltaPocS1[i++] = deltaRps;
|
||||||
|
}
|
||||||
|
for (int j = 0; j < previousNumPositivePics; j++) {
|
||||||
|
int dPoc = previousDeltaPocS1[j] + deltaRps;
|
||||||
|
if (dPoc > 0 && useDeltaFlags[previousNumNegativePics + j]) {
|
||||||
|
deltaPocS1[i++] = dPoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numPositivePics = i;
|
||||||
|
deltaPocS1 = Arrays.copyOf(deltaPocS1, numPositivePics);
|
||||||
} else {
|
} else {
|
||||||
numNegativePics = bitArray.readUnsignedExpGolombCodedInt();
|
numNegativePics = bitArray.readUnsignedExpGolombCodedInt();
|
||||||
numPositivePics = bitArray.readUnsignedExpGolombCodedInt();
|
numPositivePics = bitArray.readUnsignedExpGolombCodedInt();
|
||||||
previousNumDeltaPocs = numNegativePics + numPositivePics;
|
deltaPocS0 = new int[numNegativePics];
|
||||||
for (int i = 0; i < numNegativePics; i++) {
|
for (int i = 0; i < numNegativePics; i++) {
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
|
deltaPocS0[i] = 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];
|
||||||
for (int i = 0; i < numPositivePics; i++) {
|
for (int i = 0; i < numPositivePics; i++) {
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
|
deltaPocS1[i] = bitArray.readUnsignedExpGolombCodedInt() + 1;
|
||||||
bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
|
bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
previousNumNegativePics = numNegativePics;
|
||||||
|
previousNumPositivePics = numPositivePics;
|
||||||
|
previousDeltaPocS0 = deltaPocS0;
|
||||||
|
previousDeltaPocS1 = deltaPocS1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +170,32 @@ public final class NalUnitUtilTest {
|
|||||||
assertDiscardToSpsMatchesExpected("FF00000001660000000167FF", "0000000167FF");
|
assertDiscardToSpsMatchesExpected("FF00000001660000000167FF", "0000000167FF");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Regression test for https://github.com/google/ExoPlayer/issues/10316. */
|
||||||
|
@Test
|
||||||
|
public void parseH265SpsNalUnitPayload_exoghi_10316() {
|
||||||
|
byte[] spsNalUnitPayload =
|
||||||
|
new byte[] {
|
||||||
|
1, 2, 32, 0, 0, 3, 0, -112, 0, 0, 3, 0, 0, 3, 0, -106, -96, 1, -32, 32, 2, 28, 77, -98,
|
||||||
|
87, -110, 66, -111, -123, 22, 74, -86, -53, -101, -98, -68, -28, 9, 119, -21, -103, 120,
|
||||||
|
-16, 22, -95, 34, 1, 54, -62, 0, 0, 7, -46, 0, 0, -69, -127, -12, 85, -17, 126, 0, -29,
|
||||||
|
-128, 28, 120, 1, -57, 0, 56, -15
|
||||||
|
};
|
||||||
|
|
||||||
|
NalUnitUtil.H265SpsData spsData =
|
||||||
|
NalUnitUtil.parseH265SpsNalUnitPayload(spsNalUnitPayload, 0, spsNalUnitPayload.length);
|
||||||
|
|
||||||
|
assertThat(spsData.constraintBytes).isEqualTo(new int[] {144, 0, 0, 0, 0, 0});
|
||||||
|
assertThat(spsData.generalLevelIdc).isEqualTo(150);
|
||||||
|
assertThat(spsData.generalProfileCompatibilityFlags).isEqualTo(4);
|
||||||
|
assertThat(spsData.generalProfileIdc).isEqualTo(2);
|
||||||
|
assertThat(spsData.generalProfileSpace).isEqualTo(0);
|
||||||
|
assertThat(spsData.generalTierFlag).isFalse();
|
||||||
|
assertThat(spsData.height).isEqualTo(2160);
|
||||||
|
assertThat(spsData.pixelWidthHeightRatio).isEqualTo(1);
|
||||||
|
assertThat(spsData.seqParameterSetId).isEqualTo(0);
|
||||||
|
assertThat(spsData.width).isEqualTo(3840);
|
||||||
|
}
|
||||||
|
|
||||||
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++) {
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user