mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Filter track support by profile and level
This CL only provides checks for HEVC codecs and adds codec information to Format instances in DASH. Right now, we check that the supported profiles are advertised individually and that the supported level is equal or higher than the requested codec level. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=125470489
This commit is contained in:
parent
0139f3f276
commit
e71cdb1bad
6 changed files with 191 additions and 42 deletions
|
|
@ -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<Integer, Integer> 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.
|
||||
* <p>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<String, Integer> 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<CodecKey, List<DecoderInfo>> 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<Integer, Integer> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue