mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
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
This commit is contained in:
parent
0a617146b0
commit
41d4a132c4
8 changed files with 324 additions and 295 deletions
|
|
@ -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,121 +115,164 @@ 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() {
|
||||
if (isFlushing()) {
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
} else {
|
||||
maybeThrowException();
|
||||
return mediaCodecAsyncCallback.dequeueInputBufferIndex();
|
||||
public int dequeueInputBufferIndex() {
|
||||
synchronized (lock) {
|
||||
if (isFlushing()) {
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
} else {
|
||||
maybeThrowException();
|
||||
return mediaCodecAsyncCallback.dequeueInputBufferIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||
if (isFlushing()) {
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
} else {
|
||||
maybeThrowException();
|
||||
return mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
|
||||
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||
synchronized (lock) {
|
||||
if (isFlushing()) {
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
} else {
|
||||
maybeThrowException();
|
||||
return mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized MediaFormat getOutputFormat() {
|
||||
return mediaCodecAsyncCallback.getOutputFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flush() {
|
||||
bufferEnqueuer.flush();
|
||||
codec.flush();
|
||||
++pendingFlushCount;
|
||||
Util.castNonNull(handler).post(this::onFlushCompleted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shutdown() {
|
||||
if (state == STATE_STARTED) {
|
||||
bufferEnqueuer.shutdown();
|
||||
handlerThread.quit();
|
||||
mediaCodecAsyncCallback.flush();
|
||||
public MediaFormat getOutputFormat() {
|
||||
synchronized (lock) {
|
||||
return mediaCodecAsyncCallback.getOutputFormat();
|
||||
}
|
||||
state = STATE_SHUT_DOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onInputBufferAvailable(MediaCodec codec, int index) {
|
||||
mediaCodecAsyncCallback.onInputBufferAvailable(codec, index);
|
||||
public void flush() {
|
||||
synchronized (lock) {
|
||||
bufferEnqueuer.flush();
|
||||
codec.flush();
|
||||
++pendingFlushCount;
|
||||
Util.castNonNull(handler).post(this::onFlushCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onOutputBufferAvailable(
|
||||
MediaCodec codec, int index, MediaCodec.BufferInfo info) {
|
||||
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, index, info);
|
||||
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 onError(MediaCodec codec, MediaCodec.CodecException e) {
|
||||
mediaCodecAsyncCallback.onError(codec, e);
|
||||
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 onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
|
||||
mediaCodecAsyncCallback.onOutputFormatChanged(codec, format);
|
||||
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
|
||||
synchronized (lock) {
|
||||
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, index, info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
|
||||
synchronized (lock) {
|
||||
mediaCodecAsyncCallback.onError(codec, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
|
||||
synchronized (lock) {
|
||||
mediaCodecAsyncCallback.onOutputFormatChanged(codec, format);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ void onMediaCodecError(IllegalStateException e) {
|
||||
mediaCodecAsyncCallback.onMediaCodecError(e);
|
||||
synchronized (lock) {
|
||||
mediaCodecAsyncCallback.onMediaCodecError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
|
||||
this.codecStartRunnable = codecStartRunnable;
|
||||
@Nullable
|
||||
/* package */ Looper getLooper() {
|
||||
return handlerThread.getLooper();
|
||||
}
|
||||
|
||||
private synchronized void onFlushCompleted() {
|
||||
if (state != STATE_STARTED) {
|
||||
// The adapter has been shutdown.
|
||||
private void onFlushCompleted() {
|
||||
synchronized (lock) {
|
||||
onFlushCompletedSynchronized();
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
/**
|
||||
|
|
@ -31,6 +34,15 @@ import com.google.android.exoplayer2.decoder.CryptoInfo;
|
|||
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();
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,32 +16,25 @@
|
|||
|
||||
package com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.TestUtil.assertBufferInfosEqual;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
|
||||
import static org.robolectric.annotation.LooperMode.Mode.PAUSED;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Shadows;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
/** Unit tests for {@link AsynchronousMediaCodecAdapter}. */
|
||||
@LooperMode(LEGACY)
|
||||
@LooperMode(PAUSED)
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AsynchronousMediaCodecAdapterTest {
|
||||
private AsynchronousMediaCodecAdapter adapter;
|
||||
|
|
@ -59,7 +52,6 @@ public class AsynchronousMediaCodecAdapterTest {
|
|||
/* enableAsynchronousQueueing= */ false,
|
||||
/* trackType= */ C.TRACK_TYPE_VIDEO,
|
||||
handlerThread);
|
||||
adapter.setCodecStartRunnable(() -> {});
|
||||
bufferInfo = new MediaCodec.BufferInfo();
|
||||
}
|
||||
|
||||
|
|
@ -67,51 +59,46 @@ public class AsynchronousMediaCodecAdapterTest {
|
|||
public void tearDown() {
|
||||
adapter.shutdown();
|
||||
|
||||
assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startAndShutdown_works() {
|
||||
adapter.start();
|
||||
adapter.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException() {
|
||||
AtomicInteger codecStartCalls = new AtomicInteger(0);
|
||||
adapter.setCodecStartRunnable(
|
||||
() -> {
|
||||
if (codecStartCalls.incrementAndGet() == 2) {
|
||||
throw new IllegalStateException("codec#start() exception");
|
||||
}
|
||||
});
|
||||
adapter.start();
|
||||
adapter.flush();
|
||||
|
||||
// Wait until all tasks have been handled.
|
||||
Shadows.shadowOf(handlerThread.getLooper()).idle();
|
||||
assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
|
||||
assertThat(handlerThread.hasQuit()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
|
||||
// After start(), the ShadowMediaCodec offers one input buffer, which is available only if we
|
||||
// progress the adapter's looper. We don't progress the looper so that the buffer is not
|
||||
// available.
|
||||
|
||||
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
|
||||
adapter.start();
|
||||
adapter.onInputBufferAvailable(codec, 0);
|
||||
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueInputBufferIndex_withPendingFlush_returnsTryAgainLater() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
adapter.onInputBufferAvailable(codec, 0);
|
||||
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
// Flush enqueues a task in the looper, but we won't progress the looper to leave flush()
|
||||
// in a pending state.
|
||||
adapter.flush();
|
||||
|
||||
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||
|
|
@ -119,153 +106,137 @@ public class AsynchronousMediaCodecAdapterTest {
|
|||
|
||||
@Test
|
||||
public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
Looper looper = handlerThread.getLooper();
|
||||
Handler handler = new Handler(looper);
|
||||
// Enqueue 10 callbacks from codec
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int bufferIndex = i;
|
||||
handler.post(() -> adapter.onInputBufferAvailable(codec, bufferIndex));
|
||||
}
|
||||
adapter.flush(); // Enqueues a flush event after the onInputBufferAvailable callbacks
|
||||
// Enqueue another onInputBufferAvailable after the flush event
|
||||
handler.post(() -> adapter.onInputBufferAvailable(codec, 10));
|
||||
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
// Wait until all tasks have been handled.
|
||||
Shadows.shadowOf(handlerThread.getLooper()).idle();
|
||||
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(10);
|
||||
adapter.flush();
|
||||
// Progress the looper to complete flush(): the adapter should call codec.start(), triggering
|
||||
// the ShadowMediaCodec to offer input buffer 0.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueInputBufferIndex_withMediaCodecError_throwsException() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
|
||||
// Set an error directly on the adapter (not through the looper).
|
||||
adapter.onMediaCodecError(new IllegalStateException("error from codec"));
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withInternalException_throwsException() {
|
||||
AtomicInteger codecStartCalls = new AtomicInteger(0);
|
||||
adapter.setCodecStartRunnable(
|
||||
() -> {
|
||||
if (codecStartCalls.incrementAndGet() == 2) {
|
||||
throw new RuntimeException("codec#start() exception");
|
||||
}
|
||||
});
|
||||
public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
adapter.flush();
|
||||
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
// Wait until all tasks have been handled.
|
||||
Shadows.shadowOf(handlerThread.getLooper()).idle();
|
||||
assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
|
||||
adapter.shutdown();
|
||||
|
||||
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
|
||||
adapter.start();
|
||||
public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
|
||||
adapter.start();
|
||||
// After start(), the ShadowMediaCodec offers an output format change.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
// Assert that output buffer is available.
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
MediaCodec.BufferInfo enqueuedBufferInfo = new MediaCodec.BufferInfo();
|
||||
adapter.onOutputBufferAvailable(codec, 0, enqueuedBufferInfo);
|
||||
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
assertThat(adapter.dequeueOutputBufferIndex((bufferInfo))).isEqualTo(0);
|
||||
assertBufferInfosEqual(enqueuedBufferInfo, bufferInfo);
|
||||
int index = adapter.dequeueInputBufferIndex();
|
||||
adapter.queueInputBuffer(index, 0, 0, 0, 0);
|
||||
// Progress the looper so that the ShadowMediaCodec processes the input buffer.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
// The ShadowMediaCodec will first offer an output format and then the output buffer.
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
// Assert it's the ShadowMediaCodec's output format
|
||||
assertThat(adapter.getOutputFormat().getByteBuffer("csd-0")).isNotNull();
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(index);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withPendingFlush_returnsTryAgainLater() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
adapter.dequeueOutputBufferIndex(bufferInfo);
|
||||
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
// Flush enqueues a task in the looper, but we won't progress the looper to leave flush()
|
||||
// in a pending state.
|
||||
adapter.flush();
|
||||
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withFlushCompletedAndOutputBuffer_returnsOutputBuffer() {
|
||||
adapter.start();
|
||||
Looper looper = handlerThread.getLooper();
|
||||
Handler handler = new Handler(looper);
|
||||
// Enqueue 10 callbacks from codec
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int bufferIndex = i;
|
||||
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||
outBufferInfo.presentationTimeUs = i;
|
||||
handler.post(() -> adapter.onOutputBufferAvailable(codec, bufferIndex, outBufferInfo));
|
||||
}
|
||||
adapter.flush(); // Enqueues a flush event after the onOutputBufferAvailable callbacks
|
||||
// Enqueue another onOutputBufferAvailable after the flush event
|
||||
MediaCodec.BufferInfo lastBufferInfo = new MediaCodec.BufferInfo();
|
||||
lastBufferInfo.presentationTimeUs = 10;
|
||||
handler.post(() -> adapter.onOutputBufferAvailable(codec, 10, lastBufferInfo));
|
||||
|
||||
// Wait until all tasks have been handled.
|
||||
Shadows.shadowOf(handlerThread.getLooper()).idle();
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(10);
|
||||
assertBufferInfosEqual(lastBufferInfo, bufferInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withMediaCodecError_throwsException() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
|
||||
// Set an error directly on the adapter.
|
||||
adapter.onMediaCodecError(new IllegalStateException("error from codec"));
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withPendingOutputFormat_returnsPendingOutputFormat() {
|
||||
public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
|
||||
MediaFormat pendingMediaFormat = new MediaFormat();
|
||||
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
adapter.onOutputFormatChanged(codec, new MediaFormat());
|
||||
adapter.onOutputBufferAvailable(codec, /* index= */ 0, new MediaCodec.BufferInfo());
|
||||
adapter.onOutputFormatChanged(codec, pendingMediaFormat);
|
||||
adapter.onOutputBufferAvailable(codec, /* index= */ 1, new MediaCodec.BufferInfo());
|
||||
// Flush should clear the output queue except from the last pending output format received.
|
||||
adapter.flush();
|
||||
shadowOf(handlerThread.getLooper()).idle();
|
||||
adapter.onOutputBufferAvailable(codec, /* index= */ 2, new MediaCodec.BufferInfo());
|
||||
int index = adapter.dequeueInputBufferIndex();
|
||||
adapter.queueInputBuffer(index, 0, 0, 0, 0);
|
||||
// Progress the looper so that the ShadowMediaCodec processes the input buffer.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
adapter.shutdown();
|
||||
|
||||
assertThat(adapter.dequeueOutputBufferIndex(outputBufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
assertThat(adapter.getOutputFormat()).isEqualTo(pendingMediaFormat);
|
||||
assertThat(adapter.dequeueOutputBufferIndex(outputBufferInfo)).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dequeueOutputBufferIndex_withPendingAndNewOutputFormat_returnsNewOutputFormat() {
|
||||
adapter.start();
|
||||
MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
|
||||
MediaFormat pendingMediaFormat = new MediaFormat();
|
||||
MediaFormat latestOutputFormat = new MediaFormat();
|
||||
|
||||
adapter.onOutputFormatChanged(codec, new MediaFormat());
|
||||
adapter.onOutputBufferAvailable(codec, /* index= */ 0, new MediaCodec.BufferInfo());
|
||||
adapter.onOutputFormatChanged(codec, pendingMediaFormat);
|
||||
adapter.onOutputBufferAvailable(codec, /* index= */ 1, new MediaCodec.BufferInfo());
|
||||
// Flush should clear the output queue except from the last pending output format received.
|
||||
adapter.flush();
|
||||
shadowOf(handlerThread.getLooper()).idle();
|
||||
// New output format should overwrite the pending format.
|
||||
adapter.onOutputFormatChanged(codec, latestOutputFormat);
|
||||
|
||||
assertThat(adapter.dequeueOutputBufferIndex(outputBufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
assertThat(adapter.getOutputFormat()).isEqualTo(latestOutputFormat);
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputFormat_withoutFormatReceived_throwsException() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat());
|
||||
|
|
@ -273,107 +244,67 @@ public class AsynchronousMediaCodecAdapterTest {
|
|||
|
||||
@Test
|
||||
public void getOutputFormat_withMultipleFormats_returnsCorrectFormat() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
|
||||
adapter.start();
|
||||
MediaFormat[] formats = new MediaFormat[10];
|
||||
for (int i = 0; i < formats.length; i++) {
|
||||
formats[i] = new MediaFormat();
|
||||
adapter.onOutputFormatChanged(codec, formats[i]);
|
||||
}
|
||||
// After start(), the ShadowMediaCodec offers an output format, which is available only if we
|
||||
// progress the adapter's looper.
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
assertThat(adapter.getOutputFormat()).isEqualTo(formats[i]);
|
||||
// A subsequent call to getOutputFormat() should return the previously fetched format
|
||||
assertThat(adapter.getOutputFormat()).isEqualTo(formats[i]);
|
||||
}
|
||||
// Add another format directly on the adapter.
|
||||
adapter.onOutputFormatChanged(codec, createMediaFormat("format2"));
|
||||
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
// The first format is the ShadowMediaCodec's output format.
|
||||
assertThat(adapter.getOutputFormat().getByteBuffer("csd-0")).isNotNull();
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
// The 2nd format is the format we enqueued 'manually' above.
|
||||
assertThat(adapter.getOutputFormat().getString("name")).isEqualTo("format2");
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputFormat_afterFlush_returnsPreviousFormat() {
|
||||
adapter.configure(
|
||||
createMediaFormat("foo"), /* 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(adapter.getLooper()).idle();
|
||||
|
||||
adapter.dequeueOutputBufferIndex(bufferInfo);
|
||||
MediaFormat outputFormat = adapter.getOutputFormat();
|
||||
// Flush the adapter and progress the looper so that flush is completed.
|
||||
adapter.flush();
|
||||
shadowOf(adapter.getLooper()).idle();
|
||||
|
||||
assertThat(adapter.getOutputFormat()).isEqualTo(outputFormat);
|
||||
}
|
||||
|
||||
private static MediaFormat createMediaFormat(String name) {
|
||||
MediaFormat format = new MediaFormat();
|
||||
adapter.start();
|
||||
adapter.onOutputFormatChanged(codec, format);
|
||||
|
||||
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
assertThat(adapter.getOutputFormat()).isEqualTo(format);
|
||||
|
||||
adapter.flush();
|
||||
|
||||
// Wait until all tasks have been handled.
|
||||
Shadows.shadowOf(handlerThread.getLooper()).idle();
|
||||
assertThat(adapter.getOutputFormat()).isEqualTo(format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flush_multipleTimes_onlyLastFlushExecutes() {
|
||||
AtomicInteger codecStartCalls = new AtomicInteger(0);
|
||||
adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet());
|
||||
adapter.start();
|
||||
Looper looper = handlerThread.getLooper();
|
||||
Handler handler = new Handler(looper);
|
||||
handler.post(() -> adapter.onInputBufferAvailable(codec, 0));
|
||||
adapter.flush(); // Enqueues a flush event
|
||||
handler.post(() -> adapter.onInputBufferAvailable(codec, 2));
|
||||
AtomicInteger milestoneCount = new AtomicInteger(0);
|
||||
handler.post(() -> milestoneCount.incrementAndGet());
|
||||
adapter.flush(); // Enqueues a second flush event
|
||||
handler.post(() -> adapter.onInputBufferAvailable(codec, 3));
|
||||
|
||||
// Progress the looper until the milestoneCount is increased.
|
||||
// adapter.start() will call codec.start(). First flush event should not call codec.start().
|
||||
ShadowLooper shadowLooper = shadowOf(looper);
|
||||
while (milestoneCount.get() < 1) {
|
||||
shadowLooper.runOneTask();
|
||||
}
|
||||
assertThat(codecStartCalls.get()).isEqualTo(1);
|
||||
|
||||
// Wait until all tasks have been handled.
|
||||
shadowLooper.idle();
|
||||
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3);
|
||||
assertThat(codecStartCalls.get()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flush_andImmediatelyShutdown_flushIsNoOp() {
|
||||
AtomicInteger onCodecStartCount = new AtomicInteger(0);
|
||||
adapter.setCodecStartRunnable(() -> onCodecStartCount.incrementAndGet());
|
||||
adapter.start();
|
||||
// Grab reference to Looper before shutting down the adapter otherwise handlerThread.getLooper()
|
||||
// might return null.
|
||||
Looper looper = handlerThread.getLooper();
|
||||
adapter.flush();
|
||||
adapter.shutdown();
|
||||
|
||||
// Wait until all tasks have been handled.
|
||||
Shadows.shadowOf(looper).idle();
|
||||
// Only adapter.start() calls onCodecStart.
|
||||
assertThat(onCodecStartCount.get()).isEqualTo(1);
|
||||
format.setString("name", name);
|
||||
return format;
|
||||
}
|
||||
|
||||
private static class TestHandlerThread extends HandlerThread {
|
||||
private static final AtomicLong INSTANCES_STARTED = new AtomicLong(0);
|
||||
private boolean quit;
|
||||
|
||||
public TestHandlerThread(String name) {
|
||||
super(name);
|
||||
TestHandlerThread(String label) {
|
||||
super(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
super.start();
|
||||
INSTANCES_STARTED.incrementAndGet();
|
||||
public boolean hasQuit() {
|
||||
return quit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean quit() {
|
||||
boolean quit = super.quit();
|
||||
if (quit) {
|
||||
INSTANCES_STARTED.decrementAndGet();
|
||||
}
|
||||
return quit;
|
||||
quit = true;
|
||||
return super.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue