diff --git a/libraries/test_data/src/test/assets/media/mp4/hdr10-av1.mp4 b/libraries/test_data/src/test/assets/media/mp4/hdr10-av1.mp4 new file mode 100644 index 0000000000..cc069134ba Binary files /dev/null and b/libraries/test_data/src/test/assets/media/mp4/hdr10-av1.mp4 differ diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java index c923d5a240..0aaf1569b8 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java @@ -230,6 +230,21 @@ public final class AndroidTestUtil { .setCodecs("hvc1.2.4.L153") .build(); + public static final String MP4_ASSET_AV1_2_SECOND_HDR10 = "asset:///media/mp4/hdr10-av1.mp4"; + public static final Format MP4_ASSET_AV1_2_SECOND_HDR10_FORMAT = + new Format.Builder() + .setSampleMimeType(VIDEO_AV1) + .setWidth(1920) + .setHeight(1080) + .setFrameRate(59.94f) + .setColorInfo( + new ColorInfo.Builder() + .setColorSpace(C.COLOR_SPACE_BT2020) + .setColorRange(C.COLOR_RANGE_LIMITED) + .setColorTransfer(C.COLOR_TRANSFER_ST2084) + .build()) + .build(); + // This file needs alternative MIME type, meaning the decoder needs to be configured with // video/hevc instead of video/dolby-vision. public static final String MP4_ASSET_DOLBY_VISION_HDR = "asset:///media/mp4/dolbyVision-hdr.MOV"; 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 308732c9ce..626cfa726b 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 @@ -22,6 +22,8 @@ import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECO import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_720P_4_SECOND_HDR10; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_720P_4_SECOND_HDR10_FORMAT; +import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_AV1_2_SECOND_HDR10; +import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_AV1_2_SECOND_HDR10_FORMAT; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_DOLBY_VISION_HDR; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_DOLBY_VISION_HDR_FORMAT; import static androidx.media3.transformer.AndroidTestUtil.assumeFormatsSupported; @@ -30,6 +32,7 @@ import static androidx.media3.transformer.Composition.HDR_MODE_KEEP_HDR; import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; import static androidx.media3.transformer.mh.HdrCapabilitiesUtil.assumeDeviceDoesNotSupportHdrEditing; import static androidx.media3.transformer.mh.HdrCapabilitiesUtil.assumeDeviceSupportsHdrEditing; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -37,10 +40,12 @@ import android.net.Uri; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; +import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Util; import androidx.media3.effect.DefaultVideoFrameProcessor; import androidx.media3.transformer.Composition; import androidx.media3.transformer.EditedMediaItem; +import androidx.media3.transformer.EncoderUtil; import androidx.media3.transformer.ExportException; import androidx.media3.transformer.ExportTestResult; import androidx.media3.transformer.TransformationRequest; @@ -51,6 +56,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.AssumptionViolatedException; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -210,6 +216,46 @@ public final class HdrEditingTest { assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); } + @Test + public void + exportAndTranscode_av1FileWithAv1HdrEditingUnsupportedAndHevcHdrEditingSupported_fallsBackToH265() + throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Format format = MP4_ASSET_AV1_2_SECOND_HDR10_FORMAT; + if (EncoderUtil.getSupportedEncodersForHdrEditing(MimeTypes.VIDEO_H265, format.colorInfo) + .isEmpty()) { + String skipReason = "No H265 HDR editing support for " + format.colorInfo; + recordTestSkipped(getApplicationContext(), testId, skipReason); + throw new AssumptionViolatedException(skipReason); + } + if (!EncoderUtil.getSupportedEncodersForHdrEditing(MimeTypes.VIDEO_AV1, format.colorInfo) + .isEmpty()) { + String skipReason = "AV1 HDR editing support for " + format.colorInfo; + recordTestSkipped(getApplicationContext(), testId, skipReason); + throw new AssumptionViolatedException(skipReason); + } + + assumeFormatsSupported( + context, + testId, + /* inputFormat= */ format, + /* outputFormat= */ format.buildUpon().setSampleMimeType(MimeTypes.VIDEO_H265).build()); + + Transformer transformer = new Transformer.Builder(context).build(); + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_AV1_2_SECOND_HDR10)); + EditedMediaItem editedMediaItem = + new EditedMediaItem.Builder(mediaItem).setEffects(FORCE_TRANSCODE_VIDEO_EFFECTS).build(); + + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, editedMediaItem); + Format outputFormat = + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO); + assertThat(outputFormat.colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_ST2084); + assertThat(outputFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H265); + } + @Test public void exportAndTranscodeHdr_ignoringSdrWorkingColorSpace_whenHdrEditingIsSupported() throws Exception { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java index dabaad6702..929d4727db 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java @@ -50,7 +50,6 @@ import androidx.media3.common.util.Util; import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.effect.DebugTraceUtil; import androidx.media3.effect.VideoCompositorSettings; -import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.MoreExecutors; import java.nio.ByteBuffer; @@ -275,16 +274,14 @@ import org.checkerframework.dataflow.qual.Pure; ImmutableList hdrEncoders = getSupportedEncodersForHdrEditing(requestedOutputMimeType, inputFormat.colorInfo); if (hdrEncoders.isEmpty()) { - @Nullable - String alternativeMimeType = MediaCodecUtil.getAlternativeCodecMimeType(inputFormat); - if (alternativeMimeType != null) { - requestedOutputMimeType = alternativeMimeType; - hdrEncoders = - getSupportedEncodersForHdrEditing(alternativeMimeType, inputFormat.colorInfo); - } + // Fallback H.265/HEVC codecs for HDR content to avoid tonemapping. + hdrEncoders = + getSupportedEncodersForHdrEditing(MimeTypes.VIDEO_H265, inputFormat.colorInfo); } if (hdrEncoders.isEmpty()) { hdrMode = HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; + } else { + requestedOutputMimeType = MimeTypes.VIDEO_H265; } }