blob: 74b045223765598985d421f509637ea63e6ec79b [file] [log] [blame]
/*
* Copyright (C) 2012 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 android.media.cts;
import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback;
import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback;
import com.android.compatibility.common.util.PropertyUtil;
import android.content.pm.PackageManager;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.AudioCapabilities;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.EncoderCapabilities;
import android.media.MediaCodecInfo.VideoCapabilities;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.platform.test.annotations.RequiresDevice;
import android.test.AndroidTestCase;
import android.util.Log;
import android.util.Pair;
import android.util.Size;
import androidx.test.filters.SmallTest;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SmallTest
@RequiresDevice
public class MediaCodecListTest extends AndroidTestCase {
private static final String TAG = "MediaCodecListTest";
private static final String MEDIA_CODEC_XML_FILE = "/etc/media_codecs.xml";
private static final String VENDOR_MEDIA_CODEC_XML_FILE = "/vendor/etc/media_codecs.xml";
private static final String ODM_MEDIA_CODEC_XML_FILE = "/odm/etc/media_codecs.xml";
private final MediaCodecList mRegularCodecs =
new MediaCodecList(MediaCodecList.REGULAR_CODECS);
private final MediaCodecList mAllCodecs =
new MediaCodecList(MediaCodecList.ALL_CODECS);
private final MediaCodecInfo[] mRegularInfos =
mRegularCodecs.getCodecInfos();
private final MediaCodecInfo[] mAllInfos =
mAllCodecs.getCodecInfos();
class CodecType {
CodecType(String type, boolean isEncoder, MediaFormat sampleFormat) {
mMimeTypeName = type;
mIsEncoder = isEncoder;
mSampleFormat = sampleFormat;
}
boolean equals(CodecType codecType) {
return (mMimeTypeName.compareTo(codecType.mMimeTypeName) == 0) &&
mIsEncoder == codecType.mIsEncoder;
}
boolean canBeFound() {
return codecCanBeFound(mIsEncoder, mSampleFormat);
}
@Override
public String toString() {
return mMimeTypeName + (mIsEncoder ? " encoder" : " decoder") + " for " + mSampleFormat;
}
private String mMimeTypeName;
private boolean mIsEncoder;
private MediaFormat mSampleFormat;
};
class AudioCodec extends CodecType {
AudioCodec(String mime, boolean isEncoder, int sampleRate) {
super(mime, isEncoder, MediaFormat.createAudioFormat(
mime, sampleRate, 1 /* channelCount */));
}
}
class VideoCodec extends CodecType {
VideoCodec(String mime, boolean isEncoder) {
// implicit assumption that QVGA video is always valid
super(mime, isEncoder, MediaFormat.createVideoFormat(
mime, 176 /* width */, 144 /* height */));
}
}
public static boolean hasExpandedCodecInfo() {
return PropertyUtil.isVendorApiLevelNewerThan(29);
}
public static void testMediaCodecXmlFileExist() {
File file = new File(MEDIA_CODEC_XML_FILE);
File vendorFile = new File(VENDOR_MEDIA_CODEC_XML_FILE);
File odmFile = new File(ODM_MEDIA_CODEC_XML_FILE);
assertTrue("media_codecs.xml does not exist in /odm/etc, /vendor/etc or /etc.",
file.exists() || vendorFile.exists() || odmFile.exists());
}
private MediaCodecInfo[] getLegacyInfos() {
Log.d(TAG, "getLegacyInfos");
int codecCount = MediaCodecList.getCodecCount();
MediaCodecInfo[] res = new MediaCodecInfo[codecCount];
for (int i = 0; i < codecCount; ++i) {
res[i] = MediaCodecList.getCodecInfoAt(i);
}
return res;
}
public void assertEqualsOrSuperset(Set big, Set tiny, boolean superset) {
if (!superset) {
assertEquals(big, tiny);
} else {
assertTrue(big.containsAll(tiny));
}
}
private static <T> Set<T> asSet(T[] array) {
Set<T> s = new HashSet<T>();
for (T el : array) {
s.add(el);
}
return s;
}
private static Set<Integer> asSet(int[] array) {
Set<Integer> s = new HashSet<Integer>();
for (int el : array) {
s.add(el);
}
return s;
}
public void assertEqualsOrSuperset(
CodecCapabilities big, CodecCapabilities tiny, boolean superset) {
// ordering of enumerations may differ
assertEqualsOrSuperset(asSet(big.colorFormats), asSet(tiny.colorFormats), superset);
assertEqualsOrSuperset(asSet(big.profileLevels), asSet(tiny.profileLevels), superset);
AudioCapabilities bigAudCaps = big.getAudioCapabilities();
VideoCapabilities bigVidCaps = big.getVideoCapabilities();
EncoderCapabilities bigEncCaps = big.getEncoderCapabilities();
AudioCapabilities tinyAudCaps = tiny.getAudioCapabilities();
VideoCapabilities tinyVidCaps = tiny.getVideoCapabilities();
EncoderCapabilities tinyEncCaps = tiny.getEncoderCapabilities();
assertEquals(bigAudCaps != null, tinyAudCaps != null);
assertEquals(bigAudCaps != null, tinyAudCaps != null);
assertEquals(bigAudCaps != null, tinyAudCaps != null);
}
public void assertEqualsOrSuperset(
MediaCodecInfo big, MediaCodecInfo tiny, boolean superset) {
assertEquals(big.getName(), tiny.getName());
assertEquals(big.isEncoder(), tiny.isEncoder());
assertEqualsOrSuperset(
asSet(big.getSupportedTypes()), asSet(tiny.getSupportedTypes()), superset);
for (String type : big.getSupportedTypes()) {
assertEqualsOrSuperset(
big.getCapabilitiesForType(type),
tiny.getCapabilitiesForType(type),
superset);
}
}
public void assertSuperset(MediaCodecInfo big, MediaCodecInfo tiny) {
assertEqualsOrSuperset(big, tiny, true /* superset */);
}
public void assertEquals(MediaCodecInfo big, MediaCodecInfo tiny) {
assertEqualsOrSuperset(big, tiny, false /* superset */);
}
// Each component advertised by MediaCodecList should at least be
// instantiable.
private void testComponentInstantiation(MediaCodecInfo[] infos) throws IOException {
for (MediaCodecInfo info : infos) {
Log.d(TAG, "codec: " + info.getName());
Log.d(TAG, " isEncoder = " + info.isEncoder());
MediaCodec codec = MediaCodec.createByCodecName(info.getName());
assertEquals(codec.getName(), info.getName());
assertEquals(codec.getCanonicalName(), info.getCanonicalName());
assertEquals(codec.getCodecInfo(), info);
codec.release();
codec = null;
}
}
public void testRegularComponentInstantiation() throws IOException {
Log.d(TAG, "testRegularComponentInstantiation");
testComponentInstantiation(mRegularInfos);
}
public void testAllComponentInstantiation() throws IOException {
Log.d(TAG, "testAllComponentInstantiation");
testComponentInstantiation(mAllInfos);
}
public void testLegacyComponentInstantiation() throws IOException {
Log.d(TAG, "testLegacyComponentInstantiation");
testComponentInstantiation(getLegacyInfos());
}
// For each type advertised by any of the components we should be able
// to get capabilities.
private void testGetCapabilities(MediaCodecInfo[] infos) {
for (MediaCodecInfo info : infos) {
Log.d(TAG, "codec: " + info.getName());
Log.d(TAG, " isEncoder = " + info.isEncoder());
String[] types = info.getSupportedTypes();
for (int j = 0; j < types.length; ++j) {
Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
}
}
}
public void testGetRegularCapabilities() {
Log.d(TAG, "testGetRegularCapabilities");
testGetCapabilities(mRegularInfos);
}
public void testGetAllCapabilities() {
Log.d(TAG, "testGetAllCapabilities");
testGetCapabilities(mAllInfos);
}
public void testGetLegacyCapabilities() {
Log.d(TAG, "testGetLegacyCapabilities");
testGetCapabilities(getLegacyInfos());
}
public void testLegacyMediaCodecListIsSameAsRegular() {
// regular codecs should be equivalent to legacy codecs, including
// codec ordering
MediaCodecInfo[] legacyInfos = getLegacyInfos();
assertEquals(legacyInfos.length, mRegularInfos.length);
for (int i = 0; i < legacyInfos.length; ++i) {
assertEquals(legacyInfos[i], mRegularInfos[i]);
}
}
public void testRegularMediaCodecListIsASubsetOfAll() {
Log.d(TAG, "testRegularMediaCodecListIsASubsetOfAll");
// regular codecs should be a subsequence of all codecs, including
// codec ordering
int ix = 0;
for (MediaCodecInfo info : mAllInfos) {
if (ix == mRegularInfos.length) {
break;
}
if (!mRegularInfos[ix].getName().equals(info.getName())) {
Log.d(TAG, "skipping non-regular codec " + info.getName());
continue;
}
Log.d(TAG, "checking codec " + info.getName());
assertSuperset(info, mRegularInfos[ix]);
++ix;
}
assertEquals(
"some regular codecs are not listed in all codecs", ix, mRegularInfos.length);
}
public void testRequiredMediaCodecList() {
List<CodecType> requiredList = getRequiredCodecTypes();
List<CodecType> supportedList = getSupportedCodecTypes();
assertTrue(areRequiredCodecTypesSupported(requiredList, supportedList));
for (CodecType type : requiredList) {
assertTrue("cannot find " + type, type.canBeFound());
}
}
private boolean hasCamera() {
PackageManager pm = getContext().getPackageManager();
return pm.hasSystemFeature(pm.FEATURE_CAMERA_FRONT) ||
pm.hasSystemFeature(pm.FEATURE_CAMERA);
}
private boolean hasMicrophone() {
PackageManager pm = getContext().getPackageManager();
return pm.hasSystemFeature(pm.FEATURE_MICROPHONE);
}
private boolean isWatch() {
PackageManager pm = getContext().getPackageManager();
return pm.hasSystemFeature(pm.FEATURE_WATCH);
}
private boolean isHandheld() {
// handheld nature is not exposed to package manager, for now
// we check for touchscreen and NOT watch and NOT tv
PackageManager pm = getContext().getPackageManager();
return pm.hasSystemFeature(pm.FEATURE_TOUCHSCREEN)
&& !pm.hasSystemFeature(pm.FEATURE_WATCH)
&& !pm.hasSystemFeature(pm.FEATURE_TELEVISION)
&& !pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
}
private boolean isAutomotive() {
PackageManager pm = getContext().getPackageManager();
return pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
}
// Find whether the given codec can be found using MediaCodecList.find methods.
private boolean codecCanBeFound(boolean isEncoder, MediaFormat format) {
String codecName = isEncoder
? mRegularCodecs.findEncoderForFormat(format)
: mRegularCodecs.findDecoderForFormat(format);
return codecName != null;
}
/*
* Find whether all required media codec types are supported
*/
private boolean areRequiredCodecTypesSupported(
List<CodecType> requiredList, List<CodecType> supportedList) {
for (CodecType requiredCodec: requiredList) {
boolean isSupported = false;
for (CodecType supportedCodec: supportedList) {
if (requiredCodec.equals(supportedCodec)) {
isSupported = true;
}
}
if (!isSupported) {
String codec = requiredCodec.mMimeTypeName
+ ", " + (requiredCodec.mIsEncoder? "encoder": "decoder");
Log.e(TAG, "Media codec (" + codec + ") is not supported");
return false;
}
}
return true;
}
/*
* Find all the media codec types are supported.
*/
private List<CodecType> getSupportedCodecTypes() {
List<CodecType> supportedList = new ArrayList<CodecType>();
for (MediaCodecInfo info : mRegularInfos) {
String[] types = info.getSupportedTypes();
assertTrue("Unexpected number of supported types", types.length > 0);
boolean isEncoder = info.isEncoder();
for (int j = 0; j < types.length; ++j) {
supportedList.add(new CodecType(types[j], isEncoder, null /* sampleFormat */));
}
}
return supportedList;
}
/*
* This list should be kept in sync with the CCD document
* See http://developer.android.com/guide/appendix/media-formats.html
*/
private List<CodecType> getRequiredCodecTypes() {
List<CodecType> list = new ArrayList<CodecType>(16);
// Mandatory audio decoders
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, false, 48000));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 8000)); // mp3
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 48000)); // mp3
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 8000));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 48000));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 8000));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 48000));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 8000));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 44100));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_OPUS, false, 48000));
// Mandatory audio encoders (for non-watch devices with camera)
if (hasMicrophone() && !isWatch()) {
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 8000));
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 48000));
// flac encoder is not required
// list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, true)); // encoder
}
// Mandatory audio encoders for handheld devices
if (isHandheld()) {
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, false, 8000)); // decoder
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, true, 8000)); // encoder
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, false, 16000)); // decoder
list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, true, 16000)); // encoder
}
// Mandatory video codecs (for non-watch devices)
if (!isWatch()) {
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, false)); // avc decoder
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, true)); // avc encoder
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, false)); // vp8 decoder
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, true)); // vp8 encoder
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP9, false)); // vp9 decoder
//According to CDD, hevc decoding is not mandatory for automotive devices
if (!isAutomotive()) {
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_HEVC, false)); // hevc decoder
}
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_MPEG4, false)); // m4v decoder
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, false)); // h263 decoder
if (hasCamera()) {
list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, true)); // h263 encoder
}
}
return list;
}
public void testFindDecoderWithAacProfile() throws Exception {
Log.d(TAG, "testFindDecoderWithAacProfile");
MediaFormat format = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_AAC, 8000, 1);
List<Integer> profiles = new ArrayList<>();
profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectLC);
profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE);
profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE_PS);
// The API is added at 5.0, so the profile below must be supported.
profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectELD);
for (int profile : profiles) {
format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
String codecName = mRegularCodecs.findDecoderForFormat(format);
assertNotNull("Profile " + profile + " must be supported.", codecName);
}
}
public void testFindEncoderWithAacProfile() throws Exception {
Log.d(TAG, "testFindEncoderWithAacProfile");
MediaFormat format = MediaFormat.createAudioFormat(
MediaFormat.MIMETYPE_AUDIO_AAC, 8000, 1);
List<Integer> profiles = new ArrayList<>();
if (hasMicrophone() && !isWatch()) {
profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectLC);
// The API is added at 5.0, so the profiles below must be supported.
profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE);
profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectELD);
}
for (int profile : profiles) {
format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
String codecName = mRegularCodecs.findEncoderForFormat(format);
assertNotNull("Profile " + profile + " must be supported.", codecName);
}
}
public void testAudioCodecChannels() {
for (MediaCodecInfo info : mAllInfos) {
String[] types = info.getSupportedTypes();
for (int j = 0; j < types.length; ++j) {
CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
AudioCapabilities audioCap = cap.getAudioCapabilities();
if (audioCap == null) {
assertFalse("no audio capabilities for audio media type " + types[j] + " of "
+ info.getName(),
types[j].toLowerCase().startsWith("audio/"));
continue;
}
int n = audioCap.getMaxInputChannelCount();
Log.d(TAG, info.getName() + ": " + n);
assertTrue(info.getName() + " max input channel not positive: " + n, n > 0);
}
}
}
private void testCanonicalCodecIsNotAnAlias(String canonicalName) {
// canonical name must point to a non-alias
for (MediaCodecInfo canonical : mAllInfos) {
if (canonical.getName().equals(canonicalName)) {
assertFalse(canonical.isAlias());
return;
}
}
fail("could not find info to canonical name '" + canonicalName + "'");
}
private String getCustomPartOfComponentName(MediaCodecInfo info) {
String name = info.getName();
if (name.startsWith("OMX.") || name.startsWith("c2.")) {
// strip off OMX.<vendor_name>.
return name.replaceFirst("^OMX\\.([^.]+)\\.", "");
}
return name;
}
private void testKindInCodecNamesIsMeaningful(MediaCodecInfo info) {
String name = getCustomPartOfComponentName(info);
// codec names containing 'encoder' or 'enc' must be encoders, 'decoder' or 'dec' must
// be decoders
if (name.matches("(?i)\\b(encoder|enc)\\b")) {
assertTrue(info.isEncoder());
}
if (name.matches("(?i)\\b(decoder|dec)\\b")) {
assertFalse(info.isEncoder());
}
}
private void testMediaTypeInCodecNamesIsMeaningful(MediaCodecInfo info) {
// Codec names containing media type names must support that media type
String name = getCustomPartOfComponentName(info);
Set<String> supportedTypes = new HashSet<String>(Arrays.asList(info.getSupportedTypes()));
// video types
if (name.matches("(?i)\\b(mp(eg)?2)\\b")) {
// this may refer to audio mpeg1-layer2 or video mpeg2
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_MPEG2)
|| supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_MPEG + "-L2"));
}
if (name.matches("(?i)\\b(h\\.?263)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_H263));
}
if (name.matches("(?i)\\b(mp(eg)?4)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_MPEG4));
}
if (name.matches("(?i)\\b(h\\.?264|avc)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_AVC));
}
if (name.matches("(?i)\\b(vp8)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_VP8));
}
if (name.matches("(?i)\\b(h\\.?265|hevc)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_HEVC));
}
if (name.matches("(?i)\\b(vp9)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_VP9));
}
if (name.matches("(?i)\\b(av0?1)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_AV1));
}
// audio types
if (name.matches("(?i)\\b(mp(eg)?3)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_MPEG));
}
if (name.matches("(?i)\\b(x?aac)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AAC));
}
if (name.matches("(?i)\\b(pcm)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_RAW));
}
if (name.matches("(?i)\\b(raw)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_RAW)
|| supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_RAW));
}
if (name.matches("(?i)\\b(amr)\\b")) {
if (name.matches("(?i)\\b(nb)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_NB));
} else if (name.matches("(?i)\\b(wb)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_WB));
} else {
assertTrue(
supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
|| supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_WB));
}
}
if (name.matches("(?i)\\b(opus)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_OPUS));
}
if (name.matches("(?i)\\b(vorbis)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_VORBIS));
}
if (name.matches("(?i)\\b(flac)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_FLAC));
}
if (name.matches("(?i)\\b(ac3)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AC3));
}
if (name.matches("(?i)\\b(ac4)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AC4));
}
if (name.matches("(?i)\\b(eac3)\\b")) {
assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_EAC3)
|| supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC));
}
}
public void testCodecCharacterizations() {
for (MediaCodecInfo info : mAllInfos) {
Log.d(TAG, "codec: " + info.getName() + " canonical: " + info.getCanonicalName());
// AOSP codecs must not be marked as vendor or hardware accelerated
if (info.getName().startsWith("OMX.google.")) {
assertFalse(info.isVendor());
assertFalse(info.isHardwareAccelerated());
}
// Codec 2.0 based AOSP codecs must run in a software-only process
if (info.getName().startsWith("c2.android.")) {
assertTrue(info.isSoftwareOnly());
assertFalse(info.isVendor());
assertFalse(info.isHardwareAccelerated());
}
// validate aliases
if (info.isAlias()) {
assertFalse(info.getName().equals(info.getCanonicalName()));
testCanonicalCodecIsNotAnAlias(info.getCanonicalName());
} else {
// validate codec names: (Canonical) codec names must be meaningful.
// We only test this on canonical infos as we allow aliases to support
// existing codec names that do not fit this.
assertEquals(info.getName(), info.getCanonicalName());
testKindInCodecNamesIsMeaningful(info);
testMediaTypeInCodecNamesIsMeaningful(info);
}
// hardware accelerated codecs cannot be software only
assertFalse(info.isHardwareAccelerated() && info.isSoftwareOnly());
}
}
public void testVideoPerformancePointsSanity() {
MediaFormat hd25Format =
MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720);
hd25Format.setFloat(MediaFormat.KEY_FRAME_RATE, 25.f);
MediaFormat portraitHd240Format =
MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 720, 1280);
portraitHd240Format.setInteger(MediaFormat.KEY_FRAME_RATE, 240);
/* common-sense checks */
assertTrue(VideoCapabilities.PerformancePoint.HD_30.covers(hd25Format));
assertTrue(VideoCapabilities.PerformancePoint.HD_25.covers(hd25Format));
assertFalse(VideoCapabilities.PerformancePoint.HD_24.covers(hd25Format));
assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(hd25Format));
assertTrue(VideoCapabilities.PerformancePoint.FHD_25.covers(hd25Format));
assertFalse(VideoCapabilities.PerformancePoint.FHD_24.covers(hd25Format));
assertTrue(VideoCapabilities.PerformancePoint.HD_240.covers(portraitHd240Format));
assertFalse(VideoCapabilities.PerformancePoint.HD_200.covers(portraitHd240Format));
assertTrue(VideoCapabilities.PerformancePoint.FHD_240.covers(portraitHd240Format));
assertFalse(VideoCapabilities.PerformancePoint.FHD_200.covers(portraitHd240Format));
/* test macroblock size and conversion support */
VideoCapabilities.PerformancePoint bigBlockFHD30_120 =
new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(128, 64));
assertEquals(120, bigBlockFHD30_120.getMaxFrameRate());
assertEquals(8160, bigBlockFHD30_120.getMaxMacroBlocks());
assertEquals(244800, bigBlockFHD30_120.getMaxMacroBlockRate());
VideoCapabilities.PerformancePoint bigRotBlockFHD30_120 =
new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(64, 128));
assertEquals(120, bigRotBlockFHD30_120.getMaxFrameRate());
assertEquals(8640, bigRotBlockFHD30_120.getMaxMacroBlocks());
assertEquals(259200, bigRotBlockFHD30_120.getMaxMacroBlockRate());
/* test conversion logic */
{
/* 900*900@25-50 */
VideoCapabilities.PerformancePoint unusual =
new VideoCapabilities.PerformancePoint(900, 900, 25, 50, new Size(1, 1));
assertEquals(50, unusual.getMaxFrameRate());
assertEquals(3249, unusual.getMaxMacroBlocks());
assertEquals(81225, unusual.getMaxMacroBlockRate());
/* becomes 960*1024@25-50 */
VideoCapabilities.PerformancePoint converted1 =
new VideoCapabilities.PerformancePoint(unusual, new Size(128, 64));
assertEquals(50, converted1.getMaxFrameRate());
assertEquals(3840, converted1.getMaxMacroBlocks());
assertEquals(96000, converted1.getMaxMacroBlockRate());
/* becomes 1024*960@25-50 */
VideoCapabilities.PerformancePoint converted2 =
new VideoCapabilities.PerformancePoint(unusual, new Size(64, 128));
assertEquals(50, converted2.getMaxFrameRate());
assertEquals(3840, converted2.getMaxMacroBlocks());
assertEquals(96000, converted2.getMaxMacroBlockRate());
/* becomes 1024*1024@25-50 */
VideoCapabilities.PerformancePoint converted3 =
new VideoCapabilities.PerformancePoint(converted1, new Size(64, 128));
assertEquals(50, converted3.getMaxFrameRate());
assertEquals(4096, converted3.getMaxMacroBlocks());
assertEquals(102400, converted3.getMaxMacroBlockRate());
assertEquals(converted1, converted2);
assertEquals(converted2, converted1);
assertEquals(converted1, converted3);
assertEquals(converted3, converted1);
assertTrue(converted1.covers(converted2));
assertTrue(converted2.covers(converted1));
assertTrue(converted2.covers(converted3));
assertTrue(converted3.covers(converted2));
}
// big macroblock size does not impact standard performance points as the dimensions are set
VideoCapabilities.PerformancePoint bigBlockFHD30 =
new VideoCapabilities.PerformancePoint(1920, 1080, 30, 30, new Size(128, 64));
assertTrue(bigBlockFHD30.covers(VideoCapabilities.PerformancePoint.FHD_30));
assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(bigBlockFHD30));
assertTrue(bigBlockFHD30.equals(VideoCapabilities.PerformancePoint.FHD_30));
assertTrue(VideoCapabilities.PerformancePoint.FHD_30.equals(bigBlockFHD30));
// but it impacts the case where dimensions differ
assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1080, 1920, 30)));
assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1936, 1072, 30)));
assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1280, 720, 63)));
assertTrue(bigBlockFHD30_120.covers(new VideoCapabilities.PerformancePoint(1280, 720, 63)));
assertFalse(bigBlockFHD30_120.covers(new VideoCapabilities.PerformancePoint(1280, 720, 64)));
assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(
new VideoCapabilities.PerformancePoint(1080, 1920, 30)));
assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(
new VideoCapabilities.PerformancePoint(1936, 1072, 30)));
assertTrue(new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(1, 1))
.covers(new VideoCapabilities.PerformancePoint(1280, 720, 68)));
}
private void verifyPerformancePoints(
MediaCodecInfo info, String mediaType,
List<VideoCapabilities.PerformancePoint> points) {
List<VideoCapabilities.PerformancePoint> standardPoints = Arrays.asList(
VideoCapabilities.PerformancePoint.UHD_240,
VideoCapabilities.PerformancePoint.UHD_200,
VideoCapabilities.PerformancePoint.UHD_120,
VideoCapabilities.PerformancePoint.UHD_100,
VideoCapabilities.PerformancePoint.UHD_60,
VideoCapabilities.PerformancePoint.UHD_50,
VideoCapabilities.PerformancePoint.UHD_30,
VideoCapabilities.PerformancePoint.UHD_25,
VideoCapabilities.PerformancePoint.UHD_24,
VideoCapabilities.PerformancePoint.FHD_240,
VideoCapabilities.PerformancePoint.FHD_200,
VideoCapabilities.PerformancePoint.FHD_120,
VideoCapabilities.PerformancePoint.FHD_100,
VideoCapabilities.PerformancePoint.FHD_60,
VideoCapabilities.PerformancePoint.FHD_50,
VideoCapabilities.PerformancePoint.FHD_30,
VideoCapabilities.PerformancePoint.FHD_25,
VideoCapabilities.PerformancePoint.FHD_24,
VideoCapabilities.PerformancePoint.HD_240,
VideoCapabilities.PerformancePoint.HD_200,
VideoCapabilities.PerformancePoint.HD_120,
VideoCapabilities.PerformancePoint.HD_100,
VideoCapabilities.PerformancePoint.HD_60,
VideoCapabilities.PerformancePoint.HD_50,
VideoCapabilities.PerformancePoint.HD_30,
VideoCapabilities.PerformancePoint.HD_25,
VideoCapabilities.PerformancePoint.HD_24,
VideoCapabilities.PerformancePoint.SD_60,
VideoCapabilities.PerformancePoint.SD_50,
VideoCapabilities.PerformancePoint.SD_48,
VideoCapabilities.PerformancePoint.SD_30,
VideoCapabilities.PerformancePoint.SD_25,
VideoCapabilities.PerformancePoint.SD_24);
// Components must list all supported standard performance points unless those performance
// points are covered by other listed standard performance points.
for (VideoCapabilities.PerformancePoint pp : points) {
if (standardPoints.contains(pp)) {
// standard points must not be covered by other listed standard points
for (VideoCapabilities.PerformancePoint pp2 : points) {
if (!standardPoints.contains(pp2)) {
continue;
}
// using object equality to determine otherness
assertFalse("standard " + pp2 + " for " + info.getCanonicalName()
+ " for media type " + mediaType + " covers standard " + pp,
pp2 != pp && pp2.covers(pp));
}
} else {
// non-standard points must list all covered standard point not covered by another
// listed standard point
for (VideoCapabilities.PerformancePoint spp : standardPoints) {
if (pp.covers(spp)) {
// Must be either listed or covered by another standard. Since a point
// covers itself, it is sufficient to check that it is covered by a listed
// standard point.
boolean covered = false;
for (VideoCapabilities.PerformancePoint pp2 : points) {
// using object equality to determine otherness
if (standardPoints.contains(pp2) && pp2.covers(spp)) {
covered = true;
break;
}
}
assertTrue(pp + " for " + info.getCanonicalName() + " for media type "
+ mediaType + " covers standard " + spp
+ " that is not covered by a listed standard point",
covered);
}
}
// non-standard points should not be covered by any other performance point
for (VideoCapabilities.PerformancePoint pp2 : points) {
// using object equality to determine otherness
assertFalse(pp2 + " for " + info.getCanonicalName()
+ " for media type " + mediaType + " covers " + pp,
pp2 != pp && pp2.covers(pp));
}
}
}
}
public void testAllHardwareAcceleratedVideoCodecsPublishPerformancePoints() {
List<String> mandatoryTypes = Arrays.asList(
MediaFormat.MIMETYPE_VIDEO_AVC,
MediaFormat.MIMETYPE_VIDEO_VP8,
MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
MediaFormat.MIMETYPE_VIDEO_HEVC,
MediaFormat.MIMETYPE_VIDEO_VP9,
MediaFormat.MIMETYPE_VIDEO_AV1);
String[] featuresToConfig = new String[] {
FEATURE_SecurePlayback,
FEATURE_TunneledPlayback,
};
Set<Pair<String, Integer>> describedTypes = new HashSet<>(); // mediaType - featureIndex
Set<Pair<String, Integer>> supportedTypes = new HashSet<>(); // mediaType - featureIndex
// Once any hardware codec performance is described, we assume that all hardware codecs
// must be described, even if we cannot confirm expanded codec info support.
boolean hasPerformancePoints = hasExpandedCodecInfo();
if (!hasPerformancePoints) {
for (MediaCodecInfo info : mAllInfos) {
String[] types = info.getSupportedTypes();
for (int j = 0; j < types.length; ++j) {
String mediaType = types[j];
CodecCapabilities cap = info.getCapabilitiesForType(mediaType);
VideoCapabilities videoCap = cap.getVideoCapabilities();
if (videoCap != null
&& videoCap.getSupportedPerformancePoints() != null) {
hasPerformancePoints = true;
break;
}
}
if (hasPerformancePoints) {
break;
}
}
}
for (MediaCodecInfo info : mAllInfos) {
String[] types = info.getSupportedTypes();
for (int j = 0; j < types.length; ++j) {
String mediaType = types[j];
CodecCapabilities cap = info.getCapabilitiesForType(mediaType);
MediaFormat defaultFormat = cap.getDefaultFormat();
VideoCapabilities videoCap = cap.getVideoCapabilities();
Log.d(TAG, "codec: " + info.getName() + " canonical: " + info.getCanonicalName()
+ " type: " + mediaType);
if (videoCap == null) {
assertFalse("no video capabilities for video media type " + mediaType + " of "
+ info.getName(),
mediaType.toLowerCase().startsWith("video/"));
continue;
}
List<VideoCapabilities.PerformancePoint> pps =
videoCap.getSupportedPerformancePoints();
// see which feature combinations are supported by this codec
// we do this by counting in binary up to a number of bits
List<Integer> supportedFeatureConfigs = new ArrayList<Integer>();
for (int cfg_ix = 1 << featuresToConfig.length; --cfg_ix >= 0; ) {
boolean supported = true;
for (int f_ix = 0; supported && f_ix < featuresToConfig.length; ++f_ix) {
if (((cfg_ix >> f_ix) & 1) != 0) {
// feature is to be enabled
supported = supported && cap.isFeatureSupported(featuresToConfig[f_ix]);
} else {
// feature is not to be enabled
supported = supported && !cap.isFeatureRequired(featuresToConfig[f_ix]);
}
}
if (supported) {
supportedFeatureConfigs.add(cfg_ix);
}
}
Log.d(TAG, "codec supports configs " + Arrays.toString(
supportedFeatureConfigs.stream().mapToInt(Integer::intValue).toArray()));
boolean isMandatory = mandatoryTypes.contains(mediaType);
if (info.isHardwareAccelerated()) {
for (Integer cfg_ix : supportedFeatureConfigs) {
Pair<String, Integer> type = Pair.create(mediaType, cfg_ix);
if (hasPerformancePoints && isMandatory) {
supportedTypes.add(type);
}
if (pps != null && pps.size() > 0) {
describedTypes.add(type);
}
}
}
if (pps == null) {
if (hasExpandedCodecInfo()) {
// Hardware-accelerated video components must publish performance points,
// even if it is an empty list.
assertFalse("HW-accelerated codec '" + info.getName()
+ "' must publish performance points", info.isHardwareAccelerated());
}
continue;
}
// At least one hardware accelerated codec for each media type (including secure
// codecs) must publish valid performance points for AVC/VP8/VP9/HEVC/AV1.
if (pps.size() == 0) {
if (isMandatory) {
Log.d(TAG, "empty performance points list published by HW accelerated" +
"component " + info.getName() + " for " + types[j]);
}
} else {
for (VideoCapabilities.PerformancePoint p : pps) {
Log.d(TAG, "got performance point " + p);
}
verifyPerformancePoints(info, types[j], pps);
}
}
}
for (Pair<String, Integer> type : supportedTypes) {
assertTrue("codecs for media type " + type.first + " in configuration " + type.second
+ " do not have substantial performance point data",
describedTypes.contains(type));
}
}
}