Add error code and exception type for muxing failures.

Exceptions thrown by MediaMuxer are converted MuxerExceptions
and later to TransformationExceptions with ERROR_CODE_MUXING_FAILED.

PiperOrigin-RevId: 421033721
This commit is contained in:
hschlueter 2022-01-11 16:03:40 +00:00 committed by Ian Baker
parent b208d6d26e
commit 6070b200ae
7 changed files with 99 additions and 28 deletions

View File

@ -127,7 +127,7 @@ import java.nio.ByteBuffer;
} }
@Override @Override
public int addTrack(Format format) { public int addTrack(Format format) throws MuxerException {
String sampleMimeType = checkNotNull(format.sampleMimeType); String sampleMimeType = checkNotNull(format.sampleMimeType);
MediaFormat mediaFormat; MediaFormat mediaFormat;
if (MimeTypes.isAudio(sampleMimeType)) { if (MimeTypes.isAudio(sampleMimeType)) {
@ -137,29 +137,56 @@ import java.nio.ByteBuffer;
} else { } else {
mediaFormat = mediaFormat =
MediaFormat.createVideoFormat(castNonNull(sampleMimeType), format.width, format.height); MediaFormat.createVideoFormat(castNonNull(sampleMimeType), format.width, format.height);
try {
mediaMuxer.setOrientationHint(format.rotationDegrees); mediaMuxer.setOrientationHint(format.rotationDegrees);
} catch (RuntimeException e) {
throw new MuxerException(
"Failed to set orientation hint with rotationDegrees=" + format.rotationDegrees, e);
}
} }
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
return mediaMuxer.addTrack(mediaFormat); int trackIndex;
try {
trackIndex = mediaMuxer.addTrack(mediaFormat);
} catch (RuntimeException e) {
throw new MuxerException("Failed to add track with format=" + format, e);
}
return trackIndex;
} }
@SuppressLint("WrongConstant") // C.BUFFER_FLAG_KEY_FRAME equals MediaCodec.BUFFER_FLAG_KEY_FRAME. @SuppressLint("WrongConstant") // C.BUFFER_FLAG_KEY_FRAME equals MediaCodec.BUFFER_FLAG_KEY_FRAME.
@Override @Override
public void writeSampleData( public void writeSampleData(
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) { int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs)
throws MuxerException {
if (!isStarted) { if (!isStarted) {
isStarted = true; isStarted = true;
try {
mediaMuxer.start(); mediaMuxer.start();
} catch (RuntimeException e) {
throw new MuxerException("Failed to start the muxer", e);
}
} }
int offset = data.position(); int offset = data.position();
int size = data.limit() - offset; int size = data.limit() - offset;
int flags = isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0; int flags = isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0;
bufferInfo.set(offset, size, presentationTimeUs, flags); bufferInfo.set(offset, size, presentationTimeUs, flags);
try {
mediaMuxer.writeSampleData(trackIndex, data, bufferInfo); mediaMuxer.writeSampleData(trackIndex, data, bufferInfo);
} catch (RuntimeException e) {
throw new MuxerException(
"Failed to write sample for trackIndex="
+ trackIndex
+ ", presentationTimeUs="
+ presentationTimeUs
+ ", size="
+ size,
e);
}
} }
@Override @Override
public void release(boolean forCancellation) { public void release(boolean forCancellation) throws MuxerException {
if (!isStarted) { if (!isStarted) {
mediaMuxer.release(); mediaMuxer.release();
return; return;
@ -168,7 +195,7 @@ import java.nio.ByteBuffer;
isStarted = false; isStarted = false;
try { try {
mediaMuxer.stop(); mediaMuxer.stop();
} catch (IllegalStateException e) { } catch (RuntimeException e) {
if (SDK_INT < 30) { if (SDK_INT < 30) {
// Set the muxer state to stopped even if mediaMuxer.stop() failed so that // Set the muxer state to stopped even if mediaMuxer.stop() failed so that
// mediaMuxer.release() doesn't attempt to stop the muxer and therefore doesn't throw the // mediaMuxer.release() doesn't attempt to stop the muxer and therefore doesn't throw the
@ -187,7 +214,7 @@ import java.nio.ByteBuffer;
} }
// It doesn't matter that stopping the muxer throws if the transformation is being cancelled. // It doesn't matter that stopping the muxer throws if the transformation is being cancelled.
if (!forCancellation) { if (!forCancellation) {
throw e; throw new MuxerException("Failed to stop the muxer", e);
} }
} finally { } finally {
mediaMuxer.release(); mediaMuxer.release();

View File

@ -36,6 +36,19 @@ import java.nio.ByteBuffer;
*/ */
/* package */ interface Muxer { /* package */ interface Muxer {
/** Thrown when a muxing failure occurs. */
/* package */ final class MuxerException extends Exception {
/**
* Creates an instance.
*
* @param message See {@link #getMessage()}.
* @param cause See {@link #getCause()}.
*/
public MuxerException(String message, Throwable cause) {
super(message, cause);
}
}
/** Factory for muxers. */ /** Factory for muxers. */
interface Factory { interface Factory {
/** /**
@ -83,8 +96,11 @@ import java.nio.ByteBuffer;
/** /**
* Adds a track with the specified format, and returns its index (to be passed in subsequent calls * Adds a track with the specified format, and returns its index (to be passed in subsequent calls
* to {@link #writeSampleData(int, ByteBuffer, boolean, long)}). * to {@link #writeSampleData(int, ByteBuffer, boolean, long)}).
*
* @param format The {@link Format} of the track.
* @throws MuxerException If the muxer encounters a problem while adding the track.
*/ */
int addTrack(Format format); int addTrack(Format format) throws MuxerException;
/** /**
* Writes the specified sample. * Writes the specified sample.
@ -93,15 +109,18 @@ import java.nio.ByteBuffer;
* @param data Buffer containing the sample data to write to the container. * @param data Buffer containing the sample data to write to the container.
* @param isKeyFrame Whether the sample is a key frame. * @param isKeyFrame Whether the sample is a key frame.
* @param presentationTimeUs The presentation time of the sample in microseconds. * @param presentationTimeUs The presentation time of the sample in microseconds.
* @throws MuxerException If the muxer fails to start or an error occurs while writing the sample.
*/ */
void writeSampleData( void writeSampleData(int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs)
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs); throws MuxerException;
/** /**
* Releases any resources associated with muxing. * Releases any resources associated with muxing.
* *
* @param forCancellation Whether the reason for releasing the resources is the transformation * @param forCancellation Whether the reason for releasing the resources is the transformation
* cancellation. * cancellation.
* @throws MuxerException If the muxer fails to stop or release resources and {@code
* forCancellation} is false.
*/ */
void release(boolean forCancellation); void release(boolean forCancellation) throws MuxerException;
} }

View File

@ -103,8 +103,10 @@ import java.nio.ByteBuffer;
* @param format The {@link Format} to be added. * @param format The {@link Format} to be added.
* @throws IllegalStateException If the format is unsupported or if there is already a track * @throws IllegalStateException If the format is unsupported or if there is already a track
* format of the same type (audio or video). * format of the same type (audio or video).
* @throws Muxer.MuxerException If the underlying muxer encounters a problem while adding the
* track.
*/ */
public void addTrackFormat(Format format) { public void addTrackFormat(Format format) throws Muxer.MuxerException {
checkState(trackCount > 0, "All tracks should be registered before the formats are added."); checkState(trackCount > 0, "All tracks should be registered before the formats are added.");
checkState(trackFormatCount < trackCount, "All track formats have already been added."); checkState(trackFormatCount < trackCount, "All track formats have already been added.");
@Nullable String sampleMimeType = format.sampleMimeType; @Nullable String sampleMimeType = format.sampleMimeType;
@ -138,9 +140,11 @@ import java.nio.ByteBuffer;
* good interleaving. * good interleaving.
* @throws IllegalStateException If the muxer doesn't have any {@link #endTrack(int) non-ended} * @throws IllegalStateException If the muxer doesn't have any {@link #endTrack(int) non-ended}
* track of the given track type. * track of the given track type.
* @throws Muxer.MuxerException If the underlying muxer fails to write the sample.
*/ */
public boolean writeSample( public boolean writeSample(
@C.TrackType int trackType, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) { @C.TrackType int trackType, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs)
throws Muxer.MuxerException {
int trackIndex = trackTypeToIndex.get(trackType, /* valueIfKeyNotFound= */ C.INDEX_UNSET); int trackIndex = trackTypeToIndex.get(trackType, /* valueIfKeyNotFound= */ C.INDEX_UNSET);
checkState( checkState(
trackIndex != C.INDEX_UNSET, trackIndex != C.INDEX_UNSET,
@ -174,8 +178,10 @@ import java.nio.ByteBuffer;
* *
* @param forCancellation Whether the reason for releasing the resources is the transformation * @param forCancellation Whether the reason for releasing the resources is the transformation
* cancellation. * cancellation.
* @throws Muxer.MuxerException If the underlying muxer fails to stop and to release resources and
* {@code forCancellation} is false.
*/ */
public void release(boolean forCancellation) { public void release(boolean forCancellation) throws Muxer.MuxerException {
isReady = false; isReady = false;
muxer.release(forCancellation); muxer.release(forCancellation);
} }

View File

@ -73,6 +73,8 @@ 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_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED,
ERROR_CODE_MUXING_FAILED,
}) })
public @interface ErrorCode {} public @interface ErrorCode {}
@ -164,6 +166,8 @@ public final class TransformationException extends Exception {
* TransformationRequest.Builder#setVideoMimeType(String)} to transcode to a supported MIME type. * TransformationRequest.Builder#setVideoMimeType(String)} to transcode to a supported MIME type.
*/ */
public static final int ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED = 6001; public static final int ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED = 6001;
/** Caused by a failure while muxing media samples. */
public static final int ERROR_CODE_MUXING_FAILED = 6002;
private static final ImmutableBiMap<String, @ErrorCode Integer> NAME_TO_ERROR_CODE = private static final ImmutableBiMap<String, @ErrorCode Integer> NAME_TO_ERROR_CODE =
new ImmutableBiMap.Builder<String, @ErrorCode Integer>() new ImmutableBiMap.Builder<String, @ErrorCode Integer>()
@ -188,6 +192,7 @@ public final class TransformationException extends Exception {
.put( .put(
"ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED", "ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED",
ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED) ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED)
.put("ERROR_CODE_MUXING_FAILED", ERROR_CODE_MUXING_FAILED)
.buildOrThrow(); .buildOrThrow();
/** Returns the {@code errorCode} for a given name. */ /** Returns the {@code errorCode} for a given name. */

View File

@ -672,7 +672,11 @@ public final class Transformer {
* @throws IllegalStateException If this method is called from the wrong thread. * @throws IllegalStateException If this method is called from the wrong thread.
*/ */
public void cancel() { public void cancel() {
try {
releaseResources(/* forCancellation= */ true); releaseResources(/* forCancellation= */ true);
} catch (TransformationException impossible) {
throw new IllegalStateException(impossible);
}
} }
/** /**
@ -681,17 +685,22 @@ public final class Transformer {
* @param forCancellation Whether the reason for releasing the resources is the transformation * @param forCancellation Whether the reason for releasing the resources is the transformation
* cancellation. * cancellation.
* @throws IllegalStateException If this method is called from the wrong thread. * @throws IllegalStateException If this method is called from the wrong thread.
* @throws IllegalStateException If the muxer is in the wrong state and {@code forCancellation} is * @throws TransformationException If the muxer is in the wrong state and {@code forCancellation}
* false. * is false.
*/ */
private void releaseResources(boolean forCancellation) { private void releaseResources(boolean forCancellation) throws TransformationException {
verifyApplicationThread(); verifyApplicationThread();
if (player != null) { if (player != null) {
player.release(); player.release();
player = null; player = null;
} }
if (muxerWrapper != null) { if (muxerWrapper != null) {
try {
muxerWrapper.release(forCancellation); muxerWrapper.release(forCancellation);
} catch (Muxer.MuxerException e) {
throw TransformationException.createForMuxer(
e, TransformationException.ERROR_CODE_MUXING_FAILED);
}
muxerWrapper = null; muxerWrapper = null;
} }
progressState = PROGRESS_STATE_NO_TRANSFORMATION; progressState = PROGRESS_STATE_NO_TRANSFORMATION;
@ -826,9 +835,9 @@ public final class Transformer {
@Nullable TransformationException resourceReleaseException = null; @Nullable TransformationException resourceReleaseException = null;
try { try {
releaseResources(/* forCancellation= */ false); releaseResources(/* forCancellation= */ false);
} catch (IllegalStateException e) { } catch (TransformationException e) {
// TODO(internal b/209469847): Use a more specific error code when the IllegalStateException resourceReleaseException = e;
// is caused by the muxer. } catch (RuntimeException e) {
resourceReleaseException = TransformationException.createForUnexpected(e); resourceReleaseException = TransformationException.createForUnexpected(e);
} }

View File

@ -97,6 +97,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {} while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
} catch (TransformationException e) { } catch (TransformationException e) {
throw wrapTransformationException(e); throw wrapTransformationException(e);
} catch (Muxer.MuxerException e) {
throw wrapTransformationException(
TransformationException.createForMuxer(
e, TransformationException.ERROR_CODE_MUXING_FAILED));
} }
} }
@ -145,7 +149,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @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.
*/ */
@RequiresNonNull("samplePipeline") @RequiresNonNull("samplePipeline")
private boolean feedMuxerFromPipeline() { private boolean feedMuxerFromPipeline() throws Muxer.MuxerException {
if (!muxerWrapperTrackAdded) { if (!muxerWrapperTrackAdded) {
@Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat(); @Nullable Format samplePipelineOutputFormat = samplePipeline.getOutputFormat();
if (samplePipelineOutputFormat == null) { if (samplePipelineOutputFormat == null) {

View File

@ -45,7 +45,7 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable {
// Muxer implementation. // Muxer implementation.
@Override @Override
public int addTrack(Format format) { public int addTrack(Format format) throws MuxerException {
int trackIndex = muxer.addTrack(format); int trackIndex = muxer.addTrack(format);
dumpables.add(new DumpableFormat(format, trackIndex)); dumpables.add(new DumpableFormat(format, trackIndex));
return trackIndex; return trackIndex;
@ -53,13 +53,14 @@ public final class TestMuxer implements Muxer, Dumper.Dumpable {
@Override @Override
public void writeSampleData( public void writeSampleData(
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) { int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs)
throws MuxerException {
dumpables.add(new DumpableSample(trackIndex, data, isKeyFrame, presentationTimeUs)); dumpables.add(new DumpableSample(trackIndex, data, isKeyFrame, presentationTimeUs));
muxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs); muxer.writeSampleData(trackIndex, data, isKeyFrame, presentationTimeUs);
} }
@Override @Override
public void release(boolean forCancellation) { public void release(boolean forCancellation) throws MuxerException {
dumpables.add(dumper -> dumper.add("released", true)); dumpables.add(dumper -> dumper.add("released", true));
muxer.release(forCancellation); muxer.release(forCancellation);
} }