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 e40620622e..96c7eb4bdf 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 @@ -287,13 +287,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } // Check capabilities for the first decoder in the list, which takes priority. MediaCodecInfo decoderInfo = decoderInfos.get(0); - // Note: We assume support for unknown sampleRate and channelCount. - boolean decoderCapable = Util.SDK_INT < 21 - || ((format.sampleRate == Format.NO_VALUE - || decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate)) - && (format.channelCount == Format.NO_VALUE - || decoderInfo.isAudioChannelCountSupportedV21(format.channelCount))); - int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; + boolean isFormatSupported = decoderInfo.isFormatSupported(format); + int formatSupport = isFormatSupported ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | formatSupport; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java index d2c92854c6..ca3d99edc6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java @@ -88,6 +88,8 @@ public final class MediaCodecInfo { /** Whether this instance describes a passthrough codec. */ public final boolean passthrough; + private final boolean isVideo; + /** * Creates an instance representing an audio passthrough decoder. * @@ -157,6 +159,7 @@ public final class MediaCodecInfo { adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities); tunneling = capabilities != null && isTunneling(capabilities); secure = forceSecure || (capabilities != null && isSecure(capabilities)); + isVideo = MimeTypes.isVideo(mimeType); } @Override @@ -187,6 +190,41 @@ public final class MediaCodecInfo { : getMaxSupportedInstancesV23(capabilities); } + /** + * Returns whether the decoder may support decoding the given {@code format}. + * + * @param format The input media format. + * @return Whether the decoder may support decoding the given {@code format}. + * @throws MediaCodecUtil.DecoderQueryException Thrown if an error occurs while querying decoders. + */ + public boolean isFormatSupported(Format format) throws MediaCodecUtil.DecoderQueryException { + if (!isCodecSupported(format.codecs)) { + return false; + } + + if (isVideo) { + if (format.width <= 0 || format.height <= 0) { + return true; + } + if (Util.SDK_INT >= 21) { + return isVideoSizeAndRateSupportedV21(format.width, format.height, format.frameRate); + } else { + boolean isFormatSupported = + format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); + if (!isFormatSupported) { + logNoSupport("legacyFrameSize, " + format.width + "x" + format.height); + } + return isFormatSupported; + } + } else { // Audio + return Util.SDK_INT < 21 + || ((format.sampleRate == Format.NO_VALUE + || isAudioSampleRateSupportedV21(format.sampleRate)) + && (format.channelCount == Format.NO_VALUE + || isAudioChannelCountSupportedV21(format.channelCount))); + } + } + /** * Whether the decoder supports the given {@code codec}. If there is insufficient information to * decide, returns true. 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 817a6b0f68..570d5074b7 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 @@ -74,6 +74,9 @@ public final class MediaCodecUtil { private static final Map HEVC_CODEC_STRING_TO_PROFILE_LEVEL; private static final String CODEC_ID_HEV1 = "hev1"; private static final String CODEC_ID_HVC1 = "hvc1"; + // MP4A AAC. + private static final SparseIntArray MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE; + private static final String CODEC_ID_MP4A = "mp4a"; // Lazily initialized. private static int maxH264DecodableFrameSize = -1; @@ -199,7 +202,7 @@ public final class MediaCodecUtil { * @return A pair (profile constant, level constant) if {@code codec} is well-formed and * recognized, or null otherwise */ - public static Pair getCodecProfileAndLevel(String codec) { + public static @Nullable Pair getCodecProfileAndLevel(String codec) { if (codec == null) { return null; } @@ -211,6 +214,8 @@ public final class MediaCodecUtil { case CODEC_ID_AVC1: case CODEC_ID_AVC2: return getAvcProfileAndLevel(codec, parts); + case CODEC_ID_MP4A: + return getAacCodecProfileAndLevel(codec, parts); default: return null; } @@ -445,8 +450,8 @@ public final class MediaCodecUtil { return new Pair<>(profile, level); } - private static Pair getAvcProfileAndLevel(String codec, String[] codecsParts) { - if (codecsParts.length < 2) { + private static Pair getAvcProfileAndLevel(String codec, String[] parts) { + if (parts.length < 2) { // The codec has fewer parts than required by the AVC codec string format. Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); return null; @@ -454,14 +459,14 @@ public final class MediaCodecUtil { Integer profileInteger; Integer levelInteger; try { - if (codecsParts[1].length() == 6) { + if (parts[1].length() == 6) { // Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal. - profileInteger = Integer.parseInt(codecsParts[1].substring(0, 2), 16); - levelInteger = Integer.parseInt(codecsParts[1].substring(4), 16); - } else if (codecsParts.length >= 3) { + profileInteger = Integer.parseInt(parts[1].substring(0, 2), 16); + levelInteger = Integer.parseInt(parts[1].substring(4), 16); + } else if (parts.length >= 3) { // Format: avc1.xx.[y]yy where xx is profile and [y]yy level, both decimal. - profileInteger = Integer.parseInt(codecsParts[1]); - levelInteger = Integer.parseInt(codecsParts[2]); + profileInteger = Integer.parseInt(parts[1]); + levelInteger = Integer.parseInt(parts[2]); } else { // We don't recognize the format. Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); @@ -514,6 +519,31 @@ public final class MediaCodecUtil { } } + private static @Nullable Pair getAacCodecProfileAndLevel( + String codec, String[] parts) { + if (parts.length != 3) { + Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec); + return null; + } + try { + // Get the object type indication, which is a hexadecimal value (see RFC 6381/ISO 14496-1). + int objectTypeIndication = Integer.parseInt(parts[1], 16); + String mimeType = MimeTypes.getMimeTypeFromMp4ObjectType(objectTypeIndication); + if (MimeTypes.AUDIO_AAC.equals(mimeType)) { + // For MPEG-4 audio this is followed by an audio object type indication as a decimal number. + int audioObjectTypeIndication = Integer.parseInt(parts[2]); + int profile = MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.get(audioObjectTypeIndication, -1); + if (profile != -1) { + // Level is set to zero in AAC decoder CodecProfileLevels. + return new Pair<>(profile, 0); + } + } + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec); + } + return null; + } + private interface MediaCodecListCompat { /** @@ -725,6 +755,20 @@ public final class MediaCodecUtil { HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H180", CodecProfileLevel.HEVCHighTierLevel6); HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H183", CodecProfileLevel.HEVCHighTierLevel61); HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H186", CodecProfileLevel.HEVCHighTierLevel62); + + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE = new SparseIntArray(); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(1, CodecProfileLevel.AACObjectMain); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(2, CodecProfileLevel.AACObjectLC); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(3, CodecProfileLevel.AACObjectSSR); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(4, CodecProfileLevel.AACObjectLTP); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(5, CodecProfileLevel.AACObjectHE); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(6, CodecProfileLevel.AACObjectScalable); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(17, CodecProfileLevel.AACObjectERLC); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(20, CodecProfileLevel.AACObjectERScalable); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(23, CodecProfileLevel.AACObjectLD); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(29, CodecProfileLevel.AACObjectHE_PS); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(39, CodecProfileLevel.AACObjectELD); + MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(42, CodecProfileLevel.AACObjectXHE); } } 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 202fcba242..1fa9fbd8fb 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 @@ -265,23 +265,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } // Check capabilities for the first decoder in the list, which takes priority. MediaCodecInfo decoderInfo = decoderInfos.get(0); - boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs); - if (decoderCapable && format.width > 0 && format.height > 0) { - if (Util.SDK_INT >= 21) { - decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height, - format.frameRate); - } else { - decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); - if (!decoderCapable) { - Log.d(TAG, "FalseCheck [legacyFrameSize, " + format.width + "x" + format.height + "] [" - + Util.DEVICE_DEBUG_INFO + "]"); - } - } - } - + boolean isFormatSupported = decoderInfo.isFormatSupported(format); int adaptiveSupport = decoderInfo.adaptive ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS; int tunnelingSupport = decoderInfo.tunneling ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; - int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; + int formatSupport = isFormatSupported ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; return adaptiveSupport | tunnelingSupport | formatSupport; }