diff --git a/google3/third_party/java_src/android_libs/media/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorPixelTest.java b/google3/third_party/java_src/android_libs/media/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorPixelTest.java index 29d1d8d24cb..5757ec33a45 100644 --- a/google3/third_party/java_src/android_libs/media/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorPixelTest.java +++ b/google3/third_party/java_src/android_libs/media/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorPixelTest.java @@ -16,7 +16,6 @@ package androidx.media3.effect; import static androidx.media3.effect.BitmapTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE; -import static androidx.media3.effect.FrameProcessorTestUtil.decodeOneFrame; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; @@ -38,6 +37,7 @@ import androidx.media3.common.FrameProcessor; import androidx.media3.common.SurfaceInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.testutil.DecodeOneFrameTestUtil; import com.google.android.exoplayer2.video.ColorInfo; import com.google.common.collect.ImmutableList; import java.util.List; @@ -465,11 +465,11 @@ public void onFrameProcessingEnded() { DebugViewProvider.NONE, ColorInfo.SDR_BT709_LIMITED, /* releaseFramesAutomatically= */ true)); - decodeOneFrame( + DecodeOneFrameTestUtil.decodeOneAssetFileFrame( INPUT_MP4_ASSET_STRING, - new FrameProcessorTestUtil.Listener() { + new DecodeOneFrameTestUtil.Listener() { @Override - public void onVideoMediaFormatExtracted(MediaFormat mediaFormat) { + public void onContainerExtracted(MediaFormat mediaFormat) { glEffectsFrameProcessor.setInputFrameInfo( new FrameInfo( mediaFormat.getInteger(MediaFormat.KEY_WIDTH), @@ -480,7 +480,7 @@ public void onVideoMediaFormatExtracted(MediaFormat mediaFormat) { } @Override - public void onVideoMediaFormatRead(MediaFormat mediaFormat) { + public void onFrameDecoded(MediaFormat mediaFormat) { // Do nothing. } }, diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java index 794620283c5..9189cef3b63 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java @@ -18,20 +18,26 @@ import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER; import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10; import static com.google.android.exoplayer2.transformer.AndroidTestUtil.recordTestSkipped; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_H265; import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.media.MediaFormat; import android.net.Uri; +import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.testutil.DecodeOneFrameTestUtil; import com.google.android.exoplayer2.transformer.EncoderUtil; import com.google.android.exoplayer2.transformer.TransformationException; import com.google.android.exoplayer2.transformer.TransformationRequest; +import com.google.android.exoplayer2.transformer.TransformationTestResult; import com.google.android.exoplayer2.transformer.Transformer; import com.google.android.exoplayer2.transformer.TransformerAndroidTestRunner; +import com.google.android.exoplayer2.util.MediaFormatUtil; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.ColorInfo; import java.util.concurrent.atomic.AtomicBoolean; @@ -62,9 +68,11 @@ public void transform_noRequestedTranscode_hdr10File_transformsOrThrows() throws .build(); try { - new TransformerAndroidTestRunner.Builder(context, transformer) - .build() - .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + checkHasColorTransfer(transformationTestResult, C.COLOR_TRANSFER_ST2084); return; } catch (TransformationException exception) { assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); @@ -74,7 +82,6 @@ public void transform_noRequestedTranscode_hdr10File_transformsOrThrows() throws .hasCauseThat() .hasMessageThat() .isEqualTo("HDR editing and tone mapping not supported under API 31."); - return; } } @@ -99,9 +106,11 @@ public void transformAndTranscode_hdr10File_whenHdrEditingIsSupported() throws E .build()) .build(); - new TransformerAndroidTestRunner.Builder(context, transformer) - .build() - .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + checkHasColorTransfer(transformationTestResult, C.COLOR_TRANSFER_ST2084); } @Test @@ -141,9 +150,11 @@ public void onFallbackApplied( .build(); try { - new TransformerAndroidTestRunner.Builder(context, transformer) - .build() - .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + checkHasColorTransfer(transformationTestResult, C.COLOR_TRANSFER_SDR); } catch (TransformationException exception) { assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); // TODO(b/245364266): After fixing the bug, replace the API version check with a check that @@ -197,4 +208,30 @@ public void transformUnexpectedColorInfo() throws Exception { private static boolean deviceSupportsHdrEditing(String mimeType, ColorInfo colorInfo) { return !EncoderUtil.getSupportedEncoderNamesForHdrEditing(mimeType, colorInfo).isEmpty(); } + + private static void checkHasColorTransfer( + TransformationTestResult transformationTestResult, @C.ColorTransfer int expectedColorTransfer) + throws Exception { + if (Util.SDK_INT < 29) { + // Skipping on this API version due to lack of support for MediaFormat#getInteger, which is + // required for MediaFormatUtil#getColorInfo. + return; + } + DecodeOneFrameTestUtil.decodeOneCacheFileFrame( + checkNotNull(transformationTestResult.filePath), + new DecodeOneFrameTestUtil.Listener() { + @Override + public void onContainerExtracted(MediaFormat mediaFormat) { + @Nullable ColorInfo extractedColor = MediaFormatUtil.getColorInfo(mediaFormat); + assertThat(checkNotNull(extractedColor).colorTransfer).isEqualTo(expectedColorTransfer); + } + + @Override + public void onFrameDecoded(MediaFormat mediaFormat) { + @Nullable ColorInfo decodedColor = MediaFormatUtil.getColorInfo(mediaFormat); + assertThat(checkNotNull(decodedColor).colorTransfer).isEqualTo(expectedColorTransfer); + } + }, + /* surface= */ null); + } } diff --git a/google3/third_party/java_src/android_libs/media/libraries/effect/src/androidTest/java/androidx/media3/effect/FrameProcessorTestUtil.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DecodeOneFrameTestUtil.java similarity index 69% rename from google3/third_party/java_src/android_libs/media/libraries/effect/src/androidTest/java/androidx/media3/effect/FrameProcessorTestUtil.java rename to testutils/src/main/java/com/google/android/exoplayer2/testutil/DecodeOneFrameTestUtil.java index c71403d36f3..da5243b8bdc 100644 --- a/google3/third_party/java_src/android_libs/media/libraries/effect/src/androidTest/java/androidx/media3/effect/FrameProcessorTestUtil.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DecodeOneFrameTestUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.media3.effect; +package com.google.android.exoplayer2.testutil; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; @@ -28,52 +28,96 @@ import android.media.MediaExtractor; import android.media.MediaFormat; import android.view.Surface; -import androidx.media3.common.FrameProcessor; import com.google.android.exoplayer2.util.MimeTypes; import java.nio.ByteBuffer; import org.checkerframework.checker.nullness.qual.Nullable; -/** Utilities for instrumentation tests for {@link FrameProcessor}. */ -public class FrameProcessorTestUtil { +/** Utilities for decoding a frame for tests. */ +public class DecodeOneFrameTestUtil { /** Listener for decoding events. */ - interface Listener { + public interface Listener { /** Called when the video {@link MediaFormat} is extracted from the container. */ - void onVideoMediaFormatExtracted(MediaFormat mediaFormat); + void onContainerExtracted(MediaFormat mediaFormat); - /** Called when the video {@link MediaFormat} is read by the decoder from the byte stream. */ - void onVideoMediaFormatRead(MediaFormat mediaFormat); + /** + * Called when the video {@link MediaFormat} is read by the decoder from the byte stream, after + * a frame is decoded. + */ + void onFrameDecoded(MediaFormat mediaFormat); } /** Timeout for dequeueing buffers from the codec, in microseconds. */ private static final int DEQUEUE_TIMEOUT_US = 5_000_000; /** - * Decodes one frame from the {@code assetFilePath} and renders it to the {@code surface}. + * Reads and decodes one frame from the {@code cacheFilePath} and renders it to the {@code + * surface}. + * + * @param cacheFilePath The path to the file in the cache directory. + * @param listener A {@link Listener} implementation. + * @param surface The {@link Surface} to render the decoded frame to, {@code null} if the decoded + * frame is not needed. + */ + public static void decodeOneCacheFileFrame( + String cacheFilePath, Listener listener, @Nullable Surface surface) throws Exception { + MediaExtractor mediaExtractor = new MediaExtractor(); + try { + mediaExtractor.setDataSource(cacheFilePath); + decodeOneFrame(mediaExtractor, listener, surface); + } finally { + mediaExtractor.release(); + } + } + + /** + * Reads and decodes one frame from the {@code assetFilePath} and renders it to the {@code + * surface}. * * @param assetFilePath The path to the file in the asset directory. * @param listener A {@link Listener} implementation. * @param surface The {@link Surface} to render the decoded frame to, {@code null} if the decoded * frame is not needed. */ - public static void decodeOneFrame( + public static void decodeOneAssetFileFrame( String assetFilePath, Listener listener, @Nullable Surface surface) throws Exception { + MediaExtractor mediaExtractor = new MediaExtractor(); + Context context = getApplicationContext(); + try (AssetFileDescriptor afd = context.getAssets().openFd(assetFilePath)) { + mediaExtractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); + decodeOneFrame(mediaExtractor, listener, surface); + } finally { + mediaExtractor.release(); + } + } + + /** + * Reads and decodes one frame from the {@code mediaExtractor} and renders it to the {@code + * surface}. + * + * @param mediaExtractor The {@link MediaExtractor} with a {@link + * MediaExtractor#setDataSource(String) data source set}. + * @param listener A {@link Listener} implementation. + * @param surface The {@link Surface} to render the decoded frame to, {@code null} if the decoded + * frame is not needed. + */ + private static void decodeOneFrame( + MediaExtractor mediaExtractor, Listener listener, @Nullable Surface surface) + throws Exception { // Set up the extractor to read the first video frame and get its format. if (surface == null) { // Creates a placeholder surface. surface = new Surface(new SurfaceTexture(/* texName= */ 0)); } - MediaExtractor mediaExtractor = new MediaExtractor(); @Nullable MediaCodec mediaCodec = null; @Nullable MediaFormat mediaFormat = null; - Context context = getApplicationContext(); - try (AssetFileDescriptor afd = context.getAssets().openFd(assetFilePath)) { - mediaExtractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); + + try { for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { if (MimeTypes.isVideo(mediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME))) { mediaFormat = mediaExtractor.getTrackFormat(i); - listener.onVideoMediaFormatExtracted(checkNotNull(mediaFormat)); + listener.onContainerExtracted(checkNotNull(mediaFormat)); mediaExtractor.selectTrack(i); break; } @@ -113,7 +157,7 @@ public static void decodeOneFrame( do { outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, DEQUEUE_TIMEOUT_US); if (!decoderFormatRead && outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - listener.onVideoMediaFormatRead(mediaCodec.getOutputFormat()); + listener.onFrameDecoded(mediaCodec.getOutputFormat()); decoderFormatRead = true; } assertThat(outputBufferIndex).isNotEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); @@ -121,12 +165,11 @@ public static void decodeOneFrame( || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); mediaCodec.releaseOutputBuffer(outputBufferIndex, /* render= */ true); } finally { - mediaExtractor.release(); if (mediaCodec != null) { mediaCodec.release(); } } } - private FrameProcessorTestUtil() {} + private DecodeOneFrameTestUtil() {} }