diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 182701ec34..514528698f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index edaa9e1474..a38304d2b2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -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 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( - format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false); + List decoderInfos = + mediaCodecSelector.getDecoderInfos( + format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false); + decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format); + return Collections.unmodifiableList(decoderInfos); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 3211f7ea8e..1742e9ebf3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -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 getDecoderInfosSortedByFormatSupport( + List 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 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 void sortByScore(List list, ScoreProvider scoreProvider) { + Collections.sort(list, (a, b) -> scoreProvider.getScore(b) - scoreProvider.getScore(a)); + } + + /** Interface for providers of item scores. */ + private interface ScoreProvider { + /** 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 { - @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 { - @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); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index e693af2bd1..8e61388647 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -306,12 +306,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } List 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 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 getDecoderInfos( MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder) throws DecoderQueryException { + return getDecoderInfos(mediaCodecSelector, format, requiresSecureDecoder, tunneling); + } + + private static List getDecoderInfos( + MediaCodecSelector mediaCodecSelector, + Format format, + boolean requiresSecureDecoder, + boolean requiresTunnelingDecoder) + throws DecoderQueryException { List decoderInfos = - mediaCodecSelector.getDecoderInfos(format.sampleMimeType, requiresSecureDecoder, tunneling); + mediaCodecSelector.getDecoderInfos( + format.sampleMimeType, requiresSecureDecoder, requiresTunnelingDecoder); + decoderInfos = MediaCodecUtil.getDecoderInfosSortedByFormatSupport(decoderInfos, format); return Collections.unmodifiableList(decoderInfos); }