Commit 41d4a132 by christosts Committed by Andrew Lewis

Add configure() in MediaCodecAdapter

The correct order of initializing the MediaCodec should be (as per
documentation
https://developer.android.com/reference/android/media/MediaCodec#initialization)

"create -> setCallback -> configure -> start"

but the MediaCodecRenderer currently does

"create -> configure -> setCallback -> start"

MediaCodec implementations did not complain about this so far, but the
wrong sequence does not work with the MediaCodec in block mode (new mode
in Android R) and also the ShadowMediaCodec won't operate in
asynchronous mode otherwise. To initialize the MediaCodec in the correct
order, this commit adds configure() in the MediaCodecAdapter so the
MediaCodecRenderer can do:

adapter.configure(); // sets the callback and then configures the codec
adapter.start();     // starts the codec

PiperOrigin-RevId: 316127680
parent 0a617146
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
......@@ -289,7 +290,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
MediaCodecAdapter codecAdapter,
Format format,
@Nullable MediaCrypto crypto,
float codecOperatingRate) {
......@@ -301,7 +302,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
&& !MimeTypes.AUDIO_RAW.equals(format.sampleMimeType);
MediaFormat mediaFormat =
getMediaFormat(format, codecInfo.codecMimeType, codecMaxInputSize, codecOperatingRate);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
codecAdapter.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
// Store the input MIME type if we're using the passthrough codec.
passthroughFormat = passthroughEnabled ? format : null;
}
......
......@@ -17,9 +17,13 @@
package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.view.Surface;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
......@@ -45,22 +49,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_CREATED, STATE_STARTED, STATE_SHUT_DOWN})
@IntDef({STATE_CREATED, STATE_CONFIGURED, STATE_STARTED, STATE_SHUT_DOWN})
private @interface State {}
private static final int STATE_CREATED = 0;
private static final int STATE_STARTED = 1;
private static final int STATE_SHUT_DOWN = 2;
private static final int STATE_CONFIGURED = 1;
private static final int STATE_STARTED = 2;
private static final int STATE_SHUT_DOWN = 3;
private final Object lock;
@GuardedBy("lock")
private final MediaCodecAsyncCallback mediaCodecAsyncCallback;
private final MediaCodec codec;
private final HandlerThread handlerThread;
private @MonotonicNonNull Handler handler;
@GuardedBy("lock")
private long pendingFlushCount;
private @State int state;
private Runnable codecStartRunnable;
private final MediaCodecInputBufferEnqueuer bufferEnqueuer;
@Nullable private IllegalStateException internalException;
@GuardedBy("lock")
@Nullable
private IllegalStateException internalException;
/**
* Creates an instance that wraps the specified {@link MediaCodec}. Instances created with this
......@@ -101,46 +115,52 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
boolean enableAsynchronousQueueing,
int trackType,
HandlerThread handlerThread) {
mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
this.lock = new Object();
this.mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
this.codec = codec;
this.handlerThread = handlerThread;
state = STATE_CREATED;
codecStartRunnable = codec::start;
if (enableAsynchronousQueueing) {
bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, trackType);
} else {
bufferEnqueuer = new SynchronousMediaCodecBufferEnqueuer(this.codec);
}
this.bufferEnqueuer =
enableAsynchronousQueueing
? new AsynchronousMediaCodecBufferEnqueuer(codec, trackType)
: new SynchronousMediaCodecBufferEnqueuer(this.codec);
this.state = STATE_CREATED;
}
@Override
public synchronized void start() {
public void configure(
@Nullable MediaFormat mediaFormat,
@Nullable Surface surface,
@Nullable MediaCrypto crypto,
int flags) {
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler);
codec.configure(mediaFormat, surface, crypto, flags);
state = STATE_CONFIGURED;
}
@Override
public void start() {
bufferEnqueuer.start();
codecStartRunnable.run();
codec.start();
state = STATE_STARTED;
}
@Override
public void queueInputBuffer(
int index, int offset, int size, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it does not interact with the
// mediaCodecAsyncCallback.
bufferEnqueuer.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
}
@Override
public void queueSecureInputBuffer(
int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it does not interact with the
// mediaCodecAsyncCallback.
bufferEnqueuer.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
}
@Override
public synchronized int dequeueInputBufferIndex() {
public int dequeueInputBufferIndex() {
synchronized (lock) {
if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER;
} else {
......@@ -148,9 +168,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return mediaCodecAsyncCallback.dequeueInputBufferIndex();
}
}
}
@Override
public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
synchronized (lock) {
if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER;
} else {
......@@ -158,64 +180,99 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
}
}
}
@Override
public synchronized MediaFormat getOutputFormat() {
public MediaFormat getOutputFormat() {
synchronized (lock) {
return mediaCodecAsyncCallback.getOutputFormat();
}
}
@Override
public synchronized void flush() {
public void flush() {
synchronized (lock) {
bufferEnqueuer.flush();
codec.flush();
++pendingFlushCount;
Util.castNonNull(handler).post(this::onFlushCompleted);
}
}
@Override
public synchronized void shutdown() {
public void shutdown() {
synchronized (lock) {
if (state == STATE_STARTED) {
bufferEnqueuer.shutdown();
}
if (state == STATE_CONFIGURED || state == STATE_STARTED) {
handlerThread.quit();
mediaCodecAsyncCallback.flush();
// Leave the adapter in a flushing state so that
// it will not dequeue anything.
++pendingFlushCount;
}
state = STATE_SHUT_DOWN;
}
}
@Override
public synchronized void onInputBufferAvailable(MediaCodec codec, int index) {
public MediaCodec getCodec() {
return codec;
}
// Called from the handler thread.
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
synchronized (lock) {
mediaCodecAsyncCallback.onInputBufferAvailable(codec, index);
}
}
@Override
public synchronized void onOutputBufferAvailable(
MediaCodec codec, int index, MediaCodec.BufferInfo info) {
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
synchronized (lock) {
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, index, info);
}
}
@Override
public synchronized void onError(MediaCodec codec, MediaCodec.CodecException e) {
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
synchronized (lock) {
mediaCodecAsyncCallback.onError(codec, e);
}
}
@Override
public synchronized void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
synchronized (lock) {
mediaCodecAsyncCallback.onOutputFormatChanged(codec, format);
}
}
@VisibleForTesting
/* package */ void onMediaCodecError(IllegalStateException e) {
synchronized (lock) {
mediaCodecAsyncCallback.onMediaCodecError(e);
}
}
@VisibleForTesting
/* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.codecStartRunnable = codecStartRunnable;
@Nullable
/* package */ Looper getLooper() {
return handlerThread.getLooper();
}
private void onFlushCompleted() {
synchronized (lock) {
onFlushCompletedSynchronized();
}
}
private synchronized void onFlushCompleted() {
if (state != STATE_STARTED) {
// The adapter has been shutdown.
@GuardedBy("lock")
private void onFlushCompletedSynchronized() {
if (state == STATE_SHUT_DOWN) {
return;
}
......@@ -231,7 +288,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
mediaCodecAsyncCallback.flush();
try {
codecStartRunnable.run();
codec.start();
} catch (IllegalStateException e) {
internalException = e;
} catch (Exception e) {
......@@ -239,16 +296,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
private synchronized boolean isFlushing() {
@GuardedBy("lock")
private boolean isFlushing() {
return pendingFlushCount > 0;
}
private synchronized void maybeThrowException() {
@GuardedBy("lock")
private void maybeThrowException() {
maybeThrowInternalException();
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
}
private synchronized void maybeThrowInternalException() {
@GuardedBy("lock")
private void maybeThrowInternalException() {
if (internalException != null) {
IllegalStateException e = internalException;
internalException = null;
......
......@@ -17,7 +17,10 @@
package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.decoder.CryptoInfo;
/**
......@@ -30,12 +33,24 @@ import com.google.android.exoplayer2.decoder.CryptoInfo;
*
* @see com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.MediaCodecOperationMode
*/
/* package */ interface MediaCodecAdapter {
public interface MediaCodecAdapter {
/**
* Starts this instance.
* Configures this adapter and the underlying {@link MediaCodec}. Needs to be called before {@link
* #start()}.
*
* @see MediaCodec#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();
......@@ -109,4 +124,7 @@ import com.google.android.exoplayer2.decoder.CryptoInfo;
* is a risk the adapter might interact with a stopped or released {@link MediaCodec}.
*/
void shutdown();
/** Returns the {@link MediaCodec} instance of this adapter. */
MediaCodec getCodec();
}
......@@ -532,7 +532,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* Configures a newly created {@link MediaCodec}.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param codec The {@link MediaCodec} to configure.
* @param codecAdapter 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
......@@ -540,7 +540,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/
protected abstract void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
MediaCodecAdapter codecAdapter,
Format format,
@Nullable MediaCrypto crypto,
float codecOperatingRate);
......@@ -1036,8 +1036,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/**
* Configures passthrough where no codec is used. Called instead of {@link
* #configureCodec(MediaCodecInfo, MediaCodec, Format, MediaCrypto, float)} when no codec is used
* in passthrough.
* #configureCodec(MediaCodecInfo, MediaCodecAdapter, Format, MediaCrypto, float)} when no codec
* is used in passthrough.
*/
private void initPassthrough(Format format) {
disablePassthrough(); // In case of transition between 2 passthrough formats.
......@@ -1088,7 +1088,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
TraceUtil.endSection();
TraceUtil.beginSection("configureCodec");
configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate);
configureCodec(codecInfo, codecAdapter, inputFormat, crypto, codecOperatingRate);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codecAdapter.start();
......
......@@ -17,7 +17,10 @@
package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.decoder.CryptoInfo;
/**
......@@ -32,6 +35,15 @@ import com.google.android.exoplayer2.decoder.CryptoInfo;
}
@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();
}
......@@ -71,4 +83,9 @@ import com.google.android.exoplayer2.decoder.CryptoInfo;
@Override
public void shutdown() {}
@Override
public MediaCodec getCodec() {
return codec;
}
}
......@@ -42,6 +42,7 @@ import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
......@@ -552,7 +553,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
MediaCodecAdapter codecAdapter,
Format format,
@Nullable MediaCrypto crypto,
float codecOperatingRate) {
......@@ -575,9 +576,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
surface = dummySurface;
}
codec.configure(mediaFormat, surface, crypto, 0);
codecAdapter.configure(mediaFormat, surface, crypto, 0);
if (Util.SDK_INT >= 23 && tunneling) {
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codecAdapter.getCodec());
}
}
......
......@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
......@@ -107,7 +108,7 @@ import java.util.ArrayList;
@Override
protected void configureCodec(
MediaCodecInfo codecInfo,
MediaCodec codec,
MediaCodecAdapter codecAdapter,
Format format,
MediaCrypto crypto,
float operatingRate) {
......@@ -117,7 +118,7 @@ import java.util.ArrayList;
// 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);
super.configureCodec(codecInfo, codecAdapter, format, crypto, operatingRate);
}
@Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment