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(
......
...@@ -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