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