Check targetSdkVersion for frame dropping workaround.

Based on
https://developer.android.com/reference/android/media/MediaCodec#using-an-output-surface,
frame dropping behaviour depends on the target SDK version.
After this change transformer will only use
MediaFormat#KEY_ALLOW_FRAME_DROP if both the target and system SDK
version are at least 29 and default to its pre 29 behaviour where each
decoder output frame must be processed before a new one is rendered
to prevent frame dropping otherwise.

Also remove deprecated Transformer.Builder constructor without a
context and the context setter.

PiperOrigin-RevId: 453971097
This commit is contained in:
hschlueter 2022-06-09 18:22:39 +00:00 committed by Marc Baechinger
parent cc1f32d094
commit a105d033a7
17 changed files with 104 additions and 80 deletions

View File

@ -191,6 +191,8 @@
`DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise. `DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise.
* Remove constructor `DefaultTrackSelector(ExoTrackSelection.Factory)`. * Remove constructor `DefaultTrackSelector(ExoTrackSelection.Factory)`.
Use `DefaultTrackSelector(Context, ExoTrackSelection.Factory)` instead. Use `DefaultTrackSelector(Context, ExoTrackSelection.Factory)` instead.
* Remove `Transformer.Builder.setContext`. The `Context` should be passed
to the `Transformer.Builder` constructor instead.
### 1.0.0-alpha03 (2022-03-14) ### 1.0.0-alpha03 (2022-03-14)

View File

@ -260,6 +260,7 @@ public final class TransformerActivity extends AppCompatActivity {
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)) .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
.setEncoderFactory( .setEncoderFactory(
new DefaultEncoderFactory( new DefaultEncoderFactory(
/* context= */ this,
EncoderSelector.DEFAULT, EncoderSelector.DEFAULT,
/* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))); /* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK)));

View File

@ -224,8 +224,8 @@ public final class AndroidTestUtil {
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
/** Creates an instance that wraps {@link DefaultEncoderFactory}. */ /** Creates an instance that wraps {@link DefaultEncoderFactory}. */
public ForceEncodeEncoderFactory() { public ForceEncodeEncoderFactory(Context context) {
encoderFactory = Codec.EncoderFactory.DEFAULT; encoderFactory = new DefaultEncoderFactory(context);
} }
/** /**

View File

@ -42,7 +42,8 @@ public class TransformerEndToEndTest {
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setResolution(480).build()) new TransformationRequest.Builder().setResolution(480).build())
.setEncoderFactory( .setEncoderFactory(
new DefaultEncoderFactory(EncoderSelector.DEFAULT, /* enableFallback= */ false)) new DefaultEncoderFactory(
context, EncoderSelector.DEFAULT, /* enableFallback= */ false))
.build(); .build();
// Result of the following command: // Result of the following command:
// ffprobe -count_frames -select_streams v:0 -show_entries stream=nb_read_frames sample.mp4 // ffprobe -count_frames -select_streams v:0 -show_entries stream=nb_read_frames sample.mp4
@ -67,7 +68,8 @@ public class TransformerEndToEndTest {
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setResolution(480).build()) new TransformationRequest.Builder().setResolution(480).build())
.setEncoderFactory( .setEncoderFactory(
new DefaultEncoderFactory(EncoderSelector.DEFAULT, /* enableFallback= */ false)) new DefaultEncoderFactory(
context, EncoderSelector.DEFAULT, /* enableFallback= */ false))
.build(); .build();
long expectedDurationMs = 967; long expectedDurationMs = 967;

View File

@ -48,7 +48,7 @@ public final class RepeatedTranscodeTransformationTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setRotationDegrees(45).build()) new TransformationRequest.Builder().setRotationDegrees(45).build())
.setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory()) .setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory(context))
.build()) .build())
.build(); .build();
@ -78,7 +78,7 @@ public final class RepeatedTranscodeTransformationTest {
.setRemoveAudio(true) .setRemoveAudio(true)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setRotationDegrees(45).build()) new TransformationRequest.Builder().setRotationDegrees(45).build())
.setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory()) .setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory(context))
.build()) .build())
.build(); .build();
@ -107,7 +107,7 @@ public final class RepeatedTranscodeTransformationTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setRemoveVideo(true) .setRemoveVideo(true)
.setTransformationRequest(new TransformationRequest.Builder().build()) .setTransformationRequest(new TransformationRequest.Builder().build())
.setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory()) .setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory(context))
.build()) .build())
.build(); .build();

View File

@ -52,7 +52,7 @@ public final class TranscodeQualityTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_H264).build()) new TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_H264).build())
.setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory()) .setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory(context))
.setRemoveAudio(true) .setRemoveAudio(true)
.build(); .build();
@ -119,7 +119,7 @@ public final class TranscodeQualityTest {
new Transformer.Builder(context) new Transformer.Builder(context)
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_H264).build()) new TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_H264).build())
.setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory()) .setEncoderFactory(new AndroidTestUtil.ForceEncodeEncoderFactory(context))
.setRemoveAudio(true) .setRemoveAudio(true)
.build(); .build();

View File

@ -52,7 +52,9 @@ public class TransformationTest {
String testId = TAG + "_transform"; String testId = TAG + "_transform";
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = Transformer transformer =
new Transformer.Builder(context).setEncoderFactory(new ForceEncodeEncoderFactory()).build(); new Transformer.Builder(context)
.setEncoderFactory(new ForceEncodeEncoderFactory(context))
.build();
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
.setMaybeCalculateSsim(true) .setMaybeCalculateSsim(true)
.build() .build()
@ -80,6 +82,7 @@ public class TransformationTest {
.setEncoderFactory( .setEncoderFactory(
new ForceEncodeEncoderFactory( new ForceEncodeEncoderFactory(
/* wrappedEncoderFactory= */ new DefaultEncoderFactory( /* wrappedEncoderFactory= */ new DefaultEncoderFactory(
context,
EncoderSelector.DEFAULT, EncoderSelector.DEFAULT,
new VideoEncoderSettings.Builder().setBitrate(5_000_000).build(), new VideoEncoderSettings.Builder().setBitrate(5_000_000).build(),
/* enableFallback= */ true))) /* enableFallback= */ true)))
@ -104,7 +107,9 @@ public class TransformationTest {
} }
Transformer transformer = Transformer transformer =
new Transformer.Builder(context).setEncoderFactory(new ForceEncodeEncoderFactory()).build(); new Transformer.Builder(context)
.setEncoderFactory(new ForceEncodeEncoderFactory(context))
.build();
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
.setMaybeCalculateSsim(true) .setMaybeCalculateSsim(true)
.setTimeoutSeconds(180) .setTimeoutSeconds(180)
@ -125,7 +130,9 @@ public class TransformationTest {
return; return;
} }
Transformer transformer = Transformer transformer =
new Transformer.Builder(context).setEncoderFactory(new ForceEncodeEncoderFactory()).build(); new Transformer.Builder(context)
.setEncoderFactory(new ForceEncodeEncoderFactory(context))
.build();
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
.setMaybeCalculateSsim(true) .setMaybeCalculateSsim(true)
.setTimeoutSeconds(180) .setTimeoutSeconds(180)
@ -139,7 +146,7 @@ public class TransformationTest {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setEncoderFactory(new ForceEncodeEncoderFactory()) .setEncoderFactory(new ForceEncodeEncoderFactory(context))
.setRemoveAudio(true) .setRemoveAudio(true)
.build(); .build();
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)
@ -154,7 +161,7 @@ public class TransformationTest {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = Transformer transformer =
new Transformer.Builder(context) new Transformer.Builder(context)
.setEncoderFactory(new ForceEncodeEncoderFactory()) .setEncoderFactory(new ForceEncodeEncoderFactory(context))
.setRemoveVideo(true) .setRemoveVideo(true)
.build(); .build();
new TransformerAndroidTestRunner.Builder(context, transformer) new TransformerAndroidTestRunner.Builder(context, transformer)

View File

@ -118,6 +118,7 @@ public class BitrateAnalysisTest {
.setEncoderFactory( .setEncoderFactory(
new AndroidTestUtil.ForceEncodeEncoderFactory( new AndroidTestUtil.ForceEncodeEncoderFactory(
/* wrappedEncoderFactory= */ new DefaultEncoderFactory( /* wrappedEncoderFactory= */ new DefaultEncoderFactory(
context,
EncoderSelector.DEFAULT, EncoderSelector.DEFAULT,
new VideoEncoderSettings.Builder() new VideoEncoderSettings.Builder()
.setBitrate(bitrate) .setBitrate(bitrate)

View File

@ -128,6 +128,7 @@ public class EncoderPerformanceAnalysisTest {
.setEncoderFactory( .setEncoderFactory(
new AndroidTestUtil.ForceEncodeEncoderFactory( new AndroidTestUtil.ForceEncodeEncoderFactory(
/* wrappedEncoderFactory= */ new DefaultEncoderFactory( /* wrappedEncoderFactory= */ new DefaultEncoderFactory(
context,
EncoderSelector.DEFAULT, EncoderSelector.DEFAULT,
new VideoEncoderSettings.Builder() new VideoEncoderSettings.Builder()
.setEncoderPerformanceParameters(operatingRate, priority) .setEncoderPerformanceParameters(operatingRate, priority)

View File

@ -41,9 +41,6 @@ public interface Codec {
/** A factory for {@linkplain Codec decoder} instances. */ /** A factory for {@linkplain Codec decoder} instances. */
interface DecoderFactory { interface DecoderFactory {
/** A default {@code DecoderFactory} implementation. */
DecoderFactory DEFAULT = new DefaultDecoderFactory();
/** /**
* Returns a {@link Codec} for audio decoding. * Returns a {@link Codec} for audio decoding.
* *
@ -72,9 +69,6 @@ public interface Codec {
/** A factory for {@linkplain Codec encoder} instances. */ /** A factory for {@linkplain Codec encoder} instances. */
interface EncoderFactory { interface EncoderFactory {
/** A default {@code EncoderFactory} implementation. */
EncoderFactory DEFAULT = new DefaultEncoderFactory();
/** /**
* Returns a {@link Codec} for audio encoding. * Returns a {@link Codec} for audio encoding.
* *

View File

@ -21,6 +21,7 @@ import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.common.util.Util.SDK_INT;
import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
@ -54,6 +55,8 @@ public final class DefaultCodec implements Codec {
private final MediaCodec mediaCodec; private final MediaCodec mediaCodec;
@Nullable private final Surface inputSurface; @Nullable private final Surface inputSurface;
private final boolean decoderNeedsFrameDroppingWorkaround;
private @MonotonicNonNull Format outputFormat; private @MonotonicNonNull Format outputFormat;
@Nullable private ByteBuffer outputBuffer; @Nullable private ByteBuffer outputBuffer;
@ -65,6 +68,7 @@ public final class DefaultCodec implements Codec {
/** /**
* Creates a {@code DefaultCodec}. * Creates a {@code DefaultCodec}.
* *
* @param context The {@link Context}.
* @param configurationFormat The {@link Format} to configure the {@code DefaultCodec}. See {@link * @param configurationFormat The {@link Format} to configure the {@code DefaultCodec}. See {@link
* #getConfigurationFormat()}. The {@link Format#sampleMimeType sampleMimeType} must not be * #getConfigurationFormat()}. The {@link Format#sampleMimeType sampleMimeType} must not be
* {@code null}. * {@code null}.
@ -75,6 +79,7 @@ public final class DefaultCodec implements Codec {
* @param outputSurface The output {@link Surface} if the {@link MediaCodec} outputs to a surface. * @param outputSurface The output {@link Surface} if the {@link MediaCodec} outputs to a surface.
*/ */
public DefaultCodec( public DefaultCodec(
Context context,
Format configurationFormat, Format configurationFormat,
MediaFormat configurationMediaFormat, MediaFormat configurationMediaFormat,
String mediaCodecName, String mediaCodecName,
@ -110,6 +115,7 @@ public final class DefaultCodec implements Codec {
} }
this.mediaCodec = mediaCodec; this.mediaCodec = mediaCodec;
this.inputSurface = inputSurface; this.inputSurface = inputSurface;
decoderNeedsFrameDroppingWorkaround = decoderNeedsFrameDroppingWorkaround(context);
} }
@Override @Override
@ -124,15 +130,12 @@ public final class DefaultCodec implements Codec {
@Override @Override
public int getMaxPendingFrameCount() { public int getMaxPendingFrameCount() {
if (SDK_INT < 29) { if (decoderNeedsFrameDroppingWorkaround) {
// Prior to API 29, decoders may drop frames to keep their output surface from growing out of // Allow a maximum of one frame to be pending at a time to prevent frame dropping.
// bounds. From API 29, the {@link MediaFormat#KEY_ALLOW_FRAME_DROP} key prevents frame
// dropping even when the surface is full. Frame dropping is never desired, so allow a maximum
// of one frame to be pending at a time.
// TODO(b/226330223): Investigate increasing this limit. // TODO(b/226330223): Investigate increasing this limit.
return 1; return 1;
} }
if (Ascii.toUpperCase(mediaCodec.getCodecInfo().getCanonicalName()).startsWith("OMX.")) { if (Ascii.toUpperCase(getName()).startsWith("OMX.")) {
// Some OMX decoders don't correctly track their number of output buffers available, and get // Some OMX decoders don't correctly track their number of output buffers available, and get
// stuck if too many frames are rendered without being processed, so limit the number of // stuck if too many frames are rendered without being processed, so limit the number of
// pending frames to avoid getting stuck. This value is experimentally determined. See also // pending frames to avoid getting stuck. This value is experimentally determined. See also
@ -261,7 +264,7 @@ public final class DefaultCodec implements Codec {
* {@inheritDoc} * {@inheritDoc}
* *
* <p>This name is of the actual codec, which may not be the same as the {@code mediaCodecName} * <p>This name is of the actual codec, which may not be the same as the {@code mediaCodecName}
* passed to {@link #DefaultCodec(Format, MediaFormat, String, boolean, Surface)}. * passed to {@link #DefaultCodec(Context, Format, MediaFormat, String, boolean, Surface)}.
* *
* @see MediaCodec#getCanonicalName() * @see MediaCodec#getCanonicalName()
*/ */
@ -424,4 +427,13 @@ public final class DefaultCodec implements Codec {
codec.start(); codec.start();
TraceUtil.endSection(); TraceUtil.endSection();
} }
private static boolean decoderNeedsFrameDroppingWorkaround(Context context) {
// Prior to API 29, decoders may drop frames to keep their output surface from growing out of
// bounds. From API 29, if the app targets API 29 or later, the {@link
// MediaFormat#KEY_ALLOW_FRAME_DROP} key prevents frame dropping even when the surface is full.
// Frame dropping is never desired, so a workaround is needed for older API levels.
return SDK_INT < 29
|| context.getApplicationContext().getApplicationInfo().targetSdkVersion < 29;
}
} }

View File

@ -19,6 +19,8 @@ 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.SDK_INT; import static androidx.media3.common.util.Util.SDK_INT;
import android.annotation.SuppressLint;
import android.content.Context;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -30,6 +32,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** A default implementation of {@link Codec.DecoderFactory}. */ /** A default implementation of {@link Codec.DecoderFactory}. */
/* package */ final class DefaultDecoderFactory implements Codec.DecoderFactory { /* package */ final class DefaultDecoderFactory implements Codec.DecoderFactory {
private final Context context;
private final boolean decoderSupportsKeyAllowFrameDrop;
public DefaultDecoderFactory(Context context) {
this.context = context;
decoderSupportsKeyAllowFrameDrop =
SDK_INT >= 29
&& context.getApplicationContext().getApplicationInfo().targetSdkVersion >= 29;
}
@Override @Override
public Codec createForAudioDecoding(Format format) throws TransformationException { public Codec createForAudioDecoding(Format format) throws TransformationException {
MediaFormat mediaFormat = MediaFormat mediaFormat =
@ -45,9 +59,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
throw createTransformationException(format); throw createTransformationException(format);
} }
return new DefaultCodec( return new DefaultCodec(
format, mediaFormat, mediaCodecName, /* isDecoder= */ true, /* outputSurface= */ null); context,
format,
mediaFormat,
mediaCodecName,
/* isDecoder= */ true,
/* outputSurface= */ null);
} }
@SuppressLint("InlinedApi")
@Override @Override
public Codec createForVideoDecoding( public Codec createForVideoDecoding(
Format format, Surface outputSurface, boolean enableRequestSdrToneMapping) Format format, Surface outputSurface, boolean enableRequestSdrToneMapping)
@ -59,9 +79,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
MediaFormatUtil.maybeSetInteger( MediaFormatUtil.maybeSetInteger(
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
if (SDK_INT >= 29) { if (decoderSupportsKeyAllowFrameDrop) {
// On API levels over 29, Transformer decodes as many frames as possible in one render // This key ensures no frame dropping when the decoder's output surface is full. This allows
// cycle. This key ensures no frame dropping when the decoder's output surface is full. // transformer to decode as many frames as possible in one render cycle.
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0); mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
} }
if (SDK_INT >= 31 && enableRequestSdrToneMapping) { if (SDK_INT >= 31 && enableRequestSdrToneMapping) {
@ -75,7 +95,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
throw createTransformationException(format); throw createTransformationException(format);
} }
return new DefaultCodec( return new DefaultCodec(
format, mediaFormat, mediaCodecName, /* isDecoder= */ true, outputSurface); context, format, mediaFormat, mediaCodecName, /* isDecoder= */ true, outputSurface);
} }
@RequiresNonNull("#1.sampleMimeType") @RequiresNonNull("#1.sampleMimeType")

View File

@ -24,6 +24,7 @@ import static androidx.media3.common.util.Util.SDK_INT;
import static java.lang.Math.abs; import static java.lang.Math.abs;
import static java.lang.Math.floor; import static java.lang.Math.floor;
import android.content.Context;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.util.Pair; import android.util.Pair;
@ -46,6 +47,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
private static final int DEFAULT_FRAME_RATE = 30; private static final int DEFAULT_FRAME_RATE = 30;
private static final String TAG = "DefaultEncoderFactory"; private static final String TAG = "DefaultEncoderFactory";
private final Context context;
private final EncoderSelector videoEncoderSelector; private final EncoderSelector videoEncoderSelector;
private final VideoEncoderSettings requestedVideoEncoderSettings; private final VideoEncoderSettings requestedVideoEncoderSettings;
private final boolean enableFallback; private final boolean enableFallback;
@ -54,13 +56,14 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
* Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, a * Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, a
* default {@link VideoEncoderSettings}, and with format fallback enabled. * default {@link VideoEncoderSettings}, and with format fallback enabled.
*/ */
public DefaultEncoderFactory() { public DefaultEncoderFactory(Context context) {
this(EncoderSelector.DEFAULT, /* enableFallback= */ true); this(context, EncoderSelector.DEFAULT, /* enableFallback= */ true);
} }
/** Creates a new instance using a default {@link VideoEncoderSettings}. */ /** Creates a new instance using a default {@link VideoEncoderSettings}. */
public DefaultEncoderFactory(EncoderSelector videoEncoderSelector, boolean enableFallback) { public DefaultEncoderFactory(
this(videoEncoderSelector, VideoEncoderSettings.DEFAULT, enableFallback); Context context, EncoderSelector videoEncoderSelector, boolean enableFallback) {
this(context, videoEncoderSelector, VideoEncoderSettings.DEFAULT, enableFallback);
} }
/** /**
@ -81,14 +84,17 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
* and {@link VideoEncoderSettings#level} can cause codec configuration failure. Setting an * and {@link VideoEncoderSettings#level} can cause codec configuration failure. Setting an
* unsupported {@link VideoEncoderSettings#bitrateMode} may cause encoder instantiation failure. * unsupported {@link VideoEncoderSettings#bitrateMode} may cause encoder instantiation failure.
* *
* @param context The {@link Context}.
* @param videoEncoderSelector The {@link EncoderSelector}. * @param videoEncoderSelector The {@link EncoderSelector}.
* @param requestedVideoEncoderSettings The {@link VideoEncoderSettings}. * @param requestedVideoEncoderSettings The {@link VideoEncoderSettings}.
* @param enableFallback Whether to enable fallback. * @param enableFallback Whether to enable fallback.
*/ */
public DefaultEncoderFactory( public DefaultEncoderFactory(
Context context,
EncoderSelector videoEncoderSelector, EncoderSelector videoEncoderSelector,
VideoEncoderSettings requestedVideoEncoderSettings, VideoEncoderSettings requestedVideoEncoderSettings,
boolean enableFallback) { boolean enableFallback) {
this.context = context;
this.videoEncoderSelector = videoEncoderSelector; this.videoEncoderSelector = videoEncoderSelector;
this.requestedVideoEncoderSettings = requestedVideoEncoderSettings; this.requestedVideoEncoderSettings = requestedVideoEncoderSettings;
this.enableFallback = enableFallback; this.enableFallback = enableFallback;
@ -120,7 +126,12 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
throw createTransformationException(format); throw createTransformationException(format);
} }
return new DefaultCodec( return new DefaultCodec(
format, mediaFormat, mediaCodecName, /* isDecoder= */ false, /* outputSurface= */ null); context,
format,
mediaFormat,
mediaCodecName,
/* isDecoder= */ false,
/* outputSurface= */ null);
} }
@Override @Override
@ -210,6 +221,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
} }
return new DefaultCodec( return new DefaultCodec(
context,
format, format,
mediaFormat, mediaFormat,
encoderInfo.getName(), encoderInfo.getName(),

View File

@ -94,9 +94,7 @@ public final class Transformer {
public static final class Builder { public static final class Builder {
// Mandatory field. // Mandatory field.
// TODO(huangdarwin): Update @MonotonicNonNull to final after deprecated {@link private final Context context;
// #setContext(Context)} is removed.
private @MonotonicNonNull Context context;
// Optional fields. // Optional fields.
private MediaSource.@MonotonicNonNull Factory mediaSourceFactory; private MediaSource.@MonotonicNonNull Factory mediaSourceFactory;
@ -113,23 +111,6 @@ public final class Transformer {
private Codec.EncoderFactory encoderFactory; private Codec.EncoderFactory encoderFactory;
private Codec.DecoderFactory decoderFactory; private Codec.DecoderFactory decoderFactory;
/**
* @deprecated Use {@link #Builder(Context)} instead.
*/
@Deprecated
public Builder() {
muxerFactory = new FrameworkMuxer.Factory();
looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT;
listeners = new ListenerSet<>(looper, clock, (listener, flags) -> {});
encoderFactory = Codec.EncoderFactory.DEFAULT;
decoderFactory = Codec.DecoderFactory.DEFAULT;
debugViewProvider = DebugViewProvider.NONE;
containerMimeType = MimeTypes.VIDEO_MP4;
transformationRequest = new TransformationRequest.Builder().build();
videoFrameEffects = ImmutableList.of();
}
/** /**
* Creates a builder with default values. * Creates a builder with default values.
* *
@ -141,8 +122,8 @@ public final class Transformer {
looper = Util.getCurrentOrMainLooper(); looper = Util.getCurrentOrMainLooper();
clock = Clock.DEFAULT; clock = Clock.DEFAULT;
listeners = new ListenerSet<>(looper, clock, (listener, flags) -> {}); listeners = new ListenerSet<>(looper, clock, (listener, flags) -> {});
encoderFactory = Codec.EncoderFactory.DEFAULT; encoderFactory = new DefaultEncoderFactory(this.context);
decoderFactory = Codec.DecoderFactory.DEFAULT; decoderFactory = new DefaultDecoderFactory(this.context);
debugViewProvider = DebugViewProvider.NONE; debugViewProvider = DebugViewProvider.NONE;
containerMimeType = MimeTypes.VIDEO_MP4; containerMimeType = MimeTypes.VIDEO_MP4;
transformationRequest = new TransformationRequest.Builder().build(); transformationRequest = new TransformationRequest.Builder().build();
@ -167,15 +148,6 @@ public final class Transformer {
this.clock = transformer.clock; this.clock = transformer.clock;
} }
/**
* @deprecated Use {@link #Builder(Context)} instead.
*/
@Deprecated
public Builder setContext(Context context) {
this.context = context.getApplicationContext();
return this;
}
/** /**
* Sets the {@link TransformationRequest} which configures the editing and transcoding options. * Sets the {@link TransformationRequest} which configures the editing and transcoding options.
* *
@ -344,7 +316,7 @@ public final class Transformer {
/** /**
* Sets the {@link Codec.EncoderFactory} that will be used by the transformer. * Sets the {@link Codec.EncoderFactory} that will be used by the transformer.
* *
* <p>The default value is {@link Codec.EncoderFactory#DEFAULT}. * <p>The default value is a {@link DefaultEncoderFactory} instance.
* *
* @param encoderFactory The {@link Codec.EncoderFactory} instance. * @param encoderFactory The {@link Codec.EncoderFactory} instance.
* @return This builder. * @return This builder.
@ -357,7 +329,7 @@ public final class Transformer {
/** /**
* Sets the {@link Codec.DecoderFactory} that will be used by the transformer. * Sets the {@link Codec.DecoderFactory} that will be used by the transformer.
* *
* <p>The default value is {@link Codec.DecoderFactory#DEFAULT}. * <p>The default value is a {@link DefaultDecoderFactory} instance.
* *
* @param decoderFactory The {@link Codec.DecoderFactory} instance. * @param decoderFactory The {@link Codec.DecoderFactory} instance.
* @return This builder. * @return This builder.

View File

@ -16,9 +16,11 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
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.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
import androidx.media3.common.Format; import androidx.media3.common.Format;
@ -34,6 +36,7 @@ import org.robolectric.shadows.ShadowMediaCodecList;
/** Unit test for {@link DefaultEncoderFactory}. */ /** Unit test for {@link DefaultEncoderFactory}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DefaultEncoderFactoryTest { public class DefaultEncoderFactoryTest {
private final Context context = getApplicationContext();
@Before @Before
public void setUp() { public void setUp() {
@ -65,7 +68,7 @@ public class DefaultEncoderFactoryTest {
throws Exception { throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
Format actualVideoFormat = Format actualVideoFormat =
new DefaultEncoderFactory() new DefaultEncoderFactory(context)
.createForVideoEncoding( .createForVideoEncoding(
requestedVideoFormat, requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
@ -83,7 +86,7 @@ public class DefaultEncoderFactoryTest {
throws Exception { throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H265, 1920, 1080, 30); Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H265, 1920, 1080, 30);
Format actualVideoFormat = Format actualVideoFormat =
new DefaultEncoderFactory() new DefaultEncoderFactory(context)
.createForVideoEncoding( .createForVideoEncoding(
requestedVideoFormat, requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
@ -101,7 +104,7 @@ public class DefaultEncoderFactoryTest {
throws Exception { throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 3840, 2160, 60); Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 3840, 2160, 60);
Format actualVideoFormat = Format actualVideoFormat =
new DefaultEncoderFactory() new DefaultEncoderFactory(context)
.createForVideoEncoding( .createForVideoEncoding(
requestedVideoFormat, requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
@ -119,7 +122,7 @@ public class DefaultEncoderFactoryTest {
assertThrows( assertThrows(
TransformationException.class, TransformationException.class,
() -> () ->
new DefaultEncoderFactory() new DefaultEncoderFactory(context)
.createForVideoEncoding( .createForVideoEncoding(
requestedVideoFormat, requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H265))); /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H265)));
@ -136,6 +139,7 @@ public class DefaultEncoderFactoryTest {
TransformationException.class, TransformationException.class,
() -> () ->
new DefaultEncoderFactory( new DefaultEncoderFactory(
context,
/* videoEncoderSelector= */ mimeType -> ImmutableList.of(), /* videoEncoderSelector= */ mimeType -> ImmutableList.of(),
/* enableFallback= */ true) /* enableFallback= */ true)
.createForVideoEncoding( .createForVideoEncoding(

View File

@ -38,11 +38,6 @@ public class TransformerBuilderTest {
() -> new Transformer.Builder(context).setOutputMimeType(MimeTypes.VIDEO_UNKNOWN).build()); () -> new Transformer.Builder(context).setOutputMimeType(MimeTypes.VIDEO_UNKNOWN).build());
} }
@Test
public void build_withoutContext_throws() {
assertThrows(NullPointerException.class, () -> new Transformer.Builder().build());
}
@Test @Test
public void build_removeAudioAndVideo_throws() { public void build_removeAudioAndVideo_throws() {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();

View File

@ -750,7 +750,8 @@ public final class TransformerEndToEndTest {
return new Transformer.Builder(context) return new Transformer.Builder(context)
.setClock(clock) .setClock(clock)
.setMuxerFactory(new TestMuxerFactory()) .setMuxerFactory(new TestMuxerFactory())
.setEncoderFactory(new DefaultEncoderFactory(EncoderSelector.DEFAULT, enableFallback)); .setEncoderFactory(
new DefaultEncoderFactory(context, EncoderSelector.DEFAULT, enableFallback));
} }
private static void createEncodersAndDecoders() { private static void createEncodersAndDecoders() {