Compare commits

...

5 Commits

Author SHA1 Message Date
dancho
f261fe187a Assert frame counts with tolerance.
MediaCodec decoders sometimes output frames in
the wrong order. Make our asserts more permissive to
reduce noise in tests.

PiperOrigin-RevId: 747414696
2025-04-14 07:48:30 -07:00
shahddaghash
a2265f1dae Replace deprecated ShadowMediaCodecConfig factory methods
This is part of the efforts for adding encoder support for ShadowMediaCodecConfig to use it for ExoPlayer and Transcoding tests.
* Replaced `ShadowMediaCodecConfig#forAllSupportedMimeTypes()` calls with `ShadowMediaCodecConfig#withAllDefaultSupportedCodecs()`
* Replaced `ShadowMediaCodecConfig#withNoDefaultSupportedMimeTypes() calls with `ShadowMediaCodexConfig#withNoDefaultSupportedCodecs()`

PiperOrigin-RevId: 747413859
2025-04-14 07:46:13 -07:00
shahddaghash
07be60ed93 Add encoder support to ShadowMediaCodecConfig
This is a step towards unifying ShadowMediaCodecConfig structure to accommodate having both ExoPlayer and Transcoding codecs configuration.

This includes:
* Adding ability to configure encoders by calling `addEncoders(CodecInfo...)`
* A new factory method that takes specific encoders and decoders CodecInfos
* A new method `addCodecs(boolean, CodecConfig, CodecInfo...) that configures codecs with specified behavior by passing a `CodecConfig`.

This CL also includes migrating Transformer tests to ShadowMediaCodecConfig.

PiperOrigin-RevId: 747390451
2025-04-14 06:30:04 -07:00
bachinger
ed56ed22fb Don't enqueue ad periods that start after the end of the period
Issue: androidx/media#2215
PiperOrigin-RevId: 747376615
2025-04-14 05:41:31 -07:00
tonihei
3205811f23 Update release notes for 1.6.1
PiperOrigin-RevId: 747337479
2025-04-14 03:26:00 -07:00
43 changed files with 303 additions and 291 deletions

View File

@ -3,28 +3,12 @@
### Unreleased changes ### Unreleased changes
* Common Library: * Common Library:
* Add `PlaybackParameters.withPitch(float)` method for easily copying a
`PlaybackParameters` with a new `pitch` value
([#2257](https://github.com/androidx/media/issues/2257)).
* ExoPlayer: * ExoPlayer:
* Fix sending `CmcdData` in manifest requests for DASH, HLS, and
SmoothStreaming ([#2253](https://github.com/androidx/media/pull/2253)).
* Fix issue where media item transition fails due to recoverable renderer
error during initialization of the next media item
([#2229](https://github.com/androidx/media/issues/2229)).
* Add `ExoPlayer.setScrubbingModeEnabled(boolean)` method. This optimizes * Add `ExoPlayer.setScrubbingModeEnabled(boolean)` method. This optimizes
the player for many frequent seeks (for example, from a user dragging a the player for many frequent seeks (for example, from a user dragging a
scrubber bar around). The behavior of scrubbing mode can be customized scrubber bar around). The behavior of scrubbing mode can be customized
with `setScrubbingModeParameters(..)` on `ExoPlayer` and with `setScrubbingModeParameters(..)` on `ExoPlayer` and
`ExoPlayer.Builder`. `ExoPlayer.Builder`.
* `AdPlaybackState.withAdDurationsUs(long[][])` can be used after ad
groups have been removed. The user still needs to pass in an array of
durations for removed ad groups which can be empty or null
([#2267](https://github.com/androidx/media/issues/2267)).
* Fix issue where `ProgressiveMediaPeriod` throws an
`IllegalStateException` as `PreloadMediaSource` attempts to call its
`getBufferedDurationUs()` before it is prepared
([#2315](https://github.com/androidx/media/issues/2315)).
* Transformer: * Transformer:
* Filling an initial gap (added via `addGap()`) with silent audio now * Filling an initial gap (added via `addGap()`) with silent audio now
requires explicitly setting `experimentalSetForceAudioTrack(true)` in requires explicitly setting `experimentalSetForceAudioTrack(true)` in
@ -36,15 +20,9 @@
variable bitrate metadata when falling back to constant bitrate seeking variable bitrate metadata when falling back to constant bitrate seeking
due to `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING(_ALWAYS)` due to `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING(_ALWAYS)`
([#2194](https://github.com/androidx/media/issues/2194)). ([#2194](https://github.com/androidx/media/issues/2194)).
* MP4: Parse `alternate_group` from the `tkhd` box and expose it as an
`Mp4AlternateGroupData` entry in each track's `Format.metadata`
([#2242](https://github.com/androidx/media/issues/2242)).
* DataSource: * DataSource:
* Audio: * Audio:
* Allow constant power upmixing/downmixing in DefaultAudioMixer. * Allow constant power upmixing/downmixing in DefaultAudioMixer.
* Fix offload issue where position might get stuck when playing a playlist
of short content
([#1920](https://github.com/androidx/media/issues/1920)).
* Video: * Video:
* Add experimental `ExoPlayer` API to include the * Add experimental `ExoPlayer` API to include the
`MediaCodec.BUFFER_FLAG_DECODE_ONLY` flag when queuing decode-only input `MediaCodec.BUFFER_FLAG_DECODE_ONLY` flag when queuing decode-only input
@ -67,25 +45,7 @@
instead of `MediaCodec.BufferInfo`. instead of `MediaCodec.BufferInfo`.
* IMA extension: * IMA extension:
* Session: * Session:
* Lower aggregation timeout for platform `MediaSession` callbacks from 500
to 100 milliseconds and add an experimental setter to allow apps to
configure this value.
* Fix issue where notifications reappear after they have been dismissed by
the user ([#2302](https://github.com/androidx/media/issues/2302)).
* Fix a bug where the `PlayerWrapper` returned a single-item timeline when
the wrapped player is actually empty. This happened when the wrapped
player doesn't have `COMMAND_GET_TIMELINE` available while
`COMMAND_GET_CURRENT_MEDIA_ITEM` is available and the wrapped player is
empty ([#2320](https://github.com/androidx/media/issues/2320)).
* Fix a bug where calling
`MediaSessionService.setMediaNotificationProvider` is silently ignored
after other interactions with the service like
`setForegroundServiceTimeoutMs`
([#2305](https://github.com/androidx/media/issues/2305)).
* UI: * UI:
* Enable `PlayerSurface` to work with `ExoPlayer.setVideoEffects` and
`CompositionPlayer`.
* Fix bug where `PlayerSurface` can't be recomposed with a new `Player`.
* Downloads: * Downloads:
* Add partial download support for progressive streams. Apps can prepare a * Add partial download support for progressive streams. Apps can prepare a
progressive stream with `DownloadHelper`, and request a progressive stream with `DownloadHelper`, and request a
@ -101,22 +61,19 @@
* Cronet extension: * Cronet extension:
* RTMP extension: * RTMP extension:
* HLS extension: * HLS extension:
* Fix issue where chunk duration wasn't set in `CmcdData` for HLS media,
causing an assertion failure when processing encrypted media segments
([#2312](https://github.com/androidx/media/issues/2312)).
* DASH extension: * DASH extension:
* Smooth Streaming extension: * Smooth Streaming extension:
* RTSP extension: * RTSP extension:
* Add support for URI with RTSPT scheme as a way to configure the RTSP
session to use TCP
([#1484](https://github.com/androidx/media/issues/1484)).
* Decoder extensions (FFmpeg, VP9, AV1, etc.): * Decoder extensions (FFmpeg, VP9, AV1, etc.):
* MIDI extension: * MIDI extension:
* Leanback extension: * Leanback extension:
* Cast extension: * Cast extension:
* Add support for playlist metadata
([#2235](https://github.com/androidx/media/pull/2235)).
* Test Utilities: * Test Utilities:
* Removed `transformer.TestUtil.addAudioDecoders(String...)`,
`transformer.TestUtil.addAudioEncoders(String...)`, and
`transformer.TestUtil.addAudioEncoders(ShadowMediaCodec.CodecConfig,
String...)`. Use `ShadowMediaCodecConfig` to configure shadow encoders
and decoders instead.
* Demo app: * Demo app:
* Add `PlaybackSpeedPopUpButton` Composable UI element to be part of * Add `PlaybackSpeedPopUpButton` Composable UI element to be part of
`ExtraControls` in `demo-compose`. `ExtraControls` in `demo-compose`.
@ -138,6 +95,69 @@
## 1.6 ## 1.6
### 1.6.1 (2025-04-14)
This release includes the following changes since the
[1.6.0 release](#160-2025-03-26):
* Common Library:
* Add `PlaybackParameters.withPitch(float)` method for easily copying a
`PlaybackParameters` with a new `pitch` value
([#2257](https://github.com/androidx/media/issues/2257)).
* ExoPlayer:
* Fix issue where media item transition fails due to recoverable renderer
error during initialization of the next media item
([#2229](https://github.com/androidx/media/issues/2229)).
* Fix issue where `ProgressiveMediaPeriod` throws an
`IllegalStateException` as `PreloadMediaSource` attempts to call its
`getBufferedDurationUs()` before it is prepared
([#2315](https://github.com/androidx/media/issues/2315)).
* Fix sending `CmcdData` in manifest requests for DASH, HLS, and
SmoothStreaming ([#2253](https://github.com/androidx/media/pull/2253)).
* Ensure `AdPlaybackState.withAdDurationsUs(long[][])` can be used after
ad groups have been removed. The user still needs to pass in an array of
durations for removed ad groups which can be empty or null
([#2267](https://github.com/androidx/media/issues/2267)).
* Extractors:
* MP4: Parse `alternate_group` from the `tkhd` box and expose it as an
`Mp4AlternateGroupData` entry in each track's `Format.metadata`
([#2242](https://github.com/androidx/media/issues/2242)).
* Audio:
* Fix offload issue where the position might get stuck when playing a
playlist of short content
([#1920](https://github.com/androidx/media/issues/1920)).
* Session:
* Lower aggregation timeout for platform `MediaSession` callbacks from 500
to 100 milliseconds and add an experimental setter to allow apps to
configure this value.
* Fix issue where notifications reappear after they have been dismissed by
the user ([#2302](https://github.com/androidx/media/issues/2302)).
* Fix a bug where the session returned a single-item timeline when the
wrapped player is actually empty. This happened when the wrapped player
doesn't have `COMMAND_GET_TIMELINE` available while
`COMMAND_GET_CURRENT_MEDIA_ITEM` is available and the wrapped player is
empty ([#2320](https://github.com/androidx/media/issues/2320)).
* Fix a bug where calling
`MediaSessionService.setMediaNotificationProvider` is silently ignored
after other interactions with the service like
`setForegroundServiceTimeoutMs`
([#2305](https://github.com/androidx/media/issues/2305)).
* UI:
* Enable `PlayerSurface` to work with `ExoPlayer.setVideoEffects` and
`CompositionPlayer`.
* Fix bug where `PlayerSurface` can't be recomposed with a new `Player`.
* HLS extension:
* Fix issue where chunk duration wasn't set in `CmcdData` for HLS media,
causing an assertion failure when processing encrypted media segments
([#2312](https://github.com/androidx/media/issues/2312)).
* RTSP extension:
* Add support for URI with RTSPT scheme as a way to configure the RTSP
session to use TCP
([#1484](https://github.com/androidx/media/issues/1484)).
* Cast extension:
* Add support for playlist metadata
([#2235](https://github.com/androidx/media/pull/2235)).
### 1.6.0 (2025-03-26) ### 1.6.0 (2025-03-26)
This release includes the following changes since the This release includes the following changes since the
@ -2966,6 +2986,10 @@ This release corresponds to the
* Ad playback / IMA: * Ad playback / IMA:
* Decrease ad polling rate from every 100ms to every 200ms, to line up * Decrease ad polling rate from every 100ms to every 200ms, to line up
with Media Rating Council (MRC) recommendations. with Media Rating Council (MRC) recommendations.
* Fix bug where ad groups after the end of a VOD window stalled playback.
Ads groups with a start time after the window are not enqueued into the
`MediaPeriodQueue` anymore
([#2215](https://github.com/androidx/media/issues/2215)).
* FFmpeg extension: * FFmpeg extension:
* Update CMake version to `3.21.0+` to avoid a CMake bug causing * Update CMake version to `3.21.0+` to avoid a CMake bug causing
AndroidStudio's gradle sync to fail AndroidStudio's gradle sync to fail

View File

@ -865,7 +865,10 @@ public final class AdPlaybackState {
|| !getAdGroup(index).shouldPlayAdGroup())) { || !getAdGroup(index).shouldPlayAdGroup())) {
index++; index++;
} }
return index < adGroupCount ? index : C.INDEX_UNSET; return index < adGroupCount
&& (periodDurationUs == C.TIME_UNSET || getAdGroup(index).timeUs <= periodDurationUs)
? index
: C.INDEX_UNSET;
} }
/** Returns whether the specified ad has been marked as in {@link #AD_STATE_ERROR}. */ /** Returns whether the specified ad has been marked as in {@link #AD_STATE_ERROR}. */

View File

@ -856,6 +856,10 @@ public class AdPlaybackStateTest {
state.getAdGroupIndexAfterPositionUs( state.getAdGroupIndexAfterPositionUs(
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 5000)) /* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 5000))
.isEqualTo(C.INDEX_UNSET); .isEqualTo(C.INDEX_UNSET);
assertThat(
state.getAdGroupIndexAfterPositionUs(
/* positionUs= */ 1001, /* periodDurationUs= */ 1002))
.isEqualTo(C.INDEX_UNSET);
} }
@Test @Test

View File

@ -263,7 +263,7 @@ public final class ExoPlayerTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
// The explicit boolean parameter is only used to give clear test names. // The explicit boolean parameter is only used to give clear test names.
@Parameter(0) @Parameter(0)

View File

@ -83,7 +83,7 @@ public class ExoPlayerWithPrewarmingRenderersTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Before @Before
public void setUp() { public void setUp() {

View File

@ -88,7 +88,7 @@ public class AudioCapabilitiesEndToEndTest {
@Rule @Rule
public ShadowMediaCodecConfig shadowMediaCodecConfig = public ShadowMediaCodecConfig shadowMediaCodecConfig =
ShadowMediaCodecConfig.withNoDefaultSupportedMimeTypes(); ShadowMediaCodecConfig.withNoDefaultSupportedCodecs();
@Before @Before
public void setUp() { public void setUp() {

View File

@ -61,7 +61,7 @@ public class AudioOffloadEndToEndTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Before @Before
public void setup() { public void setup() {

View File

@ -48,7 +48,7 @@ public class AnalyticsListenerPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void loadEventsReportedAsExpected() throws Exception { public void loadEventsReportedAsExpected() throws Exception {

View File

@ -69,7 +69,7 @@ public final class ClippingPlaylistPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void playbackWithClippingMediaSources() throws Exception { public void playbackWithClippingMediaSources() throws Exception {

View File

@ -57,7 +57,7 @@ public class EndToEndOffloadFailureRecoveryTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
public FakeClock fakeClock; public FakeClock fakeClock;
public DefaultTrackSelector trackSelector; public DefaultTrackSelector trackSelector;

View File

@ -57,7 +57,7 @@ public class FlacPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -47,7 +47,7 @@ public final class FlvPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -86,7 +86,7 @@ public final class MergingPlaylistPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void transitionBetweenDifferentMergeConfigurations() throws Exception { public void transitionBetweenDifferentMergeConfigurations() throws Exception {

View File

@ -50,7 +50,7 @@ public final class MkaPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -64,7 +64,7 @@ public final class MkvPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -51,7 +51,7 @@ public class MotionPhotoPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -54,7 +54,7 @@ public final class Mp3PlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -76,7 +76,7 @@ public class Mp4PlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -52,7 +52,7 @@ public final class OggOpusPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
public FakeClock fakeClock; public FakeClock fakeClock;
public OffloadRenderersFactory offloadRenderersFactory; public OffloadRenderersFactory offloadRenderersFactory;

View File

@ -50,7 +50,7 @@ public final class OggPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -58,7 +58,7 @@ public class ParseAv1SampleDependenciesPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void playback_withClippedMediaItem_skipNonReferenceInputSamples() throws Exception { public void playback_withClippedMediaItem_skipNonReferenceInputSamples() throws Exception {

View File

@ -52,7 +52,7 @@ public final class PlaylistPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test_bypassOnThenOff() throws Exception { public void test_bypassOnThenOff() throws Exception {

View File

@ -56,7 +56,7 @@ public class PrewarmingRendererPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void playback_withTwoMediaItemsAndSecondaryVideoRenderer_dumpsCorrectOutput() public void playback_withTwoMediaItemsAndSecondaryVideoRenderer_dumpsCorrectOutput()

View File

@ -37,7 +37,7 @@ public final class SilencePlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test_500ms() throws Exception { public void test_500ms() throws Exception {

View File

@ -60,7 +60,7 @@ public class SubtitlePlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
// https://github.com/androidx/media/issues/1721 // https://github.com/androidx/media/issues/1721
@Test @Test

View File

@ -74,7 +74,7 @@ public class TsPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -51,7 +51,7 @@ public final class Vp9PlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -44,7 +44,7 @@ public final class WavPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -68,7 +68,7 @@ public class WebvttPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void test() throws Exception { public void test() throws Exception {

View File

@ -104,7 +104,7 @@ public final class ServerSideAdInsertionMediaSourceTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
private static final String TEST_ASSET = "asset:///media/mp4/sample.mp4"; private static final String TEST_ASSET = "asset:///media/mp4/sample.mp4";

View File

@ -75,7 +75,7 @@ public final class DashPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void webvttStandaloneFile() throws Exception { public void webvttStandaloneFile() throws Exception {

View File

@ -70,7 +70,7 @@ public final class HlsPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Test @Test
public void webvttStandaloneSubtitlesFile() throws Exception { public void webvttStandaloneSubtitlesFile() throws Exception {

View File

@ -868,7 +868,7 @@ import java.util.Objects;
} }
if (imaAdState == IMA_AD_STATE_NONE if (imaAdState == IMA_AD_STATE_NONE
&& playbackState == Player.STATE_BUFFERING && (playbackState == Player.STATE_BUFFERING || playbackState == Player.STATE_ENDED)
&& playWhenReady) { && playWhenReady) {
ensureSentContentCompleteIfAtEndOfStream(); ensureSentContentCompleteIfAtEndOfStream();
} else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) { } else if (imaAdState != IMA_AD_STATE_NONE && playbackState == Player.STATE_ENDED) {

View File

@ -80,7 +80,7 @@ public final class RtspPlaybackTest {
@Rule @Rule
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.withAllDefaultSupportedCodecs();
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {

View File

@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List;
import org.junit.rules.ExternalResource; import org.junit.rules.ExternalResource;
import org.robolectric.shadows.MediaCodecInfoBuilder; import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodec; import org.robolectric.shadows.ShadowMediaCodec;
@ -132,6 +133,8 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
new CodecInfo(/* codecName= */ "exotest.audio.ac3", MimeTypes.AUDIO_AC3); new CodecInfo(/* codecName= */ "exotest.audio.ac3", MimeTypes.AUDIO_AC3);
public static final CodecInfo CODEC_INFO_AC4 = public static final CodecInfo CODEC_INFO_AC4 =
new CodecInfo(/* codecName= */ "exotest.audio.ac4", MimeTypes.AUDIO_AC4); new CodecInfo(/* codecName= */ "exotest.audio.ac4", MimeTypes.AUDIO_AC4);
public static final CodecInfo CODEC_INFO_AMR_NB =
new CodecInfo(/* codecName= */ "exotest.audio.amrnb", MimeTypes.AUDIO_AMR_NB);
public static final CodecInfo CODEC_INFO_E_AC3 = public static final CodecInfo CODEC_INFO_E_AC3 =
new CodecInfo(/* codecName= */ "exotest.audio.eac3", MimeTypes.AUDIO_E_AC3); new CodecInfo(/* codecName= */ "exotest.audio.eac3", MimeTypes.AUDIO_E_AC3);
public static final CodecInfo CODEC_INFO_E_AC3_JOC = public static final CodecInfo CODEC_INFO_E_AC3_JOC =
@ -173,7 +176,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
/** /**
* @deprecated Use {@link ShadowMediaCodecConfig#withAllDefaultSupportedCodecs()} instead. * @deprecated Use {@link ShadowMediaCodecConfig#withAllDefaultSupportedCodecs()} instead.
*/ */
// TODO(b/399861060): Remove in Media3 1.8. // TODO(b/406437316): Remove in Media3 1.8.
@Deprecated @Deprecated
public static ShadowMediaCodecConfig forAllSupportedMimeTypes() { public static ShadowMediaCodecConfig forAllSupportedMimeTypes() {
return withAllDefaultSupportedCodecs(); return withAllDefaultSupportedCodecs();
@ -194,7 +197,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
/** /**
* @deprecated Use {@link ShadowMediaCodecConfig#withNoDefaultSupportedCodecs()} instead. * @deprecated Use {@link ShadowMediaCodecConfig#withNoDefaultSupportedCodecs()} instead.
*/ */
// TODO(b/399861060): Remove in Media3 1.8. // TODO(b/406437316): Remove in Media3 1.8.
@Deprecated @Deprecated
public static ShadowMediaCodecConfig withNoDefaultSupportedMimeTypes() { public static ShadowMediaCodecConfig withNoDefaultSupportedMimeTypes() {
return withNoDefaultSupportedCodecs(); return withNoDefaultSupportedCodecs();
@ -205,6 +208,20 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
return new ShadowMediaCodecConfig(ImmutableSet.of()); return new ShadowMediaCodecConfig(ImmutableSet.of());
} }
/**
* Returns a {@link ShadowMediaCodecConfig} instance configured with the provided {@code decoders}
* and {@code encoders}.
*
* <p>All codecs will work as passthrough, regardless of type.
*/
public static ShadowMediaCodecConfig withCodecs(
List<CodecInfo> decoders, List<CodecInfo> encoders) {
ImmutableSet.Builder<CodecImpl> codecs = new ImmutableSet.Builder<>();
codecs.addAll(createDecoders(decoders, /* forcePassthrough= */ true));
codecs.addAll(createEncoders(encoders));
return new ShadowMediaCodecConfig(codecs.build());
}
private final ImmutableSet<CodecImpl> defaultCodecs; private final ImmutableSet<CodecImpl> defaultCodecs;
private ShadowMediaCodecConfig(ImmutableSet<CodecImpl> defaultCodecs) { private ShadowMediaCodecConfig(ImmutableSet<CodecImpl> defaultCodecs) {
@ -267,6 +284,39 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
} }
} }
/**
* Configures and publishes {@linkplain ShadowMediaCodec shadow encoders} based on {@code
* encoders}.
*
* <p>This method configures pass-through encoders.
*/
public void addEncoders(CodecInfo... encoders) {
for (CodecInfo encoderInfo : encoders) {
CodecImpl encoder = CodecImpl.createEncoder(encoderInfo);
encoder.configure();
}
}
/**
* Configures and publishes a {@link ShadowMediaCodec} codec.
*
* <p>Input buffers are handled according to the {@link ShadowMediaCodec.CodecConfig} provided.
*
* @param codecInfo Basic codec information.
* @param isEncoder Whether the codecs registered are encoders or decoders.
* @param codecConfig Codec configuration implementation of the shadow.
*/
public void addCodec(
CodecInfo codecInfo, boolean isEncoder, ShadowMediaCodec.CodecConfig codecConfig) {
configureShadowMediaCodec(
codecInfo.codecName,
codecInfo.mimeType,
isEncoder,
codecInfo.profileLevels,
codecInfo.colorFormats,
codecConfig);
}
@Override @Override
protected void before() throws Throwable { protected void before() throws Throwable {
for (CodecImpl codec : this.defaultCodecs) { for (CodecImpl codec : this.defaultCodecs) {
@ -282,7 +332,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
} }
private static ImmutableSet<CodecImpl> createDecoders( private static ImmutableSet<CodecImpl> createDecoders(
ImmutableList<CodecInfo> decoderInfos, boolean forcePassthrough) { List<CodecInfo> decoderInfos, boolean forcePassthrough) {
ImmutableSet.Builder<CodecImpl> builder = new ImmutableSet.Builder<>(); ImmutableSet.Builder<CodecImpl> builder = new ImmutableSet.Builder<>();
for (CodecInfo info : decoderInfos) { for (CodecInfo info : decoderInfos) {
if (!forcePassthrough && MimeTypes.isAudio(info.mimeType)) { if (!forcePassthrough && MimeTypes.isAudio(info.mimeType)) {
@ -294,6 +344,14 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
return builder.build(); return builder.build();
} }
private static ImmutableSet<CodecImpl> createEncoders(List<CodecInfo> encoderInfos) {
ImmutableSet.Builder<CodecImpl> builder = new ImmutableSet.Builder<>();
for (CodecInfo info : encoderInfos) {
builder.add(CodecImpl.createEncoder(info));
}
return builder.build();
}
/** /**
* A {@link ShadowMediaCodec.CodecConfig.Codec} that provides pass-through or frame dropping * A {@link ShadowMediaCodec.CodecConfig.Codec} that provides pass-through or frame dropping
* encoders and decoders. * encoders and decoders.
@ -312,6 +370,10 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
return new CodecImpl(codecInfo, /* isPassthrough= */ true, /* isEncoder= */ false); return new CodecImpl(codecInfo, /* isPassthrough= */ true, /* isEncoder= */ false);
} }
public static CodecImpl createEncoder(CodecInfo codecInfo) {
return new CodecImpl(codecInfo, /* isPassthrough= */ true, /* isEncoder= */ true);
}
/** /**
* Creates an instance. * Creates an instance.
* *

View File

@ -556,8 +556,12 @@ public class TransformerEndToEndTest {
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);
// Rarely, MediaCodec decoders output frames in the wrong order.
// When the MediaCodec encoder sees frames in the wrong order, fewer output frames are produced.
// Use a tolerance when comparing frame counts. See b/343476417#comment5.
assertThat(result.exportResult.videoFrameCount) assertThat(result.exportResult.videoFrameCount)
.isEqualTo(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S.videoFrameCount); .isWithin(2)
.of(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S.videoFrameCount);
assertThat(new File(result.filePath).length()).isGreaterThan(0); assertThat(new File(result.filePath).length()).isGreaterThan(0);
} }
@ -583,8 +587,12 @@ public class TransformerEndToEndTest {
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);
// Rarely, MediaCodec decoders output frames in the wrong order.
// When the MediaCodec encoder sees frames in the wrong order, fewer output frames are produced.
// Use a tolerance when comparing frame counts. See b/343476417#comment5.
assertThat(result.exportResult.videoFrameCount) assertThat(result.exportResult.videoFrameCount)
.isEqualTo(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S.videoFrameCount); .isWithin(2)
.of(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_320W_240H_15S.videoFrameCount);
assertThat(new File(result.filePath).length()).isGreaterThan(0); assertThat(new File(result.filePath).length()).isGreaterThan(0);
} }

View File

@ -99,7 +99,10 @@ public class TransformerPauseResumeTest {
ExportResult exportResult = result.exportResult; ExportResult exportResult = result.exportResult;
assertThat(exportResult.processedInputs).hasSize(4); assertThat(exportResult.processedInputs).hasSize(4);
assertThat(exportResult.videoFrameCount).isEqualTo(MP4_ASSET_FRAME_COUNT); // Rarely, MediaCodec decoders output frames in the wrong order.
// When the MediaCodec encoder sees frames in the wrong order, fewer output frames are produced.
// Use a tolerance when comparing frame counts. See b/343476417#comment5.
assertThat(exportResult.videoFrameCount).isWithin(2).of(MP4_ASSET_FRAME_COUNT);
// The first processed media item corresponds to remuxing previous output video. // The first processed media item corresponds to remuxing previous output video.
assertThat(exportResult.processedInputs.get(0).audioDecoderName).isNull(); assertThat(exportResult.processedInputs.get(0).audioDecoderName).isNull();
assertThat(exportResult.processedInputs.get(0).videoDecoderName).isNull(); assertThat(exportResult.processedInputs.get(0).videoDecoderName).isNull();
@ -197,8 +200,12 @@ public class TransformerPauseResumeTest {
.isEqualTo(exportResultWithoutResume.audioEncoderName); .isEqualTo(exportResultWithoutResume.audioEncoderName);
assertThat(exportResultWithResume.videoEncoderName) assertThat(exportResultWithResume.videoEncoderName)
.isEqualTo(exportResultWithoutResume.videoEncoderName); .isEqualTo(exportResultWithoutResume.videoEncoderName);
// Rarely, MediaCodec decoders output frames in the wrong order.
// When the MediaCodec encoder sees frames in the wrong order, fewer output frames are produced.
// Use a tolerance when comparing frame counts. See b/343476417#comment5.
assertThat(exportResultWithResume.videoFrameCount) assertThat(exportResultWithResume.videoFrameCount)
.isEqualTo(exportResultWithoutResume.videoFrameCount); .isWithin(2)
.of(exportResultWithoutResume.videoFrameCount);
// TODO: b/306595508 - Remove this expected difference once inconsistent behaviour of audio // TODO: b/306595508 - Remove this expected difference once inconsistent behaviour of audio
// encoder is fixed. // encoder is fixed.
int maxDiffExpectedInDurationMs = 2; int maxDiffExpectedInDurationMs = 2;
@ -252,8 +259,12 @@ public class TransformerPauseResumeTest {
.isEqualTo(exportResultWithoutResume.audioEncoderName); .isEqualTo(exportResultWithoutResume.audioEncoderName);
assertThat(exportResultWithResume.videoEncoderName) assertThat(exportResultWithResume.videoEncoderName)
.isEqualTo(exportResultWithoutResume.videoEncoderName); .isEqualTo(exportResultWithoutResume.videoEncoderName);
// Rarely, MediaCodec decoders output frames in the wrong order.
// When the MediaCodec encoder sees frames in the wrong order, fewer output frames are produced.
// Use a tolerance when comparing frame counts. See b/343476417#comment5.
assertThat(exportResultWithResume.videoFrameCount) assertThat(exportResultWithResume.videoFrameCount)
.isEqualTo(exportResultWithoutResume.videoFrameCount); .isWithin(2)
.of(exportResultWithoutResume.videoFrameCount);
int maxDiffExpectedInDurationMs = 2; int maxDiffExpectedInDurationMs = 2;
assertThat(exportResultWithResume.durationMs - exportResultWithoutResume.durationMs) assertThat(exportResultWithResume.durationMs - exportResultWithoutResume.durationMs)
.isLessThan(maxDiffExpectedInDurationMs); .isLessThan(maxDiffExpectedInDurationMs);
@ -293,7 +304,10 @@ public class TransformerPauseResumeTest {
ExportResult exportResult = result.exportResult; ExportResult exportResult = result.exportResult;
assertThat(exportResult.processedInputs).hasSize(6); assertThat(exportResult.processedInputs).hasSize(6);
int expectedVideoFrameCount = 2 * MP4_ASSET_FRAME_COUNT; int expectedVideoFrameCount = 2 * MP4_ASSET_FRAME_COUNT;
assertThat(exportResult.videoFrameCount).isEqualTo(expectedVideoFrameCount); // Rarely, MediaCodec decoders output frames in the wrong order.
// When the MediaCodec encoder sees frames in the wrong order, fewer output frames are produced.
// Use a tolerance when comparing frame counts. See b/343476417#comment5.
assertThat(exportResult.videoFrameCount).isWithin(2).of(expectedVideoFrameCount);
// The first processed media item corresponds to remuxing previous output video. // The first processed media item corresponds to remuxing previous output video.
assertThat(exportResult.processedInputs.get(0).audioDecoderName).isNull(); assertThat(exportResult.processedInputs.get(0).audioDecoderName).isNull();
assertThat(exportResult.processedInputs.get(0).videoDecoderName).isNull(); assertThat(exportResult.processedInputs.get(0).videoDecoderName).isNull();
@ -358,8 +372,12 @@ public class TransformerPauseResumeTest {
.isEqualTo(exportResultWithoutResume.audioEncoderName); .isEqualTo(exportResultWithoutResume.audioEncoderName);
assertThat(exportResultWithResume.videoEncoderName) assertThat(exportResultWithResume.videoEncoderName)
.isEqualTo(exportResultWithoutResume.videoEncoderName); .isEqualTo(exportResultWithoutResume.videoEncoderName);
// Rarely, MediaCodec decoders output frames in the wrong order.
// When the MediaCodec encoder sees frames in the wrong order, fewer output frames are produced.
// Use a tolerance when comparing frame counts. See b/343476417#comment5.
assertThat(exportResultWithResume.videoFrameCount) assertThat(exportResultWithResume.videoFrameCount)
.isEqualTo(exportResultWithoutResume.videoFrameCount); .isWithin(2)
.of(exportResultWithoutResume.videoFrameCount);
int maxDiffExpectedInDurationMs = 2; int maxDiffExpectedInDurationMs = 2;
assertThat(exportResultWithResume.durationMs - exportResultWithoutResume.durationMs) assertThat(exportResultWithResume.durationMs - exportResultWithoutResume.durationMs)
.isLessThan(maxDiffExpectedInDurationMs); .isLessThan(maxDiffExpectedInDurationMs);

View File

@ -15,6 +15,8 @@
*/ */
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW;
import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_ONLY; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_ONLY;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
@ -23,25 +25,22 @@ import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S;
import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY; import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY;
import static androidx.media3.transformer.TestUtil.addAudioDecoders;
import static androidx.media3.transformer.TestUtil.addAudioEncoders;
import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createAudioEffects;
import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor; import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor;
import static androidx.media3.transformer.TestUtil.getCompositionDumpFilePath; import static androidx.media3.transformer.TestUtil.getCompositionDumpFilePath;
import static androidx.media3.transformer.TestUtil.getDumpFileName; import static androidx.media3.transformer.TestUtil.getDumpFileName;
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.content.Context; import android.content.Context;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.TestTransformerBuilder; import androidx.media3.test.utils.TestTransformerBuilder;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
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.common.collect.ImmutableList;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
@ -57,15 +56,16 @@ public class CompositionExportTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Before @Rule
public void setUp() { public ShadowMediaCodecConfig shadowMediaCodecConfig =
addAudioDecoders(MimeTypes.AUDIO_RAW); ShadowMediaCodecConfig.withCodecs(
addAudioEncoders(MimeTypes.AUDIO_AAC); /* decoders= */ ImmutableList.of(CODEC_INFO_RAW),
} /* encoders= */ ImmutableList.of(CODEC_INFO_AAC));
@After @After
public void tearDown() { public void tearDown() {
removeEncodersAndDecoders(); // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
EncoderUtil.clearCachedEncoders();
} }
@Test @Test

View File

@ -17,6 +17,9 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil; import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AMR_NB;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW;
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED;
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED;
import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA; import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA;
@ -36,12 +39,9 @@ import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ELST_TRIM_IDR_DURA
import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY; import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY;
import static androidx.media3.transformer.TestUtil.FILE_WITH_SEF_SLOW_MOTION; import static androidx.media3.transformer.TestUtil.FILE_WITH_SEF_SLOW_MOTION;
import static androidx.media3.transformer.TestUtil.FILE_WITH_SUBTITLES; import static androidx.media3.transformer.TestUtil.FILE_WITH_SUBTITLES;
import static androidx.media3.transformer.TestUtil.addAudioDecoders;
import static androidx.media3.transformer.TestUtil.addAudioEncoders;
import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createAudioEffects;
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
import static androidx.media3.transformer.TestUtil.getDumpFileName; import static androidx.media3.transformer.TestUtil.getDumpFileName;
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
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.Transformer.PROGRESS_STATE_UNAVAILABLE;
@ -89,6 +89,7 @@ import androidx.media3.extractor.PositionHolder;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.FakeClock; import androidx.media3.test.utils.FakeClock;
import androidx.media3.test.utils.TestTransformerBuilder; import androidx.media3.test.utils.TestTransformerBuilder;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
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.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -105,7 +106,6 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -125,24 +125,42 @@ import org.robolectric.shadows.ShadowMediaCodec;
public final class MediaItemExportTest { public final class MediaItemExportTest {
private static final long TEST_TIMEOUT_SECONDS = 10; private static final long TEST_TIMEOUT_SECONDS = 10;
private static final String EXPECTED_CODEC_EXCEPTION_MESSAGE = "Unexpected format!";
private static final ShadowMediaCodec.CodecConfig THROWING_CODEC_CONFIG =
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 100_000,
/* outputBufferSize= */ 100_000,
new ShadowMediaCodec.CodecConfig.Codec() {
@Override
public void process(ByteBuffer byteBuffer, ByteBuffer byteBuffer1) {
throw new IllegalStateException();
}
@Override
public void onConfigured(
MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
throw new IllegalArgumentException(EXPECTED_CODEC_EXCEPTION_MESSAGE);
}
});
@Rule public final TemporaryFolder outputDir = new TemporaryFolder(); @Rule public final TemporaryFolder outputDir = new TemporaryFolder();
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Before @Rule
public void setUp() { public ShadowMediaCodecConfig shadowMediaCodecConfig =
addAudioDecoders(MimeTypes.AUDIO_RAW); ShadowMediaCodecConfig.withCodecs(
addAudioEncoders(MimeTypes.AUDIO_AAC); /* decoders= */ ImmutableList.of(CODEC_INFO_RAW), /* encoders= */ ImmutableList.of());
}
@After @After
public void tearDown() { public void tearDown() {
removeEncodersAndDecoders(); // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
EncoderUtil.clearCachedEncoders();
} }
@Test @Test
public void start_gapOnlyExport_outputsSilence() throws Exception { public void start_gapOnlyExport_outputsSilence() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
Transformer transformer = Transformer transformer =
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build(); new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
@ -275,6 +293,7 @@ public final class MediaItemExportTest {
@Test @Test
public void start_trimOptimizationEnabled_fileNotMp4_fallbackToNormalExport() throws Exception { public void start_trimOptimizationEnabled_fileNotMp4_fallbackToNormalExport() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
Transformer transformer = Transformer transformer =
new TestTransformerBuilder(context) new TestTransformerBuilder(context)
@ -441,6 +460,7 @@ public final class MediaItemExportTest {
@Test @Test
public void start_forceAudioTrackAndRemoveAudioWithEffects_generatesSilentAudio() public void start_forceAudioTrackAndRemoveAudioWithEffects_generatesSilentAudio()
throws Exception { throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
Transformer transformer = Transformer transformer =
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build(); new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
@ -497,6 +517,7 @@ public final class MediaItemExportTest {
@Test @Test
public void start_forceAudioTrackOnVideoOnly_generatesSilentAudio() throws Exception { public void start_forceAudioTrackOnVideoOnly_generatesSilentAudio() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
Transformer transformer = Transformer transformer =
new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build(); new TestTransformerBuilder(context).setMuxerFactory(muxerFactory).build();
@ -521,6 +542,7 @@ public final class MediaItemExportTest {
@Test @Test
public void exportAudio_muxerReceivesExpectedNumberOfBytes() throws Exception { public void exportAudio_muxerReceivesExpectedNumberOfBytes() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
AtomicInteger bytesSeenByEffect = new AtomicInteger(); AtomicInteger bytesSeenByEffect = new AtomicInteger();
Transformer transformer = Transformer transformer =
@ -540,6 +562,7 @@ public final class MediaItemExportTest {
@Test @Test
public void start_adjustSampleRate_completesSuccessfully() throws Exception { public void start_adjustSampleRate_completesSuccessfully() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setOutputSampleRateHz(48000); sonicAudioProcessor.setOutputSampleRateHz(48000);
@ -569,6 +592,7 @@ public final class MediaItemExportTest {
@Test @Test
public void adjustAudioSpeed_toDoubleSpeed_returnsExpectedNumberOfSamples() throws Exception { public void adjustAudioSpeed_toDoubleSpeed_returnsExpectedNumberOfSamples() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setSpeed(2f); sonicAudioProcessor.setSpeed(2f);
@ -599,6 +623,7 @@ public final class MediaItemExportTest {
@Test @Test
public void start_withRawBigEndianAudioInput_completesSuccessfully() throws Exception { public void start_withRawBigEndianAudioInput_completesSuccessfully() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
ToInt16PcmAudioProcessor toInt16PcmAudioProcessor = new ToInt16PcmAudioProcessor(); ToInt16PcmAudioProcessor toInt16PcmAudioProcessor = new ToInt16PcmAudioProcessor();
Transformer transformer = Transformer transformer =
@ -622,6 +647,7 @@ public final class MediaItemExportTest {
@Test @Test
public void start_singleMediaItemAndTransmux_ignoresTransmux() throws Exception { public void start_singleMediaItemAndTransmux_ignoresTransmux() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor(); SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
sonicAudioProcessor.setOutputSampleRateHz(48000); sonicAudioProcessor.setOutputSampleRateHz(48000);
@ -702,6 +728,7 @@ public final class MediaItemExportTest {
@Test @Test
public void start_withMultipleListeners_callsEachOnFallback() throws Exception { public void start_withMultipleListeners_callsEachOnFallback() throws Exception {
shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false);
ArgumentCaptor<Composition> compositionArgumentCaptor = ArgumentCaptor<Composition> compositionArgumentCaptor =
ArgumentCaptor.forClass(Composition.class); ArgumentCaptor.forClass(Composition.class);
@ -804,29 +831,11 @@ public final class MediaItemExportTest {
@Test @Test
public void start_whenCodecFailsToConfigure_completesWithError() throws Exception { public void start_whenCodecFailsToConfigure_completesWithError() throws Exception {
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false); shadowMediaCodecConfig.addEncoders(CODEC_INFO_AAC);
String expectedFailureMessage = "Format not valid. AMR NB (3gpp)";
ShadowMediaCodec.CodecConfig throwOnConfigureCodecConfig =
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 100_000,
/* outputBufferSize= */ 100_000,
/* codec= */ new ShadowMediaCodec.CodecConfig.Codec() {
@Override
public void process(ByteBuffer in, ByteBuffer out) {
out.put(in);
}
@Override
public void onConfigured(
MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
// MediaCodec#configure documented to throw IAE if format is invalid.
throw new IllegalArgumentException(expectedFailureMessage);
}
});
// Add the AMR_NB encoder that throws when configured. // Add the AMR_NB encoder that throws when configured.
addAudioEncoders(throwOnConfigureCodecConfig, MimeTypes.AUDIO_AMR_NB); shadowMediaCodecConfig.addCodec(
CODEC_INFO_AMR_NB, /* isEncoder= */ true, THROWING_CODEC_CONFIG);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ false);
Transformer transformer = Transformer transformer =
new TestTransformerBuilder(context) new TestTransformerBuilder(context)
.setMuxerFactory(muxerFactory) .setMuxerFactory(muxerFactory)
@ -841,7 +850,10 @@ public final class MediaItemExportTest {
assertThat(exception.errorCode) assertThat(exception.errorCode)
.isEqualTo(ExportException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED); .isEqualTo(ExportException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
assertThat(exception).hasCauseThat().hasMessageThat().isEqualTo(expectedFailureMessage); assertThat(exception)
.hasCauseThat()
.hasMessageThat()
.isEqualTo(EXPECTED_CODEC_EXCEPTION_MESSAGE);
} }
@Test @Test
@ -866,11 +878,9 @@ public final class MediaItemExportTest {
public void public void
start_withAudioFormatUnsupportedByMuxer_ignoresDisabledFallbackAndCompletesSuccessfully() start_withAudioFormatUnsupportedByMuxer_ignoresDisabledFallbackAndCompletesSuccessfully()
throws Exception { throws Exception {
removeEncodersAndDecoders();
addAudioDecoders(MimeTypes.AUDIO_RAW);
// RAW supported by encoder, unsupported by muxer. // RAW supported by encoder, unsupported by muxer.
// AAC supported by encoder and muxer. // AAC supported by encoder and muxer.
addAudioEncoders(MimeTypes.AUDIO_RAW, MimeTypes.AUDIO_AAC); shadowMediaCodecConfig.addEncoders(CODEC_INFO_RAW, CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
Transformer.Listener mockListener = mock(Transformer.Listener.class); Transformer.Listener mockListener = mock(Transformer.Listener.class);
@ -901,11 +911,9 @@ public final class MediaItemExportTest {
@Test @Test
public void start_withAudioFormatUnsupportedByMuxer_fallsBackAndCompletesSuccessfully() public void start_withAudioFormatUnsupportedByMuxer_fallsBackAndCompletesSuccessfully()
throws Exception { throws Exception {
removeEncodersAndDecoders();
addAudioDecoders(MimeTypes.AUDIO_RAW);
// RAW supported by encoder, unsupported by muxer. // RAW supported by encoder, unsupported by muxer.
// AAC supported by encoder and muxer. // AAC supported by encoder and muxer.
addAudioEncoders(MimeTypes.AUDIO_RAW, MimeTypes.AUDIO_AAC); shadowMediaCodecConfig.addEncoders(CODEC_INFO_RAW, CODEC_INFO_AAC);
CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); CapturingMuxer.Factory muxerFactory = new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
Transformer.Listener mockListener = mock(Transformer.Listener.class); Transformer.Listener mockListener = mock(Transformer.Listener.class);
@ -1198,9 +1206,7 @@ public final class MediaItemExportTest {
@Test @Test
public void analyze_audioOnlyWithItemEffect_completesSuccessfully() throws Exception { public void analyze_audioOnlyWithItemEffect_completesSuccessfully() throws Exception {
removeEncodersAndDecoders(); shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG);
addAudioDecoders(MimeTypes.AUDIO_RAW);
addThrowingAudioEncoder(MimeTypes.AUDIO_AAC);
Transformer transformer = Transformer transformer =
ExperimentalAnalyzerModeFactory.buildAnalyzer( ExperimentalAnalyzerModeFactory.buildAnalyzer(
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build()); getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
@ -1220,9 +1226,7 @@ public final class MediaItemExportTest {
@Test @Test
public void analyze_audioOnlyWithCompositionEffect_completesSuccessfully() throws Exception { public void analyze_audioOnlyWithCompositionEffect_completesSuccessfully() throws Exception {
removeEncodersAndDecoders(); shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG);
addAudioDecoders(MimeTypes.AUDIO_RAW);
addThrowingAudioEncoder(MimeTypes.AUDIO_AAC);
Transformer transformer = Transformer transformer =
ExperimentalAnalyzerModeFactory.buildAnalyzer( ExperimentalAnalyzerModeFactory.buildAnalyzer(
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build()); getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
@ -1247,9 +1251,7 @@ public final class MediaItemExportTest {
@Test @Test
public void analyze_audioOnly_itemAndMixerOutputMatch() throws Exception { public void analyze_audioOnly_itemAndMixerOutputMatch() throws Exception {
removeEncodersAndDecoders(); shadowMediaCodecConfig.addCodec(CODEC_INFO_AAC, /* isEncoder= */ true, THROWING_CODEC_CONFIG);
addAudioDecoders(MimeTypes.AUDIO_RAW);
addThrowingAudioEncoder(MimeTypes.AUDIO_AAC);
Transformer transformer = Transformer transformer =
ExperimentalAnalyzerModeFactory.buildAnalyzer( ExperimentalAnalyzerModeFactory.buildAnalyzer(
getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build()); getApplicationContext(), new TestTransformerBuilder(getApplicationContext()).build());
@ -1567,27 +1569,6 @@ public final class MediaItemExportTest {
/* modifications...= */ "transmuxed")); /* modifications...= */ "transmuxed"));
} }
private static void addThrowingAudioEncoder(String mimeType) {
ShadowMediaCodec.CodecConfig.Codec codec =
new ShadowMediaCodec.CodecConfig.Codec() {
@Override
public void process(ByteBuffer byteBuffer, ByteBuffer byteBuffer1) {
throw new IllegalStateException();
}
@Override
public void onConfigured(
MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
throw new IllegalStateException();
}
};
addAudioEncoders(
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec),
mimeType);
}
private static AudioProcessor createByteCountingAudioProcessor(AtomicInteger byteCount) { private static AudioProcessor createByteCountingAudioProcessor(AtomicInteger byteCount) {
return new TeeAudioProcessor( return new TeeAudioProcessor(
new TeeAudioProcessor.AudioBufferSink() { new TeeAudioProcessor.AudioBufferSink() {

View File

@ -16,24 +16,23 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW;
import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO;
import static androidx.media3.transformer.TestUtil.addAudioDecoders;
import static androidx.media3.transformer.TestUtil.addAudioEncoders;
import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createAudioEffects;
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath; import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath;
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import android.content.Context; import android.content.Context;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.TestTransformerBuilder; import androidx.media3.test.utils.TestTransformerBuilder;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -46,7 +45,6 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
@ -126,6 +124,12 @@ public final class ParameterizedAudioExportTest {
@Rule public final TemporaryFolder outputDir = new TemporaryFolder(); @Rule public final TemporaryFolder outputDir = new TemporaryFolder();
@Rule
public ShadowMediaCodecConfig shadowMediaCodecConfig =
ShadowMediaCodecConfig.withCodecs(
/* decoders= */ ImmutableList.of(CODEC_INFO_RAW),
/* encoders= */ ImmutableList.of(CODEC_INFO_AAC));
@Parameter public SequenceConfig sequence; @Parameter public SequenceConfig sequence;
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@ -133,15 +137,10 @@ public final class ParameterizedAudioExportTest {
private final CapturingMuxer.Factory muxerFactory = private final CapturingMuxer.Factory muxerFactory =
new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true); new CapturingMuxer.Factory(/* handleAudioAsPcm= */ true);
@Before
public void setUp() {
addAudioDecoders(MimeTypes.AUDIO_RAW);
addAudioEncoders(MimeTypes.AUDIO_AAC);
}
@After @After
public void tearDown() { public void tearDown() {
removeEncodersAndDecoders(); // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
EncoderUtil.clearCachedEncoders();
} }
@Test @Test

View File

@ -16,6 +16,8 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW;
import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_AMR_NB; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_AMR_NB;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
@ -23,24 +25,20 @@ import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_STEREO_48000KH
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO;
import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY; import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY;
import static androidx.media3.transformer.TestUtil.addAudioDecoders;
import static androidx.media3.transformer.TestUtil.addAudioEncoders;
import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createAudioEffects;
import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor; import static androidx.media3.transformer.TestUtil.createVolumeScalingAudioProcessor;
import static androidx.media3.transformer.TestUtil.getDumpFileName; import static androidx.media3.transformer.TestUtil.getDumpFileName;
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeFalse;
import android.content.Context; import android.content.Context;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.TestTransformerBuilder; import androidx.media3.test.utils.TestTransformerBuilder;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
@ -93,17 +91,18 @@ public final class ParameterizedItemExportTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
// Only add RAW decoder, so non-RAW audio has no options for decoding. // Only add RAW decoder, so non-RAW audio has no options for decoding.
addAudioDecoders(MimeTypes.AUDIO_RAW);
// Use an AAC encoder because muxer supports AAC. // Use an AAC encoder because muxer supports AAC.
addAudioEncoders(MimeTypes.AUDIO_AAC); @Rule
} public ShadowMediaCodecConfig shadowMediaCodecConfig =
ShadowMediaCodecConfig.withCodecs(
/* decoders= */ ImmutableList.of(CODEC_INFO_RAW),
/* encoders= */ ImmutableList.of(CODEC_INFO_AAC));
@After @After
public void tearDown() { public void tearDown() {
removeEncodersAndDecoders(); // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
EncoderUtil.clearCachedEncoders();
} }
@Test @Test

View File

@ -16,36 +16,34 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_AAC;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.CODEC_INFO_RAW;
import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX; import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_STEREO_48000KHZ; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_STEREO_48000KHZ;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW_VIDEO;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S; import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S;
import static androidx.media3.transformer.TestUtil.addAudioDecoders;
import static androidx.media3.transformer.TestUtil.addAudioEncoders;
import static androidx.media3.transformer.TestUtil.createAudioEffects; import static androidx.media3.transformer.TestUtil.createAudioEffects;
import static androidx.media3.transformer.TestUtil.createChannelCountChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.createChannelCountChangingAudioProcessor;
import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor; import static androidx.media3.transformer.TestUtil.createPitchChangingAudioProcessor;
import static androidx.media3.transformer.TestUtil.getDumpFileName; import static androidx.media3.transformer.TestUtil.getDumpFileName;
import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath; import static androidx.media3.transformer.TestUtil.getSequenceDumpFilePath;
import static androidx.media3.transformer.TestUtil.removeEncodersAndDecoders;
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;
import android.content.Context; import android.content.Context;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.effect.RgbFilter; import androidx.media3.effect.RgbFilter;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.TestTransformerBuilder; import androidx.media3.test.utils.TestTransformerBuilder;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
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.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -69,15 +67,16 @@ public final class SequenceExportTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Before @Rule
public void setUp() { public ShadowMediaCodecConfig shadowMediaCodecConfig =
addAudioDecoders(MimeTypes.AUDIO_RAW); ShadowMediaCodecConfig.withCodecs(
addAudioEncoders(MimeTypes.AUDIO_AAC); /* decoders= */ ImmutableList.of(CODEC_INFO_RAW),
} /* encoders= */ ImmutableList.of(CODEC_INFO_AAC));
@After @After
public void tearDown() { public void tearDown() {
removeEncodersAndDecoders(); // TODO(b/406463016): Investigate moving this call to ShadowMediaCodecConfig#after() method.
EncoderUtil.clearCachedEncoders();
} }
@Test @Test

View File

@ -15,20 +15,14 @@
*/ */
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.configureShadowMediaCodec;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.audio.AudioProcessor;
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.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
import java.util.StringJoiner; import java.util.StringJoiner;
import org.robolectric.shadows.ShadowMediaCodec;
import org.robolectric.shadows.ShadowMediaCodecList;
/** Utility class for {@link Transformer} unit tests */ /** Utility class for {@link Transformer} unit tests */
@UnstableApi @UnstableApi
@ -142,83 +136,4 @@ public final class TestUtil {
+ "." + "."
+ DUMP_FILE_EXTENSION; + DUMP_FILE_EXTENSION;
} }
/**
* Adds an audio decoder for each {@linkplain MimeTypes mime type}.
*
* <p>Input buffers are copied directly to the output.
*
* <p>When adding codecs, {@link #removeEncodersAndDecoders()} should be called in the test class
* {@link org.junit.After @After} method.
*/
public static void addAudioDecoders(String... mimeTypes) {
for (String mimeType : mimeTypes) {
addCodec(
mimeType,
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 150_000,
/* outputBufferSize= */ 150_000,
/* codec= */ (in, out) -> out.put(in)),
/* colorFormats= */ ImmutableList.of(),
/* isDecoder= */ true);
}
}
/**
* Adds an audio encoder for each {@linkplain MimeTypes mime type}.
*
* <p>Input buffers are copied directly to the output.
*
* <p>When adding codecs, {@link #removeEncodersAndDecoders()} should be called in the test class
* {@link org.junit.After @After} method.
*/
public static void addAudioEncoders(String... mimeTypes) {
addAudioEncoders(
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ 150_000,
/* outputBufferSize= */ 150_000,
/* codec= */ (in, out) -> out.put(in)),
mimeTypes);
}
/**
* Adds an audio encoder for each {@linkplain MimeTypes mime type}.
*
* <p>Input buffers are handled according to the {@link
* org.robolectric.shadows.ShadowMediaCodec.CodecConfig} provided.
*
* <p>When adding codecs, {@link #removeEncodersAndDecoders()} should be called in the test's
* {@link org.junit.After @After} method.
*/
public static void addAudioEncoders(
ShadowMediaCodec.CodecConfig codecConfig, String... mimeTypes) {
for (String mimeType : mimeTypes) {
addCodec(
mimeType, codecConfig, /* colorFormats= */ ImmutableList.of(), /* isDecoder= */ false);
}
}
/** Clears all cached codecs. */
public static void removeEncodersAndDecoders() {
ShadowMediaCodec.clearCodecs();
ShadowMediaCodecList.reset();
EncoderUtil.clearCachedEncoders();
}
private static void addCodec(
String mimeType,
ShadowMediaCodec.CodecConfig codecConfig,
ImmutableList<Integer> colorFormats,
boolean isDecoder) {
String codecName =
Util.formatInvariant(
isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-'));
configureShadowMediaCodec(
codecName,
mimeType,
!isDecoder,
/* profileLevels= */ ImmutableList.of(),
colorFormats,
codecConfig);
}
} }