Prioritize decoders with format support

PiperOrigin-RevId: 244167456
This commit is contained in:
andrewlewis 2019-04-18 13:03:14 +01:00 committed by Andrew Lewis
parent a985ca93c5
commit 2347bd2c99
4 changed files with 83 additions and 52 deletions

View File

@ -2,6 +2,9 @@
### 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 ###
* Core library:

View File

@ -39,6 +39,7 @@ import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
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.MediaFormatUtil;
import com.google.android.exoplayer2.util.Log;
@ -290,8 +291,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
}
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(
format.sampleMimeType, requiresSecureDecryption, /* requiresTunnelingDecoder= */ false);
getDecoderInfos(mediaCodecSelector, format, requiresSecureDecryption);
if (decoderInfos.isEmpty()) {
return requiresSecureDecryption
&& !mediaCodecSelector
@ -327,8 +327,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return Collections.singletonList(passthroughDecoderInfo);
}
}
return mediaCodecSelector.getDecoderInfos(
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(
format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false);
decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format);
return Collections.unmodifiableList(decoderInfos);
}
/**

View File

@ -20,16 +20,17 @@ import android.annotation.TargetApi;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.Pair;
import android.util.SparseIntArray;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -185,6 +186,26 @@ public final class MediaCodecUtil {
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.
*
@ -484,7 +505,22 @@ public final class MediaCodecUtil {
*/
private static void applyWorkarounds(String mimeType, List<MediaCodecInfo> decoderInfos) {
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) {
String firstCodecName = decoderInfos.get(0).name;
if ("OMX.SEC.mp3.dec".equals(firstCodecName)
@ -494,7 +530,7 @@ public final class MediaCodecUtil {
// OMX.brcm.audio.mp3.decoder on older devices. See:
// https://github.com/google/ExoPlayer/issues/398 and
// 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;
}
/** 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 {
/**
@ -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 {
AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray();
AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline);

View File

@ -306,12 +306,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
}
List<MediaCodecInfo> decoderInfos =
getDecoderInfos(mediaCodecSelector, format, requiresSecureDecryption);
getDecoderInfos(
mediaCodecSelector,
format,
requiresSecureDecryption,
/* requiresTunnelingDecoder= */ false);
if (decoderInfos.isEmpty()) {
return requiresSecureDecryption
&& !mediaCodecSelector
.getDecoderInfos(
format.sampleMimeType,
&& !getDecoderInfos(
mediaCodecSelector,
format,
/* requiresSecureDecoder= */ false,
/* requiresTunnelingDecoder= */ false)
.isEmpty()
@ -331,8 +335,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
int tunnelingSupport = TUNNELING_NOT_SUPPORTED;
if (isFormatSupported) {
List<MediaCodecInfo> tunnelingDecoderInfos =
mediaCodecSelector.getDecoderInfos(
format.sampleMimeType,
getDecoderInfos(
mediaCodecSelector,
format,
requiresSecureDecryption,
/* requiresTunnelingDecoder= */ true);
if (!tunnelingDecoderInfos.isEmpty()) {
@ -351,8 +356,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
protected List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
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 =
mediaCodecSelector.getDecoderInfos(format.sampleMimeType, requiresSecureDecoder, tunneling);
mediaCodecSelector.getDecoderInfos(
format.sampleMimeType, requiresSecureDecoder, requiresTunnelingDecoder);
decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format);
return Collections.unmodifiableList(decoderInfos);
}