From 92ec1ab628e54aa46c286d0f1402f5481b027015 Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 6 Nov 2020 12:02:01 +0000 Subject: [PATCH] Add more MediaCodec methods to MediaCodecAdapter Add more MediaCodec methods to MediaCodedAdapter so that renderers interact with the MediaCodec through the MediaCodecAdapter. PiperOrigin-RevId: 341023452 --- .../AsynchronousMediaCodecAdapter.java | 51 +++++++++++-- .../mediacodec/MediaCodecAdapter.java | 73 +++++++++++++++++-- .../mediacodec/MediaCodecRenderer.java | 4 +- .../SynchronousMediaCodecAdapter.java | 44 ++++++++++- .../AsynchronousMediaCodecAdapterTest.java | 7 +- 5 files changed, 158 insertions(+), 21 deletions(-) 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 a705ec4208..8bc720b370 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 @@ -19,6 +19,8 @@ package com.google.android.exoplayer2.mediacodec; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; +import android.os.Bundle; +import android.os.Handler; import android.os.HandlerThread; import android.view.Surface; import androidx.annotation.IntDef; @@ -26,6 +28,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Renderer.VideoScalingMode; import com.google.android.exoplayer2.decoder.CryptoInfo; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -108,6 +111,16 @@ import java.nio.ByteBuffer; bufferEnqueuer.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags); } + @Override + public void releaseOutputBuffer(int index, boolean render) { + codec.releaseOutputBuffer(index, render); + } + + @Override + public void releaseOutputBuffer(int index, long renderTimeStampNs) { + codec.releaseOutputBuffer(index, renderTimeStampNs); + } + @Override public int dequeueInputBufferIndex() { return asynchronousMediaCodecCallback.dequeueInputBufferIndex(); @@ -148,14 +161,14 @@ import java.nio.ByteBuffer; } @Override - public void shutdown() { - if (state == STATE_STARTED) { - bufferEnqueuer.shutdown(); - } - if (state == STATE_CONFIGURED || state == STATE_STARTED) { + public void release() { + if (state == STATE_STARTED) { + bufferEnqueuer.shutdown(); + } + if (state == STATE_CONFIGURED || state == STATE_STARTED) { asynchronousMediaCodecCallback.shutdown(); - } - state = STATE_SHUT_DOWN; + } + state = STATE_SHUT_DOWN; } @Override @@ -163,6 +176,30 @@ import java.nio.ByteBuffer; return codec; } + @Override + public void setOnFrameRenderedListener(OnFrameRenderedListener listener, Handler handler) { + codec.setOnFrameRenderedListener( + (codec, presentationTimeUs, nanoTime) -> + listener.onFrameRendered( + AsynchronousMediaCodecAdapter.this, presentationTimeUs, nanoTime), + handler); + } + + @Override + public void setOutputSurface(Surface surface) { + codec.setOutputSurface(surface); + } + + @Override + public void setParameters(Bundle params) { + codec.setParameters(params); + } + + @Override + public void setVideoScalingMode(@VideoScalingMode int scalingMode) { + codec.setVideoScalingMode(scalingMode); + } + @VisibleForTesting /* package */ void onError(MediaCodec.CodecException error) { asynchronousMediaCodecCallback.onError(codec, error); 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 5d785d650c..6c43a529c6 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 @@ -19,8 +19,12 @@ package com.google.android.exoplayer2.mediacodec; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; +import android.os.Bundle; +import android.os.Handler; import android.view.Surface; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import com.google.android.exoplayer2.Renderer.VideoScalingMode; import com.google.android.exoplayer2.decoder.CryptoInfo; import java.nio.ByteBuffer; @@ -32,6 +36,15 @@ import java.nio.ByteBuffer; */ public interface MediaCodecAdapter { + /** + * Listener to be called when an output frame has rendered on the output surface. + * + * @see MediaCodec.OnFrameRenderedListener + */ + interface OnFrameRenderedListener { + void onFrameRendered(MediaCodecAdapter codec, long presentationTimeUs, long nanoTime); + } + /** * Configures this adapter and the underlying {@link MediaCodec}. Needs to be called before {@link * #start()}. @@ -118,18 +131,64 @@ public interface MediaCodecAdapter { void queueSecureInputBuffer( int index, int offset, CryptoInfo info, long presentationTimeUs, int flags); - /** Flushes both the adapter and the underlying {@link MediaCodec}. */ - void flush(); + /** + * Returns the buffer to the {@link MediaCodec}. If the {@link MediaCodec} was configured with an + * output surface, setting {@code render} to {@code true} will first send the buffer to the output + * surface. The surface will release the buffer back to the codec once it is no longer + * used/displayed. + * + * @see MediaCodec#releaseOutputBuffer(int, boolean) + */ + void releaseOutputBuffer(int index, boolean render); /** - * Shuts down the adapter. + * Updates the output buffer's surface timestamp and sends it to the {@link MediaCodec} to render + * it on the output surface. If the {@link MediaCodec} is not configured with an output surface, + * this call will simply return the buffer to the {@link MediaCodec}. * - *

This method does not stop or release the underlying {@link MediaCodec}. It should be called - * before stopping or releasing the {@link MediaCodec} to avoid the possibility of the adapter - * interacting with a stopped or released {@link MediaCodec}. + * @see MediaCodec#releaseOutputBuffer(int, long) */ - void shutdown(); + @RequiresApi(21) + void releaseOutputBuffer(int index, long renderTimeStampNs); + + /** Flushes the adapter and the underlying {@link MediaCodec}. */ + void flush(); + + /** Releases the adapter and the underlying {@link MediaCodec}. */ + void release(); /** Returns the {@link MediaCodec} instance of this adapter. */ MediaCodec getCodec(); + + /** + * Registers a callback to be invoked when an output frame is rendered on the output surface. + * + * @see MediaCodec#setOnFrameRenderedListener + */ + @RequiresApi(23) + void setOnFrameRenderedListener(OnFrameRenderedListener listener, Handler handler); + + /** + * Dynamically sets the output surface of a {@link MediaCodec}. + * + * @see MediaCodec#setOutputSurface(Surface) + */ + @RequiresApi(23) + void setOutputSurface(Surface surface); + + /** + * Communicate additional parameter changes to the {@link MediaCodec} instance. + * + * @see MediaCodec#setParameters(Bundle) + */ + @RequiresApi(19) + void setParameters(Bundle params); + + /** + * Specifies the scaling mode to use, if a surface has been specified in a previous call to {@link + * #configure}. + * + * @see MediaCodec#setVideoScalingMode(int) + */ + void setVideoScalingMode(@VideoScalingMode int scalingMode); } 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 9317031083..39abd44b7e 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 @@ -719,7 +719,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void releaseCodec() { try { if (codecAdapter != null) { - codecAdapter.shutdown(); + codecAdapter.release(); } if (codec != null) { decoderCounters.decoderReleaseCount++; @@ -1071,7 +1071,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecInitializedTimestamp = SystemClock.elapsedRealtime(); } catch (Exception e) { if (codecAdapter != null) { - codecAdapter.shutdown(); + codecAdapter.release(); } if (codec != null) { codec.release(); 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 0c5f80bd19..9619a7f5c3 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 @@ -21,8 +21,12 @@ 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; import android.view.Surface; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import com.google.android.exoplayer2.Renderer.VideoScalingMode; import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; @@ -114,13 +118,24 @@ import java.nio.ByteBuffer; index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags); } + @Override + public void releaseOutputBuffer(int index, boolean render) { + codec.releaseOutputBuffer(index, render); + } + + @Override + @RequiresApi(21) + public void releaseOutputBuffer(int index, long renderTimeStampNs) { + codec.releaseOutputBuffer(index, renderTimeStampNs); + } + @Override public void flush() { codec.flush(); } @Override - public void shutdown() { + public void release() { inputByteBuffers = null; outputByteBuffers = null; } @@ -129,4 +144,31 @@ import java.nio.ByteBuffer; public MediaCodec getCodec() { return codec; } + + @Override + @RequiresApi(23) + public void setOnFrameRenderedListener(OnFrameRenderedListener listener, Handler handler) { + codec.setOnFrameRenderedListener( + (codec, presentationTimeUs, nanoTime) -> + listener.onFrameRendered( + SynchronousMediaCodecAdapter.this, presentationTimeUs, nanoTime), + handler); + } + + @Override + @RequiresApi(23) + public void setOutputSurface(Surface surface) { + codec.setOutputSurface(surface); + } + + @Override + @RequiresApi(19) + public void setParameters(Bundle params) { + codec.setParameters(params); + } + + @Override + public void setVideoScalingMode(@VideoScalingMode int scalingMode) { + codec.setVideoScalingMode(scalingMode); + } } 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 6c3294c2aa..60e9c8b77f 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 @@ -52,8 +52,7 @@ public class AsynchronousMediaCodecAdapterTest { @After public void tearDown() { - adapter.shutdown(); - codec.release(); + adapter.release(); } @Test @@ -106,7 +105,7 @@ public class AsynchronousMediaCodecAdapterTest { // non-empty adapter. shadowOf(callbackThread.getLooper()).idle(); - adapter.shutdown(); + adapter.release(); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); } @@ -183,7 +182,7 @@ public class AsynchronousMediaCodecAdapterTest { adapter.queueInputBuffer(index, 0, 0, 0, 0); // Progress the looper so that the ShadowMediaCodec processes the input buffer. shadowLooper.idle(); - adapter.shutdown(); + adapter.release(); assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);