Merge #8401: Initialize Format.codecs from HEVC SPS NAL unit (#8393)

Imported from GitHub PR https://github.com/google/ExoPlayer/pull/8401

This will allow ExoPlayer to check if video track's profile and level are supported by decoder when playing progressive media sources.
Merge e582fb91b73c7c95e754167140211d5473c36d14 into 1347d572ef9ce79aacd667cfffa7d7468c8408a4

Issue: #8393
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/ExoPlayer/pull/8401 from equeim:hevc-codecs e582fb91b73c7c95e754167140211d5473c36d14
PiperOrigin-RevId: 350738065
This commit is contained in:
Alexey Rochev 2021-01-08 12:15:10 +00:00 committed by Ian Baker
parent eded1ca106
commit ff8c8645ab
11 changed files with 100 additions and 9 deletions

View File

@ -38,12 +38,16 @@
([#8349](https://github.com/google/ExoPlayer/issues/8349)).
* Add `DefaultHttpDataSource.Factory` and deprecate
`DefaultHttpDataSourceFactory`.
* Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to
allow decoder capability checks based on codec profile/level
([#8393](https://github.com/google/ExoPlayer/issues/8393)).
* Add option to `MergingMediaSource` to clip the durations of all sources
to have the same length
([#8422](https://github.com/google/ExoPlayer/issues/8422)).
* Extractors:
* Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to
allow decoder capability checks based on codec profile/level
([#8393](https://github.com/google/ExoPlayer/issues/8393)).
* Populate codecs string for H.265/HEVC in MP4, Matroska and MPEG-TS
streams to allow decoder capability checks based on codec profile/level
([#8393](https://github.com/google/ExoPlayer/issues/8393)).
* Track selection:
* Allow parallel adaptation for video and audio
([#5111](https://github.com/google/ExoPlayer/issues/5111)).

View File

@ -26,6 +26,8 @@ import java.util.List;
public final class CodecSpecificDataUtil {
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
new String[] {"", "A", "B", "C"};
/**
* Parses an ALAC AudioSpecificConfig (i.e. an <a
@ -83,6 +85,49 @@ public final class CodecSpecificDataUtil {
"avc1.%02X%02X%02X", profileIdc, constraintsFlagsAndReservedZero2Bits, levelIdc);
}
/**
* Returns an RFC 6381 HEVC codec string based on the SPS NAL unit read from the provided bit
* 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) {
// Skip nal_unit_header, sps_video_parameter_set_id, sps_max_sub_layers_minus1 and
// sps_temporal_id_nesting_flag.
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 =
new StringBuilder(
Util.formatInvariant(
"hvc1.%s%d.%X.%c%d",
HEVC_GENERAL_PROFILE_SPACE_STRINGS[generalProfileSpace],
generalProfileIdc,
generalProfileCompatibilityFlags,
generalTierFlag ? 'H' : 'L',
generalLevelIdc));
// Omit trailing zero bytes.
int trailingZeroIndex = constraintBytes.length;
while (trailingZeroIndex > 0 && constraintBytes[trailingZeroIndex - 1] == 0) {
trailingZeroIndex--;
}
for (int i = 0; i < trailingZeroIndex; i++) {
builder.append(String.format(".%02X", constraintBytes[i]));
}
return builder.toString();
}
/**
* Constructs a NAL unit consisting of the NAL start code followed by the specified data.
*

View File

@ -17,8 +17,10 @@ package com.google.android.exoplayer2.video;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import java.util.Collections;
import java.util.List;
@ -27,9 +29,6 @@ import java.util.List;
*/
public final class HevcConfig {
@Nullable public final List<byte[]> initializationData;
public final int nalUnitLengthFieldLength;
/**
* Parses HEVC configuration data.
*
@ -61,8 +60,9 @@ public final class HevcConfig {
data.setPosition(csdStartPosition);
byte[] buffer = new byte[csdLength];
int bufferPosition = 0;
@Nullable String codecs = null;
for (int i = 0; i < numberOfArrays; i++) {
data.skipBytes(1); // completeness (1), nal_unit_type (7)
int nalUnitType = data.readUnsignedByte() & 0x7F; // completeness (1), nal_unit_type (7)
int numberOfNalUnits = data.readUnsignedShort();
for (int j = 0; j < numberOfNalUnits; j++) {
int nalUnitLength = data.readUnsignedShort();
@ -71,21 +71,49 @@ public final class HevcConfig {
bufferPosition += NalUnitUtil.NAL_START_CODE.length;
System.arraycopy(
data.getData(), data.getPosition(), buffer, bufferPosition, nalUnitLength);
if (nalUnitType == SPS_NAL_UNIT_TYPE && j == 0) {
codecs =
CodecSpecificDataUtil.buildHevcCodecStringFromSps(
new ParsableNalUnitBitArray(buffer, bufferPosition, nalUnitLength));
}
bufferPosition += nalUnitLength;
data.skipBytes(nalUnitLength);
}
}
@Nullable
List<byte[]> initializationData = csdLength == 0 ? null : Collections.singletonList(buffer);
return new HevcConfig(initializationData, lengthSizeMinusOne + 1);
return new HevcConfig(initializationData, lengthSizeMinusOne + 1, codecs);
} catch (ArrayIndexOutOfBoundsException e) {
throw new ParserException("Error parsing HEVC config", e);
}
}
private HevcConfig(@Nullable List<byte[]> initializationData, int nalUnitLengthFieldLength) {
private static final int SPS_NAL_UNIT_TYPE = 33;
/**
* List of buffers containing the codec-specific data to be provided to the decoder, or {@code
* null} if not known.
*
* @see com.google.android.exoplayer2.Format#initializationData
*/
@Nullable public final List<byte[]> initializationData;
/** The length of the NAL unit length field in the bitstream's container, in bytes. */
public final int nalUnitLengthFieldLength;
/**
* An RFC 6381 codecs string representing the video format, or {@code null} if not known.
*
* @see com.google.android.exoplayer2.Format#codecs
*/
@Nullable public final String codecs;
private HevcConfig(
@Nullable List<byte[]> initializationData,
int nalUnitLengthFieldLength,
@Nullable String codecs) {
this.initializationData = initializationData;
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
this.codecs = codecs;
}
}

View File

@ -2093,6 +2093,7 @@ public class MatroskaExtractor implements Extractor {
HevcConfig hevcConfig = HevcConfig.parse(new ParsableByteArray(getCodecPrivate(codecId)));
initializationData = hevcConfig.initializationData;
nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength;
codecs = hevcConfig.codecs;
break;
case CODEC_ID_FOURCC:
Pair<String, @NullableType List<byte[]>> pair =

View File

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

View File

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
@ -336,9 +337,15 @@ public final class H265Reader implements ElementaryStreamReader {
}
}
// Parse the SPS to derive an RFC 6381 codecs string.
bitArray.reset(sps.nalData, 0, sps.nalLength);
bitArray.skipBits(24); // Skip start code.
String codecs = CodecSpecificDataUtil.buildHevcCodecStringFromSps(bitArray);
return new Format.Builder()
.setId(formatId)
.setSampleMimeType(MimeTypes.VIDEO_H265)
.setCodecs(codecs)
.setWidth(picWidthInLumaSamples)
.setHeight(picHeightInLumaSamples)
.setPixelWidthHeightRatio(pixelWidthHeightRatio)

View File

@ -12,6 +12,7 @@ track 256:
format 0:
id = 1/256
sampleMimeType = video/hevc
codecs = hvc1.1.6.L90.90
width = 854
height = 480
initializationData:

View File

@ -12,6 +12,7 @@ track 256:
format 0:
id = 1/256
sampleMimeType = video/hevc
codecs = hvc1.1.6.L90.90
width = 854
height = 480
initializationData:

View File

@ -12,6 +12,7 @@ track 256:
format 0:
id = 1/256
sampleMimeType = video/hevc
codecs = hvc1.1.6.L90.90
width = 854
height = 480
initializationData:

View File

@ -12,6 +12,7 @@ track 256:
format 0:
id = 1/256
sampleMimeType = video/hevc
codecs = hvc1.1.6.L90.90
width = 854
height = 480
initializationData:

View File

@ -9,6 +9,7 @@ track 256:
format 0:
id = 1/256
sampleMimeType = video/hevc
codecs = hvc1.1.6.L90.90
width = 854
height = 480
initializationData: