mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Remove MediaCodecAdapter dependency from Transformer.
Codec and its factories can use MediaCodec directly as for API >= 21, the SynchronousMediaCodecAdapter methods used in Codec just correspond to a single MediaCodec call each so there is no reason to have another wrapping layer. PiperOrigin-RevId: 421041177
This commit is contained in:
parent
657e8768be
commit
b0ae7c04d5
2 changed files with 100 additions and 96 deletions
|
|
@ -29,18 +29,17 @@ import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around {@link MediaCodecAdapter}.
|
* A wrapper around {@link MediaCodec}.
|
||||||
*
|
*
|
||||||
* <p>Provides a layer of abstraction for callers that need to interact with {@link MediaCodec}
|
* <p>Provides a layer of abstraction for callers that need to interact with {@link MediaCodec}.
|
||||||
* through {@link MediaCodecAdapter}. This is done by simplifying the calls needed to queue and
|
* This is done by simplifying the calls needed to queue and dequeue buffers, removing the need to
|
||||||
* dequeue buffers, removing the need to track buffer indices and codec events.
|
* track buffer indices and codec events.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class Codec {
|
public final class Codec {
|
||||||
|
|
@ -66,11 +65,12 @@ public final class Codec {
|
||||||
*
|
*
|
||||||
* @param format The {@link Format} (of the input data) used to determine the underlying {@link
|
* @param format The {@link Format} (of the input data) used to determine the underlying {@link
|
||||||
* MediaCodec} and its configuration values.
|
* MediaCodec} and its configuration values.
|
||||||
* @param surface The {@link Surface} to which the decoder output is rendered.
|
* @param outputSurface The {@link Surface} to which the decoder output is rendered.
|
||||||
* @return A configured and started decoder wrapper.
|
* @return A configured and started decoder wrapper.
|
||||||
* @throws TransformationException If the underlying codec cannot be created.
|
* @throws TransformationException If the underlying codec cannot be created.
|
||||||
*/
|
*/
|
||||||
Codec createForVideoDecoding(Format format, Surface surface) throws TransformationException;
|
Codec createForVideoDecoding(Format format, Surface outputSurface)
|
||||||
|
throws TransformationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A factory for {@link Codec encoder} instances. */
|
/** A factory for {@link Codec encoder} instances. */
|
||||||
|
|
@ -108,7 +108,8 @@ public final class Codec {
|
||||||
private static final int MEDIA_CODEC_PCM_ENCODING = C.ENCODING_PCM_16BIT;
|
private static final int MEDIA_CODEC_PCM_ENCODING = C.ENCODING_PCM_16BIT;
|
||||||
|
|
||||||
private final BufferInfo outputBufferInfo;
|
private final BufferInfo outputBufferInfo;
|
||||||
private final MediaCodecAdapter mediaCodecAdapter;
|
private final MediaCodec mediaCodec;
|
||||||
|
@Nullable private final Surface inputSurface;
|
||||||
|
|
||||||
private @MonotonicNonNull Format outputFormat;
|
private @MonotonicNonNull Format outputFormat;
|
||||||
@Nullable private ByteBuffer outputBuffer;
|
@Nullable private ByteBuffer outputBuffer;
|
||||||
|
|
@ -118,9 +119,10 @@ public final class Codec {
|
||||||
private boolean inputStreamEnded;
|
private boolean inputStreamEnded;
|
||||||
private boolean outputStreamEnded;
|
private boolean outputStreamEnded;
|
||||||
|
|
||||||
/** Creates a {@code Codec} from a configured and started {@link MediaCodecAdapter}. */
|
/** Creates a {@code Codec} from a configured and started {@link MediaCodec}. */
|
||||||
public Codec(MediaCodecAdapter mediaCodecAdapter) {
|
public Codec(MediaCodec mediaCodec, @Nullable Surface inputSurface) {
|
||||||
this.mediaCodecAdapter = mediaCodecAdapter;
|
this.mediaCodec = mediaCodec;
|
||||||
|
this.inputSurface = inputSurface;
|
||||||
outputBufferInfo = new BufferInfo();
|
outputBufferInfo = new BufferInfo();
|
||||||
inputBufferIndex = C.INDEX_UNSET;
|
inputBufferIndex = C.INDEX_UNSET;
|
||||||
outputBufferIndex = C.INDEX_UNSET;
|
outputBufferIndex = C.INDEX_UNSET;
|
||||||
|
|
@ -129,7 +131,7 @@ public final class Codec {
|
||||||
/** Returns the input {@link Surface}, or null if the input is not a surface. */
|
/** Returns the input {@link Surface}, or null if the input is not a surface. */
|
||||||
@Nullable
|
@Nullable
|
||||||
public Surface getInputSurface() {
|
public Surface getInputSurface() {
|
||||||
return mediaCodecAdapter.getInputSurface();
|
return inputSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -144,11 +146,11 @@ public final class Codec {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (inputBufferIndex < 0) {
|
if (inputBufferIndex < 0) {
|
||||||
inputBufferIndex = mediaCodecAdapter.dequeueInputBufferIndex();
|
inputBufferIndex = mediaCodec.dequeueInputBuffer(/* timeoutUs= */ 0);
|
||||||
if (inputBufferIndex < 0) {
|
if (inputBufferIndex < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
inputBuffer.data = mediaCodecAdapter.getInputBuffer(inputBufferIndex);
|
inputBuffer.data = mediaCodec.getInputBuffer(inputBufferIndex);
|
||||||
inputBuffer.clear();
|
inputBuffer.clear();
|
||||||
}
|
}
|
||||||
checkNotNull(inputBuffer.data);
|
checkNotNull(inputBuffer.data);
|
||||||
|
|
@ -174,13 +176,13 @@ public final class Codec {
|
||||||
inputStreamEnded = true;
|
inputStreamEnded = true;
|
||||||
flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
||||||
}
|
}
|
||||||
mediaCodecAdapter.queueInputBuffer(inputBufferIndex, offset, size, inputBuffer.timeUs, flags);
|
mediaCodec.queueInputBuffer(inputBufferIndex, offset, size, inputBuffer.timeUs, flags);
|
||||||
inputBufferIndex = C.INDEX_UNSET;
|
inputBufferIndex = C.INDEX_UNSET;
|
||||||
inputBuffer.data = null;
|
inputBuffer.data = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void signalEndOfInputStream() {
|
public void signalEndOfInputStream() {
|
||||||
mediaCodecAdapter.signalEndOfInputStream();
|
mediaCodec.signalEndOfInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the current output format, if available. */
|
/** Returns the current output format, if available. */
|
||||||
|
|
@ -224,7 +226,7 @@ public final class Codec {
|
||||||
*/
|
*/
|
||||||
public void releaseOutputBuffer(boolean render) {
|
public void releaseOutputBuffer(boolean render) {
|
||||||
outputBuffer = null;
|
outputBuffer = null;
|
||||||
mediaCodecAdapter.releaseOutputBuffer(outputBufferIndex, render);
|
mediaCodec.releaseOutputBuffer(outputBufferIndex, render);
|
||||||
outputBufferIndex = C.INDEX_UNSET;
|
outputBufferIndex = C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +238,10 @@ public final class Codec {
|
||||||
/** Releases the underlying codec. */
|
/** Releases the underlying codec. */
|
||||||
public void release() {
|
public void release() {
|
||||||
outputBuffer = null;
|
outputBuffer = null;
|
||||||
mediaCodecAdapter.release();
|
if (inputSurface != null) {
|
||||||
|
inputSurface.release();
|
||||||
|
}
|
||||||
|
mediaCodec.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -249,7 +254,7 @@ public final class Codec {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputBuffer = checkNotNull(mediaCodecAdapter.getOutputBuffer(outputBufferIndex));
|
outputBuffer = checkNotNull(mediaCodec.getOutputBuffer(outputBufferIndex));
|
||||||
outputBuffer.position(outputBufferInfo.offset);
|
outputBuffer.position(outputBufferInfo.offset);
|
||||||
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
|
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -267,10 +272,10 @@ public final class Codec {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputBufferIndex = mediaCodecAdapter.dequeueOutputBufferIndex(outputBufferInfo);
|
outputBufferIndex = mediaCodec.dequeueOutputBuffer(outputBufferInfo, /* timeoutUs= */ 0);
|
||||||
if (outputBufferIndex < 0) {
|
if (outputBufferIndex < 0) {
|
||||||
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||||
outputFormat = getFormat(mediaCodecAdapter.getOutputFormat());
|
outputFormat = getFormat(mediaCodec.getOutputFormat());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,17 @@ import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.util.MediaFormatUtil;
|
import androidx.media3.common.util.MediaFormatUtil;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
|
import androidx.media3.common.util.TraceUtil;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
|
|
||||||
import androidx.media3.exoplayer.mediacodec.SynchronousMediaCodecAdapter;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/** A default {@link Codec.DecoderFactory} and {@link Codec.EncoderFactory}. */
|
/** A default {@link Codec.DecoderFactory} and {@link Codec.EncoderFactory}. */
|
||||||
/* package */ final class DefaultCodecFactory
|
/* package */ final class DefaultCodecFactory
|
||||||
implements Codec.DecoderFactory, Codec.EncoderFactory {
|
implements Codec.DecoderFactory, Codec.EncoderFactory {
|
||||||
|
|
||||||
private static final MediaCodecInfo PLACEHOLDER_MEDIA_CODEC_INFO =
|
|
||||||
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);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Codec createForAudioDecoding(Format format) throws TransformationException {
|
public Codec createForAudioDecoding(Format format) throws TransformationException {
|
||||||
MediaFormat mediaFormat =
|
MediaFormat mediaFormat =
|
||||||
|
|
@ -57,22 +45,17 @@ import java.io.IOException;
|
||||||
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
||||||
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
||||||
|
|
||||||
MediaCodecAdapter adapter;
|
return createCodec(
|
||||||
try {
|
format,
|
||||||
adapter =
|
mediaFormat,
|
||||||
new MediaCodecFactory()
|
/* isVideo= */ false,
|
||||||
.createAdapter(
|
/* isDecoder= */ true,
|
||||||
MediaCodecAdapter.Configuration.createForAudioDecoding(
|
/* outputSurface= */ null);
|
||||||
PLACEHOLDER_MEDIA_CODEC_INFO, mediaFormat, format, /* crypto= */ null));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw createTransformationException(e, format, /* isVideo= */ false, /* isDecoder= */ true);
|
|
||||||
}
|
|
||||||
return new Codec(adapter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
public Codec createForVideoDecoding(Format format, Surface surface)
|
public Codec createForVideoDecoding(Format format, Surface outputSurface)
|
||||||
throws TransformationException {
|
throws TransformationException {
|
||||||
MediaFormat mediaFormat =
|
MediaFormat mediaFormat =
|
||||||
MediaFormat.createVideoFormat(
|
MediaFormat.createVideoFormat(
|
||||||
|
|
@ -87,21 +70,8 @@ import java.io.IOException;
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
|
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaCodecAdapter adapter;
|
return createCodec(
|
||||||
try {
|
format, mediaFormat, /* isVideo= */ true, /* isDecoder= */ true, outputSurface);
|
||||||
adapter =
|
|
||||||
new MediaCodecFactory()
|
|
||||||
.createAdapter(
|
|
||||||
MediaCodecAdapter.Configuration.createForVideoDecoding(
|
|
||||||
PLACEHOLDER_MEDIA_CODEC_INFO,
|
|
||||||
mediaFormat,
|
|
||||||
format,
|
|
||||||
surface,
|
|
||||||
/* crypto= */ null));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw createTransformationException(e, format, /* isVideo= */ true, /* isDecoder= */ true);
|
|
||||||
}
|
|
||||||
return new Codec(adapter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -111,17 +81,12 @@ import java.io.IOException;
|
||||||
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
|
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
|
||||||
|
|
||||||
MediaCodecAdapter adapter;
|
return createCodec(
|
||||||
try {
|
format,
|
||||||
adapter =
|
mediaFormat,
|
||||||
new MediaCodecFactory()
|
/* isVideo= */ false,
|
||||||
.createAdapter(
|
/* isDecoder= */ false,
|
||||||
MediaCodecAdapter.Configuration.createForAudioEncoding(
|
/* outputSurface= */ null);
|
||||||
PLACEHOLDER_MEDIA_CODEC_INFO, mediaFormat, format));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw createTransformationException(e, format, /* isVideo= */ false, /* isDecoder= */ false);
|
|
||||||
}
|
|
||||||
return new Codec(adapter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -141,36 +106,70 @@ import java.io.IOException;
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 413_000);
|
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 413_000);
|
||||||
|
|
||||||
MediaCodecAdapter adapter;
|
return createCodec(
|
||||||
try {
|
format,
|
||||||
adapter =
|
mediaFormat,
|
||||||
new MediaCodecFactory()
|
/* isVideo= */ true,
|
||||||
.createAdapter(
|
/* isDecoder= */ false,
|
||||||
MediaCodecAdapter.Configuration.createForVideoEncoding(
|
/* outputSurface= */ null);
|
||||||
PLACEHOLDER_MEDIA_CODEC_INFO, mediaFormat, format));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw createTransformationException(e, format, /* isVideo= */ true, /* isDecoder= */ false);
|
|
||||||
}
|
|
||||||
return new Codec(adapter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class MediaCodecFactory extends SynchronousMediaCodecAdapter.Factory {
|
@RequiresNonNull("#1.sampleMimeType")
|
||||||
@Override
|
private static Codec createCodec(
|
||||||
protected MediaCodec createCodec(MediaCodecAdapter.Configuration configuration)
|
Format format,
|
||||||
throws IOException {
|
MediaFormat mediaFormat,
|
||||||
String sampleMimeType =
|
boolean isVideo,
|
||||||
checkNotNull(configuration.mediaFormat.getString(MediaFormat.KEY_MIME));
|
boolean isDecoder,
|
||||||
boolean isDecoder = (configuration.flags & MediaCodec.CONFIGURE_FLAG_ENCODE) == 0;
|
@Nullable Surface outputSurface)
|
||||||
return isDecoder
|
throws TransformationException {
|
||||||
? MediaCodec.createDecoderByType(checkNotNull(sampleMimeType))
|
@Nullable MediaCodec mediaCodec = null;
|
||||||
: MediaCodec.createEncoderByType(checkNotNull(sampleMimeType));
|
@Nullable Surface inputSurface = null;
|
||||||
|
try {
|
||||||
|
mediaCodec =
|
||||||
|
isDecoder
|
||||||
|
? MediaCodec.createDecoderByType(format.sampleMimeType)
|
||||||
|
: MediaCodec.createEncoderByType(format.sampleMimeType);
|
||||||
|
configureCodec(mediaCodec, mediaFormat, isDecoder, outputSurface);
|
||||||
|
if (isVideo && !isDecoder) {
|
||||||
|
inputSurface = mediaCodec.createInputSurface();
|
||||||
|
}
|
||||||
|
startCodec(mediaCodec);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (inputSurface != null) {
|
||||||
|
inputSurface.release();
|
||||||
|
}
|
||||||
|
if (mediaCodec != null) {
|
||||||
|
mediaCodec.release();
|
||||||
|
}
|
||||||
|
throw createTransformationException(e, format, isVideo, isDecoder);
|
||||||
}
|
}
|
||||||
|
return new Codec(mediaCodec, inputSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void configureCodec(
|
||||||
|
MediaCodec codec,
|
||||||
|
MediaFormat mediaFormat,
|
||||||
|
boolean isDecoder,
|
||||||
|
@Nullable Surface outputSurface) {
|
||||||
|
TraceUtil.beginSection("configureCodec");
|
||||||
|
codec.configure(
|
||||||
|
mediaFormat,
|
||||||
|
outputSurface,
|
||||||
|
/* crypto= */ null,
|
||||||
|
isDecoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||||
|
TraceUtil.endSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startCodec(MediaCodec codec) {
|
||||||
|
TraceUtil.beginSection("startCodec");
|
||||||
|
codec.start();
|
||||||
|
TraceUtil.endSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TransformationException createTransformationException(
|
private static TransformationException createTransformationException(
|
||||||
Exception cause, Format format, boolean isVideo, boolean isDecoder) {
|
Exception cause, Format format, boolean isVideo, boolean isDecoder) {
|
||||||
String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder");
|
String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder");
|
||||||
if (cause instanceof IOException) {
|
if (cause instanceof IOException || cause instanceof MediaCodec.CodecException) {
|
||||||
return TransformationException.createForCodec(
|
return TransformationException.createForCodec(
|
||||||
cause,
|
cause,
|
||||||
componentName,
|
componentName,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue