Extend SPS parsing when building the initial MP4 HevcConfig and include the PAR…

Extend SPS parsing when building the initial MP4 HevcConfig and include the PAR for propagating it into the Format
parent afc549fb
...@@ -86,28 +86,11 @@ public final class CodecSpecificDataUtil { ...@@ -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(
......
...@@ -15,6 +15,8 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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.
} }
......
...@@ -33,7 +33,7 @@ public final class NalUnitUtilTest { ...@@ -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 { ...@@ -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();
......
...@@ -1139,6 +1139,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -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);
......
...@@ -200,8 +200,8 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -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 { ...@@ -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();
} }
......
...@@ -243,10 +243,20 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -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 { ...@@ -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)
......
...@@ -66,9 +66,8 @@ public final class AvcConfig { ...@@ -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;
......
...@@ -16,11 +16,11 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
} }
} }
...@@ -183,8 +183,8 @@ import com.google.common.collect.ImmutableMap; ...@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment