mirror of
https://github.com/samsonjs/media.git
synced 2026-03-27 09:45:47 +00:00
438 lines
15 KiB
Java
438 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
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.media.MediaCodecList;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
|
|
import java.io.IOException;
|
|
import java.util.HashMap;
|
|
|
|
/**
|
|
* A utility class for querying the available codecs.
|
|
*/
|
|
@TargetApi(16)
|
|
public final class MediaCodecUtil {
|
|
|
|
/**
|
|
* Thrown when an error occurs querying the device for its underlying media capabilities.
|
|
* <p>
|
|
* Such failures are not expected in normal operation and are normally temporary (e.g. if the
|
|
* mediaserver process has crashed and is yet to restart).
|
|
*/
|
|
public static class DecoderQueryException extends IOException {
|
|
|
|
private DecoderQueryException(Throwable cause) {
|
|
super("Failed to query underlying media codecs", cause);
|
|
}
|
|
|
|
}
|
|
|
|
private static final String TAG = "MediaCodecUtil";
|
|
|
|
private static final HashMap<CodecKey, Pair<String, CodecCapabilities>> codecs = new HashMap<>();
|
|
|
|
private MediaCodecUtil() {}
|
|
|
|
/**
|
|
* Get information about the decoder that will be used for a given mime type.
|
|
*
|
|
* @param mimeType The mime type.
|
|
* @param secure Whether the decoder is required to support secure decryption. Always pass false
|
|
* unless secure decryption really is required.
|
|
* @return Information about the decoder that will be used, or null if no decoder exists.
|
|
*/
|
|
public static DecoderInfo getDecoderInfo(String mimeType, boolean secure)
|
|
throws DecoderQueryException {
|
|
Pair<String, CodecCapabilities> info = getMediaCodecInfo(mimeType, secure);
|
|
if (info == null) {
|
|
return null;
|
|
}
|
|
return new DecoderInfo(info.first, isAdaptive(info.second));
|
|
}
|
|
|
|
/**
|
|
* Optional call to warm the codec cache for a given mime type.
|
|
* <p>
|
|
* Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)}.
|
|
*
|
|
* @param mimeType The mime type.
|
|
* @param secure Whether the decoder is required to support secure decryption. Always pass false
|
|
* unless secure decryption really is required.
|
|
*/
|
|
public static synchronized void warmCodec(String mimeType, boolean secure) {
|
|
try {
|
|
getMediaCodecInfo(mimeType, secure);
|
|
} catch (DecoderQueryException e) {
|
|
// Codec warming is best effort, so we can swallow the exception.
|
|
Log.e(TAG, "Codec warming failed", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the best decoder and its capabilities for the given mimeType.
|
|
*/
|
|
private static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
|
|
String mimeType, boolean secure) throws DecoderQueryException {
|
|
CodecKey key = new CodecKey(mimeType, secure);
|
|
if (codecs.containsKey(key)) {
|
|
return codecs.get(key);
|
|
}
|
|
MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21
|
|
? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16();
|
|
Pair<String, CodecCapabilities> codecInfo = getMediaCodecInfo(key, mediaCodecList);
|
|
// TODO: Verify this cannot occur on v22, and change >= to == [Internal: b/18678462].
|
|
if (secure && codecInfo == null && Util.SDK_INT >= 21) {
|
|
// Some devices don't list secure decoders on API level 21. Try the legacy path.
|
|
mediaCodecList = new MediaCodecListCompatV16();
|
|
codecInfo = getMediaCodecInfo(key, mediaCodecList);
|
|
if (codecInfo != null) {
|
|
Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType
|
|
+ ". Assuming: " + codecInfo.first);
|
|
}
|
|
}
|
|
return codecInfo;
|
|
}
|
|
|
|
private static Pair<String, CodecCapabilities> getMediaCodecInfo(CodecKey key,
|
|
MediaCodecListCompat mediaCodecList) throws DecoderQueryException {
|
|
try {
|
|
return getMediaCodecInfoInternal(key, mediaCodecList);
|
|
} catch (Exception e) {
|
|
// If the underlying mediaserver is in a bad state, we may catch an IllegalStateException
|
|
// or an IllegalArgumentException here.
|
|
throw new DecoderQueryException(e);
|
|
}
|
|
}
|
|
|
|
private static Pair<String, CodecCapabilities> getMediaCodecInfoInternal(CodecKey key,
|
|
MediaCodecListCompat mediaCodecList) {
|
|
String mimeType = key.mimeType;
|
|
int numberOfCodecs = mediaCodecList.getCodecCount();
|
|
boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit();
|
|
// Note: MediaCodecList is sorted by the framework such that the best decoders come first.
|
|
for (int i = 0; i < numberOfCodecs; i++) {
|
|
MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i);
|
|
String codecName = info.getName();
|
|
if (isCodecUsableDecoder(info, codecName, secureDecodersExplicit)) {
|
|
String[] supportedTypes = info.getSupportedTypes();
|
|
for (int j = 0; j < supportedTypes.length; j++) {
|
|
String supportedType = supportedTypes[j];
|
|
if (supportedType.equalsIgnoreCase(mimeType)) {
|
|
CodecCapabilities capabilities = info.getCapabilitiesForType(supportedType);
|
|
boolean secure = mediaCodecList.isSecurePlaybackSupported(key.mimeType, capabilities);
|
|
if (!secureDecodersExplicit) {
|
|
// Cache variants for both insecure and (if we think it's supported) secure playback.
|
|
codecs.put(key.secure ? new CodecKey(mimeType, false) : key,
|
|
Pair.create(codecName, capabilities));
|
|
if (secure) {
|
|
codecs.put(key.secure ? key : new CodecKey(mimeType, true),
|
|
Pair.create(codecName + ".secure", capabilities));
|
|
}
|
|
} else {
|
|
// Only cache this variant. If both insecure and secure decoders are available, they
|
|
// should both be listed separately.
|
|
codecs.put(key.secure == secure ? key : new CodecKey(mimeType, secure),
|
|
Pair.create(codecName, capabilities));
|
|
}
|
|
if (codecs.containsKey(key)) {
|
|
return codecs.get(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the specified codec is usable for decoding on the current device.
|
|
*/
|
|
private static boolean isCodecUsableDecoder(MediaCodecInfo info, String name,
|
|
boolean secureDecodersExplicit) {
|
|
if (info.isEncoder() || (!secureDecodersExplicit && name.endsWith(".secure"))) {
|
|
return false;
|
|
}
|
|
|
|
// Work around an issue where creating a particular MP3 decoder on some devices on platform API
|
|
// version 16 crashes mediaserver.
|
|
if (Util.SDK_INT == 16
|
|
&& ("dlxu".equals(Util.DEVICE) // HTC Butterfly
|
|
|| "protou".equals(Util.DEVICE) // HTC Desire X
|
|
|| "C6602".equals(Util.DEVICE) || "C6603".equals(Util.DEVICE)) // Sony Xperia Z
|
|
&& name.equals("OMX.qcom.audio.decoder.mp3")) {
|
|
return false;
|
|
}
|
|
|
|
// Work around an issue where the VP8 decoder on Samsung Galaxy S4 Mini does not render video.
|
|
if (Util.SDK_INT <= 19 && Util.DEVICE != null && Util.DEVICE.startsWith("serrano")
|
|
&& "samsung".equals(Util.MANUFACTURER) && name.equals("OMX.SEC.vp8.dec")) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static boolean isAdaptive(CodecCapabilities capabilities) {
|
|
if (Util.SDK_INT >= 19) {
|
|
return isAdaptiveV19(capabilities);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@TargetApi(19)
|
|
private static boolean isAdaptiveV19(CodecCapabilities capabilities) {
|
|
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
|
|
}
|
|
|
|
/**
|
|
* Tests whether the device advertises it can decode video of a given type at a specified
|
|
* width, height, and frame rate.
|
|
* <p>
|
|
* Must not be called if the device SDK version is less than 21.
|
|
*
|
|
* @param mimeType The mime type.
|
|
* @param secure Whether the decoder is required to support secure decryption. Always pass false
|
|
* unless secure decryption really is required.
|
|
* @param width Width in pixels.
|
|
* @param height Height in pixels.
|
|
* @param frameRate Frame rate in frames per second.
|
|
* @return Whether the decoder advertises support of the given size and frame rate.
|
|
*/
|
|
@TargetApi(21)
|
|
public static boolean isSizeAndRateSupportedV21(String mimeType, boolean secure,
|
|
int width, int height, double frameRate) throws DecoderQueryException {
|
|
Assertions.checkState(Util.SDK_INT >= 21);
|
|
Pair<String, CodecCapabilities> info = getMediaCodecInfo(mimeType, secure);
|
|
if (info == null) {
|
|
return false;
|
|
}
|
|
MediaCodecInfo.VideoCapabilities videoCapabilities = info.second.getVideoCapabilities();
|
|
return videoCapabilities != null
|
|
&& videoCapabilities.areSizeAndRateSupported(width, height, frameRate);
|
|
}
|
|
|
|
/**
|
|
* @param profile An AVC profile constant from {@link CodecProfileLevel}.
|
|
* @param level An AVC profile level from {@link CodecProfileLevel}.
|
|
* @return Whether the specified profile is supported at the specified level.
|
|
*/
|
|
public static boolean isH264ProfileSupported(int profile, int level)
|
|
throws DecoderQueryException {
|
|
Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false);
|
|
if (info == null) {
|
|
return false;
|
|
}
|
|
|
|
CodecCapabilities capabilities = info.second;
|
|
for (int i = 0; i < capabilities.profileLevels.length; i++) {
|
|
CodecProfileLevel profileLevel = capabilities.profileLevels[i];
|
|
if (profileLevel.profile == profile && profileLevel.level >= level) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return the maximum frame size for an H264 stream that can be decoded on the device.
|
|
*/
|
|
public static int maxH264DecodableFrameSize() throws DecoderQueryException {
|
|
Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false);
|
|
if (info == null) {
|
|
return 0;
|
|
}
|
|
|
|
int maxH264DecodableFrameSize = 0;
|
|
CodecCapabilities capabilities = info.second;
|
|
for (int i = 0; i < capabilities.profileLevels.length; i++) {
|
|
CodecProfileLevel profileLevel = capabilities.profileLevels[i];
|
|
maxH264DecodableFrameSize = Math.max(
|
|
avcLevelToMaxFrameSize(profileLevel.level), maxH264DecodableFrameSize);
|
|
}
|
|
|
|
return maxH264DecodableFrameSize;
|
|
}
|
|
|
|
/**
|
|
* Conversion values taken from: https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC.
|
|
*
|
|
* @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)
|
|
*/
|
|
private static int avcLevelToMaxFrameSize(int avcLevel) {
|
|
switch (avcLevel) {
|
|
case CodecProfileLevel.AVCLevel1: return 25344;
|
|
case CodecProfileLevel.AVCLevel1b: return 25344;
|
|
case CodecProfileLevel.AVCLevel12: return 101376;
|
|
case CodecProfileLevel.AVCLevel13: return 101376;
|
|
case CodecProfileLevel.AVCLevel2: return 101376;
|
|
case CodecProfileLevel.AVCLevel21: return 202752;
|
|
case CodecProfileLevel.AVCLevel22: return 414720;
|
|
case CodecProfileLevel.AVCLevel3: return 414720;
|
|
case CodecProfileLevel.AVCLevel31: return 921600;
|
|
case CodecProfileLevel.AVCLevel32: return 1310720;
|
|
case CodecProfileLevel.AVCLevel4: return 2097152;
|
|
case CodecProfileLevel.AVCLevel41: return 2097152;
|
|
case CodecProfileLevel.AVCLevel42: return 2228224;
|
|
case CodecProfileLevel.AVCLevel5: return 5652480;
|
|
case CodecProfileLevel.AVCLevel51: return 9437184;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
private interface MediaCodecListCompat {
|
|
|
|
/**
|
|
* The number of codecs in the list.
|
|
*/
|
|
public int getCodecCount();
|
|
|
|
/**
|
|
* The info at the specified index in the list.
|
|
*
|
|
* @param index The index.
|
|
*/
|
|
public MediaCodecInfo getCodecInfoAt(int index);
|
|
|
|
/**
|
|
* @return Returns whether secure decoders are explicitly listed, if present.
|
|
*/
|
|
public boolean secureDecodersExplicit();
|
|
|
|
/**
|
|
* Whether secure playback is supported for the given {@link CodecCapabilities}, which should
|
|
* have been obtained from a {@link MediaCodecInfo} obtained from this list.
|
|
*/
|
|
public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities);
|
|
|
|
}
|
|
|
|
@TargetApi(21)
|
|
private static final class MediaCodecListCompatV21 implements MediaCodecListCompat {
|
|
|
|
private final int codecKind;
|
|
|
|
private MediaCodecInfo[] mediaCodecInfos;
|
|
|
|
public MediaCodecListCompatV21(boolean includeSecure) {
|
|
codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS;
|
|
}
|
|
|
|
@Override
|
|
public int getCodecCount() {
|
|
ensureMediaCodecInfosInitialized();
|
|
return mediaCodecInfos.length;
|
|
}
|
|
|
|
@Override
|
|
public MediaCodecInfo getCodecInfoAt(int index) {
|
|
ensureMediaCodecInfosInitialized();
|
|
return mediaCodecInfos[index];
|
|
}
|
|
|
|
@Override
|
|
public boolean secureDecodersExplicit() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) {
|
|
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
|
|
}
|
|
|
|
private void ensureMediaCodecInfosInitialized() {
|
|
if (mediaCodecInfos == null) {
|
|
mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
private static final class MediaCodecListCompatV16 implements MediaCodecListCompat {
|
|
|
|
@Override
|
|
public int getCodecCount() {
|
|
return MediaCodecList.getCodecCount();
|
|
}
|
|
|
|
@Override
|
|
public MediaCodecInfo getCodecInfoAt(int index) {
|
|
return MediaCodecList.getCodecInfoAt(index);
|
|
}
|
|
|
|
@Override
|
|
public boolean secureDecodersExplicit() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) {
|
|
// Secure decoders weren't explicitly listed prior to API level 21. We assume that a secure
|
|
// H264 decoder exists.
|
|
return MimeTypes.VIDEO_H264.equals(mimeType);
|
|
}
|
|
|
|
}
|
|
|
|
private static final class CodecKey {
|
|
|
|
public final String mimeType;
|
|
public final boolean secure;
|
|
|
|
public CodecKey(String mimeType, boolean secure) {
|
|
this.mimeType = mimeType;
|
|
this.secure = secure;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode());
|
|
result = prime * result + (secure ? 1231 : 1237);
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj == null || obj.getClass() != CodecKey.class) {
|
|
return false;
|
|
}
|
|
CodecKey other = (CodecKey) obj;
|
|
return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure;
|
|
}
|
|
|
|
}
|
|
|
|
}
|