Transformer: add speedChange API

PiperOrigin-RevId: 616163061
This commit is contained in:
tofunmi 2024-03-15 09:59:48 -07:00 committed by Copybara-Service
parent 762065fae0
commit fe640da5e8
2 changed files with 66 additions and 0 deletions

View File

@ -52,6 +52,7 @@ import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.util.Pair;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.Format; import androidx.media3.common.Format;
@ -64,6 +65,7 @@ import androidx.media3.common.audio.AudioProcessor.AudioFormat;
import androidx.media3.common.audio.ChannelMixingAudioProcessor; 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.audio.SpeedProvider;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSourceBitmapLoader; import androidx.media3.datasource.DataSourceBitmapLoader;
@ -81,6 +83,7 @@ import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory; import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.test.utils.FakeExtractorOutput; import androidx.media3.test.utils.FakeExtractorOutput;
import androidx.media3.test.utils.FakeTrackOutput; import androidx.media3.test.utils.FakeTrackOutput;
import androidx.media3.test.utils.TestSpeedProvider;
import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.TestUtil;
import androidx.media3.transformer.AssetLoader.CompositionSettings; import androidx.media3.transformer.AssetLoader.CompositionSettings;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@ -868,6 +871,40 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED); assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
} }
@Test
public void speedAdjustedMedia_completesWithCorrectDuration() throws Exception {
Transformer transformer = new Transformer.Builder(context).build();
SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes(
new long[] {
0L,
3 * C.MICROS_PER_SECOND,
6 * C.MICROS_PER_SECOND,
9 * C.MICROS_PER_SECOND,
12 * C.MICROS_PER_SECOND
},
new float[] {0.5f, 0.75f, 1f, 1.5f, 2f});
Pair<AudioProcessor, Effect> speedEffect =
Effects.createExperimentalSpeedChangingEffect(speedProvider);
Effects effects =
new Effects(
/* audioProcessors= */ ImmutableList.of(speedEffect.first),
/* videoEffects= */ ImmutableList.of(speedEffect.second));
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(
MediaItem.fromUri(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S_URI_STRING))
.setEffects(effects)
.build();
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
// The input video is 15.537 seconds.
// 3 / 0.5 + 3 / 0.75 + 3 + 3 / 1.5 + 3.537 / 2 rounds up to 16_770
assertThat(result.exportResult.durationMs).isAtMost(16_770);
}
@Test @Test
public void videoEncoderFormatUnsupported_completesWithError() throws Exception { public void videoEncoderFormatUnsupported_completesWithError() throws Exception {
if (AndroidTestUtil.skipAndLogIfFormatsUnsupported( if (AndroidTestUtil.skipAndLogIfFormatsUnsupported(

View File

@ -15,10 +15,15 @@
*/ */
package androidx.media3.transformer; package androidx.media3.transformer;
import android.util.Pair;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.audio.AudioProcessor;
import androidx.media3.common.audio.SpeedChangingAudioProcessor;
import androidx.media3.common.audio.SpeedProvider;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.effect.SpeedChangeEffect;
import androidx.media3.effect.TimestampAdjustment;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
@ -54,4 +59,28 @@ public final class Effects {
this.audioProcessors = ImmutableList.copyOf(audioProcessors); this.audioProcessors = ImmutableList.copyOf(audioProcessors);
this.videoEffects = ImmutableList.copyOf(videoEffects); this.videoEffects = ImmutableList.copyOf(videoEffects);
} }
/**
* Creates an interlinked {@linkplain AudioProcessor audio processor} and {@linkplain Effect video
* effect} that changes the speed to media samples in segments of the input file specified by the
* given {@link SpeedProvider}.
*
* <p>The {@linkplain AudioProcessor audio processor} and {@linkplain Effect video effect} are
* interlinked to help maintain A/V sync. When using Transformer, if the input file doesn't have
* audio, or audio is being removed, you may have to {@linkplain
* Composition.Builder#experimentalSetForceAudioTrack force an audio track} for the interlinked
* effects to function correctly. Alternatively, you can use {@link SpeedChangeEffect} when input
* has no audio.
*
* @param speedProvider The {@link SpeedProvider} determining the speed for the media at specific
* timestamps.
*/
public static Pair<AudioProcessor, Effect> createExperimentalSpeedChangingEffect(
SpeedProvider speedProvider) {
SpeedChangingAudioProcessor speedChangingAudioProcessor =
new SpeedChangingAudioProcessor(speedProvider);
Effect audioDrivenvideoEffect =
new TimestampAdjustment(speedChangingAudioProcessor::getSpeedAdjustedTimeAsync);
return Pair.create(speedChangingAudioProcessor, audioDrivenvideoEffect);
}
} }