From 6070b200ae412ee86a02359939cb8df8a255cf7c Mon Sep 17 00:00:00 2001 From: hschlueter Date: Tue, 11 Jan 2022 16:03:40 +0000 Subject: [PATCH] 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 --- .../media3/transformer/FrameworkMuxer.java | 45 +++++++++++++++---- .../androidx/media3/transformer/Muxer.java | 27 +++++++++-- .../media3/transformer/MuxerWrapper.java | 12 +++-- .../transformer/TransformationException.java | 5 +++ .../media3/transformer/Transformer.java | 25 +++++++---- .../transformer/TransformerBaseRenderer.java | 6 ++- .../media3/transformer/TestMuxer.java | 7 +-- 7 files changed, 99 insertions(+), 28 deletions(-) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java index 57ca4989ab..b3aed9b064 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java @@ -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(); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java index e831bd0727..a6bfa5db7d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java @@ -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; } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java index d9899e40a9..5f62af713c 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java @@ -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); } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java index cf57953bc3..d980b5a8da 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationException.java @@ -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 NAME_TO_ERROR_CODE = new ImmutableBiMap.Builder() @@ -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. */ diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java index 6e7ee9a9fc..917a8b07b4 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -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); } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java index 945f392244..b72b9fc641 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java @@ -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) { diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java index a346d4bba0..db28cf8f3c 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TestMuxer.java @@ -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); }