diff --git a/RELEASENOTES.md b/RELEASENOTES.md index afdc7f52c5..33ad5ff71b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -29,6 +29,14 @@ `PlaybackParameters.DEFAULT` instead. * Use an empty string instead of the URI if the media ID is not explicitly set with `MediaItem.Builder.setMediaId(String)`. + * Remove `MediaCodecRenderer.configureCodec()` and add + `MediaCodecRenderer.getMediaCodecConfiguration()`. The new method is + called just before the `MediaCodec` is created and returns the + parameters needed to create and configure the `MediaCodec` instance. + Applications can override `MediaCodecRenderer.onCodecInitialized()` to + get notified after MediaCodec is initialized, or they can inject a + custom `MediaCodecAdapter.Factory` if they want to control how the + `MediaCodec` is configured. * UI: * Add builder for `PlayerNotificationManager`. * Add group setting to `PlayerNotificationManager`. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 7d0f2585c1..0d5379ed79 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -347,9 +347,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - protected void configureCodec( + protected MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, - MediaCodecAdapter codec, Format format, @Nullable MediaCrypto crypto, float codecOperatingRate) { @@ -357,12 +356,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); MediaFormat mediaFormat = getMediaFormat(format, codecInfo.codecMimeType, codecMaxInputSize, codecOperatingRate); - codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0); // Store the input MIME type if we're only using the codec for decryption. boolean decryptOnlyCodecEnabled = MimeTypes.AUDIO_RAW.equals(codecInfo.mimeType) && !MimeTypes.AUDIO_RAW.equals(format.sampleMimeType); decryptOnlyCodecFormat = decryptOnlyCodecEnabled ? format : null; + return new MediaCodecAdapter.Configuration( + codecInfo, mediaFormat, /* surface= */ null, crypto, /* flags= */ 0); } @Override 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 e5c50c1f8e..6d77cdac8d 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 @@ -29,7 +29,9 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; +import com.google.android.exoplayer2.util.TraceUtil; import com.google.common.base.Supplier; +import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -96,13 +98,41 @@ import java.nio.ByteBuffer; } @Override - public AsynchronousMediaCodecAdapter createAdapter(MediaCodec codec) { - return new AsynchronousMediaCodecAdapter( - codec, - callbackThreadSupplier.get(), - queueingThreadSupplier.get(), - forceQueueingSynchronizationWorkaround, - synchronizeCodecInteractionsWithQueueing); + public AsynchronousMediaCodecAdapter createAdapter(Configuration configuration) + throws IOException { + String codecName = configuration.codecInfo.name; + @Nullable AsynchronousMediaCodecAdapter codecAdapter = null; + @Nullable MediaCodec codec = null; + try { + TraceUtil.beginSection("createCodec:" + codecName); + codec = MediaCodec.createByCodecName(codecName); + codecAdapter = + new AsynchronousMediaCodecAdapter( + codec, + callbackThreadSupplier.get(), + queueingThreadSupplier.get(), + forceQueueingSynchronizationWorkaround, + synchronizeCodecInteractionsWithQueueing); + TraceUtil.endSection(); + TraceUtil.beginSection("configureCodec"); + codecAdapter.configure( + configuration.mediaFormat, + configuration.surface, + configuration.crypto, + configuration.flags); + TraceUtil.endSection(); + TraceUtil.beginSection("startCodec"); + codecAdapter.start(); + TraceUtil.endSection(); + return codecAdapter; + } catch (Exception e) { + if (codecAdapter != null) { + codecAdapter.release(); + } else if (codec != null) { + codec.release(); + } + throw e; + } } } @@ -138,8 +168,7 @@ import java.nio.ByteBuffer; this.state = STATE_CREATED; } - @Override - public void configure( + private void configure( @Nullable MediaFormat mediaFormat, @Nullable Surface surface, @Nullable MediaCrypto crypto, @@ -149,8 +178,7 @@ import java.nio.ByteBuffer; state = STATE_CONFIGURED; } - @Override - public void start() { + private void start() { bufferEnqueuer.start(); codec.start(); state = STATE_STARTED; 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 53b7928b47..904ebba2d8 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 @@ -26,6 +26,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; +import java.io.IOException; import java.nio.ByteBuffer; /** @@ -35,6 +36,36 @@ import java.nio.ByteBuffer; * regardless of the mode the {@link MediaCodec} is operating in. */ public interface MediaCodecAdapter { + /** Configuration parameters for a {@link MediaCodecAdapter}. */ + class Configuration { + /** Information about the {@link MediaCodec} being configured. */ + public final MediaCodecInfo codecInfo; + /** The {@link MediaFormat} for which the codec is being configured. */ + public final MediaFormat mediaFormat; + /** For video playbacks, the output where the object will render the decoded frames. */ + @Nullable public final Surface surface; + /** For DRM protected playbacks, a {@link MediaCrypto} to use for decryption. */ + @Nullable public final MediaCrypto crypto; + /** + * Specify CONFIGURE_FLAG_ENCODE to configure the component as an encoder. + * + * @see MediaCodec#configure + */ + public final int flags; + + public Configuration( + MediaCodecInfo codecInfo, + MediaFormat mediaFormat, + @Nullable Surface surface, + @Nullable MediaCrypto crypto, + int flags) { + this.codecInfo = codecInfo; + this.mediaFormat = mediaFormat; + this.surface = surface; + this.crypto = crypto; + this.flags = flags; + } + } /** A factory for {@link MediaCodecAdapter} instances. */ interface Factory { @@ -42,8 +73,8 @@ public interface MediaCodecAdapter { /** Default factory used in most cases. */ Factory DEFAULT = new SynchronousMediaCodecAdapter.Factory(); - /** Creates an instance wrapping the provided {@link MediaCodec} instance. */ - MediaCodecAdapter createAdapter(MediaCodec codec); + /** Creates a {@link MediaCodecAdapter} instance. */ + MediaCodecAdapter createAdapter(Configuration configuration) throws IOException; } /** @@ -55,25 +86,6 @@ public interface MediaCodecAdapter { void onFrameRendered(MediaCodecAdapter codec, long presentationTimeUs, long nanoTime); } - /** - * Configures this adapter and the underlying {@link MediaCodec}. Needs to be called before {@link - * #start()}. - * - * @see MediaCodec#configure(MediaFormat, Surface, MediaCrypto, int) - */ - void configure( - @Nullable MediaFormat mediaFormat, - @Nullable Surface surface, - @Nullable MediaCrypto crypto, - int flags); - - /** - * Starts this instance. Needs to be called after {@link #configure}. - * - * @see MediaCodec#start() - */ - void start(); - /** * Returns the next available input buffer index from the underlying {@link MediaCodec} or {@link * MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists. @@ -192,8 +204,7 @@ public interface MediaCodecAdapter { void setParameters(Bundle params); /** - * Specifies the scaling mode to use, if a surface has been specified in a previous call to {@link - * #configure}. + * Specifies the scaling mode to use, if a surface was specified when the codec was created. * * @see MediaCodec#setVideoScalingMode(int) */ 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 ae53782f18..d2fad6268b 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 @@ -528,18 +528,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { throws DecoderQueryException; /** - * Configures a newly created {@link MediaCodec}. + * Returns the {@link MediaCodecAdapter.Configuration} that will be used to create and configure a + * {@link MediaCodec} to decode the given {@link Format} for a playback. * * @param codecInfo Information about the {@link MediaCodec} being configured. - * @param codec The {@link MediaCodecAdapter} to configure. * @param format The {@link Format} for which the codec is being configured. * @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption. * @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if * no codec operating rate should be set. + * @return The parameters needed to call {@link MediaCodec#configure}. */ - protected abstract void configureCodec( + @Nullable + protected abstract MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, - MediaCodecAdapter codec, Format format, @Nullable MediaCrypto crypto, float codecOperatingRate); @@ -1111,7 +1112,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { long codecInitializedTimestamp; @Nullable MediaCodecAdapter codecAdapter = null; String codecName = codecInfo.name; - float codecOperatingRate = Util.SDK_INT < 23 ? CODEC_OPERATING_RATE_UNSET @@ -1119,35 +1119,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (codecOperatingRate <= assumedMinimumCodecOperatingRate) { codecOperatingRate = CODEC_OPERATING_RATE_UNSET; } - - try { - codecInitializingTimestamp = SystemClock.elapsedRealtime(); - TraceUtil.beginSection("createCodec:" + codecName); - MediaCodec codec = MediaCodec.createByCodecName(codecName); - if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) { - codecAdapter = - new AsynchronousMediaCodecAdapter.Factory( - getTrackType(), - forceAsyncQueueingSynchronizationWorkaround, - enableSynchronizeCodecInteractionsWithQueueing) - .createAdapter(codec); - } else { - codecAdapter = codecAdapterFactory.createAdapter(codec); - } - TraceUtil.endSection(); - TraceUtil.beginSection("configureCodec"); - configureCodec(codecInfo, codecAdapter, inputFormat, crypto, codecOperatingRate); - TraceUtil.endSection(); - TraceUtil.beginSection("startCodec"); - codecAdapter.start(); - TraceUtil.endSection(); - codecInitializedTimestamp = SystemClock.elapsedRealtime(); - } catch (Exception e) { - if (codecAdapter != null) { - codecAdapter.release(); - } - throw e; + codecInitializingTimestamp = SystemClock.elapsedRealtime(); + 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); } + codecInitializedTimestamp = SystemClock.elapsedRealtime(); this.codec = codecAdapter; this.codecInfo = codecInfo; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java index db1401cd52..67fb24f1dd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java @@ -16,10 +16,10 @@ package com.google.android.exoplayer2.mediacodec; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; import android.media.MediaCodec; -import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Bundle; import android.os.Handler; @@ -28,19 +28,51 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; +import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import java.io.IOException; import java.nio.ByteBuffer; /** * A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode. */ -public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { +public class SynchronousMediaCodecAdapter implements MediaCodecAdapter { /** A factory for {@link SynchronousMediaCodecAdapter} instances. */ - public static final class Factory implements MediaCodecAdapter.Factory { + public static class Factory implements MediaCodecAdapter.Factory { + @Override - public MediaCodecAdapter createAdapter(MediaCodec codec) { - return new SynchronousMediaCodecAdapter(codec); + public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException { + @Nullable MediaCodec codec = null; + try { + codec = createCodec(configuration); + TraceUtil.beginSection("configureCodec"); + codec.configure( + configuration.mediaFormat, + configuration.surface, + configuration.crypto, + configuration.flags); + TraceUtil.endSection(); + TraceUtil.beginSection("startCodec"); + codec.start(); + TraceUtil.endSection(); + return new SynchronousMediaCodecAdapter(codec); + } catch (IOException | RuntimeException e) { + if (codec != null) { + codec.release(); + } + throw e; + } + } + + /** Creates a new {@link MediaCodec} instance. */ + protected MediaCodec createCodec(Configuration configuration) throws IOException { + checkNotNull(configuration.codecInfo); + String codecName = configuration.codecInfo.name; + TraceUtil.beginSection("createCodec:" + codecName); + MediaCodec mediaCodec = MediaCodec.createByCodecName(codecName); + TraceUtil.endSection(); + return mediaCodec; } } @@ -50,20 +82,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { this.codec = mediaCodec; - } - - @Override - public void configure( - @Nullable MediaFormat mediaFormat, - @Nullable Surface surface, - @Nullable MediaCrypto crypto, - int flags) { - codec.configure(mediaFormat, surface, crypto, flags); - } - - @Override - public void start() { - codec.start(); if (Util.SDK_INT < 21) { inputByteBuffers = codec.getInputBuffers(); outputByteBuffers = codec.getOutputBuffers(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 651891ef72..56c53f3b26 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -600,9 +600,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected void configureCodec( + protected MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, - MediaCodecAdapter codec, Format format, @Nullable MediaCrypto crypto, float codecOperatingRate) { @@ -625,10 +624,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } surface = dummySurface; } - codec.configure(mediaFormat, surface, crypto, 0); - if (Util.SDK_INT >= 23 && tunneling) { - tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec); - } + return new MediaCodecAdapter.Configuration( + codecInfo, mediaFormat, surface, crypto, /* flags= */ 0); } @Override @@ -688,6 +685,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { codecNeedsSetOutputSurfaceWorkaround = codecNeedsSetOutputSurfaceWorkaround(name); codecHandlesHdr10PlusOutOfBandMetadata = Assertions.checkNotNull(getCodecInfo()).isHdr10PlusOutOfBandMetadataSupported(); + if (Util.SDK_INT >= 23 && tunneling) { + tunnelingOnFrameRenderedListener = + new OnFrameRenderedListenerV23(Assertions.checkNotNull(getCodec())); + } } @Override 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 48ecd2e582..0a19f7c9c5 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 @@ -24,26 +24,30 @@ import android.media.MediaCodec; import android.media.MediaFormat; import android.os.HandlerThread; import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.io.IOException; import java.lang.reflect.Constructor; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.shadows.ShadowLooper; /** Unit tests for {@link AsynchronousMediaCodecAdapter}. */ @RunWith(AndroidJUnit4.class) public class AsynchronousMediaCodecAdapterTest { private AsynchronousMediaCodecAdapter adapter; - private MediaCodec codec; private HandlerThread callbackThread; private HandlerThread queueingThread; private MediaCodec.BufferInfo bufferInfo; @Before - public void setUp() throws IOException { - codec = MediaCodec.createByCodecName("h264"); + public void setUp() throws Exception { + MediaCodecInfo codecInfo = createMediaCodecInfo("h264", "video/mp4"); + MediaCodecAdapter.Configuration configuration = + new MediaCodecAdapter.Configuration( + codecInfo, + createMediaFormat("format"), + /* surface= */ null, + /* crypto= */ null, + /* flags= */ 0); callbackThread = new HandlerThread("TestCallbackThread"); queueingThread = new HandlerThread("TestQueueingThread"); adapter = @@ -52,8 +56,11 @@ public class AsynchronousMediaCodecAdapterTest { /* queueingThreadSupplier= */ () -> queueingThread, /* forceQueueingSynchronizationWorkaround= */ false, /* synchronizeCodecInteractionsWithQueueing= */ false) - .createAdapter(codec); + .createAdapter(configuration); bufferInfo = new MediaCodec.BufferInfo(); + // After start(), the ShadowMediaCodec offers input buffer 0. We advance the looper to make sure + // and messages have been propagated to the adapter. + shadowOf(callbackThread.getLooper()).idle(); } @After @@ -61,40 +68,14 @@ public class AsynchronousMediaCodecAdapterTest { adapter.release(); } - @Test - public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - // After adapter.start(), the ShadowMediaCodec offers one input buffer. We pause the looper so - // that the buffer is not propagated to the adapter. - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - - assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); - } - @Test public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0. We advance the looper to make sure - // and messages have been propagated to the adapter. - shadowOf(callbackThread.getLooper()).idle(); - assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0); } - @Test public void dequeueInputBufferIndex_withMediaCodecError_throwsException() throws Exception { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - // Pause the looper so that we interact with the adapter from this thread only. - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - // Set an error directly on the adapter (not through the looper). adapter.onError(createCodecException()); @@ -103,14 +84,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we - // progress the adapter's looper. We progress the looper so that we call shutdown() on a - // non-empty adapter. - shadowOf(callbackThread.getLooper()).idle(); - adapter.release(); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); @@ -118,14 +91,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - - adapter.start(); - // After start(), the ShadowMediaCodec offers an output format change. We progress the looper - // so that the format change is propagated to the adapter. - shadowOf(callbackThread.getLooper()).idle(); - assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); // Assert that output buffer is available. @@ -135,14 +100,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we - // progress the adapter's looper. - ShadowLooper callbackShadowLooper = shadowOf(callbackThread.getLooper()); - callbackShadowLooper.idle(); - int index = adapter.dequeueInputBufferIndex(); adapter.queueInputBuffer(index, 0, 0, 0, 0); // Progress the queueuing looper first so the asynchronous enqueuer submits the input buffer, @@ -150,7 +107,7 @@ public class AsynchronousMediaCodecAdapterTest { // the callback looper so that the available output buffer callback is handled and the output // buffer reaches the adapter. shadowOf(queueingThread.getLooper()).idle(); - callbackShadowLooper.idle(); + shadowOf(callbackThread.getLooper()).idle(); // The ShadowMediaCodec will first offer an output format and then the output buffer. assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) @@ -162,12 +119,6 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withMediaCodecError_throwsException() throws Exception { - // Pause the looper so that we interact with the adapter from this thread only. - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - // Set an error directly on the adapter. adapter.onError(createCodecException()); @@ -176,18 +127,10 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we - // progress the adapter's looper. - ShadowLooper shadowLooper = shadowOf(callbackThread.getLooper()); - shadowLooper.idle(); - int index = adapter.dequeueInputBufferIndex(); adapter.queueInputBuffer(index, 0, 0, 0, 0); // Progress the looper so that the ShadowMediaCodec processes the input buffer. - shadowLooper.idle(); + shadowOf(callbackThread.getLooper()).idle(); adapter.release(); assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) @@ -196,25 +139,11 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_withoutFormatReceived_throwsException() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - // After start() the ShadowMediaCodec offers an output format change. Pause the looper so that - // the format change is not propagated to the adapter. - shadowOf(callbackThread.getLooper()).pause(); - adapter.start(); - assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat()); } @Test public void getOutputFormat_withMultipleFormats_returnsCorrectFormat() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers an output format, which is available only if we - // progress the adapter's looper. - shadowOf(callbackThread.getLooper()).idle(); - // Add another format on the adapter. adapter.onOutputFormatChanged(createMediaFormat("format2")); @@ -232,23 +161,28 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_afterFlush_returnsPreviousFormat() { - adapter.configure( - createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); - // After start(), the ShadowMediaCodec offers an output format, which is available only if we - // progress the adapter's looper. - ShadowLooper shadowLooper = shadowOf(callbackThread.getLooper()); - shadowLooper.idle(); - adapter.dequeueOutputBufferIndex(bufferInfo); MediaFormat outputFormat = adapter.getOutputFormat(); // Flush the adapter and progress the looper so that flush is completed. adapter.flush(); - shadowLooper.idle(); + shadowOf(callbackThread.getLooper()).idle(); assertThat(adapter.getOutputFormat()).isEqualTo(outputFormat); } + private static MediaCodecInfo createMediaCodecInfo(String name, String mimeType) { + return MediaCodecInfo.newInstance( + name, + mimeType, + /* codecMimeType= */ mimeType, + /* capabilities= */ null, + /* hardwareAccelerated= */ false, + /* softwareOnly= */ false, + /* vendor= */ false, + /* forceDisableAdaptive= */ false, + /* forceSecure= */ false); + } + private static MediaFormat createMediaFormat(String name) { MediaFormat format = new MediaFormat(); format.setString("name", name); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java index 29df04d33f..c389fe9e40 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MediaCodecAdapterWrapper.java @@ -27,6 +27,8 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; +import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter.Configuration; +import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.SynchronousMediaCodecAdapter; import com.google.android.exoplayer2.util.MediaFormatUtil; import com.google.android.exoplayer2.util.MimeTypes; @@ -60,6 +62,36 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private boolean inputStreamEnded; private boolean outputStreamEnded; + private static class Factory extends SynchronousMediaCodecAdapter.Factory { + private final boolean decoder; + + public Factory(boolean decoder) { + this.decoder = decoder; + } + + @Override + protected MediaCodec createCodec(Configuration configuration) throws IOException { + String sampleMimeType = + checkNotNull(configuration.mediaFormat.getString(MediaFormat.KEY_MIME)); + return decoder + ? MediaCodec.createDecoderByType(checkNotNull(sampleMimeType)) + : MediaCodec.createEncoderByType(checkNotNull(sampleMimeType)); + } + } + + private static MediaCodecInfo createPlaceholderMediaCodecInfo() { + return MediaCodecInfo.newInstance( + /* name= */ "name-placeholder", + /* mimeType= */ "mime-type-placeholder", + /* codecMimeType= */ "mime-type-placeholder", + /* capabilities= */ null, + /* hardwareAccelerated= */ false, + /* softwareOnly= */ false, + /* vendor= */ false, + /* forceDisableAdaptive= */ false, + /* forceSecure= */ false); + } + /** * Returns a {@link MediaCodecAdapterWrapper} for a configured and started {@link * MediaCodecAdapter} audio decoder. @@ -70,25 +102,27 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @throws IOException If the underlying codec cannot be created. */ public static MediaCodecAdapterWrapper createForAudioDecoding(Format format) throws IOException { - @Nullable MediaCodec decoder = null; @Nullable MediaCodecAdapter adapter = null; try { - decoder = MediaCodec.createDecoderByType(checkNotNull(format.sampleMimeType)); MediaFormat mediaFormat = MediaFormat.createAudioFormat( - format.sampleMimeType, format.sampleRate, format.channelCount); + checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount); MediaFormatUtil.maybeSetInteger( mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); - adapter = new SynchronousMediaCodecAdapter.Factory().createAdapter(decoder); - adapter.configure(mediaFormat, /* surface= */ null, /* crypto= */ null, /* flags= */ 0); - adapter.start(); + adapter = + new Factory(/* decoder= */ true) + .createAdapter( + new MediaCodecAdapter.Configuration( + createPlaceholderMediaCodecInfo(), + mediaFormat, + /* surface= */ null, + /* crypto= */ null, + /* flags= */ 0)); return new MediaCodecAdapterWrapper(adapter); } catch (Exception e) { if (adapter != null) { adapter.release(); - } else if (decoder != null) { - decoder.release(); } throw e; } @@ -107,18 +141,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Nullable MediaCodec encoder = null; @Nullable MediaCodecAdapter adapter = null; try { - encoder = MediaCodec.createEncoderByType(checkNotNull(format.sampleMimeType)); MediaFormat mediaFormat = MediaFormat.createAudioFormat( - format.sampleMimeType, format.sampleRate, format.channelCount); + checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate); - adapter = new SynchronousMediaCodecAdapter.Factory().createAdapter(encoder); - adapter.configure( - mediaFormat, - /* surface= */ null, - /* crypto= */ null, - /* flags= */ MediaCodec.CONFIGURE_FLAG_ENCODE); - adapter.start(); + adapter = + new Factory(/* decoder= */ false) + .createAdapter( + new MediaCodecAdapter.Configuration( + createPlaceholderMediaCodecInfo(), + mediaFormat, + /* surface= */ null, + /* crypto= */ null, + /* flags= */ MediaCodec.CONFIGURE_FLAG_ENCODE)); return new MediaCodecAdapterWrapper(adapter); } catch (Exception e) { if (adapter != null) { diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java index 580f0e72a3..f0c7fd4664 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java @@ -131,19 +131,15 @@ import java.util.ArrayList; } @Override - protected void configureCodec( - MediaCodecInfo codecInfo, - MediaCodecAdapter codec, - Format format, - MediaCrypto crypto, - float operatingRate) { + protected MediaCodecAdapter.Configuration getMediaCodecConfiguration( + MediaCodecInfo codecInfo, Format format, MediaCrypto crypto, float operatingRate) { // If the codec is being initialized whilst the renderer is started, default behavior is to // render the first frame (i.e. the keyframe before the current position), then drop frames up // to the current playback position. For test runs that place a maximum limit on the number of // dropped frames allowed, this is not desired behavior. Hence we skip (rather than drop) // frames up to the current playback position [Internal: b/66494991]. skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED; - super.configureCodec(codecInfo, codec, format, crypto, operatingRate); + return super.getMediaCodecConfiguration(codecInfo, format, crypto, operatingRate); } @Override diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingRenderersFactory.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingRenderersFactory.java index b3b837a087..1650ee6396 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingRenderersFactory.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingRenderersFactory.java @@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkState; import android.content.Context; import android.media.MediaCodec; -import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Bundle; import android.os.Handler; @@ -46,6 +45,7 @@ import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.common.collect.ImmutableList; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -121,10 +121,11 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa @RequiresApi(18) @Override - public MediaCodecAdapter createAdapter(MediaCodec codec) { + public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException { CapturingMediaCodecAdapter adapter = new CapturingMediaCodecAdapter( - MediaCodecAdapter.Factory.DEFAULT.createAdapter(codec), codec.getName()); + MediaCodecAdapter.Factory.DEFAULT.createAdapter(configuration), + configuration.codecInfo.name); constructedAdapters.add(adapter); return adapter; } @@ -167,20 +168,6 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa // MediaCodecAdapter implementation - @Override - public void configure( - @Nullable MediaFormat mediaFormat, - @Nullable Surface surface, - @Nullable MediaCrypto crypto, - int flags) { - delegate.configure(mediaFormat, surface, crypto, flags); - } - - @Override - public void start() { - delegate.start(); - } - @Override public int dequeueInputBufferIndex() { return delegate.dequeueInputBufferIndex();