Transformer: Add support for transmuxing audio in trim optimization

PiperOrigin-RevId: 588711597
This commit is contained in:
tofunmi 2023-12-07 02:15:33 -08:00 committed by Copybara-Service
parent faeff17a4c
commit cd346ca14d
10 changed files with 4621 additions and 708 deletions

View File

@ -1,560 +0,0 @@
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]
sample:
trackType = video
dataHashCode = 983000500
size = 13539
isKeyFrame = true
presentationTimeUs = 0
sample:
trackType = video
dataHashCode = -1834230781
size = 32
isKeyFrame = false
presentationTimeUs = 16666
sample:
trackType = video
dataHashCode = 521720738
size = 1534
isKeyFrame = false
presentationTimeUs = 33333
sample:
trackType = video
dataHashCode = 722836039
size = 123
isKeyFrame = false
presentationTimeUs = 50000
sample:
trackType = video
dataHashCode = -1702585381
size = 2061
isKeyFrame = false
presentationTimeUs = 66666
sample:
trackType = video
dataHashCode = -365856396
size = 147
isKeyFrame = false
presentationTimeUs = 83333
sample:
trackType = video
dataHashCode = 1258185334
size = 2534
isKeyFrame = false
presentationTimeUs = 100000
sample:
trackType = video
dataHashCode = -179623006
size = 87
isKeyFrame = false
presentationTimeUs = 116666
sample:
trackType = video
dataHashCode = -541393824
size = 2762
isKeyFrame = false
presentationTimeUs = 133333
sample:
trackType = video
dataHashCode = -1912932514
size = 57
isKeyFrame = false
presentationTimeUs = 150000
sample:
trackType = video
dataHashCode = 485634444
size = 2833
isKeyFrame = false
presentationTimeUs = 166666
sample:
trackType = video
dataHashCode = 570625802
size = 189
isKeyFrame = false
presentationTimeUs = 183333
sample:
trackType = video
dataHashCode = 1819668957
size = 3153
isKeyFrame = false
presentationTimeUs = 200000
sample:
trackType = video
dataHashCode = 1004398066
size = 104
isKeyFrame = false
presentationTimeUs = 216666
sample:
trackType = video
dataHashCode = 2087741113
size = 2304
isKeyFrame = false
presentationTimeUs = 233333
sample:
trackType = video
dataHashCode = -419782502
size = 222
isKeyFrame = false
presentationTimeUs = 250000
sample:
trackType = video
dataHashCode = -1867110345
size = 2306
isKeyFrame = false
presentationTimeUs = 266666
sample:
trackType = video
dataHashCode = 1908323737
size = 257
isKeyFrame = false
presentationTimeUs = 283333
sample:
trackType = video
dataHashCode = 884063337
size = 2201
isKeyFrame = false
presentationTimeUs = 300000
sample:
trackType = video
dataHashCode = -1308458590
size = 174
isKeyFrame = false
presentationTimeUs = 316666
sample:
trackType = video
dataHashCode = -1686938678
size = 2524
isKeyFrame = false
presentationTimeUs = 333333
sample:
trackType = video
dataHashCode = -1372845971
size = 171
isKeyFrame = false
presentationTimeUs = 350000
sample:
trackType = video
dataHashCode = 1130876644
size = 2306
isKeyFrame = false
presentationTimeUs = 366666
sample:
trackType = video
dataHashCode = 1707671352
size = 188
isKeyFrame = false
presentationTimeUs = 383333
sample:
trackType = video
dataHashCode = 300233313
size = 2529
isKeyFrame = false
presentationTimeUs = 400000
sample:
trackType = video
dataHashCode = -1284013406
size = 182
isKeyFrame = false
presentationTimeUs = 416666
sample:
trackType = video
dataHashCode = -2088617828
size = 2047
isKeyFrame = false
presentationTimeUs = 433333
sample:
trackType = video
dataHashCode = 2116374999
size = 259
isKeyFrame = false
presentationTimeUs = 450000
sample:
trackType = video
dataHashCode = -2123019940
size = 2234
isKeyFrame = false
presentationTimeUs = 466666
sample:
trackType = video
dataHashCode = 1901454757
size = 138
isKeyFrame = false
presentationTimeUs = 483333
sample:
trackType = video
dataHashCode = 1576638059
size = 2088
isKeyFrame = false
presentationTimeUs = 500000
sample:
trackType = video
dataHashCode = 1120133924
size = 151
isKeyFrame = false
presentationTimeUs = 516666
sample:
trackType = video
dataHashCode = 264118578
size = 2235
isKeyFrame = false
presentationTimeUs = 533333
sample:
trackType = video
dataHashCode = 64254117
size = 164
isKeyFrame = false
presentationTimeUs = 550000
sample:
trackType = video
dataHashCode = -1000078879
size = 2231
isKeyFrame = false
presentationTimeUs = 566666
sample:
trackType = video
dataHashCode = 286919946
size = 123
isKeyFrame = false
presentationTimeUs = 583333
sample:
trackType = video
dataHashCode = -320312658
size = 2303
isKeyFrame = false
presentationTimeUs = 600000
sample:
trackType = video
dataHashCode = 1057750590
size = 175
isKeyFrame = false
presentationTimeUs = 616666
sample:
trackType = video
dataHashCode = 1961415074
size = 2165
isKeyFrame = false
presentationTimeUs = 633333
sample:
trackType = video
dataHashCode = 667267023
size = 260
isKeyFrame = false
presentationTimeUs = 650000
sample:
trackType = video
dataHashCode = 979033489
size = 1924
isKeyFrame = false
presentationTimeUs = 666666
sample:
trackType = video
dataHashCode = -1974473017
size = 286
isKeyFrame = false
presentationTimeUs = 683333
sample:
trackType = video
dataHashCode = -962519103
size = 1992
isKeyFrame = false
presentationTimeUs = 700000
sample:
trackType = video
dataHashCode = -1312094075
size = 204
isKeyFrame = false
presentationTimeUs = 716666
sample:
trackType = video
dataHashCode = 2068151127
size = 1826
isKeyFrame = false
presentationTimeUs = 733333
sample:
trackType = video
dataHashCode = -1531967506
size = 284
isKeyFrame = false
presentationTimeUs = 750000
sample:
trackType = video
dataHashCode = -778066699
size = 1940
isKeyFrame = false
presentationTimeUs = 766666
sample:
trackType = video
dataHashCode = -1219952117
size = 129
isKeyFrame = false
presentationTimeUs = 783333
sample:
trackType = video
dataHashCode = -1218204223
size = 1947
isKeyFrame = false
presentationTimeUs = 800000
sample:
trackType = video
dataHashCode = -1816247511
size = 147
isKeyFrame = false
presentationTimeUs = 816666
sample:
trackType = video
dataHashCode = 299686318
size = 2066
isKeyFrame = false
presentationTimeUs = 833333
sample:
trackType = video
dataHashCode = -1520242765
size = 185
isKeyFrame = false
presentationTimeUs = 850000
sample:
trackType = video
dataHashCode = -1702498409
size = 2159
isKeyFrame = false
presentationTimeUs = 866666
sample:
trackType = video
dataHashCode = 345202950
size = 189
isKeyFrame = false
presentationTimeUs = 883333
sample:
trackType = video
dataHashCode = 220746796
size = 2098
isKeyFrame = false
presentationTimeUs = 900000
sample:
trackType = video
dataHashCode = -32341189
size = 159
isKeyFrame = false
presentationTimeUs = 916666
sample:
trackType = video
dataHashCode = -1838476361
size = 1914
isKeyFrame = false
presentationTimeUs = 933333
sample:
trackType = video
dataHashCode = -1322093590
size = 99
isKeyFrame = false
presentationTimeUs = 950000
sample:
trackType = video
dataHashCode = -1391064751
size = 2168
isKeyFrame = false
presentationTimeUs = 966666
sample:
trackType = video
dataHashCode = 1479204931
size = 129
isKeyFrame = false
presentationTimeUs = 983333
sample:
trackType = video
dataHashCode = 1131230500
size = 2327
isKeyFrame = false
presentationTimeUs = 1000000
sample:
trackType = video
dataHashCode = -393815961
size = 160
isKeyFrame = false
presentationTimeUs = 1016666
sample:
trackType = video
dataHashCode = -242739025
size = 2136
isKeyFrame = false
presentationTimeUs = 1033333
sample:
trackType = video
dataHashCode = 65238903
size = 163
isKeyFrame = false
presentationTimeUs = 1050000
sample:
trackType = video
dataHashCode = 1720840922
size = 2043
isKeyFrame = false
presentationTimeUs = 1066666
sample:
trackType = video
dataHashCode = -1006231050
size = 178
isKeyFrame = false
presentationTimeUs = 1083333
sample:
trackType = video
dataHashCode = 1742965952
size = 2022
isKeyFrame = false
presentationTimeUs = 1100000
sample:
trackType = video
dataHashCode = -971065365
size = 240
isKeyFrame = false
presentationTimeUs = 1116666
sample:
trackType = video
dataHashCode = 1757434551
size = 1887
isKeyFrame = false
presentationTimeUs = 1133333
sample:
trackType = video
dataHashCode = 1501849116
size = 252
isKeyFrame = false
presentationTimeUs = 1150000
sample:
trackType = video
dataHashCode = 825501977
size = 1816
isKeyFrame = false
presentationTimeUs = 1166666
sample:
trackType = video
dataHashCode = -1616223509
size = 246
isKeyFrame = false
presentationTimeUs = 1183333
sample:
trackType = video
dataHashCode = 457119646
size = 1817
isKeyFrame = false
presentationTimeUs = 1200000
sample:
trackType = video
dataHashCode = -1382929639
size = 146
isKeyFrame = false
presentationTimeUs = 1216666
sample:
trackType = video
dataHashCode = -1580853131
size = 1929
isKeyFrame = false
presentationTimeUs = 1233333
sample:
trackType = video
dataHashCode = 1758706551
size = 196
isKeyFrame = false
presentationTimeUs = 1250000
sample:
trackType = video
dataHashCode = 207289556
size = 2154
isKeyFrame = false
presentationTimeUs = 1266666
sample:
trackType = video
dataHashCode = -981284942
size = 182
isKeyFrame = false
presentationTimeUs = 1283333
sample:
trackType = video
dataHashCode = 855103964
size = 2144
isKeyFrame = false
presentationTimeUs = 1300000
sample:
trackType = video
dataHashCode = 380479426
size = 90
isKeyFrame = false
presentationTimeUs = 1316666
sample:
trackType = video
dataHashCode = -1677996152
size = 2005
isKeyFrame = false
presentationTimeUs = 1333333
sample:
trackType = video
dataHashCode = 1516852008
size = 156
isKeyFrame = false
presentationTimeUs = 1350000
sample:
trackType = video
dataHashCode = -1602805193
size = 1772
isKeyFrame = false
presentationTimeUs = 1366666
sample:
trackType = video
dataHashCode = -1720426556
size = 162
isKeyFrame = false
presentationTimeUs = 1383333
sample:
trackType = video
dataHashCode = -1392260423
size = 1865
isKeyFrame = false
presentationTimeUs = 1400000
sample:
trackType = video
dataHashCode = -1842432151
size = 151
isKeyFrame = false
presentationTimeUs = 1416666
sample:
trackType = video
dataHashCode = -537063215
size = 1848
isKeyFrame = false
presentationTimeUs = 1433333
sample:
trackType = video
dataHashCode = 2089388394
size = 206
isKeyFrame = false
presentationTimeUs = 1450000
sample:
trackType = video
dataHashCode = -1761777019
size = 1934
isKeyFrame = false
presentationTimeUs = 1466666
sample:
trackType = video
dataHashCode = 235471194
size = 119
isKeyFrame = false
presentationTimeUs = 1483333
released = true

View File

@ -16,6 +16,7 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.isRunningOnEmulator;
import static androidx.media3.transformer.AndroidTestUtil.JPG_ASSET_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.JPG_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.MP3_ASSET_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.MP3_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_FORMAT; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_FORMAT;
@ -26,8 +27,10 @@ import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_WITH_INCREAS
import static androidx.media3.transformer.AndroidTestUtil.PNG_ASSET_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.PNG_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.createOpenGlObjects; import static androidx.media3.transformer.AndroidTestUtil.createOpenGlObjects;
import static androidx.media3.transformer.AndroidTestUtil.generateTextureFromBitmap; import static androidx.media3.transformer.AndroidTestUtil.generateTextureFromBitmap;
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -49,6 +52,7 @@ import androidx.media3.common.audio.ChannelMixingAudioProcessor;
import androidx.media3.common.audio.ChannelMixingMatrix; import androidx.media3.common.audio.ChannelMixingMatrix;
import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSourceBitmapLoader; import androidx.media3.datasource.DataSourceBitmapLoader;
import androidx.media3.effect.Contrast; import androidx.media3.effect.Contrast;
import androidx.media3.effect.DefaultGlObjectsProvider; import androidx.media3.effect.DefaultGlObjectsProvider;
@ -437,6 +441,43 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.durationMs).isAtMost(clippingEndMs - clippingStartMs); assertThat(result.exportResult.durationMs).isAtMost(clippingEndMs - clippingStartMs);
} }
@Test
public void clippedMedia_trimOptimizationEnabled_completesWithOptimizationApplied()
throws Exception {
String testId = "clippedMedia_trimOptimizationEnabled_completesWithOptimizationApplied";
if (!isRunningOnEmulator()) {
// The trim optimization is only guaranteed to work on emulator for this file.
recordTestSkipped(context, testId, /* reason= */ "Emulator only test");
assumeTrue(false);
}
if (Util.SDK_INT == 26) {
// MediaCodec returns a segmentation fault fails at this SDK level on emulators.
recordTestSkipped(context, testId, /* reason= */ "SDK 26 not supported.");
assumeTrue(false);
}
Transformer transformer =
new Transformer.Builder(context).experimentalSetTrimOptimizationEnabled(true).build();
MediaItem mediaItem =
new MediaItem.Builder()
.setUri("asset:///media/mp4/crow_emulator_transformer_output.mp4")
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(500)
.setEndPositionMs(2500)
.build())
.build();
EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
assertThat(result.exportResult.optimizationResult)
.isEqualTo(ExportResult.OPTIMIZATION_SUCCEEDED);
assertThat(result.exportResult.durationMs).isAtMost(2000);
}
@Test @Test
public void videoEncoderFormatUnsupported_completesWithError() throws Exception { public void videoEncoderFormatUnsupported_completesWithError() throws Exception {
String testId = "videoEncoderFormatUnsupported_completesWithError"; String testId = "videoEncoderFormatUnsupported_completesWithError";

View File

@ -256,6 +256,7 @@ public final class ExportResult {
width = C.LENGTH_UNSET; width = C.LENGTH_UNSET;
videoFrameCount = 0; videoFrameCount = 0;
videoEncoderName = null; videoEncoderName = null;
optimizationResult = OPTIMIZATION_NONE;
exportException = null; exportException = null;
} }
} }
@ -418,6 +419,7 @@ public final class ExportResult {
.setWidth(width) .setWidth(width)
.setVideoFrameCount(videoFrameCount) .setVideoFrameCount(videoFrameCount)
.setVideoEncoderName(videoEncoderName) .setVideoEncoderName(videoEncoderName)
.setOptimizationResult(optimizationResult)
.setExportException(exportException); .setExportException(exportException);
} }
@ -443,6 +445,7 @@ public final class ExportResult {
&& width == result.width && width == result.width
&& videoFrameCount == result.videoFrameCount && videoFrameCount == result.videoFrameCount
&& Objects.equals(videoEncoderName, result.videoEncoderName) && Objects.equals(videoEncoderName, result.videoEncoderName)
&& optimizationResult == result.optimizationResult
&& Objects.equals(exportException, result.exportException); && Objects.equals(exportException, result.exportException);
} }
@ -461,6 +464,7 @@ public final class ExportResult {
result = 31 * result + width; result = 31 * result + width;
result = 31 * result + videoFrameCount; result = 31 * result + videoFrameCount;
result = 31 * result + Objects.hashCode(videoEncoderName); result = 31 * result + Objects.hashCode(videoEncoderName);
result = 31 * result + optimizationResult;
result = 31 * result + Objects.hashCode(exportException); result = 31 * result + Objects.hashCode(exportException);
return result; return result;
} }

View File

@ -19,7 +19,9 @@ package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.areEqual;
import static androidx.media3.common.util.Util.contains; import static androidx.media3.common.util.Util.contains;
import static androidx.media3.common.util.Util.usToMs;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
@ -57,22 +59,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({MUXER_MODE_DEFAULT, MUXER_MODE_MUX_PARTIAL_VIDEO, MUXER_MODE_APPEND_VIDEO}) @IntDef({MUXER_MODE_DEFAULT, MUXER_MODE_MUX_PARTIAL, MUXER_MODE_APPEND})
public @interface MuxerMode {} public @interface MuxerMode {}
/** The default muxer mode. */ /** The default muxer mode. */
public static final int MUXER_MODE_DEFAULT = 0; public static final int MUXER_MODE_DEFAULT = 0;
/** /**
* Used for muxing a partial video. The video {@link TrackInfo} is kept the same when {@linkplain * Used for muxing a partial track(s). The {@link TrackInfo} is kept the same when {@linkplain
* #changeToAppendVideoMode() transitioning} to {@link #MUXER_MODE_APPEND_VIDEO} after finishing * #changeToAppendMode() transitioning} to {@link #MUXER_MODE_APPEND} after finishing muxing
* muxing partial video. Only one video track can be {@link #addTrackFormat(Format) added} in this * partial tracks.
* mode.
*/ */
public static final int MUXER_MODE_MUX_PARTIAL_VIDEO = 1; public static final int MUXER_MODE_MUX_PARTIAL = 1;
/** Used for appending the remaining video samples with the previously muxed partial video. */ /** Used for appending the remaining samples with the previously muxed partial file. */
public static final int MUXER_MODE_APPEND_VIDEO = 2; public static final int MUXER_MODE_APPEND = 2;
private static final String TIMER_THREAD_NAME = "Muxer:Timer"; private static final String TIMER_THREAD_NAME = "Muxer:Timer";
private static final String MUXER_TIMEOUT_ERROR_FORMAT_STRING = private static final String MUXER_TIMEOUT_ERROR_FORMAT_STRING =
@ -111,7 +112,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private @MonotonicNonNull Muxer muxer; private @MonotonicNonNull Muxer muxer;
private @MuxerMode int muxerMode; private @MuxerMode int muxerMode;
// Read by any thread, only written to on the transformerInternal thread.
private volatile boolean muxedPartialVideo; private volatile boolean muxedPartialVideo;
// Read by any thread, only written to on the transformerInternal thread.
private volatile boolean muxedPartialAudio;
private volatile int additionalRotationDegrees; private volatile int additionalRotationDegrees;
private volatile int trackCount; private volatile int trackCount;
@ -122,14 +127,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @param muxerFactory A {@link Muxer.Factory} to create a {@link Muxer}. * @param muxerFactory A {@link Muxer.Factory} to create a {@link Muxer}.
* @param listener A {@link MuxerWrapper.Listener}. * @param listener A {@link MuxerWrapper.Listener}.
* @param muxerMode The {@link MuxerMode}. The initial mode must be {@link #MUXER_MODE_DEFAULT} or * @param muxerMode The {@link MuxerMode}. The initial mode must be {@link #MUXER_MODE_DEFAULT} or
* {@link #MUXER_MODE_MUX_PARTIAL_VIDEO}. * {@link #MUXER_MODE_MUX_PARTIAL}.
*/ */
public MuxerWrapper( public MuxerWrapper(
String outputPath, Muxer.Factory muxerFactory, Listener listener, @MuxerMode int muxerMode) { String outputPath, Muxer.Factory muxerFactory, Listener listener, @MuxerMode int muxerMode) {
this.outputPath = outputPath; this.outputPath = outputPath;
this.muxerFactory = muxerFactory; this.muxerFactory = muxerFactory;
this.listener = listener; this.listener = listener;
checkArgument(muxerMode == MUXER_MODE_DEFAULT || muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO); checkArgument(muxerMode == MUXER_MODE_DEFAULT || muxerMode == MUXER_MODE_MUX_PARTIAL);
this.muxerMode = muxerMode; this.muxerMode = muxerMode;
trackTypeToInfo = new SparseArray<>(); trackTypeToInfo = new SparseArray<>();
previousTrackType = C.TRACK_TYPE_NONE; previousTrackType = C.TRACK_TYPE_NONE;
@ -137,15 +142,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Changes {@link MuxerMode} to {@link #MUXER_MODE_APPEND_VIDEO}. * Changes {@link MuxerMode} to {@link #MUXER_MODE_APPEND}.
* *
* <p>This method must be called only after partial video is muxed using {@link * <p>This method must be called only after partial file is muxed using {@link
* #MUXER_MODE_MUX_PARTIAL_VIDEO}. * #MUXER_MODE_MUX_PARTIAL}.
*/ */
public void changeToAppendVideoMode() { public void changeToAppendMode() {
checkState(muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO); checkState(muxerMode == MUXER_MODE_MUX_PARTIAL);
muxerMode = MUXER_MODE_APPEND_VIDEO; muxerMode = MUXER_MODE_APPEND;
} }
/** /**
@ -181,15 +186,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* before calling this method. * before calling this method.
*/ */
public void setTrackCount(@IntRange(from = 1) int trackCount) { public void setTrackCount(@IntRange(from = 1) int trackCount) {
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO || muxerMode == MUXER_MODE_APPEND_VIDEO) { if (muxerMode == MUXER_MODE_APPEND) {
checkArgument(
trackCount == 1,
"Only one video track can be added in MUXER_MODE_MUX_PARTIAL_VIDEO and"
+ " MUXER_MODE_APPEND_VIDEO");
if (muxerMode == MUXER_MODE_APPEND_VIDEO) {
return; return;
} }
}
checkState( checkState(
trackTypeToInfo.size() == 0, trackTypeToInfo.size() == 0,
"The track count cannot be changed after adding track formats."); "The track count cannot be changed after adding track formats.");
@ -219,11 +218,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* *
* <p>{@link Muxer#addMetadata(Metadata)} is called if the {@link Format#metadata} is present. * <p>{@link Muxer#addMetadata(Metadata)} is called if the {@link Format#metadata} is present.
* *
* @param format The {@link Format} to be added. In {@link #MUXER_MODE_APPEND_VIDEO} mode, the * @param format The {@link Format} to be added. In {@link #MUXER_MODE_APPEND} mode, the added
* added {@link Format} must match the existing {@link Format} set when the muxer was in * {@link Format} must match the existing {@link Format} set when the muxer was in {@link
* {@link #MUXER_MODE_MUX_PARTIAL_VIDEO} mode. * #MUXER_MODE_MUX_PARTIAL} mode.
* @throws IllegalArgumentException If the format is unsupported or if it does not match the * @throws IllegalArgumentException If the format is unsupported or if it does not match the
* existing format in {@link #MUXER_MODE_APPEND_VIDEO} mode. * existing format in {@link #MUXER_MODE_APPEND} mode.
* @throws IllegalStateException If the number of formats added exceeds the {@linkplain * @throws IllegalStateException If the number of formats added exceeds the {@linkplain
* #setTrackCount track count}, if {@link #setTrackCount(int)} has not been called or if there * #setTrackCount track count}, if {@link #setTrackCount(int)} has not been called or if there
* is already a track of that {@link C.TrackType}. * is already a track of that {@link C.TrackType}.
@ -231,36 +230,43 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* the track. * the track.
*/ */
public void addTrackFormat(Format format) throws Muxer.MuxerException { public void addTrackFormat(Format format) throws Muxer.MuxerException {
if (muxerMode == MUXER_MODE_APPEND_VIDEO) {
checkState(contains(trackTypeToInfo, C.TRACK_TYPE_VIDEO));
TrackInfo videoTrackInfo = trackTypeToInfo.get(C.TRACK_TYPE_VIDEO);
// Ensure that video formats are the same. Some fields like codecs, averageBitrate, framerate,
// etc, don't match exactly in the Extractor output format and the Encoder output
// format but these fields can be ignored.
// TODO: b/308180225 - Compare Format.colorInfo as well.
Format existingFormat = videoTrackInfo.format;
checkArgument(Util.areEqual(existingFormat.sampleMimeType, format.sampleMimeType));
checkArgument(existingFormat.width == format.width);
checkArgument(existingFormat.height == format.height);
checkArgument(existingFormat.initializationDataEquals(format));
checkNotNull(muxer);
resetAbortTimer();
return;
} else if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO) {
checkArgument(MimeTypes.isVideo(format.sampleMimeType));
}
int trackCount = this.trackCount;
checkState(trackCount > 0, "The track count should be set before the formats are added.");
checkState(trackTypeToInfo.size() < trackCount, "All track formats have already been added.");
@Nullable String sampleMimeType = format.sampleMimeType; @Nullable String sampleMimeType = format.sampleMimeType;
@C.TrackType int trackType = MimeTypes.getTrackType(sampleMimeType); @C.TrackType int trackType = MimeTypes.getTrackType(sampleMimeType);
checkArgument( checkArgument(
trackType == C.TRACK_TYPE_AUDIO || trackType == C.TRACK_TYPE_VIDEO, trackType == C.TRACK_TYPE_AUDIO || trackType == C.TRACK_TYPE_VIDEO,
"Unsupported track format: " + sampleMimeType); "Unsupported track format: " + sampleMimeType);
if (muxerMode == MUXER_MODE_APPEND) {
if (trackType == C.TRACK_TYPE_VIDEO) {
checkState(contains(trackTypeToInfo, C.TRACK_TYPE_VIDEO));
TrackInfo videoTrackInfo = trackTypeToInfo.get(C.TRACK_TYPE_VIDEO);
// Ensure that video formats are the same. Some fields like codecs, averageBitrate,
// framerate, etc, don't match exactly in the Extractor output format and the Encoder output
// format but these fields can be ignored.
// TODO: b/308180225 - Compare Format.colorInfo as well.
Format existingFormat = videoTrackInfo.format;
checkArgument(areEqual(existingFormat.sampleMimeType, format.sampleMimeType));
checkArgument(existingFormat.width == format.width);
checkArgument(existingFormat.height == format.height);
checkArgument(existingFormat.initializationDataEquals(format));
} else if (trackType == C.TRACK_TYPE_AUDIO) {
checkState(contains(trackTypeToInfo, C.TRACK_TYPE_AUDIO));
TrackInfo audioTrackInfo = trackTypeToInfo.get(C.TRACK_TYPE_AUDIO);
Format existingFormat = audioTrackInfo.format;
checkArgument(areEqual(existingFormat.sampleMimeType, format.sampleMimeType));
checkArgument(existingFormat.channelCount == format.channelCount);
checkArgument(existingFormat.sampleRate == format.sampleRate);
checkArgument(existingFormat.initializationDataEquals(format));
}
checkNotNull(muxer);
resetAbortTimer();
return;
}
int trackCount = this.trackCount;
checkState(trackCount > 0, "The track count should be set before the formats are added.");
checkState(trackTypeToInfo.size() < trackCount, "All track formats have already been added.");
checkState( checkState(
!contains(trackTypeToInfo, trackType), "There is already a track of type " + trackType); !contains(trackTypeToInfo, trackType), "There is already a track of type " + trackType);
@ -362,8 +368,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
DebugTraceUtil.logEvent(DebugTraceUtil.EVENT_MUXER_TRACK_ENDED_AUDIO, trackInfo.timeUs); DebugTraceUtil.logEvent(DebugTraceUtil.EVENT_MUXER_TRACK_ENDED_AUDIO, trackInfo.timeUs);
} }
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO) { if (muxerMode == MUXER_MODE_MUX_PARTIAL) {
if (trackType == C.TRACK_TYPE_VIDEO) {
muxedPartialVideo = true; muxedPartialVideo = true;
} else if (trackType == C.TRACK_TYPE_AUDIO) {
muxedPartialAudio = true;
}
} else { } else {
trackTypeToInfo.delete(trackType); trackTypeToInfo.delete(trackType);
if (trackTypeToInfo.size() == 0) { if (trackTypeToInfo.size() == 0) {
@ -371,8 +381,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
} }
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO && muxedPartialVideo) { if (muxerMode == MUXER_MODE_MUX_PARTIAL
listener.onEnded(Util.usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes()); && muxedPartialVideo
&& (muxedPartialAudio || trackCount == 1)) {
listener.onEnded(usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes());
if (abortScheduledFuture != null) { if (abortScheduledFuture != null) {
abortScheduledFuture.cancel(/* mayInterruptIfRunning= */ false); abortScheduledFuture.cancel(/* mayInterruptIfRunning= */ false);
} }
@ -380,26 +392,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
if (isEnded) { if (isEnded) {
listener.onEnded(Util.usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes()); listener.onEnded(usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes());
abortScheduledExecutorService.shutdownNow(); abortScheduledExecutorService.shutdownNow();
} }
} }
/** /**
* Returns whether all the tracks are {@linkplain #endTrack(int) ended} or a partial video is * Returns whether all the tracks are {@linkplain #endTrack(int) ended} or a partial file is
* completely muxed using {@link #MUXER_MODE_MUX_PARTIAL_VIDEO}. * completely muxed using {@link #MUXER_MODE_MUX_PARTIAL}.
*/ */
public boolean isEnded() { public boolean isEnded() {
return isEnded || (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO && muxedPartialVideo); return isEnded
|| (muxerMode == MUXER_MODE_MUX_PARTIAL
&& muxedPartialVideo
&& (muxedPartialAudio || trackCount == 1));
} }
/** /**
* Finishes writing the output and releases any resources associated with muxing. * Finishes writing the output and releases any resources associated with muxing.
* *
* <p>When this method is called in {@link #MUXER_MODE_MUX_PARTIAL_VIDEO} mode, the resources are * <p>When this method is called in {@link #MUXER_MODE_MUX_PARTIAL} mode, the resources are not
* not released and the {@link MuxerWrapper} can be reused after {@link #changeToAppendVideoMode() * released and the {@link MuxerWrapper} can be reused after {@link #changeToAppendMode() changing
* changing mode} to {@link #MUXER_MODE_APPEND_VIDEO} mode. In all other modes the {@link * mode} to {@link #MUXER_MODE_APPEND}. In all other modes the {@link MuxerWrapper} cannot be used
* MuxerWrapper} cannot be used anymore once this method has been called. * anymore once this method has been called.
* *
* @param forCancellation Whether the reason for releasing the resources is the transformation * @param forCancellation Whether the reason for releasing the resources is the transformation
* cancellation. * cancellation.
@ -407,7 +422,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* and {@code forCancellation} is false. * and {@code forCancellation} is false.
*/ */
public void release(boolean forCancellation) throws Muxer.MuxerException { public void release(boolean forCancellation) throws Muxer.MuxerException {
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO && !forCancellation) { if (muxerMode == MUXER_MODE_MUX_PARTIAL && !forCancellation) {
return; return;
} }
isReady = false; isReady = false;

View File

@ -287,7 +287,7 @@ public final class Transformer {
return this; return this;
} }
// TODO: b/304476154 - Support audio and progress updates in trim optimization. // TODO: b/304476154 - Support progress updates in trim optimization.
/** /**
* Sets whether to attempt to optimize trims from the start of the {@link EditedMediaItem} by * Sets whether to attempt to optimize trims from the start of the {@link EditedMediaItem} by
* transcoding as little of the file as possible and transmuxing the rest. * transcoding as little of the file as possible and transmuxing the rest.
@ -298,7 +298,6 @@ public final class Transformer {
* <li>Only supported for single-asset (i.e. only one {@link EditedMediaItem} in the whole * <li>Only supported for single-asset (i.e. only one {@link EditedMediaItem} in the whole
* {@link Composition}) exports of mp4 files. * {@link Composition}) exports of mp4 files.
* <li>Not guaranteed to work with any effects. * <li>Not guaranteed to work with any effects.
* <li>Video track only (removes audio from the file).
* <li>Progress updates will be unavailable. * <li>Progress updates will be unavailable.
* </ul> * </ul>
* *
@ -708,8 +707,8 @@ public final class Transformer {
TRANSFORMER_STATE_PROCESS_REMAINING_VIDEO, TRANSFORMER_STATE_PROCESS_REMAINING_VIDEO,
TRANSFORMER_STATE_PROCESS_AUDIO, TRANSFORMER_STATE_PROCESS_AUDIO,
TRANSFORMER_STATE_COPY_OUTPUT, TRANSFORMER_STATE_COPY_OUTPUT,
TRANSFORMER_STATE_TRANSCODE_VIDEO_START, TRANSFORMER_STATE_PROCESS_MEDIA_START,
TRANSFORMER_STATE_TRANSMUX_REMAINING_VIDEO TRANSFORMER_STATE_REMUX_REMAINING_MEDIA
}) })
private @interface TransformerState {} private @interface TransformerState {}
@ -718,9 +717,11 @@ public final class Transformer {
private static final int TRANSFORMER_STATE_PROCESS_REMAINING_VIDEO = 2; private static final int TRANSFORMER_STATE_PROCESS_REMAINING_VIDEO = 2;
private static final int TRANSFORMER_STATE_PROCESS_AUDIO = 3; private static final int TRANSFORMER_STATE_PROCESS_AUDIO = 3;
private static final int TRANSFORMER_STATE_COPY_OUTPUT = 4; private static final int TRANSFORMER_STATE_COPY_OUTPUT = 4;
private static final int TRANSFORMER_STATE_TRANSCODE_VIDEO_START = 5; private static final int TRANSFORMER_STATE_PROCESS_MEDIA_START = 5;
private static final int TRANSFORMER_STATE_TRANSMUX_REMAINING_VIDEO = 6; private static final int TRANSFORMER_STATE_REMUX_REMAINING_MEDIA = 6;
// TODO: b/304476154 - Calculate duration based on sample rate from audio format.
private static final int MAX_ENCODED_AUDIO_BUFFER_DURATION_US = 25_000;
private final Context context; private final Context context;
private final TransformationRequest transformationRequest; private final TransformationRequest transformationRequest;
private final ImmutableList<AudioProcessor> audioProcessors; private final ImmutableList<AudioProcessor> audioProcessors;
@ -925,7 +926,7 @@ public final class Transformer {
componentListener, componentListener,
/* initialTimestampOffsetUs= */ 0); /* initialTimestampOffsetUs= */ 0);
} else { } else {
transcodeVideoBeforeFirstSyncSampleAfterTrimStartTime(); processMediaBeforeFirstSyncSampleAfterTrimStartTime();
} }
} }
@ -1139,7 +1140,7 @@ public final class Transformer {
checkNotNull(outputFilePath), checkNotNull(outputFilePath),
muxerFactory, muxerFactory,
componentListener, componentListener,
MuxerWrapper.MUXER_MODE_MUX_PARTIAL_VIDEO); MuxerWrapper.MUXER_MODE_MUX_PARTIAL);
startInternal( startInternal(
TransmuxTranscodeHelper.createVideoOnlyComposition( TransmuxTranscodeHelper.createVideoOnlyComposition(
@ -1169,7 +1170,7 @@ public final class Transformer {
resumeMetadata); resumeMetadata);
checkNotNull(remuxingMuxerWrapper); checkNotNull(remuxingMuxerWrapper);
remuxingMuxerWrapper.changeToAppendVideoMode(); remuxingMuxerWrapper.changeToAppendMode();
startInternal( startInternal(
videoOnlyComposition, videoOnlyComposition,
@ -1218,11 +1219,12 @@ public final class Transformer {
applicationHandler::post); applicationHandler::post);
} }
private void transcodeVideoBeforeFirstSyncSampleAfterTrimStartTime() { private void processMediaBeforeFirstSyncSampleAfterTrimStartTime() {
transformerState = TRANSFORMER_STATE_TRANSCODE_VIDEO_START; transformerState = TRANSFORMER_STATE_PROCESS_MEDIA_START;
MediaItem firstMediaItem = MediaItem firstMediaItem =
checkNotNull(composition).sequences.get(0).editedMediaItems.get(0).mediaItem; checkNotNull(composition).sequences.get(0).editedMediaItems.get(0).mediaItem;
long trimStartTimeUs = firstMediaItem.clippingConfiguration.startPositionUs; long trimStartTimeUs = firstMediaItem.clippingConfiguration.startPositionUs;
long trimEndTimeUs = firstMediaItem.clippingConfiguration.endPositionUs;
getMp4MetadataInfoFuture = getMp4MetadataInfoFuture =
TransmuxTranscodeHelper.getMp4MetadataInfo( TransmuxTranscodeHelper.getMp4MetadataInfo(
context, context,
@ -1233,16 +1235,21 @@ public final class Transformer {
new FutureCallback<Mp4MetadataInfo>() { new FutureCallback<Mp4MetadataInfo>() {
@Override @Override
public void onSuccess(Mp4MetadataInfo mp4MetadataInfo) { public void onSuccess(Mp4MetadataInfo mp4MetadataInfo) {
if (mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs == C.TIME_UNSET) { if (mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs == C.TIME_UNSET
|| (trimEndTimeUs != C.TIME_END_OF_SOURCE
&& trimEndTimeUs < mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs)) {
exportResultBuilder.setOptimizationResult(OPTIMIZATION_FAILED_NO_VIDEO_TRACK_TO_TRIM); exportResultBuilder.setOptimizationResult(OPTIMIZATION_FAILED_NO_VIDEO_TRACK_TO_TRIM);
processFullInput(); processFullInput();
return; return;
} }
if (mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs == trimStartTimeUs) { // Ensure there is an audio sample to mux between the two clip times to prevent
// Transformer from hanging because it received an audio track but no audio samples.
if (mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs - trimStartTimeUs
< MAX_ENCODED_AUDIO_BUFFER_DURATION_US) {
Transformer.this.composition = Transformer.this.composition =
buildNewCompositionWithClipTimes( buildNewCompositionWithClipTimes(
composition, composition,
trimStartTimeUs, mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs,
firstMediaItem.clippingConfiguration.endPositionUs, firstMediaItem.clippingConfiguration.endPositionUs,
mp4MetadataInfo.durationUs, mp4MetadataInfo.durationUs,
/* startsAtKeyFrame= */ true); /* startsAtKeyFrame= */ true);
@ -1266,7 +1273,7 @@ public final class Transformer {
checkNotNull(outputFilePath), checkNotNull(outputFilePath),
muxerFactory, muxerFactory,
componentListener, componentListener,
MuxerWrapper.MUXER_MODE_MUX_PARTIAL_VIDEO); MuxerWrapper.MUXER_MODE_MUX_PARTIAL);
startInternal( startInternal(
trancodeComposition, trancodeComposition,
remuxingMuxerWrapper, remuxingMuxerWrapper,
@ -1283,8 +1290,8 @@ public final class Transformer {
applicationHandler::post); applicationHandler::post);
} }
private void transmuxRemainingVideo() { private void remuxRemainingMedia() {
transformerState = TRANSFORMER_STATE_TRANSMUX_REMAINING_VIDEO; transformerState = TRANSFORMER_STATE_REMUX_REMAINING_MEDIA;
// TODO: b/304476154 - check original file format against transcode file format here to fail // TODO: b/304476154 - check original file format against transcode file format here to fail
// fast if necessary. // fast if necessary.
MediaItem firstMediaItem = MediaItem firstMediaItem =
@ -1300,7 +1307,7 @@ public final class Transformer {
mp4MetadataInfo.durationUs, mp4MetadataInfo.durationUs,
/* startsAtKeyFrame= */ true); /* startsAtKeyFrame= */ true);
checkNotNull(remuxingMuxerWrapper); checkNotNull(remuxingMuxerWrapper);
remuxingMuxerWrapper.changeToAppendVideoMode(); remuxingMuxerWrapper.changeToAppendMode();
startInternal( startInternal(
transmuxComposition, transmuxComposition,
remuxingMuxerWrapper, remuxingMuxerWrapper,
@ -1410,9 +1417,9 @@ public final class Transformer {
processAudio(); processAudio();
} else if (transformerState == TRANSFORMER_STATE_PROCESS_AUDIO) { } else if (transformerState == TRANSFORMER_STATE_PROCESS_AUDIO) {
copyOutput(); copyOutput();
} else if (transformerState == TRANSFORMER_STATE_TRANSCODE_VIDEO_START) { } else if (transformerState == TRANSFORMER_STATE_PROCESS_MEDIA_START) {
transmuxRemainingVideo(); remuxRemainingMedia();
} else if (transformerState == TRANSFORMER_STATE_TRANSMUX_REMAINING_VIDEO) { } else if (transformerState == TRANSFORMER_STATE_REMUX_REMAINING_MEDIA) {
exportResultBuilder.setOptimizationResult(ExportResult.OPTIMIZATION_SUCCEEDED); exportResultBuilder.setOptimizationResult(ExportResult.OPTIMIZATION_SUCCEEDED);
onExportCompletedWithSuccess(); onExportCompletedWithSuccess();
} else { } else {

View File

@ -101,8 +101,6 @@ import java.util.List;
.buildUpon() .buildUpon()
.setMediaItem(mediaItem) .setMediaItem(mediaItem)
.setDurationUs(mediaDurationUs) .setDurationUs(mediaDurationUs)
// TODO: b/304476154 - Support audio in trim optimization.
.setRemoveAudio(true)
.build(); .build();
return oldComposition return oldComposition

View File

@ -196,14 +196,12 @@ public final class MediaItemExportTest {
ExportResult exportResult = TransformerTestRunner.runLooper(transformer); ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
assertThat(exportResult.optimizationResult).isEqualTo(ExportResult.OPTIMIZATION_NONE); assertThat(exportResult.optimizationResult).isEqualTo(ExportResult.OPTIMIZATION_NONE);
// TODO: b/304476154 - When trim optimization supports audio, remove trim optimization specific
// file and use the pre-existing clipped file made from normal export path.
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, context,
muxerFactory.getCreatedMuxer(), muxerFactory.getCreatedMuxer(),
getDumpFileName( getDumpFileName(
/* originalFileName= */ FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S, /* originalFileName= */ FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S,
/* modifications...= */ "trimOptimizedClippedAtKeyFrame")); /* modifications...= */ "clipped"));
} }
@Test @Test

View File

@ -17,7 +17,7 @@ package androidx.media3.transformer;
import static androidx.media3.common.MimeTypes.AUDIO_AAC; import static androidx.media3.common.MimeTypes.AUDIO_AAC;
import static androidx.media3.common.MimeTypes.VIDEO_H264; import static androidx.media3.common.MimeTypes.VIDEO_H264;
import static androidx.media3.transformer.MuxerWrapper.MUXER_MODE_MUX_PARTIAL_VIDEO; import static androidx.media3.transformer.MuxerWrapper.MUXER_MODE_MUX_PARTIAL;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
@ -66,7 +66,7 @@ public class MuxerWrapperTest {
} }
@Test @Test
public void changeToAppendVideoMode_afterDefaultMode_throws() throws Exception { public void changeToAppendMode_afterDefaultMode_throws() throws Exception {
muxerWrapper = muxerWrapper =
new MuxerWrapper( new MuxerWrapper(
temporaryFolder.newFile().getPath(), temporaryFolder.newFile().getPath(),
@ -74,86 +74,62 @@ public class MuxerWrapperTest {
new NoOpMuxerListenerImpl(), new NoOpMuxerListenerImpl(),
MuxerWrapper.MUXER_MODE_DEFAULT); MuxerWrapper.MUXER_MODE_DEFAULT);
assertThrows(IllegalStateException.class, muxerWrapper::changeToAppendVideoMode); assertThrows(IllegalStateException.class, muxerWrapper::changeToAppendMode);
} }
@Test @Test
public void setTrackCount_toTwoInMuxPartialVideoMode_throws() throws Exception { public void addTrackFormat_withSameVideoFormatInAppendMode_doesNotThrow() throws Exception {
muxerWrapper = muxerWrapper =
new MuxerWrapper( new MuxerWrapper(
temporaryFolder.newFile().getPath(), temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(), new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(), new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL_VIDEO); MUXER_MODE_MUX_PARTIAL);
assertThrows(IllegalArgumentException.class, () -> muxerWrapper.setTrackCount(2));
}
@Test
public void setTrackCount_toTwoInAppendVideoMode_throws() throws Exception {
muxerWrapper =
new MuxerWrapper(
temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL_VIDEO);
muxerWrapper.setTrackCount(1); muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT); muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
muxerWrapper.writeSample( muxerWrapper.writeSample(
C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0); C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO); muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO);
muxerWrapper.changeToAppendVideoMode(); muxerWrapper.changeToAppendMode();
assertThrows(IllegalArgumentException.class, () -> muxerWrapper.setTrackCount(2));
}
@Test
public void addTrackFormat_withAudioFormatInMuxPartialVideoMode_throws() throws Exception {
muxerWrapper =
new MuxerWrapper(
temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL_VIDEO);
muxerWrapper.setTrackCount(1); muxerWrapper.setTrackCount(1);
assertThrows( muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
IllegalArgumentException.class, () -> muxerWrapper.addTrackFormat(FAKE_AUDIO_TRACK_FORMAT));
} }
@Test @Test
public void addTrackFormat_withSameVideoFormatInAppendVideoMode_doesNotThrow() throws Exception { public void addTrackFormat_withSameAudioFormatInAppendMode_doesNotThrow() throws Exception {
muxerWrapper = muxerWrapper =
new MuxerWrapper( new MuxerWrapper(
temporaryFolder.newFile().getPath(), temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(), new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(), new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL_VIDEO); MUXER_MODE_MUX_PARTIAL);
muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_AUDIO_TRACK_FORMAT);
muxerWrapper.writeSample(
C.TRACK_TYPE_AUDIO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_AUDIO);
muxerWrapper.changeToAppendMode();
muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_AUDIO_TRACK_FORMAT);
}
@Test
public void addTrackFormat_withDifferentVideoFormatInAppendMode_throws() throws Exception {
muxerWrapper =
new MuxerWrapper(
temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL);
muxerWrapper.setTrackCount(1); muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT); muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
muxerWrapper.writeSample( muxerWrapper.writeSample(
C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0); C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO); muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO);
muxerWrapper.changeToAppendVideoMode(); muxerWrapper.changeToAppendMode();
muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
}
@Test
public void addTrackFormat_withDifferentVideoFormatInAppendVideoMode_throws() throws Exception {
muxerWrapper =
new MuxerWrapper(
temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL_VIDEO);
muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
muxerWrapper.writeSample(
C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO);
muxerWrapper.changeToAppendVideoMode();
muxerWrapper.setTrackCount(1); muxerWrapper.setTrackCount(1);
Format differentVideoFormat = FAKE_VIDEO_TRACK_FORMAT.buildUpon().setHeight(5000).build(); Format differentVideoFormat = FAKE_VIDEO_TRACK_FORMAT.buildUpon().setHeight(5000).build();
@ -161,6 +137,27 @@ public class MuxerWrapperTest {
IllegalArgumentException.class, () -> muxerWrapper.addTrackFormat(differentVideoFormat)); IllegalArgumentException.class, () -> muxerWrapper.addTrackFormat(differentVideoFormat));
} }
@Test
public void addTrackFormat_withDifferentAudioFormatInAppendMode_throws() throws Exception {
muxerWrapper =
new MuxerWrapper(
temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL);
muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_AUDIO_TRACK_FORMAT);
muxerWrapper.writeSample(
C.TRACK_TYPE_AUDIO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_AUDIO);
muxerWrapper.changeToAppendMode();
muxerWrapper.setTrackCount(1);
Format differentAudioFormat = FAKE_AUDIO_TRACK_FORMAT.buildUpon().setSampleRate(48000).build();
assertThrows(
IllegalArgumentException.class, () -> muxerWrapper.addTrackFormat(differentAudioFormat));
}
@Test @Test
public void isEnded_afterPartialVideoMuxed_returnsTrue() throws Exception { public void isEnded_afterPartialVideoMuxed_returnsTrue() throws Exception {
muxerWrapper = muxerWrapper =
@ -168,7 +165,7 @@ public class MuxerWrapperTest {
temporaryFolder.newFile().getPath(), temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(), new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(), new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL_VIDEO); MUXER_MODE_MUX_PARTIAL);
muxerWrapper.setTrackCount(1); muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT); muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
muxerWrapper.writeSample( muxerWrapper.writeSample(
@ -179,19 +176,44 @@ public class MuxerWrapperTest {
} }
@Test @Test
public void isEnded_afterStartingAppendVideo_returnsFalse() throws Exception { public void isEnded_afterPartialAudioAndVideoMuxed_returnsTrue() throws Exception {
muxerWrapper = muxerWrapper =
new MuxerWrapper( new MuxerWrapper(
temporaryFolder.newFile().getPath(), temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(), new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(), new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL_VIDEO); MUXER_MODE_MUX_PARTIAL);
muxerWrapper.setTrackCount(2);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
muxerWrapper.addTrackFormat(FAKE_AUDIO_TRACK_FORMAT);
muxerWrapper.writeSample(
C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO);
assertThat(muxerWrapper.isEnded()).isFalse();
muxerWrapper.writeSample(
C.TRACK_TYPE_AUDIO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_AUDIO);
assertThat(muxerWrapper.isEnded()).isTrue();
}
@Test
public void isEnded_afterEnteringAppendMode_returnsFalse() throws Exception {
muxerWrapper =
new MuxerWrapper(
temporaryFolder.newFile().getPath(),
new DefaultMuxer.Factory(),
new NoOpMuxerListenerImpl(),
MUXER_MODE_MUX_PARTIAL);
muxerWrapper.setTrackCount(1); muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT); muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
muxerWrapper.writeSample( muxerWrapper.writeSample(
C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0); C.TRACK_TYPE_VIDEO, FAKE_SAMPLE, /* isKeyFrame= */ true, /* presentationTimeUs= */ 0);
muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO); muxerWrapper.endTrack(C.TRACK_TYPE_VIDEO);
muxerWrapper.changeToAppendVideoMode(); muxerWrapper.changeToAppendMode();
muxerWrapper.setTrackCount(1); muxerWrapper.setTrackCount(1);
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT); muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);