From a7b20fd133f8976ca29c91d69a71558ae99f765c Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 11 Jan 2021 14:22:03 +0000 Subject: [PATCH] Rollback of https://github.com/google/ExoPlayer/commit/59aec416afc2305103aa01b3470a024081b3fde9 *** Original commit *** Rollback of https://github.com/google/ExoPlayer/commit/ff8c8645abb1490fe130a9cd5a4bf4576734d140 *** 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 --- RELEASENOTES.md | 10 +- .../util/CodecSpecificDataUtil.java | 45 +++++ .../android/exoplayer2/video/HevcConfig.java | 43 ++++- .../exoplayer2/video/HevcConfigTest.java | 173 ++++++++++++++++++ .../extractor/mkv/MatroskaExtractor.java | 1 + .../exoplayer2/extractor/mp4/AtomParsers.java | 1 + .../exoplayer2/extractor/ts/H265Reader.java | 7 + .../extractordumps/ts/sample_h265.ts.0.dump | 1 + .../extractordumps/ts/sample_h265.ts.1.dump | 1 + .../extractordumps/ts/sample_h265.ts.2.dump | 1 + .../extractordumps/ts/sample_h265.ts.3.dump | 1 + .../ts/sample_h265.ts.unknown_length.dump | 1 + 12 files changed, 276 insertions(+), 9 deletions(-) create mode 100644 library/common/src/test/java/com/google/android/exoplayer2/video/HevcConfigTest.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 781a41be86..7d135dadd3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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)). diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java index 3360e88d4f..0f8edb4acd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java @@ -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 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. * diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java b/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java index 100a824a97..9ef12a5c33 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java @@ -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 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 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 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 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 initializationData, + int nalUnitLengthFieldLength, + @Nullable String codecs) { this.initializationData = initializationData; this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + this.codecs = codecs; } } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/video/HevcConfigTest.java b/library/common/src/test/java/com/google/android/exoplayer2/video/HevcConfigTest.java new file mode 100644 index 0000000000..a4c312e8f7 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/video/HevcConfigTest.java @@ -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); + } +} diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 568385ad3b..53a6fbabea 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -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> pair = diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 9d285fe8dc..ff63798583 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -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) { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index ea23e1ef7a..0ef719c961 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -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) diff --git a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.0.dump b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.0.dump index 1bf1d8af7f..ca03201f88 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.0.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.0.dump @@ -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: diff --git a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.1.dump b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.1.dump index 18dc2ebabe..7f26d0b1e9 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.1.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.1.dump @@ -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: diff --git a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.2.dump b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.2.dump index 1b81fef517..d22d002651 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.2.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.2.dump @@ -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: diff --git a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.3.dump b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.3.dump index 1dd24c870e..68a7e61768 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.3.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.3.dump @@ -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: diff --git a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.unknown_length.dump b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.unknown_length.dump index 09b1103840..920032dd23 100644 --- a/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.unknown_length.dump +++ b/testdata/src/test/assets/extractordumps/ts/sample_h265.ts.unknown_length.dump @@ -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: