mirror of
https://github.com/samsonjs/media.git
synced 2026-04-02 10:45:51 +00:00
MediaCodecAdapter.Factory creates a started Adapter.
This change moves the responsibility of creating, configuring and starting the MediaCodec from the MediaCodecRender to the MediaCodecAdapter.Factory. This move allows ExoPlayer's client to decide how and when codecs are created and/or reused. To allow the move, this CL replaces MediaCodecRenderer.ConfigureCodec with MediaCodecRenderer.getCodecConfiguration PiperOrigin-RevId: 369273887
This commit is contained in:
parent
9fc3b48377
commit
7a13163664
11 changed files with 235 additions and 231 deletions
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue