Split parameterized android test utils into utility class.

Includes adjusting how effects are defined, to make it clearer in a
test that a given ItemConfig has effects associated with it.

PiperOrigin-RevId: 644684308
This commit is contained in:
samrobinson 2024-06-19 02:53:46 -07:00 committed by Copybara-Service
parent d27549d29a
commit afa7935553
2 changed files with 232 additions and 172 deletions

View File

@ -0,0 +1,201 @@
/*
* Copyright 2024 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.MimeTypes.VIDEO_H265;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.transformer.AndroidTestUtil.assumeFormatsSupported;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Util;
import androidx.media3.transformer.AndroidTestUtil.AssetInfo;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.math.RoundingMode;
import java.util.Objects;
/** Test utilities for parameterized instrumentation tests. */
/* package */ final class ParameterizedAndroidTestUtil {
/**
* Assume that the device supports the inputs and output of the sequence.
*
* <p>See {@link AndroidTestUtil#assumeFormatsSupported(Context, String, Format, Format)}.
*
* @param context The {@link Context context}.
* @param testId The test ID.
* @param sequence The {@link SequenceConfig}.
* @throws Exception If an error occurs checking device support.
*/
public static void assumeSequenceFormatsSupported(
Context context, String testId, SequenceConfig sequence) throws Exception {
checkState(!sequence.itemConfigs.isEmpty());
Format outputFormat = checkNotNull(sequence.itemConfigs.get(0).outputFormat);
for (ItemConfig item : sequence.itemConfigs) {
assumeFormatsSupported(context, testId, item.format, outputFormat);
}
}
/** Test parameters for an {@link EditedMediaItemSequence}. */
public static final class SequenceConfig {
public final int totalExpectedFrameCount;
public final ImmutableList<ItemConfig> itemConfigs;
public SequenceConfig(ItemConfig... itemConfigs) {
this.itemConfigs = ImmutableList.copyOf(itemConfigs);
int frameCountSum = 0;
for (ItemConfig item : itemConfigs) {
frameCountSum += item.frameCount;
}
this.totalExpectedFrameCount = frameCountSum;
}
/** Builds a {@link Composition} from the sequence configuration. */
public Composition buildComposition(Effects compositionEffects) {
ImmutableList.Builder<EditedMediaItem> editedMediaItems = new ImmutableList.Builder<>();
for (ItemConfig itemConfig : itemConfigs) {
editedMediaItems.add(itemConfig.build());
}
return new Composition.Builder(new EditedMediaItemSequence(editedMediaItems.build()))
.setEffects(compositionEffects)
.build();
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Seq{");
for (ItemConfig itemConfig : itemConfigs) {
stringBuilder.append(itemConfig).append(",");
}
stringBuilder.replace(stringBuilder.length() - 1, stringBuilder.length(), "}");
return stringBuilder.toString();
}
}
/** Test parameters for an {@link EditedMediaItem}. */
public abstract static class ItemConfig {
public final int frameCount;
@Nullable public final Format format;
@Nullable public final Format outputFormat;
protected final Effects effects;
private final String uri;
public ItemConfig(
String uri,
int frameCount,
@Nullable Format format,
@Nullable Format outputFormat,
Effects effects) {
this.uri = uri;
this.frameCount = frameCount;
this.format = format;
this.outputFormat = outputFormat;
this.effects = effects;
}
public final EditedMediaItem build() {
EditedMediaItem.Builder builder =
new EditedMediaItem.Builder(MediaItem.fromUri(uri)).setEffects(effects);
onBuild(builder);
return builder.build();
}
/**
* Called when an {@link EditedMediaItem} is being {@linkplain #build() built}.
*
* @param builder The {@link EditedMediaItem.Builder} to optionally modify before the item is
* built.
*/
protected abstract void onBuild(EditedMediaItem.Builder builder);
@Override
public String toString() {
return Iterables.getLast(Splitter.on("/").splitToList(uri))
+ (Objects.equals(effects, Effects.EMPTY) ? "" : "-effects");
}
}
/** {@link ItemConfig} for an SDR image {@link EditedMediaItem}. */
public static final class SdrImageItemConfig extends ItemConfig {
private final int frameRate;
private final long durationUs;
public SdrImageItemConfig(AssetInfo assetInfo, int frameCount) {
this(assetInfo, frameCount, C.MICROS_PER_SECOND);
}
public SdrImageItemConfig(AssetInfo assetInfo, int frameRate, long durationUs) {
this(assetInfo, frameRate, durationUs, Effects.EMPTY);
}
public SdrImageItemConfig(
AssetInfo assetInfo, int frameRate, long durationUs, Effects effects) {
super(
assetInfo.uri,
/* frameCount= */ (int)
Util.scaleLargeValue(
frameRate, durationUs, C.MICROS_PER_SECOND, RoundingMode.CEILING),
/* format= */ assetInfo.videoFormat,
/* outputFormat= */ assetInfo
.videoFormat
.buildUpon()
// Image by default are encoded in H265 and BT709 SDR.
.setSampleMimeType(VIDEO_H265)
.setColorInfo(ColorInfo.SDR_BT709_LIMITED)
.setFrameRate(frameRate)
.build(),
effects);
this.frameRate = frameRate;
this.durationUs = durationUs;
}
@Override
protected void onBuild(EditedMediaItem.Builder builder) {
builder.setFrameRate(frameRate).setDurationUs(durationUs);
}
}
/**
* {@link ItemConfig} for a video {@link EditedMediaItem}.
*
* <p>Audio is removed.
*/
public static final class VideoItemConfig extends ItemConfig {
public VideoItemConfig(AssetInfo asset, Effects effects) {
super(asset.uri, asset.videoFrameCount, asset.videoFormat, asset.videoFormat, effects);
}
@Override
protected void onBuild(EditedMediaItem.Builder builder) {
builder.setRemoveAudio(true);
}
}
private ParameterizedAndroidTestUtil() {}
}

View File

@ -14,32 +14,23 @@
* limitations under the License.
*
*/
package androidx.media3.transformer;
import static androidx.media3.transformer.AndroidTestUtil.BT601_MP4_ASSET;
import static androidx.media3.transformer.AndroidTestUtil.JPG_ASSET;
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET;
import static androidx.media3.transformer.AndroidTestUtil.PNG_ASSET;
import static androidx.media3.transformer.ParameterizedAndroidTestUtil.assumeSequenceFormatsSupported;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Util;
import androidx.media3.effect.Presentation;
import androidx.media3.transformer.AndroidTestUtil.AssetInfo;
import androidx.media3.transformer.ParameterizedAndroidTestUtil.SdrImageItemConfig;
import androidx.media3.transformer.ParameterizedAndroidTestUtil.SequenceConfig;
import androidx.media3.transformer.ParameterizedAndroidTestUtil.VideoItemConfig;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.io.File;
import java.math.RoundingMode;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
@ -57,12 +48,22 @@ import org.junit.runners.Parameterized.Parameters;
/** Parameterized end to end {@linkplain EditedMediaItemSequence sequence} export tests. */
@RunWith(Parameterized.class)
public class ParameterizedInputSequenceExportTest {
private static final ImageItemConfig PNG_ITEM =
new ImageItemConfig(PNG_ASSET, /* frameCount= */ 34);
private static final ImageItemConfig JPG_ITEM =
new ImageItemConfig(JPG_ASSET, /* frameCount= */ 41);
private static final VideoItemConfig BT709_ITEM = new VideoItemConfig(MP4_ASSET);
private static final VideoItemConfig BT601_ITEM = new VideoItemConfig(BT601_MP4_ASSET);
private static final SdrImageItemConfig PNG_ITEM =
new SdrImageItemConfig(PNG_ASSET, /* frameCount= */ 34);
private static final SdrImageItemConfig JPG_ITEM =
new SdrImageItemConfig(JPG_ASSET, /* frameCount= */ 41);
private static final VideoItemConfig BT709_ITEM =
new VideoItemConfig(
MP4_ASSET,
new Effects(
/* audioProcessors= */ ImmutableList.of(),
ImmutableList.of(Presentation.createForHeight(360))));
private static final VideoItemConfig BT601_ITEM =
new VideoItemConfig(
BT601_MP4_ASSET,
new Effects(
/* audioProcessors= */ ImmutableList.of(),
ImmutableList.of(Presentation.createForHeight(360))));
@Parameters(name = "{0}")
public static ImmutableList<SequenceConfig> params() {
@ -117,161 +118,19 @@ public class ParameterizedInputSequenceExportTest {
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, sequence.buildComposition());
assertThat(result.exportResult.videoFrameCount).isEqualTo(sequence.totalExpectedFrameCount);
assertThat(new File(result.filePath).length()).isGreaterThan(0);
}
private static void assumeSequenceFormatsSupported(
Context context, String testId, SequenceConfig sequence) throws Exception {
Assertions.checkState(!sequence.itemConfigs.isEmpty());
Format outputFormat = Assertions.checkNotNull(sequence.itemConfigs.get(0).outputFormat);
for (ItemConfig item : sequence.itemConfigs) {
AndroidTestUtil.assumeFormatsSupported(context, testId, item.format, outputFormat);
}
}
/** Test parameters for an {@link EditedMediaItemSequence}. */
private static final class SequenceConfig {
public final int totalExpectedFrameCount;
public final ImmutableList<ItemConfig> itemConfigs;
public SequenceConfig(ItemConfig... itemConfigs) {
this.itemConfigs = ImmutableList.copyOf(itemConfigs);
int frameCountSum = 0;
for (ItemConfig item : itemConfigs) {
frameCountSum += item.frameCount;
}
this.totalExpectedFrameCount = frameCountSum;
}
/**
* Builds a {@link Composition} from the sequence configuration.
*
* <p>{@link Presentation} of {@code width 480, height 360} is used to ensure software encoders
* can encode.
*/
public Composition buildComposition() {
ImmutableList.Builder<EditedMediaItem> editedMediaItems = new ImmutableList.Builder<>();
for (ItemConfig itemConfig : itemConfigs) {
editedMediaItems.add(itemConfig.build());
}
return new Composition.Builder(new EditedMediaItemSequence(editedMediaItems.build()))
.setEffects(
.run(
testId,
sequence.buildComposition(
// Presentation of 480Wx360H is used to ensure software encoders can encode.
new Effects(
/* audioProcessors= */ ImmutableList.of(),
ImmutableList.of(
Presentation.createForWidthAndHeight(
/* width= */ 480, /* height= */ 360, Presentation.LAYOUT_SCALE_TO_FIT))))
.build();
}
/* width= */ 480,
/* height= */ 360,
Presentation.LAYOUT_SCALE_TO_FIT)))));
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Seq{");
for (ItemConfig itemConfig : itemConfigs) {
stringBuilder.append(itemConfig).append(",");
}
stringBuilder.replace(stringBuilder.length() - 2, stringBuilder.length(), "}");
return stringBuilder.toString();
}
}
/** Test parameters for an {@link EditedMediaItem}. */
private abstract static class ItemConfig {
public final int frameCount;
private final String uri;
@Nullable private final Format format;
@Nullable private final Format outputFormat;
public ItemConfig(
String uri, int frameCount, @Nullable Format format, @Nullable Format outputFormat) {
this.uri = uri;
this.frameCount = frameCount;
this.format = format;
this.outputFormat = outputFormat;
}
public final EditedMediaItem build() {
EditedMediaItem.Builder builder = new EditedMediaItem.Builder(MediaItem.fromUri(uri));
onBuild(builder);
return builder.build();
}
/**
* Called when an {@link EditedMediaItem} is being {@linkplain #build() built}.
*
* @param builder The {@link EditedMediaItem.Builder} to optionally modify before the item is
* built.
*/
protected abstract void onBuild(EditedMediaItem.Builder builder);
@Override
public String toString() {
return Iterables.getLast(Splitter.on("/").splitToList(uri));
}
}
/** {@link ItemConfig} for an image {@link EditedMediaItem} with a duration of one second. */
private static final class ImageItemConfig extends ItemConfig {
// Image by default are encoded in H265 and BT709 SDR.
private static final Format OUTPUT_FORMAT =
new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_H265)
.setFrameRate(30.f)
.setWidth(480)
.setHeight(360)
.setColorInfo(ColorInfo.SDR_BT709_LIMITED)
.build();
private final long durationUs;
private final int frameRate;
public ImageItemConfig(AssetInfo assetInfo, int frameCount) {
this(assetInfo, frameCount, C.MICROS_PER_SECOND);
}
public ImageItemConfig(AssetInfo assetInfo, int frameRate, long durationUs) {
super(
assetInfo.uri,
/* frameCount= */ (int)
Util.scaleLargeValue(
frameRate, durationUs, C.MICROS_PER_SECOND, RoundingMode.CEILING),
/* format= */ assetInfo.videoFormat,
OUTPUT_FORMAT);
this.frameRate = frameRate;
this.durationUs = durationUs;
}
@Override
protected void onBuild(EditedMediaItem.Builder builder) {
builder.setFrameRate(frameRate).setDurationUs(durationUs);
}
}
/**
* {@link ItemConfig} for a video {@link EditedMediaItem}.
*
* <p>Audio is removed and a {@link Presentation} of specified {@code height=360}.
*/
private static final class VideoItemConfig extends ItemConfig {
public VideoItemConfig(AssetInfo asset) {
super(asset.uri, asset.videoFrameCount, asset.videoFormat, asset.videoFormat);
}
@Override
protected void onBuild(EditedMediaItem.Builder builder) {
builder
.setEffects(
new Effects(
/* audioProcessors= */ ImmutableList.of(),
ImmutableList.of(Presentation.createForHeight(360))))
.setRemoveAudio(true);
}
assertThat(result.exportResult.videoFrameCount).isEqualTo(sequence.totalExpectedFrameCount);
assertThat(new File(result.filePath).length()).isGreaterThan(0);
}
}