mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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.util.Pair;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.common.Format;
|
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.MediaCodecInfo;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of {@link Codec.DecoderFactory} that uses {@link MediaCodec} for decoding.
|
* Default implementation of {@link Codec.DecoderFactory} that uses {@link MediaCodec} for decoding.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
public class DefaultDecoderFactory implements Codec.DecoderFactory {
|
||||||
|
|
||||||
private static final String TAG = "DefaultDecoderFactory";
|
private static final String TAG = "DefaultDecoderFactory";
|
||||||
|
|
||||||
private final Context context;
|
protected final Context context;
|
||||||
|
|
||||||
private final boolean decoderSupportsKeyAllowFrameDrop;
|
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. */
|
/** Creates a new factory. */
|
||||||
public DefaultDecoderFactory(Context context) {
|
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.context = context;
|
||||||
|
this.fallbackListener = fallbackListener;
|
||||||
|
|
||||||
decoderSupportsKeyAllowFrameDrop =
|
decoderSupportsKeyAllowFrameDrop =
|
||||||
SDK_INT >= 29
|
SDK_INT >= 29
|
||||||
|
|
@ -66,30 +84,8 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
||||||
@Override
|
@Override
|
||||||
public DefaultCodec createForAudioDecoding(Format format) throws ExportException {
|
public DefaultCodec createForAudioDecoding(Format format) throws ExportException {
|
||||||
MediaFormat mediaFormat = createMediaFormatFromFormat(format);
|
MediaFormat mediaFormat = createMediaFormatFromFormat(format);
|
||||||
|
return createCodecForMediaFormatAndReportOnFallback(
|
||||||
String mediaCodecName;
|
mediaFormat, format, /* outputSurface= */ null);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
|
|
@ -129,23 +125,6 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
||||||
MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
|
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
|
@Nullable
|
||||||
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
|
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
|
||||||
if (codecProfileAndLevel != null) {
|
if (codecProfileAndLevel != null) {
|
||||||
|
|
@ -154,8 +133,73 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
||||||
MediaFormatUtil.maybeSetInteger(
|
MediaFormatUtil.maybeSetInteger(
|
||||||
mediaFormat, MediaFormat.KEY_LEVEL, codecProfileAndLevel.second);
|
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) {
|
private static boolean deviceNeedsDisable8kWorkaround(Format format) {
|
||||||
|
|
@ -207,23 +251,4 @@ public final class DefaultDecoderFactory implements Codec.DecoderFactory {
|
||||||
/* isDecoder= */ true,
|
/* isDecoder= */ true,
|
||||||
format);
|
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