Commit bc891273 by claincly Committed by Ian Baker

Rename MediaCodecAdapterWrapper to Codec.

Move static factories into a separate class and make it implement an interface
that will let tests customize encoder/decoder creation.

PiperOrigin-RevId: 417610825
parent 7d83c979
......@@ -44,8 +44,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final Format inputFormat;
private final Transformation transformation;
private final Codec.EncoderFactory encoderFactory;
private final MediaCodecAdapterWrapper decoder;
private final Codec decoder;
private final DecoderInputBuffer decoderInputBuffer;
private final SonicAudioProcessor sonicAudioProcessor;
......@@ -55,7 +56,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final DecoderInputBuffer encoderOutputBuffer;
private @MonotonicNonNull AudioFormat encoderInputAudioFormat;
private @MonotonicNonNull MediaCodecAdapterWrapper encoder;
private @MonotonicNonNull Codec encoder;
private long nextEncoderInputBufferTimeUs;
private long encoderBufferDurationRemainder;
......@@ -63,10 +64,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private boolean drainingSonicForSpeedChange;
private float currentSpeed;
public AudioSamplePipeline(Format inputFormat, Transformation transformation)
public AudioSamplePipeline(
Format inputFormat,
Transformation transformation,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory)
throws TransformationException {
this.inputFormat = inputFormat;
this.transformation = transformation;
this.encoderFactory = encoderFactory;
decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
encoderInputBuffer =
......@@ -77,7 +83,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
speedProvider = new SegmentSpeedProvider(inputFormat);
currentSpeed = speedProvider.getSpeed(0);
this.decoder = MediaCodecAdapterWrapper.createForAudioDecoding(inputFormat);
this.decoder = decoderFactory.createForAudioDecoding(inputFormat);
}
@Override
......@@ -301,7 +307,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
}
}
encoder =
MediaCodecAdapterWrapper.createForAudioEncoding(
encoderFactory.createForAudioEncoding(
new Format.Builder()
.setSampleMimeType(
transformation.audioMimeType == null
......
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Util.SDK_INT;
import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaFormat;
import android.view.Surface;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.SynchronousMediaCodecAdapter;
import com.google.android.exoplayer2.util.MediaFormatUtil;
import java.io.IOException;
/** A default {@link Codec.DecoderFactory} and {@link Codec.EncoderFactory}. */
/* package */ final class DefaultCodecFactory
implements Codec.DecoderFactory, Codec.EncoderFactory {
@Override
public Codec createForAudioDecoding(Format format) throws TransformationException {
MediaFormat mediaFormat =
MediaFormat.createAudioFormat(
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
MediaFormatUtil.maybeSetInteger(
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
MediaCodecAdapter adapter;
try {
adapter =
new MediaCodecFactory()
.createAdapter(
MediaCodecAdapter.Configuration.createForAudioDecoding(
createPlaceholderMediaCodecInfo(), mediaFormat, format, /* crypto= */ null));
} catch (Exception e) {
throw createTransformationException(e, format, /* isVideo= */ false, /* isDecoder= */ true);
}
return new Codec(adapter);
}
@Override
@SuppressLint("InlinedApi")
public Codec createForVideoDecoding(Format format, Surface surface)
throws TransformationException {
MediaFormat mediaFormat =
MediaFormat.createVideoFormat(
checkNotNull(format.sampleMimeType), format.width, format.height);
MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees);
MediaFormatUtil.maybeSetInteger(
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
if (SDK_INT >= 29) {
// On API levels over 29, Transformer decodes as many frames as possible in one render
// cycle. This key ensures no frame dropping when the decoder's output surface is full.
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
}
MediaCodecAdapter adapter;
try {
adapter =
new MediaCodecFactory()
.createAdapter(
MediaCodecAdapter.Configuration.createForVideoDecoding(
createPlaceholderMediaCodecInfo(),
mediaFormat,
format,
surface,
/* crypto= */ null));
} catch (Exception e) {
throw createTransformationException(e, format, /* isVideo= */ true, /* isDecoder= */ true);
}
return new Codec(adapter);
}
@Override
public Codec createForAudioEncoding(Format format) throws TransformationException {
MediaFormat mediaFormat =
MediaFormat.createAudioFormat(
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
MediaCodecAdapter adapter;
try {
adapter =
new MediaCodecFactory()
.createAdapter(
MediaCodecAdapter.Configuration.createForAudioEncoding(
createPlaceholderMediaCodecInfo(), mediaFormat, format));
} catch (Exception e) {
throw createTransformationException(e, format, /* isVideo= */ false, /* isDecoder= */ false);
}
return new Codec(adapter);
}
@Override
public Codec createForVideoEncoding(Format format) throws TransformationException {
checkArgument(format.width != Format.NO_VALUE);
checkArgument(format.height != Format.NO_VALUE);
// According to interface Javadoc, format.rotationDegrees should be 0. The video should always
// be in landscape orientation.
checkArgument(format.height < format.width);
checkArgument(format.rotationDegrees == 0);
MediaFormat mediaFormat =
MediaFormat.createVideoFormat(
checkNotNull(format.sampleMimeType), format.width, format.height);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, CodecCapabilities.COLOR_FormatSurface);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 413_000);
MediaCodecAdapter adapter;
try {
adapter =
new MediaCodecFactory()
.createAdapter(
MediaCodecAdapter.Configuration.createForVideoEncoding(
createPlaceholderMediaCodecInfo(), 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 {
@Override
protected MediaCodec createCodec(MediaCodecAdapter.Configuration configuration)
throws IOException {
String sampleMimeType =
checkNotNull(configuration.mediaFormat.getString(MediaFormat.KEY_MIME));
boolean isDecoder = (configuration.flags & MediaCodec.CONFIGURE_FLAG_ENCODE) == 0;
return isDecoder
? 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);
}
private static TransformationException createTransformationException(
Exception cause, Format format, boolean isVideo, boolean isDecoder) {
String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder");
if (cause instanceof IOException) {
return TransformationException.createForCodec(
cause,
componentName,
format,
isDecoder
? TransformationException.ERROR_CODE_DECODER_INIT_FAILED
: TransformationException.ERROR_CODE_ENCODER_INIT_FAILED);
}
if (cause instanceof IllegalArgumentException) {
return TransformationException.createForCodec(
cause,
componentName,
format,
isDecoder
? TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED
: TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
}
return TransformationException.createForUnexpected(cause);
}
}
......@@ -107,6 +107,7 @@ public final class Transformer {
private DebugViewProvider debugViewProvider;
private Looper looper;
private Clock clock;
private Codec.EncoderFactory encoderFactory;
/** @deprecated Use {@link #Builder(Context)} instead. */
@Deprecated
......@@ -118,6 +119,7 @@ public final class Transformer {
listener = new Listener() {};
looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT;
encoderFactory = Codec.EncoderFactory.DEFAULT;
debugViewProvider = DebugViewProvider.NONE;
}
......@@ -135,6 +137,7 @@ public final class Transformer {
listener = new Listener() {};
looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT;
encoderFactory = Codec.EncoderFactory.DEFAULT;
debugViewProvider = DebugViewProvider.NONE;
}
......@@ -153,6 +156,7 @@ public final class Transformer {
this.videoMimeType = transformer.transformation.videoMimeType;
this.listener = transformer.listener;
this.looper = transformer.looper;
this.encoderFactory = transformer.encoderFactory;
this.debugViewProvider = transformer.debugViewProvider;
this.clock = transformer.clock;
}
......@@ -361,6 +365,18 @@ public final class Transformer {
}
/**
* Sets the {@link Codec.EncoderFactory} that will be used by the transformer. The default value
* is {@link Codec.EncoderFactory#DEFAULT}.
*
* @param encoderFactory The {@link Codec.EncoderFactory} instance.
* @return This builder.
*/
public Builder setEncoderFactory(Codec.EncoderFactory encoderFactory) {
this.encoderFactory = encoderFactory;
return this;
}
/**
* Sets a provider for views to show diagnostic information (if available) during
* transformation. This is intended for debugging. The default value is {@link
* DebugViewProvider#NONE}, which doesn't show any debug info.
......@@ -448,6 +464,8 @@ public final class Transformer {
listener,
looper,
clock,
encoderFactory,
Codec.DecoderFactory.DEFAULT,
debugViewProvider);
}
......@@ -536,6 +554,8 @@ public final class Transformer {
private final Transformation transformation;
private final Looper looper;
private final Clock clock;
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final Transformer.DebugViewProvider debugViewProvider;
private Transformer.Listener listener;
......@@ -551,6 +571,8 @@ public final class Transformer {
Transformer.Listener listener,
Looper looper,
Clock clock,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider) {
checkState(
!transformation.removeAudio || !transformation.removeVideo,
......@@ -562,6 +584,8 @@ public final class Transformer {
this.listener = listener;
this.looper = looper;
this.clock = clock;
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
this.debugViewProvider = debugViewProvider;
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
}
......@@ -662,7 +686,12 @@ public final class Transformer {
new ExoPlayer.Builder(
context,
new TransformerRenderersFactory(
context, muxerWrapper, transformation, debugViewProvider))
context,
muxerWrapper,
transformation,
encoderFactory,
decoderFactory,
debugViewProvider))
.setMediaSourceFactory(mediaSourceFactory)
.setTrackSelector(trackSelector)
.setLoadControl(loadControl)
......@@ -751,16 +780,22 @@ public final class Transformer {
private final MuxerWrapper muxerWrapper;
private final TransformerMediaClock mediaClock;
private final Transformation transformation;
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final Transformer.DebugViewProvider debugViewProvider;
public TransformerRenderersFactory(
Context context,
MuxerWrapper muxerWrapper,
Transformation transformation,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider) {
this.context = context;
this.muxerWrapper = muxerWrapper;
this.transformation = transformation;
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
this.debugViewProvider = debugViewProvider;
mediaClock = new TransformerMediaClock();
}
......@@ -776,13 +811,21 @@ public final class Transformer {
Renderer[] renderers = new Renderer[rendererCount];
int index = 0;
if (!transformation.removeAudio) {
renderers[index] = new TransformerAudioRenderer(muxerWrapper, mediaClock, transformation);
renderers[index] =
new TransformerAudioRenderer(
muxerWrapper, mediaClock, transformation, encoderFactory, decoderFactory);
index++;
}
if (!transformation.removeVideo) {
renderers[index] =
new TransformerVideoRenderer(
context, muxerWrapper, mediaClock, transformation, debugViewProvider);
context,
muxerWrapper,
mediaClock,
transformation,
encoderFactory,
decoderFactory,
debugViewProvider);
index++;
}
return renderers;
......
......@@ -32,11 +32,19 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
private static final String TAG = "TAudioRenderer";
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final DecoderInputBuffer decoderInputBuffer;
public TransformerAudioRenderer(
MuxerWrapper muxerWrapper, TransformerMediaClock mediaClock, Transformation transformation) {
MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock,
Transformation transformation,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory) {
super(C.TRACK_TYPE_AUDIO, muxerWrapper, mediaClock, transformation);
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
}
......@@ -60,7 +68,8 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
}
Format inputFormat = checkNotNull(formatHolder.format);
if (shouldTranscode(inputFormat)) {
samplePipeline = new AudioSamplePipeline(inputFormat, transformation);
samplePipeline =
new AudioSamplePipeline(inputFormat, transformation, encoderFactory, decoderFactory);
} else {
samplePipeline = new PassthroughSamplePipeline(inputFormat);
}
......
......@@ -34,6 +34,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private static final String TAG = "TVideoRenderer";
private final Context context;
private final Codec.EncoderFactory encoderFactory;
private final Codec.DecoderFactory decoderFactory;
private final Transformer.DebugViewProvider debugViewProvider;
private final DecoderInputBuffer decoderInputBuffer;
......@@ -44,9 +46,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock,
Transformation transformation,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider) {
super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation);
this.context = context;
this.encoderFactory = encoderFactory;
this.decoderFactory = decoderFactory;
this.debugViewProvider = debugViewProvider;
decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
......@@ -72,7 +78,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Format inputFormat = checkNotNull(formatHolder.format);
if (shouldTranscode(inputFormat)) {
samplePipeline =
new VideoSamplePipeline(context, inputFormat, transformation, debugViewProvider);
new VideoSamplePipeline(
context,
inputFormat,
transformation,
encoderFactory,
decoderFactory,
debugViewProvider);
} else {
samplePipeline = new PassthroughSamplePipeline(inputFormat);
}
......
......@@ -27,7 +27,6 @@ import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.common.collect.ImmutableMap;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
......@@ -39,9 +38,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final int outputRotationDegrees;
private final DecoderInputBuffer decoderInputBuffer;
private final MediaCodecAdapterWrapper decoder;
private final Codec decoder;
private final MediaCodecAdapterWrapper encoder;
private final Codec encoder;
private final DecoderInputBuffer encoderOutputBuffer;
private @MonotonicNonNull FrameEditor frameEditor;
......@@ -52,6 +51,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Context context,
Format inputFormat,
Transformation transformation,
Codec.EncoderFactory encoderFactory,
Codec.DecoderFactory decoderFactory,
Transformer.DebugViewProvider debugViewProvider)
throws TransformationException {
decoderInputBuffer =
......@@ -85,7 +86,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
transformation.transformationMatrix.postRotate(outputRotationDegrees);
encoder =
MediaCodecAdapterWrapper.createForVideoEncoding(
encoderFactory.createForVideoEncoding(
new Format.Builder()
.setWidth(outputWidth)
.setHeight(outputHeight)
......@@ -94,8 +95,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
transformation.videoMimeType != null
? transformation.videoMimeType
: inputFormat.sampleMimeType)
.build(),
ImmutableMap.of());
.build());
if (inputFormat.height != outputHeight
|| inputFormat.width != outputWidth
|| !transformation.transformationMatrix.isIdentity()) {
......@@ -109,7 +109,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
debugViewProvider);
}
decoder =
MediaCodecAdapterWrapper.createForVideoDecoding(
decoderFactory.createForVideoDecoding(
inputFormat,
frameEditor == null
? checkNotNull(encoder.getInputSurface())
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment