Extend SPS parsing when building the initial MP4 HevcConfig and include the PAR for propagating it into the Format

This commit is contained in:
Marcus Wichelmann 2021-09-13 10:27:55 +02:00
parent afc549fba4
commit 296074fbea
No known key found for this signature in database
GPG key ID: D9FC1B92E557C80D
9 changed files with 322 additions and 57 deletions

View file

@ -86,28 +86,11 @@ public final class CodecSpecificDataUtil {
} }
/** /**
* Returns an RFC 6381 HEVC codec string based on the SPS NAL unit read from the provided bit * Builds a RFC 6381 HEVC codec string using the provided parameters.
* array. The position of the bit array must be the start of an SPS NALU (nal_unit_header), and
* the position may be modified by this method.
*/ */
public static String buildHevcCodecStringFromSps(ParsableNalUnitBitArray bitArray) { public static String buildHevcCodecString(
// Skip nal_unit_header, sps_video_parameter_set_id, sps_max_sub_layers_minus1 and int generalProfileSpace, boolean generalTierFlag, int generalProfileIdc,
// sps_temporal_id_nesting_flag. int generalProfileCompatibilityFlags, int[] constraintBytes, int generalLevelIdc) {
bitArray.skipBits(16 + 4 + 3 + 1);
int generalProfileSpace = bitArray.readBits(2);
boolean generalTierFlag = bitArray.readBit();
int generalProfileIdc = bitArray.readBits(5);
int generalProfileCompatibilityFlags = 0;
for (int i = 0; i < 32; i++) {
if (bitArray.readBit()) {
generalProfileCompatibilityFlags |= (1 << i);
}
}
int[] constraintBytes = new int[6];
for (int i = 0; i < constraintBytes.length; ++i) {
constraintBytes[i] = bitArray.readBits(8);
}
int generalLevelIdc = bitArray.readBits(8);
StringBuilder builder = StringBuilder builder =
new StringBuilder( new StringBuilder(
Util.formatInvariant( Util.formatInvariant(

View file

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import static java.lang.Math.min;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
@ -24,7 +26,7 @@ public final class NalUnitUtil {
private static final String TAG = "NalUnitUtil"; private static final String TAG = "NalUnitUtil";
/** Holds data parsed from a sequence parameter set NAL unit. */ /** Holds data parsed from a H.264 sequence parameter set NAL unit. */
public static final class SpsData { public static final class SpsData {
public final int profileIdc; public final int profileIdc;
@ -71,6 +73,44 @@ public final class NalUnitUtil {
} }
} }
/** Holds data parsed from a H.265 sequence parameter set NAL unit. */
public static final class H265SpsData {
public final int generalProfileSpace;
public final boolean generalTierFlag;
public final int generalProfileIdc;
public final int generalProfileCompatibilityFlags;
public final int[] constraintBytes;
public final int generalLevelIdc;
public final int seqParameterSetId;
public final int picWidthInLumaSamples;
public final int picHeightInLumaSamples;
public final float pixelWidthHeightRatio;
public H265SpsData(
int generalProfileSpace,
boolean generalTierFlag,
int generalProfileIdc,
int generalProfileCompatibilityFlags,
int[] constraintBytes,
int generalLevelIdc,
int seqParameterSetId,
int picWidthInLumaSamples,
int picHeightInLumaSamples,
float pixelWidthHeightRatio) {
this.generalProfileSpace = generalProfileSpace;
this.generalTierFlag = generalTierFlag;
this.generalProfileIdc = generalProfileIdc;
this.generalProfileCompatibilityFlags = generalProfileCompatibilityFlags;
this.constraintBytes = constraintBytes;
this.generalLevelIdc = generalLevelIdc;
this.seqParameterSetId = seqParameterSetId;
this.picWidthInLumaSamples = picWidthInLumaSamples;
this.picHeightInLumaSamples = picHeightInLumaSamples;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
}
}
/** Holds data parsed from a picture parameter set NAL unit. */ /** Holds data parsed from a picture parameter set NAL unit. */
public static final class PpsData { public static final class PpsData {
@ -252,17 +292,16 @@ public final class NalUnitUtil {
} }
/** /**
* Parses an SPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection * Parses a SPS NAL unit payload using the syntax defined in ITU-T Recommendation H.264 (2013)
* 7.3.2.1.1. * subsection 7.3.2.1.1.
* *
* @param nalData A buffer containing escaped SPS data. * @param nalData A buffer containing escaped SPS data.
* @param nalOffset The offset of the NAL unit header in {@code nalData}. * @param nalOffset The offset of the NAL unit in {@code nalData}.
* @param nalLimit The limit of the NAL unit in {@code nalData}. * @param nalLimit The limit of the NAL unit in {@code nalData}.
* @return A parsed representation of the SPS data. * @return A parsed representation of the SPS data.
*/ */
public static SpsData parseSpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) { public static SpsData parseSpsNalUnitPayload(byte[] nalData, int nalOffset, int nalLimit) {
ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit); ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);
data.skipBits(8); // nal_unit
int profileIdc = data.readBits(8); int profileIdc = data.readBits(8);
int constraintsFlagsAndReservedZero2Bits = data.readBits(8); int constraintsFlagsAndReservedZero2Bits = data.readBits(8);
int levelIdc = data.readBits(8); int levelIdc = data.readBits(8);
@ -387,17 +426,166 @@ public final class NalUnitUtil {
} }
/** /**
* Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection * Parses a H.265 SPS NAL unit payload using the syntax defined in ITU-T Recommendation H.265 (2019)
* 7.3.2.2. * subsection 7.3.2.2.1.
*
* @param nalData A buffer containing escaped SPS data.
* @param nalOffset The offset of the NAL unit in {@code nalData}.
* @param nalLimit The limit of the NAL unit in {@code nalData}.
* @return A parsed representation of the SPS data.
*/
public static H265SpsData parseH265SpsNalUnitPayload(byte[] nalData, int nalOffset, int nalLimit) {
ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);
// Skip sps_video_parameter_set_id.
data.skipBits(8 + 4);
int maxSubLayersMinus1 = data.readBits(3);
data.skipBit(); // sps_temporal_id_nesting_flag
int generalProfileSpace = data.readBits(2);
boolean generalTierFlag = data.readBit();
int generalProfileIdc = data.readBits(5);
int generalProfileCompatibilityFlags = 0;
for (int i = 0; i < 32; i++) {
if (data.readBit()) {
generalProfileCompatibilityFlags |= (1 << i);
}
}
int[] constraintBytes = new int[6];
for (int i = 0; i < constraintBytes.length; ++i) {
constraintBytes[i] = data.readBits(8);
}
int generalLevelIdc = data.readBits(8);
int toSkip = 0;
for (int i = 0; i < maxSubLayersMinus1; i++) {
if (data.readBit()) { // sub_layer_profile_present_flag[i]
toSkip += 89;
}
if (data.readBit()) { // sub_layer_level_present_flag[i]
toSkip += 8;
}
}
data.skipBits(toSkip);
if (maxSubLayersMinus1 > 0) {
data.skipBits(2 * (8 - maxSubLayersMinus1));
}
int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
int chromaFormatIdc = data.readUnsignedExpGolombCodedInt();
if (chromaFormatIdc == 3) {
data.skipBit(); // separate_colour_plane_flag
}
int picWidthInLumaSamples = data.readUnsignedExpGolombCodedInt();
int picHeightInLumaSamples = data.readUnsignedExpGolombCodedInt();
if (data.readBit()) { // conformance_window_flag
int confWinLeftOffset = data.readUnsignedExpGolombCodedInt();
int confWinRightOffset = data.readUnsignedExpGolombCodedInt();
int confWinTopOffset = data.readUnsignedExpGolombCodedInt();
int confWinBottomOffset = data.readUnsignedExpGolombCodedInt();
// H.265/HEVC (2014) Table 6-1
int subWidthC = chromaFormatIdc == 1 || chromaFormatIdc == 2 ? 2 : 1;
int subHeightC = chromaFormatIdc == 1 ? 2 : 1;
picWidthInLumaSamples -= subWidthC * (confWinLeftOffset + confWinRightOffset);
picHeightInLumaSamples -= subHeightC * (confWinTopOffset + confWinBottomOffset);
}
data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
int log2MaxPicOrderCntLsbMinus4 = data.readUnsignedExpGolombCodedInt();
// for (i = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1; ...)
for (int i = data.readBit() ? 0 : maxSubLayersMinus1; i <= maxSubLayersMinus1; i++) {
data.readUnsignedExpGolombCodedInt(); // sps_max_dec_pic_buffering_minus1[i]
data.readUnsignedExpGolombCodedInt(); // sps_max_num_reorder_pics[i]
data.readUnsignedExpGolombCodedInt(); // sps_max_latency_increase_plus1[i]
}
data.readUnsignedExpGolombCodedInt(); // log2_min_luma_coding_block_size_minus3
data.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_coding_block_size
data.readUnsignedExpGolombCodedInt(); // log2_min_luma_transform_block_size_minus2
data.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_luma_transform_block_size
data.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_inter
data.readUnsignedExpGolombCodedInt(); // max_transform_hierarchy_depth_intra
// if (scaling_list_enabled_flag) { if (sps_scaling_list_data_present_flag) {...}}
boolean scalingListEnabled = data.readBit();
if (scalingListEnabled && data.readBit()) {
skipH265ScalingList(data);
}
data.skipBits(2); // amp_enabled_flag (1), sample_adaptive_offset_enabled_flag (1)
if (data.readBit()) { // pcm_enabled_flag
// pcm_sample_bit_depth_luma_minus1 (4), pcm_sample_bit_depth_chroma_minus1 (4)
data.skipBits(8);
data.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3
data.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size
data.skipBit(); // pcm_loop_filter_disabled_flag
}
// Skips all short term reference picture sets.
skipShortTermRefPicSets(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 ltRefPicPocLsbSpsLength = log2MaxPicOrderCntLsbMinus4 + 4;
// lt_ref_pic_poc_lsb_sps[i], used_by_curr_pic_lt_sps_flag[i]
data.skipBits(ltRefPicPocLsbSpsLength + 1);
}
}
data.skipBits(2); // sps_temporal_mvp_enabled_flag, strong_intra_smoothing_enabled_flag
float pixelWidthHeightRatio = 1;
if (data.readBit()) { // vui_parameters_present_flag
if (data.readBit()) { // aspect_ratio_info_present_flag
int aspectRatioIdc = data.readBits(8);
if (aspectRatioIdc == NalUnitUtil.EXTENDED_SAR) {
int sarWidth = data.readBits(16);
int sarHeight = data.readBits(16);
if (sarWidth != 0 && sarHeight != 0) {
pixelWidthHeightRatio = (float) sarWidth / sarHeight;
}
} else if (aspectRatioIdc < NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) {
pixelWidthHeightRatio = NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc];
} else {
Log.w(TAG, "Unexpected aspect_ratio_idc value: " + aspectRatioIdc);
}
}
if (data.readBit()) { // overscan_info_present_flag
data.skipBit(); // overscan_appropriate_flag
}
if (data.readBit()) { // video_signal_type_present_flag
data.skipBits(4); // video_format, video_full_range_flag
if (data.readBit()) { // colour_description_present_flag
// colour_primaries, transfer_characteristics, matrix_coeffs
data.skipBits(24);
}
}
if (data.readBit()) { // chroma_loc_info_present_flag
data.readUnsignedExpGolombCodedInt(); // chroma_sample_loc_type_top_field
data.readUnsignedExpGolombCodedInt(); // chroma_sample_loc_type_bottom_field
}
data.skipBit(); // neutral_chroma_indication_flag
if (data.readBit()) { // field_seq_flag
// field_seq_flag equal to 1 indicates that the coded video sequence conveys pictures that
// represent fields, which means that frame height is double the picture height.
picHeightInLumaSamples *= 2;
}
}
return new H265SpsData(
generalProfileSpace,
generalTierFlag,
generalProfileIdc,
generalProfileCompatibilityFlags,
constraintBytes,
generalLevelIdc,
seqParameterSetId,
picWidthInLumaSamples,
picHeightInLumaSamples,
pixelWidthHeightRatio);
}
/**
* Parses a PPS NAL unit payload using the syntax defined in ITU-T Recommendation H.264 (2013)
* subsection 7.3.2.2.
* *
* @param nalData A buffer containing escaped PPS data. * @param nalData A buffer containing escaped PPS data.
* @param nalOffset The offset of the NAL unit header in {@code nalData}. * @param nalOffset The offset of the NAL unit in {@code nalData}.
* @param nalLimit The limit of the NAL unit in {@code nalData}. * @param nalLimit The limit of the NAL unit in {@code nalData}.
* @return A parsed representation of the PPS data. * @return A parsed representation of the PPS data.
*/ */
public static PpsData parsePpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) { public static PpsData parsePpsNalUnitPayload(byte[] nalData, int nalOffset, int nalLimit) {
ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit); ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);
data.skipBits(8); // nal_unit
int picParameterSetId = data.readUnsignedExpGolombCodedInt(); int picParameterSetId = data.readUnsignedExpGolombCodedInt();
int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
data.skipBit(); // entropy_coding_mode_flag data.skipBit(); // entropy_coding_mode_flag
@ -516,6 +704,63 @@ public final class NalUnitUtil {
} }
} }
private static void skipH265ScalingList(ParsableNalUnitBitArray bitArray) {
for (int sizeId = 0; sizeId < 4; sizeId++) {
for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) {
if (!bitArray.readBit()) { // scaling_list_pred_mode_flag[sizeId][matrixId]
// scaling_list_pred_matrix_id_delta[sizeId][matrixId]
bitArray.readUnsignedExpGolombCodedInt();
} else {
int coefNum = min(64, 1 << (4 + (sizeId << 1)));
if (sizeId > 1) {
// scaling_list_dc_coef_minus8[sizeId - 2][matrixId]
bitArray.readSignedExpGolombCodedInt();
}
for (int i = 0; i < coefNum; i++) {
bitArray.readSignedExpGolombCodedInt(); // scaling_list_delta_coef
}
}
}
}
}
private static void skipShortTermRefPicSets(ParsableNalUnitBitArray bitArray) {
int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt();
boolean interRefPicSetPredictionFlag = false;
int numNegativePics;
int numPositivePics;
// As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous
// one, so we just keep track of that rather than storing the whole array.
// RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS.
int previousNumDeltaPocs = 0;
for (int stRpsIdx = 0; stRpsIdx < numShortTermRefPicSets; stRpsIdx++) {
if (stRpsIdx != 0) {
interRefPicSetPredictionFlag = bitArray.readBit();
}
if (interRefPicSetPredictionFlag) {
bitArray.skipBit(); // delta_rps_sign
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
for (int j = 0; j <= previousNumDeltaPocs; j++) {
if (bitArray.readBit()) { // used_by_curr_pic_flag[j]
bitArray.skipBit(); // use_delta_flag[j]
}
}
} else {
numNegativePics = bitArray.readUnsignedExpGolombCodedInt();
numPositivePics = bitArray.readUnsignedExpGolombCodedInt();
previousNumDeltaPocs = numNegativePics + numPositivePics;
for (int i = 0; i < numNegativePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
bitArray.skipBit(); // used_by_curr_pic_s0_flag[i]
}
for (int i = 0; i < numPositivePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
}
}
}
}
private NalUnitUtil() { private NalUnitUtil() {
// Prevent instantiation. // Prevent instantiation.
} }

View file

@ -33,7 +33,7 @@ public final class NalUnitUtilTest {
createByteArray( createByteArray(
0x00, 0x00, 0x01, 0x67, 0x4D, 0x40, 0x16, 0xEC, 0xA0, 0x50, 0x17, 0xFC, 0xB8, 0x08, 0x80, 0x00, 0x00, 0x01, 0x67, 0x4D, 0x40, 0x16, 0xEC, 0xA0, 0x50, 0x17, 0xFC, 0xB8, 0x08, 0x80,
0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0F, 0x47, 0x8B, 0x16, 0xCB); 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0F, 0x47, 0x8B, 0x16, 0xCB);
private static final int SPS_TEST_DATA_OFFSET = 3; private static final int SPS_TEST_DATA_OFFSET = 4;
@Test @Test
public void findNalUnit() { public void findNalUnit() {
@ -121,9 +121,10 @@ public final class NalUnitUtilTest {
} }
@Test @Test
public void parseSpsNalUnit() { public void parseSpsNalUnitPayload() {
NalUnitUtil.SpsData data = NalUnitUtil.SpsData data =
NalUnitUtil.parseSpsNalUnit(SPS_TEST_DATA, SPS_TEST_DATA_OFFSET, SPS_TEST_DATA.length); NalUnitUtil.parseSpsNalUnitPayload(
SPS_TEST_DATA, SPS_TEST_DATA_OFFSET, SPS_TEST_DATA.length);
assertThat(data.width).isEqualTo(640); assertThat(data.width).isEqualTo(640);
assertThat(data.height).isEqualTo(360); assertThat(data.height).isEqualTo(360);
assertThat(data.deltaPicOrderAlwaysZeroFlag).isFalse(); assertThat(data.deltaPicOrderAlwaysZeroFlag).isFalse();

View file

@ -1139,6 +1139,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
HevcConfig hevcConfig = HevcConfig.parse(parent); HevcConfig hevcConfig = HevcConfig.parse(parent);
initializationData = hevcConfig.initializationData; initializationData = hevcConfig.initializationData;
out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength;
if (!pixelWidthHeightRatioFromPasp) {
pixelWidthHeightRatio = hevcConfig.pixelWidthHeightRatio;
}
codecs = hevcConfig.codecs; codecs = hevcConfig.codecs;
} else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) { } else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) {
@Nullable DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent); @Nullable DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent);

View file

@ -200,8 +200,8 @@ public final class H264Reader implements ElementaryStreamReader {
List<byte[]> initializationData = new ArrayList<>(); List<byte[]> initializationData = new ArrayList<>();
initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength)); initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength)); initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnitPayload(sps.nalData, 4, sps.nalLength);
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength); NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnitPayload(pps.nalData, 4, pps.nalLength);
String codecs = String codecs =
CodecSpecificDataUtil.buildAvcCodecString( CodecSpecificDataUtil.buildAvcCodecString(
spsData.profileIdc, spsData.profileIdc,
@ -224,11 +224,11 @@ public final class H264Reader implements ElementaryStreamReader {
pps.reset(); pps.reset();
} }
} else if (sps.isCompleted()) { } else if (sps.isCompleted()) {
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnitPayload(sps.nalData, 4, sps.nalLength);
sampleReader.putSps(spsData); sampleReader.putSps(spsData);
sps.reset(); sps.reset();
} else if (pps.isCompleted()) { } else if (pps.isCompleted()) {
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength); NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnitPayload(pps.nalData, 4, pps.nalLength);
sampleReader.putPps(ppsData); sampleReader.putPps(ppsData);
pps.reset(); pps.reset();
} }

View file

@ -243,10 +243,20 @@ public final class H265Reader implements ElementaryStreamReader {
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
int maxSubLayersMinus1 = bitArray.readBits(3); int maxSubLayersMinus1 = bitArray.readBits(3);
bitArray.skipBit(); // sps_temporal_id_nesting_flag bitArray.skipBit(); // sps_temporal_id_nesting_flag
int generalProfileSpace = bitArray.readBits(2);
// profile_tier_level(1, sps_max_sub_layers_minus1) boolean generalTierFlag = bitArray.readBit();
bitArray.skipBits(88); // if (profilePresentFlag) {...} int generalProfileIdc = bitArray.readBits(5);
bitArray.skipBits(8); // general_level_idc int generalProfileCompatibilityFlags = 0;
for (int i = 0; i < 32; i++) {
if (bitArray.readBit()) {
generalProfileCompatibilityFlags |= (1 << i);
}
}
int[] constraintBytes = new int[6];
for (int i = 0; i < constraintBytes.length; ++i) {
constraintBytes[i] = bitArray.readBits(8);
}
int generalLevelIdc = bitArray.readBits(8);
int toSkip = 0; int toSkip = 0;
for (int i = 0; i < maxSubLayersMinus1; i++) { for (int i = 0; i < maxSubLayersMinus1; i++) {
if (bitArray.readBit()) { // sub_layer_profile_present_flag[i] if (bitArray.readBit()) { // sub_layer_profile_present_flag[i]
@ -359,7 +369,9 @@ public final class H265Reader implements ElementaryStreamReader {
// Parse the SPS to derive an RFC 6381 codecs string. // Parse the SPS to derive an RFC 6381 codecs string.
bitArray.reset(sps.nalData, 0, sps.nalLength); bitArray.reset(sps.nalData, 0, sps.nalLength);
bitArray.skipBits(24); // Skip start code. bitArray.skipBits(24); // Skip start code.
String codecs = CodecSpecificDataUtil.buildHevcCodecStringFromSps(bitArray); String codecs = CodecSpecificDataUtil.buildHevcCodecString(
generalProfileSpace, generalTierFlag, generalProfileIdc,
generalProfileCompatibilityFlags, constraintBytes, generalLevelIdc);
return new Format.Builder() return new Format.Builder()
.setId(formatId) .setId(formatId)

View file

@ -66,9 +66,8 @@ public final class AvcConfig {
@Nullable String codecs = null; @Nullable String codecs = null;
if (numSequenceParameterSets > 0) { if (numSequenceParameterSets > 0) {
byte[] sps = initializationData.get(0); byte[] sps = initializationData.get(0);
SpsData spsData = SpsData spsData = NalUnitUtil.parseSpsNalUnitPayload(sps,
NalUnitUtil.parseSpsNalUnit( nalUnitLengthFieldLength + 1, sps.length);
initializationData.get(0), nalUnitLengthFieldLength, sps.length);
width = spsData.width; width = spsData.width;
height = spsData.height; height = spsData.height;
pixelWidthAspectRatio = spsData.pixelWidthAspectRatio; pixelWidthAspectRatio = spsData.pixelWidthAspectRatio;

View file

@ -16,11 +16,11 @@
package com.google.android.exoplayer2.video; package com.google.android.exoplayer2.video;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -58,6 +58,9 @@ public final class HevcConfig {
data.setPosition(csdStartPosition); data.setPosition(csdStartPosition);
byte[] buffer = new byte[csdLength]; byte[] buffer = new byte[csdLength];
int bufferPosition = 0; int bufferPosition = 0;
int width = Format.NO_VALUE;
int height = Format.NO_VALUE;
float pixelWidthAspectRatio = 1;
@Nullable String codecs = null; @Nullable String codecs = null;
for (int i = 0; i < numberOfArrays; i++) { for (int i = 0; i < numberOfArrays; i++) {
int nalUnitType = data.readUnsignedByte() & 0x7F; // completeness (1), nal_unit_type (7) int nalUnitType = data.readUnsignedByte() & 0x7F; // completeness (1), nal_unit_type (7)
@ -74,12 +77,16 @@ public final class HevcConfig {
System.arraycopy( System.arraycopy(
data.getData(), data.getPosition(), buffer, bufferPosition, nalUnitLength); data.getData(), data.getPosition(), buffer, bufferPosition, nalUnitLength);
if (nalUnitType == SPS_NAL_UNIT_TYPE && j == 0) { if (nalUnitType == SPS_NAL_UNIT_TYPE && j == 0) {
ParsableNalUnitBitArray bitArray = NalUnitUtil.H265SpsData spsData =
new ParsableNalUnitBitArray( NalUnitUtil.parseH265SpsNalUnitPayload(
buffer, buffer, bufferPosition + 1, bufferPosition + nalUnitLength);
/* offset= */ bufferPosition, width = spsData.picWidthInLumaSamples;
/* limit= */ bufferPosition + nalUnitLength); height = spsData.picHeightInLumaSamples;
codecs = CodecSpecificDataUtil.buildHevcCodecStringFromSps(bitArray); pixelWidthAspectRatio = spsData.pixelWidthHeightRatio;
codecs = CodecSpecificDataUtil.buildHevcCodecString(spsData.generalProfileSpace,
spsData.generalTierFlag, spsData.generalProfileIdc,
spsData.generalProfileCompatibilityFlags, spsData.constraintBytes,
spsData.generalLevelIdc);
} }
bufferPosition += nalUnitLength; bufferPosition += nalUnitLength;
data.skipBytes(nalUnitLength); data.skipBytes(nalUnitLength);
@ -88,7 +95,13 @@ public final class HevcConfig {
@Nullable @Nullable
List<byte[]> initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); List<byte[]> initializationData = csdLength == 0 ? null : Collections.singletonList(buffer);
return new HevcConfig(initializationData, lengthSizeMinusOne + 1, codecs); return new HevcConfig(
initializationData,
lengthSizeMinusOne + 1,
width,
height,
pixelWidthAspectRatio,
codecs);
} catch (ArrayIndexOutOfBoundsException e) { } catch (ArrayIndexOutOfBoundsException e) {
throw ParserException.createForMalformedContainer("Error parsing HEVC config", e); throw ParserException.createForMalformedContainer("Error parsing HEVC config", e);
} }
@ -105,6 +118,9 @@ public final class HevcConfig {
@Nullable public final List<byte[]> initializationData; @Nullable public final List<byte[]> initializationData;
/** The length of the NAL unit length field in the bitstream's container, in bytes. */ /** The length of the NAL unit length field in the bitstream's container, in bytes. */
public final int nalUnitLengthFieldLength; public final int nalUnitLengthFieldLength;
public final int width;
public final int height;
public final float pixelWidthHeightRatio;
/** /**
* An RFC 6381 codecs string representing the video format, or {@code null} if not known. * An RFC 6381 codecs string representing the video format, or {@code null} if not known.
* *
@ -115,9 +131,15 @@ public final class HevcConfig {
private HevcConfig( private HevcConfig(
@Nullable List<byte[]> initializationData, @Nullable List<byte[]> initializationData,
int nalUnitLengthFieldLength, int nalUnitLengthFieldLength,
int width,
int height,
float pixelWidthAspectRatio,
@Nullable String codecs) { @Nullable String codecs) {
this.initializationData = initializationData; this.initializationData = initializationData;
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
this.width = width;
this.height = height;
this.pixelWidthHeightRatio = pixelWidthAspectRatio;
this.codecs = codecs; this.codecs = codecs;
} }
} }

View file

@ -183,8 +183,8 @@ import com.google.common.collect.ImmutableMap;
// Process SPS (Sequence Parameter Set). // Process SPS (Sequence Parameter Set).
byte[] spsNalDataWithStartCode = initializationData.get(0); byte[] spsNalDataWithStartCode = initializationData.get(0);
NalUnitUtil.SpsData spsData = NalUnitUtil.SpsData spsData =
NalUnitUtil.parseSpsNalUnit( NalUnitUtil.parseSpsNalUnitPayload(
spsNalDataWithStartCode, NAL_START_CODE.length, spsNalDataWithStartCode.length); spsNalDataWithStartCode, NAL_START_CODE.length + 1, spsNalDataWithStartCode.length);
formatBuilder.setPixelWidthHeightRatio(spsData.pixelWidthAspectRatio); formatBuilder.setPixelWidthHeightRatio(spsData.pixelWidthAspectRatio);
formatBuilder.setHeight(spsData.height); formatBuilder.setHeight(spsData.height);
formatBuilder.setWidth(spsData.width); formatBuilder.setWidth(spsData.width);