From e07b8fe7d5b241268bf7d428ff4e0ab348359000 Mon Sep 17 00:00:00 2001 From: anjalibh Date: Mon, 24 Apr 2017 14:33:15 -0700 Subject: [PATCH] Parse ColorInfo from WebM Container and set it on MediaFormat. * colorSpace, colorRange and colorTransfer are relevant to all videos. * The rest of the fields are relevant only to HDR videos. * Tested on a prototype device that decodes VP9 profile 2, 10 bit. * The Webm spec spells color as 'colour' so the webm id reference use the same spelling. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154098189 --- .../google/android/exoplayer2/FormatTest.java | 9 +- .../java/com/google/android/exoplayer2/C.java | 62 ++++++ .../com/google/android/exoplayer2/Format.java | 107 ++++++---- .../extractor/mkv/MatroskaExtractor.java | 191 +++++++++++++++++- .../exoplayer2/extractor/mp4/AtomParsers.java | 2 +- .../android/exoplayer2/video/ColorInfo.java | 149 ++++++++++++++ 6 files changed, 476 insertions(+), 44 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java index e13afceb40..a47a3fb12d 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.ColorInfo; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -61,11 +62,13 @@ public final class FormatTest extends TestCase { Metadata metadata = new Metadata( new TextInformationFrame("id1", "description1", "value1"), new TextInformationFrame("id2", "description2", "value2")); + ColorInfo colorInfo = new ColorInfo(C.COLOR_SPACE_BT709, + C.COLOR_RANGE_LIMITED, C.COLOR_TRANSFER_SDR, new byte[] {1, 2, 3, 4, 5, 6, 7}); Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, - 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100, - C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, Format.OFFSET_SAMPLE_RELATIVE, - INIT_DATA, drmInitData, metadata); + 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, colorInfo, 6, + 44100, C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, + Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, drmInitData, metadata); Parcel parcel = Parcel.obtain(); formatToParcel.writeToParcel(parcel, 0); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 6fb3c4500e..35a69df39e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -20,6 +20,7 @@ import android.content.Context; import android.media.AudioFormat; import android.media.AudioManager; import android.media.MediaCodec; +import android.media.MediaFormat; import android.support.annotation.IntDef; import android.view.Surface; import com.google.android.exoplayer2.util.Util; @@ -554,6 +555,67 @@ public final class C { */ public static final int STEREO_MODE_STEREO_MESH = 3; + /** + * Video colorspaces. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) + public @interface ColorSpace {} + /** + * @see MediaFormat#COLOR_STANDARD_BT709 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709; + /** + * @see MediaFormat#COLOR_STANDARD_BT601_PAL + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL; + /** + * @see MediaFormat#COLOR_STANDARD_BT2020 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; + + /** + * Video color transfer characteristics. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) + public @interface ColorTransfer {} + /** + * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO; + /** + * @see MediaFormat#COLOR_TRANSFER_ST2084 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084; + /** + * @see MediaFormat#COLOR_TRANSFER_HLG + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; + + /** + * Video color range. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) + public @interface ColorRange {} + /** + * @see MediaFormat#COLOR_RANGE_LIMITED + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED; + /** + * @see MediaFormat#COLOR_RANGE_FULL + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL; + /** * Priority for media playback. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Format.java b/library/core/src/main/java/com/google/android/exoplayer2/Format.java index c29c8fbd83..0bffd28ba5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Format.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.ColorInfo; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -128,6 +129,10 @@ public final class Format implements Parcelable { * The projection data for 360/VR video, or null if not applicable. */ public final byte[] projectionData; + /** + * The color metadata associated with the video, helps with accurate color reproduction. + */ + public final ColorInfo colorInfo; // Audio specific. @@ -192,7 +197,7 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, int width, int height, float frameRate, List initializationData, @C.SelectionFlags int selectionFlags) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width, - height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, null, null); } @@ -210,17 +215,18 @@ public final class Format implements Parcelable { DrmInitData drmInitData) { return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, initializationData, rotationDegrees, pixelWidthHeightRatio, null, - NO_VALUE, drmInitData); + NO_VALUE, null, drmInitData); } public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, List initializationData, int rotationDegrees, float pixelWidthHeightRatio, - byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) { + byte[] projectionData, @C.StereoMode int stereoMode, ColorInfo colorInfo, + DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, - frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, - initializationData, drmInitData, null); + frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, + colorInfo, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, + OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null); } // Audio. @@ -229,8 +235,8 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, List initializationData, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE, - NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, + NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, null, null); } @@ -257,7 +263,7 @@ public final class Format implements Parcelable { List initializationData, DrmInitData drmInitData, @C.SelectionFlags int selectionFlags, String language, Metadata metadata) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, metadata); } @@ -275,8 +281,8 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, OFFSET_SAMPLE_RELATIVE, null, null, null); } @@ -305,7 +311,7 @@ public final class Format implements Parcelable { int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs, List initializationData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData, null); } @@ -315,7 +321,7 @@ public final class Format implements Parcelable { public static Format createImageSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, List initializationData, String language, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null); } @@ -326,7 +332,7 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, null); } @@ -334,22 +340,22 @@ public final class Format implements Parcelable { public static Format createSampleFormat(String id, String sampleMimeType, long subsampleOffsetUs) { return new Format(id, null, sampleMimeType, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null); } public static Format createSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); } /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode, - int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay, - int encoderPadding, @C.SelectionFlags int selectionFlags, String language, + ColorInfo colorInfo, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, + int encoderDelay, int encoderPadding, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel, long subsampleOffsetUs, List initializationData, DrmInitData drmInitData, Metadata metadata) { this.id = id; @@ -365,6 +371,7 @@ public final class Format implements Parcelable { this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.projectionData = projectionData; this.stereoMode = stereoMode; + this.colorInfo = colorInfo; this.channelCount = channelCount; this.sampleRate = sampleRate; this.pcmEncoding = pcmEncoding; @@ -396,6 +403,7 @@ public final class Format implements Parcelable { boolean hasProjectionData = in.readInt() != 0; projectionData = hasProjectionData ? in.createByteArray() : null; stereoMode = in.readInt(); + colorInfo = in.readParcelable(ColorInfo.class.getClassLoader()); channelCount = in.readInt(); sampleRate = in.readInt(); pcmEncoding = in.readInt(); @@ -417,26 +425,26 @@ public final class Format implements Parcelable { public Format copyWithMaxInputSize(int maxInputSize) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } @SuppressWarnings("ReferenceEquality") @@ -455,33 +463,33 @@ public final class Format implements Parcelable { : this.drmInitData; return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, - channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, - language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData, - metadata); + colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithDrmInitData(DrmInitData drmInitData) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithMetadata(Metadata metadata) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } /** @@ -513,6 +521,7 @@ public final class Format implements Parcelable { for (int i = 0; i < initializationData.size(); i++) { format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); } + maybeSetColorInfoV24(format, colorInfo); return format; } @@ -569,6 +578,7 @@ public final class Format implements Parcelable { || !Util.areEqual(codecs, other.codecs) || !Util.areEqual(drmInitData, other.drmInitData) || !Util.areEqual(metadata, other.metadata) + || !Util.areEqual(colorInfo, other.colorInfo) || !Arrays.equals(projectionData, other.projectionData) || initializationData.size() != other.initializationData.size()) { return false; @@ -581,6 +591,17 @@ public final class Format implements Parcelable { return true; } + @TargetApi(24) + private static void maybeSetColorInfoV24(MediaFormat format, ColorInfo colorInfo) { + if (colorInfo == null) { + return; + } + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace); + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange); + maybeSetByteBufferV16(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo); + } + @TargetApi(16) private static void maybeSetStringV16(MediaFormat format, String key, String value) { if (value != null) { @@ -602,6 +623,13 @@ public final class Format implements Parcelable { } } + @TargetApi(16) + private static void maybeSetByteBufferV16(MediaFormat format, String key, byte[] value) { + if (value != null) { + format.setByteBuffer(key, ByteBuffer.wrap(value)); + } + } + // Utility methods /** @@ -659,6 +687,7 @@ public final class Format implements Parcelable { dest.writeByteArray(projectionData); } dest.writeInt(stereoMode); + dest.writeParcelable(colorInfo, flags); dest.writeInt(channelCount); dest.writeInt(sampleRate); dest.writeInt(pcmEncoding); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 9273dc2952..8f3abf4688 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.AvcConfig; +import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.HevcConfig; import java.io.IOException; import java.lang.annotation.Retention; @@ -187,6 +188,23 @@ public final class MatroskaExtractor implements Extractor { private static final int ID_PROJECTION = 0x7670; private static final int ID_PROJECTION_PRIVATE = 0x7672; private static final int ID_STEREO_MODE = 0x53B8; + private static final int ID_COLOUR = 0x55B0; + private static final int ID_COLOUR_RANGE = 0x55B9; + private static final int ID_COLOUR_TRANSFER = 0x55BA; + private static final int ID_COLOUR_PRIMARIES = 0x55BB; + private static final int ID_MAX_CLL = 0x55BC; + private static final int ID_MAX_FALL = 0x55BD; + private static final int ID_MASTERING_METADATA = 0x55D0; + private static final int ID_PRIMARY_R_CHROMATICITY_X = 0x55D1; + private static final int ID_PRIMARY_R_CHROMATICITY_Y = 0x55D2; + private static final int ID_PRIMARY_G_CHROMATICITY_X = 0x55D3; + private static final int ID_PRIMARY_G_CHROMATICITY_Y = 0x55D4; + private static final int ID_PRIMARY_B_CHROMATICITY_X = 0x55D5; + private static final int ID_PRIMARY_B_CHROMATICITY_Y = 0x55D6; + private static final int ID_WHITE_POINT_CHROMATICITY_X = 0x55D7; + private static final int ID_WHITE_POINT_CHROMATICITY_Y = 0x55D8; + private static final int ID_LUMNINANCE_MAX = 0x55D9; + private static final int ID_LUMNINANCE_MIN = 0x55DA; private static final int LACING_NONE = 0; private static final int LACING_XIPH = 1; @@ -393,6 +411,8 @@ public final class MatroskaExtractor implements Extractor { case ID_CUE_TRACK_POSITIONS: case ID_BLOCK_GROUP: case ID_PROJECTION: + case ID_COLOUR: + case ID_MASTERING_METADATA: return EbmlReader.TYPE_MASTER; case ID_EBML_READ_VERSION: case ID_DOC_TYPE_READ_VERSION: @@ -423,6 +443,11 @@ public final class MatroskaExtractor implements Extractor { case ID_CUE_CLUSTER_POSITION: case ID_REFERENCE_BLOCK: case ID_STEREO_MODE: + case ID_COLOUR_RANGE: + case ID_COLOUR_TRANSFER: + case ID_COLOUR_PRIMARIES: + case ID_MAX_CLL: + case ID_MAX_FALL: return EbmlReader.TYPE_UNSIGNED_INT; case ID_DOC_TYPE: case ID_CODEC_ID: @@ -438,6 +463,16 @@ public final class MatroskaExtractor implements Extractor { return EbmlReader.TYPE_BINARY; case ID_DURATION: case ID_SAMPLING_FREQUENCY: + case ID_PRIMARY_R_CHROMATICITY_X: + case ID_PRIMARY_R_CHROMATICITY_Y: + case ID_PRIMARY_G_CHROMATICITY_X: + case ID_PRIMARY_G_CHROMATICITY_Y: + case ID_PRIMARY_B_CHROMATICITY_X: + case ID_PRIMARY_B_CHROMATICITY_Y: + case ID_WHITE_POINT_CHROMATICITY_X: + case ID_WHITE_POINT_CHROMATICITY_Y: + case ID_LUMNINANCE_MAX: + case ID_LUMNINANCE_MIN: return EbmlReader.TYPE_FLOAT; default: return EbmlReader.TYPE_UNKNOWN; @@ -496,6 +531,9 @@ public final class MatroskaExtractor implements Extractor { case ID_TRACK_ENTRY: currentTrack = new Track(); break; + case ID_MASTERING_METADATA: + currentTrack.hasColorInfo = true; + break; default: break; } @@ -706,6 +744,60 @@ public final class MatroskaExtractor implements Extractor { break; } break; + case ID_COLOUR_PRIMARIES: + currentTrack.hasColorInfo = true; + switch ((int) value) { + case 1: + currentTrack.colorSpace = C.COLOR_SPACE_BT709; + break; + case 4: // BT.470M. + case 5: // BT.470BG. + case 6: // SMPTE 170M. + case 7: // SMPTE 240M. + currentTrack.colorSpace = C.COLOR_SPACE_BT601; + break; + case 9: + currentTrack.colorSpace = C.COLOR_SPACE_BT2020; + break; + default: + break; + } + break; + case ID_COLOUR_TRANSFER: + switch ((int) value) { + case 1: // BT.709. + case 6: // SMPTE 170M. + case 7: // SMPTE 240M. + currentTrack.colorTransfer = C.COLOR_TRANSFER_SDR; + break; + case 16: + currentTrack.colorTransfer = C.COLOR_TRANSFER_ST2084; + break; + case 18: + currentTrack.colorTransfer = C.COLOR_TRANSFER_HLG; + break; + default: + break; + } + break; + case ID_COLOUR_RANGE: + switch((int) value) { + case 1: // Broadcast range. + currentTrack.colorRange = C.COLOR_RANGE_LIMITED; + break; + case 2: + currentTrack.colorRange = C.COLOR_RANGE_FULL; + break; + default: + break; + } + break; + case ID_MAX_CLL: + currentTrack.maxContentLuminance = (int) value; + break; + case ID_MAX_FALL: + currentTrack.maxFrameAverageLuminance = (int) value; + break; default: break; } @@ -719,6 +811,36 @@ public final class MatroskaExtractor implements Extractor { case ID_SAMPLING_FREQUENCY: currentTrack.sampleRate = (int) value; break; + case ID_PRIMARY_R_CHROMATICITY_X: + currentTrack.primaryRChromaticityX = (float) value; + break; + case ID_PRIMARY_R_CHROMATICITY_Y: + currentTrack.primaryRChromaticityY = (float) value; + break; + case ID_PRIMARY_G_CHROMATICITY_X: + currentTrack.primaryGChromaticityX = (float) value; + break; + case ID_PRIMARY_G_CHROMATICITY_Y: + currentTrack.primaryGChromaticityY = (float) value; + break; + case ID_PRIMARY_B_CHROMATICITY_X: + currentTrack.primaryBChromaticityX = (float) value; + break; + case ID_PRIMARY_B_CHROMATICITY_Y: + currentTrack.primaryBChromaticityY = (float) value; + break; + case ID_WHITE_POINT_CHROMATICITY_X: + currentTrack.whitePointChromaticityX = (float) value; + break; + case ID_WHITE_POINT_CHROMATICITY_Y: + currentTrack.whitePointChromaticityY = (float) value; + break; + case ID_LUMNINANCE_MAX: + currentTrack.maxMasteringLuminance = (float) value; + break; + case ID_LUMNINANCE_MIN: + currentTrack.minMasteringLuminance = (float) value; + break; default: break; } @@ -1330,6 +1452,16 @@ public final class MatroskaExtractor implements Extractor { private static final class Track { private static final int DISPLAY_UNIT_PIXELS = 0; + private static final int MAX_CHROMATICITY = 50000; // Defined in CTA-861.3. + /** + * Default max content light level (CLL) that should be encoded into hdrStaticInfo. + */ + private static final int DEFAULT_MAX_CLL = 1000; // nits. + + /** + * Default frame-average light level (FALL) that should be encoded into hdrStaticInfo. + */ + private static final int DEFAULT_MAX_FALL = 200; // nits. // Common elements. public String codecId; @@ -1351,6 +1483,25 @@ public final class MatroskaExtractor implements Extractor { public byte[] projectionData = null; @C.StereoMode public int stereoMode = Format.NO_VALUE; + public boolean hasColorInfo = false; + @C.ColorSpace + public int colorSpace = Format.NO_VALUE; + @C.ColorTransfer + public int colorTransfer = Format.NO_VALUE; + @C.ColorRange + public int colorRange = Format.NO_VALUE; + public int maxContentLuminance = DEFAULT_MAX_CLL; + public int maxFrameAverageLuminance = DEFAULT_MAX_FALL; + public float primaryRChromaticityX = Format.NO_VALUE; + public float primaryRChromaticityY = Format.NO_VALUE; + public float primaryGChromaticityX = Format.NO_VALUE; + public float primaryGChromaticityY = Format.NO_VALUE; + public float primaryBChromaticityX = Format.NO_VALUE; + public float primaryBChromaticityY = Format.NO_VALUE; + public float whitePointChromaticityX = Format.NO_VALUE; + public float whitePointChromaticityY = Format.NO_VALUE; + public float maxMasteringLuminance = Format.NO_VALUE; + public float minMasteringLuminance = Format.NO_VALUE; // Audio elements. Initially set to their default values. public int channelCount = 1; @@ -1520,9 +1671,15 @@ public final class MatroskaExtractor implements Extractor { if (displayWidth != Format.NO_VALUE && displayHeight != Format.NO_VALUE) { pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight); } + ColorInfo colorInfo = null; + if (hasColorInfo) { + byte[] hdrStaticInfo = getHdrStaticInfo(); + colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo); + } format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, - Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); + Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, + drmInitData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, @@ -1541,6 +1698,38 @@ public final class MatroskaExtractor implements Extractor { this.output.format(format); } + /** + * Returns the HDR Static Info as defined in CTA-861.3. + */ + private byte[] getHdrStaticInfo() { + // Are all fields present. + if (primaryRChromaticityX == Format.NO_VALUE || primaryRChromaticityY == Format.NO_VALUE + || primaryGChromaticityX == Format.NO_VALUE || primaryGChromaticityY == Format.NO_VALUE + || primaryBChromaticityX == Format.NO_VALUE || primaryBChromaticityY == Format.NO_VALUE + || whitePointChromaticityX == Format.NO_VALUE + || whitePointChromaticityY == Format.NO_VALUE || maxMasteringLuminance == Format.NO_VALUE + || minMasteringLuminance == Format.NO_VALUE) { + return null; + } + + byte[] hdrStaticInfoData = new byte[25]; + ByteBuffer hdrStaticInfo = ByteBuffer.wrap(hdrStaticInfoData); + hdrStaticInfo.put((byte) 0); // Type. + hdrStaticInfo.putShort((short) ((primaryRChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryRChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryGChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryGChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryBChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryBChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((whitePointChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((whitePointChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) (maxMasteringLuminance + 0.5f)); + hdrStaticInfo.putShort((short) (minMasteringLuminance + 0.5f)); + hdrStaticInfo.putShort((short) maxContentLuminance); + hdrStaticInfo.putShort((short) maxFrameAverageLuminance); + return hdrStaticInfoData; + } + /** * Builds initialization data for a {@link Format} from FourCC codec private data. *

diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 4b30b383ec..474ba65d86 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -762,7 +762,7 @@ import java.util.List; out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData, - rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); + rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, null, drmInitData); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java new file mode 100644 index 0000000000..7bdc43f85c --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 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 android.os.Parcel; +import android.os.Parcelable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import java.util.Arrays; + +/** + * Stores color info. + */ +public final class ColorInfo implements Parcelable { + + /** + * The color space of the video. Valid values are {@link C#COLOR_SPACE_BT601}, {@link + * C#COLOR_SPACE_BT709}, {@link C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown. + */ + @C.ColorSpace + public final int colorSpace; + + /** + * The color range of the video. Valid values are {@link C#COLOR_RANGE_LIMITED}, {@link + * C#COLOR_RANGE_FULL} or {@link Format#NO_VALUE} if unknown. + */ + @C.ColorRange + public final int colorRange; + + /** + * The color transfer characteristicks of the video. Valid values are {@link + * C#COLOR_TRANSFER_HLG}, {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link + * Format#NO_VALUE} if unknown. + */ + @C.ColorTransfer + public final int colorTransfer; + + /** + * HdrStaticInfo as defined in CTA-861.3. + */ + public final byte[] hdrStaticInfo; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * Constructs the ColorInfo. + * + * @param colorSpace The color space of the video. + * @param colorRange The color range of the video. + * @param colorTransfer The color transfer characteristics of the video. + * @param hdrStaticInfo HdrStaticInfo as defined in CTA-861.3. + */ + public ColorInfo(@C.ColorSpace int colorSpace, @C.ColorRange int colorRange, + @C.ColorTransfer int colorTransfer, byte[] hdrStaticInfo) { + this.colorSpace = colorSpace; + this.colorRange = colorRange; + this.colorTransfer = colorTransfer; + this.hdrStaticInfo = hdrStaticInfo; + } + + @SuppressWarnings("ResourceType") + /* package */ ColorInfo(Parcel in) { + colorSpace = in.readInt(); + colorRange = in.readInt(); + colorTransfer = in.readInt(); + boolean hasHdrStaticInfo = in.readInt() != 0; + hdrStaticInfo = hasHdrStaticInfo ? in.createByteArray() : null; + } + + // Parcelable implementation. + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ColorInfo other = (ColorInfo) obj; + if (colorSpace != other.colorSpace || colorRange != other.colorRange + || colorTransfer != other.colorTransfer + || !Arrays.equals(hdrStaticInfo, other.hdrStaticInfo)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "ColorInfo(" + colorSpace + ", " + colorRange + ", " + colorTransfer + + ", " + (hdrStaticInfo != null) + ")"; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + colorSpace; + result = 31 * result + colorRange; + result = 31 * result + colorTransfer; + result = 31 * result + Arrays.hashCode(hdrStaticInfo); + hashCode = result; + } + return hashCode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(colorSpace); + dest.writeInt(colorRange); + dest.writeInt(colorTransfer); + dest.writeInt(hdrStaticInfo != null ? 1 : 0); + if (hdrStaticInfo != null) { + dest.writeByteArray(hdrStaticInfo); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public ColorInfo createFromParcel(Parcel in) { + return new ColorInfo(in); + } + + @Override + public ColorInfo[] newArray(int size) { + return new ColorInfo[0]; + } + }; + +}