diff --git a/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java b/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java index 09b010e3f7..84b45b2b75 100644 --- a/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java +++ b/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java @@ -15,12 +15,15 @@ */ package com.google.android.exoplayer; +import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.Util; import android.annotation.TargetApi; import android.media.MediaCodecInfo; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecProfileLevel; +import android.util.Pair; /** * Contains information about a media decoder. @@ -44,25 +47,27 @@ public final class DecoderInfo { */ public final boolean adaptive; + private final String mimeType; private final CodecCapabilities capabilities; - /** - * @param name The name of the decoder. - */ - /* package */ DecoderInfo(String name) { - this.name = name; - this.adaptive = false; - this.capabilities = null; + public static DecoderInfo newPassthroughInstance(String name) { + return new DecoderInfo(name, null, null); + } + + public static DecoderInfo newInstance(String name, String mimeType, + CodecCapabilities capabilities) { + return new DecoderInfo(name, mimeType, capabilities); } /** * @param name The name of the decoder. * @param capabilities The capabilities of the decoder. */ - /* package */ DecoderInfo(String name, CodecCapabilities capabilities) { - this.name = name; + private DecoderInfo(String name, String mimeType, CodecCapabilities capabilities) { + this.name = Assertions.checkNotNull(name); + this.mimeType = mimeType; this.capabilities = capabilities; - adaptive = isAdaptive(capabilities); + adaptive = capabilities != null && isAdaptive(capabilities); } /** @@ -75,6 +80,42 @@ public final class DecoderInfo { : capabilities.profileLevels; } + /** + * Whether the decoder supports the given {@code codec}. If there is insufficient information to + * decide, returns true. + * + * @param codec Codec string as defined in RFC 6381. + * @return True if the given codec is supported by the decoder. + */ + public boolean isCodecSupported(String codec) { + if (codec == null || mimeType == null) { + return true; + } + String codecMimeType = MimeTypes.getMediaMimeType(codec); + if (codecMimeType == null) { + return true; + } + if (!mimeType.equals(codecMimeType)) { + return false; + } + if (!codecMimeType.equals(MimeTypes.VIDEO_H265)) { + return true; + } + Pair codecProfileAndLevel = + MediaCodecUtil.getHevcProfileAndLevel(codec); + if (codecProfileAndLevel == null) { + // If we don't know any better, we assume that the profile and level are supported. + return true; + } + for (MediaCodecInfo.CodecProfileLevel capabilities : getProfileLevels()) { + if (capabilities.profile == codecProfileAndLevel.first + && capabilities.level >= codecProfileAndLevel.second) { + return true; + } + } + return false; + } + /** * Whether the decoder supports video with a specified width and height. *

diff --git a/library/src/main/java/com/google/android/exoplayer/Format.java b/library/src/main/java/com/google/android/exoplayer/Format.java index f4ec857c25..0d8883238c 100644 --- a/library/src/main/java/com/google/android/exoplayer/Format.java +++ b/library/src/main/java/com/google/android/exoplayer/Format.java @@ -388,6 +388,7 @@ public final class Format implements Parcelable { public Format copyWithManifestFormatInfo(Format manifestFormat, boolean preferManifestDrmInitData) { String id = manifestFormat.id; + String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs; int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate; float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; String language = this.language == null ? manifestFormat.language : this.language; @@ -395,7 +396,7 @@ public final class Format implements Parcelable { || this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData; boolean requiresSecureDecryption = this.requiresSecureDecryption || manifestFormat.requiresSecureDecryption; - return new Format(id, containerMimeType, sampleMimeType, null, bitrate, maxInputSize, width, + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData, requiresSecureDecryption); diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java index 1622e1bf71..cceaeccf87 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.Util; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.media.MediaCodecInfo; import android.media.MediaCodecInfo.CodecCapabilities; @@ -25,16 +26,21 @@ import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCodecList; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A utility class for querying the available codecs. */ @TargetApi(16) +@SuppressLint("InlinedApi") public final class MediaCodecUtil { /** @@ -53,7 +59,11 @@ public final class MediaCodecUtil { private static final String TAG = "MediaCodecUtil"; private static final DecoderInfo PASSTHROUGH_DECODER_INFO = - new DecoderInfo("OMX.google.raw.decoder"); + DecoderInfo.newPassthroughInstance("OMX.google.raw.decoder"); + 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"; + private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); private static final HashMap> decoderInfosCache = new HashMap<>(); @@ -158,9 +168,10 @@ public final class MediaCodecUtil { boolean secure = mediaCodecList.isSecurePlaybackSupported(mimeType, capabilities); if ((secureDecodersExplicit && key.secure == secure) || (!secureDecodersExplicit && !key.secure)) { - decoderInfos.add(new DecoderInfo(codecName, capabilities)); + decoderInfos.add(DecoderInfo.newInstance(codecName, mimeType, capabilities)); } else if (!secureDecodersExplicit && secure) { - decoderInfos.add(new DecoderInfo(codecName + ".secure", capabilities)); + decoderInfos.add(DecoderInfo.newInstance(codecName + ".secure", mimeType, + capabilities)); // It only makes sense to have one synthesized secure decoder, return immediately. return decoderInfos; } @@ -272,12 +283,57 @@ public final class MediaCodecUtil { return maxH264DecodableFrameSize; } + /** + * Returns the HEVC profile and level (as defined by {@link MediaCodecInfo.CodecProfileLevel}) + * corresponding to the given codec description string (as defined by RFC 6381). + * + * @param codec An HEVC codec description string, as defined by RFC 6381. + * @return A pair (profile constant, level constant) if {@code codec} is well-formed and + * supported, null otherwise. + */ + public static Pair getHevcProfileAndLevel(String codec) { + if (codec == null) { + return null; + } + String[] parts = codec.split("\\."); + if (!CODEC_ID_HEV1.equals(parts[0]) && !CODEC_ID_HVC1.equals(parts[0])) { + return null; + } + if (parts.length < 4) { + // The codec has fewer parts than required by the HEVC codec string format. + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + // The profile_space gets ignored. + Matcher matcher = PROFILE_PATTERN.matcher(parts[1]); + if (!matcher.matches()) { + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + String profileString = matcher.group(1); + int profile; + if ("1".equals(profileString)) { + profile = CodecProfileLevel.HEVCProfileMain; + } else if ("2".equals(profileString)) { + profile = CodecProfileLevel.HEVCProfileMain10; + } else { + Log.w(TAG, "Unknown HEVC profile string: " + profileString); + return null; + } + Integer level = HEVC_CODEC_STRING_TO_PROFILE_LEVEL.get(parts[3]); + if (level == null) { + Log.w(TAG, "Unknown HEVC level string: " + matcher.group(1)); + return null; + } + return new Pair<>(profile, level); + } + /** * Conversion values taken from ISO 14496-10 Table A-1. * * @param avcLevel one of CodecProfileLevel.AVCLevel* constants. * @return maximum frame size that can be decoded by a decoder with the specified avc level - * (or {@code -1} if the level is not recognized) + * (or {@code -1} if the level is not recognized) */ private static int avcLevelToMaxFrameSize(int avcLevel) { switch (avcLevel) { @@ -428,4 +484,35 @@ public final class MediaCodecUtil { } + static { + HEVC_CODEC_STRING_TO_PROFILE_LEVEL = new HashMap<>(); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L30", CodecProfileLevel.HEVCMainTierLevel1); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L60", CodecProfileLevel.HEVCMainTierLevel2); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L63", CodecProfileLevel.HEVCMainTierLevel21); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L90", CodecProfileLevel.HEVCMainTierLevel3); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L93", CodecProfileLevel.HEVCMainTierLevel31); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L120", CodecProfileLevel.HEVCMainTierLevel4); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L123", CodecProfileLevel.HEVCMainTierLevel41); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L150", CodecProfileLevel.HEVCMainTierLevel5); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L153", CodecProfileLevel.HEVCMainTierLevel51); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L156", CodecProfileLevel.HEVCMainTierLevel52); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L180", CodecProfileLevel.HEVCMainTierLevel6); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L183", CodecProfileLevel.HEVCMainTierLevel61); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L186", CodecProfileLevel.HEVCMainTierLevel62); + + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H30", CodecProfileLevel.HEVCHighTierLevel1); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H60", CodecProfileLevel.HEVCHighTierLevel2); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H63", CodecProfileLevel.HEVCHighTierLevel21); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H90", CodecProfileLevel.HEVCHighTierLevel3); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H93", CodecProfileLevel.HEVCHighTierLevel31); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H120", CodecProfileLevel.HEVCHighTierLevel4); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H123", CodecProfileLevel.HEVCHighTierLevel41); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H150", CodecProfileLevel.HEVCHighTierLevel5); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H153", CodecProfileLevel.HEVCHighTierLevel51); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H156", CodecProfileLevel.HEVCHighTierLevel52); + 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); + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java index 599e9ea0aa..f76ed2b03c 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -188,6 +188,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { } else { decoderCapable = decoderInfo.isVideoSizeSupportedV21(format.width, format.height); } + decoderCapable &= decoderInfo.isCodecSupported(format.codecs); } else { decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index 7996077814..1e70183915 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -405,13 +405,13 @@ public class MediaPresentationDescriptionParser extends DefaultHandler String sampleMimeType = getSampleMimeType(containerMimeType, codecs); if (sampleMimeType != null) { if (MimeTypes.isVideo(sampleMimeType)) { - return Format.createVideoContainerFormat(id, containerMimeType, sampleMimeType, null, + return Format.createVideoContainerFormat(id, containerMimeType, sampleMimeType, codecs, bitrate, width, height, frameRate, null); } else if (MimeTypes.isAudio(sampleMimeType)) { - return Format.createAudioContainerFormat(id, containerMimeType, sampleMimeType, null, + return Format.createAudioContainerFormat(id, containerMimeType, sampleMimeType, codecs, bitrate, audioChannels, audioSamplingRate, null, 0, language); } else if (mimeTypeIsRawText(sampleMimeType)) { - return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, null, + return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, bitrate, 0, language); } else { return Format.createContainerFormat(id, containerMimeType, sampleMimeType, bitrate); diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java index 45d3e46b58..1ee76bf749 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java @@ -124,15 +124,9 @@ public final class MimeTypes { } String[] codecList = codecs.split(","); for (String codec : codecList) { - codec = codec.trim(); - if (codec.startsWith("avc1") || codec.startsWith("avc3")) { - return MimeTypes.VIDEO_H264; - } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { - return MimeTypes.VIDEO_H265; - } else if (codec.startsWith("vp9")) { - return MimeTypes.VIDEO_VP9; - } else if (codec.startsWith("vp8")) { - return MimeTypes.VIDEO_VP8; + String mimeType = getMediaMimeType(codec); + if (mimeType != null && isVideo(mimeType)) { + return mimeType; } } return null; @@ -150,26 +144,51 @@ public final class MimeTypes { } String[] codecList = codecs.split(","); for (String codec : codecList) { - codec = codec.trim(); - if (codec.startsWith("mp4a")) { - return MimeTypes.AUDIO_AAC; - } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { - return MimeTypes.AUDIO_AC3; - } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { - return MimeTypes.AUDIO_E_AC3; - } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { - return MimeTypes.AUDIO_DTS; - } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { - return MimeTypes.AUDIO_DTS_HD; - } else if (codec.startsWith("opus")) { - return MimeTypes.AUDIO_OPUS; - } else if (codec.startsWith("vorbis")) { - return MimeTypes.AUDIO_VORBIS; + String mimeType = getMediaMimeType(codec); + if (mimeType != null && isAudio(mimeType)) { + return mimeType; } } return null; } + /** + * Derives a mimeType from a codec identifier, as defined in RFC 6381. + * + * @param codec The codec identifier to derive. + * @return The mimeType, or null if it could not be derived. + */ + public static String getMediaMimeType(String codec) { + if (codec == null) { + return null; + } + codec = codec.trim(); + if (codec.startsWith("avc1") || codec.startsWith("avc3")) { + return MimeTypes.VIDEO_H264; + } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { + return MimeTypes.VIDEO_H265; + } else if (codec.startsWith("vp9")) { + return MimeTypes.VIDEO_VP9; + } else if (codec.startsWith("vp8")) { + return MimeTypes.VIDEO_VP8; + } else if (codec.startsWith("mp4a")) { + return MimeTypes.AUDIO_AAC; + } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { + return MimeTypes.AUDIO_AC3; + } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { + return MimeTypes.AUDIO_E_AC3; + } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { + return MimeTypes.AUDIO_DTS; + } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { + return MimeTypes.AUDIO_DTS_HD; + } else if (codec.startsWith("opus")) { + return MimeTypes.AUDIO_OPUS; + } else if (codec.startsWith("vorbis")) { + return MimeTypes.AUDIO_VORBIS; + } + return null; + } + /** * Returns the top-level type of {@code mimeType}. *