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
This commit is contained in:
christosts 2020-11-06 12:02:01 +00:00 committed by Andrew Lewis
parent ae4cf9f1da
commit 92ec1ab628
5 changed files with 158 additions and 21 deletions

View file

@ -19,6 +19,8 @@ package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
@ -26,6 +28,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Renderer.VideoScalingMode;
import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.decoder.CryptoInfo;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -108,6 +111,16 @@ import java.nio.ByteBuffer;
bufferEnqueuer.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags); 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 @Override
public int dequeueInputBufferIndex() { public int dequeueInputBufferIndex() {
return asynchronousMediaCodecCallback.dequeueInputBufferIndex(); return asynchronousMediaCodecCallback.dequeueInputBufferIndex();
@ -148,14 +161,14 @@ import java.nio.ByteBuffer;
} }
@Override @Override
public void shutdown() { public void release() {
if (state == STATE_STARTED) { if (state == STATE_STARTED) {
bufferEnqueuer.shutdown(); bufferEnqueuer.shutdown();
} }
if (state == STATE_CONFIGURED || state == STATE_STARTED) { if (state == STATE_CONFIGURED || state == STATE_STARTED) {
asynchronousMediaCodecCallback.shutdown(); asynchronousMediaCodecCallback.shutdown();
} }
state = STATE_SHUT_DOWN; state = STATE_SHUT_DOWN;
} }
@Override @Override
@ -163,6 +176,30 @@ import java.nio.ByteBuffer;
return codec; 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 @VisibleForTesting
/* package */ void onError(MediaCodec.CodecException error) { /* package */ void onError(MediaCodec.CodecException error) {
asynchronousMediaCodecCallback.onError(codec, error); asynchronousMediaCodecCallback.onError(codec, error);

View file

@ -19,8 +19,12 @@ package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Handler;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; 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.decoder.CryptoInfo;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -32,6 +36,15 @@ import java.nio.ByteBuffer;
*/ */
public interface MediaCodecAdapter { 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 * Configures this adapter and the underlying {@link MediaCodec}. Needs to be called before {@link
* #start()}. * #start()}.
@ -118,18 +131,64 @@ public interface MediaCodecAdapter {
void queueSecureInputBuffer( void queueSecureInputBuffer(
int index, int offset, CryptoInfo info, long presentationTimeUs, int flags); 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}.
* *
* <p>This method does not stop or release the underlying {@link MediaCodec}. It should be called * @see MediaCodec#releaseOutputBuffer(int, long)
* before stopping or releasing the {@link MediaCodec} to avoid the possibility of the adapter
* interacting with a stopped or released {@link MediaCodec}.
*/ */
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. */ /** Returns the {@link MediaCodec} instance of this adapter. */
MediaCodec getCodec(); 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);
} }

View file

@ -719,7 +719,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected void releaseCodec() { protected void releaseCodec() {
try { try {
if (codecAdapter != null) { if (codecAdapter != null) {
codecAdapter.shutdown(); codecAdapter.release();
} }
if (codec != null) { if (codec != null) {
decoderCounters.decoderReleaseCount++; decoderCounters.decoderReleaseCount++;
@ -1071,7 +1071,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecInitializedTimestamp = SystemClock.elapsedRealtime(); codecInitializedTimestamp = SystemClock.elapsedRealtime();
} catch (Exception e) { } catch (Exception e) {
if (codecAdapter != null) { if (codecAdapter != null) {
codecAdapter.shutdown(); codecAdapter.release();
} }
if (codec != null) { if (codec != null) {
codec.release(); codec.release();

View file

@ -21,8 +21,12 @@ import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Handler;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; 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.decoder.CryptoInfo;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -114,13 +118,24 @@ import java.nio.ByteBuffer;
index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags); 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 @Override
public void flush() { public void flush() {
codec.flush(); codec.flush();
} }
@Override @Override
public void shutdown() { public void release() {
inputByteBuffers = null; inputByteBuffers = null;
outputByteBuffers = null; outputByteBuffers = null;
} }
@ -129,4 +144,31 @@ import java.nio.ByteBuffer;
public MediaCodec getCodec() { public MediaCodec getCodec() {
return codec; 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);
}
} }

View file

@ -52,8 +52,7 @@ public class AsynchronousMediaCodecAdapterTest {
@After @After
public void tearDown() { public void tearDown() {
adapter.shutdown(); adapter.release();
codec.release();
} }
@Test @Test
@ -106,7 +105,7 @@ public class AsynchronousMediaCodecAdapterTest {
// non-empty adapter. // non-empty adapter.
shadowOf(callbackThread.getLooper()).idle(); shadowOf(callbackThread.getLooper()).idle();
adapter.shutdown(); adapter.release();
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
} }
@ -183,7 +182,7 @@ public class AsynchronousMediaCodecAdapterTest {
adapter.queueInputBuffer(index, 0, 0, 0, 0); adapter.queueInputBuffer(index, 0, 0, 0, 0);
// Progress the looper so that the ShadowMediaCodec processes the input buffer. // Progress the looper so that the ShadowMediaCodec processes the input buffer.
shadowLooper.idle(); shadowLooper.idle();
adapter.shutdown(); adapter.release();
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);