Use specific error code for exceptions during encoding/decoding.

After this change exceptions throw by MediaCodec during
encoding/decoding will result in TransformationExceptions with
ERROR_CODE_ENCODING_FAILED/ERROR_CODE_DECODING_FAILED.
Before this change ERROR_CODE_FAILED_RUNTIME_CHECK was used.

PiperOrigin-RevId: 421560396
This commit is contained in:
hschlueter 2022-01-13 15:32:21 +00:00 committed by Ian Baker
parent 05924eaa52
commit 80851807f2
8 changed files with 184 additions and 53 deletions

View File

@ -113,17 +113,17 @@ import java.nio.ByteBuffer;
@Override @Override
@Nullable @Nullable
public DecoderInputBuffer dequeueInputBuffer() { public DecoderInputBuffer dequeueInputBuffer() throws TransformationException {
return decoder.maybeDequeueInputBuffer(decoderInputBuffer) ? decoderInputBuffer : null; return decoder.maybeDequeueInputBuffer(decoderInputBuffer) ? decoderInputBuffer : null;
} }
@Override @Override
public void queueInputBuffer() { public void queueInputBuffer() throws TransformationException {
decoder.queueInputBuffer(decoderInputBuffer); decoder.queueInputBuffer(decoderInputBuffer);
} }
@Override @Override
public boolean processData() { public boolean processData() throws TransformationException {
if (sonicAudioProcessor.isActive()) { if (sonicAudioProcessor.isActive()) {
return feedEncoderFromSonic() || feedSonicFromDecoder(); return feedEncoderFromSonic() || feedSonicFromDecoder();
} else { } else {
@ -133,13 +133,13 @@ import java.nio.ByteBuffer;
@Override @Override
@Nullable @Nullable
public Format getOutputFormat() { public Format getOutputFormat() throws TransformationException {
return encoder != null ? encoder.getOutputFormat() : null; return encoder != null ? encoder.getOutputFormat() : null;
} }
@Override @Override
@Nullable @Nullable
public DecoderInputBuffer getOutputBuffer() { public DecoderInputBuffer getOutputBuffer() throws TransformationException {
if (encoder != null) { if (encoder != null) {
encoderOutputBuffer.data = encoder.getOutputBuffer(); encoderOutputBuffer.data = encoder.getOutputBuffer();
if (encoderOutputBuffer.data != null) { if (encoderOutputBuffer.data != null) {
@ -152,7 +152,7 @@ import java.nio.ByteBuffer;
} }
@Override @Override
public void releaseOutputBuffer() { public void releaseOutputBuffer() throws TransformationException {
checkStateNotNull(encoder).releaseOutputBuffer(); checkStateNotNull(encoder).releaseOutputBuffer();
} }
@ -174,7 +174,7 @@ import java.nio.ByteBuffer;
* Attempts to pass decoder output data to the encoder, and returns whether it may be possible to * Attempts to pass decoder output data to the encoder, and returns whether it may be possible to
* pass more data immediately by calling this method again. * pass more data immediately by calling this method again.
*/ */
private boolean feedEncoderFromDecoder() { private boolean feedEncoderFromDecoder() throws TransformationException {
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) { if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
return false; return false;
} }
@ -203,7 +203,7 @@ import java.nio.ByteBuffer;
* Attempts to pass audio processor output data to the encoder, and returns whether it may be * Attempts to pass audio processor output data to the encoder, and returns whether it may be
* possible to pass more data immediately by calling this method again. * possible to pass more data immediately by calling this method again.
*/ */
private boolean feedEncoderFromSonic() { private boolean feedEncoderFromSonic() throws TransformationException {
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) { if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
return false; return false;
} }
@ -226,7 +226,7 @@ import java.nio.ByteBuffer;
* Attempts to process decoder output data, and returns whether it may be possible to process more * Attempts to process decoder output data, and returns whether it may be possible to process more
* data immediately by calling this method again. * data immediately by calling this method again.
*/ */
private boolean feedSonicFromDecoder() { private boolean feedSonicFromDecoder() throws TransformationException {
if (drainingSonicForSpeedChange) { if (drainingSonicForSpeedChange) {
if (sonicAudioProcessor.isEnded() && !sonicOutputBuffer.hasRemaining()) { if (sonicAudioProcessor.isEnded() && !sonicOutputBuffer.hasRemaining()) {
flushSonicAndSetSpeed(currentSpeed); flushSonicAndSetSpeed(currentSpeed);
@ -267,7 +267,7 @@ import java.nio.ByteBuffer;
* Feeds as much data as possible between the current position and limit of the specified {@link * Feeds as much data as possible between the current position and limit of the specified {@link
* ByteBuffer} to the encoder, and advances its position by the number of bytes fed. * ByteBuffer} to the encoder, and advances its position by the number of bytes fed.
*/ */
private void feedEncoder(ByteBuffer inputBuffer) { private void feedEncoder(ByteBuffer inputBuffer) throws TransformationException {
ByteBuffer encoderInputBufferData = checkNotNull(encoderInputBuffer.data); ByteBuffer encoderInputBufferData = checkNotNull(encoderInputBuffer.data);
int bufferLimit = inputBuffer.limit(); int bufferLimit = inputBuffer.limit();
inputBuffer.limit(min(bufferLimit, inputBuffer.position() + encoderInputBufferData.capacity())); inputBuffer.limit(min(bufferLimit, inputBuffer.position() + encoderInputBufferData.capacity()));
@ -283,7 +283,7 @@ import java.nio.ByteBuffer;
encoder.queueInputBuffer(encoderInputBuffer); encoder.queueInputBuffer(encoderInputBuffer);
} }
private void queueEndOfStreamToEncoder() { private void queueEndOfStreamToEncoder() throws TransformationException {
checkState(checkNotNull(encoderInputBuffer.data).position() == 0); checkState(checkNotNull(encoderInputBuffer.data).position() == 0);
encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs; encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs;
encoderInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); encoderInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);

View File

@ -107,6 +107,7 @@ public final class Codec {
private final BufferInfo outputBufferInfo; private final BufferInfo outputBufferInfo;
private final MediaCodec mediaCodec; private final MediaCodec mediaCodec;
private final Format configurationFormat;
@Nullable private final Surface inputSurface; @Nullable private final Surface inputSurface;
private @MonotonicNonNull Format outputFormat; private @MonotonicNonNull Format outputFormat;
@ -117,15 +118,36 @@ 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 MediaCodec}. */ /**
public Codec(MediaCodec mediaCodec, @Nullable Surface inputSurface) { * Creates a {@code Codec} from a configured and started {@link MediaCodec}.
*
* @param mediaCodec The configured and started {@link MediaCodec}.
* @param configurationFormat See {@link #getConfigurationFormat()}.
* @param inputSurface The input {@link Surface} if the {@link MediaCodec} receives input from a
* surface.
*/
public Codec(MediaCodec mediaCodec, Format configurationFormat, @Nullable Surface inputSurface) {
this.mediaCodec = mediaCodec; this.mediaCodec = mediaCodec;
this.configurationFormat = configurationFormat;
this.inputSurface = inputSurface; 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;
} }
/**
* Returns the {@link Format} used for configuring the codec.
*
* <p>The configuration {@link Format} is the input {@link Format} used by the {@link
* DecoderFactory} or output {@link Format} used by the {@link EncoderFactory} for selecting and
* configuring the underlying {@link MediaCodec}.
*/
// TODO(b/214012830): Use this to check whether the Format passed to the factory and actual
// configuration format differ to see whether fallback was applied.
public Format getConfigurationFormat() {
return configurationFormat;
}
/** 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() {
@ -137,18 +159,28 @@ public final class Codec {
* *
* @param inputBuffer The buffer where the dequeued buffer data is stored. * @param inputBuffer The buffer where the dequeued buffer data is stored.
* @return Whether an input buffer is ready to be used. * @return Whether an input buffer is ready to be used.
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/ */
@EnsuresNonNullIf(expression = "#1.data", result = true) @EnsuresNonNullIf(expression = "#1.data", result = true)
public boolean maybeDequeueInputBuffer(DecoderInputBuffer inputBuffer) { public boolean maybeDequeueInputBuffer(DecoderInputBuffer inputBuffer)
throws TransformationException {
if (inputStreamEnded) { if (inputStreamEnded) {
return false; return false;
} }
if (inputBufferIndex < 0) { if (inputBufferIndex < 0) {
inputBufferIndex = mediaCodec.dequeueInputBuffer(/* timeoutUs= */ 0); try {
inputBufferIndex = mediaCodec.dequeueInputBuffer(/* timeoutUs= */ 0);
} catch (RuntimeException e) {
throw createTransformationException(e);
}
if (inputBufferIndex < 0) { if (inputBufferIndex < 0) {
return false; return false;
} }
inputBuffer.data = mediaCodec.getInputBuffer(inputBufferIndex); try {
inputBuffer.data = mediaCodec.getInputBuffer(inputBufferIndex);
} catch (RuntimeException e) {
throw createTransformationException(e);
}
inputBuffer.clear(); inputBuffer.clear();
} }
checkNotNull(inputBuffer.data); checkNotNull(inputBuffer.data);
@ -158,8 +190,13 @@ public final class Codec {
/** /**
* Queues an input buffer to the decoder. No buffers may be queued after an {@link * Queues an input buffer to the decoder. No buffers may be queued after an {@link
* DecoderInputBuffer#isEndOfStream() end of stream} buffer has been queued. * DecoderInputBuffer#isEndOfStream() end of stream} buffer has been queued.
*
* @param inputBuffer The {@link DecoderInputBuffer input buffer}.
* @throws IllegalStateException If called again after an {@link
* DecoderInputBuffer#isEndOfStream() end of stream} buffer has been queued.
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/ */
public void queueInputBuffer(DecoderInputBuffer inputBuffer) { public void queueInputBuffer(DecoderInputBuffer inputBuffer) throws TransformationException {
checkState( checkState(
!inputStreamEnded, "Input buffer can not be queued after the input stream has ended."); !inputStreamEnded, "Input buffer can not be queued after the input stream has ended.");
@ -174,32 +211,64 @@ public final class Codec {
inputStreamEnded = true; inputStreamEnded = true;
flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
} }
mediaCodec.queueInputBuffer(inputBufferIndex, offset, size, inputBuffer.timeUs, flags); try {
mediaCodec.queueInputBuffer(inputBufferIndex, offset, size, inputBuffer.timeUs, flags);
} catch (RuntimeException e) {
throw createTransformationException(e);
}
inputBufferIndex = C.INDEX_UNSET; inputBufferIndex = C.INDEX_UNSET;
inputBuffer.data = null; inputBuffer.data = null;
} }
public void signalEndOfInputStream() { /**
mediaCodec.signalEndOfInputStream(); * Signals end-of-stream on input to a video encoder.
*
* <p>This method does not need to be called for audio/video decoders or audio encoders. For these
* the {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag should be set on the last input buffer
* {@link #queueInputBuffer(DecoderInputBuffer) queued}.
*
* @throws IllegalStateException If the codec is not an encoder receiving input from a {@link
* Surface}.
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/
public void signalEndOfInputStream() throws TransformationException {
checkState(mediaCodec.getCodecInfo().isEncoder() && inputSurface != null);
try {
mediaCodec.signalEndOfInputStream();
} catch (RuntimeException e) {
throw createTransformationException(e);
}
} }
/** Returns the current output format, if available. */ /**
* Returns the current output format, if available.
*
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/
@Nullable @Nullable
public Format getOutputFormat() { public Format getOutputFormat() throws TransformationException {
// The format is updated when dequeueing a 'special' buffer index, so attempt to dequeue now. // The format is updated when dequeueing a 'special' buffer index, so attempt to dequeue now.
maybeDequeueOutputBuffer(); maybeDequeueOutputBuffer();
return outputFormat; return outputFormat;
} }
/** Returns the current output {@link ByteBuffer}, if available. */ /**
* Returns the current output {@link ByteBuffer}, if available.
*
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/
@Nullable @Nullable
public ByteBuffer getOutputBuffer() { public ByteBuffer getOutputBuffer() throws TransformationException {
return maybeDequeueAndSetOutputBuffer() ? outputBuffer : null; return maybeDequeueAndSetOutputBuffer() ? outputBuffer : null;
} }
/** Returns the {@link BufferInfo} associated with the current output buffer, if available. */ /**
* Returns the {@link BufferInfo} associated with the current output buffer, if available.
*
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/
@Nullable @Nullable
public BufferInfo getOutputBufferInfo() { public BufferInfo getOutputBufferInfo() throws TransformationException {
return maybeDequeueOutputBuffer() ? outputBufferInfo : null; return maybeDequeueOutputBuffer() ? outputBufferInfo : null;
} }
@ -208,8 +277,10 @@ public final class Codec {
* *
* <p>This should be called after the buffer has been processed. The next output buffer will not * <p>This should be called after the buffer has been processed. The next output buffer will not
* be available until the previous has been released. * be available until the previous has been released.
*
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/ */
public void releaseOutputBuffer() { public void releaseOutputBuffer() throws TransformationException {
releaseOutputBuffer(/* render= */ false); releaseOutputBuffer(/* render= */ false);
} }
@ -221,10 +292,17 @@ public final class Codec {
* *
* <p>This should be called after the buffer has been processed. The next output buffer will not * <p>This should be called after the buffer has been processed. The next output buffer will not
* be available until the previous has been released. * be available until the previous has been released.
*
* @param render Whether the buffer needs to be sent to the output {@link Surface}.
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/ */
public void releaseOutputBuffer(boolean render) { public void releaseOutputBuffer(boolean render) throws TransformationException {
outputBuffer = null; outputBuffer = null;
mediaCodec.releaseOutputBuffer(outputBufferIndex, render); try {
mediaCodec.releaseOutputBuffer(outputBufferIndex, render);
} catch (RuntimeException e) {
throw createTransformationException(e);
}
outputBufferIndex = C.INDEX_UNSET; outputBufferIndex = C.INDEX_UNSET;
} }
@ -246,13 +324,18 @@ public final class Codec {
* Tries obtaining an output buffer and sets {@link #outputBuffer} to the obtained output buffer. * Tries obtaining an output buffer and sets {@link #outputBuffer} to the obtained output buffer.
* *
* @return {@code true} if a buffer is successfully obtained, {@code false} otherwise. * @return {@code true} if a buffer is successfully obtained, {@code false} otherwise.
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/ */
private boolean maybeDequeueAndSetOutputBuffer() { private boolean maybeDequeueAndSetOutputBuffer() throws TransformationException {
if (!maybeDequeueOutputBuffer()) { if (!maybeDequeueOutputBuffer()) {
return false; return false;
} }
outputBuffer = checkNotNull(mediaCodec.getOutputBuffer(outputBufferIndex)); try {
outputBuffer = checkNotNull(mediaCodec.getOutputBuffer(outputBufferIndex));
} catch (RuntimeException e) {
throw createTransformationException(e);
}
outputBuffer.position(outputBufferInfo.offset); outputBuffer.position(outputBufferInfo.offset);
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
return true; return true;
@ -261,8 +344,10 @@ public final class Codec {
/** /**
* Returns true if there is already an output buffer pending. Otherwise attempts to dequeue an * Returns true if there is already an output buffer pending. Otherwise attempts to dequeue an
* output buffer and returns whether there is a new output buffer. * output buffer and returns whether there is a new output buffer.
*
* @throws TransformationException If the underlying {@link MediaCodec} encounters a problem.
*/ */
private boolean maybeDequeueOutputBuffer() { private boolean maybeDequeueOutputBuffer() throws TransformationException {
if (outputBufferIndex >= 0) { if (outputBufferIndex >= 0) {
return true; return true;
} }
@ -270,7 +355,11 @@ public final class Codec {
return false; return false;
} }
outputBufferIndex = mediaCodec.dequeueOutputBuffer(outputBufferInfo, /* timeoutUs= */ 0); try {
outputBufferIndex = mediaCodec.dequeueOutputBuffer(outputBufferInfo, /* timeoutUs= */ 0);
} catch (RuntimeException e) {
throw createTransformationException(e);
}
if (outputBufferIndex < 0) { if (outputBufferIndex < 0) {
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
outputFormat = getFormat(mediaCodec.getOutputFormat()); outputFormat = getFormat(mediaCodec.getOutputFormat());
@ -292,6 +381,20 @@ public final class Codec {
return true; return true;
} }
private TransformationException createTransformationException(Exception cause) {
boolean isEncoder = mediaCodec.getCodecInfo().isEncoder();
boolean isVideo = MimeTypes.isVideo(configurationFormat.sampleMimeType);
String componentName = (isVideo ? "Video" : "Audio") + (isEncoder ? "Encoder" : "Decoder");
return TransformationException.createForCodec(
cause,
componentName,
configurationFormat,
mediaCodec.getName(),
isEncoder
? TransformationException.ERROR_CODE_ENCODING_FAILED
: TransformationException.ERROR_CODE_DECODING_FAILED);
}
private static Format getFormat(MediaFormat mediaFormat) { private static Format getFormat(MediaFormat mediaFormat) {
ImmutableList.Builder<byte[]> csdBuffers = new ImmutableList.Builder<>(); ImmutableList.Builder<byte[]> csdBuffers = new ImmutableList.Builder<>();
int csdIndex = 0; int csdIndex = 0;

View File

@ -138,12 +138,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
if (inputSurface != null) { if (inputSurface != null) {
inputSurface.release(); inputSurface.release();
} }
@Nullable String mediaCodecName = null;
if (mediaCodec != null) { if (mediaCodec != null) {
mediaCodecName = mediaCodec.getName();
mediaCodec.release(); mediaCodec.release();
} }
throw createTransformationException(e, format, isVideo, isDecoder); throw createTransformationException(e, format, isVideo, isDecoder, mediaCodecName);
} }
return new Codec(mediaCodec, inputSurface); return new Codec(mediaCodec, format, inputSurface);
} }
private static void configureCodec( private static void configureCodec(
@ -167,13 +169,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
private static TransformationException createTransformationException( private static TransformationException createTransformationException(
Exception cause, Format format, boolean isVideo, boolean isDecoder) { Exception cause,
Format format,
boolean isVideo,
boolean isDecoder,
@Nullable String mediaCodecName) {
String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder"); String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder");
if (cause instanceof IOException || cause instanceof MediaCodec.CodecException) { if (cause instanceof IOException || cause instanceof MediaCodec.CodecException) {
return TransformationException.createForCodec( return TransformationException.createForCodec(
cause, cause,
componentName, componentName,
format, format,
mediaCodecName,
isDecoder isDecoder
? TransformationException.ERROR_CODE_DECODER_INIT_FAILED ? TransformationException.ERROR_CODE_DECODER_INIT_FAILED
: TransformationException.ERROR_CODE_ENCODER_INIT_FAILED); : TransformationException.ERROR_CODE_ENCODER_INIT_FAILED);
@ -183,6 +190,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
cause, cause,
componentName, componentName,
format, format,
mediaCodecName,
isDecoder isDecoder
? TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED ? TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED
: TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED); : TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);

View File

@ -29,7 +29,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
/** Returns a buffer if the pipeline is ready to accept input, and {@code null} otherwise. */ /** Returns a buffer if the pipeline is ready to accept input, and {@code null} otherwise. */
@Nullable @Nullable
DecoderInputBuffer dequeueInputBuffer(); DecoderInputBuffer dequeueInputBuffer() throws TransformationException;
/** /**
* Informs the pipeline that its input buffer contains new input. * Informs the pipeline that its input buffer contains new input.
@ -37,7 +37,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
* <p>Should be called after filling the input buffer from {@link #dequeueInputBuffer()} with new * <p>Should be called after filling the input buffer from {@link #dequeueInputBuffer()} with new
* input. * input.
*/ */
void queueInputBuffer(); void queueInputBuffer() throws TransformationException;
/** /**
* 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
@ -47,18 +47,18 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
/** 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
Format getOutputFormat(); Format getOutputFormat() throws TransformationException;
/** Returns an output buffer if the pipeline has produced output, and {@code null} otherwise */ /** Returns an output buffer if the pipeline has produced output, and {@code null} otherwise */
@Nullable @Nullable
DecoderInputBuffer getOutputBuffer(); DecoderInputBuffer getOutputBuffer() throws TransformationException;
/** /**
* Releases the pipeline's output buffer. * Releases the pipeline's output buffer.
* *
* <p>Should be called when the output buffer from {@link #getOutputBuffer()} is no longer needed. * <p>Should be called when the output buffer from {@link #getOutputBuffer()} is no longer needed.
*/ */
void releaseOutputBuffer(); void releaseOutputBuffer() throws TransformationException;
/** Returns whether the pipeline has ended. */ /** Returns whether the pipeline has ended. */
boolean isEnded(); boolean isEnded();

View File

@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.MediaCodec;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -216,14 +217,25 @@ public final class TransformationException extends Exception {
* *
* @param cause The cause of the failure. * @param cause The cause of the failure.
* @param componentName The name of the component used, e.g. 'VideoEncoder'. * @param componentName The name of the component used, e.g. 'VideoEncoder'.
* @param format The {@link Format} used for the decoder/encoder. * @param configurationFormat The {@link Format} used for configuring the decoder/encoder.
* @param mediaCodecName The name of the {@link MediaCodec} used, if known.
* @param errorCode See {@link #errorCode}. * @param errorCode See {@link #errorCode}.
* @return The created instance. * @return The created instance.
*/ */
public static TransformationException createForCodec( public static TransformationException createForCodec(
Throwable cause, String componentName, Format format, int errorCode) { Throwable cause,
String componentName,
Format configurationFormat,
@Nullable String mediaCodecName,
int errorCode) {
return new TransformationException( return new TransformationException(
componentName + " error, format = " + format, cause, errorCode); componentName
+ " error, format = "
+ configurationFormat
+ ", mediaCodecName="
+ mediaCodecName,
cause,
errorCode);
} }
/** /**

View File

@ -139,7 +139,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
protected abstract boolean ensureConfigured() throws TransformationException; protected abstract boolean ensureConfigured() throws TransformationException;
@RequiresNonNull({"samplePipeline", "#1.data"}) @RequiresNonNull({"samplePipeline", "#1.data"})
protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) { protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer)
throws TransformationException {
samplePipeline.queueInputBuffer(); samplePipeline.queueInputBuffer();
} }
@ -147,9 +148,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* Attempts to write sample pipeline output data to the muxer. * Attempts to write sample pipeline output data to the muxer.
* *
* @return Whether it may be possible to write more data immediately by calling this method again. * @return Whether it may be possible to write more data immediately by calling this method again.
* @throws Muxer.MuxerException If a muxing problem occurs.
* @throws TransformationException If a {@link SamplePipeline} problem occurs.
*/ */
@RequiresNonNull("samplePipeline") @RequiresNonNull("samplePipeline")
private boolean feedMuxerFromPipeline() throws Muxer.MuxerException { private boolean feedMuxerFromPipeline() throws Muxer.MuxerException, TransformationException {
if (!muxerWrapperTrackAdded) { if (!muxerWrapperTrackAdded) {
@Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat(); @Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat();
if (samplePipelineOutputFormat == null) { if (samplePipelineOutputFormat == null) {
@ -185,9 +188,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* Attempts to read input data and pass the input data to the sample pipeline. * Attempts to read input data and pass the input data to the sample pipeline.
* *
* @return Whether it may be possible to read more data immediately by calling this method again. * @return Whether it may be possible to read more data immediately by calling this method again.
* @throws TransformationException If a {@link SamplePipeline} problem occurs.
*/ */
@RequiresNonNull("samplePipeline") @RequiresNonNull("samplePipeline")
private boolean feedPipelineFromInput() { private boolean feedPipelineFromInput() throws TransformationException {
@Nullable DecoderInputBuffer samplePipelineInputBuffer = samplePipeline.dequeueInputBuffer(); @Nullable DecoderInputBuffer samplePipelineInputBuffer = samplePipeline.dequeueInputBuffer();
if (samplePipelineInputBuffer == null) { if (samplePipelineInputBuffer == null) {
return false; return false;

View File

@ -122,10 +122,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Queues the input buffer to the sample pipeline unless it should be dropped because of slow * Queues the input buffer to the sample pipeline unless it should be dropped because of slow
* motion flattening. * motion flattening.
*
* @param inputBuffer The {@link DecoderInputBuffer}.
* @throws TransformationException If a {@link SamplePipeline} problem occurs.
*/ */
@Override @Override
@RequiresNonNull({"samplePipeline", "#1.data"}) @RequiresNonNull({"samplePipeline", "#1.data"})
protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) { protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer)
throws TransformationException {
ByteBuffer data = inputBuffer.data; ByteBuffer data = inputBuffer.data;
boolean shouldDropSample = boolean shouldDropSample =
sefSlowMotionFlattener != null && sefSlowMotionFlattener.dropOrTransformSample(inputBuffer); sefSlowMotionFlattener != null && sefSlowMotionFlattener.dropOrTransformSample(inputBuffer);

View File

@ -131,12 +131,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
@Nullable @Nullable
public DecoderInputBuffer dequeueInputBuffer() { public DecoderInputBuffer dequeueInputBuffer() throws TransformationException {
return decoder.maybeDequeueInputBuffer(decoderInputBuffer) ? decoderInputBuffer : null; return decoder.maybeDequeueInputBuffer(decoderInputBuffer) ? decoderInputBuffer : null;
} }
@Override @Override
public void queueInputBuffer() { public void queueInputBuffer() throws TransformationException {
decoder.queueInputBuffer(decoderInputBuffer); decoder.queueInputBuffer(decoderInputBuffer);
} }
@ -217,7 +217,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
@Nullable @Nullable
public Format getOutputFormat() { public Format getOutputFormat() throws TransformationException {
Format format = encoder.getOutputFormat(); Format format = encoder.getOutputFormat();
return format == null return format == null
? null ? null
@ -226,7 +226,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
@Nullable @Nullable
public DecoderInputBuffer getOutputBuffer() { public DecoderInputBuffer getOutputBuffer() throws TransformationException {
encoderOutputBuffer.data = encoder.getOutputBuffer(); encoderOutputBuffer.data = encoder.getOutputBuffer();
if (encoderOutputBuffer.data == null) { if (encoderOutputBuffer.data == null) {
return null; return null;
@ -238,7 +238,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public void releaseOutputBuffer() { public void releaseOutputBuffer() throws TransformationException {
encoder.releaseOutputBuffer(); encoder.releaseOutputBuffer();
} }