Making mediaCodecName @NonNull in DefaultCodec constructor.
PiperOrigin-RevId: 435045138
This commit is contained in:
parent
1a6e176873
commit
191629ed7c
@ -26,7 +26,7 @@ project.ext {
|
||||
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
|
||||
guavaVersion = '31.0.1-android'
|
||||
mockitoVersion = '3.12.4'
|
||||
robolectricVersion = '4.6.1'
|
||||
robolectricVersion = '4.8-SNAPSHOT'
|
||||
// Keep this in sync with Google's internal Checker Framework version.
|
||||
checkerframeworkVersion = '3.13.0'
|
||||
checkerframeworkCompatVersion = '2.5.5'
|
||||
|
@ -68,16 +68,14 @@ public final class DefaultCodec implements Codec {
|
||||
* {@code null}.
|
||||
* @param configurationMediaFormat The {@link MediaFormat} to configure the underlying {@link
|
||||
* MediaCodec}.
|
||||
* @param mediaCodecName The name of a specific {@link MediaCodec} to instantiate. If {@code
|
||||
* null}, {@code DefaultCodec} uses {@link Format#sampleMimeType
|
||||
* configurationFormat.sampleMimeType} to create the underlying {@link MediaCodec codec}.
|
||||
* @param mediaCodecName The name of a specific {@link MediaCodec} to instantiate.
|
||||
* @param isDecoder Whether the {@code DefaultCodec} is intended as a decoder.
|
||||
* @param outputSurface The output {@link Surface} if the {@link MediaCodec} outputs to a surface.
|
||||
*/
|
||||
public DefaultCodec(
|
||||
Format configurationFormat,
|
||||
MediaFormat configurationMediaFormat,
|
||||
@Nullable String mediaCodecName,
|
||||
String mediaCodecName,
|
||||
boolean isDecoder,
|
||||
@Nullable Surface outputSurface)
|
||||
throws TransformationException {
|
||||
@ -87,17 +85,11 @@ public final class DefaultCodec implements Codec {
|
||||
inputBufferIndex = C.INDEX_UNSET;
|
||||
outputBufferIndex = C.INDEX_UNSET;
|
||||
|
||||
String sampleMimeType = checkNotNull(configurationFormat.sampleMimeType);
|
||||
boolean isVideo = MimeTypes.isVideo(sampleMimeType);
|
||||
boolean isVideo = MimeTypes.isVideo(checkNotNull(configurationFormat.sampleMimeType));
|
||||
@Nullable MediaCodec mediaCodec = null;
|
||||
@Nullable Surface inputSurface = null;
|
||||
try {
|
||||
mediaCodec =
|
||||
mediaCodecName != null
|
||||
? MediaCodec.createByCodecName(mediaCodecName)
|
||||
: isDecoder
|
||||
? MediaCodec.createDecoderByType(sampleMimeType)
|
||||
: MediaCodec.createEncoderByType(sampleMimeType);
|
||||
mediaCodec = MediaCodec.createByCodecName(mediaCodecName);
|
||||
configureCodec(mediaCodec, configurationMediaFormat, isDecoder, outputSurface);
|
||||
if (isVideo && !isDecoder) {
|
||||
inputSurface = mediaCodec.createInputSurface();
|
||||
@ -108,7 +100,6 @@ public final class DefaultCodec implements Codec {
|
||||
inputSurface.release();
|
||||
}
|
||||
if (mediaCodec != null) {
|
||||
mediaCodecName = mediaCodec.getName();
|
||||
mediaCodec.release();
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,15 @@ import static androidx.media3.common.util.Util.SDK_INT;
|
||||
|
||||
import android.media.MediaFormat;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.MediaFormatUtil;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/** A default implementation of {@link Codec.DecoderFactory}. */
|
||||
/* package */ final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
||||
|
||||
@Override
|
||||
public Codec createForAudioDecoding(Format format) throws TransformationException {
|
||||
MediaFormat mediaFormat =
|
||||
@ -35,12 +39,13 @@ import androidx.media3.common.util.MediaFormatUtil;
|
||||
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
|
||||
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
|
||||
|
||||
@Nullable
|
||||
String mediaCodecName = EncoderUtil.findCodecForFormat(mediaFormat, /* isDecoder= */ true);
|
||||
if (mediaCodecName == null) {
|
||||
throw createTransformationException(format);
|
||||
}
|
||||
return new DefaultCodec(
|
||||
format,
|
||||
mediaFormat,
|
||||
/* mediaCodecName= */ null,
|
||||
/* isDecoder= */ true,
|
||||
/* outputSurface= */ null);
|
||||
format, mediaFormat, mediaCodecName, /* isDecoder= */ true, /* outputSurface= */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -59,7 +64,23 @@ import androidx.media3.common.util.MediaFormatUtil;
|
||||
mediaFormat.setInteger(MediaFormat.KEY_ALLOW_FRAME_DROP, 0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String mediaCodecName = EncoderUtil.findCodecForFormat(mediaFormat, /* isDecoder= */ true);
|
||||
if (mediaCodecName == null) {
|
||||
throw createTransformationException(format);
|
||||
}
|
||||
return new DefaultCodec(
|
||||
format, mediaFormat, /* mediaCodecName= */ null, /* isDecoder= */ true, outputSurface);
|
||||
format, mediaFormat, mediaCodecName, /* isDecoder= */ true, outputSurface);
|
||||
}
|
||||
|
||||
@RequiresNonNull("#1.sampleMimeType")
|
||||
private static TransformationException createTransformationException(Format format) {
|
||||
return TransformationException.createForCodec(
|
||||
new IllegalArgumentException("The requested decoding format is not supported."),
|
||||
MimeTypes.isVideo(format.sampleMimeType),
|
||||
/* isDecoder= */ true,
|
||||
format,
|
||||
/* mediaCodecName= */ null,
|
||||
TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED);
|
||||
}
|
||||
}
|
||||
|
@ -74,19 +74,14 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
||||
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 TransformationException.createForCodec(
|
||||
new IllegalArgumentException("The requested output format is not supported."),
|
||||
/* isVideo= */ false,
|
||||
/* isDecoder= */ false,
|
||||
format,
|
||||
/* mediaCodecName= */ null,
|
||||
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
throw createTransformationException(format);
|
||||
}
|
||||
}
|
||||
MediaFormat mediaFormat =
|
||||
@ -94,12 +89,13 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
||||
checkNotNull(format.sampleMimeType), format.sampleRate, format.channelCount);
|
||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
|
||||
|
||||
@Nullable
|
||||
String mediaCodecName = EncoderUtil.findCodecForFormat(mediaFormat, /* isDecoder= */ false);
|
||||
if (mediaCodecName == null) {
|
||||
throw createTransformationException(format);
|
||||
}
|
||||
return new DefaultCodec(
|
||||
format,
|
||||
mediaFormat,
|
||||
/* mediaCodecName= */ null,
|
||||
/* isDecoder= */ false,
|
||||
/* outputSurface= */ null);
|
||||
format, mediaFormat, mediaCodecName, /* isDecoder= */ false, /* outputSurface= */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -120,13 +116,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
||||
findEncoderWithClosestFormatSupport(
|
||||
format, videoEncoderSelector, allowedMimeTypes, enableFallback);
|
||||
if (encoderAndClosestFormatSupport == null) {
|
||||
throw TransformationException.createForCodec(
|
||||
new IllegalArgumentException("The requested output format is not supported."),
|
||||
/* isVideo= */ true,
|
||||
/* isDecoder= */ false,
|
||||
format,
|
||||
/* mediaCodecName= */ null,
|
||||
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
throw createTransformationException(format);
|
||||
}
|
||||
|
||||
MediaCodecInfo encoderInfo = encoderAndClosestFormatSupport.first;
|
||||
@ -371,4 +361,15 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
|
||||
// 1080p30 -> 6.2Mbps, 720p30 -> 2.7Mbps.
|
||||
return (int) (width * height * frameRate * 0.1);
|
||||
}
|
||||
|
||||
@RequiresNonNull("#1.sampleMimeType")
|
||||
private static TransformationException createTransformationException(Format format) {
|
||||
return TransformationException.createForCodec(
|
||||
new IllegalArgumentException("The requested encoding format is not supported."),
|
||||
MimeTypes.isVideo(format.sampleMimeType),
|
||||
/* isDecoder= */ false,
|
||||
format,
|
||||
/* mediaCodecName= */ null,
|
||||
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,14 @@ import static java.lang.Math.round;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.util.Size;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.MediaFormatUtil;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.base.Ascii;
|
||||
@ -154,6 +156,32 @@ public final class EncoderUtil {
|
||||
return maxSupportedLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a {@link MediaCodec codec} that supports the {@link MediaFormat}, or {@code null} if none
|
||||
* is found.
|
||||
*/
|
||||
@Nullable
|
||||
public static String findCodecForFormat(MediaFormat format, boolean isDecoder) {
|
||||
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
|
||||
// Format must not include KEY_FRAME_RATE on API21.
|
||||
// https://developer.android.com/reference/android/media/MediaCodecList#findDecoderForFormat(android.media.MediaFormat)
|
||||
@Nullable String frameRate = null;
|
||||
if (Util.SDK_INT == 21 && format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
|
||||
frameRate = format.getString(MediaFormat.KEY_FRAME_RATE);
|
||||
format.setString(MediaFormat.KEY_FRAME_RATE, null);
|
||||
}
|
||||
|
||||
String mediaCodecName =
|
||||
isDecoder
|
||||
? mediaCodecList.findDecoderForFormat(format)
|
||||
: mediaCodecList.findEncoderForFormat(format);
|
||||
|
||||
if (Util.SDK_INT == 21) {
|
||||
MediaFormatUtil.maybeSetString(format, MediaFormat.KEY_FRAME_RATE, frameRate);
|
||||
}
|
||||
return mediaCodecName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@link MediaCodecInfo encoder}'s closest supported bitrate from the given bitrate.
|
||||
*/
|
||||
|
@ -30,6 +30,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Handler;
|
||||
@ -49,6 +50,7 @@ import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
@ -64,7 +66,9 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.shadows.MediaCodecInfoBuilder;
|
||||
import org.robolectric.shadows.ShadowMediaCodec;
|
||||
import org.robolectric.shadows.ShadowMediaCodecList;
|
||||
|
||||
/** End-to-end test for {@link Transformer}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@ -750,10 +754,26 @@ public final class TransformerEndToEndTest {
|
||||
/* inputBufferSize= */ 10_000,
|
||||
/* outputBufferSize= */ 10_000,
|
||||
/* codec= */ (in, out) -> out.put(in));
|
||||
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AAC, codecConfig);
|
||||
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AC3, codecConfig);
|
||||
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AMR_NB, codecConfig);
|
||||
ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AAC, codecConfig);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AAC,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AC3,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AMR_NB,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AAC,
|
||||
codecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ false);
|
||||
|
||||
ShadowMediaCodec.CodecConfig throwingCodecConfig =
|
||||
new ShadowMediaCodec.CodecConfig(
|
||||
@ -776,9 +796,54 @@ public final class TransformerEndToEndTest {
|
||||
}
|
||||
});
|
||||
|
||||
ShadowMediaCodec.addDecoder(MimeTypes.AUDIO_AMR_WB, throwingCodecConfig);
|
||||
ShadowMediaCodec.addEncoder(MimeTypes.AUDIO_AMR_NB, throwingCodecConfig);
|
||||
ShadowMediaCodec.addEncoder(MimeTypes.VIDEO_H263, throwingCodecConfig);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AMR_WB,
|
||||
throwingCodecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ true);
|
||||
addCodec(
|
||||
MimeTypes.VIDEO_H263,
|
||||
throwingCodecConfig,
|
||||
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible),
|
||||
/* isDecoder= */ false);
|
||||
addCodec(
|
||||
MimeTypes.AUDIO_AMR_NB,
|
||||
throwingCodecConfig,
|
||||
/* colorFormats= */ ImmutableList.of(),
|
||||
/* isDecoder= */ false);
|
||||
}
|
||||
|
||||
private static void addCodec(
|
||||
String mimeType,
|
||||
ShadowMediaCodec.CodecConfig codecConfig,
|
||||
List<Integer> colorFormats,
|
||||
boolean isDecoder) {
|
||||
String codecName =
|
||||
Util.formatInvariant(
|
||||
isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-'));
|
||||
if (isDecoder) {
|
||||
ShadowMediaCodec.addDecoder(codecName, codecConfig);
|
||||
} else {
|
||||
ShadowMediaCodec.addEncoder(codecName, codecConfig);
|
||||
}
|
||||
|
||||
MediaFormat mediaFormat = new MediaFormat();
|
||||
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
|
||||
MediaCodecInfoBuilder.CodecCapabilitiesBuilder codecCapabilities =
|
||||
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
|
||||
.setMediaFormat(mediaFormat)
|
||||
.setIsEncoder(!isDecoder);
|
||||
|
||||
if (!colorFormats.isEmpty()) {
|
||||
codecCapabilities.setColorFormats(Ints.toArray(colorFormats));
|
||||
}
|
||||
|
||||
ShadowMediaCodecList.addCodec(
|
||||
MediaCodecInfoBuilder.newBuilder()
|
||||
.setName(codecName)
|
||||
.setIsEncoder(!isDecoder)
|
||||
.setCapabilities(codecCapabilities.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
private static void removeEncodersAndDecoders() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user