diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 793a5bacef..c880d831e8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,6 +3,10 @@ ### dev-v2 (not yet released) * Core Library: + * Enable MediaCodec asynchronous queueing by default on devices with API + level >= 31. Add methods in `DefaultMediaCodecRendererFactory` and + `DefaultRenderersFactory` to force enable or force disable asynchronous + queueing ([6348](https://github.com/google/ExoPlayer/issues/6348)). * Move `com.google.android.exoplayer2.device.DeviceInfo` to `com.google.android.exoplayer2.DeviceInfo`. * Move `com.google.android.exoplayer2.drm.DecryptionException` to @@ -13,8 +17,8 @@ `GlUtil.glAssertionsEnabled` instead. * Move `Player.addListener(EventListener)` and `Player.removeListener(EventListener)` out of `Player` into subclasses. - * Fix `mediaMetadata` being reset when media is - repeated ([#9458](https://github.com/google/ExoPlayer/issues/9458)). + * Fix `mediaMetadata` being reset when media is repeated + ([#9458](https://github.com/google/ExoPlayer/issues/9458)). * Video: * Fix bug in `MediaCodecVideoRenderer` that resulted in re-using a released `Surface` when playing without an app-provided `Surface` diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index d4284842df..03ccc297a8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.DefaultAudioSink; import com.google.android.exoplayer2.audio.DefaultAudioSink.DefaultAudioProcessorChain; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; +import com.google.android.exoplayer2.mediacodec.DefaultMediaCodecAdapterFactory; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.metadata.MetadataRenderer; @@ -87,13 +88,11 @@ public class DefaultRenderersFactory implements RenderersFactory { private static final String TAG = "DefaultRenderersFactory"; private final Context context; + private final DefaultMediaCodecAdapterFactory codecAdapterFactory; @ExtensionRendererMode private int extensionRendererMode; private long allowedVideoJoiningTimeMs; private boolean enableDecoderFallback; private MediaCodecSelector mediaCodecSelector; - private boolean enableAsyncQueueing; - private boolean forceAsyncQueueingSynchronizationWorkaround; - private boolean enableSynchronizeCodecInteractionsWithQueueing; private boolean enableFloatOutput; private boolean enableAudioTrackPlaybackParams; private boolean enableOffload; @@ -101,6 +100,7 @@ public class DefaultRenderersFactory implements RenderersFactory { /** @param context A {@link Context}. */ public DefaultRenderersFactory(Context context) { this.context = context; + codecAdapterFactory = new DefaultMediaCodecAdapterFactory(); extensionRendererMode = EXTENSION_RENDERER_MODE_OFF; allowedVideoJoiningTimeMs = DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS; mediaCodecSelector = MediaCodecSelector.DEFAULT; @@ -130,6 +130,7 @@ public class DefaultRenderersFactory implements RenderersFactory { this.extensionRendererMode = extensionRendererMode; this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs; mediaCodecSelector = MediaCodecSelector.DEFAULT; + codecAdapterFactory = new DefaultMediaCodecAdapterFactory(); } /** @@ -149,34 +150,28 @@ public class DefaultRenderersFactory implements RenderersFactory { } /** - * Enable asynchronous buffer queueing for both {@link MediaCodecAudioRenderer} and {@link - * MediaCodecVideoRenderer} instances. + * Enables {@link com.google.android.exoplayer2.mediacodec.MediaCodecRenderer} instances to + * operate their {@link MediaCodec} in asynchronous mode and perform asynchronous queueing. * - *
This method is experimental, and will be renamed or removed in a future release. + *
This feature can be enabled only on devices with API versions >= 23. For devices with + * older API versions, this method is a no-op. * - * @param enabled Whether asynchronous queueing is enabled. * @return This factory, for convenience. */ - public DefaultRenderersFactory experimentalSetAsynchronousBufferQueueingEnabled(boolean enabled) { - enableAsyncQueueing = enabled; + public DefaultRenderersFactory forceEnableMediaCodecAsynchronousQueueing() { + codecAdapterFactory.forceEnableAsynchronous(); return this; } /** - * Enable the asynchronous queueing synchronization workaround. + * Disables {@link com.google.android.exoplayer2.mediacodec.MediaCodecRenderer} instances from + * operating their {@link MediaCodec} in asynchronous mode and perform asynchronous queueing. + * {@link MediaCodec} instances will be operated synchronous mode. * - *
When enabled, the queueing threads for {@link MediaCodec} instances will synchronize on a - * shared lock when submitting buffers to the respective {@link MediaCodec}. - * - *
This method is experimental, and will be renamed or removed in a future release.
- *
- * @param enabled Whether the asynchronous queueing synchronization workaround is enabled by
- * default.
* @return This factory, for convenience.
*/
- public DefaultRenderersFactory experimentalSetForceAsyncQueueingSynchronizationWorkaround(
- boolean enabled) {
- this.forceAsyncQueueingSynchronizationWorkaround = enabled;
+ public DefaultRenderersFactory forceDisableMediaCodecAsynchronousQueueing() {
+ codecAdapterFactory.forceDisableAsynchronous();
return this;
}
@@ -191,7 +186,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
*/
public DefaultRenderersFactory experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(
boolean enabled) {
- enableSynchronizeCodecInteractionsWithQueueing = enabled;
+ codecAdapterFactory.experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(enabled);
return this;
}
@@ -373,17 +368,13 @@ public class DefaultRenderersFactory implements RenderersFactory {
MediaCodecVideoRenderer videoRenderer =
new MediaCodecVideoRenderer(
context,
+ codecAdapterFactory,
mediaCodecSelector,
allowedVideoJoiningTimeMs,
enableDecoderFallback,
eventHandler,
eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
- videoRenderer.experimentalSetAsynchronousBufferQueueingEnabled(enableAsyncQueueing);
- videoRenderer.experimentalSetForceAsyncQueueingSynchronizationWorkaround(
- forceAsyncQueueingSynchronizationWorkaround);
- videoRenderer.experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(
- enableSynchronizeCodecInteractionsWithQueueing);
out.add(videoRenderer);
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
@@ -497,16 +488,12 @@ public class DefaultRenderersFactory implements RenderersFactory {
MediaCodecAudioRenderer audioRenderer =
new MediaCodecAudioRenderer(
context,
+ codecAdapterFactory,
mediaCodecSelector,
enableDecoderFallback,
eventHandler,
eventListener,
audioSink);
- audioRenderer.experimentalSetAsynchronousBufferQueueingEnabled(enableAsyncQueueing);
- audioRenderer.experimentalSetForceAsyncQueueingSynchronizationWorkaround(
- forceAsyncQueueingSynchronizationWorkaround);
- audioRenderer.experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(
- enableSynchronizeCodecInteractionsWithQueueing);
out.add(audioRenderer);
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
index 5832144f5a..8302bd2903 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
@@ -49,15 +49,11 @@ import java.nio.ByteBuffer;
public static final class Factory implements MediaCodecAdapter.Factory {
private final Supplier By default, this factory {@link #createAdapter creates} {@link AsynchronousMediaCodecAdapter}
+ * instances on devices with API level >= 31 (Android 12+). For devices with older API versions,
+ * the default behavior is to create {@link SynchronousMediaCodecAdapter} instances. The factory
+ * offers APIs to force the creation of {@link AsynchronousMediaCodecAdapter} (applicable for
+ * devices with API >= 23) or {@link SynchronousMediaCodecAdapter} instances.
+ */
+public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.Factory {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({MODE_DEFAULT, MODE_ENABLED, MODE_DISABLED})
+ private @interface Mode {}
+
+ private static final int MODE_DEFAULT = 0;
+ private static final int MODE_ENABLED = 1;
+ private static final int MODE_DISABLED = 2;
+
+ private static final String TAG = "DefaultMediaCodecAdapterFactory";
+
+ @Mode private int asynchronousMode;
+ private boolean enableSynchronizeCodecInteractionsWithQueueing;
+
+ public DefaultMediaCodecAdapterFactory() {
+ asynchronousMode = MODE_DEFAULT;
+ }
+
+ /**
+ * Forces this factory to always create {@link AsynchronousMediaCodecAdapter} instances, provided
+ * the device API level is >= 23. For devices with API level < 23, the factory will create
+ * {@link SynchronousMediaCodecAdapter SynchronousMediaCodecAdapters}.
+ *
+ * @return This factory, for convenience.
+ */
+ public DefaultMediaCodecAdapterFactory forceEnableAsynchronous() {
+ asynchronousMode = MODE_ENABLED;
+ return this;
+ }
+
+ /**
+ * Forces the factory to always create {@link SynchronousMediaCodecAdapter} instances.
+ *
+ * @return This factory, for convenience.
+ */
+ public DefaultMediaCodecAdapterFactory forceDisableAsynchronous() {
+ asynchronousMode = MODE_DISABLED;
+ return this;
+ }
+
+ /**
+ * Enable synchronizing codec interactions with asynchronous buffer queueing.
+ *
+ * This method is experimental, and will be renamed or removed in a future release.
+ *
+ * @param enabled Whether codec interactions will be synchronized with asynchronous buffer
+ * queueing.
+ */
+ public void experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(boolean enabled) {
+ enableSynchronizeCodecInteractionsWithQueueing = enabled;
+ }
+
+ @Override
+ public MediaCodecAdapter createAdapter(MediaCodecAdapter.Configuration configuration)
+ throws IOException {
+ if ((asynchronousMode == MODE_ENABLED && Util.SDK_INT >= 23)
+ || (asynchronousMode == MODE_DEFAULT && Util.SDK_INT >= 31)) {
+ int trackType = MimeTypes.getTrackType(configuration.format.sampleMimeType);
+ Log.i(
+ TAG,
+ "Creating an asynchronous MediaCodec adapter for track type "
+ + Util.getTrackTypeString(trackType));
+ AsynchronousMediaCodecAdapter.Factory factory =
+ new AsynchronousMediaCodecAdapter.Factory(
+ trackType, enableSynchronizeCodecInteractionsWithQueueing);
+ return factory.createAdapter(configuration);
+ }
+ return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration);
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java
index 2353baf516..ffd7758680 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec;
@@ -179,7 +178,7 @@ public interface MediaCodecAdapter {
interface Factory {
/** Default factory used in most cases. */
- Factory DEFAULT = new SynchronousMediaCodecAdapter.Factory();
+ Factory DEFAULT = new DefaultMediaCodecAdapterFactory();
/** Creates a {@link MediaCodecAdapter} instance. */
MediaCodecAdapter createAdapter(Configuration configuration) throws IOException;
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
index 0836102ae6..389e675c59 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
@@ -350,9 +350,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean outputStreamEnded;
private boolean waitingForFirstSampleInFormat;
private boolean pendingOutputEndOfStream;
- private boolean enableAsynchronousBufferQueueing;
- private boolean forceAsyncQueueingSynchronizationWorkaround;
- private boolean enableSynchronizeCodecInteractionsWithQueueing;
@Nullable private ExoPlaybackException pendingPlaybackException;
protected DecoderCounters decoderCounters;
private long outputStreamStartPositionUs;
@@ -428,46 +425,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
this.renderTimeLimitMs = renderTimeLimitMs;
}
- /**
- * Enables asynchronous input buffer queueing.
- *
- * Operates the underlying {@link MediaCodec} in asynchronous mode and submits input buffers
- * from a separate thread to unblock the playback thread.
- *
- * This method is experimental, and will be renamed or removed in a future release. It should
- * only be called before the renderer is used.
- */
- public void experimentalSetAsynchronousBufferQueueingEnabled(boolean enabled) {
- enableAsynchronousBufferQueueing = enabled;
- }
-
- /**
- * Enables the asynchronous queueing synchronization workaround.
- *
- * When enabled, the queueing threads for {@link MediaCodec} instance will synchronize on a
- * shared lock when submitting buffers to the respective {@link MediaCodec}.
- *
- * This method is experimental, and will be renamed or removed in a future release. It should
- * only be called before the renderer is used.
- */
- public void experimentalSetForceAsyncQueueingSynchronizationWorkaround(boolean enabled) {
- this.forceAsyncQueueingSynchronizationWorkaround = enabled;
- }
-
- /**
- * Enables synchronizing codec interactions with asynchronous buffer queueing.
- *
- * When enabled, codec interactions will wait until all input buffers pending for asynchronous
- * queueing are submitted to the {@link MediaCodec} first. This method is effective only if {@link
- * #experimentalSetAsynchronousBufferQueueingEnabled asynchronous buffer queueing} is enabled.
- *
- * This method is experimental, and will be renamed or removed in a future release. It should
- * only be called before the renderer is used.
- */
- public void experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(boolean enabled) {
- enableSynchronizeCodecInteractionsWithQueueing = enabled;
- }
-
@Override
@AdaptiveSupport
public final int supportsMixedMimeTypeAdaptation() {
@@ -520,7 +477,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* no codec operating rate should be set.
* @return The parameters needed to call {@link MediaCodec#configure}.
*/
- @Nullable
protected abstract MediaCodecAdapter.Configuration getMediaCodecConfiguration(
MediaCodecInfo codecInfo,
Format format,
@@ -1092,7 +1048,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private void initCodec(MediaCodecInfo codecInfo, MediaCrypto crypto) throws Exception {
long codecInitializingTimestamp;
long codecInitializedTimestamp;
- @Nullable MediaCodecAdapter codecAdapter = null;
String codecName = codecInfo.name;
float codecOperatingRate =
Util.SDK_INT < 23
@@ -1105,19 +1060,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
TraceUtil.beginSection("createCodec:" + codecName);
MediaCodecAdapter.Configuration configuration =
getMediaCodecConfiguration(codecInfo, inputFormat, crypto, codecOperatingRate);
- if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) {
- codecAdapter =
- new AsynchronousMediaCodecAdapter.Factory(
- getTrackType(),
- forceAsyncQueueingSynchronizationWorkaround,
- enableSynchronizeCodecInteractionsWithQueueing)
- .createAdapter(configuration);
- } else {
- codecAdapter = codecAdapterFactory.createAdapter(configuration);
- }
+ codec = codecAdapterFactory.createAdapter(configuration);
codecInitializedTimestamp = SystemClock.elapsedRealtime();
- this.codec = codecAdapter;
this.codecInfo = codecInfo;
this.codecOperatingRate = codecOperatingRate;
codecInputFormat = inputFormat;
@@ -1133,7 +1078,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecNeedsMonoChannelCountWorkaround(codecName, codecInputFormat);
codecNeedsEosPropagation =
codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation();
- if (codecAdapter.needsReconfiguration()) {
+ if (codec.needsReconfiguration()) {
this.codecReconfigured = true;
this.codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
this.codecNeedsAdaptationWorkaroundBuffer =
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
index 94410ab11f..9aa2b5f3ba 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
@@ -54,7 +54,6 @@ public class AsynchronousMediaCodecAdapterTest {
new AsynchronousMediaCodecAdapter.Factory(
/* callbackThreadSupplier= */ () -> callbackThread,
/* queueingThreadSupplier= */ () -> queueingThread,
- /* forceQueueingSynchronizationWorkaround= */ false,
/* synchronizeCodecInteractionsWithQueueing= */ false)
.createAdapter(configuration);
bufferInfo = new MediaCodec.BufferInfo();
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
index e5fdd126f4..f3a08df819 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
@@ -56,11 +56,7 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
codec.start();
handlerThread = new TestHandlerThread("TestHandlerThread");
enqueuer =
- new AsynchronousMediaCodecBufferEnqueuer(
- codec,
- handlerThread,
- /* forceQueueingSynchronizationWorkaround= */ false,
- mockConditionVariable);
+ new AsynchronousMediaCodecBufferEnqueuer(codec, handlerThread, mockConditionVariable);
}
@After