diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/HevcConfig.java b/libraries/extractor/src/main/java/androidx/media3/extractor/HevcConfig.java index cbe14e9b16..ad2c0851dc 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/HevcConfig.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/HevcConfig.java @@ -16,6 +16,7 @@ package androidx.media3.extractor; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.ParserException; import androidx.media3.common.util.CodecSpecificDataUtil; @@ -61,6 +62,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++) { @@ -84,6 +88,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( @@ -102,7 +109,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); } @@ -129,6 +144,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. * @@ -142,12 +173,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/libraries/extractor/src/main/java/androidx/media3/extractor/NalUnitUtil.java b/libraries/extractor/src/main/java/androidx/media3/extractor/NalUnitUtil.java index 2a401cbbae..354b59f666 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/NalUnitUtil.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/NalUnitUtil.java @@ -19,6 +19,8 @@ import static java.lang.Math.min; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; +import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Log; @@ -110,6 +112,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, @@ -121,7 +126,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; @@ -132,6 +140,9 @@ public final class NalUnitUtil { this.width = width; this.height = height; this.pixelWidthHeightRatio = pixelWidthHeightRatio; + this.colorSpace = colorSpace; + this.colorRange = colorRange; + this.colorTransfer = colorTransfer; } } @@ -488,6 +499,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 @@ -594,10 +609,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 @@ -622,7 +644,10 @@ public final class NalUnitUtil { seqParameterSetId, frameWidth, frameHeight, - pixelWidthHeightRatio); + pixelWidthHeightRatio, + colorSpace, + colorRange, + colorTransfer); } /** diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java index 6db7d68786..83fd79fc09 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java +++ b/libraries/extractor/src/main/java/androidx/media3/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/libraries/extractor/src/test/java/androidx/media3/extractor/NalUnitUtilTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/NalUnitUtilTest.java index 59dd8543db..dd5f108bd1 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/NalUnitUtilTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/NalUnitUtilTest.java @@ -194,6 +194,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() {