Set MediaItem's image duration for image export

This field will become mandatory

PiperOrigin-RevId: 678656879
This commit is contained in:
kimvde 2024-09-25 05:20:13 -07:00 committed by Copybara-Service
parent f83d2b1392
commit eaec7a4a61
8 changed files with 69 additions and 53 deletions

View File

@ -121,6 +121,8 @@ import org.json.JSONObject;
/** An {@link Activity} that exports and plays media using {@link Transformer}. */ /** An {@link Activity} that exports and plays media using {@link Transformer}. */
public final class TransformerActivity extends AppCompatActivity { public final class TransformerActivity extends AppCompatActivity {
private static final String TAG = "TransformerActivity"; private static final String TAG = "TransformerActivity";
private static final int IMAGE_DURATION_MS = 5_000;
private static final int IMAGE_FRAME_RATE_FPS = 30;
private static int LOAD_CONTROL_MIN_BUFFER_MS = 5_000; private static int LOAD_CONTROL_MIN_BUFFER_MS = 5_000;
private static int LOAD_CONTROL_MAX_BUFFER_MS = 5_000; private static int LOAD_CONTROL_MAX_BUFFER_MS = 5_000;
@ -267,7 +269,8 @@ public final class TransformerActivity extends AppCompatActivity {
} }
private MediaItem createMediaItem(@Nullable Bundle bundle, Uri uri) { private MediaItem createMediaItem(@Nullable Bundle bundle, Uri uri) {
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri); MediaItem.Builder mediaItemBuilder =
new MediaItem.Builder().setUri(uri).setImageDurationMs(IMAGE_DURATION_MS);
if (bundle != null) { if (bundle != null) {
long trimStartMs = long trimStartMs =
bundle.getLong(ConfigurationActivity.TRIM_START_MS, /* defaultValue= */ C.TIME_UNSET); bundle.getLong(ConfigurationActivity.TRIM_START_MS, /* defaultValue= */ C.TIME_UNSET);
@ -359,7 +362,7 @@ public final class TransformerActivity extends AppCompatActivity {
private Composition createComposition(MediaItem mediaItem, @Nullable Bundle bundle) { private Composition createComposition(MediaItem mediaItem, @Nullable Bundle bundle) {
EditedMediaItem.Builder editedMediaItemBuilder = new EditedMediaItem.Builder(mediaItem); EditedMediaItem.Builder editedMediaItemBuilder = new EditedMediaItem.Builder(mediaItem);
// For image inputs. Automatically ignored if input is audio/video. // For image inputs. Automatically ignored if input is audio/video.
editedMediaItemBuilder.setDurationUs(5_000_000).setFrameRate(30); editedMediaItemBuilder.setFrameRate(IMAGE_FRAME_RATE_FPS);
if (bundle != null) { if (bundle != null) {
ImmutableList<AudioProcessor> audioProcessors = createAudioProcessorsFromBundle(bundle); ImmutableList<AudioProcessor> audioProcessors = createAudioProcessorsFromBundle(bundle);
ImmutableList<Effect> videoEffects = createVideoEffectsFromBundle(bundle); ImmutableList<Effect> videoEffects = createVideoEffectsFromBundle(bundle);

View File

@ -105,7 +105,7 @@ import java.util.Objects;
protected final Effects effects; protected final Effects effects;
private final String uri; protected final String uri;
public ItemConfig( public ItemConfig(
String uri, String uri,
@ -120,20 +120,7 @@ import java.util.Objects;
this.effects = effects; this.effects = effects;
} }
public final EditedMediaItem build() { protected abstract 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 @Override
public String toString() { public String toString() {
@ -178,8 +165,13 @@ import java.util.Objects;
} }
@Override @Override
protected void onBuild(EditedMediaItem.Builder builder) { protected EditedMediaItem build() {
builder.setFrameRate(frameRate).setDurationUs(durationUs); MediaItem mediaItem =
new MediaItem.Builder().setUri(uri).setImageDurationMs(Util.usToMs(durationUs)).build();
return new EditedMediaItem.Builder(mediaItem)
.setEffects(effects)
.setFrameRate(frameRate)
.build();
} }
} }
@ -194,8 +186,11 @@ import java.util.Objects;
} }
@Override @Override
protected void onBuild(EditedMediaItem.Builder builder) { protected EditedMediaItem build() {
builder.setRemoveAudio(true); return new EditedMediaItem.Builder(MediaItem.fromUri(uri))
.setEffects(effects)
.setRemoveAudio(true)
.build();
} }
} }

View File

@ -102,10 +102,10 @@ public final class SequenceEffectTestUtil {
* effects} applied. * effects} applied.
*/ */
public static EditedMediaItem oneFrameFromImage(String uri, List<Effect> effects) { public static EditedMediaItem oneFrameFromImage(String uri, List<Effect> effects) {
return new EditedMediaItem.Builder(MediaItem.fromUri(uri)) // 50ms for a 20-fps video is one frame.
// 50ms for a 20-fps video is one frame. return new EditedMediaItem.Builder(
new MediaItem.Builder().setUri(uri).setImageDurationMs(50).build())
.setFrameRate(20) .setFrameRate(20)
.setDurationUs(50_000)
.setEffects( .setEffects(
new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.copyOf(effects))) new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.copyOf(effects)))
.build(); .build();

View File

@ -145,8 +145,8 @@ public class TransformerEndToEndTest {
ImmutableList.of(RgbFilter.createInvertedFilter()))) ImmutableList.of(RgbFilter.createInvertedFilter())))
.build(); .build();
EditedMediaItem imageItem = EditedMediaItem imageItem =
new EditedMediaItem.Builder(MediaItem.fromUri(JPG_ASSET.uri)) new EditedMediaItem.Builder(
.setDurationUs(1_500_000) new MediaItem.Builder().setUri(JPG_ASSET.uri).setImageDurationMs(1500).build())
.setFrameRate(30) .setFrameRate(30)
.build(); .build();
@ -209,8 +209,8 @@ public class TransformerEndToEndTest {
/* inputFormat= */ MP4_ASSET.videoFormat, /* inputFormat= */ MP4_ASSET.videoFormat,
/* outputFormat= */ MP4_ASSET.videoFormat); /* outputFormat= */ MP4_ASSET.videoFormat);
EditedMediaItem imageItem = EditedMediaItem imageItem =
new EditedMediaItem.Builder(MediaItem.fromUri(JPG_ASSET.uri)) new EditedMediaItem.Builder(
.setDurationUs(500_000) new MediaItem.Builder().setUri(JPG_ASSET.uri).setImageDurationMs(500).build())
.setFrameRate(30) .setFrameRate(30)
.build(); .build();
@ -251,9 +251,13 @@ public class TransformerEndToEndTest {
ImmutableList<Effect> videoEffects = ImmutableList.of(Presentation.createForHeight(480)); ImmutableList<Effect> videoEffects = ImmutableList.of(Presentation.createForHeight(480));
Effects effects = new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects); Effects effects = new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects);
int expectedFrameCount = 40; int expectedFrameCount = 40;
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(PNG_ASSET.uri)
.setImageDurationMs(C.MILLIS_PER_SECOND)
.build();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET.uri)) new EditedMediaItem.Builder(mediaItem)
.setDurationUs(C.MICROS_PER_SECOND)
.setFrameRate(expectedFrameCount) .setFrameRate(expectedFrameCount)
.setEffects(effects) .setEffects(effects)
.build(); .build();
@ -275,8 +279,11 @@ public class TransformerEndToEndTest {
Transformer transformer = new Transformer.Builder(context).build(); Transformer transformer = new Transformer.Builder(context).build();
int expectedFrameCount = 40; int expectedFrameCount = 40;
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET.uri)) new EditedMediaItem.Builder(
.setDurationUs(C.MICROS_PER_SECOND) new MediaItem.Builder()
.setUri(PNG_ASSET.uri)
.setImageDurationMs(C.MILLIS_PER_SECOND)
.build())
.setFrameRate(expectedFrameCount) .setFrameRate(expectedFrameCount)
.build(); .build();
ExportTestResult result = ExportTestResult result =
@ -299,8 +306,11 @@ public class TransformerEndToEndTest {
Effects effects = new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects); Effects effects = new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects);
int expectedFrameCount = 40; int expectedFrameCount = 40;
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(WEBP_LARGE.uri)) new EditedMediaItem.Builder(
.setDurationUs(C.MICROS_PER_SECOND) new MediaItem.Builder()
.setUri(WEBP_LARGE.uri)
.setImageDurationMs(C.MILLIS_PER_SECOND)
.build())
.setFrameRate(expectedFrameCount) .setFrameRate(expectedFrameCount)
.setEffects(effects) .setEffects(effects)
.build(); .build();
@ -516,14 +526,14 @@ public class TransformerEndToEndTest {
.build(); .build();
EditedMediaItem image1 = EditedMediaItem image1 =
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET.uri)) new EditedMediaItem.Builder(
.setDurationUs(100_000) new MediaItem.Builder().setUri(PNG_ASSET.uri).setImageDurationMs(100).build())
.setFrameRate(30) .setFrameRate(30)
.build(); .build();
int image1FrameCount = 3; int image1FrameCount = 3;
EditedMediaItem image2 = EditedMediaItem image2 =
new EditedMediaItem.Builder(MediaItem.fromUri(JPG_ASSET.uri)) new EditedMediaItem.Builder(
.setDurationUs(200_000) new MediaItem.Builder().setUri(JPG_ASSET.uri).setImageDurationMs(200).build())
.setFrameRate(30) .setFrameRate(30)
.build(); .build();
int image2FrameCount = 6; int image2FrameCount = 6;
@ -1459,8 +1469,8 @@ public class TransformerEndToEndTest {
audioEditedMediaItem, audioEditedMediaItem, audioEditedMediaItem) audioEditedMediaItem, audioEditedMediaItem, audioEditedMediaItem)
.build(); .build();
EditedMediaItem imageEditedMediaItem = EditedMediaItem imageEditedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET.uri)) new EditedMediaItem.Builder(
.setDurationUs(1_000_000) new MediaItem.Builder().setUri(PNG_ASSET.uri).setImageDurationMs(1000).build())
.setFrameRate(30) .setFrameRate(30)
.build(); .build();
EditedMediaItemSequence loopingImageSequence = EditedMediaItemSequence loopingImageSequence =
@ -1496,8 +1506,8 @@ public class TransformerEndToEndTest {
EditedMediaItemSequence audioSequence = EditedMediaItemSequence audioSequence =
new EditedMediaItemSequence.Builder(audioEditedMediaItem).build(); new EditedMediaItemSequence.Builder(audioEditedMediaItem).build();
EditedMediaItem imageEditedMediaItem = EditedMediaItem imageEditedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET.uri)) new EditedMediaItem.Builder(
.setDurationUs(1_050_000) new MediaItem.Builder().setUri(PNG_ASSET.uri).setImageDurationMs(1050).build())
.setFrameRate(20) .setFrameRate(20)
.build(); .build();
EditedMediaItemSequence loopingImageSequence = EditedMediaItemSequence loopingImageSequence =

View File

@ -17,7 +17,6 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.msToUs;
import static androidx.media3.test.utils.BitmapPixelTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE_LUMA; import static androidx.media3.test.utils.BitmapPixelTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE_LUMA;
import static androidx.media3.test.utils.BitmapPixelTestUtil.getBitmapAveragePixelAbsoluteDifferenceArgb8888; import static androidx.media3.test.utils.BitmapPixelTestUtil.getBitmapAveragePixelAbsoluteDifferenceArgb8888;
import static androidx.media3.test.utils.BitmapPixelTestUtil.maybeSaveTestBitmap; import static androidx.media3.test.utils.BitmapPixelTestUtil.maybeSaveTestBitmap;
@ -286,9 +285,9 @@ public final class TransformerMultiSequenceCompositionTest {
} }
private static EditedMediaItem editedMediaItemOfOneFrameImage(String uri, List<Effect> effects) { private static EditedMediaItem editedMediaItemOfOneFrameImage(String uri, List<Effect> effects) {
return new EditedMediaItem.Builder(MediaItem.fromUri(uri)) return new EditedMediaItem.Builder(
new MediaItem.Builder().setUri(uri).setImageDurationMs(ONE_FRAME_DURATION_MS).build())
.setRemoveAudio(true) .setRemoveAudio(true)
.setDurationUs(msToUs(ONE_FRAME_DURATION_MS))
.setFrameRate((int) (1000 / ONE_FRAME_DURATION_MS)) .setFrameRate((int) (1000 / ONE_FRAME_DURATION_MS))
.setEffects( .setEffects(
new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.copyOf(effects))) new Effects(/* audioProcessors= */ ImmutableList.of(), ImmutableList.copyOf(effects)))

View File

@ -352,9 +352,12 @@ public final class TransformerSequenceEffectTest {
Composition composition = Composition composition =
createComposition( createComposition(
/* presentation= */ null, /* presentation= */ null,
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET_LINES_1080P.uri)) new EditedMediaItem.Builder(
new MediaItem.Builder()
.setUri(PNG_ASSET_LINES_1080P.uri)
.setImageDurationMs(C.MILLIS_PER_SECOND / 4)
.build())
.setFrameRate(30) .setFrameRate(30)
.setDurationUs(C.MICROS_PER_SECOND / 4)
.build()); .build());
// Some devices need a very high bitrate to avoid encoding artifacts. // Some devices need a very high bitrate to avoid encoding artifacts.
int bitrate = 30_000_000; int bitrate = 30_000_000;
@ -417,9 +420,12 @@ public final class TransformerSequenceEffectTest {
Composition composition = Composition composition =
createComposition( createComposition(
/* presentation= */ null, /* presentation= */ null,
new EditedMediaItem.Builder(MediaItem.fromUri(PNG_ASSET_LINES_1080P.uri)) new EditedMediaItem.Builder(
new MediaItem.Builder()
.setUri(PNG_ASSET_LINES_1080P.uri)
.setImageDurationMs(C.MILLIS_PER_SECOND / 4)
.build())
.setFrameRate(30) .setFrameRate(30)
.setDurationUs(C.MICROS_PER_SECOND / 4)
.setEffects( .setEffects(
new Effects( new Effects(
ImmutableList.of(), ImmutableList.of(),

View File

@ -130,9 +130,12 @@ public class TranscodeSpeedTest {
// This test uses ULTRA_HDR_URI_STRING because it's high resolution. // This test uses ULTRA_HDR_URI_STRING because it's high resolution.
// Ultra HDR gainmap is ignored. // Ultra HDR gainmap is ignored.
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(JPG_ULTRA_HDR_ASSET.uri)) new EditedMediaItem.Builder(
new MediaItem.Builder()
.setUri(JPG_ULTRA_HDR_ASSET.uri)
.setImageDurationMs(isHighPerformance ? 45_000 : 15_000)
.build())
.setFrameRate(30) .setFrameRate(30)
.setDurationUs(isHighPerformance ? 45_000_000 : 15_000_000)
.setEffects( .setEffects(
new Effects( new Effects(
/* audioProcessors= */ ImmutableList.of(), /* audioProcessors= */ ImmutableList.of(),

View File

@ -167,8 +167,8 @@ public class ImageAssetLoaderTest {
private static AssetLoader getAssetLoader(AssetLoader.Listener listener, String uri) { private static AssetLoader getAssetLoader(AssetLoader.Listener listener, String uri) {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
EditedMediaItem editedMediaItem = EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(uri)) new EditedMediaItem.Builder(
.setDurationUs(1_000_000) new MediaItem.Builder().setUri(uri).setImageDurationMs(1000).build())
.setFrameRate(30) .setFrameRate(30)
.build(); .build();
return new ImageAssetLoader.Factory( return new ImageAssetLoader.Factory(