From e4a55844d0c99cdfefb37259a8c3bbe0fb9ddc74 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 11 Mar 2024 06:27:15 -0700 Subject: [PATCH] Add device-specific opt-ins for async MediaCodecAdapter Some devices just don't work very well with the synchronous model, but are currently still excluded from our approximate API 31 check. This change allows to include additional devices or device groups by passing in the Context to the default adapter. It also adopts the workaround added in https://github.com/amzn/exoplayer-amazon-port/commit/ebceee08c6ec0218cd89ffdd867327f44750bffe for Fire TV Smart devices that exhibit this issue. PiperOrigin-RevId: 614642545 --- .../exoplayer/DefaultRenderersFactory.java | 2 +- .../audio/MediaCodecAudioRenderer.java | 4 +- .../DefaultMediaCodecAdapterFactory.java | 37 ++++++++++++++++++- .../mediacodec/MediaCodecAdapter.java | 17 ++++++++- .../video/MediaCodecVideoRenderer.java | 4 +- .../mediacodec/MediaCodecRendererTest.java | 3 +- .../test/utils/CapturingRenderersFactory.java | 8 ++-- 7 files changed, 64 insertions(+), 11 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java index 7dbf06c86d..6c28504521 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java @@ -112,7 +112,7 @@ public class DefaultRenderersFactory implements RenderersFactory { */ public DefaultRenderersFactory(Context context) { this.context = context; - codecAdapterFactory = new DefaultMediaCodecAdapterFactory(); + codecAdapterFactory = new DefaultMediaCodecAdapterFactory(context); extensionRendererMode = EXTENSION_RENDERER_MODE_OFF; allowedVideoJoiningTimeMs = DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS; mediaCodecSelector = MediaCodecSelector.DEFAULT; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java index 0c0765ea49..ddf7164cd1 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java @@ -191,7 +191,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media AudioSink audioSink) { this( context, - MediaCodecAdapter.Factory.DEFAULT, + MediaCodecAdapter.Factory.getDefault(context), mediaCodecSelector, /* enableDecoderFallback= */ false, eventHandler, @@ -219,7 +219,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media AudioSink audioSink) { this( context, - MediaCodecAdapter.Factory.DEFAULT, + MediaCodecAdapter.Factory.getDefault(context), mediaCodecSelector, enableDecoderFallback, eventHandler, diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java index 48ffcc87a0..6699c424cc 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java @@ -17,8 +17,10 @@ package androidx.media3.exoplayer.mediacodec; import static java.lang.annotation.ElementType.TYPE_USE; +import android.content.Context; import android.media.MediaCodec; import androidx.annotation.IntDef; +import androidx.annotation.Nullable; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; @@ -54,12 +56,30 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter. private static final String TAG = "DMCodecAdapterFactory"; + @Nullable private final Context context; + private @Mode int asynchronousMode; private boolean asyncCryptoFlagEnabled; + /** + * @deprecated Use {@link #DefaultMediaCodecAdapterFactory(Context)} instead. + */ + @Deprecated public DefaultMediaCodecAdapterFactory() { asynchronousMode = MODE_DEFAULT; asyncCryptoFlagEnabled = true; + context = null; + } + + /** + * Creates the default media codec adapter factory. + * + * @param context A {@link Context}. + */ + public DefaultMediaCodecAdapterFactory(Context context) { + this.context = context; + asynchronousMode = MODE_DEFAULT; + asyncCryptoFlagEnabled = true; } /** @@ -105,7 +125,7 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter. throws IOException { if (Util.SDK_INT >= 23 && (asynchronousMode == MODE_ENABLED - || (asynchronousMode == MODE_DEFAULT && Util.SDK_INT >= 31))) { + || (asynchronousMode == MODE_DEFAULT && shouldUseAsynchronousAdapterInDefaultMode()))) { int trackType = MimeTypes.getTrackType(configuration.format.sampleMimeType); Log.i( TAG, @@ -118,4 +138,19 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter. } return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration); } + + private boolean shouldUseAsynchronousAdapterInDefaultMode() { + if (Util.SDK_INT >= 31) { + // Asynchronous codec interactions started to be reliable for all devices on API 31+. + return true; + } + // Allow additional devices that work reliably with the asynchronous adapter and show + // performance problems when not using it. + if (context != null + && Util.SDK_INT >= 28 + && context.getPackageManager().hasSystemFeature("com.amazon.hardware.tv_screen")) { + return true; + } + return false; + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecAdapter.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecAdapter.java index 5eb6848320..1adc7b19f5 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecAdapter.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecAdapter.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.mediacodec; +import android.content.Context; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; @@ -120,9 +121,23 @@ public interface MediaCodecAdapter { /** A factory for {@link MediaCodecAdapter} instances. */ interface Factory { - /** Default factory used in most cases. */ + /** + * @deprecated Use {@link #getDefault} instead. + */ + @Deprecated + @SuppressWarnings("deprecation") // Forwarding to deprecated method. Factory DEFAULT = new DefaultMediaCodecAdapterFactory(); + /** + * Returns the default factory that should be used in most cases. + * + * @param context A {@link Context}. + * @return The default factory. + */ + static Factory getDefault(Context context) { + return new DefaultMediaCodecAdapterFactory(context); + } + /** Creates a {@link MediaCodecAdapter} instance. */ MediaCodecAdapter createAdapter(Configuration configuration) throws IOException; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java index 518954d123..1e5d13909e 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java @@ -222,7 +222,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer int maxDroppedFramesToNotify) { this( context, - MediaCodecAdapter.Factory.DEFAULT, + MediaCodecAdapter.Factory.getDefault(context), mediaCodecSelector, allowedJoiningTimeMs, /* enableDecoderFallback= */ false, @@ -256,7 +256,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer int maxDroppedFramesToNotify) { this( context, - MediaCodecAdapter.Factory.DEFAULT, + MediaCodecAdapter.Factory.getDefault(context), mediaCodecSelector, allowedJoiningTimeMs, enableDecoderFallback, diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java index 157bbd7bbc..45c0f177bc 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java @@ -44,6 +44,7 @@ import androidx.media3.exoplayer.drm.DrmSessionManager; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.upstream.DefaultAllocator; import androidx.media3.test.utils.FakeSampleStream; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; @@ -480,7 +481,7 @@ public class MediaCodecRendererTest { public TestRenderer() { super( C.TRACK_TYPE_AUDIO, - MediaCodecAdapter.Factory.DEFAULT, + MediaCodecAdapter.Factory.getDefault(ApplicationProvider.getApplicationContext()), /* mediaCodecSelector= */ (mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> Collections.singletonList( MediaCodecInfo.newInstance( diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java index d11aa70432..515a94974b 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java @@ -81,7 +81,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa */ public CapturingRenderersFactory(Context context) { this.context = context; - this.mediaCodecAdapterFactory = new CapturingMediaCodecAdapter.Factory(); + this.mediaCodecAdapterFactory = new CapturingMediaCodecAdapter.Factory(context); this.audioSink = new CapturingAudioSink(new DefaultAudioSink.Builder(context).build()); this.imageOutput = new CapturingImageOutput(); this.imageDecoderFactory = ImageDecoder.Factory.DEFAULT; @@ -168,9 +168,11 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa private static class Factory implements MediaCodecAdapter.Factory, Dumper.Dumpable { + private final Context context; private final List constructedAdapters; - private Factory() { + private Factory(Context context) { + this.context = context; constructedAdapters = new ArrayList<>(); } @@ -178,7 +180,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException { CapturingMediaCodecAdapter adapter = new CapturingMediaCodecAdapter( - MediaCodecAdapter.Factory.DEFAULT.createAdapter(configuration), + MediaCodecAdapter.Factory.getDefault(context).createAdapter(configuration), configuration.codecInfo.name); constructedAdapters.add(adapter); return adapter;