diff --git a/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4/clipped_to_empty.dump b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4/clipped_to_empty.dump new file mode 100644 index 0000000000..a299a17038 --- /dev/null +++ b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4/clipped_to_empty.dump @@ -0,0 +1,34 @@ +format audio: + averageBitrate = 192181 + peakBitrate = 192181 + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 643 + channelCount = 2 + sampleRate = 48000 + language = en + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000] + initializationData: + data = length 2, hash 560 +format video: + id = 1 + sampleMimeType = video/avc + codecs = avc1.42C015 + maxInputSize = 14839 + width = 320 + height = 240 + frameRate = 59.997425 + colorInfo: + colorSpace = 2 + colorRange = 1 + colorTransfer = 3 + lumaBitdepth = 8 + chromaBitdepth = 8 + metadata = entries=[TSSE: description=null: values=[Lavf58.76.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000] + initializationData: + data = length 31, hash 4B108214 + data = length 9, hash FBA158BB +container metadata = entries=[TSSE: description=null: values=[Lavf58.76.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000] +container metadata = entries=[TSSE: description=null: values=[Lavf58.76.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000] +released = true diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java index d67842e23f..9d9d99c692 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java @@ -88,7 +88,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @Override public boolean isReady() { - return isSourceReady(); + return true; } @Override 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 cb5393525e..8012120ea0 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java @@ -84,6 +84,7 @@ import java.nio.ByteBuffer; private int videoTrackIndex; private boolean isStarted; + private boolean isReleased; private FrameworkMuxer(MediaMuxer mediaMuxer, long videoDurationMs) { this.mediaMuxer = mediaMuxer; @@ -140,15 +141,10 @@ import java.nio.ByteBuffer; } if (!isStarted) { - isStarted = true; if (Util.SDK_INT < 30 && presentationTimeUs < 0) { trackIndexToPresentationTimeOffsetUs.put(trackIndex, -presentationTimeUs); } - try { - mediaMuxer.start(); - } catch (RuntimeException e) { - throw new MuxerException("Failed to start the muxer", e); - } + startMuxer(); } int offset = data.position(); @@ -204,11 +200,16 @@ import java.nio.ByteBuffer; @Override public void release(boolean forCancellation) throws MuxerException { - if (!isStarted) { - mediaMuxer.release(); + if (isReleased) { return; } + if (!isStarted) { + // Start the muxer even if no samples have been written so that it throws instead of silently + // writing nothing to the output file. + startMuxer(); + } + if (videoDurationUs != C.TIME_UNSET && videoTrackIndex != C.INDEX_UNSET) { writeSampleData( videoTrackIndex, @@ -227,9 +228,19 @@ import java.nio.ByteBuffer; } } finally { mediaMuxer.release(); + isReleased = true; } } + private void startMuxer() throws MuxerException { + try { + mediaMuxer.start(); + } catch (RuntimeException e) { + throw new MuxerException("Failed to start the muxer", e); + } + isStarted = true; + } + // Accesses MediaMuxer state via reflection to ensure that muxer resources can be released even // if stopping fails. @SuppressLint("PrivateApi") 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 ed31a14f79..41390f6959 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java @@ -561,13 +561,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } /** - * Notifies the muxer that all the samples have been {@linkplain #writeSample(int, ByteBuffer, - * boolean, long) written} for a given track. + * Attempts to notify the muxer that all the samples have been {@linkplain #writeSample(int, + * ByteBuffer, boolean, long) written} for a given track. * * @param trackType The {@link C.TrackType}. */ public void endTrack(@C.TrackType int trackType) { - if (!contains(trackTypeToInfo, trackType)) { + if (!isReady || !contains(trackTypeToInfo, trackType)) { return; } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java index c28f825f26..081b431ebb 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java @@ -160,6 +160,32 @@ public final class MediaItemExportTest { /* modifications...= */ "clipped")); } + @Test + public void start_withClippingStartAndEndEqual_completesSuccessfully() throws Exception { + CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false); + Transformer transformer = + createTransformerBuilder(muxerFactory, /* enableFallback= */ false).build(); + MediaItem mediaItem = + new MediaItem.Builder() + .setUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S) + .setClippingConfiguration( + new MediaItem.ClippingConfiguration.Builder() + .setStartPositionMs(0) + .setEndPositionMs(0) + .build()) + .build(); + + transformer.start(mediaItem, outputDir.newFile().getPath()); + TransformerTestRunner.runLooper(transformer); + + DumpFileAsserts.assertOutput( + context, + muxerFactory.getCreatedMuxer(), + getDumpFileName( + /* originalFileName= */ FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S, + /* modifications...= */ "clipped_to_empty")); + } + @Test public void start_trimOptimizationEnabled_clippingConfigurationUnset_outputMatchesOriginal() throws Exception {