diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultDecoderFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultDecoderFactory.java index 180664bb2f..7b8671e33d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultDecoderFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultDecoderFactory.java @@ -135,6 +135,9 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory { if (SDK_INT >= 31 && requestSdrToneMapping) { mediaFormat.setInteger( MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO); + } else if (SDK_INT >= 31 && MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)) { + mediaFormat.setInteger( + MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_HLG); } @Nullable diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java index d3071a63db..108afe6dd7 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java @@ -150,6 +150,13 @@ public final class EncoderUtil { return ImmutableList.of(MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10); } break; + case MimeTypes.VIDEO_DOLBY_VISION: + if (colorTransfer == C.COLOR_TRANSFER_HLG) { + return ImmutableList.of( + MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt); + } + // CodecProfileLevel does not support PQ for Dolby Vision. + break; default: break; } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java index c63d8148f6..b309fe2448 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java @@ -22,9 +22,12 @@ import static androidx.media3.common.util.Util.castNonNull; import android.annotation.SuppressLint; import android.media.MediaCodec; +import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaMuxer; +import android.os.Build; import android.util.SparseLongArray; +import androidx.annotation.RequiresApi; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.Metadata; @@ -108,6 +111,11 @@ import java.nio.ByteBuffer; if (isVideo) { mediaFormat = MediaFormat.createVideoFormat(sampleMimeType, format.width, format.height); MediaFormatUtil.maybeSetColorInfo(mediaFormat, format.colorInfo); + if (sampleMimeType.equals(MimeTypes.VIDEO_DOLBY_VISION) && + android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + mediaFormat.setInteger(MediaFormat.KEY_PROFILE, getDvProfile(format)); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, getDvLevel(format)); + } try { mediaMuxer.setOrientationHint(format.rotationDegrees); } catch (RuntimeException e) { @@ -276,9 +284,64 @@ import java.nio.ByteBuffer; if (SDK_INT >= 24) { supportedMimeTypes.add(MimeTypes.VIDEO_H265); } + if (SDK_INT >= 33) { + supportedMimeTypes.add(MimeTypes.VIDEO_DOLBY_VISION); + } if (SDK_INT >= 34) { supportedMimeTypes.add(MimeTypes.VIDEO_AV1); } return supportedMimeTypes.build(); } + + // Get Dolby Vision profile + // Refer to https://professionalsupport.dolby.com/s/article/What-is-Dolby-Vision-Profile + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) + private static int getDvProfile(Format format) { + // Currently, only profile 8 is supported for encoding + // TODO: set profile ID based on format. + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt; + } + + // Get Dolby Vision level + // Refer to https://professionalsupport.dolby.com/s/article/What-is-Dolby-Vision-Profile + @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) + private static int getDvLevel(Format format) { + int level = -1; + int maxWidthHeight = Math.max(format.width, format.height); + float pps = format.width * format.height * format.frameRate; + + if (maxWidthHeight <= 1280) { + if (pps <= 22118400) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd24; // Level 01 + } else if (pps <= 27648000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd30; // Level 02 + } + } else if (maxWidthHeight <= 1920 && pps <= 49766400) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd24; // Level 03 + } else if (maxWidthHeight <= 2560 && pps <= 62208000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30; // Level 04 + } else if (maxWidthHeight <= 3840) { + if (pps <= 124416000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60; // Level 05 + } else if (pps <= 199065600) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd24; // Level 06 + } else if (pps <= 248832000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30; // Level 07 + } else if (pps <= 398131200) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd48; // Level 08 + } else if (pps <= 497664000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60; // Level 09 + } else if (pps <= 995328000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd120; // Level 10 + } + } else if (maxWidthHeight <= 7680) { + if (pps <= 995328000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k30; // Level 11 + } else if (pps <= 1990656000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k60; // Level 12 + } + } + + return level; + } }