blob: 341c822d99b4377f1f598d615820f28ac7e88586 [file] [log] [blame]
/*
* Copyright (C) 2013 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.codec.cts;
import static android.media.MediaCodecInfo.CodecProfileLevel.*;
import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;
import static android.media.MediaFormat.MIMETYPE_VIDEO_H263;
import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
import static android.media.MediaFormat.MIMETYPE_VIDEO_MPEG4;
import static android.media.MediaFormat.MIMETYPE_VIDEO_VP9;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.AudioCapabilities;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecInfo.VideoCapabilities;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.cts.MediaPlayerTestBase;
import android.net.Uri;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.view.Display;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.MediaUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
import java.util.stream.Stream;
/**
* Basic validation test of data returned by MediaCodeCapabilities.
*/
@AppModeFull(reason = "Instant apps cannot access the SD card")
@RunWith(AndroidJUnit4.class)
public class MediaCodecCapabilitiesTest extends MediaPlayerTestBase {
private static final String TAG = "MediaCodecCapabilitiesTest";
private static final String MEDIA_DIR = WorkDir.getMediaDirString();
private static final int PLAY_TIME_MS = 20000;
private static final int TIMEOUT_US = 1000000; // 1 sec
private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
private final MediaCodecList mAllCodecs =
new MediaCodecList(MediaCodecList.ALL_CODECS);
private final MediaCodecInfo[] mAllInfos =
mAllCodecs.getCodecInfos();
private static final String MODULE_NAME = "CtsMediaCodecTestCases";
@Before
@Override
public void setUp() throws Throwable {
super.setUp();
}
@After
@Override
public void tearDown() {
super.tearDown();
}
// Android device implementations with H.264 encoders, MUST support Baseline Profile Level 3.
// SHOULD support Main Profile/ Level 4, if supported the device must also support Main
// Profile/Level 4 decoding.
@Test
public void testH264EncoderProfileAndLevel() throws Exception {
if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_AVC)) {
return; // skip
}
assertTrue(
"H.264 must support Baseline Profile Level 3",
hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
if (hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4)) {
assertTrue(
"H.264 decoder must support Main Profile Level 4 if it can encode it",
hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4));
}
}
// Android device implementations with H.264 decoders, MUST support Baseline Profile Level 3.
// Android Television Devices MUST support High Profile Level 4.2.
@Test
public void testH264DecoderProfileAndLevel() throws Exception {
if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_AVC)) {
return; // skip
}
assertTrue(
"H.264 must support Baseline Profile Level 3",
hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
if (isTv()) {
assertTrue(
"H.264 must support High Profile Level 4.2 on TV",
checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel42));
}
}
// Android device implementations with H.263 encoders, MUST support Level 45.
@Test
public void testH263EncoderProfileAndLevel() throws Exception {
if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_H263)) {
return; // skip
}
assertTrue(
"H.263 must support Level 45",
hasEncoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level45));
}
// Android device implementations with H.263 decoders, MUST support Level 30.
@Test
public void testH263DecoderProfileAndLevel() throws Exception {
if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_H263)) {
return; // skip
}
assertTrue(
"H.263 must support Level 30",
hasDecoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level30));
}
// Android device implementations with MPEG-4 decoders, MUST support Simple Profile Level 3.
@Test
public void testMpeg4DecoderProfileAndLevel() throws Exception {
if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_MPEG4)) {
return; // skip
}
assertTrue(
"MPEG-4 must support Simple Profile Level 3",
hasDecoder(MIMETYPE_VIDEO_MPEG4, MPEG4ProfileSimple, MPEG4Level3));
}
// Android device implementations, when supporting H.265 codec MUST support the Main Profile
// Level 3 Main tier.
// Android Television Devices MUST support the Main Profile Level 4.1 Main tier.
// When the UHD video decoding profile is supported, it MUST support Main10 Level 5 Main
// Tier profile.
@Test
public void testH265DecoderProfileAndLevel() throws Exception {
if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_HEVC)) {
return; // skip
}
assertTrue(
"H.265 must support Main Profile Main Tier Level 3",
hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3));
if (isTv()) {
assertTrue(
"H.265 must support Main Profile Main Tier Level 4.1 on TV",
hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41));
}
if (isTv() && MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_HEVC, 3840, 2160, 30)) {
assertTrue(
"H.265 must support Main10 Profile Main Tier Level 5 if UHD is supported",
hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain10, HEVCMainTierLevel5));
}
}
@Test
public void testVp9DecoderCapabilitiesOnTv() throws Exception {
if (isTv() && MediaUtils.hasHardwareCodec(MIMETYPE_VIDEO_VP9, /* encode = */ false)) {
// CDD [5.3.7.4/T-1-1]
assertTrue(MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 1920, 1080, 60 /* fps */,
VP9Profile0, null, null));
if (MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 3840, 2160, 60 /* fps */)) {
// CDD [5.3.7.5/T-2-1]
assertTrue(MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 3840, 2160, 60 /* fps */,
VP9Profile0, null, null));
}
}
}
@Test
public void testAvcBaseline1() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel1)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testAvcBaseline12() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
return; // skip
}
if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
String urlString = Uri.fromFile(new File(MEDIA_DIR,
"media_codec_capabilities_test_avc_baseline12.mp4")).toString();
playVideoWithRetries(urlString, 256, 144, PLAY_TIME_MS);
}
}
@Test
public void testAvcBaseline30() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
return; // skip
}
if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
String urlString = Uri.fromFile(new File(MEDIA_DIR,
"media_codec_capabilities_test_avc_baseline30.mp4")).toString();
playVideoWithRetries(urlString, 640, 360, PLAY_TIME_MS);
}
}
private int getMaxDisplayHeight() {
return Arrays.stream(mContext.getSystemService(DisplayManager.class).getDisplays())
.map(Display::getSupportedModes)
.flatMap(Stream::of)
.max(Comparator.comparing(Display.Mode::getPhysicalHeight))
.orElseThrow(() -> new RuntimeException("Failed to determine max height"))
.getPhysicalHeight();
}
private boolean mustSupportAvcHeight(int videoResolutionHeight) {
// https://source.android.com/docs/compatibility/13/android-13-cdd#534_h264
// If the height that is reported by the Display.getSupportedModes() method is equal or
// greater than the video resolution, device implementations:
// [C-2-1] MUST support the HD 720p video decoding profiles in the following table.
// [C-2-2] MUST support the HD 1080p video decoding profiles in the following table.
return getMaxDisplayHeight() >= videoResolutionHeight;
}
@Test
public void testAvcHigh31() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
return; // skip
}
assumeTrue(mustSupportAvcHeight(720));
if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
String urlString = Uri.fromFile(new File(MEDIA_DIR,
"media_codec_capabilities_test_avc_high31.mp4")).toString();
playVideoWithRetries(urlString, 1280, 720, PLAY_TIME_MS);
}
}
@Test
public void testAvcHigh40() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
return; // skip
}
if (ApiLevelUtil.isBefore(18)) {
MediaUtils.skipTest(TAG, "fragmented mp4 not supported");
return;
}
assumeTrue(mustSupportAvcHeight(1080));
if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
String urlString = Uri.fromFile(new File(MEDIA_DIR,
"media_codec_capabilities_test_avc_high40.mp4")).toString();
playVideoWithRetries(urlString, 1920, 1080, PLAY_TIME_MS);
}
}
@Test
public void testHevcMain1() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel1)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain2() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel2)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain21() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel21)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain3() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain31() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel31)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain4() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel4)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain41() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain5() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel5)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
@Test
public void testHevcMain51() throws Exception {
if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel51)) {
return; // skip
}
// TODO: add a test stream
MediaUtils.skipTest(TAG, "no test stream");
}
private boolean checkDecoder(String mime, int profile, int level) {
if (!hasDecoder(mime, profile, level)) {
MediaUtils.skipTest(TAG, "no " + mime + " decoder for profile "
+ profile + " and level " + level);
return false;
}
return true;
}
private boolean hasDecoder(String mime, int profile, int level) {
return supports(mime, false /* isEncoder */, profile, level, false /* defaultOnly */);
}
private boolean hasEncoder(String mime, int profile, int level) {
return supports(mime, true /* isEncoder */, profile, level, false /* defaultOnly */);
}
// Checks whether the default AOSP player can play back a specific profile and level for a
// given media type. If it cannot, it automatically logs that the test is skipped.
private boolean checkDecodeWithDefaultPlayer(String mime, int profile, int level) {
if (!supports(mime, false /* isEncoder */, profile, level, true /* defaultOnly */)) {
MediaUtils.skipTest(TAG, "default player cannot test codec");
return false;
}
return true;
}
private boolean supports(
String mime, boolean isEncoder, int profile, int level,
boolean defaultOnly) {
MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : mcl.getCodecInfos()) {
if (isEncoder != info.isEncoder()) {
continue;
}
try {
CodecCapabilities caps = info.getCapabilitiesForType(mime);
for (CodecProfileLevel pl : caps.profileLevels) {
if (pl.profile != profile) {
continue;
}
// H.263 levels are not completely ordered:
// Level45 support only implies Level10 support
if (mime.equalsIgnoreCase(MIMETYPE_VIDEO_H263)) {
if (pl.level != level && pl.level == H263Level45 && level > H263Level10) {
continue;
}
}
if (pl.level >= level) {
return true;
}
}
// the default AOSP player picks the first codec for a specific mime type, so
// we can stop after the first one found
if (defaultOnly) {
return false;
}
} catch (IllegalArgumentException e) {
}
}
return false;
}
private boolean isVideoMime(String mime) {
return mime.toLowerCase().startsWith("video/");
}
private Set<String> requiredAdaptiveFormats() {
Set<String> adaptiveFormats = new HashSet<String>();
adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_AVC);
adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP8);
adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP9);
return adaptiveFormats;
}
@Test
public void testHaveAdaptiveVideoDecoderForAllSupportedFormats() {
Set<String> supportedFormats = new HashSet<String>();
boolean skipped = true;
// gather all supported video formats
for (MediaCodecInfo info : mAllInfos) {
if (info.isEncoder()) {
continue;
}
for (String mime : info.getSupportedTypes()) {
if (isVideoMime(mime)) {
supportedFormats.add(mime);
}
}
}
// limit to CDD-required formats for now
supportedFormats.retainAll(requiredAdaptiveFormats());
// check if there is an adaptive decoder for each
for (String mime : supportedFormats) {
skipped = false;
// implicit assumption that QCIF video is always valid.
MediaFormat format = MediaFormat.createVideoFormat(mime, 176, 144);
format.setFeatureEnabled(CodecCapabilities.FEATURE_AdaptivePlayback, true);
String codec = mAllCodecs.findDecoderForFormat(format);
assertTrue(
"could not find adaptive decoder for " + mime, codec != null);
}
if (skipped) {
MediaUtils.skipTest("no video decoders that are required to be adaptive found");
}
}
@Test
public void testAllVideoDecodersAreAdaptive() {
Set<String> adaptiveFormats = requiredAdaptiveFormats();
boolean skipped = true;
for (MediaCodecInfo info : mAllInfos) {
if (info.isEncoder()) {
continue;
}
for (String mime : info.getSupportedTypes()) {
if (!isVideoMime(mime)
// limit to CDD-required formats for now
|| !adaptiveFormats.contains(mime)) {
continue;
}
skipped = false;
CodecCapabilities caps = info.getCapabilitiesForType(mime);
assertTrue(
info.getName() + " is not adaptive for " + mime,
caps.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback));
}
}
if (skipped) {
MediaUtils.skipTest("no video decoders that are required to be adaptive found");
}
}
private MediaFormat createReasonableVideoFormat(
CodecCapabilities caps, String mime, boolean encoder, int width, int height) {
VideoCapabilities vidCaps = caps.getVideoCapabilities();
MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
if (encoder) {
// bitrate
int maxWidth = vidCaps.getSupportedWidths().getUpper();
int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
int bitrate = vidCaps.getBitrateRange().clamp(
(int)(vidCaps.getBitrateRange().getUpper()
/ Math.sqrt((double)maxWidth * maxHeight / width / height)));
Log.i(TAG, "reasonable bitrate for " + width + "x" + height + "@" + maxRate
+ " " + mime + " = " + bitrate);
format.setInteger(format.KEY_BIT_RATE, bitrate);
format.setInteger(format.KEY_FRAME_RATE, maxRate);
format.setInteger(format.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
}
return format;
}
@Test
public void testSecureCodecsAdvertiseSecurePlayback() throws IOException {
boolean skipped = true;
for (MediaCodecInfo info : mAllInfos) {
boolean isEncoder = info.isEncoder();
if (isEncoder || !info.getName().endsWith(".secure")) {
continue;
}
for (String mime : info.getSupportedTypes()) {
if (!isVideoMime(mime)) {
continue;
}
skipped = false;
CodecCapabilities caps = info.getCapabilitiesForType(mime);
assertTrue(
info.getName() + " does not advertise secure playback",
caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback));
}
}
if (skipped) {
MediaUtils.skipTest("no video decoders found ending in .secure");
}
}
private Size getVideoSizeForTest(VideoCapabilities vidCaps) {
Size size = new Size(176, 144); // Use QCIF by default.
if (vidCaps != null && !vidCaps.isSizeSupported(size.getWidth(), size.getHeight())) {
int minWidth = vidCaps.getSupportedWidths().getLower();
int minHeight = vidCaps.getSupportedHeightsFor(minWidth).getLower();
size = new Size(minWidth, minHeight);
}
return size;
}
private MediaFormat createVideoFormatForBitrateMode(String mime, int width, int height,
int bitrateMode, CodecCapabilities caps) {
MediaCodecInfo.EncoderCapabilities encoderCaps = caps.getEncoderCapabilities();
if (!encoderCaps.isBitrateModeSupported(bitrateMode)) {
return null;
}
VideoCapabilities vidCaps = caps.getVideoCapabilities();
MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
// bitrate
int maxWidth = vidCaps.getSupportedWidths().getUpper();
int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
if (bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ) {
int quality = encoderCaps.getQualityRange().getLower();
Log.i(TAG, "reasonable quality for " + width + "x" + height + "@" + maxRate
+ " " + mime + " = " + quality);
format.setInteger(MediaFormat.KEY_QUALITY, quality);
} else {
int bitrate = vidCaps.getBitrateRange().clamp(
(int)(vidCaps.getBitrateRange().getUpper()
/ Math.sqrt((double)maxWidth * maxHeight / width / height)));
Log.i(TAG, "reasonable bitrate for " + width + "x" + height + "@" + maxRate
+ " " + mime + " = " + bitrate + " mode " + bitrateMode);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
}
format.setInteger(MediaFormat.KEY_FRAME_RATE, maxRate);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
CodecCapabilities.COLOR_FormatYUV420Flexible);
return format;
}
@Test
public void testAllAdvertisedVideoEncoderBitrateModes() throws IOException {
boolean skipped = true;
final int[] modes = {
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
};
for (MediaCodecInfo info : mAllInfos) {
if (!info.isEncoder()) {
continue;
}
for (String mime: info.getSupportedTypes()) {
boolean isVideo = isVideoMime(mime);
if (!isVideo) {
continue;
}
CodecCapabilities caps = info.getCapabilitiesForType(mime);
Size size = getVideoSizeForTest(caps.getVideoCapabilities());
skipped = false;
int numSupportedModes = 0;
for (int mode : modes) {
MediaFormat format = createVideoFormatForBitrateMode(
mime, size.getWidth(), size.getHeight(), mode, caps);
if (format == null) {
continue;
}
MediaCodec codec = null;
try {
codec = MediaCodec.createByCodecName(info.getName());
codec.configure(format, null /* surface */, null /* crypto */,
MediaCodec.CONFIGURE_FLAG_ENCODE);
} finally {
if (codec != null) {
codec.release();
}
}
numSupportedModes++;
}
assertTrue(info.getName() + " has no supported bitrate mode",
numSupportedModes > 0);
}
}
if (skipped) {
MediaUtils.skipTest("no video encoders found");
}
}
@Test
public void testAllNonTunneledVideoCodecsSupportFlexibleYUV() throws IOException {
boolean skipped = true;
for (MediaCodecInfo info : mAllInfos) {
boolean isEncoder = info.isEncoder();
for (String mime: info.getSupportedTypes()) {
if (!isVideoMime(mime)) {
continue;
}
CodecCapabilities caps = info.getCapabilitiesForType(mime);
if (caps.isFeatureRequired(CodecCapabilities.FEATURE_TunneledPlayback)
|| caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback)) {
continue;
}
skipped = false;
boolean found = false;
for (int c : caps.colorFormats) {
if (c == caps.COLOR_FormatYUV420Flexible) {
found = true;
break;
}
}
assertTrue(
info.getName() + " does not advertise COLOR_FormatYUV420Flexible",
found);
MediaCodec codec = null;
MediaFormat format = null;
try {
Size size = getVideoSizeForTest(caps.getVideoCapabilities());
codec = MediaCodec.createByCodecName(info.getName());
format = createReasonableVideoFormat(
caps, mime, isEncoder, size.getWidth(), size.getHeight());
format.setInteger(
MediaFormat.KEY_COLOR_FORMAT,
caps.COLOR_FormatYUV420Flexible);
codec.configure(format, null /* surface */, null /* crypto */,
isEncoder ? codec.CONFIGURE_FLAG_ENCODE : 0);
MediaFormat configuredFormat =
isEncoder ? codec.getInputFormat() : codec.getOutputFormat();
Log.d(TAG, "color format is " + configuredFormat.getInteger(
MediaFormat.KEY_COLOR_FORMAT));
if (isEncoder) {
codec.start();
int ix = codec.dequeueInputBuffer(TIMEOUT_US);
assertNotNull(
info.getName() + " encoder has non-flexYUV input buffer #" + ix,
codec.getInputImage(ix));
} else {
// TODO: test these on various decoders (need test streams)
}
} finally {
if (codec != null) {
codec.release();
}
}
}
}
if (skipped) {
MediaUtils.skipTest("no non-tunneled/non-secure video decoders found");
}
}
private static MediaFormat createMinFormat(String mime, CodecCapabilities caps) {
MediaFormat format;
if (caps.getVideoCapabilities() != null) {
VideoCapabilities vcaps = caps.getVideoCapabilities();
int minWidth = vcaps.getSupportedWidths().getLower();
int minHeight = vcaps.getSupportedHeightsFor(minWidth).getLower();
int minBitrate = vcaps.getBitrateRange().getLower();
int minFrameRate = Math.max(vcaps.getSupportedFrameRatesFor(minWidth, minHeight)
.getLower().intValue(), 1);
format = MediaFormat.createVideoFormat(mime, minWidth, minHeight);
int colorFormat = caps.colorFormats[0];
for (int i = 0; i < caps.colorFormats.length; ++i) {
colorFormat = caps.colorFormats[i];
// Avoid COLOR_FormatSurface as we will be configuring the codec without a surface.
if (colorFormat != CodecCapabilities.COLOR_FormatSurface) {
break;
}
}
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, minFrameRate);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
} else {
AudioCapabilities acaps = caps.getAudioCapabilities();
int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
int minChannelCount = 1;
int minBitrate = acaps.getBitrateRange().getLower();
format = MediaFormat.createAudioFormat(mime, minSampleRate, minChannelCount);
format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
}
return format;
}
private int getActualMax(
boolean isEncoder, String name, String mime, CodecCapabilities caps, int max) {
int flag = isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
boolean memory_limited = false;
MediaFormat format = createMinFormat(mime, caps);
Log.d(TAG, "Test format " + format);
Vector<MediaCodec> codecs = new Vector<MediaCodec>();
MediaCodec codec = null;
ActivityManager am = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
for (int i = 0; i < max; ++i) {
try {
Log.d(TAG, "Create codec " + name + " #" + i);
codec = MediaCodec.createByCodecName(name);
codec.configure(format, null, null, flag);
codec.start();
codecs.add(codec);
codec = null;
am.getMemoryInfo(outInfo);
if (outInfo.lowMemory) {
Log.d(TAG, "System is in low memory condition, stopping. max: " + i);
memory_limited = true;
break;
}
} catch (IllegalArgumentException e) {
fail("Got unexpected IllegalArgumentException " + e.getMessage());
} catch (IOException e) {
fail("Got unexpected IOException " + e.getMessage());
} catch (MediaCodec.CodecException e) {
// ERROR_INSUFFICIENT_RESOURCE is expected as the test keep creating codecs.
// But other exception should be treated as failure.
if (e.getErrorCode() == MediaCodec.CodecException.ERROR_INSUFFICIENT_RESOURCE) {
Log.d(TAG, "Got CodecException with ERROR_INSUFFICIENT_RESOURCE.");
break;
} else {
fail("Unexpected CodecException " + e.getDiagnosticInfo());
}
} finally {
if (codec != null) {
Log.d(TAG, "release codec");
codec.release();
codec = null;
}
}
}
int actualMax = codecs.size();
for (int i = 0; i < codecs.size(); ++i) {
Log.d(TAG, "release codec #" + i);
codecs.get(i).release();
}
codecs.clear();
// encode both actual max and whether we ran out of memory
if (memory_limited) {
actualMax = -actualMax;
}
return actualMax;
}
private boolean knownTypes(String type) {
return (type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3 ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3 ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1 ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263 ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2 ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4 ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8 ) ||
type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9 ));
}
@Test
public void testGetMaxSupportedInstances() {
StringBuilder xmlOverrides = new StringBuilder();
MediaCodecList allCodecs = new MediaCodecList(MediaCodecList.ALL_CODECS);
final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
for (MediaCodecInfo info : allCodecs.getCodecInfos()) {
Log.d(TAG, "codec: " + info.getName());
Log.d(TAG, " isEncoder = " + info.isEncoder());
// don't bother testing aliases
if (info.isAlias()) {
Log.d(TAG, "skipping: " + info.getName() + " is an alias for " +
info.getCanonicalName());
continue;
}
String[] types = info.getSupportedTypes();
for (int j = 0; j < types.length; ++j) {
if (!knownTypes(types[j])) {
Log.d(TAG, "skipping unknown type " + types[j]);
continue;
}
Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
CodecCapabilities caps = info.getCapabilitiesForType(types[j]);
int advertised = caps.getMaxSupportedInstances();
Log.d(TAG, "getMaxSupportedInstances returns " + advertised);
assertTrue(advertised > 0);
// see how well the declared max matches against reality
int tryMax = isLowRam ? 16 : 32;
int tryMin = isLowRam ? 4 : 16;
int trials = Math.min(advertised + 2, tryMax);
int actualMax = getActualMax(
info.isEncoder(), info.getName(), types[j], caps, trials);
Log.d(TAG, "actualMax " + actualMax + " vs advertised " + advertised
+ " tryMin " + tryMin + " tryMax " + tryMax);
boolean memory_limited = false;
if (actualMax < 0) {
memory_limited = true;
actualMax = -actualMax;
}
boolean compliant = true;
if (info.isHardwareAccelerated()) {
// very specific bounds for HW codecs
// so the adv+2 above is to see if the HW codec lets us go beyond adv
// (it should not)
if (actualMax != Math.min(advertised, tryMax)) {
Log.d(TAG, "NO: hwcodec " + actualMax + " != min(" + advertised +
"," + tryMax + ")");
compliant = false;
}
} else {
// sw codecs get a little more relaxation due to memory pressure
if (actualMax >= Math.min(advertised, tryMax)) {
// no memory issues, and we allocated them all
Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
"," + tryMax + ")");
} else if (actualMax >= Math.min(advertised, tryMin) &&
memory_limited) {
// memory issues, but we hit our floors
Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
"," + tryMin + ") + memory limited");
} else {
Log.d(TAG, "NO: swcodec didn't meet criteria");
compliant = false;
}
}
if (!compliant) {
String codec = "<MediaCodec name=\"" + info.getName() +
"\" type=\"" + types[j] + "\" >";
String limit = " <Limit name=\"concurrent-instances\" max=\"" +
actualMax + "\" />";
xmlOverrides.append(codec);
xmlOverrides.append("\n");
xmlOverrides.append(limit);
xmlOverrides.append("\n");
xmlOverrides.append("</MediaCodec>\n");
}
}
}
if (xmlOverrides.length() > 0) {
String failMessage = "In order to pass the test, please publish following " +
"codecs' concurrent instances limit in /etc/media_codecs.xml: \n";
fail(failMessage + xmlOverrides.toString());
}
}
@Test
public void testGetSupportedFrameRates() throws IOException {
// Chose MediaFormat.MIMETYPE_VIDEO_H263 randomly
CodecCapabilities codecCap = CodecCapabilities.createFromProfileLevel(
MediaFormat.MIMETYPE_VIDEO_H263, H263ProfileBaseline, H263Level45);
Range<Integer> supportedFrameRates =
codecCap.getVideoCapabilities().getSupportedFrameRates();
Log.d(TAG, "Supported Frame Rates : " + supportedFrameRates.toString());
/*
ITU-T Rec. H.263/Annex X (03/2004) says that for H263ProfileBaseline and H263Level45,
the device has to support 15 fps.
*/
assertTrue("Invalid framerate range", Range.create(1, 15).equals(supportedFrameRates));
}
@Test
public void testIsSampleRateSupported() throws IOException {
if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
return; // skip
}
// Chose AAC Decoder/MediaFormat.MIMETYPE_AUDIO_AAC randomly
MediaCodec codec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
MediaCodecInfo.AudioCapabilities audioCap = codec.getCodecInfo()
.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC).getAudioCapabilities();
final int[] validSampleRates = {8000, 16000, 22050, 44100};
for(int sampleRate : validSampleRates) {
Log.d(TAG, "SampleRate = " + sampleRate);
assertTrue("Expected True for isSampleRateSupported",
audioCap.isSampleRateSupported(sampleRate));
}
final int[] invalidSampleRates = {-1, 0, 1, Integer.MAX_VALUE};
for(int sampleRate : invalidSampleRates) {
Log.d(TAG, "SampleRate = " + sampleRate);
assertFalse("Expected False for isSampleRateSupported",
audioCap.isSampleRateSupported(sampleRate));
}
codec.release();
}
// API test coverage for MediaCodecInfo.EncoderCapabilities.getComplexityRange()
@Test
public void testGetComplexityRange() throws IOException {
boolean skipTest = true;
if (MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
// Chose AAC Encoder/MediaFormat.MIMETYPE_AUDIO_AAC randomly
MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
Range<Integer> complexityRange =
codec.getCodecInfo()
.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC)
.getEncoderCapabilities()
.getComplexityRange();
Log.d(TAG, "AAC ComplexityRange : " + complexityRange.toString());
assertTrue("AAC ComplexityRange invalid low value", complexityRange.getLower() >= 0);
codec.release();
skipTest = false;
}
if (MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
// Repeat test with FLAC Encoder
MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_FLAC);
Range<Integer> complexityRange =
codec.getCodecInfo()
.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_FLAC)
.getEncoderCapabilities()
.getComplexityRange();
Log.d(TAG, "FLAC ComplexityRange : " + complexityRange.toString());
assertTrue("FLAC ComplexityRange invalid low value", complexityRange.getLower() >= 0);
codec.release();
skipTest = false;
}
if (skipTest) {
MediaUtils.skipTest(TAG, "AAC and FLAC encoders not present");
}
}
@Test
public void testLowLatencyFeatureIsSupportedOnly() throws IOException {
MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo info : list.getCodecInfos()) {
for (String type : info.getSupportedTypes()) {
CodecCapabilities caps = info.getCapabilitiesForType(type);
if (caps.isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
assertFalse(
info.getName() + "(" + type + ") "
+ " supports low latency, but low latency shall not be required",
caps.isFeatureRequired(CodecCapabilities.FEATURE_LowLatency));
}
}
}
}
}