*** Original commit ***

Rollback of ff8c8645ab

*** Original commit ***

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 so...

***

PiperOrigin-RevId: 351139861
This commit is contained in:
andrewlewis 2021-01-11 14:22:03 +00:00 committed by Ian Baker
parent 01ae2b047f
commit a7b20fd133
12 changed files with 276 additions and 9 deletions

View File

@ -38,9 +38,6 @@
([#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)).
@ -49,6 +46,13 @@
creating subtitle media sources from
`MediaItem.playbackProperties.subtitles`
([#8430](https://github.com/google/ExoPlayer/issues/8430)).
* 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,52 @@ 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) {
ParsableNalUnitBitArray bitArray =
new ParsableNalUnitBitArray(
buffer,
/* offset= */ bufferPosition,
/* limit= */ bufferPosition + nalUnitLength);
codecs = CodecSpecificDataUtil.buildHevcCodecStringFromSps(bitArray);
}
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

@ -0,0 +1,173 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.video;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.util.ParsableByteArray;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link HevcConfig}. */
@RunWith(AndroidJUnit4.class)
public final class HevcConfigTest {
private static final byte[] HVCC_BOX_PAYLOAD =
new byte[] {
// Header
1,
1,
96,
0,
0,
0,
-80,
0,
0,
0,
0,
0,
-103,
-16,
0,
-4,
-4,
-8,
-8,
0,
0,
15,
// Number of arrays
3,
// NAL unit type = VPS
32,
// Number of NAL units
0,
1,
// NAL unit length
0,
23,
// NAL unit
64,
1,
12,
1,
-1,
-1,
1,
96,
0,
0,
3,
0,
-80,
0,
0,
3,
0,
0,
3,
0,
-103,
-84,
9,
// NAL unit type = SPS
33,
// Number of NAL units
0,
1,
// NAL unit length
0,
39,
// NAL unit
66,
1,
1,
1,
96,
0,
0,
3,
0,
-80,
0,
0,
3,
0,
0,
3,
0,
-103,
-96,
1,
-32,
32,
2,
32,
124,
78,
90,
-18,
76,
-110,
-22,
86,
10,
12,
12,
5,
-38,
20,
37,
// NAL unit type = PPS
34,
// Number of NAL units
0,
1,
// NAL unit length
0,
14,
// NAL unit
68,
1,
-64,
-29,
15,
8,
-80,
96,
48,
24,
12,
115,
8,
64
};
@Test
public void parseHevcDecoderConfigurationRecord() throws Exception {
ParsableByteArray data = new ParsableByteArray(HVCC_BOX_PAYLOAD);
HevcConfig hevcConfig = HevcConfig.parse(data);
assertThat(hevcConfig.codecs).isEqualTo("hvc1.1.6.L153.B0");
assertThat(hevcConfig.nalUnitLengthFieldLength).isEqualTo(4);
}
}

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: