Add ImageAssetLoader tests.

PiperOrigin-RevId: 514431184
This commit is contained in:
tofunmi 2023-03-06 17:16:33 +00:00 committed by Rohit Singh
parent 55f2d1a5e2
commit 6188952682
4 changed files with 204 additions and 18 deletions

View File

@ -53,6 +53,9 @@ public final class AndroidTestUtil {
*/ */
public static final int MEDIA_CODEC_PRIORITY_NON_REALTIME = 1; public static final int MEDIA_CODEC_PRIORITY_NON_REALTIME = 1;
public static final String PNG_ASSET_URI_STRING =
"asset:///media/bitmap/input_images/media3test.png";
public static final String MP4_ASSET_URI_STRING = "asset:///media/mp4/sample.mp4"; public static final String MP4_ASSET_URI_STRING = "asset:///media/mp4/sample.mp4";
public static final Format MP4_ASSET_FORMAT = public static final Format MP4_ASSET_FORMAT =
new Format.Builder() new Format.Builder()

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI_STRING; import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_URI_STRING;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S_URI_STRING; import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S_URI_STRING;
import static com.google.android.exoplayer2.transformer.AndroidTestUtil.PNG_ASSET_URI_STRING;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
@ -24,6 +25,7 @@ import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.effect.Presentation; import com.google.android.exoplayer2.effect.Presentation;
@ -41,6 +43,53 @@ public class TransformerEndToEndTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Test
public void videoEditing_withImageInput_completesWithCorrectFrameCountAndDuration()
throws Exception {
String testId = "videoEditing_withImageInput_completesWithCorrectFrameCountAndDuration";
Transformer transformer = new Transformer.Builder(context).build();
ImmutableList<Effect> videoEffects = ImmutableList.of(Presentation.createForHeight(480));
Effects effects = new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects);
int expectedFrameCount = 40;
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET_URI_STRING))
.setDurationUs(C.MICROS_PER_SECOND)
.setFrameRate(expectedFrameCount)
.setEffects(effects)
.build();
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
assertThat(result.exportResult.videoFrameCount).isEqualTo(expectedFrameCount);
// Expected timestamp of the last frame.
assertThat(result.exportResult.durationMs)
.isEqualTo((C.MILLIS_PER_SECOND / expectedFrameCount) * (expectedFrameCount - 1));
}
@Test
public void videoTranscoding_withImageInput_completesWithCorrectFrameCountAndDuration()
throws Exception {
String testId = "videoTranscoding_withImageInput_completesWithCorrectFrameCountAndDuration";
Transformer transformer = new Transformer.Builder(context).build();
int expectedFrameCount = 40;
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET_URI_STRING))
.setDurationUs(C.MICROS_PER_SECOND)
.setFrameRate(expectedFrameCount)
.build();
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
assertThat(result.exportResult.videoFrameCount).isEqualTo(expectedFrameCount);
// Expected timestamp of the last frame.
assertThat(result.exportResult.durationMs)
.isEqualTo((C.MILLIS_PER_SECOND / expectedFrameCount) * (expectedFrameCount - 1));
}
@Test @Test
public void videoEditing_completesWithConsistentFrameCount() throws Exception { public void videoEditing_completesWithConsistentFrameCount() throws Exception {
Transformer transformer = Transformer transformer =

View File

@ -253,22 +253,4 @@ public class ExportTest {
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);
} }
@Test
public void exportImage() throws Exception {
// TODO(b/262693274): consider removing this test when Robolectric tests have been added for
// image input.
String testId = TAG + "_exportImage";
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build();
String imageUri = "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg";
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(imageUri))
.setDurationUs(1_000_000)
.setFrameRate(30)
.build();
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
}
} }

View File

@ -0,0 +1,152 @@
/*
* 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 com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runLooperUntil;
import static com.google.common.truth.Truth.assertThat;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.shadows.ShadowSystemClock;
/** Unit tests for {@link ExoPlayerAssetLoader}. */
@RunWith(AndroidJUnit4.class)
public class ImageAssetLoaderTest {
@Test
public void imageAssetLoader_callsListenerCallbacksInRightOrder() throws Exception {
HandlerThread assetLoaderThread = new HandlerThread("AssetLoaderThread");
assetLoaderThread.start();
Looper assetLoaderLooper = assetLoaderThread.getLooper();
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
AtomicBoolean isOutputFormatSet = new AtomicBoolean();
AssetLoader.Listener listener =
new AssetLoader.Listener() {
private volatile boolean isDurationSet;
private volatile boolean isTrackCountSet;
private volatile boolean isTrackAdded;
@Override
public void onDurationUs(long durationUs) {
sleep();
isDurationSet = true;
}
@Override
public void onTrackCount(int trackCount) {
// Sleep to increase the chances of the test failing.
sleep();
isTrackCountSet = true;
}
@Override
public boolean onTrackAdded(
Format inputFormat,
@AssetLoader.SupportedOutputTypes int supportedOutputTypes,
long streamStartPositionUs,
long streamOffsetUs) {
if (!isTrackCountSet) {
exceptionRef.set(
new IllegalStateException("onTrackAdded() called before onTrackCount()"));
}
sleep();
isTrackAdded = true;
return false;
}
@Override
public SampleConsumer onOutputFormat(Format format) {
if (!isDurationSet) {
exceptionRef.set(
new IllegalStateException("onTrackAdded() called before onDurationUs()"));
} else if (!isTrackAdded) {
exceptionRef.set(
new IllegalStateException("onOutputFormat() called before onTrackAdded()"));
}
isOutputFormatSet.set(true);
return new FakeSampleConsumer();
}
@Override
public void onError(ExportException e) {
exceptionRef.set(e);
}
void sleep() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
exceptionRef.set(e);
}
}
};
AssetLoader assetLoader = getAssetLoader(assetLoaderLooper, listener);
new Handler(assetLoaderLooper).post(assetLoader::start);
runLooperUntil(
Looper.myLooper(),
() -> {
ShadowSystemClock.advanceBy(Duration.ofMillis(10));
return isOutputFormatSet.get() || exceptionRef.get() != null;
});
assertThat(exceptionRef.get()).isNull();
}
private static AssetLoader getAssetLoader(Looper looper, AssetLoader.Listener listener) {
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(
MediaItem.fromUri("asset:///media/bitmap/input_images/media3test.png"))
.setDurationUs(1_000_000)
.setFrameRate(30)
.build();
return new ImageAssetLoader.Factory(ApplicationProvider.getApplicationContext())
.createAssetLoader(editedMediaItem, looper, listener);
}
private static final class FakeSampleConsumer implements SampleConsumer {
@Nullable
@Override
public DecoderInputBuffer getInputBuffer() {
return null;
}
@Override
public void queueInputBuffer() {}
@Override
public void queueInputBitmap(Bitmap inputBitmap, long durationUs, int frameRate) {}
@Override
public void signalEndOfVideoInput() {}
}
}