mirror of
https://github.com/androidx/media.git
synced 2025-05-09 16:40:55 +08:00
Transformer: Add support for transmuxing audio in trim optimization
PiperOrigin-RevId: 588711597
This commit is contained in:
parent
faeff17a4c
commit
cd346ca14d
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -16,6 +16,7 @@
|
||||
package androidx.media3.transformer;
|
||||
|
||||
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.MP3_ASSET_URI_STRING;
|
||||
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.createOpenGlObjects;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.generateTextureFromBitmap;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import android.content.Context;
|
||||
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.SonicAudioProcessor;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||
import androidx.media3.effect.Contrast;
|
||||
import androidx.media3.effect.DefaultGlObjectsProvider;
|
||||
@ -437,6 +441,43 @@ public class TransformerEndToEndTest {
|
||||
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
|
||||
public void videoEncoderFormatUnsupported_completesWithError() throws Exception {
|
||||
String testId = "videoEncoderFormatUnsupported_completesWithError";
|
||||
|
@ -256,6 +256,7 @@ public final class ExportResult {
|
||||
width = C.LENGTH_UNSET;
|
||||
videoFrameCount = 0;
|
||||
videoEncoderName = null;
|
||||
optimizationResult = OPTIMIZATION_NONE;
|
||||
exportException = null;
|
||||
}
|
||||
}
|
||||
@ -418,6 +419,7 @@ public final class ExportResult {
|
||||
.setWidth(width)
|
||||
.setVideoFrameCount(videoFrameCount)
|
||||
.setVideoEncoderName(videoEncoderName)
|
||||
.setOptimizationResult(optimizationResult)
|
||||
.setExportException(exportException);
|
||||
}
|
||||
|
||||
@ -443,6 +445,7 @@ public final class ExportResult {
|
||||
&& width == result.width
|
||||
&& videoFrameCount == result.videoFrameCount
|
||||
&& Objects.equals(videoEncoderName, result.videoEncoderName)
|
||||
&& optimizationResult == result.optimizationResult
|
||||
&& Objects.equals(exportException, result.exportException);
|
||||
}
|
||||
|
||||
@ -461,6 +464,7 @@ public final class ExportResult {
|
||||
result = 31 * result + width;
|
||||
result = 31 * result + videoFrameCount;
|
||||
result = 31 * result + Objects.hashCode(videoEncoderName);
|
||||
result = 31 * result + optimizationResult;
|
||||
result = 31 * result + Objects.hashCode(exportException);
|
||||
return result;
|
||||
}
|
||||
|
@ -19,7 +19,9 @@ package androidx.media3.transformer;
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
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.usToMs;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
@ -57,22 +59,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@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 {}
|
||||
|
||||
/** The default muxer mode. */
|
||||
public static final int MUXER_MODE_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Used for muxing a partial video. The video {@link TrackInfo} is kept the same when {@linkplain
|
||||
* #changeToAppendVideoMode() transitioning} to {@link #MUXER_MODE_APPEND_VIDEO} after finishing
|
||||
* muxing partial video. Only one video track can be {@link #addTrackFormat(Format) added} in this
|
||||
* mode.
|
||||
* Used for muxing a partial track(s). The {@link TrackInfo} is kept the same when {@linkplain
|
||||
* #changeToAppendMode() transitioning} to {@link #MUXER_MODE_APPEND} after finishing muxing
|
||||
* partial tracks.
|
||||
*/
|
||||
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. */
|
||||
public static final int MUXER_MODE_APPEND_VIDEO = 2;
|
||||
/** Used for appending the remaining samples with the previously muxed partial file. */
|
||||
public static final int MUXER_MODE_APPEND = 2;
|
||||
|
||||
private static final String TIMER_THREAD_NAME = "Muxer:Timer";
|
||||
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 @MuxerMode int muxerMode;
|
||||
|
||||
// Read by any thread, only written to on the transformerInternal thread.
|
||||
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 trackCount;
|
||||
|
||||
@ -122,14 +127,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
* @param muxerFactory A {@link Muxer.Factory} to create a {@link Muxer}.
|
||||
* @param listener A {@link MuxerWrapper.Listener}.
|
||||
* @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(
|
||||
String outputPath, Muxer.Factory muxerFactory, Listener listener, @MuxerMode int muxerMode) {
|
||||
this.outputPath = outputPath;
|
||||
this.muxerFactory = muxerFactory;
|
||||
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;
|
||||
trackTypeToInfo = new SparseArray<>();
|
||||
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
|
||||
* #MUXER_MODE_MUX_PARTIAL_VIDEO}.
|
||||
* <p>This method must be called only after partial file is muxed using {@link
|
||||
* #MUXER_MODE_MUX_PARTIAL}.
|
||||
*/
|
||||
public void changeToAppendVideoMode() {
|
||||
checkState(muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO);
|
||||
public void changeToAppendMode() {
|
||||
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.
|
||||
*/
|
||||
public void setTrackCount(@IntRange(from = 1) int trackCount) {
|
||||
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO || muxerMode == MUXER_MODE_APPEND_VIDEO) {
|
||||
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) {
|
||||
if (muxerMode == MUXER_MODE_APPEND) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
checkState(
|
||||
trackTypeToInfo.size() == 0,
|
||||
"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.
|
||||
*
|
||||
* @param format The {@link Format} to be added. In {@link #MUXER_MODE_APPEND_VIDEO} mode, the
|
||||
* added {@link Format} must match the existing {@link Format} set when the muxer was in
|
||||
* {@link #MUXER_MODE_MUX_PARTIAL_VIDEO} mode.
|
||||
* @param format The {@link Format} to be added. In {@link #MUXER_MODE_APPEND} mode, the added
|
||||
* {@link Format} must match the existing {@link Format} set when the muxer was in {@link
|
||||
* #MUXER_MODE_MUX_PARTIAL} mode.
|
||||
* @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
|
||||
* #setTrackCount track count}, if {@link #setTrackCount(int)} has not been called or if there
|
||||
* is already a track of that {@link C.TrackType}.
|
||||
@ -231,36 +230,43 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
* the track.
|
||||
*/
|
||||
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;
|
||||
@C.TrackType int trackType = MimeTypes.getTrackType(sampleMimeType);
|
||||
checkArgument(
|
||||
trackType == C.TRACK_TYPE_AUDIO || trackType == C.TRACK_TYPE_VIDEO,
|
||||
"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(
|
||||
!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);
|
||||
}
|
||||
|
||||
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO) {
|
||||
if (muxerMode == MUXER_MODE_MUX_PARTIAL) {
|
||||
if (trackType == C.TRACK_TYPE_VIDEO) {
|
||||
muxedPartialVideo = true;
|
||||
} else if (trackType == C.TRACK_TYPE_AUDIO) {
|
||||
muxedPartialAudio = true;
|
||||
}
|
||||
} else {
|
||||
trackTypeToInfo.delete(trackType);
|
||||
if (trackTypeToInfo.size() == 0) {
|
||||
@ -371,8 +381,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO && muxedPartialVideo) {
|
||||
listener.onEnded(Util.usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes());
|
||||
if (muxerMode == MUXER_MODE_MUX_PARTIAL
|
||||
&& muxedPartialVideo
|
||||
&& (muxedPartialAudio || trackCount == 1)) {
|
||||
listener.onEnded(usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes());
|
||||
if (abortScheduledFuture != null) {
|
||||
abortScheduledFuture.cancel(/* mayInterruptIfRunning= */ false);
|
||||
}
|
||||
@ -380,26 +392,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
}
|
||||
|
||||
if (isEnded) {
|
||||
listener.onEnded(Util.usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes());
|
||||
listener.onEnded(usToMs(maxEndedTrackTimeUs), getCurrentOutputSizeBytes());
|
||||
abortScheduledExecutorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether all the tracks are {@linkplain #endTrack(int) ended} or a partial video is
|
||||
* completely muxed using {@link #MUXER_MODE_MUX_PARTIAL_VIDEO}.
|
||||
* Returns whether all the tracks are {@linkplain #endTrack(int) ended} or a partial file is
|
||||
* completely muxed using {@link #MUXER_MODE_MUX_PARTIAL}.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p>When this method is called in {@link #MUXER_MODE_MUX_PARTIAL_VIDEO} mode, the resources are
|
||||
* not released and the {@link MuxerWrapper} can be reused after {@link #changeToAppendVideoMode()
|
||||
* changing mode} to {@link #MUXER_MODE_APPEND_VIDEO} mode. In all other modes the {@link
|
||||
* MuxerWrapper} cannot be used anymore once this method has been called.
|
||||
* <p>When this method is called in {@link #MUXER_MODE_MUX_PARTIAL} mode, the resources are not
|
||||
* released and the {@link MuxerWrapper} can be reused after {@link #changeToAppendMode() changing
|
||||
* mode} to {@link #MUXER_MODE_APPEND}. In all other modes the {@link MuxerWrapper} cannot be used
|
||||
* anymore once this method has been called.
|
||||
*
|
||||
* @param forCancellation Whether the reason for releasing the resources is the transformation
|
||||
* cancellation.
|
||||
@ -407,7 +422,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
* and {@code forCancellation} is false.
|
||||
*/
|
||||
public void release(boolean forCancellation) throws Muxer.MuxerException {
|
||||
if (muxerMode == MUXER_MODE_MUX_PARTIAL_VIDEO && !forCancellation) {
|
||||
if (muxerMode == MUXER_MODE_MUX_PARTIAL && !forCancellation) {
|
||||
return;
|
||||
}
|
||||
isReady = false;
|
||||
|
@ -287,7 +287,7 @@ public final class Transformer {
|
||||
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
|
||||
* 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
|
||||
* {@link Composition}) exports of mp4 files.
|
||||
* <li>Not guaranteed to work with any effects.
|
||||
* <li>Video track only (removes audio from the file).
|
||||
* <li>Progress updates will be unavailable.
|
||||
* </ul>
|
||||
*
|
||||
@ -708,8 +707,8 @@ public final class Transformer {
|
||||
TRANSFORMER_STATE_PROCESS_REMAINING_VIDEO,
|
||||
TRANSFORMER_STATE_PROCESS_AUDIO,
|
||||
TRANSFORMER_STATE_COPY_OUTPUT,
|
||||
TRANSFORMER_STATE_TRANSCODE_VIDEO_START,
|
||||
TRANSFORMER_STATE_TRANSMUX_REMAINING_VIDEO
|
||||
TRANSFORMER_STATE_PROCESS_MEDIA_START,
|
||||
TRANSFORMER_STATE_REMUX_REMAINING_MEDIA
|
||||
})
|
||||
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_AUDIO = 3;
|
||||
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_TRANSMUX_REMAINING_VIDEO = 6;
|
||||
private static final int TRANSFORMER_STATE_PROCESS_MEDIA_START = 5;
|
||||
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 TransformationRequest transformationRequest;
|
||||
private final ImmutableList<AudioProcessor> audioProcessors;
|
||||
@ -925,7 +926,7 @@ public final class Transformer {
|
||||
componentListener,
|
||||
/* initialTimestampOffsetUs= */ 0);
|
||||
} else {
|
||||
transcodeVideoBeforeFirstSyncSampleAfterTrimStartTime();
|
||||
processMediaBeforeFirstSyncSampleAfterTrimStartTime();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1139,7 +1140,7 @@ public final class Transformer {
|
||||
checkNotNull(outputFilePath),
|
||||
muxerFactory,
|
||||
componentListener,
|
||||
MuxerWrapper.MUXER_MODE_MUX_PARTIAL_VIDEO);
|
||||
MuxerWrapper.MUXER_MODE_MUX_PARTIAL);
|
||||
|
||||
startInternal(
|
||||
TransmuxTranscodeHelper.createVideoOnlyComposition(
|
||||
@ -1169,7 +1170,7 @@ public final class Transformer {
|
||||
resumeMetadata);
|
||||
|
||||
checkNotNull(remuxingMuxerWrapper);
|
||||
remuxingMuxerWrapper.changeToAppendVideoMode();
|
||||
remuxingMuxerWrapper.changeToAppendMode();
|
||||
|
||||
startInternal(
|
||||
videoOnlyComposition,
|
||||
@ -1218,11 +1219,12 @@ public final class Transformer {
|
||||
applicationHandler::post);
|
||||
}
|
||||
|
||||
private void transcodeVideoBeforeFirstSyncSampleAfterTrimStartTime() {
|
||||
transformerState = TRANSFORMER_STATE_TRANSCODE_VIDEO_START;
|
||||
private void processMediaBeforeFirstSyncSampleAfterTrimStartTime() {
|
||||
transformerState = TRANSFORMER_STATE_PROCESS_MEDIA_START;
|
||||
MediaItem firstMediaItem =
|
||||
checkNotNull(composition).sequences.get(0).editedMediaItems.get(0).mediaItem;
|
||||
long trimStartTimeUs = firstMediaItem.clippingConfiguration.startPositionUs;
|
||||
long trimEndTimeUs = firstMediaItem.clippingConfiguration.endPositionUs;
|
||||
getMp4MetadataInfoFuture =
|
||||
TransmuxTranscodeHelper.getMp4MetadataInfo(
|
||||
context,
|
||||
@ -1233,16 +1235,21 @@ public final class Transformer {
|
||||
new FutureCallback<Mp4MetadataInfo>() {
|
||||
@Override
|
||||
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);
|
||||
processFullInput();
|
||||
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 =
|
||||
buildNewCompositionWithClipTimes(
|
||||
composition,
|
||||
trimStartTimeUs,
|
||||
mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs,
|
||||
firstMediaItem.clippingConfiguration.endPositionUs,
|
||||
mp4MetadataInfo.durationUs,
|
||||
/* startsAtKeyFrame= */ true);
|
||||
@ -1266,7 +1273,7 @@ public final class Transformer {
|
||||
checkNotNull(outputFilePath),
|
||||
muxerFactory,
|
||||
componentListener,
|
||||
MuxerWrapper.MUXER_MODE_MUX_PARTIAL_VIDEO);
|
||||
MuxerWrapper.MUXER_MODE_MUX_PARTIAL);
|
||||
startInternal(
|
||||
trancodeComposition,
|
||||
remuxingMuxerWrapper,
|
||||
@ -1283,8 +1290,8 @@ public final class Transformer {
|
||||
applicationHandler::post);
|
||||
}
|
||||
|
||||
private void transmuxRemainingVideo() {
|
||||
transformerState = TRANSFORMER_STATE_TRANSMUX_REMAINING_VIDEO;
|
||||
private void remuxRemainingMedia() {
|
||||
transformerState = TRANSFORMER_STATE_REMUX_REMAINING_MEDIA;
|
||||
// TODO: b/304476154 - check original file format against transcode file format here to fail
|
||||
// fast if necessary.
|
||||
MediaItem firstMediaItem =
|
||||
@ -1300,7 +1307,7 @@ public final class Transformer {
|
||||
mp4MetadataInfo.durationUs,
|
||||
/* startsAtKeyFrame= */ true);
|
||||
checkNotNull(remuxingMuxerWrapper);
|
||||
remuxingMuxerWrapper.changeToAppendVideoMode();
|
||||
remuxingMuxerWrapper.changeToAppendMode();
|
||||
startInternal(
|
||||
transmuxComposition,
|
||||
remuxingMuxerWrapper,
|
||||
@ -1410,9 +1417,9 @@ public final class Transformer {
|
||||
processAudio();
|
||||
} else if (transformerState == TRANSFORMER_STATE_PROCESS_AUDIO) {
|
||||
copyOutput();
|
||||
} else if (transformerState == TRANSFORMER_STATE_TRANSCODE_VIDEO_START) {
|
||||
transmuxRemainingVideo();
|
||||
} else if (transformerState == TRANSFORMER_STATE_TRANSMUX_REMAINING_VIDEO) {
|
||||
} else if (transformerState == TRANSFORMER_STATE_PROCESS_MEDIA_START) {
|
||||
remuxRemainingMedia();
|
||||
} else if (transformerState == TRANSFORMER_STATE_REMUX_REMAINING_MEDIA) {
|
||||
exportResultBuilder.setOptimizationResult(ExportResult.OPTIMIZATION_SUCCEEDED);
|
||||
onExportCompletedWithSuccess();
|
||||
} else {
|
||||
|
@ -101,8 +101,6 @@ import java.util.List;
|
||||
.buildUpon()
|
||||
.setMediaItem(mediaItem)
|
||||
.setDurationUs(mediaDurationUs)
|
||||
// TODO: b/304476154 - Support audio in trim optimization.
|
||||
.setRemoveAudio(true)
|
||||
.build();
|
||||
|
||||
return oldComposition
|
||||
|
@ -196,14 +196,12 @@ public final class MediaItemExportTest {
|
||||
ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
|
||||
|
||||
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(
|
||||
context,
|
||||
muxerFactory.getCreatedMuxer(),
|
||||
getDumpFileName(
|
||||
/* originalFileName= */ FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S,
|
||||
/* modifications...= */ "trimOptimizedClippedAtKeyFrame"));
|
||||
/* modifications...= */ "clipped"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -17,7 +17,7 @@ package androidx.media3.transformer;
|
||||
|
||||
import static androidx.media3.common.MimeTypes.AUDIO_AAC;
|
||||
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 org.junit.Assert.assertThrows;
|
||||
|
||||
@ -66,7 +66,7 @@ public class MuxerWrapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeToAppendVideoMode_afterDefaultMode_throws() throws Exception {
|
||||
public void changeToAppendMode_afterDefaultMode_throws() throws Exception {
|
||||
muxerWrapper =
|
||||
new MuxerWrapper(
|
||||
temporaryFolder.newFile().getPath(),
|
||||
@ -74,86 +74,62 @@ public class MuxerWrapperTest {
|
||||
new NoOpMuxerListenerImpl(),
|
||||
MuxerWrapper.MUXER_MODE_DEFAULT);
|
||||
|
||||
assertThrows(IllegalStateException.class, muxerWrapper::changeToAppendVideoMode);
|
||||
assertThrows(IllegalStateException.class, muxerWrapper::changeToAppendMode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTrackCount_toTwoInMuxPartialVideoMode_throws() throws Exception {
|
||||
public void addTrackFormat_withSameVideoFormatInAppendMode_doesNotThrow() throws Exception {
|
||||
muxerWrapper =
|
||||
new MuxerWrapper(
|
||||
temporaryFolder.newFile().getPath(),
|
||||
new DefaultMuxer.Factory(),
|
||||
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.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();
|
||||
|
||||
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.changeToAppendMode();
|
||||
muxerWrapper.setTrackCount(1);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> muxerWrapper.addTrackFormat(FAKE_AUDIO_TRACK_FORMAT));
|
||||
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackFormat_withSameVideoFormatInAppendVideoMode_doesNotThrow() throws Exception {
|
||||
public void addTrackFormat_withSameAudioFormatInAppendMode_doesNotThrow() throws Exception {
|
||||
muxerWrapper =
|
||||
new MuxerWrapper(
|
||||
temporaryFolder.newFile().getPath(),
|
||||
new DefaultMuxer.Factory(),
|
||||
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.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.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.changeToAppendMode();
|
||||
muxerWrapper.setTrackCount(1);
|
||||
Format differentVideoFormat = FAKE_VIDEO_TRACK_FORMAT.buildUpon().setHeight(5000).build();
|
||||
|
||||
@ -161,6 +137,27 @@ public class MuxerWrapperTest {
|
||||
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
|
||||
public void isEnded_afterPartialVideoMuxed_returnsTrue() throws Exception {
|
||||
muxerWrapper =
|
||||
@ -168,7 +165,7 @@ public class MuxerWrapperTest {
|
||||
temporaryFolder.newFile().getPath(),
|
||||
new DefaultMuxer.Factory(),
|
||||
new NoOpMuxerListenerImpl(),
|
||||
MUXER_MODE_MUX_PARTIAL_VIDEO);
|
||||
MUXER_MODE_MUX_PARTIAL);
|
||||
muxerWrapper.setTrackCount(1);
|
||||
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
|
||||
muxerWrapper.writeSample(
|
||||
@ -179,19 +176,44 @@ public class MuxerWrapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEnded_afterStartingAppendVideo_returnsFalse() throws Exception {
|
||||
public void isEnded_afterPartialAudioAndVideoMuxed_returnsTrue() throws Exception {
|
||||
muxerWrapper =
|
||||
new MuxerWrapper(
|
||||
temporaryFolder.newFile().getPath(),
|
||||
new DefaultMuxer.Factory(),
|
||||
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.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.changeToAppendMode();
|
||||
muxerWrapper.setTrackCount(1);
|
||||
muxerWrapper.addTrackFormat(FAKE_VIDEO_TRACK_FORMAT);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user