mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add a Frame Extractor test with rotated input
Adds a Frame extractor test that verifies decoder respects rotation metadata from the mp4 container. Do not rely on the MediaCodec decoder rotate the input. Rotate via a video effect instead. PiperOrigin-RevId: 698381282
This commit is contained in:
parent
66e8b53b43
commit
d92c9aa8a1
Binary file not shown.
After Width: | Height: | Size: 488 KiB |
@ -20,6 +20,7 @@ import static androidx.media3.exoplayer.SeekParameters.CLOSEST_SYNC;
|
||||
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 androidx.media3.transformer.AndroidTestUtil.MP4_TRIM_OPTIMIZATION_270;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
@ -341,4 +342,32 @@ public class FrameExtractorTest {
|
||||
instrumentation.runOnMainSync(frameExtractor::release);
|
||||
frameExtractor = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractFrame_oneFrameRotated_returnsFrameInCorrectOrientation() throws Exception {
|
||||
frameExtractor =
|
||||
new ExperimentalFrameExtractor(
|
||||
context,
|
||||
new ExperimentalFrameExtractor.Configuration.Builder().build(),
|
||||
MediaItem.fromUri(MP4_TRIM_OPTIMIZATION_270.uri),
|
||||
/* effects= */ ImmutableList.of());
|
||||
|
||||
ListenableFuture<Frame> frameFuture = frameExtractor.getFrame(/* positionMs= */ 0);
|
||||
Frame frame = frameFuture.get(TIMEOUT_SECONDS, SECONDS);
|
||||
Bitmap actualBitmap = frame.bitmap;
|
||||
Bitmap expectedBitmap =
|
||||
readBitmap(
|
||||
/* assetString= */ GOLDEN_ASSET_FOLDER_PATH
|
||||
+ "internal_emulator_transformer_output_180_rotated_0.000.png");
|
||||
maybeSaveTestBitmap(testId, /* bitmapLabel= */ "actual", actualBitmap, /* path= */ null);
|
||||
|
||||
assertThat(frame.presentationTimeMs).isEqualTo(0);
|
||||
assertBitmapsAreSimilar(expectedBitmap, actualBitmap, PSNR_THRESHOLD);
|
||||
assertThat(
|
||||
frameExtractor
|
||||
.getDecoderCounters()
|
||||
.get(TIMEOUT_SECONDS, SECONDS)
|
||||
.renderedOutputBufferCount)
|
||||
.isEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
@ -47,9 +47,12 @@ import androidx.media3.effect.GlEffect;
|
||||
import androidx.media3.effect.GlShaderProgram;
|
||||
import androidx.media3.effect.MatrixTransformation;
|
||||
import androidx.media3.effect.PassthroughShaderProgram;
|
||||
import androidx.media3.effect.ScaleAndRotateTransformation;
|
||||
import androidx.media3.exoplayer.DecoderCounters;
|
||||
import androidx.media3.exoplayer.DecoderReuseEvaluation;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.FormatHolder;
|
||||
import androidx.media3.exoplayer.Renderer;
|
||||
import androidx.media3.exoplayer.SeekParameters;
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||
@ -379,6 +382,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private static final class FrameExtractorRenderer extends MediaCodecVideoRenderer {
|
||||
|
||||
private boolean frameRenderedSinceLastReset;
|
||||
private List<Effect> effectsFromPlayer;
|
||||
private @MonotonicNonNull Effect rotation;
|
||||
|
||||
public FrameExtractorRenderer(
|
||||
Context context, VideoRendererEventListener videoRendererEventListener) {
|
||||
@ -389,6 +394,43 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
Util.createHandlerForCurrentOrMainLooper(),
|
||||
videoRendererEventListener,
|
||||
/* maxDroppedFramesToNotify= */ 0);
|
||||
effectsFromPlayer = ImmutableList.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoEffects(List<Effect> effects) {
|
||||
effectsFromPlayer = effects;
|
||||
setEffectsWithRotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||
throws ExoPlaybackException {
|
||||
if (formatHolder.format != null) {
|
||||
Format format = formatHolder.format;
|
||||
if (format.rotationDegrees != 0) {
|
||||
// Some decoders do not apply rotation. It's no extra cost to rotate with a GL matrix
|
||||
// transformation effect instead.
|
||||
// https://developer.android.com/reference/android/media/MediaCodec#transformations-when-rendering-onto-surface
|
||||
rotation =
|
||||
new ScaleAndRotateTransformation.Builder()
|
||||
.setRotationDegrees(360 - format.rotationDegrees)
|
||||
.build();
|
||||
setEffectsWithRotation();
|
||||
formatHolder.format = format.buildUpon().setRotationDegrees(0).build();
|
||||
}
|
||||
}
|
||||
return super.onInputFormatChanged(formatHolder);
|
||||
}
|
||||
|
||||
private void setEffectsWithRotation() {
|
||||
ImmutableList.Builder<Effect> effectBuilder = new ImmutableList.Builder<>();
|
||||
if (rotation != null) {
|
||||
effectBuilder.add(rotation);
|
||||
}
|
||||
effectBuilder.addAll(effectsFromPlayer);
|
||||
super.setVideoEffects(effectBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user