diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.000.png b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.000.png new file mode 100644 index 0000000000..118398eaff Binary files /dev/null and b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.000.png differ diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.033.png b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.033.png new file mode 100644 index 0000000000..89890c4226 Binary files /dev/null and b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.033.png differ diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.066.png b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.066.png new file mode 100644 index 0000000000..584e2e4123 Binary files /dev/null and b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_0.066.png differ diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_17.029.png b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_17.029.png new file mode 100644 index 0000000000..acb7f952a0 Binary files /dev/null and b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_17.029.png differ diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_8.531.png b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_8.531.png new file mode 100644 index 0000000000..2edfdcc922 Binary files /dev/null and b/libraries/test_data/src/test/assets/test-generated-goldens/FrameExtractorTest/sample_with_increasing_timestamps_360p_8.531.png differ diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameExtractorTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameExtractorTest.java index 8082b33855..8a404b1813 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameExtractorTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameExtractorTest.java @@ -17,6 +17,8 @@ package androidx.media3.transformer; import static androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND; import static androidx.media3.test.utils.BitmapPixelTestUtil.maybeSaveTestBitmap; +import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap; +import static androidx.media3.test.utils.TestUtil.assertBitmapsAreSimilar; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.concurrent.TimeUnit.SECONDS; @@ -33,9 +35,13 @@ import androidx.media3.transformer.ExperimentalFrameExtractor.Frame; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -49,9 +55,15 @@ import org.junit.runner.RunWith; /** End-to-end instrumentation test for {@link ExperimentalFrameExtractor}. */ @RunWith(AndroidJUnit4.class) public class FrameExtractorTest { + // Golden files are generated by MediaMetadataRetriever running on Pixel 8. + private static final String GOLDEN_ASSET_FOLDER_PATH = + "test-generated-goldens/FrameExtractorTest/"; private static final String FILE_PATH = "asset:///media/mp4/sample_with_increasing_timestamps_360p.mp4"; private static final long TIMEOUT_SECONDS = 10; + // TODO: b/350498258 - Due to bugs in hardware decoders, we can only assert for low PSNR values. + // Move to using software decoders in pixel tests, and increase PSNR threshold. + private static final float PSNR_THRESHOLD = 25f; @Rule public final TestName testName = new TestName(); @@ -77,15 +89,16 @@ public class FrameExtractorTest { frameExtractor = new ExperimentalFrameExtractor(context, MediaItem.fromUri(FILE_PATH)); ListenableFuture frameFuture = frameExtractor.getFrame(/* positionMs= */ 8_500); - Bitmap bitmap = frameFuture.get(TIMEOUT_SECONDS, SECONDS).bitmap; + Frame frame = frameFuture.get(TIMEOUT_SECONDS, SECONDS); + Bitmap actualBitmap = frame.bitmap; + Bitmap expectedBitmap = + readBitmap( + /* assetString= */ GOLDEN_ASSET_FOLDER_PATH + + "sample_with_increasing_timestamps_360p_8.531.png"); + maybeSaveTestBitmap(testId, /* bitmapLabel= */ "actual", actualBitmap, /* path= */ null); - maybeSaveTestBitmap(testId, /* bitmapLabel= */ "actual", bitmap, /* path= */ null); - assertThat(frameFuture.get(TIMEOUT_SECONDS, SECONDS).presentationTimeMs).isEqualTo(8_531); - // TODO: b/350498258 - Actually check Bitmap contents. Due to bugs in hardware decoders, - // such a test would require a too high tolerance. - assertThat(bitmap.getWidth()).isEqualTo(640); - assertThat(bitmap.getHeight()).isEqualTo(360); - assertThat(bitmap.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888); + assertThat(frame.presentationTimeMs).isEqualTo(8_531); + assertBitmapsAreSimilar(expectedBitmap, actualBitmap, PSNR_THRESHOLD); } @Test @@ -93,27 +106,43 @@ public class FrameExtractorTest { frameExtractor = new ExperimentalFrameExtractor(context, MediaItem.fromUri(FILE_PATH)); ListenableFuture frameFuture = frameExtractor.getFrame(/* positionMs= */ 200_000); + Frame frame = frameFuture.get(TIMEOUT_SECONDS, SECONDS); + Bitmap actualBitmap = frame.bitmap; int lastVideoFramePresentationTimeMs = 17_029; + Bitmap expectedBitmap = + readBitmap( + /* assetString= */ GOLDEN_ASSET_FOLDER_PATH + + "sample_with_increasing_timestamps_360p_17.029.png"); + maybeSaveTestBitmap(testId, /* bitmapLabel= */ "actual", actualBitmap, /* path= */ null); - assertThat(frameFuture.get(TIMEOUT_SECONDS, SECONDS).presentationTimeMs) - .isEqualTo(lastVideoFramePresentationTimeMs); + assertThat(frame.presentationTimeMs).isEqualTo(lastVideoFramePresentationTimeMs); + assertBitmapsAreSimilar(expectedBitmap, actualBitmap, PSNR_THRESHOLD); } @Test public void extractFrame_repeatedPositionMs_returnsTheSameFrame() throws Exception { frameExtractor = new ExperimentalFrameExtractor(context, MediaItem.fromUri(FILE_PATH)); + ImmutableList requestedFramePositionsMs = ImmutableList.of(0L, 0L, 33L, 34L, 34L); + ImmutableList expectedFramePositionsMs = ImmutableList.of(0L, 0L, 33L, 66L, 66L); + List> frameFutures = new ArrayList<>(); - ListenableFuture frame0 = frameExtractor.getFrame(/* positionMs= */ 0); - ListenableFuture frame0Again = frameExtractor.getFrame(/* positionMs= */ 0); - ListenableFuture frame33 = frameExtractor.getFrame(/* positionMs= */ 33); - ListenableFuture frame34 = frameExtractor.getFrame(/* positionMs= */ 34); - ListenableFuture frame34Again = frameExtractor.getFrame(/* positionMs= */ 34); + for (long positionMs : requestedFramePositionsMs) { + frameFutures.add(frameExtractor.getFrame(positionMs)); + } + for (int i = 0; i < expectedFramePositionsMs.size(); i++) { + ListenableFuture frameListenableFuture = frameFutures.get(i); + Frame frame = frameListenableFuture.get(TIMEOUT_SECONDS, SECONDS); + maybeSaveTestBitmap(testId, /* bitmapLabel= */ "actual_" + i, frame.bitmap, /* path= */ null); + Bitmap expectedBitmap = + readBitmap( + /* assetString= */ GOLDEN_ASSET_FOLDER_PATH + + "sample_with_increasing_timestamps_360p_" + + String.format(Locale.US, "%.3f", frame.presentationTimeMs / 1000f) + + ".png"); - assertThat(frame0.get(TIMEOUT_SECONDS, SECONDS).presentationTimeMs).isEqualTo(0); - assertThat(frame0Again.get(TIMEOUT_SECONDS, SECONDS).presentationTimeMs).isEqualTo(0); - assertThat(frame33.get(TIMEOUT_SECONDS, SECONDS).presentationTimeMs).isEqualTo(33); - assertThat(frame34.get(TIMEOUT_SECONDS, SECONDS).presentationTimeMs).isEqualTo(66); - assertThat(frame34Again.get(TIMEOUT_SECONDS, SECONDS).presentationTimeMs).isEqualTo(66); + assertBitmapsAreSimilar(expectedBitmap, frame.bitmap, PSNR_THRESHOLD); + assertThat(frame.presentationTimeMs).isEqualTo(expectedFramePositionsMs.get(i)); + } } @Test