From e4446c37fb4cb6f021bd5e14c72938f84b4dc53b Mon Sep 17 00:00:00 2001 From: rohks Date: Thu, 16 Mar 2023 11:37:04 +0000 Subject: [PATCH] Add support to fetch `ColorInfo` from `hvcc` box in `AtomParsers` #minor-release PiperOrigin-RevId: 517086016 --- .../exoplayer2/extractor/mp4/AtomParsers.java | 9 ++++ .../android/exoplayer2/util/NalUnitUtil.java | 35 +++++++++++++--- .../android/exoplayer2/video/HevcConfig.java | 41 ++++++++++++++++++- .../exoplayer2/util/NalUnitUtilTest.java | 3 ++ 4 files changed, 81 insertions(+), 7 deletions(-) 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 06d7a8d733..c3f7b53ccd 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 @@ -1208,6 +1208,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; pixelWidthHeightRatio = hevcConfig.pixelWidthHeightRatio; } codecs = hevcConfig.codecs; + // Modify these values only if they have not already been set. If 'Atom.TYPE_colr' atom is + // present, these values may be overridden. + if (colorSpace == Format.NO_VALUE + && colorRange == Format.NO_VALUE + && colorTransfer == Format.NO_VALUE) { + colorSpace = hevcConfig.colorSpace; + colorRange = hevcConfig.colorRange; + colorTransfer = hevcConfig.colorTransfer; + } } 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/util/NalUnitUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java index b2232b0a5e..7f854579e7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java @@ -19,6 +19,8 @@ import static java.lang.Math.min; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.video.ColorInfo; import java.nio.ByteBuffer; import java.util.Arrays; @@ -105,6 +107,9 @@ public final class NalUnitUtil { public final int width; public final int height; public final float pixelWidthHeightRatio; + public final @C.ColorSpace int colorSpace; + public final @C.ColorRange int colorRange; + public final @C.ColorTransfer int colorTransfer; public H265SpsData( int generalProfileSpace, @@ -116,7 +121,10 @@ public final class NalUnitUtil { int seqParameterSetId, int width, int height, - float pixelWidthHeightRatio) { + float pixelWidthHeightRatio, + @C.ColorSpace int colorSpace, + @C.ColorRange int colorRange, + @C.ColorTransfer int colorTransfer) { this.generalProfileSpace = generalProfileSpace; this.generalTierFlag = generalTierFlag; this.generalProfileIdc = generalProfileIdc; @@ -127,6 +135,9 @@ public final class NalUnitUtil { this.width = width; this.height = height; this.pixelWidthHeightRatio = pixelWidthHeightRatio; + this.colorSpace = colorSpace; + this.colorRange = colorRange; + this.colorTransfer = colorTransfer; } } @@ -483,6 +494,10 @@ public final class NalUnitUtil { public static H265SpsData parseH265SpsNalUnitPayload( byte[] nalData, int nalOffset, int nalLimit) { ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit); + // HDR related metadata. + @C.ColorSpace int colorSpace = Format.NO_VALUE; + @C.ColorRange int colorRange = Format.NO_VALUE; + @C.ColorTransfer int colorTransfer = Format.NO_VALUE; data.skipBits(4); // sps_video_parameter_set_id int maxSubLayersMinus1 = data.readBits(3); data.skipBit(); // sps_temporal_id_nesting_flag @@ -589,10 +604,17 @@ public final class NalUnitUtil { data.skipBit(); // overscan_appropriate_flag } if (data.readBit()) { // video_signal_type_present_flag - data.skipBits(4); // video_format, video_full_range_flag + data.skipBits(3); // video_format + boolean fullRangeFlag = data.readBit(); // video_full_range_flag if (data.readBit()) { // colour_description_present_flag - // colour_primaries, transfer_characteristics, matrix_coeffs - data.skipBits(24); + int colorPrimaries = data.readBits(8); // colour_primaries + int transferCharacteristics = data.readBits(8); // transfer_characteristics + data.skipBits(8); // matrix_coeffs + + colorSpace = ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries); + colorRange = fullRangeFlag ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED; + colorTransfer = + ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics); } } if (data.readBit()) { // chroma_loc_info_present_flag @@ -617,7 +639,10 @@ public final class NalUnitUtil { seqParameterSetId, frameWidth, frameHeight, - pixelWidthHeightRatio); + pixelWidthHeightRatio, + colorSpace, + colorRange, + colorTransfer); } /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java b/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java index 6b8e633555..79d16c8347 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.video; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.util.CodecSpecificDataUtil; @@ -60,6 +61,9 @@ public final class HevcConfig { int bufferPosition = 0; int width = Format.NO_VALUE; int height = Format.NO_VALUE; + @C.ColorSpace int colorSpace = Format.NO_VALUE; + @C.ColorRange int colorRange = Format.NO_VALUE; + @C.ColorTransfer int colorTransfer = Format.NO_VALUE; float pixelWidthHeightRatio = 1; @Nullable String codecs = null; for (int i = 0; i < numberOfArrays; i++) { @@ -83,6 +87,9 @@ public final class HevcConfig { buffer, bufferPosition, bufferPosition + nalUnitLength); width = spsData.width; height = spsData.height; + colorSpace = spsData.colorSpace; + colorRange = spsData.colorRange; + colorTransfer = spsData.colorTransfer; pixelWidthHeightRatio = spsData.pixelWidthHeightRatio; codecs = CodecSpecificDataUtil.buildHevcCodecString( @@ -101,7 +108,15 @@ public final class HevcConfig { List initializationData = csdLength == 0 ? Collections.emptyList() : Collections.singletonList(buffer); return new HevcConfig( - initializationData, lengthSizeMinusOne + 1, width, height, pixelWidthHeightRatio, codecs); + initializationData, + lengthSizeMinusOne + 1, + width, + height, + pixelWidthHeightRatio, + codecs, + colorSpace, + colorRange, + colorTransfer); } catch (ArrayIndexOutOfBoundsException e) { throw ParserException.createForMalformedContainer("Error parsing HEVC config", e); } @@ -128,6 +143,22 @@ public final class HevcConfig { /** The pixel width to height ratio. */ public final float pixelWidthHeightRatio; + /** + * The {@link C.ColorSpace} of the video or {@link Format#NO_VALUE} if unknown or not applicable. + */ + public final @C.ColorSpace int colorSpace; + + /** + * The {@link C.ColorRange} of the video or {@link Format#NO_VALUE} if unknown or not applicable. + */ + public final @C.ColorRange int colorRange; + + /** + * The {@link C.ColorTransfer} of the video or {@link Format#NO_VALUE} if unknown or not + * applicable. + */ + public final @C.ColorTransfer int colorTransfer; + /** * An RFC 6381 codecs string representing the video format, or {@code null} if not known. * @@ -141,12 +172,18 @@ public final class HevcConfig { int width, int height, float pixelWidthHeightRatio, - @Nullable String codecs) { + @Nullable String codecs, + @C.ColorSpace int colorSpace, + @C.ColorRange int colorRange, + @C.ColorTransfer int colorTransfer) { this.initializationData = initializationData; this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; this.width = width; this.height = height; this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.codecs = codecs; + this.colorSpace = colorSpace; + this.colorRange = colorRange; + this.colorTransfer = colorTransfer; } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java index 5b7958638c..f4b774bcd3 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java @@ -192,6 +192,9 @@ public final class NalUnitUtilTest { assertThat(spsData.pixelWidthHeightRatio).isEqualTo(1); assertThat(spsData.seqParameterSetId).isEqualTo(0); assertThat(spsData.width).isEqualTo(3840); + assertThat(spsData.colorSpace).isEqualTo(6); + assertThat(spsData.colorRange).isEqualTo(2); + assertThat(spsData.colorTransfer).isEqualTo(6); } private static byte[] buildTestData() {