Allow RawAssetLoader media to have unset duration

This will be useful for recording use cases, including screen recording with
audio queued via `RawAssetLoader`, where the duration is unknown.

PiperOrigin-RevId: 712895577
This commit is contained in:
andrewlewis 2025-01-07 06:54:35 -08:00 committed by Copybara-Service
parent 871381288c
commit e11aabb794
3 changed files with 61 additions and 6 deletions

View File

@ -101,8 +101,36 @@ public class RawAssetLoaderAndroidTest {
// See b/324245196. // See b/324245196.
// Audio encoders on different API versions seems to output slightly different durations, so add // Audio encoders on different API versions seems to output slightly different durations, so add
// 50ms tolerance. // 50ms tolerance.
assertThat(exportResult.durationMs).isAtLeast(975); assertThat(exportResult.durationMs).isWithin(25).of(1000);
assertThat(exportResult.durationMs).isAtMost(1025); }
@Test
public void audioTranscoding_withRawAudioAndUnsetDuration_completesWithCorrectDuration()
throws Exception {
SettableFuture<RawAssetLoader> rawAssetLoaderFuture = SettableFuture.create();
Transformer transformer =
new Transformer.Builder(context)
.setAssetLoaderFactory(
new TestRawAssetLoaderFactory(
AUDIO_FORMAT, /* videoFormat= */ null, rawAssetLoaderFuture))
.build();
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(Uri.EMPTY)).build();
ListenableFuture<ExportResult> exportCompletionFuture =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.runAsync(testId, editedMediaItem);
RawAssetLoader rawAssetLoader = rawAssetLoaderFuture.get();
feedRawAudioDataToAssetLoader(
rawAssetLoader, AUDIO_FORMAT, /* durationUs= */ C.MICROS_PER_SECOND);
ExportResult exportResult = exportCompletionFuture.get();
// The durationMs is the timestamp of the last sample and not the total duration.
// See b/324245196.
// Audio encoders on different API versions seems to output slightly different durations, so add
// 50ms tolerance.
assertThat(exportResult.durationMs).isWithin(25).of(1000);
} }
@Test @Test
@ -242,8 +270,7 @@ public class RawAssetLoaderAndroidTest {
// See b/324245196. // See b/324245196.
// Audio encoders on different API versions seems to output slightly different durations, so add // Audio encoders on different API versions seems to output slightly different durations, so add
// 50ms tolerance. // 50ms tolerance.
assertThat(exportResult.durationMs).isAtLeast(975); assertThat(exportResult.durationMs).isWithin(25).of(1000);
assertThat(exportResult.durationMs).isAtMost(1025);
} }
private void feedRawAudioDataToAssetLoader( private void feedRawAudioDataToAssetLoader(

View File

@ -23,6 +23,7 @@ import static androidx.media3.transformer.SampleConsumer.INPUT_RESULT_END_OF_STR
import static androidx.media3.transformer.SampleConsumer.INPUT_RESULT_TRY_AGAIN_LATER; import static androidx.media3.transformer.SampleConsumer.INPUT_RESULT_TRY_AGAIN_LATER;
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE; 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_NOT_STARTED;
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
import static androidx.media3.transformer.TransformerUtil.getValidColor; import static androidx.media3.transformer.TransformerUtil.getValidColor;
import static java.lang.Math.min; import static java.lang.Math.min;
import static java.lang.Math.round; import static java.lang.Math.round;
@ -96,7 +97,6 @@ public final class RawAssetLoader implements AssetLoader {
@Nullable Format videoFormat, @Nullable Format videoFormat,
@Nullable OnInputFrameProcessedListener frameProcessedListener) { @Nullable OnInputFrameProcessedListener frameProcessedListener) {
checkArgument(audioFormat != null || videoFormat != null); checkArgument(audioFormat != null || videoFormat != null);
checkArgument(editedMediaItem.durationUs != C.TIME_UNSET);
checkArgument( checkArgument(
videoFormat == null videoFormat == null
|| (videoFormat.height != Format.NO_VALUE && videoFormat.width != Format.NO_VALUE)); || (videoFormat.height != Format.NO_VALUE && videoFormat.width != Format.NO_VALUE));
@ -119,7 +119,10 @@ public final class RawAssetLoader implements AssetLoader {
@Override @Override
public void start() { public void start() {
progressState = PROGRESS_STATE_AVAILABLE; progressState =
editedMediaItem.durationUs == C.TIME_UNSET
? PROGRESS_STATE_UNAVAILABLE
: PROGRESS_STATE_AVAILABLE;
assetLoaderListener.onDurationUs(editedMediaItem.durationUs); assetLoaderListener.onDurationUs(editedMediaItem.durationUs);
// The constructor guarantees at least one track is present. // The constructor guarantees at least one track is present.
int trackCount = 1; int trackCount = 1;

View File

@ -16,6 +16,7 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE; import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE;
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static java.lang.Math.min; import static java.lang.Math.min;
import static java.lang.Math.round; import static java.lang.Math.round;
@ -121,6 +122,30 @@ public class RawAssetLoaderTest {
.isEqualTo(round(audioSamplePresentationTimeUs * 100 / (float) audioDurationUs)); .isEqualTo(round(audioSamplePresentationTimeUs * 100 / (float) audioDurationUs));
} }
@Test
public void getProgress_withOnlyAudioDataAndUnsetDuration_returnsUnavailableProgress() {
long audioSamplePresentationTimeUs = 100;
AssetLoader.Listener fakeAssetLoaderListener =
new FakeAssetLoaderListener(new FakeAudioSampleConsumer(), /* videoSampleConsumer= */ null);
ProgressHolder progressHolder = new ProgressHolder();
RawAssetLoader rawAssetLoader =
new RawAssetLoader(
new EditedMediaItem.Builder(new MediaItem.Builder().build()).build(),
fakeAssetLoaderListener,
FAKE_AUDIO_FORMAT,
/* videoFormat= */ null,
/* frameProcessedListener= */ null);
rawAssetLoader.start();
boolean queuedAudioData =
rawAssetLoader.queueAudioData(
ByteBuffer.wrap(FAKE_AUDIO_DATA), audioSamplePresentationTimeUs, /* isLast= */ false);
@Transformer.ProgressState int progressState = rawAssetLoader.getProgress(progressHolder);
assertThat(queuedAudioData).isTrue();
assertThat(progressState).isEqualTo(PROGRESS_STATE_UNAVAILABLE);
}
@Test @Test
public void getProgress_withOnlyVideoData_returnsExpectedProgress() throws ExportException { public void getProgress_withOnlyVideoData_returnsExpectedProgress() throws ExportException {
long videoDurationUs = 1_000; long videoDurationUs = 1_000;