mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Move audio MIME type fallback away to ATSP
PiperOrigin-RevId: 491660842
This commit is contained in:
parent
d12afe0596
commit
4033013ff8
@ -36,7 +36,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@ -472,9 +471,8 @@ public final class AndroidTestUtil {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
return encoderFactory.createForAudioEncoding(format, allowedMimeTypes);
|
||||
public Codec createForAudioEncoding(Format format) throws TransformationException {
|
||||
return encoderFactory.createForAudioEncoding(format);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,7 +35,6 @@ import com.google.common.base.Ascii;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@ -446,9 +445,8 @@ public class TransformerAndroidTestRunner {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
Codec audioEncoder = encoderFactory.createForAudioEncoding(format, allowedMimeTypes);
|
||||
public Codec createForAudioEncoding(Format format) throws TransformationException {
|
||||
Codec audioEncoder = encoderFactory.createForAudioEncoding(format);
|
||||
audioEncoderName = audioEncoder.getName();
|
||||
return audioEncoder;
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@ -140,9 +139,8 @@ public class TransformerEndToEndTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
return encoderFactory.createForAudioEncoding(format, allowedMimeTypes);
|
||||
public Codec createForAudioEncoding(Format format) throws TransformationException {
|
||||
return encoderFactory.createForAudioEncoding(format);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,6 +31,7 @@ import androidx.media3.common.util.Util;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
/**
|
||||
@ -103,20 +104,33 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
audioProcessingPipeline.flush();
|
||||
|
||||
String requestedMimeType =
|
||||
transformationRequest.audioMimeType != null
|
||||
? transformationRequest.audioMimeType
|
||||
: checkNotNull(inputFormat.sampleMimeType);
|
||||
Format requestedOutputFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(
|
||||
transformationRequest.audioMimeType == null
|
||||
? inputFormat.sampleMimeType
|
||||
: transformationRequest.audioMimeType)
|
||||
.setSampleMimeType(requestedMimeType)
|
||||
.setSampleRate(encoderInputAudioFormat.sampleRate)
|
||||
.setChannelCount(encoderInputAudioFormat.channelCount)
|
||||
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
||||
.build();
|
||||
|
||||
ImmutableList<String> muxerSupportedMimeTypes =
|
||||
muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO);
|
||||
|
||||
// TODO(b/259570024): investigate overhauling fallback.
|
||||
@Nullable
|
||||
String supportedMimeType =
|
||||
selectEncoderAndMuxerSupportedMimeType(requestedMimeType, muxerSupportedMimeTypes);
|
||||
if (supportedMimeType == null) {
|
||||
throw createNoSupportedMimeTypeException(requestedOutputFormat);
|
||||
}
|
||||
|
||||
encoder =
|
||||
encoderFactory.createForAudioEncoding(
|
||||
requestedOutputFormat, muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_AUDIO));
|
||||
|
||||
requestedOutputFormat.buildUpon().setSampleMimeType(supportedMimeType).build());
|
||||
checkState(supportedMimeType.equals(encoder.getConfigurationFormat().sampleMimeType));
|
||||
fallbackListener.onTransformationRequestFinalized(
|
||||
createFallbackTransformationRequest(
|
||||
transformationRequest, requestedOutputFormat, encoder.getConfigurationFormat()));
|
||||
@ -284,6 +298,23 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
encoder.queueInputBuffer(encoderInputBuffer);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String selectEncoderAndMuxerSupportedMimeType(
|
||||
String requestedMimeType, List<String> muxerSupportedMimeTypes) {
|
||||
if (!EncoderUtil.getSupportedEncoders(requestedMimeType).isEmpty()) {
|
||||
return requestedMimeType;
|
||||
} else {
|
||||
// No encoder supports the requested MIME type.
|
||||
for (int i = 0; i < muxerSupportedMimeTypes.size(); i++) {
|
||||
String mimeType = muxerSupportedMimeTypes.get(i);
|
||||
if (!EncoderUtil.getSupportedEncoders(mimeType).isEmpty()) {
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Pure
|
||||
private static TransformationRequest createFallbackTransformationRequest(
|
||||
TransformationRequest transformationRequest, Format requestedFormat, Format actualFormat) {
|
||||
|
@ -58,6 +58,17 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
: null;
|
||||
}
|
||||
|
||||
protected static TransformationException createNoSupportedMimeTypeException(
|
||||
Format requestedEncoderFormat) {
|
||||
return TransformationException.createForCodec(
|
||||
new IllegalArgumentException("No MIME type is supported by both encoder and muxer."),
|
||||
MimeTypes.isVideo(requestedEncoderFormat.sampleMimeType),
|
||||
/* isDecoder= */ false,
|
||||
requestedEncoderFormat,
|
||||
/* mediaCodecName= */ null,
|
||||
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DecoderInputBuffer dequeueInputBuffer() throws TransformationException {
|
||||
|
@ -21,11 +21,9 @@ import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides a layer of abstraction for interacting with decoders and encoders.
|
||||
@ -72,19 +70,18 @@ public interface Codec {
|
||||
/**
|
||||
* Returns a {@link Codec} for audio encoding.
|
||||
*
|
||||
* <p>This method must validate that the {@link Codec} is configured to produce one of the
|
||||
* {@code allowedMimeTypes}. The {@linkplain Format#sampleMimeType sample MIME type} given in
|
||||
* {@code format} is not necessarily allowed.
|
||||
* <p>The caller should ensure the {@linkplain Format#sampleMimeType MIME type} is supported on
|
||||
* the device before calling this method.
|
||||
*
|
||||
* @param format The {@link Format} (of the output data) used to determine the underlying
|
||||
* encoder and its configuration values.
|
||||
* @param allowedMimeTypes The non-empty list of allowed output sample {@linkplain MimeTypes
|
||||
* MIME types}.
|
||||
* @return A {@link Codec} for audio encoding.
|
||||
* encoder and its configuration values. {@link Format#sampleMimeType}, {@link
|
||||
* Format#sampleRate}, {@link Format#channelCount} and {@link Format#bitrate} are set to
|
||||
* those of the desired output video format.
|
||||
* @return A {@link Codec} for encoding audio to the requested {@link Format#sampleMimeType MIME
|
||||
* type}.
|
||||
* @throws TransformationException If no suitable {@link Codec} can be created.
|
||||
*/
|
||||
Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException;
|
||||
Codec createForAudioEncoding(Format format) throws TransformationException;
|
||||
|
||||
/**
|
||||
* Returns a {@link Codec} for video encoding.
|
||||
|
@ -174,23 +174,12 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
public DefaultCodec createForAudioEncoding(Format format) throws TransformationException {
|
||||
// TODO(b/210591626) Add encoder selection for audio.
|
||||
checkArgument(!allowedMimeTypes.isEmpty());
|
||||
checkNotNull(format.sampleMimeType);
|
||||
if (!allowedMimeTypes.contains(format.sampleMimeType)) {
|
||||
if (enableFallback) {
|
||||
// TODO(b/210591626): Pick fallback MIME type using same strategy as for encoder
|
||||
// capabilities limitations.
|
||||
format = format.buildUpon().setSampleMimeType(allowedMimeTypes.get(0)).build();
|
||||
} else {
|
||||
throw createTransformationException(format);
|
||||
}
|
||||
}
|
||||
MediaFormat mediaFormat =
|
||||
MediaFormat.createAudioFormat(
|
||||
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
||||
format.sampleMimeType, format.sampleRate, format.channelCount);
|
||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
|
||||
|
||||
@Nullable
|
||||
|
@ -446,9 +446,7 @@ public final class EncoderUtil {
|
||||
}
|
||||
String[] supportedMimeTypes = mediaCodecInfo.getSupportedTypes();
|
||||
for (String mimeType : supportedMimeTypes) {
|
||||
if (MimeTypes.isVideo(mimeType)) {
|
||||
encoderInfosBuilder.put(Ascii.toLowerCase(mimeType), mediaCodecInfo);
|
||||
}
|
||||
encoderInfosBuilder.put(Ascii.toLowerCase(mimeType), mediaCodecInfo);
|
||||
}
|
||||
}
|
||||
return encoderInfosBuilder.build();
|
||||
|
@ -461,13 +461,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
selectEncoderAndMuxerSupportedMimeType(
|
||||
requestedOutputMimeType, muxerSupportedMimeTypes, requestedEncoderFormat.colorInfo);
|
||||
if (supportedMimeType == null) {
|
||||
throw TransformationException.createForCodec(
|
||||
new IllegalArgumentException("No MIME type is supported by both encoder and muxer."),
|
||||
/* isVideo= */ true,
|
||||
/* isDecoder= */ false,
|
||||
requestedEncoderFormat,
|
||||
/* mediaCodecName= */ null,
|
||||
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
throw createNoSupportedMimeTypeException(requestedEncoderFormat);
|
||||
}
|
||||
|
||||
encoder =
|
||||
|
@ -453,18 +453,25 @@ public final class TransformerEndToEndTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startTransformation_withAudioMuxerFormatUnsupported_completesWithError()
|
||||
public void startTransformation_withAudioMuxerFormatUnsupported_completesSuccessfully()
|
||||
throws Exception {
|
||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||
// Test succeeds because MIME type fallback is mandatory.
|
||||
Transformer.Listener mockListener = mock(Transformer.Listener.class);
|
||||
TransformationRequest originalTransformationRequest =
|
||||
new TransformationRequest.Builder().build();
|
||||
TransformationRequest fallbackTransformationRequest =
|
||||
new TransformationRequest.Builder().setAudioMimeType(MimeTypes.AUDIO_AAC).build();
|
||||
Transformer transformer =
|
||||
createTransformerBuilder(/* enableFallback= */ false).addListener(mockListener).build();
|
||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_UNSUPPORTED_BY_MUXER);
|
||||
|
||||
transformer.startTransformation(mediaItem, outputPath);
|
||||
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||
TransformerTestRunner.runUntilCompleted(transformer);
|
||||
|
||||
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(exception).hasMessageThat().contains("audio");
|
||||
assertThat(exception.errorCode)
|
||||
.isEqualTo(TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
DumpFileAsserts.assertOutput(
|
||||
context, testMuxer, getDumpFileName(FILE_AUDIO_UNSUPPORTED_BY_MUXER + ".fallback"));
|
||||
verify(mockListener)
|
||||
.onFallbackApplied(mediaItem, originalTransformationRequest, fallbackTransformationRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -32,7 +32,6 @@ import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.ListenerSet;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -165,7 +164,7 @@ public final class VideoEncoderWrapperTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForAudioEncoding(Format format, List<String> allowedMimeTypes) {
|
||||
public Codec createForAudioEncoding(Format format) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user