mirror of
https://github.com/androidx/media.git
synced 2025-05-04 06:00:37 +08:00
Remove ExoPlaybackException dependency from sample pipelines.
Use TransformationException for codec and audio processor initialization problems instead. PiperOrigin-RevId: 416765510
This commit is contained in:
parent
1633ad12a3
commit
a8dbc744db
@ -25,13 +25,10 @@ import android.media.MediaCodec.BufferInfo;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.PlaybackException;
|
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
|
||||||
import androidx.media3.exoplayer.audio.AudioProcessor;
|
import androidx.media3.exoplayer.audio.AudioProcessor;
|
||||||
import androidx.media3.exoplayer.audio.AudioProcessor.AudioFormat;
|
import androidx.media3.exoplayer.audio.AudioProcessor.AudioFormat;
|
||||||
import androidx.media3.exoplayer.audio.SonicAudioProcessor;
|
import androidx.media3.exoplayer.audio.SonicAudioProcessor;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@ -47,7 +44,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
private final Format inputFormat;
|
private final Format inputFormat;
|
||||||
private final Transformation transformation;
|
private final Transformation transformation;
|
||||||
private final int rendererIndex;
|
|
||||||
|
|
||||||
private final MediaCodecAdapterWrapper decoder;
|
private final MediaCodecAdapterWrapper decoder;
|
||||||
private final DecoderInputBuffer decoderInputBuffer;
|
private final DecoderInputBuffer decoderInputBuffer;
|
||||||
@ -67,11 +63,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
private boolean drainingSonicForSpeedChange;
|
private boolean drainingSonicForSpeedChange;
|
||||||
private float currentSpeed;
|
private float currentSpeed;
|
||||||
|
|
||||||
public AudioSamplePipeline(Format inputFormat, Transformation transformation, int rendererIndex)
|
public AudioSamplePipeline(Format inputFormat, Transformation transformation)
|
||||||
throws ExoPlaybackException {
|
throws TransformationException {
|
||||||
this.inputFormat = inputFormat;
|
this.inputFormat = inputFormat;
|
||||||
this.transformation = transformation;
|
this.transformation = transformation;
|
||||||
this.rendererIndex = rendererIndex;
|
|
||||||
decoderInputBuffer =
|
decoderInputBuffer =
|
||||||
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderInputBuffer =
|
encoderInputBuffer =
|
||||||
@ -82,19 +77,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
|
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
|
||||||
speedProvider = new SegmentSpeedProvider(inputFormat);
|
speedProvider = new SegmentSpeedProvider(inputFormat);
|
||||||
currentSpeed = speedProvider.getSpeed(0);
|
currentSpeed = speedProvider.getSpeed(0);
|
||||||
try {
|
this.decoder = MediaCodecAdapterWrapper.createForAudioDecoding(inputFormat);
|
||||||
this.decoder = MediaCodecAdapterWrapper.createForAudioDecoding(inputFormat);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO(internal b/192864511): Assign a specific error code.
|
|
||||||
throw ExoPlaybackException.createForRenderer(
|
|
||||||
e,
|
|
||||||
TAG,
|
|
||||||
rendererIndex,
|
|
||||||
inputFormat,
|
|
||||||
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
|
|
||||||
/* isRecoverable= */ false,
|
|
||||||
PlaybackException.ERROR_CODE_UNSPECIFIED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -109,7 +92,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean processData() throws ExoPlaybackException {
|
public boolean processData() throws TransformationException {
|
||||||
if (!ensureEncoderAndAudioProcessingConfigured()) {
|
if (!ensureEncoderAndAudioProcessingConfigured()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -292,7 +275,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
@EnsuresNonNullIf(
|
@EnsuresNonNullIf(
|
||||||
expression = {"encoder", "encoderInputAudioFormat"},
|
expression = {"encoder", "encoderInputAudioFormat"},
|
||||||
result = true)
|
result = true)
|
||||||
private boolean ensureEncoderAndAudioProcessingConfigured() throws ExoPlaybackException {
|
private boolean ensureEncoderAndAudioProcessingConfigured() throws TransformationException {
|
||||||
if (encoder != null && encoderInputAudioFormat != null) {
|
if (encoder != null && encoderInputAudioFormat != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -310,27 +293,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat);
|
outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat);
|
||||||
flushSonicAndSetSpeed(currentSpeed);
|
flushSonicAndSetSpeed(currentSpeed);
|
||||||
} catch (AudioProcessor.UnhandledAudioFormatException e) {
|
} catch (AudioProcessor.UnhandledAudioFormatException e) {
|
||||||
// TODO(internal b/192864511): Assign an adequate error code.
|
throw TransformationException.createForAudioProcessor(
|
||||||
throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED);
|
e,
|
||||||
|
"Sonic",
|
||||||
|
outputAudioFormat,
|
||||||
|
TransformationException.ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String audioMimeType =
|
encoder =
|
||||||
transformation.audioMimeType == null
|
MediaCodecAdapterWrapper.createForAudioEncoding(
|
||||||
? inputFormat.sampleMimeType
|
new Format.Builder()
|
||||||
: transformation.audioMimeType;
|
.setSampleMimeType(
|
||||||
try {
|
transformation.audioMimeType == null
|
||||||
encoder =
|
? inputFormat.sampleMimeType
|
||||||
MediaCodecAdapterWrapper.createForAudioEncoding(
|
: transformation.audioMimeType)
|
||||||
new Format.Builder()
|
.setSampleRate(outputAudioFormat.sampleRate)
|
||||||
.setSampleMimeType(audioMimeType)
|
.setChannelCount(outputAudioFormat.channelCount)
|
||||||
.setSampleRate(outputAudioFormat.sampleRate)
|
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
||||||
.setChannelCount(outputAudioFormat.channelCount)
|
.build());
|
||||||
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
|
||||||
.build());
|
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO(internal b/192864511): Assign an adequate error code.
|
|
||||||
throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED);
|
|
||||||
}
|
|
||||||
encoderInputAudioFormat = outputAudioFormat;
|
encoderInputAudioFormat = outputAudioFormat;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -351,17 +331,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
sonicAudioProcessor.flush();
|
sonicAudioProcessor.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExoPlaybackException createRendererException(Throwable cause, int errorCode) {
|
|
||||||
return ExoPlaybackException.createForRenderer(
|
|
||||||
cause,
|
|
||||||
TAG,
|
|
||||||
rendererIndex,
|
|
||||||
inputFormat,
|
|
||||||
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
|
|
||||||
/* isRecoverable= */ false,
|
|
||||||
errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void computeNextEncoderInputBufferTimeUs(
|
private void computeNextEncoderInputBufferTimeUs(
|
||||||
long bytesWritten, int bytesPerFrame, int sampleRate) {
|
long bytesWritten, int bytesPerFrame, int sampleRate) {
|
||||||
// The calculation below accounts for remainders and rounding. Without that it corresponds to
|
// The calculation below accounts for remainders and rounding. Without that it corresponds to
|
||||||
|
@ -100,29 +100,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* @param format The {@link Format} (of the input data) used to determine the underlying {@link
|
* @param format The {@link Format} (of the input data) used to determine the underlying {@link
|
||||||
* MediaCodec} and its configuration values.
|
* MediaCodec} and its configuration values.
|
||||||
* @return A configured and started decoder wrapper.
|
* @return A configured and started decoder wrapper.
|
||||||
* @throws IOException If the underlying codec cannot be created.
|
* @throws TransformationException If the underlying codec cannot be created.
|
||||||
*/
|
*/
|
||||||
public static MediaCodecAdapterWrapper createForAudioDecoding(Format format) throws IOException {
|
public static MediaCodecAdapterWrapper createForAudioDecoding(Format format)
|
||||||
@Nullable MediaCodecAdapter adapter = null;
|
throws TransformationException {
|
||||||
|
MediaFormat mediaFormat =
|
||||||
|
MediaFormat.createAudioFormat(
|
||||||
|
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
||||||
|
MediaFormatUtil.maybeSetInteger(
|
||||||
|
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
||||||
|
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
||||||
|
|
||||||
|
MediaCodecAdapter adapter;
|
||||||
try {
|
try {
|
||||||
MediaFormat mediaFormat =
|
|
||||||
MediaFormat.createAudioFormat(
|
|
||||||
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
|
||||||
MediaFormatUtil.maybeSetInteger(
|
|
||||||
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
|
||||||
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
|
||||||
adapter =
|
adapter =
|
||||||
new Factory()
|
new Factory()
|
||||||
.createAdapter(
|
.createAdapter(
|
||||||
MediaCodecAdapter.Configuration.createForAudioDecoding(
|
MediaCodecAdapter.Configuration.createForAudioDecoding(
|
||||||
createPlaceholderMediaCodecInfo(), mediaFormat, format, /* crypto= */ null));
|
createPlaceholderMediaCodecInfo(), mediaFormat, format, /* crypto= */ null));
|
||||||
return new MediaCodecAdapterWrapper(adapter);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (adapter != null) {
|
throw createTransformationException(e, format, /* isVideo= */ false, /* isDecoder= */ true);
|
||||||
adapter.release();
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
return new MediaCodecAdapterWrapper(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,28 +132,26 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* MediaCodec} and its configuration values.
|
* MediaCodec} and its configuration values.
|
||||||
* @param surface The {@link Surface} to which the decoder output is rendered.
|
* @param surface The {@link Surface} to which the decoder output is rendered.
|
||||||
* @return A configured and started decoder wrapper.
|
* @return A configured and started decoder wrapper.
|
||||||
* @throws IOException If the underlying codec cannot be created.
|
* @throws TransformationException If the underlying codec cannot be created.
|
||||||
*/
|
*/
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
public static MediaCodecAdapterWrapper createForVideoDecoding(Format format, Surface surface)
|
public static MediaCodecAdapterWrapper createForVideoDecoding(Format format, Surface surface)
|
||||||
throws IOException {
|
throws TransformationException {
|
||||||
@Nullable MediaCodecAdapter adapter = null;
|
MediaFormat mediaFormat =
|
||||||
|
MediaFormat.createVideoFormat(
|
||||||
|
checkNotNull(format.sampleMimeType), format.width, format.height);
|
||||||
|
MediaFormatUtil.maybeSetInteger(mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees);
|
||||||
|
MediaFormatUtil.maybeSetInteger(
|
||||||
|
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
||||||
|
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
||||||
|
if (SDK_INT >= 29) {
|
||||||
|
// On API levels over 29, Transformer decodes as many frames as possible in one render
|
||||||
|
// cycle. This key ensures no frame dropping when the decoder's output surface is full.
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaCodecAdapter adapter;
|
||||||
try {
|
try {
|
||||||
MediaFormat mediaFormat =
|
|
||||||
MediaFormat.createVideoFormat(
|
|
||||||
checkNotNull(format.sampleMimeType), format.width, format.height);
|
|
||||||
MediaFormatUtil.maybeSetInteger(
|
|
||||||
mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees);
|
|
||||||
MediaFormatUtil.maybeSetInteger(
|
|
||||||
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
|
||||||
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
|
||||||
|
|
||||||
if (SDK_INT >= 29) {
|
|
||||||
// On API levels over 29, Transformer decodes as many frames as possible in one render
|
|
||||||
// cycle. This key ensures no frame dropping when the decoder's output surface is full.
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter =
|
adapter =
|
||||||
new Factory()
|
new Factory()
|
||||||
.createAdapter(
|
.createAdapter(
|
||||||
@ -164,13 +161,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
format,
|
format,
|
||||||
surface,
|
surface,
|
||||||
/* crypto= */ null));
|
/* crypto= */ null));
|
||||||
return new MediaCodecAdapterWrapper(adapter);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (adapter != null) {
|
throw createTransformationException(e, format, /* isVideo= */ true, /* isDecoder= */ true);
|
||||||
adapter.release();
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
return new MediaCodecAdapterWrapper(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,30 +174,26 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* @param format The {@link Format} (of the output data) used to determine the underlying {@link
|
* @param format The {@link Format} (of the output data) used to determine the underlying {@link
|
||||||
* MediaCodec} and its configuration values.
|
* MediaCodec} and its configuration values.
|
||||||
* @return A configured and started encoder wrapper.
|
* @return A configured and started encoder wrapper.
|
||||||
* @throws IOException If the underlying codec cannot be created.
|
* @throws TransformationException If the underlying codec cannot be created.
|
||||||
*/
|
*/
|
||||||
public static MediaCodecAdapterWrapper createForAudioEncoding(Format format) throws IOException {
|
public static MediaCodecAdapterWrapper createForAudioEncoding(Format format)
|
||||||
@Nullable MediaCodec encoder = null;
|
throws TransformationException {
|
||||||
@Nullable MediaCodecAdapter adapter = null;
|
MediaFormat mediaFormat =
|
||||||
|
MediaFormat.createAudioFormat(
|
||||||
|
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
|
||||||
|
|
||||||
|
MediaCodecAdapter adapter;
|
||||||
try {
|
try {
|
||||||
MediaFormat mediaFormat =
|
|
||||||
MediaFormat.createAudioFormat(
|
|
||||||
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
|
|
||||||
adapter =
|
adapter =
|
||||||
new Factory()
|
new Factory()
|
||||||
.createAdapter(
|
.createAdapter(
|
||||||
MediaCodecAdapter.Configuration.createForAudioEncoding(
|
MediaCodecAdapter.Configuration.createForAudioEncoding(
|
||||||
createPlaceholderMediaCodecInfo(), mediaFormat, format));
|
createPlaceholderMediaCodecInfo(), mediaFormat, format));
|
||||||
return new MediaCodecAdapterWrapper(adapter);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (adapter != null) {
|
throw createTransformationException(e, format, /* isVideo= */ false, /* isDecoder= */ false);
|
||||||
adapter.release();
|
|
||||||
} else if (encoder != null) {
|
|
||||||
encoder.release();
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
return new MediaCodecAdapterWrapper(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,41 +209,37 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* are from {@code MediaFormat.KEY_*} constants. Its values will override those in {@code
|
* are from {@code MediaFormat.KEY_*} constants. Its values will override those in {@code
|
||||||
* format}.
|
* format}.
|
||||||
* @return A configured and started encoder wrapper.
|
* @return A configured and started encoder wrapper.
|
||||||
* @throws IOException If the underlying codec cannot be created.
|
* @throws TransformationException If the underlying codec cannot be created.
|
||||||
*/
|
*/
|
||||||
public static MediaCodecAdapterWrapper createForVideoEncoding(
|
public static MediaCodecAdapterWrapper createForVideoEncoding(
|
||||||
Format format, Map<String, Integer> additionalEncoderConfig) throws IOException {
|
Format format, Map<String, Integer> additionalEncoderConfig) throws TransformationException {
|
||||||
checkArgument(format.width != Format.NO_VALUE);
|
checkArgument(format.width != Format.NO_VALUE);
|
||||||
checkArgument(format.height != Format.NO_VALUE);
|
checkArgument(format.height != Format.NO_VALUE);
|
||||||
checkArgument(format.height < format.width);
|
checkArgument(format.height < format.width);
|
||||||
checkArgument(format.rotationDegrees == 0);
|
checkArgument(format.rotationDegrees == 0);
|
||||||
|
|
||||||
@Nullable MediaCodecAdapter adapter = null;
|
MediaFormat mediaFormat =
|
||||||
|
MediaFormat.createVideoFormat(
|
||||||
|
checkNotNull(format.sampleMimeType), format.width, format.height);
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, CodecCapabilities.COLOR_FormatSurface);
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
||||||
|
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 413_000);
|
||||||
|
for (Map.Entry<String, Integer> encoderSetting : additionalEncoderConfig.entrySet()) {
|
||||||
|
mediaFormat.setInteger(encoderSetting.getKey(), encoderSetting.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaCodecAdapter adapter;
|
||||||
try {
|
try {
|
||||||
MediaFormat mediaFormat =
|
|
||||||
MediaFormat.createVideoFormat(
|
|
||||||
checkNotNull(format.sampleMimeType), format.width, format.height);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, CodecCapabilities.COLOR_FormatSurface);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 413_000);
|
|
||||||
|
|
||||||
for (Map.Entry<String, Integer> encoderSetting : additionalEncoderConfig.entrySet()) {
|
|
||||||
mediaFormat.setInteger(encoderSetting.getKey(), encoderSetting.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter =
|
adapter =
|
||||||
new Factory()
|
new Factory()
|
||||||
.createAdapter(
|
.createAdapter(
|
||||||
MediaCodecAdapter.Configuration.createForVideoEncoding(
|
MediaCodecAdapter.Configuration.createForVideoEncoding(
|
||||||
createPlaceholderMediaCodecInfo(), mediaFormat, format));
|
createPlaceholderMediaCodecInfo(), mediaFormat, format));
|
||||||
return new MediaCodecAdapterWrapper(adapter);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (adapter != null) {
|
throw createTransformationException(e, format, /* isVideo= */ true, /* isDecoder= */ false);
|
||||||
adapter.release();
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
return new MediaCodecAdapterWrapper(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaCodecAdapterWrapper(MediaCodecAdapter codec) {
|
private MediaCodecAdapterWrapper(MediaCodecAdapter codec) {
|
||||||
@ -458,4 +444,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
return formatBuilder.build();
|
return formatBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TransformationException createTransformationException(
|
||||||
|
Exception cause, Format format, boolean isVideo, boolean isDecoder) {
|
||||||
|
String componentName = (isVideo ? "Video" : "Audio") + (isDecoder ? "Decoder" : "Encoder");
|
||||||
|
if (cause instanceof IOException) {
|
||||||
|
return TransformationException.createForCodec(
|
||||||
|
cause,
|
||||||
|
componentName,
|
||||||
|
format,
|
||||||
|
isDecoder
|
||||||
|
? TransformationException.ERROR_CODE_DECODER_INIT_FAILED
|
||||||
|
: TransformationException.ERROR_CODE_ENCODER_INIT_FAILED);
|
||||||
|
}
|
||||||
|
if (cause instanceof IllegalArgumentException) {
|
||||||
|
return TransformationException.createForCodec(
|
||||||
|
cause,
|
||||||
|
componentName,
|
||||||
|
format,
|
||||||
|
isDecoder
|
||||||
|
? TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED
|
||||||
|
: TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
|
||||||
|
}
|
||||||
|
return TransformationException.createForUnexpected(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ package androidx.media3.transformer;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pipeline for processing {@link DecoderInputBuffer DecoderInputBuffers}.
|
* Pipeline for processing {@link DecoderInputBuffer DecoderInputBuffers}.
|
||||||
@ -44,7 +43,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
|
|||||||
* Processes the input data and returns whether more data can be processed by calling this method
|
* Processes the input data and returns whether more data can be processed by calling this method
|
||||||
* again.
|
* again.
|
||||||
*/
|
*/
|
||||||
boolean processData() throws ExoPlaybackException;
|
boolean processData() throws TransformationException;
|
||||||
|
|
||||||
/** Returns the output format of the pipeline if available, and {@code null} otherwise. */
|
/** Returns the output format of the pipeline if available, and {@code null} otherwise. */
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -24,9 +24,12 @@ import static java.lang.annotation.ElementType.TYPE_USE;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.exoplayer.audio.AudioProcessor;
|
||||||
|
import androidx.media3.exoplayer.audio.AudioProcessor.AudioFormat;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -36,6 +39,36 @@ import java.lang.annotation.Target;
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class TransformationException extends Exception {
|
public final class TransformationException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance for a decoder or encoder related exception.
|
||||||
|
*
|
||||||
|
* @param cause The cause of the failure.
|
||||||
|
* @param componentName The name of the component used, e.g. 'VideoEncoder'.
|
||||||
|
* @param format The {@link Format} used for the decoder/encoder.
|
||||||
|
* @param errorCode See {@link #errorCode}.
|
||||||
|
* @return The created instance.
|
||||||
|
*/
|
||||||
|
public static TransformationException createForCodec(
|
||||||
|
Throwable cause, String componentName, Format format, int errorCode) {
|
||||||
|
return new TransformationException(
|
||||||
|
componentName + " error, format = " + format, cause, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance for an audio processing related exception.
|
||||||
|
*
|
||||||
|
* @param cause The cause of the failure.
|
||||||
|
* @param componentName The name of the {@link AudioProcessor} used.
|
||||||
|
* @param audioFormat The {@link AudioFormat} used.
|
||||||
|
* @param errorCode See {@link #errorCode}.
|
||||||
|
* @return The created instance.
|
||||||
|
*/
|
||||||
|
public static TransformationException createForAudioProcessor(
|
||||||
|
Throwable cause, String componentName, AudioFormat audioFormat, int errorCode) {
|
||||||
|
return new TransformationException(
|
||||||
|
componentName + " error, audio_format = " + audioFormat, cause, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance for an unexpected exception.
|
* Creates an instance for an unexpected exception.
|
||||||
*
|
*
|
||||||
@ -74,6 +107,7 @@ public final class TransformationException extends Exception {
|
|||||||
ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
|
ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
|
||||||
ERROR_CODE_GL_INIT_FAILED,
|
ERROR_CODE_GL_INIT_FAILED,
|
||||||
ERROR_CODE_GL_PROCESSING_FAILED,
|
ERROR_CODE_GL_PROCESSING_FAILED,
|
||||||
|
ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED,
|
||||||
})
|
})
|
||||||
public @interface ErrorCode {}
|
public @interface ErrorCode {}
|
||||||
|
|
||||||
@ -106,13 +140,18 @@ public final class TransformationException extends Exception {
|
|||||||
/** Caused by requesting to encode content in a format that is not supported by the device. */
|
/** Caused by requesting to encode content in a format that is not supported by the device. */
|
||||||
public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 3003;
|
public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 3003;
|
||||||
|
|
||||||
// GL errors (4xxx).
|
// Video editing errors (4xxx).
|
||||||
|
|
||||||
/** Caused by a GL initialization failure. */
|
/** Caused by a GL initialization failure. */
|
||||||
public static final int ERROR_CODE_GL_INIT_FAILED = 4001;
|
public static final int ERROR_CODE_GL_INIT_FAILED = 4001;
|
||||||
/** Caused by a failure while using or releasing a GL program. */
|
/** Caused by a failure while using or releasing a GL program. */
|
||||||
public static final int ERROR_CODE_GL_PROCESSING_FAILED = 4002;
|
public static final int ERROR_CODE_GL_PROCESSING_FAILED = 4002;
|
||||||
|
|
||||||
|
// Audio editing errors (5xxx).
|
||||||
|
|
||||||
|
/** Caused by an audio processor initialization failure. */
|
||||||
|
public static final int ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED = 5001;
|
||||||
|
|
||||||
/** Returns the name of a given {@code errorCode}. */
|
/** Returns the name of a given {@code errorCode}. */
|
||||||
public static String getErrorCodeName(@ErrorCode int errorCode) {
|
public static String getErrorCodeName(@ErrorCode int errorCode) {
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
@ -136,6 +175,8 @@ public final class TransformationException extends Exception {
|
|||||||
return "ERROR_CODE_GL_INIT_FAILED";
|
return "ERROR_CODE_GL_INIT_FAILED";
|
||||||
case ERROR_CODE_GL_PROCESSING_FAILED:
|
case ERROR_CODE_GL_PROCESSING_FAILED:
|
||||||
return "ERROR_CODE_GL_PROCESSING_FAILED";
|
return "ERROR_CODE_GL_PROCESSING_FAILED";
|
||||||
|
case ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED:
|
||||||
|
return "ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED";
|
||||||
default:
|
default:
|
||||||
return "invalid error code";
|
return "invalid error code";
|
||||||
}
|
}
|
||||||
|
@ -831,29 +831,33 @@ public final class Transformer {
|
|||||||
@Override
|
@Override
|
||||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||||
if (muxerWrapper.getTrackCount() == 0) {
|
if (muxerWrapper.getTrackCount() == 0) {
|
||||||
|
// TODO(b/209469847): Do not silently drop unsupported tracks and throw a more specific
|
||||||
|
// exception earlier.
|
||||||
handleTransformationEnded(
|
handleTransformationEnded(
|
||||||
new IllegalStateException(
|
TransformationException.createForUnexpected(
|
||||||
"The output does not contain any tracks. Check that at least one of the input"
|
new IllegalStateException(
|
||||||
+ " sample formats is supported."));
|
"The output does not contain any tracks. Check that at least one of the input"
|
||||||
|
+ " sample formats is supported.")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(PlaybackException error) {
|
public void onPlayerError(PlaybackException error) {
|
||||||
// TODO(internal b/209469847): Once TransformationException is used in transformer components,
|
Throwable cause = error.getCause();
|
||||||
// extract TransformationExceptions wrapped in the PlaybackExceptions here before passing them
|
handleTransformationEnded(
|
||||||
// on.
|
cause instanceof TransformationException
|
||||||
handleTransformationEnded(error);
|
? (TransformationException) cause
|
||||||
|
: TransformationException.createForUnexpected(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTransformationEnded(@Nullable Exception exception) {
|
private void handleTransformationEnded(@Nullable TransformationException exception) {
|
||||||
@Nullable Exception resourceReleaseException = null;
|
@Nullable TransformationException resourceReleaseException = null;
|
||||||
try {
|
try {
|
||||||
releaseResources(/* forCancellation= */ false);
|
releaseResources(/* forCancellation= */ false);
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
// TODO(internal b/209469847): Use a TransformationException with a specific error code when
|
// TODO(internal b/209469847): Use a more specific error code when the IllegalStateException
|
||||||
// the IllegalStateException is caused by the muxer.
|
// is caused by the muxer.
|
||||||
resourceReleaseException = e;
|
resourceReleaseException = TransformationException.createForUnexpected(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception == null && resourceReleaseException == null) {
|
if (exception == null && resourceReleaseException == null) {
|
||||||
@ -862,15 +866,10 @@ public final class Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
listener.onTransformationError(
|
listener.onTransformationError(mediaItem, exception);
|
||||||
mediaItem,
|
|
||||||
exception instanceof TransformationException
|
|
||||||
? exception
|
|
||||||
: TransformationException.createForUnexpected(exception));
|
|
||||||
}
|
}
|
||||||
if (resourceReleaseException != null) {
|
if (resourceReleaseException != null) {
|
||||||
listener.onTransformationError(
|
listener.onTransformationError(mediaItem, resourceReleaseException);
|
||||||
mediaItem, TransformationException.createForUnexpected(resourceReleaseException));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
|
||||||
import androidx.media3.exoplayer.FormatHolder;
|
import androidx.media3.exoplayer.FormatHolder;
|
||||||
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
||||||
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
||||||
@ -49,7 +48,7 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
|||||||
|
|
||||||
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
|
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
|
||||||
@Override
|
@Override
|
||||||
protected boolean ensureConfigured() throws ExoPlaybackException {
|
protected boolean ensureConfigured() throws TransformationException {
|
||||||
if (samplePipeline != null) {
|
if (samplePipeline != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -61,7 +60,7 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
|||||||
}
|
}
|
||||||
Format inputFormat = checkNotNull(formatHolder.format);
|
Format inputFormat = checkNotNull(formatHolder.format);
|
||||||
if (shouldTranscode(inputFormat)) {
|
if (shouldTranscode(inputFormat)) {
|
||||||
samplePipeline = new AudioSamplePipeline(inputFormat, transformation, getIndex());
|
samplePipeline = new AudioSamplePipeline(inputFormat, transformation);
|
||||||
} else {
|
} else {
|
||||||
samplePipeline = new PassthroughSamplePipeline(inputFormat);
|
samplePipeline = new PassthroughSamplePipeline(inputFormat);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.common.PlaybackException;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import androidx.media3.exoplayer.BaseRenderer;
|
import androidx.media3.exoplayer.BaseRenderer;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||||
@ -95,11 +96,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
public final void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||||
if (!isRendererStarted || isEnded() || !ensureConfigured()) {
|
try {
|
||||||
return;
|
if (!isRendererStarted || isEnded() || !ensureConfigured()) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
|
while (feedMuxerFromPipeline() || samplePipeline.processData() || feedPipelineFromInput()) {}
|
||||||
|
} catch (TransformationException e) {
|
||||||
|
// Transformer extracts the TransformationException from this ExoPlaybackException again. This
|
||||||
|
// temporary wrapping is needed due to the dependence on ExoPlayer's BaseRenderer.
|
||||||
|
throw ExoPlaybackException.createForRenderer(
|
||||||
|
e,
|
||||||
|
"Transformer",
|
||||||
|
getIndex(),
|
||||||
|
/* rendererFormat= */ null,
|
||||||
|
C.FORMAT_HANDLED,
|
||||||
|
/* isRecoverable= */ false,
|
||||||
|
PlaybackException.ERROR_CODE_UNSPECIFIED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -134,7 +148,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
@ForOverride
|
@ForOverride
|
||||||
@EnsuresNonNullIf(expression = "samplePipeline", result = true)
|
@EnsuresNonNullIf(expression = "samplePipeline", result = true)
|
||||||
protected abstract boolean ensureConfigured() throws ExoPlaybackException;
|
protected abstract boolean ensureConfigured() throws TransformationException;
|
||||||
|
|
||||||
@RequiresNonNull({"samplePipeline", "#1.data"})
|
@RequiresNonNull({"samplePipeline", "#1.data"})
|
||||||
protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) {
|
protected void maybeQueueSampleToPipeline(DecoderInputBuffer inputBuffer) {
|
||||||
|
@ -23,7 +23,6 @@ import android.content.Context;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
|
||||||
import androidx.media3.exoplayer.FormatHolder;
|
import androidx.media3.exoplayer.FormatHolder;
|
||||||
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -60,7 +59,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
|
/** Attempts to read the input format and to initialize the {@link SamplePipeline}. */
|
||||||
@Override
|
@Override
|
||||||
protected boolean ensureConfigured() throws ExoPlaybackException {
|
protected boolean ensureConfigured() throws TransformationException {
|
||||||
if (samplePipeline != null) {
|
if (samplePipeline != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -73,8 +72,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
Format inputFormat = checkNotNull(formatHolder.format);
|
Format inputFormat = checkNotNull(formatHolder.format);
|
||||||
if (shouldTranscode(inputFormat)) {
|
if (shouldTranscode(inputFormat)) {
|
||||||
samplePipeline =
|
samplePipeline =
|
||||||
new VideoSamplePipeline(
|
new VideoSamplePipeline(context, inputFormat, transformation, debugViewProvider);
|
||||||
context, inputFormat, transformation, getIndex(), debugViewProvider);
|
|
||||||
} else {
|
} else {
|
||||||
samplePipeline = new PassthroughSamplePipeline(inputFormat);
|
samplePipeline = new PassthroughSamplePipeline(inputFormat);
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,8 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.PlaybackException;
|
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.io.IOException;
|
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,9 +52,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
Context context,
|
Context context,
|
||||||
Format inputFormat,
|
Format inputFormat,
|
||||||
Transformation transformation,
|
Transformation transformation,
|
||||||
int rendererIndex,
|
|
||||||
Transformer.DebugViewProvider debugViewProvider)
|
Transformer.DebugViewProvider debugViewProvider)
|
||||||
throws ExoPlaybackException {
|
throws TransformationException {
|
||||||
decoderInputBuffer =
|
decoderInputBuffer =
|
||||||
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderOutputBuffer =
|
encoderOutputBuffer =
|
||||||
@ -88,24 +84,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
// postrotation in a later vertex shader.
|
// postrotation in a later vertex shader.
|
||||||
transformation.transformationMatrix.postRotate(outputRotationDegrees);
|
transformation.transformationMatrix.postRotate(outputRotationDegrees);
|
||||||
|
|
||||||
try {
|
encoder =
|
||||||
encoder =
|
MediaCodecAdapterWrapper.createForVideoEncoding(
|
||||||
MediaCodecAdapterWrapper.createForVideoEncoding(
|
new Format.Builder()
|
||||||
new Format.Builder()
|
.setWidth(outputWidth)
|
||||||
.setWidth(outputWidth)
|
.setHeight(outputHeight)
|
||||||
.setHeight(outputHeight)
|
.setRotationDegrees(0)
|
||||||
.setRotationDegrees(0)
|
.setSampleMimeType(
|
||||||
.setSampleMimeType(
|
transformation.videoMimeType != null
|
||||||
transformation.videoMimeType != null
|
? transformation.videoMimeType
|
||||||
? transformation.videoMimeType
|
: inputFormat.sampleMimeType)
|
||||||
: inputFormat.sampleMimeType)
|
.build(),
|
||||||
.build(),
|
ImmutableMap.of());
|
||||||
ImmutableMap.of());
|
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO(internal b/192864511): Assign a specific error code.
|
|
||||||
throw createRendererException(
|
|
||||||
e, rendererIndex, inputFormat, PlaybackException.ERROR_CODE_UNSPECIFIED);
|
|
||||||
}
|
|
||||||
if (inputFormat.height != outputHeight
|
if (inputFormat.height != outputHeight
|
||||||
|| inputFormat.width != outputWidth
|
|| inputFormat.width != outputWidth
|
||||||
|| !transformation.transformationMatrix.isIdentity()) {
|
|| !transformation.transformationMatrix.isIdentity()) {
|
||||||
@ -118,17 +108,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
/* outputSurface= */ checkNotNull(encoder.getInputSurface()),
|
/* outputSurface= */ checkNotNull(encoder.getInputSurface()),
|
||||||
debugViewProvider);
|
debugViewProvider);
|
||||||
}
|
}
|
||||||
try {
|
decoder =
|
||||||
decoder =
|
MediaCodecAdapterWrapper.createForVideoDecoding(
|
||||||
MediaCodecAdapterWrapper.createForVideoDecoding(
|
inputFormat,
|
||||||
inputFormat,
|
frameEditor == null
|
||||||
frameEditor == null
|
? checkNotNull(encoder.getInputSurface())
|
||||||
? checkNotNull(encoder.getInputSurface())
|
: frameEditor.getInputSurface());
|
||||||
: frameEditor.getInputSurface());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw createRendererException(
|
|
||||||
e, rendererIndex, inputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -257,16 +242,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
decoder.release();
|
decoder.release();
|
||||||
encoder.release();
|
encoder.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ExoPlaybackException createRendererException(
|
|
||||||
Throwable cause, int rendererIndex, Format inputFormat, int errorCode) {
|
|
||||||
return ExoPlaybackException.createForRenderer(
|
|
||||||
cause,
|
|
||||||
TAG,
|
|
||||||
rendererIndex,
|
|
||||||
inputFormat,
|
|
||||||
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
|
|
||||||
/* isRecoverable= */ false,
|
|
||||||
errorCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,15 @@ 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 android.media.MediaCrypto;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.view.Surface;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
@ -39,6 +43,7 @@ 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.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -232,6 +237,63 @@ public final class TransformerTest {
|
|||||||
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
|
DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_WITH_SEF_SLOW_MOTION));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void startTransformation_withAudioEncoderFormatUnsupported_completesWithError()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
new Transformer.Builder(context)
|
||||||
|
.setClock(clock)
|
||||||
|
.setMuxerFactory(new TestMuxerFactory())
|
||||||
|
.setAudioMimeType(MimeTypes.AUDIO_AMR_WB) // unsupported encoder MIME type
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_AUDIO_ONLY);
|
||||||
|
|
||||||
|
transformer.startTransformation(mediaItem, outputPath);
|
||||||
|
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||||
|
|
||||||
|
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||||
|
assertThat(exception.errorCode)
|
||||||
|
.isEqualTo(TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void startTransformation_withAudioDecoderFormatUnsupported_completesWithError()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
new Transformer.Builder(context)
|
||||||
|
.setClock(clock)
|
||||||
|
.setMuxerFactory(new TestMuxerFactory())
|
||||||
|
.setAudioMimeType(MimeTypes.AUDIO_AAC) // supported encoder MIME type
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_WITH_ALL_SAMPLE_FORMATS_UNSUPPORTED);
|
||||||
|
|
||||||
|
transformer.startTransformation(mediaItem, outputPath);
|
||||||
|
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||||
|
|
||||||
|
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||||
|
assertThat(exception.errorCode)
|
||||||
|
.isEqualTo(TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void startTransformation_withVideoEncoderFormatUnsupported_completesWithError()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
new Transformer.Builder(context)
|
||||||
|
.setClock(clock)
|
||||||
|
.setMuxerFactory(new TestMuxerFactory())
|
||||||
|
.setVideoMimeType(MimeTypes.VIDEO_H263) // unsupported encoder MIME type
|
||||||
|
.build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(URI_PREFIX + FILE_VIDEO_ONLY);
|
||||||
|
|
||||||
|
transformer.startTransformation(mediaItem, outputPath);
|
||||||
|
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||||
|
|
||||||
|
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||||
|
assertThat(exception.errorCode)
|
||||||
|
.isEqualTo(TransformationException.ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startTransformation_withPlayerError_completesWithError() throws Exception {
|
public void startTransformation_withPlayerError_completesWithError() throws Exception {
|
||||||
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
Transformer transformer = new Transformer.Builder(context).setClock(clock).build();
|
||||||
@ -541,6 +603,30 @@ public final class TransformerTest {
|
|||||||
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AAC, codecConfig);
|
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AAC, codecConfig);
|
||||||
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AMR_NB, codecConfig);
|
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AMR_NB, codecConfig);
|
||||||
ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AAC, codecConfig);
|
ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AAC, codecConfig);
|
||||||
|
|
||||||
|
ShadowMediaCodec.CodecConfig throwingCodecConfig =
|
||||||
|
new ShadowMediaCodec.CodecConfig(
|
||||||
|
/* inputBufferSize= */ 10_000,
|
||||||
|
/* outputBufferSize= */ 10_000,
|
||||||
|
new ShadowMediaCodec.CodecConfig.Codec() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(ByteBuffer in, ByteBuffer out) {
|
||||||
|
out.put(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigured(
|
||||||
|
MediaFormat format,
|
||||||
|
@Nullable Surface surface,
|
||||||
|
@Nullable MediaCrypto crypto,
|
||||||
|
int flags) {
|
||||||
|
throw new IllegalArgumentException("Format unsupported");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AC3, throwingCodecConfig);
|
||||||
|
ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AMR_WB, throwingCodecConfig);
|
||||||
|
ShadowMediaCodec.addEncoder(MimeTypes.VIDEO_H263, throwingCodecConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void removeEncodersAndDecoders() {
|
private static void removeEncodersAndDecoders() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user