Prioritize decoders with format support
PiperOrigin-RevId: 244167456
This commit is contained in:
parent
a985ca93c5
commit
2347bd2c99
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
### dev-v2 (not yet released) ###
|
### dev-v2 (not yet released) ###
|
||||||
|
|
||||||
|
* Decoders: prefer codecs that advertise format support over ones that do not,
|
||||||
|
even if they are listed lower in the `MediaCodecList`.
|
||||||
|
|
||||||
### 2.10.0 ###
|
### 2.10.0 ###
|
||||||
|
|
||||||
* Core library:
|
* Core library:
|
||||||
|
@ -39,6 +39,7 @@ import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
|||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
|
||||||
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaFormatUtil;
|
import com.google.android.exoplayer2.mediacodec.MediaFormatUtil;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
@ -290,8 +291,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<MediaCodecInfo> decoderInfos =
|
List<MediaCodecInfo> decoderInfos =
|
||||||
mediaCodecSelector.getDecoderInfos(
|
getDecoderInfos(mediaCodecSelector, format, requiresSecureDecryption);
|
||||||
format.sampleMimeType, requiresSecureDecryption, /* requiresTunnelingDecoder= */ false);
|
|
||||||
if (decoderInfos.isEmpty()) {
|
if (decoderInfos.isEmpty()) {
|
||||||
return requiresSecureDecryption
|
return requiresSecureDecryption
|
||||||
&& !mediaCodecSelector
|
&& !mediaCodecSelector
|
||||||
@ -327,8 +327,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
return Collections.singletonList(passthroughDecoderInfo);
|
return Collections.singletonList(passthroughDecoderInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mediaCodecSelector.getDecoderInfos(
|
List<MediaCodecInfo> decoderInfos =
|
||||||
|
mediaCodecSelector.getDecoderInfos(
|
||||||
format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false);
|
format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false);
|
||||||
|
decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format);
|
||||||
|
return Collections.unmodifiableList(decoderInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,16 +20,17 @@ import android.annotation.TargetApi;
|
|||||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||||
import android.media.MediaCodecList;
|
import android.media.MediaCodecList;
|
||||||
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -185,6 +186,26 @@ public final class MediaCodecUtil {
|
|||||||
return unmodifiableDecoderInfos;
|
return unmodifiableDecoderInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the provided decoder list sorted such that decoders with format support are
|
||||||
|
* listed first. The returned list is modifiable for convenience.
|
||||||
|
*/
|
||||||
|
@CheckResult
|
||||||
|
public static List<MediaCodecInfo> getDecoderInfosSortedByFormatSupport(
|
||||||
|
List<MediaCodecInfo> decoderInfos, Format format) {
|
||||||
|
decoderInfos = new ArrayList<>(decoderInfos);
|
||||||
|
sortByScore(
|
||||||
|
decoderInfos,
|
||||||
|
decoderInfo -> {
|
||||||
|
try {
|
||||||
|
return decoderInfo.isFormatSupported(format) ? 1 : 0;
|
||||||
|
} catch (DecoderQueryException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return decoderInfos;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the maximum frame size supported by the default H264 decoder.
|
* Returns the maximum frame size supported by the default H264 decoder.
|
||||||
*
|
*
|
||||||
@ -484,7 +505,22 @@ public final class MediaCodecUtil {
|
|||||||
*/
|
*/
|
||||||
private static void applyWorkarounds(String mimeType, List<MediaCodecInfo> decoderInfos) {
|
private static void applyWorkarounds(String mimeType, List<MediaCodecInfo> decoderInfos) {
|
||||||
if (MimeTypes.AUDIO_RAW.equals(mimeType)) {
|
if (MimeTypes.AUDIO_RAW.equals(mimeType)) {
|
||||||
Collections.sort(decoderInfos, new RawAudioCodecComparator());
|
// Work around inconsistent raw audio decoding behavior across different devices.
|
||||||
|
sortByScore(
|
||||||
|
decoderInfos,
|
||||||
|
decoderInfo -> {
|
||||||
|
String name = decoderInfo.name;
|
||||||
|
if (name.startsWith("OMX.google") || name.startsWith("c2.android")) {
|
||||||
|
// Prefer generic decoders over ones provided by the device.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (Util.SDK_INT < 26 && name.equals("OMX.MTK.AUDIO.DECODER.RAW")) {
|
||||||
|
// This decoder may modify the audio, so any other compatible decoders take
|
||||||
|
// precedence. See [Internal: b/62337687].
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
} else if (Util.SDK_INT < 21 && decoderInfos.size() > 1) {
|
} else if (Util.SDK_INT < 21 && decoderInfos.size() > 1) {
|
||||||
String firstCodecName = decoderInfos.get(0).name;
|
String firstCodecName = decoderInfos.get(0).name;
|
||||||
if ("OMX.SEC.mp3.dec".equals(firstCodecName)
|
if ("OMX.SEC.mp3.dec".equals(firstCodecName)
|
||||||
@ -494,7 +530,7 @@ public final class MediaCodecUtil {
|
|||||||
// OMX.brcm.audio.mp3.decoder on older devices. See:
|
// OMX.brcm.audio.mp3.decoder on older devices. See:
|
||||||
// https://github.com/google/ExoPlayer/issues/398 and
|
// https://github.com/google/ExoPlayer/issues/398 and
|
||||||
// https://github.com/google/ExoPlayer/issues/4519.
|
// https://github.com/google/ExoPlayer/issues/4519.
|
||||||
Collections.sort(decoderInfos, new PreferOmxGoogleCodecComparator());
|
sortByScore(decoderInfos, decoderInfo -> decoderInfo.name.startsWith("OMX.google") ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -676,6 +712,17 @@ public final class MediaCodecUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Stably sorts the provided {@code list} in-place, in order of decreasing score. */
|
||||||
|
private static <T> void sortByScore(List<T> list, ScoreProvider<T> scoreProvider) {
|
||||||
|
Collections.sort(list, (a, b) -> scoreProvider.getScore(b) - scoreProvider.getScore(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Interface for providers of item scores. */
|
||||||
|
private interface ScoreProvider<T> {
|
||||||
|
/** Returns the score of the provided item. */
|
||||||
|
int getScore(T t);
|
||||||
|
}
|
||||||
|
|
||||||
private interface MediaCodecListCompat {
|
private interface MediaCodecListCompat {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -826,44 +873,6 @@ public final class MediaCodecUtil {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Comparator for ordering media codecs that handle {@link MimeTypes#AUDIO_RAW} to work around
|
|
||||||
* possible inconsistent behavior across different devices. A list sorted with this comparator has
|
|
||||||
* more preferred codecs first.
|
|
||||||
*/
|
|
||||||
private static final class RawAudioCodecComparator implements Comparator<MediaCodecInfo> {
|
|
||||||
@Override
|
|
||||||
public int compare(MediaCodecInfo a, MediaCodecInfo b) {
|
|
||||||
return scoreMediaCodecInfo(a) - scoreMediaCodecInfo(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int scoreMediaCodecInfo(MediaCodecInfo mediaCodecInfo) {
|
|
||||||
String name = mediaCodecInfo.name;
|
|
||||||
if (name.startsWith("OMX.google") || name.startsWith("c2.android")) {
|
|
||||||
// Prefer generic decoders over ones provided by the device.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (Util.SDK_INT < 26 && name.equals("OMX.MTK.AUDIO.DECODER.RAW")) {
|
|
||||||
// This decoder may modify the audio, so any other compatible decoders take precedence. See
|
|
||||||
// [Internal: b/62337687].
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Comparator for preferring OMX.google media codecs. */
|
|
||||||
private static final class PreferOmxGoogleCodecComparator implements Comparator<MediaCodecInfo> {
|
|
||||||
@Override
|
|
||||||
public int compare(MediaCodecInfo a, MediaCodecInfo b) {
|
|
||||||
return scoreMediaCodecInfo(a) - scoreMediaCodecInfo(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int scoreMediaCodecInfo(MediaCodecInfo mediaCodecInfo) {
|
|
||||||
return mediaCodecInfo.name.startsWith("OMX.google") ? -1 : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray();
|
AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray();
|
||||||
AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline);
|
AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline);
|
||||||
|
@ -306,12 +306,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<MediaCodecInfo> decoderInfos =
|
List<MediaCodecInfo> decoderInfos =
|
||||||
getDecoderInfos(mediaCodecSelector, format, requiresSecureDecryption);
|
getDecoderInfos(
|
||||||
|
mediaCodecSelector,
|
||||||
|
format,
|
||||||
|
requiresSecureDecryption,
|
||||||
|
/* requiresTunnelingDecoder= */ false);
|
||||||
if (decoderInfos.isEmpty()) {
|
if (decoderInfos.isEmpty()) {
|
||||||
return requiresSecureDecryption
|
return requiresSecureDecryption
|
||||||
&& !mediaCodecSelector
|
&& !getDecoderInfos(
|
||||||
.getDecoderInfos(
|
mediaCodecSelector,
|
||||||
format.sampleMimeType,
|
format,
|
||||||
/* requiresSecureDecoder= */ false,
|
/* requiresSecureDecoder= */ false,
|
||||||
/* requiresTunnelingDecoder= */ false)
|
/* requiresTunnelingDecoder= */ false)
|
||||||
.isEmpty()
|
.isEmpty()
|
||||||
@ -331,8 +335,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
int tunnelingSupport = TUNNELING_NOT_SUPPORTED;
|
int tunnelingSupport = TUNNELING_NOT_SUPPORTED;
|
||||||
if (isFormatSupported) {
|
if (isFormatSupported) {
|
||||||
List<MediaCodecInfo> tunnelingDecoderInfos =
|
List<MediaCodecInfo> tunnelingDecoderInfos =
|
||||||
mediaCodecSelector.getDecoderInfos(
|
getDecoderInfos(
|
||||||
format.sampleMimeType,
|
mediaCodecSelector,
|
||||||
|
format,
|
||||||
requiresSecureDecryption,
|
requiresSecureDecryption,
|
||||||
/* requiresTunnelingDecoder= */ true);
|
/* requiresTunnelingDecoder= */ true);
|
||||||
if (!tunnelingDecoderInfos.isEmpty()) {
|
if (!tunnelingDecoderInfos.isEmpty()) {
|
||||||
@ -351,8 +356,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
protected List<MediaCodecInfo> getDecoderInfos(
|
protected List<MediaCodecInfo> getDecoderInfos(
|
||||||
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
|
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
|
||||||
throws DecoderQueryException {
|
throws DecoderQueryException {
|
||||||
|
return getDecoderInfos(mediaCodecSelector, format, requiresSecureDecoder, tunneling);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MediaCodecInfo> getDecoderInfos(
|
||||||
|
MediaCodecSelector mediaCodecSelector,
|
||||||
|
Format format,
|
||||||
|
boolean requiresSecureDecoder,
|
||||||
|
boolean requiresTunnelingDecoder)
|
||||||
|
throws DecoderQueryException {
|
||||||
List<MediaCodecInfo> decoderInfos =
|
List<MediaCodecInfo> decoderInfos =
|
||||||
mediaCodecSelector.getDecoderInfos(format.sampleMimeType, requiresSecureDecoder, tunneling);
|
mediaCodecSelector.getDecoderInfos(
|
||||||
|
format.sampleMimeType, requiresSecureDecoder, requiresTunnelingDecoder);
|
||||||
|
decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format);
|
||||||
return Collections.unmodifiableList(decoderInfos);
|
return Collections.unmodifiableList(decoderInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user