Skip to content

Commit e47f358

Browse files
committed
Use NalUnitUtil.parseH265SpsNalUnitPayload when parsing MPEG-TS files
This implicitly fixes a bug by removing the buggy implementation in H265Reader in favour of a working one. This change also adds tests to confirm the parsing bug is fixed.
1 parent 3e43d6c commit e47f358

File tree

1 file changed

+11
-211
lines changed
  • libraries/extractor/src/main/java/androidx/media3/extractor/ts

1 file changed

+11
-211
lines changed

libraries/extractor/src/main/java/androidx/media3/extractor/ts/H265Reader.java

Lines changed: 11 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -246,229 +246,29 @@ private static Format parseMediaFormat(
246246
System.arraycopy(sps.nalData, 0, csdData, vps.nalLength, sps.nalLength);
247247
System.arraycopy(pps.nalData, 0, csdData, vps.nalLength + sps.nalLength, pps.nalLength);
248248

249-
// Parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1.
250-
ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength);
251-
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
252-
int maxSubLayersMinus1 = bitArray.readBits(3);
253-
bitArray.skipBit(); // sps_temporal_id_nesting_flag
254-
int generalProfileSpace = bitArray.readBits(2);
255-
boolean generalTierFlag = bitArray.readBit();
256-
int generalProfileIdc = bitArray.readBits(5);
257-
int generalProfileCompatibilityFlags = 0;
258-
for (int i = 0; i < 32; i++) {
259-
if (bitArray.readBit()) {
260-
generalProfileCompatibilityFlags |= (1 << i);
261-
}
262-
}
263-
int[] constraintBytes = new int[6];
264-
for (int i = 0; i < constraintBytes.length; ++i) {
265-
constraintBytes[i] = bitArray.readBits(8);
266-
}
267-
int generalLevelIdc = bitArray.readBits(8);
268-
int toSkip = 0;
269-
for (int i = 0; i < maxSubLayersMinus1; i++) {
270-
if (bitArray.readBit()) { // sub_layer_profile_present_flag[i]
271-
toSkip += 89;
272-
}
273-
if (bitArray.readBit()) { // sub_layer_level_present_flag[i]
274-
toSkip += 8;
275-
}
276-
}
277-
bitArray.skipBits(toSkip);
278-
if (maxSubLayersMinus1 > 0) {
279-
bitArray.skipBits(2 * (8 - maxSubLayersMinus1));
280-
}
281-
282-
bitArray.readUnsignedExpGolombCodedInt(); // sps_seq_parameter_set_id
283-
int chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt();
284-
if (chromaFormatIdc == 3) {
285-
bitArray.skipBit(); // separate_colour_plane_flag
286-
}
287-
int picWidthInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
288-
int picHeightInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
289-
if (bitArray.readBit()) { // conformance_window_flag
290-
int confWinLeftOffset = bitArray.readUnsignedExpGolombCodedInt();
291-
int confWinRightOffset = bitArray.readUnsignedExpGolombCodedInt();
292-
int confWinTopOffset = bitArray.readUnsignedExpGolombCodedInt();
293-
int confWinBottomOffset = bitArray.readUnsignedExpGolombCodedInt();
294-
// H.265/HEVC (2014) Table 6-1
295-
int subWidthC = chromaFormatIdc == 1 || chromaFormatIdc == 2 ? 2 : 1;
296-
int subHeightC = chromaFormatIdc == 1 ? 2 : 1;
297-
picWidthInLumaSamples -= subWidthC * (confWinLeftOffset + confWinRightOffset);
298-
picHeightInLumaSamples -= subHeightC * (confWinTopOffset + confWinBottomOffset);
299-
}
300-
bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
301-
bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
302-
int log2MaxPicOrderCntLsbMinus4 = bitArray.readUnsignedExpGolombCodedInt();
303-
// for (i = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1; ...)
304-
for (int i = bitArray.readBit() ? 0 : maxSubLayersMinus1; i <= maxSubLayersMinus1; i++) {
305-
bitArray.readUnsignedExpGolombCodedInt(); // sps_max_dec_pic_buffering_minus1[i]
306-
bitArray.readUnsignedExpGolombCodedInt(); // sps_max_num_reorder_pics[i]
307-
bitArray.readUnsignedExpGolombCodedInt(); // sps_max_latency_increase_plus1[i]
308-
}
309-
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_luma_coding_block_size_minus3
310-
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_coding_block_size
311-
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_luma_transform_block_size_minus2
312-
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_transform_block_size
313-
bitArray.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_inter
314-
bitArray.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_intra
315-
// if (scaling_list_enabled_flag) { if (sps_scaling_list_data_present_flag) {...}}
316-
boolean scalingListEnabled = bitArray.readBit();
317-
if (scalingListEnabled && bitArray.readBit()) {
318-
skipScalingList(bitArray);
319-
}
320-
bitArray.skipBits(2); // amp_enabled_flag (1), sample_adaptive_offset_enabled_flag (1)
321-
if (bitArray.readBit()) { // pcm_enabled_flag
322-
// pcm_sample_bit_depth_luma_minus1 (4), pcm_sample_bit_depth_chroma_minus1 (4)
323-
bitArray.skipBits(8);
324-
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3
325-
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size
326-
bitArray.skipBit(); // pcm_loop_filter_disabled_flag
327-
}
328-
// Skips all short term reference picture sets.
329-
skipShortTermRefPicSets(bitArray);
330-
if (bitArray.readBit()) { // long_term_ref_pics_present_flag
331-
// num_long_term_ref_pics_sps
332-
for (int i = 0; i < bitArray.readUnsignedExpGolombCodedInt(); i++) {
333-
int ltRefPicPocLsbSpsLength = log2MaxPicOrderCntLsbMinus4 + 4;
334-
// lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i]
335-
bitArray.skipBits(ltRefPicPocLsbSpsLength + 1);
336-
}
337-
}
338-
bitArray.skipBits(2); // sps_temporal_mvp_enabled_flag, strong_intra_smoothing_enabled_flag
339-
float pixelWidthHeightRatio = 1;
340-
if (bitArray.readBit()) { // vui_parameters_present_flag
341-
if (bitArray.readBit()) { // aspect_ratio_info_present_flag
342-
int aspectRatioIdc = bitArray.readBits(8);
343-
if (aspectRatioIdc == NalUnitUtil.EXTENDED_SAR) {
344-
int sarWidth = bitArray.readBits(16);
345-
int sarHeight = bitArray.readBits(16);
346-
if (sarWidth != 0 && sarHeight != 0) {
347-
pixelWidthHeightRatio = (float) sarWidth / sarHeight;
348-
}
349-
} else if (aspectRatioIdc < NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) {
350-
pixelWidthHeightRatio = NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc];
351-
} else {
352-
Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc);
353-
}
354-
}
355-
if (bitArray.readBit()) { // overscan_info_present_flag
356-
bitArray.skipBit(); // overscan_appropriate_flag
357-
}
358-
if (bitArray.readBit()) { // video_signal_type_present_flag
359-
bitArray.skipBits(4); // video_format, video_full_range_flag
360-
if (bitArray.readBit()) { // colour_description_present_flag
361-
// colour_primaries, transfer_characteristics, matrix_coeffs
362-
bitArray.skipBits(24);
363-
}
364-
}
365-
if (bitArray.readBit()) { // chroma_loc_info_present_flag
366-
bitArray.readUnsignedExpGolombCodedInt(); // chroma_sample_loc_type_top_field
367-
bitArray.readUnsignedExpGolombCodedInt(); // chroma_sample_loc_type_bottom_field
368-
}
369-
bitArray.skipBit(); // neutral_chroma_indication_flag
370-
if (bitArray.readBit()) { // field_seq_flag
371-
// field_seq_flag equal to 1 indicates that the coded video sequence conveys pictures that
372-
// represent fields, which means that frame height is double the picture height.
373-
picHeightInLumaSamples *= 2;
374-
}
375-
}
249+
NalUnitUtil.H265SpsData spsData =
250+
NalUnitUtil.parseH265SpsNalUnitPayload(sps.nalData, 40/8, sps.nalLength);
376251

377252
String codecs =
378253
CodecSpecificDataUtil.buildHevcCodecString(
379-
generalProfileSpace,
380-
generalTierFlag,
381-
generalProfileIdc,
382-
generalProfileCompatibilityFlags,
383-
constraintBytes,
384-
generalLevelIdc);
254+
spsData.generalProfileSpace,
255+
spsData.generalTierFlag,
256+
spsData.generalProfileIdc,
257+
spsData.generalProfileCompatibilityFlags,
258+
spsData.constraintBytes,
259+
spsData.generalLevelIdc);
385260

386261
return new Format.Builder()
387262
.setId(formatId)
388263
.setSampleMimeType(MimeTypes.VIDEO_H265)
389264
.setCodecs(codecs)
390-
.setWidth(picWidthInLumaSamples)
391-
.setHeight(picHeightInLumaSamples)
392-
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
265+
.setWidth(spsData.width)
266+
.setHeight(spsData.height)
267+
.setPixelWidthHeightRatio(spsData.pixelWidthHeightRatio)
393268
.setInitializationData(Collections.singletonList(csdData))
394269
.build();
395270
}
396271

397-
/** Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */
398-
private static void skipScalingList(ParsableNalUnitBitArray bitArray) {
399-
for (int sizeId = 0; sizeId < 4; sizeId++) {
400-
for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) {
401-
if (!bitArray.readBit()) { // scaling_list_pred_mode_flag[sizeId][matrixId]
402-
// scaling_list_pred_matrix_id_delta[sizeId][matrixId]
403-
bitArray.readUnsignedExpGolombCodedInt();
404-
} else {
405-
int coefNum = min(64, 1 << (4 + (sizeId << 1)));
406-
if (sizeId > 1) {
407-
// scaling_list_dc_coef_minus8[sizeId - 2][matrixId]
408-
bitArray.readSignedExpGolombCodedInt();
409-
}
410-
for (int i = 0; i < coefNum; i++) {
411-
bitArray.readSignedExpGolombCodedInt(); // scaling_list_delta_coef
412-
}
413-
}
414-
}
415-
}
416-
}
417-
418-
/**
419-
* Reads the number of short term reference picture sets in a SPS as ue(v), then skips all of
420-
* them. See H.265/HEVC (2014) 7.3.7 and 7.4.8.
421-
*/
422-
private static void skipShortTermRefPicSets(ParsableNalUnitBitArray bitArray) {
423-
int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt();
424-
boolean interRefPicSetPredictionFlag = false;
425-
int numNegativePics;
426-
int numPositivePics;
427-
// As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous
428-
// one, so we just keep track of that rather than storing the whole array.
429-
// RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS.
430-
int previousNumDeltaPocs = 0;
431-
for (int stRpsIdx = 0; stRpsIdx < numShortTermRefPicSets; stRpsIdx++) {
432-
if (stRpsIdx != 0) {
433-
interRefPicSetPredictionFlag = bitArray.readBit();
434-
}
435-
if (interRefPicSetPredictionFlag) {
436-
boolean deltaRpsSign = bitArray.readBit(); // delta_rps_sign
437-
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
438-
numNegativePics = 0;
439-
numPositivePics = 0;
440-
for (int j = 0; j <= previousNumDeltaPocs; j++) {
441-
if (!bitArray.readBit()) { // used_by_curr_pic_flag[j]
442-
if (!bitArray.readBit()) { // use_delta_flag[j]
443-
continue; // if not use_delta_flag, skip increase numDeltaPocs
444-
}
445-
}
446-
if (deltaRpsSign) {
447-
// See H.265/HEVC (2014) section 7.4.8 equation 7-61
448-
numNegativePics++;
449-
} else {
450-
// See H.265/HEVC (2014) section 7.4.8 equation 7-62
451-
numPositivePics++;
452-
}
453-
}
454-
455-
} else {
456-
numNegativePics = bitArray.readUnsignedExpGolombCodedInt();
457-
numPositivePics = bitArray.readUnsignedExpGolombCodedInt();
458-
for (int i = 0; i < numNegativePics; i++) {
459-
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
460-
bitArray.skipBit(); // used_by_curr_pic_s0_flag[i]
461-
}
462-
for (int i = 0; i < numPositivePics; i++) {
463-
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
464-
bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
465-
}
466-
}
467-
// See H.265/HEVC (2014) section 7.4.8 equation 7-71
468-
previousNumDeltaPocs = numNegativePics + numPositivePics;
469-
}
470-
}
471-
472272
@EnsuresNonNull({"output", "sampleReader"})
473273
private void assertTracksCreated() {
474274
Assertions.checkStateNotNull(output);

0 commit comments

Comments
 (0)