Make TransformerTranscodingVideoRenderer support API < 23

PiperOrigin-RevId: 393100075
This commit is contained in:
kimvde 2021-08-26 13:13:22 +01:00 committed by bachinger
parent c90d4fd371
commit f574ec952a
6 changed files with 93 additions and 38 deletions

View File

@ -118,7 +118,8 @@ import java.nio.ByteBuffer;
configuration.mediaFormat, configuration.mediaFormat,
configuration.surface, configuration.surface,
configuration.crypto, configuration.crypto,
configuration.flags); configuration.flags,
configuration.createInputSurface);
return codecAdapter; return codecAdapter;
} catch (Exception e) { } catch (Exception e) {
if (codecAdapter != null) { if (codecAdapter != null) {
@ -146,6 +147,7 @@ import java.nio.ByteBuffer;
private final boolean synchronizeCodecInteractionsWithQueueing; private final boolean synchronizeCodecInteractionsWithQueueing;
private boolean codecReleased; private boolean codecReleased;
@State private int state; @State private int state;
@Nullable private Surface inputSurface;
private AsynchronousMediaCodecAdapter( private AsynchronousMediaCodecAdapter(
MediaCodec codec, MediaCodec codec,
@ -166,11 +168,15 @@ import java.nio.ByteBuffer;
@Nullable MediaFormat mediaFormat, @Nullable MediaFormat mediaFormat,
@Nullable Surface surface, @Nullable Surface surface,
@Nullable MediaCrypto crypto, @Nullable MediaCrypto crypto,
int flags) { int flags,
boolean createInputSurface) {
asynchronousMediaCodecCallback.initialize(codec); asynchronousMediaCodecCallback.initialize(codec);
TraceUtil.beginSection("configureCodec"); TraceUtil.beginSection("configureCodec");
codec.configure(mediaFormat, surface, crypto, flags); codec.configure(mediaFormat, surface, crypto, flags);
TraceUtil.endSection(); TraceUtil.endSection();
if (createInputSurface) {
inputSurface = codec.createInputSurface();
}
bufferEnqueuer.start(); bufferEnqueuer.start();
TraceUtil.beginSection("startCodec"); TraceUtil.beginSection("startCodec");
codec.start(); codec.start();
@ -226,6 +232,12 @@ import java.nio.ByteBuffer;
return codec.getInputBuffer(index); return codec.getInputBuffer(index);
} }
@Override
@Nullable
public Surface getInputSurface() {
return inputSurface;
}
@Override @Override
@Nullable @Nullable
public ByteBuffer getOutputBuffer(int index) { public ByteBuffer getOutputBuffer(int index) {
@ -253,6 +265,9 @@ import java.nio.ByteBuffer;
} }
state = STATE_SHUT_DOWN; state = STATE_SHUT_DOWN;
} finally { } finally {
if (inputSurface != null) {
inputSurface.release();
}
if (!codecReleased) { if (!codecReleased) {
codec.release(); codec.release();
codecReleased = true; codecReleased = true;

View File

@ -45,10 +45,7 @@ public interface MediaCodecAdapter {
public final MediaFormat mediaFormat; public final MediaFormat mediaFormat;
/** The {@link Format} for which the codec is being configured. */ /** The {@link Format} for which the codec is being configured. */
public final Format format; public final Format format;
/** /** For video decoding, the output where the object will render the decoded frames. */
* For video decoding, the output where the object will render the decoded frames; for video
* encoding, this is the input surface from which the decoded frames are retrieved.
*/
@Nullable public final Surface surface; @Nullable public final Surface surface;
/** For DRM protected playbacks, a {@link MediaCrypto} to use for decryption. */ /** For DRM protected playbacks, a {@link MediaCrypto} to use for decryption. */
@Nullable public final MediaCrypto crypto; @Nullable public final MediaCrypto crypto;
@ -58,6 +55,11 @@ public interface MediaCodecAdapter {
* @see MediaCodec#configure * @see MediaCodec#configure
*/ */
public final int flags; public final int flags;
/**
* Whether to request a {@link Surface} and use it as to the input to an encoder. This can only
* be set to {@code true} on API 18+.
*/
public final boolean createInputSurface;
public Configuration( public Configuration(
MediaCodecInfo codecInfo, MediaCodecInfo codecInfo,
@ -66,12 +68,24 @@ public interface MediaCodecAdapter {
@Nullable Surface surface, @Nullable Surface surface,
@Nullable MediaCrypto crypto, @Nullable MediaCrypto crypto,
int flags) { int flags) {
this(codecInfo, mediaFormat, format, surface, crypto, flags, /* createInputSurface= */ false);
}
public Configuration(
MediaCodecInfo codecInfo,
MediaFormat mediaFormat,
Format format,
@Nullable Surface surface,
@Nullable MediaCrypto crypto,
int flags,
boolean createInputSurface) {
this.codecInfo = codecInfo; this.codecInfo = codecInfo;
this.mediaFormat = mediaFormat; this.mediaFormat = mediaFormat;
this.format = format; this.format = format;
this.surface = surface; this.surface = surface;
this.crypto = crypto; this.crypto = crypto;
this.flags = flags; this.flags = flags;
this.createInputSurface = createInputSurface;
} }
} }
@ -129,6 +143,14 @@ public interface MediaCodecAdapter {
@Nullable @Nullable
ByteBuffer getInputBuffer(int index); ByteBuffer getInputBuffer(int index);
/**
* Returns the input {@link Surface}, or null if the input is not a surface.
*
* @see MediaCodec#createInputSurface()
*/
@Nullable
Surface getInputSurface();
/** /**
* Returns a read-only ByteBuffer for a dequeued output buffer index. * Returns a read-only ByteBuffer for a dequeued output buffer index.
* *

View File

@ -21,7 +21,6 @@ import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.view.Surface; import android.view.Surface;
@ -47,31 +46,34 @@ public class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
@RequiresApi(16) @RequiresApi(16)
public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException { public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException {
@Nullable MediaCodec codec = null; @Nullable MediaCodec codec = null;
boolean isEncoder = configuration.flags == MediaCodec.CONFIGURE_FLAG_ENCODE; @Nullable Surface inputSurface = null;
@Nullable Surface decoderOutputSurface = isEncoder ? null : configuration.surface;
try { try {
codec = createCodec(configuration); codec = createCodec(configuration);
TraceUtil.beginSection("configureCodec"); TraceUtil.beginSection("configureCodec");
codec.configure( codec.configure(
configuration.mediaFormat, configuration.mediaFormat,
decoderOutputSurface, configuration.surface,
configuration.crypto, configuration.crypto,
configuration.flags); configuration.flags);
TraceUtil.endSection(); TraceUtil.endSection();
if (isEncoder && configuration.surface != null) {
if (Build.VERSION.SDK_INT >= 23) { if (configuration.createInputSurface) {
Api23.setCodecInputSurface(codec, configuration.surface); if (Util.SDK_INT >= 18) {
inputSurface = Api18.createCodecInputSurface(codec);
} else { } else {
throw new IllegalStateException( throw new IllegalStateException(
"Encoding from a surface is only supported on API 23 and up"); "Encoding from a surface is only supported on API 18 and up.");
} }
} }
TraceUtil.beginSection("startCodec"); TraceUtil.beginSection("startCodec");
codec.start(); codec.start();
TraceUtil.endSection(); TraceUtil.endSection();
return new SynchronousMediaCodecAdapter(codec); return new SynchronousMediaCodecAdapter(codec, inputSurface);
} catch (IOException | RuntimeException e) { } catch (IOException | RuntimeException e) {
if (inputSurface != null) {
inputSurface.release();
}
if (codec != null) { if (codec != null) {
codec.release(); codec.release();
} }
@ -91,11 +93,13 @@ public class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
} }
private final MediaCodec codec; private final MediaCodec codec;
@Nullable private final Surface inputSurface;
@Nullable private ByteBuffer[] inputByteBuffers; @Nullable private ByteBuffer[] inputByteBuffers;
@Nullable private ByteBuffer[] outputByteBuffers; @Nullable private ByteBuffer[] outputByteBuffers;
private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { private SynchronousMediaCodecAdapter(MediaCodec mediaCodec, @Nullable Surface inputSurface) {
this.codec = mediaCodec; this.codec = mediaCodec;
this.inputSurface = inputSurface;
if (Util.SDK_INT < 21) { if (Util.SDK_INT < 21) {
inputByteBuffers = codec.getInputBuffers(); inputByteBuffers = codec.getInputBuffers();
outputByteBuffers = codec.getOutputBuffers(); outputByteBuffers = codec.getOutputBuffers();
@ -140,6 +144,12 @@ public class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
} }
} }
@Override
@Nullable
public Surface getInputSurface() {
return inputSurface;
}
@Override @Override
@Nullable @Nullable
public ByteBuffer getOutputBuffer(int index) { public ByteBuffer getOutputBuffer(int index) {
@ -183,6 +193,9 @@ public class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
public void release() { public void release() {
inputByteBuffers = null; inputByteBuffers = null;
outputByteBuffers = null; outputByteBuffers = null;
if (inputSurface != null) {
inputSurface.release();
}
codec.release(); codec.release();
} }
@ -213,11 +226,11 @@ public class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
codec.setVideoScalingMode(scalingMode); codec.setVideoScalingMode(scalingMode);
} }
@RequiresApi(23) @RequiresApi(18)
private static final class Api23 { private static final class Api18 {
@DoNotInline @DoNotInline
public static void setCodecInputSurface(MediaCodec codec, Surface surface) { public static Surface createCodecInputSurface(MediaCodec codec) {
codec.setInputSurface(surface); return codec.createInputSurface();
} }
} }
} }

View File

@ -48,6 +48,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* through {@link MediaCodecAdapter}. This is done by simplifying the calls needed to queue and * through {@link MediaCodecAdapter}. This is done by simplifying the calls needed to queue and
* dequeue buffers, removing the need to track buffer indices and codec events. * dequeue buffers, removing the need to track buffer indices and codec events.
*/ */
@RequiresApi(18)
/* package */ final class MediaCodecAdapterWrapper { /* package */ final class MediaCodecAdapterWrapper {
// MediaCodec decoders always output 16 bit PCM, unless configured to output PCM float. // MediaCodec decoders always output 16 bit PCM, unless configured to output PCM float.
@ -142,7 +143,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @return A configured and started decoder wrapper. * @return A configured and started decoder wrapper.
* @throws IOException If the underlying codec cannot be created. * @throws IOException If the underlying codec cannot be created.
*/ */
@RequiresApi(23)
public static MediaCodecAdapterWrapper createForVideoDecoding(Format format, Surface surface) public static MediaCodecAdapterWrapper createForVideoDecoding(Format format, Surface surface)
throws IOException { throws IOException {
@Nullable MediaCodecAdapter adapter = null; @Nullable MediaCodecAdapter adapter = null;
@ -163,7 +163,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
surface, surface,
/* crypto= */ null, /* crypto= */ null,
/* flags= */ 0)); /* flags= */ 0));
adapter.setOutputSurface(surface);
return new MediaCodecAdapterWrapper(adapter); return new MediaCodecAdapterWrapper(adapter);
} catch (Exception e) { } catch (Exception e) {
if (adapter != null) { if (adapter != null) {
@ -217,13 +216,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* *
* @param format The {@link Format} (of the output data) used to determine the underlying {@link * @param format The {@link Format} (of the output data) used to determine the underlying {@link
* MediaCodec} and its configuration values. * MediaCodec} and its configuration values.
* @param surface The {@link Surface} from which the encoder obtains the frame input.
* @return A configured and started encoder wrapper. * @return A configured and started encoder wrapper.
* @throws IOException If the underlying codec cannot be created. * @throws IOException If the underlying codec cannot be created.
*/ */
@RequiresApi(18) public static MediaCodecAdapterWrapper createForVideoEncoding(Format format) throws IOException {
public static MediaCodecAdapterWrapper createForVideoEncoding(Format format, Surface surface)
throws IOException {
@Nullable MediaCodecAdapter adapter = null; @Nullable MediaCodecAdapter adapter = null;
try { try {
MediaFormat mediaFormat = MediaFormat mediaFormat =
@ -242,9 +238,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
createPlaceholderMediaCodecInfo(), createPlaceholderMediaCodecInfo(),
mediaFormat, mediaFormat,
format, format,
surface, /* surface= */ null,
/* crypto= */ null, /* crypto= */ null,
MediaCodec.CONFIGURE_FLAG_ENCODE)); MediaCodec.CONFIGURE_FLAG_ENCODE,
/* createInputSurface= */ true));
return new MediaCodecAdapterWrapper(adapter); return new MediaCodecAdapterWrapper(adapter);
} catch (Exception e) { } catch (Exception e) {
if (adapter != null) { if (adapter != null) {
@ -261,6 +258,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputBufferIndex = C.INDEX_UNSET; outputBufferIndex = C.INDEX_UNSET;
} }
/** Returns the input {@link Surface}, or null if the input is not a surface. */
@Nullable
public Surface getInputSurface() {
return codec.getInputSurface();
}
/** /**
* Dequeues a writable input buffer, if available. * Dequeues a writable input buffer, if available.
* *

View File

@ -19,7 +19,6 @@ package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
@ -32,7 +31,7 @@ import com.google.android.exoplayer2.source.SampleStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@RequiresApi(23) @RequiresApi(18)
/* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer { /* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer {
private static final String TAG = "TransformerTranscodingVideoRenderer"; private static final String TAG = "TransformerTranscodingVideoRenderer";
@ -41,8 +40,6 @@ import java.nio.ByteBuffer;
/** The format the encoder is configured to output, may differ from the actual output format. */ /** The format the encoder is configured to output, may differ from the actual output format. */
private final Format encoderConfigurationOutputFormat; private final Format encoderConfigurationOutputFormat;
private final Surface surface;
@Nullable private MediaCodecAdapterWrapper decoder; @Nullable private MediaCodecAdapterWrapper decoder;
@Nullable private MediaCodecAdapterWrapper encoder; @Nullable private MediaCodecAdapterWrapper encoder;
/** Whether encoder's actual output format is obtained. */ /** Whether encoder's actual output format is obtained. */
@ -58,7 +55,6 @@ import java.nio.ByteBuffer;
super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation); super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation);
decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
surface = MediaCodec.createPersistentInputSurface();
this.encoderConfigurationOutputFormat = encoderConfigurationOutputFormat; this.encoderConfigurationOutputFormat = encoderConfigurationOutputFormat;
} }
@ -93,7 +89,6 @@ import java.nio.ByteBuffer;
protected void onReset() { protected void onReset() {
decoderInputBuffer.clear(); decoderInputBuffer.clear();
decoderInputBuffer.data = null; decoderInputBuffer.data = null;
surface.release();
if (decoder != null) { if (decoder != null) {
decoder.release(); decoder.release();
decoder = null; decoder = null;
@ -121,8 +116,11 @@ import java.nio.ByteBuffer;
} }
Format inputFormat = checkNotNull(formatHolder.format); Format inputFormat = checkNotNull(formatHolder.format);
MediaCodecAdapterWrapper encoder = checkNotNull(this.encoder);
try { try {
decoder = MediaCodecAdapterWrapper.createForVideoDecoding(inputFormat, surface); decoder =
MediaCodecAdapterWrapper.createForVideoDecoding(
inputFormat, checkNotNull(encoder.getInputSurface()));
} catch (IOException e) { } catch (IOException e) {
throw createRendererException( throw createRendererException(
e, formatHolder.format, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED); e, formatHolder.format, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
@ -136,9 +134,7 @@ import java.nio.ByteBuffer;
} }
try { try {
encoder = encoder = MediaCodecAdapterWrapper.createForVideoEncoding(encoderConfigurationOutputFormat);
MediaCodecAdapterWrapper.createForVideoEncoding(
encoderConfigurationOutputFormat, surface);
} catch (IOException e) { } catch (IOException e) {
throw createRendererException( throw createRendererException(
// TODO(claincly): should be "ENCODER_INIT_FAILED" // TODO(claincly): should be "ENCODER_INIT_FAILED"

View File

@ -193,6 +193,12 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
return inputBuffer; return inputBuffer;
} }
@Nullable
@Override
public Surface getInputSurface() {
return delegate.getInputSurface();
}
@Nullable @Nullable
@Override @Override
public ByteBuffer getOutputBuffer(int index) { public ByteBuffer getOutputBuffer(int index) {