From fa0fb38ca635f4eb576cbaab8671e3fd91fff391 Mon Sep 17 00:00:00 2001 From: kimvde Date: Mon, 15 Apr 2024 00:51:04 -0700 Subject: [PATCH] Handle clip start position equal to end position in Transformer After this CL, Transformer will throw if the clipping start and end positions are the same because MediaMuxer doesn't support writing a file with no samples. This should work once we default to the in-app muxer. Issue: androidx/media#1242 PiperOrigin-RevId: 624861950 --- .../clipped_to_empty.dump | 34 +++++++++++++++++++ .../ExoAssetLoaderBaseRenderer.java | 2 +- .../media3/transformer/FrameworkMuxer.java | 27 ++++++++++----- .../media3/transformer/MuxerWrapper.java | 6 ++-- .../transformer/MediaItemExportTest.java | 26 ++++++++++++++ 5 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 libraries/test_data/src/test/assets/transformerdumps/mp4/sample_with_increasing_timestamps_320w_240h.mp4/clipped_to_empty.dump 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 {