Add rough progress updates in trim optimization
PiperOrigin-RevId: 593116025
This commit is contained in:
parent
1632f37d70
commit
7e12b9e15f
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Util.isRunningOnEmulator;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assume.assumeFalse;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.util.NullableType;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.effect.DebugTraceUtil;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TestName;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End-to-end instrumentation test for {@link Transformer#getProgress} when {@link
|
||||||
|
* Transformer.Builder#experimentalSetTrimOptimizationEnabled} is enabled.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TransformerTrimOptimizationProgressTest {
|
||||||
|
@Rule public final TestName testName = new TestName();
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
private @MonotonicNonNull String testId;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@EnsuresNonNull({"testId"})
|
||||||
|
public void setUp() {
|
||||||
|
testId = testName.getMethodName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresNonNull("testId")
|
||||||
|
public void getProgress_trimOptimizationEnabledAndApplied_givesIncreasingPercentages()
|
||||||
|
throws Exception {
|
||||||
|
// The trim optimization is only guaranteed to work on emulator for this file.
|
||||||
|
assumeTrue(isRunningOnEmulator());
|
||||||
|
// MediaCodec returns a segmentation fault fails at this SDK level on emulators.
|
||||||
|
assumeFalse(Util.SDK_INT == 26);
|
||||||
|
Transformer transformer =
|
||||||
|
new Transformer.Builder(context).experimentalSetTrimOptimizationEnabled(true).build();
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("asset:///media/mp4/internal_emulator_transformer_output.mp4")
|
||||||
|
.setClippingConfiguration(
|
||||||
|
new MediaItem.ClippingConfiguration.Builder()
|
||||||
|
.setStartPositionMs(500)
|
||||||
|
.setEndPositionMs(2500)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
// Written to on main sync, read on test thread.
|
||||||
|
Queue<Integer> progresses = new ConcurrentLinkedDeque<>();
|
||||||
|
SettableFuture<@NullableType Exception> transformerExceptionFuture = SettableFuture.create();
|
||||||
|
DebugTraceUtil.enableTracing = true;
|
||||||
|
// Created on test thread, only used on main sync.
|
||||||
|
Transformer testTransformer =
|
||||||
|
transformer
|
||||||
|
.buildUpon()
|
||||||
|
.addListener(
|
||||||
|
new Transformer.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onCompleted(Composition composition, ExportResult exportResult) {
|
||||||
|
transformerExceptionFuture.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(
|
||||||
|
Composition composition,
|
||||||
|
ExportResult exportResult,
|
||||||
|
ExportException exportException) {
|
||||||
|
transformerExceptionFuture.set(exportException);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
File outputVideoFile =
|
||||||
|
AndroidTestUtil.createExternalCacheFile(context, /* fileName= */ testId + "-output.mp4");
|
||||||
|
|
||||||
|
InstrumentationRegistry.getInstrumentation()
|
||||||
|
.runOnMainSync(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
testTransformer.start(mediaItem, outputVideoFile.getAbsolutePath());
|
||||||
|
// Catch all exceptions to report. Exceptions thrown here that are not caught will
|
||||||
|
// NOT propagate.
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
transformerExceptionFuture.set(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while (!transformerExceptionFuture.isDone()) {
|
||||||
|
InstrumentationRegistry.getInstrumentation()
|
||||||
|
.runOnMainSync(
|
||||||
|
() -> {
|
||||||
|
ProgressHolder progressHolder = new ProgressHolder();
|
||||||
|
if (testTransformer.getProgress(progressHolder) == PROGRESS_STATE_AVAILABLE
|
||||||
|
&& (progresses.isEmpty()
|
||||||
|
|| Iterables.getLast(progresses) != progressHolder.progress)) {
|
||||||
|
progresses.add(progressHolder.progress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Thread.sleep(/* millis= */ 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(transformerExceptionFuture.get()).isNull();
|
||||||
|
assertThat(progresses).isInOrder();
|
||||||
|
if (!progresses.isEmpty()) {
|
||||||
|
// The progress list could be empty if the export ends before any progress can be retrieved.
|
||||||
|
assertThat(Iterables.getFirst(progresses, /* defaultValue= */ -1)).isAtLeast(0);
|
||||||
|
assertThat(Iterables.getLast(progresses)).isAtMost(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresNonNull("testId")
|
||||||
|
public void getProgress_trimOptimizationEnabledAndActive_returnsConsistentStates()
|
||||||
|
throws Exception {
|
||||||
|
// The trim optimization is only guaranteed to work on emulator for this file.
|
||||||
|
assumeTrue(isRunningOnEmulator());
|
||||||
|
// MediaCodec returns a segmentation fault fails at this SDK level on emulators.
|
||||||
|
assumeFalse(Util.SDK_INT == 26);
|
||||||
|
Transformer transformer =
|
||||||
|
new Transformer.Builder(context).experimentalSetTrimOptimizationEnabled(true).build();
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri("asset:///media/mp4/internal_emulator_transformer_output.mp4")
|
||||||
|
.setClippingConfiguration(
|
||||||
|
new MediaItem.ClippingConfiguration.Builder()
|
||||||
|
.setStartPositionMs(500)
|
||||||
|
.setEndPositionMs(2500)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
AtomicInteger previousProgressState =
|
||||||
|
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
||||||
|
AtomicBoolean foundInconsistentState = new AtomicBoolean();
|
||||||
|
SettableFuture<@NullableType Exception> transformerExceptionFuture = SettableFuture.create();
|
||||||
|
DebugTraceUtil.enableTracing = true;
|
||||||
|
// Created on test thread, only used on main sync.
|
||||||
|
Transformer testTransformer =
|
||||||
|
transformer
|
||||||
|
.buildUpon()
|
||||||
|
.addListener(
|
||||||
|
new Transformer.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onCompleted(Composition composition, ExportResult exportResult) {
|
||||||
|
transformerExceptionFuture.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(
|
||||||
|
Composition composition,
|
||||||
|
ExportResult exportResult,
|
||||||
|
ExportException exportException) {
|
||||||
|
transformerExceptionFuture.set(exportException);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
File outputVideoFile =
|
||||||
|
AndroidTestUtil.createExternalCacheFile(context, /* fileName= */ testId + "-output.mp4");
|
||||||
|
|
||||||
|
InstrumentationRegistry.getInstrumentation()
|
||||||
|
.runOnMainSync(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
testTransformer.start(mediaItem, outputVideoFile.getAbsolutePath());
|
||||||
|
// Catch all exceptions to report. Exceptions thrown here that are not caught will
|
||||||
|
// NOT propagate.
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
transformerExceptionFuture.set(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while (!transformerExceptionFuture.isDone()) {
|
||||||
|
InstrumentationRegistry.getInstrumentation()
|
||||||
|
.runOnMainSync(
|
||||||
|
() -> {
|
||||||
|
@Transformer.ProgressState
|
||||||
|
int progressState = transformer.getProgress(new ProgressHolder());
|
||||||
|
if (progressState == PROGRESS_STATE_UNAVAILABLE) {
|
||||||
|
foundInconsistentState.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (previousProgressState.get()) {
|
||||||
|
case PROGRESS_STATE_WAITING_FOR_AVAILABILITY:
|
||||||
|
break;
|
||||||
|
case PROGRESS_STATE_AVAILABLE:
|
||||||
|
if (progressState == PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||||
|
foundInconsistentState.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PROGRESS_STATE_NOT_STARTED:
|
||||||
|
if (progressState != PROGRESS_STATE_NOT_STARTED) {
|
||||||
|
foundInconsistentState.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
previousProgressState.set(progressState);
|
||||||
|
});
|
||||||
|
Thread.sleep(/* millis= */ 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(transformerExceptionFuture.get()).isNull();
|
||||||
|
assertThat(foundInconsistentState.get()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,7 @@ import static androidx.media3.transformer.ExportResult.OPTIMIZATION_FAILED_FORMA
|
|||||||
import static androidx.media3.transformer.TransformerUtil.shouldTranscodeAudio;
|
import static androidx.media3.transformer.TransformerUtil.shouldTranscodeAudio;
|
||||||
import static androidx.media3.transformer.TransformerUtil.shouldTranscodeVideo;
|
import static androidx.media3.transformer.TransformerUtil.shouldTranscodeVideo;
|
||||||
import static androidx.media3.transformer.TransmuxTranscodeHelper.buildNewCompositionWithClipTimes;
|
import static androidx.media3.transformer.TransmuxTranscodeHelper.buildNewCompositionWithClipTimes;
|
||||||
|
import static java.lang.Math.round;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -294,7 +295,6 @@ public final class Transformer {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@ -305,7 +305,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>Progress updates will be unavailable.
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>This process relies on the given {@linkplain #setEncoderFactory EncoderFactory} providing
|
* <p>This process relies on the given {@linkplain #setEncoderFactory EncoderFactory} providing
|
||||||
@ -781,7 +780,7 @@ public final class Transformer {
|
|||||||
getResumeMetadataFuture;
|
getResumeMetadataFuture;
|
||||||
private @MonotonicNonNull ListenableFuture<Void> copyOutputFuture;
|
private @MonotonicNonNull ListenableFuture<Void> copyOutputFuture;
|
||||||
private @MonotonicNonNull ListenableFuture<Mp4MetadataInfo> getMp4MetadataInfoFuture;
|
private @MonotonicNonNull ListenableFuture<Mp4MetadataInfo> getMp4MetadataInfoFuture;
|
||||||
private @MonotonicNonNull Mp4MetadataInfo mp4MetadataInfo;
|
@Nullable private Mp4MetadataInfo mediaItemMetadataInfo;
|
||||||
|
|
||||||
private Transformer(
|
private Transformer(
|
||||||
Context context,
|
Context context,
|
||||||
@ -821,6 +820,7 @@ public final class Transformer {
|
|||||||
this.looper = looper;
|
this.looper = looper;
|
||||||
this.debugViewProvider = debugViewProvider;
|
this.debugViewProvider = debugViewProvider;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
|
transformerState = TRANSFORMER_STATE_PROCESS_FULL_INPUT;
|
||||||
applicationHandler = clock.createHandler(looper, /* callback= */ null);
|
applicationHandler = clock.createHandler(looper, /* callback= */ null);
|
||||||
componentListener = new ComponentListener();
|
componentListener = new ComponentListener();
|
||||||
exportResultBuilder = new ExportResult.Builder();
|
exportResultBuilder = new ExportResult.Builder();
|
||||||
@ -1069,13 +1069,66 @@ public final class Transformer {
|
|||||||
*/
|
*/
|
||||||
public @ProgressState int getProgress(ProgressHolder progressHolder) {
|
public @ProgressState int getProgress(ProgressHolder progressHolder) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
if (transformerState != TRANSFORMER_STATE_PROCESS_FULL_INPUT) {
|
if (transformerState == TRANSFORMER_STATE_PROCESS_FULL_INPUT) {
|
||||||
return PROGRESS_STATE_UNAVAILABLE;
|
|
||||||
}
|
|
||||||
return transformerInternal == null
|
return transformerInternal == null
|
||||||
? PROGRESS_STATE_NOT_STARTED
|
? PROGRESS_STATE_NOT_STARTED
|
||||||
: transformerInternal.getProgress(progressHolder);
|
: transformerInternal.getProgress(progressHolder);
|
||||||
}
|
}
|
||||||
|
if (isExportResumed()) {
|
||||||
|
return PROGRESS_STATE_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getTrimOptimizationProgress(progressHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExportResumed() {
|
||||||
|
return transformerState == TRANSFORMER_STATE_REMUX_PROCESSED_VIDEO
|
||||||
|
|| transformerState == TRANSFORMER_STATE_PROCESS_REMAINING_VIDEO
|
||||||
|
|| transformerState == TRANSFORMER_STATE_PROCESS_AUDIO
|
||||||
|
|| transformerState == TRANSFORMER_STATE_COPY_OUTPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @ProgressState int getTrimOptimizationProgress(ProgressHolder progressHolder) {
|
||||||
|
if (mediaItemMetadataInfo == null) {
|
||||||
|
return PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||||
|
}
|
||||||
|
MediaItem firstMediaItem =
|
||||||
|
checkNotNull(composition).sequences.get(0).editedMediaItems.get(0).mediaItem;
|
||||||
|
long trimStartTimeUs = firstMediaItem.clippingConfiguration.startPositionUs;
|
||||||
|
long transcodeDuration =
|
||||||
|
mediaItemMetadataInfo.firstSyncSampleTimestampUsAfterTimeUs - trimStartTimeUs;
|
||||||
|
float transcodeWeighting = (float) transcodeDuration / mediaItemMetadataInfo.durationUs;
|
||||||
|
|
||||||
|
if (transformerState == TRANSFORMER_STATE_PROCESS_MEDIA_START) {
|
||||||
|
if (transformerInternal == null) {
|
||||||
|
return PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
|
||||||
|
}
|
||||||
|
@ProgressState
|
||||||
|
int processMediaStartProgressState = transformerInternal.getProgress(progressHolder);
|
||||||
|
if (processMediaStartProgressState == PROGRESS_STATE_AVAILABLE) {
|
||||||
|
progressHolder.progress = round(progressHolder.progress * transcodeWeighting);
|
||||||
|
}
|
||||||
|
return processMediaStartProgressState;
|
||||||
|
}
|
||||||
|
|
||||||
|
float fullTranscodeProgress = 100 * transcodeWeighting;
|
||||||
|
if (transformerInternal == null) {
|
||||||
|
// Transformer has not started remuxing the remaining media yet.
|
||||||
|
progressHolder.progress = round(fullTranscodeProgress);
|
||||||
|
return PROGRESS_STATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
@ProgressState
|
||||||
|
int remuxRemainingMediaProgressState = transformerInternal.getProgress(progressHolder);
|
||||||
|
if (remuxRemainingMediaProgressState == PROGRESS_STATE_NOT_STARTED
|
||||||
|
|| remuxRemainingMediaProgressState == PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||||
|
progressHolder.progress = round(fullTranscodeProgress);
|
||||||
|
return PROGRESS_STATE_AVAILABLE;
|
||||||
|
} else if (remuxRemainingMediaProgressState == PROGRESS_STATE_AVAILABLE) {
|
||||||
|
progressHolder.progress =
|
||||||
|
round(fullTranscodeProgress + (1 - transcodeWeighting) * progressHolder.progress);
|
||||||
|
}
|
||||||
|
return remuxRemainingMediaProgressState;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels the export that is currently in progress, if any.
|
* Cancels the export that is currently in progress, if any.
|
||||||
@ -1329,7 +1382,7 @@ public final class Transformer {
|
|||||||
processFullInput();
|
processFullInput();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Transformer.this.mp4MetadataInfo = mp4MetadataInfo;
|
Transformer.this.mediaItemMetadataInfo = mp4MetadataInfo;
|
||||||
Composition trancodeComposition =
|
Composition trancodeComposition =
|
||||||
buildNewCompositionWithClipTimes(
|
buildNewCompositionWithClipTimes(
|
||||||
composition,
|
composition,
|
||||||
@ -1355,6 +1408,7 @@ public final class Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void remuxRemainingMedia() {
|
private void remuxRemainingMedia() {
|
||||||
|
Mp4MetadataInfo mediaItemMetadataInfo = checkNotNull(this.mediaItemMetadataInfo);
|
||||||
transformerState = TRANSFORMER_STATE_REMUX_REMAINING_MEDIA;
|
transformerState = TRANSFORMER_STATE_REMUX_REMAINING_MEDIA;
|
||||||
if (!doesFormatsMatch()) {
|
if (!doesFormatsMatch()) {
|
||||||
remuxingMuxerWrapper = null;
|
remuxingMuxerWrapper = null;
|
||||||
@ -1367,13 +1421,12 @@ public final class Transformer {
|
|||||||
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;
|
long trimEndTimeUs = firstMediaItem.clippingConfiguration.endPositionUs;
|
||||||
checkNotNull(mp4MetadataInfo);
|
|
||||||
Composition transmuxComposition =
|
Composition transmuxComposition =
|
||||||
buildNewCompositionWithClipTimes(
|
buildNewCompositionWithClipTimes(
|
||||||
composition,
|
composition,
|
||||||
mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs,
|
mediaItemMetadataInfo.firstSyncSampleTimestampUsAfterTimeUs,
|
||||||
trimEndTimeUs,
|
trimEndTimeUs,
|
||||||
mp4MetadataInfo.durationUs,
|
mediaItemMetadataInfo.durationUs,
|
||||||
/* startsAtKeyFrame= */ true);
|
/* startsAtKeyFrame= */ true);
|
||||||
checkNotNull(remuxingMuxerWrapper);
|
checkNotNull(remuxingMuxerWrapper);
|
||||||
remuxingMuxerWrapper.changeToAppendMode();
|
remuxingMuxerWrapper.changeToAppendMode();
|
||||||
@ -1381,19 +1434,19 @@ public final class Transformer {
|
|||||||
transmuxComposition,
|
transmuxComposition,
|
||||||
remuxingMuxerWrapper,
|
remuxingMuxerWrapper,
|
||||||
componentListener,
|
componentListener,
|
||||||
/* initialTimestampOffsetUs= */ mp4MetadataInfo.firstSyncSampleTimestampUsAfterTimeUs
|
/* initialTimestampOffsetUs= */ mediaItemMetadataInfo.firstSyncSampleTimestampUsAfterTimeUs
|
||||||
- trimStartTimeUs);
|
- trimStartTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doesFormatsMatch() {
|
private boolean doesFormatsMatch() {
|
||||||
checkNotNull(mp4MetadataInfo);
|
Mp4MetadataInfo mediaItemMetadataInfo = checkNotNull(this.mediaItemMetadataInfo);
|
||||||
boolean videoFormatMatches =
|
boolean videoFormatMatches =
|
||||||
checkNotNull(remuxingMuxerWrapper)
|
checkNotNull(remuxingMuxerWrapper)
|
||||||
.getTrackFormat(C.TRACK_TYPE_VIDEO)
|
.getTrackFormat(C.TRACK_TYPE_VIDEO)
|
||||||
.initializationDataEquals(checkNotNull(mp4MetadataInfo.videoFormat));
|
.initializationDataEquals(checkNotNull(mediaItemMetadataInfo.videoFormat));
|
||||||
boolean audioFormatMatches =
|
boolean audioFormatMatches =
|
||||||
mp4MetadataInfo.audioFormat == null
|
mediaItemMetadataInfo.audioFormat == null
|
||||||
|| mp4MetadataInfo.audioFormat.initializationDataEquals(
|
|| mediaItemMetadataInfo.audioFormat.initializationDataEquals(
|
||||||
checkNotNull(remuxingMuxerWrapper).getTrackFormat(C.TRACK_TYPE_AUDIO));
|
checkNotNull(remuxingMuxerWrapper).getTrackFormat(C.TRACK_TYPE_AUDIO));
|
||||||
return videoFormatMatches && audioFormatMatches;
|
return videoFormatMatches && audioFormatMatches;
|
||||||
}
|
}
|
||||||
@ -1501,6 +1554,8 @@ public final class Transformer {
|
|||||||
} else if (transformerState == TRANSFORMER_STATE_PROCESS_MEDIA_START) {
|
} else if (transformerState == TRANSFORMER_STATE_PROCESS_MEDIA_START) {
|
||||||
remuxRemainingMedia();
|
remuxRemainingMedia();
|
||||||
} else if (transformerState == TRANSFORMER_STATE_REMUX_REMAINING_MEDIA) {
|
} else if (transformerState == TRANSFORMER_STATE_REMUX_REMAINING_MEDIA) {
|
||||||
|
transformerState = TRANSFORMER_STATE_PROCESS_FULL_INPUT;
|
||||||
|
mediaItemMetadataInfo = null;
|
||||||
exportResultBuilder.setOptimizationResult(ExportResult.OPTIMIZATION_SUCCEEDED);
|
exportResultBuilder.setOptimizationResult(ExportResult.OPTIMIZATION_SUCCEEDED);
|
||||||
onExportCompletedWithSuccess();
|
onExportCompletedWithSuccess();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1065,6 +1065,51 @@ public final class MediaItemExportTest {
|
|||||||
/* originalFileName= */ FILE_AUDIO_VIDEO, /* modifications...= */ "rotated"));
|
/* originalFileName= */ FILE_AUDIO_VIDEO, /* modifications...= */ "rotated"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getProgress_unknownDuration_returnsConsistentStates() throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
createTransformerBuilder(muxerFactory, /* enableFallback= */ false).build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION);
|
||||||
|
AtomicInteger previousProgressState =
|
||||||
|
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
||||||
|
AtomicBoolean foundInconsistentState = new AtomicBoolean();
|
||||||
|
Handler progressHandler =
|
||||||
|
new Handler(Looper.myLooper()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
@Transformer.ProgressState
|
||||||
|
int progressState = transformer.getProgress(new ProgressHolder());
|
||||||
|
switch (previousProgressState.get()) {
|
||||||
|
case PROGRESS_STATE_WAITING_FOR_AVAILABILITY:
|
||||||
|
break;
|
||||||
|
case PROGRESS_STATE_UNAVAILABLE:
|
||||||
|
case PROGRESS_STATE_AVAILABLE: // See [Internal: b/176145097].
|
||||||
|
if (progressState == PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||||
|
foundInconsistentState.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PROGRESS_STATE_NOT_STARTED:
|
||||||
|
if (progressState != PROGRESS_STATE_NOT_STARTED) {
|
||||||
|
foundInconsistentState.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
previousProgressState.set(progressState);
|
||||||
|
sendEmptyMessage(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
transformer.start(mediaItem, outputDir.newFile().getPath());
|
||||||
|
progressHandler.sendEmptyMessage(0);
|
||||||
|
TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
|
assertThat(foundInconsistentState.get()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
|
public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
@ -1165,10 +1210,14 @@ public final class MediaItemExportTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getProgress_unknownDuration_returnsConsistentStates() throws Exception {
|
public void
|
||||||
|
getProgress_trimOptimizationEnabledButNotApplied_withClippingConfigurationUnset_returnsConsistentStates()
|
||||||
|
throws Exception {
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
createTransformerBuilder(muxerFactory, /* enableFallback= */ false).build();
|
createTransformerBuilder(muxerFactory, /* enableFallback= */ false)
|
||||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_UNKNOWN_DURATION);
|
.experimentalSetTrimOptimizationEnabled(true)
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||||
AtomicInteger previousProgressState =
|
AtomicInteger previousProgressState =
|
||||||
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
new AtomicInteger(PROGRESS_STATE_WAITING_FOR_AVAILABILITY);
|
||||||
AtomicBoolean foundInconsistentState = new AtomicBoolean();
|
AtomicBoolean foundInconsistentState = new AtomicBoolean();
|
||||||
@ -1178,11 +1227,14 @@ public final class MediaItemExportTest {
|
|||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
@Transformer.ProgressState
|
@Transformer.ProgressState
|
||||||
int progressState = transformer.getProgress(new ProgressHolder());
|
int progressState = transformer.getProgress(new ProgressHolder());
|
||||||
|
if (progressState == PROGRESS_STATE_UNAVAILABLE) {
|
||||||
|
foundInconsistentState.set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (previousProgressState.get()) {
|
switch (previousProgressState.get()) {
|
||||||
case PROGRESS_STATE_WAITING_FOR_AVAILABILITY:
|
case PROGRESS_STATE_WAITING_FOR_AVAILABILITY:
|
||||||
break;
|
break;
|
||||||
case PROGRESS_STATE_UNAVAILABLE:
|
case PROGRESS_STATE_AVAILABLE:
|
||||||
case PROGRESS_STATE_AVAILABLE: // See [Internal: b/176145097].
|
|
||||||
if (progressState == PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
if (progressState == PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
|
||||||
foundInconsistentState.set(true);
|
foundInconsistentState.set(true);
|
||||||
return;
|
return;
|
||||||
@ -1198,17 +1250,76 @@ public final class MediaItemExportTest {
|
|||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
previousProgressState.set(progressState);
|
previousProgressState.set(progressState);
|
||||||
sendEmptyMessage(0);
|
sendEmptyMessageDelayed(/* what= */ 0, /* delayMillis= */ 50);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
transformer.start(mediaItem, outputDir.newFile().getPath());
|
transformer.start(mediaItem, outputDir.newFile().getPath());
|
||||||
progressHandler.sendEmptyMessage(0);
|
progressHandler.sendEmptyMessage(/* what= */ 0);
|
||||||
TransformerTestRunner.runLooper(transformer);
|
TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
assertThat(foundInconsistentState.get()).isFalse();
|
assertThat(foundInconsistentState.get()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getProgress_trimOptimizationEnabledButNotApplied_withClippingConfigurationUnset_givesIncreasingPercentages()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
createTransformerBuilder(muxerFactory, /* enableFallback= */ false)
|
||||||
|
.experimentalSetTrimOptimizationEnabled(true)
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||||
|
List<Integer> progresses = new ArrayList<>();
|
||||||
|
Handler progressHandler =
|
||||||
|
new Handler(Looper.myLooper()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
ProgressHolder progressHolder = new ProgressHolder();
|
||||||
|
@Transformer.ProgressState int progressState = transformer.getProgress(progressHolder);
|
||||||
|
if (progressState == PROGRESS_STATE_NOT_STARTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (progressState == PROGRESS_STATE_AVAILABLE
|
||||||
|
&& (progresses.isEmpty()
|
||||||
|
|| Iterables.getLast(progresses) != progressHolder.progress)) {
|
||||||
|
progresses.add(progressHolder.progress);
|
||||||
|
}
|
||||||
|
sendEmptyMessageDelayed(/* what= */ 0, /* delayMillis= */ 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
transformer.start(mediaItem, outputDir.newFile().getPath());
|
||||||
|
progressHandler.sendEmptyMessage(/* what= */ 0);
|
||||||
|
TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
|
assertThat(progresses).isInOrder();
|
||||||
|
if (!progresses.isEmpty()) {
|
||||||
|
// The progress list could be empty if the export ends before any progress can be retrieved.
|
||||||
|
assertThat(progresses.get(0)).isAtLeast(0);
|
||||||
|
assertThat(Iterables.getLast(progresses)).isAtMost(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getProgress_trimOptimizationEnabledButNotApplied_withClippingConfigurationUnset_noCurrentExport_returnsNotStarted()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
createTransformerBuilder(muxerFactory, /* enableFallback= */ false)
|
||||||
|
.experimentalSetTrimOptimizationEnabled(true)
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY);
|
||||||
|
ProgressHolder progressHolder = new ProgressHolder();
|
||||||
|
@Transformer.ProgressState int stateBeforeTransform = transformer.getProgress(progressHolder);
|
||||||
|
transformer.start(mediaItem, outputDir.newFile().getPath());
|
||||||
|
TransformerTestRunner.runLooper(transformer);
|
||||||
|
@Transformer.ProgressState int stateAfterTransform = transformer.getProgress(progressHolder);
|
||||||
|
|
||||||
|
assertThat(stateBeforeTransform).isEqualTo(PROGRESS_STATE_NOT_STARTED);
|
||||||
|
assertThat(stateAfterTransform).isEqualTo(PROGRESS_STATE_NOT_STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getProgress_fromWrongThread_throwsError() throws Exception {
|
public void getProgress_fromWrongThread_throwsError() throws Exception {
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user