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
This commit is contained in:
kimvde 2024-04-15 00:51:04 -07:00 committed by Copybara-Service
parent c151d13a1d
commit fa0fb38ca6
5 changed files with 83 additions and 12 deletions

View File

@ -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

View File

@ -88,7 +88,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override @Override
public boolean isReady() { public boolean isReady() {
return isSourceReady(); return true;
} }
@Override @Override

View File

@ -84,6 +84,7 @@ import java.nio.ByteBuffer;
private int videoTrackIndex; private int videoTrackIndex;
private boolean isStarted; private boolean isStarted;
private boolean isReleased;
private FrameworkMuxer(MediaMuxer mediaMuxer, long videoDurationMs) { private FrameworkMuxer(MediaMuxer mediaMuxer, long videoDurationMs) {
this.mediaMuxer = mediaMuxer; this.mediaMuxer = mediaMuxer;
@ -140,15 +141,10 @@ import java.nio.ByteBuffer;
} }
if (!isStarted) { if (!isStarted) {
isStarted = true;
if (Util.SDK_INT < 30 && presentationTimeUs < 0) { if (Util.SDK_INT < 30 && presentationTimeUs < 0) {
trackIndexToPresentationTimeOffsetUs.put(trackIndex, -presentationTimeUs); trackIndexToPresentationTimeOffsetUs.put(trackIndex, -presentationTimeUs);
} }
try { startMuxer();
mediaMuxer.start();
} catch (RuntimeException e) {
throw new MuxerException("Failed to start the muxer", e);
}
} }
int offset = data.position(); int offset = data.position();
@ -204,11 +200,16 @@ import java.nio.ByteBuffer;
@Override @Override
public void release(boolean forCancellation) throws MuxerException { public void release(boolean forCancellation) throws MuxerException {
if (!isStarted) { if (isReleased) {
mediaMuxer.release();
return; 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) { if (videoDurationUs != C.TIME_UNSET && videoTrackIndex != C.INDEX_UNSET) {
writeSampleData( writeSampleData(
videoTrackIndex, videoTrackIndex,
@ -227,9 +228,19 @@ import java.nio.ByteBuffer;
} }
} finally { } finally {
mediaMuxer.release(); 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 // Accesses MediaMuxer state via reflection to ensure that muxer resources can be released even
// if stopping fails. // if stopping fails.
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")

View File

@ -561,13 +561,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
/** /**
* Notifies the muxer that all the samples have been {@linkplain #writeSample(int, ByteBuffer, * Attempts to notify the muxer that all the samples have been {@linkplain #writeSample(int,
* boolean, long) written} for a given track. * ByteBuffer, boolean, long) written} for a given track.
* *
* @param trackType The {@link C.TrackType}. * @param trackType The {@link C.TrackType}.
*/ */
public void endTrack(@C.TrackType int trackType) { public void endTrack(@C.TrackType int trackType) {
if (!contains(trackTypeToInfo, trackType)) { if (!isReady || !contains(trackTypeToInfo, trackType)) {
return; return;
} }

View File

@ -160,6 +160,32 @@ public final class MediaItemExportTest {
/* modifications...= */ "clipped")); /* 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 @Test
public void start_trimOptimizationEnabled_clippingConfigurationUnset_outputMatchesOriginal() public void start_trimOptimizationEnabled_clippingConfigurationUnset_outputMatchesOriginal()
throws Exception { throws Exception {