mirror of
https://github.com/samsonjs/media.git
synced 2026-04-03 10:55:48 +00:00
Make DefaultDecoderFactory try multiple formats before giving up
PiperOrigin-RevId: 592622544
This commit is contained in:
parent
98519931e7
commit
e25e497227
1 changed files with 90 additions and 65 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue