mirror of
https://github.com/androidx/media.git
synced 2025-05-05 06:30:24 +08:00
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:
parent
05924eaa52
commit
80851807f2
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user