Make DefaultDecoderFactory try multiple formats before giving up

PiperOrigin-RevId: 592622544
This commit is contained in:
Googler 2023-12-20 12:04:04 -08:00 committed by Copybara-Service
parent 98519931e7
commit e25e497227

View file

@ -28,7 +28,6 @@ import android.os.Build;
import android.util.Pair;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
@ -40,23 +39,42 @@ import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
/**
* Default implementation of {@link Codec.DecoderFactory} that uses {@link MediaCodec} for decoding.
*/
@UnstableApi
public final class DefaultDecoderFactory implements Codec.DecoderFactory {
public class DefaultDecoderFactory implements Codec.DecoderFactory {
private static final String TAG = "DefaultDecoderFactory";
private final Context context;
protected final Context context;
private final boolean decoderSupportsKeyAllowFrameDrop;
@Nullable private final CodecFallbackListener fallbackListener;
/** A custom listener of codec initialization failures. */
public static interface CodecFallbackListener {
/**
* Reports that we were able to initialize the codec, however we had to apply a fallback due to
* {@code initializationExceptions}.
*/
public void onCodecFallback(
String fallbackCodecName, List<ExportException> initializationExceptions);
}
/** Creates a new factory. */
public DefaultDecoderFactory(Context context) {
this(context, /* fallbackListener= */ null);
}
/** Creates a new factory that supports falling back to a different codec when needed. */
public DefaultDecoderFactory(Context context, @Nullable CodecFallbackListener fallbackListener) {
this.context = context;
this.fallbackListener = fallbackListener;
decoderSupportsKeyAllowFrameDrop =
SDK_INT >= 29
@ -66,30 +84,8 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
@Override
public DefaultCodec createForAudioDecoding(Format format) throws ExportException {
MediaFormat mediaFormat = createMediaFormatFromFormat(format);
String mediaCodecName;
try {
@Nullable MediaCodecInfo decoderInfo = getDecoderInfo(format);
if (decoderInfo == null) {
throw createExportException(format, /* reason= */ "No decoders for format");
}
mediaCodecName = decoderInfo.name;
String codecMimeType = decoderInfo.codecMimeType;
// Does not alter format.sampleMimeType to keep the original MimeType.
// The MIME type of the selected decoder may differ from Format.sampleMimeType.
mediaFormat.setString(MediaFormat.KEY_MIME, codecMimeType);
} catch (MediaCodecUtil.DecoderQueryException e) {
Log.e(TAG, "Error querying decoders", e);
throw createExportException(format, /* reason= */ "Querying codecs failed.");
}
return new DefaultCodec(
context,
format,
mediaFormat,
mediaCodecName,
/* isDecoder= */ true,
/* outputSurface= */ null);
return createCodecForMediaFormatAndReportOnFallback(
mediaFormat, format, /* outputSurface= */ null);
}
@SuppressLint("InlinedApi")
@ -129,23 +125,6 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
}
String mediaCodecName;
try {
@Nullable MediaCodecInfo decoderInfo = getDecoderInfo(format);
if (decoderInfo == null) {
throw createExportException(format, /* reason= */ "No decoders for format");
}
mediaCodecName = decoderInfo.name;
String codecMimeType = decoderInfo.codecMimeType;
// Does not alter format.sampleMimeType to keep the original MimeType.
// The MIME type of the selected decoder may differ from Format.sampleMimeType, for example,
// video/hevc is used instead of video/dolby-vision for some specific DolbyVision videos.
mediaFormat.setString(MediaFormat.KEY_MIME, codecMimeType);
} catch (MediaCodecUtil.DecoderQueryException e) {
Log.e(TAG, "Error querying decoders", e);
throw createExportException(format, /* reason= */ "Querying codecs failed");
}
@Nullable
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
if (codecProfileAndLevel != null) {
@ -154,8 +133,73 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
MediaFormatUtil.maybeSetInteger(
mediaFormat, MediaFormat.KEY_LEVEL, codecProfileAndLevel.second);
}
return new DefaultCodec(
context, format, mediaFormat, mediaCodecName, /* isDecoder= */ true, outputSurface);
return createCodecForMediaFormatAndReportOnFallback(mediaFormat, format, outputSurface);
}
private DefaultCodec createCodecForMediaFormatAndReportOnFallback(
MediaFormat mediaFormat, Format format, @Nullable Surface outputSurface)
throws ExportException {
List<MediaCodecInfo> decoderInfos = ImmutableList.of();
checkNotNull(format.sampleMimeType);
try {
decoderInfos =
MediaCodecUtil.getDecoderInfosSortedByFormatSupport(
MediaCodecUtil.getDecoderInfosSoftMatch(
MediaCodecSelector.DEFAULT,
format,
/* requiresSecureDecoder= */ false,
/* requiresTunnelingDecoder= */ false),
format);
} catch (MediaCodecUtil.DecoderQueryException e) {
Log.e(TAG, "Error querying decoders", e);
throw createExportException(format, /* reason= */ "Querying codecs failed");
}
if (decoderInfos.isEmpty()) {
throw createExportException(format, /* reason= */ "No decoders for format");
}
List<ExportException> codecInitExceptions = new ArrayList<>();
DefaultCodec codec =
createCodecFromDecoderInfos(
context,
fallbackListener == null ? decoderInfos.subList(0, 1) : decoderInfos,
format,
mediaFormat,
outputSurface,
codecInitExceptions);
if (fallbackListener != null && !codecInitExceptions.isEmpty()) {
fallbackListener.onCodecFallback(codec.getName(), codecInitExceptions);
}
return codec;
}
private static DefaultCodec createCodecFromDecoderInfos(
Context context,
List<MediaCodecInfo> decoderInfos,
Format format,
MediaFormat mediaFormat,
@Nullable Surface outputSurface,
List<ExportException> codecInitExceptions)
throws ExportException {
for (MediaCodecInfo decoderInfo : decoderInfos) {
String codecMimeType = decoderInfo.codecMimeType;
// Does not alter format.sampleMimeType to keep the original MimeType.
// The MIME type of the selected decoder may differ from Format.sampleMimeType, for example,
// video/hevc is used instead of video/dolby-vision for some specific DolbyVision videos.
mediaFormat.setString(MediaFormat.KEY_MIME, codecMimeType);
try {
return new DefaultCodec(
context, format, mediaFormat, decoderInfo.name, /* isDecoder= */ true, outputSurface);
} catch (ExportException e) {
codecInitExceptions.add(e);
}
}
// All codecs failed to be initialized, throw the first codec init error out.
throw codecInitExceptions.get(0);
}
private static boolean deviceNeedsDisable8kWorkaround(Format format) {
@ -207,23 +251,4 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
/* isDecoder= */ true,
format);
}
@VisibleForTesting
@Nullable
/* package */ static MediaCodecInfo getDecoderInfo(Format format)
throws MediaCodecUtil.DecoderQueryException {
checkNotNull(format.sampleMimeType);
List<MediaCodecInfo> decoderInfos =
MediaCodecUtil.getDecoderInfosSortedByFormatSupport(
MediaCodecUtil.getDecoderInfosSoftMatch(
MediaCodecSelector.DEFAULT,
format,
/* requiresSecureDecoder= */ false,
/* requiresTunnelingDecoder= */ false),
format);
if (decoderInfos.isEmpty()) {
return null;
}
return decoderInfos.get(0);
}
}