Making mediaCodecName @NonNull in DefaultCodec constructor.

PiperOrigin-RevId: 435045138
This commit is contained in:
claincly 2022-03-16 14:34:52 +00:00 committed by Ian Baker
parent 1a6e176873
commit 191629ed7c
6 changed files with 152 additions and 46 deletions

View File

@ -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'

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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.
*/

View File

@ -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() {