diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1097fb8b24..b6a0ea00a8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -41,6 +41,8 @@ while `InAppFragmentedMp4Muxer` is to be used for producing a fragmented MP4 file. * Move `Muxer` interface from `media3-muxer` to `media3-transformer`. + * Add support for transcoding and transmuxing Dolby Vision (profile 8) + format. * Track Selection: * Extractors: * Fix handling of NAL units with lengths expressed in 1 or 2 bytes (rather diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java index aac26162ff..0930b217a9 100644 --- a/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java @@ -266,7 +266,8 @@ public final class ConfigurationActivity extends AppCompatActivity { MimeTypes.VIDEO_H264, MimeTypes.VIDEO_H265, MimeTypes.VIDEO_MP4V, - MimeTypes.VIDEO_AV1); + MimeTypes.VIDEO_AV1, + MimeTypes.VIDEO_DOLBY_VISION); ArrayAdapter resolutionHeightAdapter = new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java index fb9d24ae21..097a75f8f8 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java @@ -1776,33 +1776,46 @@ public class TransformerEndToEndTest { } @Test - public void dolbyVisionVideo_noEffects_transmuxesToHevc() throws Exception { - assumeTrue("This test requires B-frame support", Util.SDK_INT > 24); - assumeTrue( - new DefaultMuxer.Factory() - .getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO) - .contains(MimeTypes.VIDEO_H265)); + public void transmuxDolbyVisionVideo_whenMuxerDoesNotSupportDolbyVision_transmuxesToHevc() + throws Exception { + // Hevc support is available from API 24. + // The asset has B-frames and B-frame support is available from API 25. + // Dolby vision support is available from API 33. + assumeTrue(Util.SDK_INT >= 25 && Util.SDK_INT < 33); EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(MediaItem.fromUri(Uri.parse(MP4_ASSET_DOLBY_VISION_HDR.uri))) .setRemoveAudio(true) .build(); ExportTestResult result = - new TransformerAndroidTestRunner.Builder( - context, - new Transformer.Builder(context).setVideoMimeType(MimeTypes.VIDEO_H265).build()) + new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build()) .build() .run(testId, editedMediaItem); - MediaExtractorCompat mediaExtractor = new MediaExtractorCompat(context); - mediaExtractor.setDataSource(Uri.parse(result.filePath), /* offset= */ 0); - checkState(mediaExtractor.getTrackCount() == 1); - MediaFormat mediaFormat = mediaExtractor.getTrackFormat(/* trackIndex= */ 0); - Format format = createFormatFromMediaFormat(mediaFormat); + Format format = retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO); assertThat(format.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H265); assertThat(result.exportResult.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED); } + @Test + public void transmuxDolbyVisionVideo_transmuxesSuccessfully() throws Exception { + assumeTrue("Dolby vision support available from API 33", Util.SDK_INT >= 33); + Transformer transformer = new Transformer.Builder(context).build(); + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_DOLBY_VISION_HDR.uri)); + + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, mediaItem); + + Format trackFormat = + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO); + assertThat(trackFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_DOLBY_VISION); + assertThat(trackFormat.codecs).isEqualTo("dvhe.08.02"); + assertThat(exportTestResult.exportResult.videoConversionProcess) + .isEqualTo(CONVERSION_PROCESS_TRANSMUXED); + } + @Test public void dolbyVisionVideo_noEffects_withInAppMuxer_transmuxesToHevc() throws Exception { EditedMediaItem editedMediaItem = diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrCapabilitiesUtil.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrCapabilitiesUtil.java index 851abcbec5..a218383aa0 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrCapabilitiesUtil.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrCapabilitiesUtil.java @@ -66,7 +66,11 @@ public final class HdrCapabilitiesUtil { checkState(ColorInfo.isTransferHdr(format.colorInfo)); if (EncoderUtil.getSupportedEncodersForHdrEditing(format.sampleMimeType, format.colorInfo) .isEmpty()) { - String skipReason = "No HDR editing support for " + format.colorInfo; + String skipReason = + "No HDR editing supported for sample mime type " + + format.sampleMimeType + + " and color info " + + format.colorInfo; recordTestSkipped(getApplicationContext(), testId, skipReason); throw new AssumptionViolatedException(skipReason); } @@ -82,7 +86,11 @@ public final class HdrCapabilitiesUtil { checkState(ColorInfo.isTransferHdr(format.colorInfo)); if (!EncoderUtil.getSupportedEncodersForHdrEditing(format.sampleMimeType, format.colorInfo) .isEmpty()) { - String skipReason = "HDR editing support for " + format.colorInfo; + String skipReason = + "HDR editing supported for sample mime type " + + format.sampleMimeType + + " and color info " + + format.colorInfo; recordTestSkipped(getApplicationContext(), testId, skipReason); throw new AssumptionViolatedException(skipReason); } diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java index dfd9d2509a..a845ced424 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java @@ -50,6 +50,7 @@ import androidx.media3.transformer.Composition; import androidx.media3.transformer.EditedMediaItem; import androidx.media3.transformer.EncoderUtil; import androidx.media3.transformer.ExportException; +import androidx.media3.transformer.ExportResult; import androidx.media3.transformer.ExportTestResult; import androidx.media3.transformer.TransformationRequest; import androidx.media3.transformer.Transformer; @@ -207,15 +208,72 @@ public final class HdrEditingTest { } @Test - public void exportAndTranscode_dolbyVisionFile_whenHdrEditingIsSupported() throws Exception { + public void exportAndTranscode_hlg10VideoToDolbyVision_whenDolbyVisionSupported() + throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + assumeFormatsSupported( + context, + testId, + /* inputFormat= */ MP4_ASSET_1080P_5_SECOND_HLG10.videoFormat, + /* outputFormat= */ MP4_ASSET_1080P_5_SECOND_HLG10 + .videoFormat + .buildUpon() + .setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION) + .setCodecs(null) + .build()); + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10.uri)); + EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build(); + Transformer transformer = + new Transformer.Builder(context).setVideoMimeType(MimeTypes.VIDEO_DOLBY_VISION).build(); + + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, editedMediaItem); + ExportResult exportResult = exportTestResult.exportResult; + + assertThat(exportResult.videoMimeType).isEqualTo(MimeTypes.VIDEO_DOLBY_VISION); + Format videoTrackFormat = + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO); + assertThat(videoTrackFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_DOLBY_VISION); + int actualColorTransfer = videoTrackFormat.colorInfo.colorTransfer; + assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); + } + + @Test + public void exportAndTranscode_dolbyVisionFileToDolbyVision_whenDolbyVisionSupported() + throws Exception { Context context = ApplicationProvider.getApplicationContext(); Format format = MP4_ASSET_DOLBY_VISION_HDR.videoFormat; - // Check HDR support for both VIDEO_DOLBY_VISION and VIDEO_H265 mime types. - if (EncoderUtil.getSupportedEncodersForHdrEditing(format.sampleMimeType, format.colorInfo) - .isEmpty()) { - assumeDeviceSupportsHdrEditing( - testId, format.buildUpon().setSampleMimeType(MimeTypes.VIDEO_H265).build()); - } + assumeFormatsSupported(context, testId, /* inputFormat= */ format, /* outputFormat= */ format); + Transformer transformer = new Transformer.Builder(context).build(); + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_DOLBY_VISION_HDR.uri)); + EditedMediaItem editedMediaItem = + new EditedMediaItem.Builder(mediaItem).setEffects(FORCE_TRANSCODE_VIDEO_EFFECTS).build(); + + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, editedMediaItem); + ExportResult exportResult = exportTestResult.exportResult; + + assertThat(exportResult.videoMimeType).isEqualTo(MimeTypes.VIDEO_DOLBY_VISION); + Format videoTrackFormat = + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO); + assertThat(videoTrackFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_DOLBY_VISION); + int actualColorTransfer = videoTrackFormat.colorInfo.colorTransfer; + assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); + } + + @Test + public void + exportAndTranscode_dolbyVisionFileToHlg_whenDolbyVisionIsNotSupportedAndHlgIsSupported() + throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Format format = MP4_ASSET_DOLBY_VISION_HDR.videoFormat; + assumeDeviceDoesNotSupportHdrEditing(testId, format); + assumeDeviceSupportsHdrEditing( + testId, format.buildUpon().setSampleMimeType(MimeTypes.VIDEO_H265).build()); assumeFormatsSupported(context, testId, /* inputFormat= */ format, /* outputFormat= */ null); Transformer transformer = new Transformer.Builder(context).build(); MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_DOLBY_VISION_HDR.uri)); @@ -226,11 +284,13 @@ public final class HdrEditingTest { new TransformerAndroidTestRunner.Builder(context, transformer) .build() .run(testId, editedMediaItem); - @C.ColorTransfer - int actualColorTransfer = - retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO) - .colorInfo - .colorTransfer; + ExportResult exportResult = exportTestResult.exportResult; + + assertThat(exportResult.videoMimeType).isEqualTo(MimeTypes.VIDEO_H265); + Format videoTrackFormat = + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO); + assertThat(videoTrackFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H265); + int actualColorTransfer = videoTrackFormat.colorInfo.colorTransfer; assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); } @@ -428,6 +488,66 @@ public final class HdrEditingTest { } } + @Test + public void exportAndTranscode_dolbyVisionFile_whenHdrEditingUnsupported_toneMapsOrThrows() + throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Format format = MP4_ASSET_DOLBY_VISION_HDR.videoFormat; + // Check HDR support for both VIDEO_DOLBY_VISION and VIDEO_H265 mime types. + assumeDeviceDoesNotSupportHdrEditing(testId, format); + assumeDeviceDoesNotSupportHdrEditing( + testId, format.buildUpon().setSampleMimeType(MimeTypes.VIDEO_H265).build()); + assumeFormatsSupported(context, testId, /* inputFormat= */ format, /* outputFormat= */ null); + AtomicBoolean isFallbackListenerInvoked = new AtomicBoolean(); + AtomicBoolean isToneMappingFallbackApplied = new AtomicBoolean(); + Transformer transformer = + new Transformer.Builder(context) + .addListener( + new Transformer.Listener() { + @Override + public void onFallbackApplied( + Composition composition, + TransformationRequest originalTransformationRequest, + TransformationRequest fallbackTransformationRequest) { + isFallbackListenerInvoked.set(true); + assertThat(originalTransformationRequest.hdrMode).isEqualTo(HDR_MODE_KEEP_HDR); + isToneMappingFallbackApplied.set( + fallbackTransformationRequest.hdrMode + == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL); + } + }) + .build(); + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_DOLBY_VISION_HDR.uri)); + EditedMediaItem editedMediaItem = + new EditedMediaItem.Builder(mediaItem).setEffects(FORCE_TRANSCODE_VIDEO_EFFECTS).build(); + + try { + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, editedMediaItem); + assertThat(isToneMappingFallbackApplied.get()).isTrue(); + @C.ColorTransfer + int actualColorTransfer = + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO) + .colorInfo + .colorTransfer; + assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_SDR); + } catch (ExportException exception) { + if (exception.getCause() != null) { + @Nullable String message = exception.getCause().getMessage(); + if (message != null + && (Objects.equals(message, "Decoding HDR is not supported on this device.") + || message.contains( + "OpenGL ES 3.0 context support is required for HDR input or output.") + || Objects.equals(message, "Device lacks YUV extension support."))) { + return; + } + } + throw exception; + } + } + private static List byteList(ByteBuffer buffer) { ArrayList outputBytes = new ArrayList<>(); while (buffer.hasRemaining()) { 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 675c3fa3b0..dc82edc794 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultDecoderFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultDecoderFactory.java @@ -339,6 +339,12 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory { } } + // MediaFormat#KEY_COLOR_TRANSFER_REQUEST is available from API 31. + if (SDK_INT >= 31 && decoderInfos.get(0).codecMimeType.equals(MimeTypes.VIDEO_DOLBY_VISION)) { + // Ignore the dolby vision dynamic metadata. + mediaFormat.setInteger( + MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_HLG); + } List codecInitExceptions = new ArrayList<>(); DefaultCodec codec = createCodecFromDecoderInfos( 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 289d0d5949..ab3e31065a 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncoderUtil.java @@ -112,18 +112,27 @@ public final class EncoderUtil { @RequiresApi(33) public static boolean isHdrEditingSupported( MediaCodecInfo mediaCodecInfo, String mimeType, ColorInfo colorInfo) { - ImmutableList allowedColorProfiles = - getCodecProfilesForHdrFormat(mimeType, colorInfo.colorTransfer); - boolean hasNeededHdrSupport = - isFeatureSupported( - mediaCodecInfo, mimeType, MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing) - || (colorInfo.colorTransfer == C.COLOR_TRANSFER_HLG - && Util.SDK_INT >= 35 - && isFeatureSupported( - mediaCodecInfo, mimeType, MediaCodecInfo.CodecCapabilities.FEATURE_HlgEditing)); + boolean hasNeededHdrSupport = false; + // Some Dolby Vision encoders do not advertise FEATURE_HlgEditing but correctly support 10-bit + // input surface. + if (mimeType.equals(MimeTypes.VIDEO_DOLBY_VISION)) { + hasNeededHdrSupport = true; + } else { + hasNeededHdrSupport = + isFeatureSupported( + mediaCodecInfo, mimeType, MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing) + || (colorInfo.colorTransfer == C.COLOR_TRANSFER_HLG + && Util.SDK_INT >= 35 + && isFeatureSupported( + mediaCodecInfo, + mimeType, + MediaCodecInfo.CodecCapabilities.FEATURE_HlgEditing)); + } if (!hasNeededHdrSupport) { return false; } + ImmutableList allowedColorProfiles = + getCodecProfilesForHdrFormat(mimeType, colorInfo.colorTransfer); for (MediaCodecInfo.CodecProfileLevel codecProfileLevel : mediaCodecInfo.getCapabilitiesForType(mimeType).profileLevels) { if (allowedColorProfiles.contains(codecProfileLevel.profile)) { @@ -172,6 +181,12 @@ 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 bd0ce5bcbf..863174ec8e 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java @@ -17,14 +17,19 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; +import static androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel; import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.common.util.Util.castNonNull; +import static java.lang.Integer.max; import android.annotation.SuppressLint; import android.media.MediaCodec.BufferInfo; +import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaMuxer; +import android.util.Pair; import android.util.SparseArray; +import androidx.annotation.RequiresApi; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.Metadata; @@ -130,6 +135,10 @@ import java.util.Locale; if (isVideo) { mediaFormat = MediaFormat.createVideoFormat(sampleMimeType, format.width, format.height); MediaFormatUtil.maybeSetColorInfo(mediaFormat, format.colorInfo); + if (sampleMimeType.equals(MimeTypes.VIDEO_DOLBY_VISION) && SDK_INT >= 33) { + mediaFormat.setInteger(MediaFormat.KEY_PROFILE, getDvProfile()); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, getDvLevel(format)); + } try { mediaMuxer.setOrientationHint(format.rotationDegrees); } catch (RuntimeException e) { @@ -305,9 +314,78 @@ import java.util.Locale; 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 Dolby + * Vision profiles and levels.. + */ + @RequiresApi(33) + private static int getDvProfile() { + // Currently, only profile 8 is supported. + return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt; + } + + /** + * Get Dolby Vision level + * + *

Refer to What are + * Dolby Vision profiles and levels. + */ + @RequiresApi(33) + private static int getDvLevel(Format format) { + if (format.codecs != null) { + Pair profileAndLevel = getCodecProfileAndLevel(format); + return checkNotNull(profileAndLevel).second; + } + int maxWidthHeight = max(format.width, format.height); + checkState(maxWidthHeight <= 7680); + float pps = format.width * format.height * format.frameRate; + + int level = -1; + if (maxWidthHeight <= 1_280) { + if (pps <= 22_118_400) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd24; // Level 01 + } else { // pps <= 27_648_000 + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd30; // Level 02 + } + } else if (maxWidthHeight <= 1_920 && pps <= 49_766_400) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd24; // Level 03 + } else if (maxWidthHeight <= 2_560 && pps <= 62_208_000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30; // Level 04 + } else if (maxWidthHeight <= 3_840) { + if (pps <= 124_416_000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60; // Level 05 + } else if (pps <= 199_065_600) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd24; // Level 06 + } else if (pps <= 248_832_000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30; // Level 07 + } else if (pps <= 398_131_200) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd48; // Level 08 + } else if (pps <= 497_664_000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60; // Level 09 + } else { // pps <= 995_328_000 + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd120; // Level 10 + } + } else if (maxWidthHeight <= 7_680) { + if (pps <= 995_328_000) { + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k30; // Level 11 + } else { // pps <= 1_990_656_000 + level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k60; // Level 12 + } + } + + return level; + } }