Commit 8bb53b40 by hschlueter Committed by Ian Baker

Remove ExoPlaybackException dependency from sample pipelines.

Use TransformationException for codec and audio processor
initialization problems instead.

PiperOrigin-RevId: 416765510
parent 66adeabb
...@@ -24,14 +24,11 @@ import static java.lang.Math.min; ...@@ -24,14 +24,11 @@ import static java.lang.Math.min;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.audio.AudioProcessor; import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat; import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat;
import com.google.android.exoplayer2.audio.SonicAudioProcessor; import com.google.android.exoplayer2.audio.SonicAudioProcessor;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import java.io.IOException;
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;
...@@ -47,7 +44,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -47,7 +44,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final Format inputFormat; private final Format inputFormat;
private final Transformation transformation; private final Transformation transformation;
private final int rendererIndex;
private final MediaCodecAdapterWrapper decoder; private final MediaCodecAdapterWrapper decoder;
private final DecoderInputBuffer decoderInputBuffer; private final DecoderInputBuffer decoderInputBuffer;
...@@ -67,11 +63,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -67,11 +63,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private boolean drainingSonicForSpeedChange; private boolean drainingSonicForSpeedChange;
private float currentSpeed; private float currentSpeed;
public AudioSamplePipeline(Format inputFormat, Transformation transformation, int rendererIndex) public AudioSamplePipeline(Format inputFormat, Transformation transformation)
throws ExoPlaybackException { throws TransformationException {
this.inputFormat = inputFormat; this.inputFormat = inputFormat;
this.transformation = transformation; this.transformation = transformation;
this.rendererIndex = rendererIndex;
decoderInputBuffer = decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
encoderInputBuffer = encoderInputBuffer =
...@@ -82,19 +77,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -82,19 +77,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER; sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
speedProvider = new SegmentSpeedProvider(inputFormat); speedProvider = new SegmentSpeedProvider(inputFormat);
currentSpeed = speedProvider.getSpeed(0); currentSpeed = speedProvider.getSpeed(0);
try { this.decoder = MediaCodecAdapterWrapper.createForAudioDecoding(inputFormat);
this.decoder = MediaCodecAdapterWrapper.createForAudioDecoding(inputFormat);
} catch (IOException e) {
// TODO(internal b/192864511): Assign a specific error code.
throw ExoPlaybackException.createForRenderer(
e,
TAG,
rendererIndex,
inputFormat,
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
/* isRecoverable= */ false,
PlaybackException.ERROR_CODE_UNSPECIFIED);
}
} }
@Override @Override
...@@ -109,7 +92,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -109,7 +92,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
@Override @Override
public boolean processData() throws ExoPlaybackException { public boolean processData() throws TransformationException {
if (!ensureEncoderAndAudioProcessingConfigured()) { if (!ensureEncoderAndAudioProcessingConfigured()) {
return false; return false;
} }
...@@ -292,7 +275,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -292,7 +275,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@EnsuresNonNullIf( @EnsuresNonNullIf(
expression = {"encoder", "encoderInputAudioFormat"}, expression = {"encoder", "encoderInputAudioFormat"},
result = true) result = true)
private boolean ensureEncoderAndAudioProcessingConfigured() throws ExoPlaybackException { private boolean ensureEncoderAndAudioProcessingConfigured() throws TransformationException {
if (encoder != null && encoderInputAudioFormat != null) { if (encoder != null && encoderInputAudioFormat != null) {
return true; return true;
} }
...@@ -310,27 +293,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -310,27 +293,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat); outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat);
flushSonicAndSetSpeed(currentSpeed); flushSonicAndSetSpeed(currentSpeed);
} catch (AudioProcessor.UnhandledAudioFormatException e) { } catch (AudioProcessor.UnhandledAudioFormatException e) {
// TODO(internal b/192864511): Assign an adequate error code. throw TransformationException.createForAudioProcessor(
throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED); e,
"Sonic",
outputAudioFormat,
TransformationException.ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED);
} }
} }
String audioMimeType = encoder =
transformation.audioMimeType == null MediaCodecAdapterWrapper.createForAudioEncoding(
? inputFormat.sampleMimeType new Format.Builder()
: transformation.audioMimeType; .setSampleMimeType(
try { transformation.audioMimeType == null
encoder = ? inputFormat.sampleMimeType
MediaCodecAdapterWrapper.createForAudioEncoding( : transformation.audioMimeType)
new Format.Builder() .setSampleRate(outputAudioFormat.sampleRate)
.setSampleMimeType(audioMimeType) .setChannelCount(outputAudioFormat.channelCount)
.setSampleRate(outputAudioFormat.sampleRate) .setAverageBitrate(DEFAULT_ENCODER_BITRATE)
.setChannelCount(outputAudioFormat.channelCount) .build());
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
.build());
} catch (IOException e) {
// TODO(internal b/192864511): Assign an adequate error code.
throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED);
}
encoderInputAudioFormat = outputAudioFormat; encoderInputAudioFormat = outputAudioFormat;
return true; return true;
} }
...@@ -351,17 +331,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -351,17 +331,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
sonicAudioProcessor.flush(); sonicAudioProcessor.flush();
} }
private ExoPlaybackException createRendererException(Throwable cause, int errorCode) {
return ExoPlaybackException.createForRenderer(
cause,
TAG,
rendererIndex,
inputFormat,
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
/* isRecoverable= */ false,
errorCode);
}
private void computeNextEncoderInputBufferTimeUs( private void computeNextEncoderInputBufferTimeUs(
long bytesWritten, int bytesPerFrame, int sampleRate) { long bytesWritten, int bytesPerFrame, int sampleRate) {
// The calculation below accounts for remainders and rounding. Without that it corresponds to // The calculation below accounts for remainders and rounding. Without that it corresponds to
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
...@@ -44,7 +43,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer; ...@@ -44,7 +43,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
* Processes the input data and returns whether more data can be processed by calling this method * Processes the input data and returns whether more data can be processed by calling this method
* again. * again.
*/ */
boolean processData() throws ExoPlaybackException; boolean processData() throws TransformationException;
/** Returns the output format of the pipeline if available, and {@code null} otherwise. */ /** Returns the output format of the pipeline if available, and {@code null} otherwise. */
@Nullable @Nullable
......
...@@ -24,6 +24,9 @@ import static java.lang.annotation.ElementType.TYPE_USE; ...@@ -24,6 +24,9 @@ import static java.lang.annotation.ElementType.TYPE_USE;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioProcessor;
import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
...@@ -35,6 +38,36 @@ import java.lang.annotation.Target; ...@@ -35,6 +38,36 @@ import java.lang.annotation.Target;
public final class TransformationException extends Exception { public final class TransformationException extends Exception {
/** /**
* Creates an instance for a decoder or encoder related exception.
*
* @param cause The cause of the failure.
* @param componentName The name of the component used, e.g. 'VideoEncoder'.
* @param format The {@link Format} used for the decoder/encoder.
* @param errorCode See {@link #errorCode}.
* @return The created instance.
*/
public static TransformationException createForCodec(
Throwable cause, String componentName, Format format, int errorCode) {
return new TransformationException(
componentName + " error, format = " + format, cause, errorCode);
}
/**
* Creates an instance for an audio processing related exception.
*
* @param cause The cause of the failure.
* @param componentName The name of the {@link AudioProcessor} used.
* @param audioFormat The {@link AudioFormat} used.
* @param errorCode See {@link #errorCode}.
* @return The created instance.
*/
public static TransformationException createForAudioProcessor(
Throwable cause, String componentName, AudioFormat audioFormat, int errorCode) {
return new TransformationException(
componentName + " error, audio_format = " + audioFormat, cause, errorCode);
}
/**
* Creates an instance for an unexpected exception. * Creates an instance for an unexpected exception.
* *
* <p>If the exception is a runtime exception, error code {@link ERROR_CODE_FAILED_RUNTIME_CHECK} * <p>If the exception is a runtime exception, error code {@link ERROR_CODE_FAILED_RUNTIME_CHECK}
...@@ -72,6 +105,7 @@ public final class TransformationException extends Exception { ...@@ -72,6 +105,7 @@ public final class TransformationException extends Exception {
ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED, ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
ERROR_CODE_GL_INIT_FAILED, ERROR_CODE_GL_INIT_FAILED,
ERROR_CODE_GL_PROCESSING_FAILED, ERROR_CODE_GL_PROCESSING_FAILED,
ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED,
}) })
public @interface ErrorCode {} public @interface ErrorCode {}
...@@ -104,13 +138,18 @@ public final class TransformationException extends Exception { ...@@ -104,13 +138,18 @@ public final class TransformationException extends Exception {
/** Caused by requesting to encode content in a format that is not supported by the device. */ /** Caused by requesting to encode content in a format that is not supported by the device. */
public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 3003; public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 3003;
// GL errors (4xxx). // Video editing errors (4xxx).
/** Caused by a GL initialization failure. */ /** Caused by a GL initialization failure. */
public static final int ERROR_CODE_GL_INIT_FAILED = 4001; public static final int ERROR_CODE_GL_INIT_FAILED = 4001;
/** Caused by a failure while using or releasing a GL program. */ /** Caused by a failure while using or releasing a GL program. */
public static final int ERROR_CODE_GL_PROCESSING_FAILED = 4002; public static final int ERROR_CODE_GL_PROCESSING_FAILED = 4002;
// Audio editing errors (5xxx).
/** Caused by an audio processor initialization failure. */
public static final int ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED = 5001;
/** Returns the name of a given {@code errorCode}. */ /** Returns the name of a given {@code errorCode}. */
public static String getErrorCodeName(@ErrorCode int errorCode) { public static String getErrorCodeName(@ErrorCode int errorCode) {
switch (errorCode) { switch (errorCode) {
...@@ -134,6 +173,8 @@ public final class TransformationException extends Exception { ...@@ -134,6 +173,8 @@ public final class TransformationException extends Exception {
return "ERROR_CODE_GL_INIT_FAILED"; return "ERROR_CODE_GL_INIT_FAILED";
case ERROR_CODE_GL_PROCESSING_FAILED: case ERROR_CODE_GL_PROCESSING_FAILED:
return "ERROR_CODE_GL_PROCESSING_FAILED"; return "ERROR_CODE_GL_PROCESSING_FAILED";
case ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED:
return "ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED";
default: default:
return "invalid error code"; return "invalid error code";
} }
......
...@@ -829,29 +829,33 @@ public final class Transformer { ...@@ -829,29 +829,33 @@ public final class Transformer {
@Override @Override
public void onTracksInfoChanged(TracksInfo tracksInfo) { public void onTracksInfoChanged(TracksInfo tracksInfo) {
if (muxerWrapper.getTrackCount() == 0) { if (muxerWrapper.getTrackCount() == 0) {
// TODO(b/209469847): Do not silently drop unsupported tracks and throw a more specific
// exception earlier.
handleTransformationEnded( handleTransformationEnded(
new IllegalStateException( TransformationException.createForUnexpected(
"The output does not contain any tracks. Check that at least one of the input" new IllegalStateException(
+ " sample formats is supported.")); "The output does not contain any tracks. Check that at least one of the input"
+ " sample formats is supported.")));
} }
} }
@Override @Override
public void onPlayerError(PlaybackException error) { public void onPlayerError(PlaybackException error) {
// TODO(internal b/209469847): Once TransformationException is used in transformer components, Throwable cause = error.getCause();
// extract TransformationExceptions wrapped in the PlaybackExceptions here before passing them handleTransformationEnded(
// on. cause instanceof TransformationException
handleTransformationEnded(error); ? (TransformationException) cause
: TransformationException.createForUnexpected(error));
} }
private void handleTransformationEnded(@Nullable Exception exception) { private void handleTransformationEnded(@Nullable TransformationException exception) {
@Nullable Exception resourceReleaseException = null; @Nullable TransformationException resourceReleaseException = null;
try { try {
releaseResources(/* forCancellation= */ false); releaseResources(/* forCancellation= */ false);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// TODO(internal b/209469847): Use a TransformationException with a specific error code when // TODO(internal b/209469847): Use a more specific error code when the IllegalStateException
// the IllegalStateException is caused by the muxer. // is caused by the muxer.
resourceReleaseException = e; resourceReleaseException = TransformationException.createForUnexpected(e);
} }
if (exception == null && resourceReleaseException == null) { if (exception == null && resourceReleaseException == null) {
...@@ -860,15 +864,10 @@ public final class Transformer { ...@@ -860,15 +864,10 @@ public final class Transformer {
} }
if (exception != null) { if (exception != null) {
listener.onTransformationError( listener.onTransformationError(mediaItem, exception);
mediaItem,
exception instanceof TransformationException
? exception
: TransformationException.createForUnexpected(exception));
} }
if (resourceReleaseException != null) { if (resourceReleaseException != null) {
listener.onTransformationError( listener.onTransformationError(mediaItem, resourceReleaseException);
mediaItem, TransformationException.createForUnexpected(resourceReleaseException));
} }
} }
} }
......
...@@ -21,7 +21,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; ...@@ -21,7 +21,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
...@@ -49,7 +48,7 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; ...@@ -49,7 +48,7 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */ /** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
@Override @Override
protected boolean ensureConfigured() throws ExoPlaybackException { protected boolean ensureConfigured() throws TransformationException {
if (samplePipeline != null) { if (samplePipeline != null) {
return true; return true;
} }
...@@ -61,7 +60,7 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; ...@@ -61,7 +60,7 @@ import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
} }
Format inputFormat = checkNotNull(formatHolder.format); Format inputFormat = checkNotNull(formatHolder.format);
if (shouldTranscode(inputFormat)) { if (shouldTranscode(inputFormat)) {
samplePipeline = new AudioSamplePipeline(inputFormat, transformation, getIndex()); samplePipeline = new AudioSamplePipeline(inputFormat, transformation);
} else { } else {
samplePipeline = new PassthroughSamplePipeline(inputFormat); samplePipeline = new PassthroughSamplePipeline(inputFormat);
} }
......
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.BaseRenderer; ...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
...@@ -95,11 +96,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -95,11 +96,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override @Override
public final void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { public final void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (!isRendererStarted || isEnded() || !ensureConfigured()) { try {
return; if (!isRendererStarted || isEnded() || !ensureConfigured()) {
} return;
}
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {} while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
} catch (TransformationException e) {
// Transformer extracts the TransformationException from this ExoPlaybackException again. This
// temporary wrapping is needed due to the dependence on ExoPlayer's BaseRenderer.
throw ExoPlaybackException.createForRenderer(
e,
"Transformer",
getIndex(),
/* rendererFormat= */ null,
C.FORMAT_HANDLED,
/* isRecoverable= */ false,
PlaybackException.ERROR_CODE_UNSPECIFIED);
}
} }
@Override @Override
...@@ -134,7 +148,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -134,7 +148,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@ForOverride @ForOverride
@EnsuresNonNullIf(expression = "samplePipeline", result = true) @EnsuresNonNullIf(expression = "samplePipeline", result = true)
protected abstract boolean ensureConfigured() throws ExoPlaybackException; protected abstract boolean ensureConfigured() throws TransformationException;
@RequiresNonNull({"samplePipeline", "#1.data"}) @RequiresNonNull({"samplePipeline", "#1.data"})
protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) { protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) {
......
...@@ -21,7 +21,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; ...@@ -21,7 +21,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.content.Context; import android.content.Context;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
...@@ -60,7 +59,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -60,7 +59,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */ /** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
@Override @Override
protected boolean ensureConfigured() throws ExoPlaybackException { protected boolean ensureConfigured() throws TransformationException {
if (samplePipeline != null) { if (samplePipeline != null) {
return true; return true;
} }
...@@ -73,8 +72,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -73,8 +72,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Format inputFormat = checkNotNull(formatHolder.format); Format inputFormat = checkNotNull(formatHolder.format);
if (shouldTranscode(inputFormat)) { if (shouldTranscode(inputFormat)) {
samplePipeline = samplePipeline =
new VideoSamplePipeline( new VideoSamplePipeline(context, inputFormat, transformation, debugViewProvider);
context, inputFormat, transformation, getIndex(), debugViewProvider);
} else { } else {
samplePipeline = new PassthroughSamplePipeline(inputFormat); samplePipeline = new PassthroughSamplePipeline(inputFormat);
} }
......
...@@ -25,12 +25,9 @@ import android.media.MediaFormat; ...@@ -25,12 +25,9 @@ import android.media.MediaFormat;
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;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
...@@ -55,9 +52,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -55,9 +52,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Context context, Context context,
Format inputFormat, Format inputFormat,
Transformation transformation, Transformation transformation,
int rendererIndex,
Transformer.DebugViewProvider debugViewProvider) Transformer.DebugViewProvider debugViewProvider)
throws ExoPlaybackException { throws TransformationException {
decoderInputBuffer = decoderInputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
encoderOutputBuffer = encoderOutputBuffer =
...@@ -88,24 +84,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -88,24 +84,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// postrotation in a later vertex shader. // postrotation in a later vertex shader.
transformation.transformationMatrix.postRotate(outputRotationDegrees); transformation.transformationMatrix.postRotate(outputRotationDegrees);
try { encoder =
encoder = MediaCodecAdapterWrapper.createForVideoEncoding(
MediaCodecAdapterWrapper.createForVideoEncoding( new Format.Builder()
new Format.Builder() .setWidth(outputWidth)
.setWidth(outputWidth) .setHeight(outputHeight)
.setHeight(outputHeight) .setRotationDegrees(0)
.setRotationDegrees(0) .setSampleMimeType(
.setSampleMimeType( transformation.videoMimeType != null
transformation.videoMimeType != null ? transformation.videoMimeType
? transformation.videoMimeType : inputFormat.sampleMimeType)
: inputFormat.sampleMimeType) .build(),
.build(), ImmutableMap.of());
ImmutableMap.of());
} catch (IOException e) {
// TODO(internal b/192864511): Assign a specific error code.
throw createRendererException(
e, rendererIndex, inputFormat, PlaybackException.ERROR_CODE_UNSPECIFIED);
}
if (inputFormat.height != outputHeight if (inputFormat.height != outputHeight
|| inputFormat.width != outputWidth || inputFormat.width != outputWidth
|| !transformation.transformationMatrix.isIdentity()) { || !transformation.transformationMatrix.isIdentity()) {
...@@ -118,17 +108,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -118,17 +108,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* outputSurface= */ checkNotNull(encoder.getInputSurface()), /* outputSurface= */ checkNotNull(encoder.getInputSurface()),
debugViewProvider); debugViewProvider);
} }
try { decoder =
decoder = MediaCodecAdapterWrapper.createForVideoDecoding(
MediaCodecAdapterWrapper.createForVideoDecoding( inputFormat,
inputFormat, frameEditor == null
frameEditor == null ? checkNotNull(encoder.getInputSurface())
? checkNotNull(encoder.getInputSurface()) : frameEditor.getInputSurface());
: frameEditor.getInputSurface());
} catch (IOException e) {
throw createRendererException(
e, rendererIndex, inputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
}
} }
@Override @Override
...@@ -257,16 +242,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -257,16 +242,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
decoder.release(); decoder.release();
encoder.release(); encoder.release();
} }
private static ExoPlaybackException createRendererException(
Throwable cause, int rendererIndex, Format inputFormat, int errorCode) {
return ExoPlaybackException.createForRenderer(
cause,
TAG,
rendererIndex,
inputFormat,
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
/* isRecoverable= */ false,
errorCode);
}
} }
...@@ -24,11 +24,15 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -24,11 +24,15 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import android.content.Context; import android.content.Context;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -39,6 +43,7 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -39,6 +43,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -233,6 +238,63 @@ public final class TransformerTest { ...@@ -233,6 +238,63 @@ public final class TransformerTest {
} }
@Test @Test
public void startTransformation_withAudioEncoderFormatUnsupported_completesWithError()
throws Exception {
Transformer transformer =
new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.setAudioMimeType(MimeTypes.AUDIO_AMR_WB) // unsupported encoder MIME type
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
assertThat(exception.errorCode)
.isEqualTo(TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
}
@Test
public void startTransformation_withAudioDecoderFormatUnsupported_completesWithError()
throws Exception {
Transformer transformer =
new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported encoder MIME type
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_ALL_SAMPLE_FORMATS_UNSUPPORTED);
transformer.startTransformation(mediaItem, outputPath);
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
assertThat(exception.errorCode)
.isEqualTo(TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED);
}
@Test
public void startTransformation_withVideoEncoderFormatUnsupported_completesWithError()
throws Exception {
Transformer transformer =
new Transformer.Builder(context)
.setClock(clock)
.setMuxerFactory(new TestMuxerFactory())
.setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type
.build();
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
transformer.startTransformation(mediaItem, outputPath);
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
assertThat(exception.errorCode)
.isEqualTo(TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
}
@Test
public void startTransformation_withPlayerError_completesWithError() throws Exception { public void startTransformation_withPlayerError_completesWithError() throws Exception {
Transformer transformer = new Transformer.Builder(context).setClock(clock).build(); Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4"); MediaItem mediaItem = MediaItem.fromUri("asset:///non-existing-path.mp4");
...@@ -541,6 +603,30 @@ public final class TransformerTest { ...@@ -541,6 +603,30 @@ public final class TransformerTest {
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AAC, codecConfig); ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AAC, codecConfig);
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AMR_NB, codecConfig); ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AMR_NB, codecConfig);
ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AAC, codecConfig); ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AAC, codecConfig);
ShadowMediaCodec.CodecConfig throwingCodecConfig =
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 10_000,
/* outputBufferSize= */ 10_000,
new ShadowMediaCodec.CodecConfig.Codec() {
@Override
public void process(ByteBuffer in, ByteBuffer out) {
out.put(in);
}
@Override
public void onConfigured(
MediaFormat format,
@Nullable Surface surface,
@Nullable MediaCrypto crypto,
int flags) {
throw new IllegalArgumentException("Format unsupported");
}
});
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AC3, throwingCodecConfig);
ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AMR_WB, throwingCodecConfig);
ShadowMediaCodec.addEncoder(MimeTypes.VIDEO_H263, throwingCodecConfig);
} }
private static void removeEncodersAndDecoders() { private static void removeEncodersAndDecoders() {
......
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