/* * 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.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.util.Pair; import java.util.HashMap; /** * A utility class for querying the available codecs. */ @TargetApi(16) public class MediaCodecUtil { private static final HashMap<String, Pair<MediaCodecInfo, CodecCapabilities>> codecs = new HashMap<String, Pair<MediaCodecInfo, CodecCapabilities>>(); /** * Get information about the decoder that will be used for a given mime type. If no decoder * exists for the mime type then null is returned. * * @param mimeType The mime type. * @return Information about the decoder that will be used, or null if no decoder exists. */ public static DecoderInfo getDecoderInfo(String mimeType) { Pair<MediaCodecInfo, CodecCapabilities> info = getMediaCodecInfo(mimeType); if (info == null) { return null; } return new DecoderInfo(info.first.getName(), isAdaptive(info.second)); } /** * Optional call to warm the codec cache. Call from any appropriate * place to hide latency. */ public static synchronized void warmCodecs(String[] mimeTypes) { for (int i = 0; i < mimeTypes.length; i++) { getMediaCodecInfo(mimeTypes[i]); } } /** * Returns the best decoder and its capabilities for the given mimeType. If there's no decoder * returns null. */ private static synchronized Pair<MediaCodecInfo, CodecCapabilities> getMediaCodecInfo( String mimeType) { Pair<MediaCodecInfo, CodecCapabilities> result = codecs.get(mimeType); if (result != null) { return result; } int numberOfCodecs = MediaCodecList.getCodecCount(); // 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 (!info.isEncoder() && isOmxCodec(codecName)) { String[] supportedTypes = info.getSupportedTypes(); for (int j = 0; j < supportedTypes.length; j++) { String supportedType = supportedTypes[j]; if (supportedType.equalsIgnoreCase(mimeType)) { result = Pair.create(info, info.getCapabilitiesForType(supportedType)); codecs.put(mimeType, result); return result; } } } } return null; } private static boolean isOmxCodec(String name) { return name.startsWith("OMX."); } 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); } /** * @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) { Pair<MediaCodecInfo, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264); 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() { Pair<MediaCodecInfo, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264); 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; } } }