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
public int addTrack(Format format) {
public int addTrack(Format format) throws MuxerException {
String sampleMimeType = checkNotNull(format.sampleMimeType);
MediaFormat mediaFormat;
if (MimeTypes.isAudio(sampleMimeType)) {
@ -137,29 +137,56 @@ import java.nio.ByteBuffer;
} else {
mediaFormat =
MediaFormat.createVideoFormat(castNonNull(sampleMimeType), format.width, format.height);
mediaMuxer.setOrientationHint(format.rotationDegrees);
try {
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);
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.
@Override
public void writeSampleData(
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) {
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs)
throws MuxerException {
if (!isStarted) {
isStarted = true;
mediaMuxer.start();
try {
mediaMuxer.start();
} catch (RuntimeException e) {
throw new MuxerException("Failed to start the muxer", e);
}
}
int offset = data.position();
int size = data.limit() - offset;
int flags = isKeyFrame ? C.BUFFER_FLAG_KEY_FRAME : 0;
bufferInfo.set(offset, size, presentationTimeUs, flags);
mediaMuxer.writeSampleData(trackIndex, data, bufferInfo);
try {
mediaMuxer.writeSampleData(trackIndex, data, bufferInfo);
} catch (RuntimeException e) {
throw new MuxerException(
"Failed to write sample for trackIndex="
+ trackIndex
+ ", presentationTimeUs="
+ presentationTimeUs
+ ", size="
+ size,
e);
}
}
@Override
public void release(boolean forCancellation) {
public void release(boolean forCancellation) throws MuxerException {
if (!isStarted) {
mediaMuxer.release();
return;
@ -168,7 +195,7 @@ import java.nio.ByteBuffer;
isStarted = false;
try {
mediaMuxer.stop();
} catch (IllegalStateException e) {
} catch (RuntimeException e) {
if (SDK_INT < 30) {
// 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
@ -187,7 +214,7 @@ import java.nio.ByteBuffer;
}
// It doesn't matter that stopping the muxer throws if the transformation is being cancelled.
if (!forCancellation) {
throw e;
throw new MuxerException("Failed to stop the muxer", e);
}
} finally {
mediaMuxer.release();

View File

@ -36,6 +36,19 @@ import java.nio.ByteBuffer;
*/
/* 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. */
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
* 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.
@ -93,15 +109,18 @@ import java.nio.ByteBuffer;
* @param data Buffer containing the sample data to write to the container.
* @param isKeyFrame Whether the sample is a key frame.
* @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(
int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs);
void writeSampleData(int trackIndex, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs)
throws MuxerException;
/**
* Releases any resources associated with muxing.
*
* @param forCancellation Whether the reason for releasing the resources is the transformation
* 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.
* @throws IllegalStateException If the format is unsupported or if there is already a track
* 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(trackFormatCount < trackCount, "All track formats have already been added.");
@Nullable String sampleMimeType = format.sampleMimeType;
@ -138,9 +140,11 @@ import java.nio.ByteBuffer;
* good interleaving.
* @throws IllegalStateException If the muxer doesn't have any {@link #endTrack(int) non-ended}
* track of the given track type.
* @throws Muxer.MuxerException If the underlying muxer fails to write the sample.
*/
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);
checkState(
trackIndex != C.INDEX_UNSET,
@ -174,8 +178,10 @@ import java.nio.ByteBuffer;
*
* @param forCancellation Whether the reason for releasing the resources is the transformation
* 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;
muxer.release(forCancellation);
}

View File

@ -73,6 +73,8 @@ public final class TransformationException extends Exception {
ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
ERROR_CODE_GL_INIT_FAILED,
ERROR_CODE_GL_PROCESSING_FAILED,
ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED,
ERROR_CODE_MUXING_FAILED,
})
public @interface ErrorCode {}
@ -164,6 +166,8 @@ public final class TransformationException extends Exception {
* TransformationRequest.Builder#setVideoMimeType(String)} to transcode to a supported MIME type.
*/
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 =
new ImmutableBiMap.Builder<String, @ErrorCode Integer>()
@ -188,6 +192,7 @@ public final class TransformationException extends Exception {
.put(
"ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED",
ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED)
.put("ERROR_CODE_MUXING_FAILED", ERROR_CODE_MUXING_FAILED)
.buildOrThrow();
/** 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.
*/
public void cancel() {
releaseResources(/* forCancellation= */ true);
try {
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
* cancellation.
* @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
* false.
* @throws TransformationException If the muxer is in the wrong state and {@code forCancellation}
* is false.
*/
private void releaseResources(boolean forCancellation) {
private void releaseResources(boolean forCancellation) throws TransformationException {
verifyApplicationThread();
if (player != null) {
player.release();
player = null;
}
if (muxerWrapper != null) {
muxerWrapper.release(forCancellation);
try {
muxerWrapper.release(forCancellation);
} catch (Muxer.MuxerException e) {
throw TransformationException.createForMuxer(
e, TransformationException.ERROR_CODE_MUXING_FAILED);
}
muxerWrapper = null;
}
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
@ -826,9 +835,9 @@ public final class Transformer {
@Nullable TransformationException resourceReleaseException = null;
try {
releaseResources(/* forCancellation= */ false);
} catch (IllegalStateException e) {
// TODO(internal b/209469847): Use a more specific error code when the IllegalStateException
// is caused by the muxer.
} catch (TransformationException e) {
resourceReleaseException = e;
} catch (RuntimeException e) {
resourceReleaseException = TransformationException.createForUnexpected(e);
}

View File

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

View File

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