blob: a5eb22cd78ee65a242cf177478b4ba00b9f93d21 [file] [log] [blame]
/*
* Copyright (C) 2020 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.mediav2.cts;
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
import static android.media.MediaCodecInfo.CodecProfileLevel.*;
import static org.junit.Assert.assertEquals;
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 android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.mediav2.common.cts.EncoderConfigParams;
import android.mediav2.common.cts.EncoderTestBase;
import android.mediav2.common.cts.OutputManager;
import android.util.Log;
import android.util.Pair;
import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.CddTest;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
/**
* EncoderProfileLevelTest validates the profile, level information advertised by the component
* in its codec capabilities. The test sets profile and level keys in media format and uses it
* during encoder configuration. Upon successful configuration, frames are queued for encoding
* (byte buffer mode) and the encoded output (bitstream) is expected to contain the same profile
* and level information that was used during configure.
* <p>
* NOTE: The test configures profile, level information basing on standard guidelines, not
* arbitrarily so encoders ARE expected to place these values in the bitstream as-is.
* <p>
* The test additionally checks if the output format returned by component contains same profile
* and level information. Having output format contain this information is useful during muxing
* <p>
* As per cdd, if a device contains an encoder capable of encoding a profile/level combination
* then it should contain a decoder capable of decoding the same profile/level combination. This
* is verified.
* <p>
* If device implementations support encoding in a media type, then as per cdd they are expected to
* handle certain profile and level configurations. This is verified as well.
*/
@RunWith(Parameterized.class)
public class EncoderProfileLevelTest extends EncoderTestBase {
private static final String LOG_TAG = EncoderProfileLevelTest.class.getSimpleName();
private static final HashMap<String, Pair<int[], Integer>> PROFILE_LEVEL_CDD = new HashMap<>();
public EncoderProfileLevelTest(String encoder, String mediaType,
EncoderConfigParams[] encCfgParams, @SuppressWarnings("unused") String testLabel,
String allTestParams) {
super(encoder, mediaType, encCfgParams, allTestParams);
}
private static EncoderConfigParams[] getVideoEncoderCfgParams(String mediaType, int bitRate,
int width, int height, int frameRate, int colorFormat, int maxBframe, int[] profiles) {
ArrayList<EncoderConfigParams> cfgParams = new ArrayList<>();
for (int profile : profiles) {
int level = getMinLevel(mediaType, width, height, frameRate, bitRate, profile);
if (mediaType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)
&& maxBframe != 0
&& (profile == AVCProfileBaseline
|| profile == AVCProfileConstrainedBaseline)) {
continue;
}
cfgParams.add(new EncoderConfigParams.Builder(mediaType)
.setBitRate(bitRate)
.setWidth(width)
.setHeight(height)
.setFrameRate(frameRate)
.setMaxBFrames(maxBframe)
.setProfile(profile)
.setLevel(level)
.setColorFormat(colorFormat)
.build());
}
return cfgParams.toArray(new EncoderConfigParams[0]);
}
private static EncoderConfigParams[] getAudioEncoderCfgParams(String mediaType, int bitRate,
int sampleRate, int channelCount, int[] profiles) {
EncoderConfigParams[] cfgParams = new EncoderConfigParams[profiles.length];
for (int i = 0; i < profiles.length; i++) {
cfgParams[i] = new EncoderConfigParams.Builder(mediaType)
.setBitRate(bitRate)
.setSampleRate(sampleRate)
.setChannelCount(channelCount)
.setProfile(profiles[i])
.build();
}
return cfgParams;
}
@Parameterized.Parameters(name = "{index}({0}_{1}_{3})")
public static Collection<Object[]> input() {
final boolean isEncoder = true;
final boolean needAudio = true;
final boolean needVideo = true;
final Object[][] exhaustiveArgsList = new Object[][]{
// Audio - CodecMime, bit-rate, sample rate, channel count
{MediaFormat.MIMETYPE_AUDIO_AAC, 64000, 48000, 1, -1},
{MediaFormat.MIMETYPE_AUDIO_AAC, 128000, 48000, 2, -1},
// Video - CodecMime, bit-rate, height, width, frame-rate
// TODO (b/151423508)
/*{MediaFormat.MIMETYPE_VIDEO_AVC, 64000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_AVC, 128000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_AVC, 192000, 352, 288, 7},
{MediaFormat.MIMETYPE_VIDEO_AVC, 384000, 352, 288, 15},*/
{MediaFormat.MIMETYPE_VIDEO_AVC, 768000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_AVC, 2000000, 352, 288, 30},
// TODO (b/151423508)
/*{MediaFormat.MIMETYPE_VIDEO_AVC, 4000000, 352, 576, 25},
{MediaFormat.MIMETYPE_VIDEO_AVC, 4000000, 720, 576, 12},
{MediaFormat.MIMETYPE_VIDEO_AVC, 10000000, 720, 576, 25},*/
{MediaFormat.MIMETYPE_VIDEO_AVC, 14000000, 1280, 720, 30},
{MediaFormat.MIMETYPE_VIDEO_AVC, 20000000, 1280, 1024, 42},
{MediaFormat.MIMETYPE_VIDEO_AVC, 20000000, 2048, 1024, 30},
{MediaFormat.MIMETYPE_VIDEO_AVC, 50000000, 2048, 1024, 30},
{MediaFormat.MIMETYPE_VIDEO_AVC, 50000000, 2048, 1080, 60},
{MediaFormat.MIMETYPE_VIDEO_AVC, 135000000, 3672, 1536, 25},
{MediaFormat.MIMETYPE_VIDEO_AVC, 240000000, 4096, 2304, 25},
{MediaFormat.MIMETYPE_VIDEO_AVC, 240000000, 4096, 2304, 50},
{MediaFormat.MIMETYPE_VIDEO_AVC, 240000000, 8192, 4320, 30},
{MediaFormat.MIMETYPE_VIDEO_AVC, 480000000, 8192, 4320, 60},
{MediaFormat.MIMETYPE_VIDEO_AVC, 800000000, 8192, 4320, 120},
{MediaFormat.MIMETYPE_VIDEO_MPEG2, 4000000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG2, 15000000, 720, 576, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG2, 60000000, 1440, 1088, 60},
{MediaFormat.MIMETYPE_VIDEO_MPEG2, 80000000, 1920, 1088, 60},
{MediaFormat.MIMETYPE_VIDEO_MPEG2, 80000000, 1920, 1088, 60},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 64000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 64000, 176, 144, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 128000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 128000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 384000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 4000000, 640, 480, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 8000000, 720, 576, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 12000000, 1280, 720, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 128000, 176, 144, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 384000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 768000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 1500000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 3000000, 704, 576, 30},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, 8000000, 720, 576, 30},
// TODO (b/151430764)
/*{MediaFormat.MIMETYPE_VIDEO_VP9, 200000, 256, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_VP9, 8000000, 384, 192, 30},
{MediaFormat.MIMETYPE_VIDEO_VP9, 1800000, 480, 256, 30},
{MediaFormat.MIMETYPE_VIDEO_VP9, 3600000, 640, 384, 30},
{MediaFormat.MIMETYPE_VIDEO_VP9, 7200000, 1080, 512, 30},*/
{MediaFormat.MIMETYPE_VIDEO_VP9, 12000000, 1280, 768, 30},
{MediaFormat.MIMETYPE_VIDEO_VP9, 18000000, 2048, 1088, 30},
{MediaFormat.MIMETYPE_VIDEO_VP9, 30000000, 2048, 1088, 60},
{MediaFormat.MIMETYPE_VIDEO_VP9, 60000000, 4096, 2176, 30},
{MediaFormat.MIMETYPE_VIDEO_VP9, 120000000, 4096, 2176, 60},
{MediaFormat.MIMETYPE_VIDEO_VP9, 180000000, 4096, 2176, 120},
{MediaFormat.MIMETYPE_VIDEO_VP9, 180000000, 8192, 4352, 30},
{MediaFormat.MIMETYPE_VIDEO_VP9, 240000000, 8192, 4352, 60},
{MediaFormat.MIMETYPE_VIDEO_VP9, 480000000, 8192, 4352, 120},
{MediaFormat.MIMETYPE_VIDEO_H263, 64000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_H263, 128000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_H263, 128000, 176, 144, 30},
{MediaFormat.MIMETYPE_VIDEO_H263, 128000, 352, 288, 15},
{MediaFormat.MIMETYPE_VIDEO_H263, 384000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_H263, 2048000, 352, 288, 30},
{MediaFormat.MIMETYPE_VIDEO_H263, 4096000, 352, 240, 60},
{MediaFormat.MIMETYPE_VIDEO_H263, 4096000, 352, 288, 50},
{MediaFormat.MIMETYPE_VIDEO_H263, 8192000, 720, 240, 60},
{MediaFormat.MIMETYPE_VIDEO_H263, 8192000, 720, 288, 50},
{MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 480, 60},
{MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 576, 50},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 128000, 176, 144, 15},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 1500000, 352, 288, 30},
// TODO (b/152576008) - Limit HEVC Encoder test to 512x512
{MediaFormat.MIMETYPE_VIDEO_HEVC, 3000000, 512, 512, 30},
//{MediaFormat.MIMETYPE_VIDEO_HEVC, 3000000, 640, 360, 30},
//{MediaFormat.MIMETYPE_VIDEO_HEVC, 6000000, 960, 540, 30},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 10000000, 1280, 720, 33},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 12000000, 2048, 1080, 30},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 20000000, 2048, 1080, 60},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 25000000, 4096, 2160, 30},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 40000000, 4096, 2160, 60},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 60000000, 4096, 2160, 120},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 60000000, 8192, 4320, 30},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 120000000, 8192, 4320, 60},
{MediaFormat.MIMETYPE_VIDEO_HEVC, 240000000, 8192, 4320, 120},
{MediaFormat.MIMETYPE_VIDEO_AV1, 1500000, 426, 240, 30},
{MediaFormat.MIMETYPE_VIDEO_AV1, 3000000, 640, 360, 30},
{MediaFormat.MIMETYPE_VIDEO_AV1, 6000000, 854, 480, 30},
{MediaFormat.MIMETYPE_VIDEO_AV1, 10000000, 1280, 720, 30},
{MediaFormat.MIMETYPE_VIDEO_AV1, 12000000, 1920, 1080, 30},
{MediaFormat.MIMETYPE_VIDEO_AV1, 20000000, 1920, 1080, 60},
{MediaFormat.MIMETYPE_VIDEO_AV1, 30000000, 3840, 2160, 30},
{MediaFormat.MIMETYPE_VIDEO_AV1, 40000000, 3840, 2160, 60},
{MediaFormat.MIMETYPE_VIDEO_AV1, 60000000, 3840, 2160, 120},
{MediaFormat.MIMETYPE_VIDEO_AV1, 60000000, 7680, 4320, 30},
{MediaFormat.MIMETYPE_VIDEO_AV1, 100000000, 7680, 4320, 60},
{MediaFormat.MIMETYPE_VIDEO_AV1, 160000000, 7680, 4320, 120},
{MediaFormat.MIMETYPE_VIDEO_VP8, 512000, 176, 144, 20},
{MediaFormat.MIMETYPE_VIDEO_VP8, 512000, 480, 360, 20},
};
final List<Object[]> argsList = new ArrayList<>();
final int[] maxBFrames = {0, 2};
for (Object[] arg : exhaustiveArgsList) {
final String mediaType = (String) arg[0];
boolean isVideo = mediaType.startsWith("video/");
final int br = (int) arg[1];
final int param1 = (int) arg[2];
final int param2 = (int) arg[3];
final int fps = (int) arg[4];
Object[] testArgs = new Object[3];
testArgs[0] = arg[0];
if (isVideo) {
for (int maxBframe : maxBFrames) {
if (maxBframe != 0) {
if (!mediaType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)
&& !mediaType.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
continue;
}
}
testArgs[1] = getVideoEncoderCfgParams(mediaType, br, param1, param2, fps,
COLOR_FormatYUV420Flexible, maxBframe,
Objects.requireNonNull(PROFILE_SDR_MAP.get(mediaType)));
testArgs[2] = String.format("%dkbps_%dx%d_%dfps_%s_%d-bframes", br / 1000,
param1, param2, fps, colorFormatToString(COLOR_FormatYUV420Flexible, 8),
maxBframe);
argsList.add(testArgs);
}
} else {
testArgs[1] = getAudioEncoderCfgParams(mediaType, br, param1, param2,
Objects.requireNonNull(PROFILE_SDR_MAP.get(mediaType)));
testArgs[2] = String.format("%dkbps_%dkHz_%dch", br / 1000, param1 / 1000, param2);
argsList.add(testArgs);
}
// P010 support was added in Android T, hence limit the following tests to Android
// T and above
if (IS_AT_LEAST_T && PROFILE_HLG_MAP.get(mediaType) != null) {
for (int maxBframe : maxBFrames) {
if (maxBframe != 0) {
if (!mediaType.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)
&& !mediaType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
continue;
}
}
testArgs = new Object[3];
testArgs[0] = arg[0];
testArgs[1] = getVideoEncoderCfgParams(mediaType, br, param1, param2, fps,
COLOR_FormatYUVP010, maxBframe,
Objects.requireNonNull(PROFILE_HLG_MAP.get(mediaType)));
testArgs[2] = String.format("%dkbps_%dx%d_%dfps_%s_%d-bframes", br / 1000,
param1, param2, fps, colorFormatToString(COLOR_FormatYUVP010, 10),
maxBframe);
argsList.add(testArgs);
}
}
}
return prepareParamList(argsList, isEncoder, needAudio, needVideo, false);
}
static {
PROFILE_LEVEL_CDD.put(MediaFormat.MIMETYPE_AUDIO_AAC,
new Pair<>(new int[]{AACObjectLC, AACObjectHE, AACObjectELD}, -1));
PROFILE_LEVEL_CDD.put(MediaFormat.MIMETYPE_VIDEO_H263,
new Pair<>(new int[]{H263ProfileBaseline}, H263Level45));
PROFILE_LEVEL_CDD.put(MediaFormat.MIMETYPE_VIDEO_AVC,
new Pair<>(new int[]{AVCProfileBaseline}, AVCLevel3));
PROFILE_LEVEL_CDD.put(MediaFormat.MIMETYPE_VIDEO_HEVC,
new Pair<>(new int[]{HEVCProfileMain}, HEVCMainTierLevel3));
PROFILE_LEVEL_CDD.put(MediaFormat.MIMETYPE_VIDEO_VP8,
new Pair<>(new int[]{VP8ProfileMain}, VP8Level_Version0));
PROFILE_LEVEL_CDD.put(MediaFormat.MIMETYPE_VIDEO_VP9,
new Pair<>(new int[]{VP9Profile0}, VP9Level3));
}
private static int getMinLevel(String mime, int width, int height, int frameRate, int bitrate,
int profile) {
switch (mime) {
case MediaFormat.MIMETYPE_VIDEO_AVC:
return getMinLevelAVC(width, height, frameRate, bitrate);
case MediaFormat.MIMETYPE_VIDEO_HEVC:
return getMinLevelHEVC(width, height, frameRate, bitrate);
case MediaFormat.MIMETYPE_VIDEO_H263:
return getMinLevelH263(width, height, frameRate, bitrate);
case MediaFormat.MIMETYPE_VIDEO_MPEG2:
return getMinLevelMPEG2(width, height, frameRate, bitrate);
case MediaFormat.MIMETYPE_VIDEO_MPEG4:
return getMinLevelMPEG4(width, height, frameRate, bitrate, profile);
// complex features disabled in VP8 Level/Version 0
case MediaFormat.MIMETYPE_VIDEO_VP8:
return VP8Level_Version0;
case MediaFormat.MIMETYPE_VIDEO_VP9:
return getMinLevelVP9(width, height, frameRate, bitrate);
case MediaFormat.MIMETYPE_VIDEO_AV1:
return getMinLevelAV1(width, height, frameRate, bitrate);
default:
return -1;
}
}
private static int getMinLevelAVC(int width, int height, int frameRate, int bitrate) {
class LevelLimitAVC {
private LevelLimitAVC(int level, int mbsPerSec, long mbs, int bitrate) {
this.level = level;
this.mbsPerSec = mbsPerSec;
this.mbs = mbs;
this.bitrate = bitrate;
}
private final int level;
private final int mbsPerSec;
private final long mbs;
private final int bitrate;
}
LevelLimitAVC[] limitsAVC = {
new LevelLimitAVC(AVCLevel1, 1485, 99, 64000),
new LevelLimitAVC(AVCLevel1b, 1485, 99, 128000),
new LevelLimitAVC(AVCLevel11, 3000, 396, 192000),
new LevelLimitAVC(AVCLevel12, 6000, 396, 384000),
new LevelLimitAVC(AVCLevel13, 11880, 396, 768000),
new LevelLimitAVC(AVCLevel2, 11880, 396, 2000000),
new LevelLimitAVC(AVCLevel21, 19800, 792, 4000000),
new LevelLimitAVC(AVCLevel22, 20250, 1620, 4000000),
new LevelLimitAVC(AVCLevel3, 40500, 1620, 10000000),
new LevelLimitAVC(AVCLevel31, 108000, 3600, 14000000),
new LevelLimitAVC(AVCLevel32, 216000, 5120, 20000000),
new LevelLimitAVC(AVCLevel4, 245760, 8192, 20000000),
new LevelLimitAVC(AVCLevel41, 245760, 8192, 50000000),
new LevelLimitAVC(AVCLevel42, 522240, 8704, 50000000),
new LevelLimitAVC(AVCLevel5, 589824, 22080, 135000000),
new LevelLimitAVC(AVCLevel51, 983040, 36864, 240000000),
new LevelLimitAVC(AVCLevel52, 2073600, 36864, 240000000),
new LevelLimitAVC(AVCLevel6, 4177920, 139264, 240000000),
new LevelLimitAVC(AVCLevel61, 8355840, 139264, 480000000),
new LevelLimitAVC(AVCLevel62, 16711680, 139264, 800000000),
};
int mbs = ((width + 15) / 16) * ((height + 15) / 16);
float mbsPerSec = mbs * frameRate;
for (LevelLimitAVC levelLimitsAVC : limitsAVC) {
if (mbs <= levelLimitsAVC.mbs && mbsPerSec <= levelLimitsAVC.mbsPerSec
&& bitrate <= levelLimitsAVC.bitrate) {
return levelLimitsAVC.level;
}
}
// if none of the levels suffice, select the highest level
return AVCLevel62;
}
private static int getMinLevelHEVC(int width, int height, int frameRate, int bitrate) {
class LevelLimitHEVC {
private LevelLimitHEVC(int level, int frameRate, long samples, int bitrate) {
this.level = level;
this.frameRate = frameRate;
this.samples = samples;
this.bitrate = bitrate;
}
private final int level;
private final int frameRate;
private final long samples;
private final int bitrate;
}
LevelLimitHEVC[] limitsHEVC = {
new LevelLimitHEVC(HEVCMainTierLevel1, 15, 36864, 128000),
new LevelLimitHEVC(HEVCMainTierLevel2, 30, 122880, 1500000),
new LevelLimitHEVC(HEVCMainTierLevel21, 30, 245760, 3000000),
new LevelLimitHEVC(HEVCMainTierLevel3, 30, 552960, 6000000),
new LevelLimitHEVC(HEVCMainTierLevel31, 30, 983040, 10000000),
new LevelLimitHEVC(HEVCMainTierLevel4, 30, 2228224, 12000000),
new LevelLimitHEVC(HEVCHighTierLevel4, 30, 2228224, 30000000),
new LevelLimitHEVC(HEVCMainTierLevel41, 60, 2228224, 20000000),
new LevelLimitHEVC(HEVCHighTierLevel41, 60, 2228224, 50000000),
new LevelLimitHEVC(HEVCMainTierLevel5, 30, 8912896, 25000000),
new LevelLimitHEVC(HEVCHighTierLevel5, 30, 8912896, 100000000),
new LevelLimitHEVC(HEVCMainTierLevel51, 60, 8912896, 40000000),
new LevelLimitHEVC(HEVCHighTierLevel51, 60, 8912896, 160000000),
new LevelLimitHEVC(HEVCMainTierLevel52, 120, 8912896, 60000000),
new LevelLimitHEVC(HEVCHighTierLevel52, 120, 8912896, 240000000),
new LevelLimitHEVC(HEVCMainTierLevel6, 30, 35651584, 60000000),
new LevelLimitHEVC(HEVCHighTierLevel6, 30, 35651584, 240000000),
new LevelLimitHEVC(HEVCMainTierLevel61, 60, 35651584, 120000000),
new LevelLimitHEVC(HEVCHighTierLevel61, 60, 35651584, 480000000),
new LevelLimitHEVC(HEVCMainTierLevel62, 120, 35651584, 240000000),
new LevelLimitHEVC(HEVCHighTierLevel62, 120, 35651584, 800000000),
};
int samples = width * height;
for (LevelLimitHEVC levelLimitsHEVC : limitsHEVC) {
if (samples <= levelLimitsHEVC.samples && frameRate <= levelLimitsHEVC.frameRate
&& bitrate <= levelLimitsHEVC.bitrate) {
return levelLimitsHEVC.level;
}
}
// if none of the levels suffice, select the highest level
return HEVCHighTierLevel62;
}
private static int getMinLevelH263(int width, int height, int frameRate, int bitrate) {
class LevelLimitH263 {
private LevelLimitH263(int level, int height, int width, int frameRate,
int bitrate) {
this.level = level;
this.height = height;
this.width = width;
this.frameRate = frameRate;
this.bitrate = bitrate;
}
private final int level;
private final int height;
private final int width;
private final int frameRate;
private final int bitrate;
}
LevelLimitH263[] limitsH263 = {
new LevelLimitH263(H263Level10, 176, 144, 15, 64000),
new LevelLimitH263(H263Level45, 176, 144, 15, 128000),
new LevelLimitH263(H263Level20, 176, 144, 30, 128000),
new LevelLimitH263(H263Level20, 352, 288, 15, 128000),
new LevelLimitH263(H263Level30, 352, 288, 30, 384000),
new LevelLimitH263(H263Level40, 352, 288, 30, 2048000),
new LevelLimitH263(H263Level50, 352, 240, 60, 4096000),
new LevelLimitH263(H263Level50, 352, 288, 50, 4096000),
new LevelLimitH263(H263Level60, 720, 240, 60, 8192000),
new LevelLimitH263(H263Level60, 720, 288, 50, 8192000),
new LevelLimitH263(H263Level70, 720, 480, 60, 16384000),
new LevelLimitH263(H263Level70, 720, 576, 50, 16384000),
};
for (LevelLimitH263 levelLimitsH263 : limitsH263) {
if (height <= levelLimitsH263.height && width <= levelLimitsH263.width &&
frameRate <= levelLimitsH263.frameRate && bitrate <= levelLimitsH263.bitrate) {
return levelLimitsH263.level;
}
}
// if none of the levels suffice, select the highest level
return H263Level70;
}
private static int getMinLevelVP9(int width, int height, int frameRate, int bitrate) {
class LevelLimitVP9 {
private LevelLimitVP9(int level, long sampleRate, int size, int breadth,
int bitrate) {
this.level = level;
this.sampleRate = sampleRate;
this.size = size;
this.breadth = breadth;
this.bitrate = bitrate;
}
private final int level;
private final long sampleRate;
private final int size;
private final int breadth;
private final int bitrate;
}
LevelLimitVP9[] limitsVP9 = {
new LevelLimitVP9(VP9Level1, 829440, 36864, 512, 200000),
new LevelLimitVP9(VP9Level11, 2764800, 73728, 768, 800000),
new LevelLimitVP9(VP9Level2, 4608000, 122880, 960, 1800000),
new LevelLimitVP9(VP9Level21, 9216000, 245760, 1344, 3600000),
new LevelLimitVP9(VP9Level3, 20736000, 552960, 2048, 7200000),
new LevelLimitVP9(VP9Level31, 36864000, 983040, 2752, 12000000),
new LevelLimitVP9(VP9Level4, 83558400, 2228224, 4160, 18000000),
new LevelLimitVP9(VP9Level41, 160432128, 2228224, 4160, 30000000),
new LevelLimitVP9(VP9Level5, 311951360, 8912896, 8384, 60000000),
new LevelLimitVP9(VP9Level51, 588251136, 8912896, 8384, 120000000),
new LevelLimitVP9(VP9Level52, 1176502272, 8912896, 8384, 180000000),
new LevelLimitVP9(VP9Level6, 1176502272, 35651584, 16832, 180000000),
new LevelLimitVP9(VP9Level61, 2353004544L, 35651584, 16832, 240000000),
new LevelLimitVP9(VP9Level62, 4706009088L, 35651584, 16832, 480000000),
};
int size = width * height;
int sampleRate = size * frameRate;
int breadth = Math.max(width, height);
for (LevelLimitVP9 levelLimitsVP9 : limitsVP9) {
if (sampleRate <= levelLimitsVP9.sampleRate && size <= levelLimitsVP9.size &&
breadth <= levelLimitsVP9.breadth && bitrate <= levelLimitsVP9.bitrate) {
return levelLimitsVP9.level;
}
}
// if none of the levels suffice, select the highest level
return VP9Level62;
}
private static int getMinLevelMPEG2(int width, int height, int frameRate, int bitrate) {
class LevelLimitMPEG2 {
private LevelLimitMPEG2(int level, long sampleRate, int width, int height,
int frameRate, int bitrate) {
this.level = level;
this.sampleRate = sampleRate;
this.width = width;
this.height = height;
this.frameRate = frameRate;
this.bitrate = bitrate;
}
private final int level;
private final long sampleRate;
private final int width;
private final int height;
private final int frameRate;
private final int bitrate;
}
// main profile limits, higher profiles will also support selected level
LevelLimitMPEG2[] limitsMPEG2 = {
new LevelLimitMPEG2(MPEG2LevelLL, 3041280, 352, 288, 30, 4000000),
new LevelLimitMPEG2(MPEG2LevelML, 10368000, 720, 576, 30, 15000000),
new LevelLimitMPEG2(MPEG2LevelH14, 47001600, 1440, 1088, 60, 60000000),
new LevelLimitMPEG2(MPEG2LevelHL, 62668800, 1920, 1088, 60, 80000000),
new LevelLimitMPEG2(MPEG2LevelHP, 125337600, 1920, 1088, 60, 80000000),
};
int size = width * height;
int sampleRate = size * frameRate;
for (LevelLimitMPEG2 levelLimitsMPEG2 : limitsMPEG2) {
if (sampleRate <= levelLimitsMPEG2.sampleRate && width <= levelLimitsMPEG2.width &&
height <= levelLimitsMPEG2.height && frameRate <= levelLimitsMPEG2.frameRate &&
bitrate <= levelLimitsMPEG2.bitrate) {
return levelLimitsMPEG2.level;
}
}
// if none of the levels suffice, select the highest level
return MPEG2LevelHP;
}
private static int getMinLevelMPEG4(int width, int height, int frameRate, int bitrate,
int profile) {
class LevelLimitMPEG4 {
private LevelLimitMPEG4(int profile, int level, long sampleRate, int width,
int height, int frameRate, int bitrate) {
this.profile = profile;
this.level = level;
this.sampleRate = sampleRate;
this.width = width;
this.height = height;
this.frameRate = frameRate;
this.bitrate = bitrate;
}
private final int profile;
private final int level;
private final long sampleRate;
private final int width;
private final int height;
private final int frameRate;
private final int bitrate;
}
// simple profile limits, higher profiles will also support selected level
LevelLimitMPEG4[] limitsMPEG4 = {
new LevelLimitMPEG4(MPEG4ProfileSimple, MPEG4Level0, 380160, 176, 144, 15, 64000),
new LevelLimitMPEG4(MPEG4ProfileSimple, MPEG4Level1, 380160, 176, 144, 30, 64000),
new LevelLimitMPEG4(MPEG4ProfileSimple, MPEG4Level0b, 380160, 176, 144, 15, 128000),
new LevelLimitMPEG4(MPEG4ProfileSimple, MPEG4Level2, 1520640, 352, 288, 30, 128000),
new LevelLimitMPEG4(MPEG4ProfileSimple, MPEG4Level3, 3041280, 352, 288, 30, 384000),
new LevelLimitMPEG4(
MPEG4ProfileSimple, MPEG4Level4a, 9216000, 640, 480, 30, 4000000),
new LevelLimitMPEG4(
MPEG4ProfileSimple, MPEG4Level5, 10368000, 720, 576, 30, 8000000),
new LevelLimitMPEG4(
MPEG4ProfileSimple, MPEG4Level6, 27648000, 1280, 720, 30, 12000000),
new LevelLimitMPEG4(
MPEG4ProfileAdvancedSimple, MPEG4Level1, 760320, 176, 144, 30, 128000),
new LevelLimitMPEG4(
MPEG4ProfileAdvancedSimple, MPEG4Level2, 1520640, 352, 288, 30, 384000),
new LevelLimitMPEG4(
MPEG4ProfileAdvancedSimple, MPEG4Level3, 3041280, 352, 288, 30, 768000),
new LevelLimitMPEG4(
MPEG4ProfileAdvancedSimple, MPEG4Level3b, 3041280, 352, 288, 30, 1500000),
new LevelLimitMPEG4(
MPEG4ProfileAdvancedSimple, MPEG4Level4, 3041280, 704, 576, 30, 3000000),
new LevelLimitMPEG4(
MPEG4ProfileAdvancedSimple, MPEG4Level5, 3041280, 720, 576, 30, 8000000),
};
int size = width * height;
int sampleRate = size * frameRate;
for (LevelLimitMPEG4 levelLimitsMPEG4 : limitsMPEG4) {
if (((profile & (MPEG4ProfileAdvancedSimple | MPEG4ProfileSimple)) != 0) &&
profile != levelLimitsMPEG4.profile) continue;
if (sampleRate <= levelLimitsMPEG4.sampleRate && width <= levelLimitsMPEG4.width &&
height <= levelLimitsMPEG4.height && frameRate <= levelLimitsMPEG4.frameRate &&
bitrate <= levelLimitsMPEG4.bitrate) {
return levelLimitsMPEG4.level;
}
}
// if none of the levels suffice, select the highest level
return MPEG4Level6;
}
private static int getMinLevelAV1(int width, int height, int frameRate, int bitrate) {
class LevelLimitAV1 {
private LevelLimitAV1(int level, int size, int width, int height, long sampleRate,
int bitrate) {
this.level = level;
this.size = size;
this.width = width;
this.height = height;
this.sampleRate = sampleRate;
this.bitrate = bitrate;
}
private final int level;
private final int size;
private final int width;
private final int height;
private final long sampleRate;
private final int bitrate;
}
// taking bitrate from main profile, will also be supported by high profile
LevelLimitAV1[] limitsAV1 = {
new LevelLimitAV1(AV1Level2, 147456, 2048, 1152, 4423680, 1500000),
new LevelLimitAV1(AV1Level21, 278784, 2816, 1584, 8363520, 3000000),
new LevelLimitAV1(AV1Level3, 665856, 4352, 2448, 19975680, 6000000),
new LevelLimitAV1(AV1Level31, 1065024, 5504, 3096, 31950720, 10000000),
new LevelLimitAV1(AV1Level4, 2359296, 6144, 3456, 70778880, 12000000),
new LevelLimitAV1(AV1Level41, 2359296, 6144, 3456, 141557760, 20000000),
new LevelLimitAV1(AV1Level5, 8912896, 8192, 4352, 267386880, 30000000),
new LevelLimitAV1(AV1Level51, 8912896, 8192, 4352, 534773760, 40000000),
new LevelLimitAV1(AV1Level52, 8912896, 8192, 4352, 1069547520, 60000000),
new LevelLimitAV1(AV1Level53, 8912896, 8192, 4352, 1069547520, 60000000),
new LevelLimitAV1(AV1Level6, 35651584, 16384, 8704, 1069547520, 60000000),
new LevelLimitAV1(AV1Level61, 35651584, 16384, 8704, 2139095040, 100000000),
new LevelLimitAV1(AV1Level62, 35651584, 16384, 8704, 4278190080L, 160000000),
new LevelLimitAV1(AV1Level63, 35651584, 16384, 8704, 4278190080L, 160000000),
};
int size = width * height;
int sampleRate = size * frameRate;
for (LevelLimitAV1 levelLimitsAV1 : limitsAV1) {
if (size <= levelLimitsAV1.size && width <= levelLimitsAV1.width &&
height <= levelLimitsAV1.height && sampleRate <= levelLimitsAV1.sampleRate &&
bitrate <= levelLimitsAV1.bitrate) {
return levelLimitsAV1.level;
}
}
// if none of the levels suffice or high profile, select the highest level
return AV1Level73;
}
private int getAacProfile(MediaFormat format) {
int aacProfile = format.getInteger(MediaFormat.KEY_AAC_PROFILE, -1);
int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1);
if (aacProfile != -1 && profile != -1) {
assertEquals(String.format("aac-profile :- %d and profile :- %d are different.",
aacProfile, profile), aacProfile, profile);
return aacProfile;
} else if (aacProfile != -1) {
return aacProfile;
} else if (profile != -1) {
return profile;
} else {
Log.e(LOG_TAG, "format doesn't contain either KEY_AAC_PROFILE or KEY_PROFILE");
return -1;
}
}
@Override
public boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) {
if (!super.isFormatSimilar(inpFormat, outFormat)) {
Log.e(LOG_TAG, "Basic channel-rate/resolution comparisons failed");
return false;
}
String inpMime = inpFormat.getString(MediaFormat.KEY_MIME);
String outMime = outFormat.getString(MediaFormat.KEY_MIME);
assertEquals(String.format("input mime :- %s and output mime :- %s are different.", inpMime,
outMime), inpMime, outMime);
if (outMime.startsWith("audio/")) {
if (outFormat.getString(MediaFormat.KEY_MIME).equals(MediaFormat.MIMETYPE_AUDIO_AAC)) {
int inputProfileKey, outputProfileKey;
outputProfileKey = getAacProfile(outFormat);
inputProfileKey = getAacProfile(inpFormat);
if (outputProfileKey != inputProfileKey) {
Log.e(LOG_TAG, "aac-profile in output " + outputProfileKey +
" doesn't match configured input " + inputProfileKey);
return false;
}
}
} else if (outMime.startsWith("video/")) {
if (!outFormat.containsKey(MediaFormat.KEY_PROFILE)) {
Log.e(LOG_TAG, "Output format doesn't contain profile key");
//TODO (b/151398466)
if (true) return true;
return false;
}
if (!outFormat.containsKey(MediaFormat.KEY_LEVEL)) {
Log.e(LOG_TAG, "Output format doesn't contain level key");
//TODO (b/151398466)
if (true) return true;
return false;
}
if (!inpFormat.containsKey(MediaFormat.KEY_PROFILE)) {
Log.e(LOG_TAG, "Input format doesn't contain profile key");
return false;
}
if (!inpFormat.containsKey(MediaFormat.KEY_LEVEL)) {
Log.e(LOG_TAG, "Input format doesn't contain level key");
return false;
}
if (outFormat.getInteger(MediaFormat.KEY_PROFILE)
!= inpFormat.getInteger(MediaFormat.KEY_PROFILE)) {
Log.e(LOG_TAG, "profile in output doesn't match configured input");
return false;
}
if (outFormat.getInteger(MediaFormat.KEY_LEVEL)
!= inpFormat.getInteger(MediaFormat.KEY_LEVEL)) {
Log.e(LOG_TAG, "level key in output doesn't match configured input");
return false;
}
} else {
Log.w(LOG_TAG, "non media mime:" + outMime);
}
return true;
}
/**
* @see EncoderProfileLevelTest
* Besides the above, the test muxes the encoder output in all supported container formats
* and checks if muxers and extractors on device are signalling profile/level information
* correctly
*/
@CddTest(requirements = {"2.2.2/5.1/H-0-3", "2.2.2/5.1/H-0-4", "2.2.2/5.1/H-0-5", "5/C-0-3",
"5.2.1/C-1-1", "5.2.2/C-2-1", "5.2.3/C-2-1", "5.2.4/C-1-2",
"5.2.5/C-1-1"})
@ApiTest(apis = {"android.media.MediaFormat#KEY_PROFILE",
"android.media.MediaFormat#KEY_AAC_PROFILE",
"android.media.MediaFormat#KEY_LEVEL"})
@Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
public void testValidateProfileLevel() throws IOException, InterruptedException {
if (mEncCfgParams[0].mInputBitDepth != 8) {
Assume.assumeTrue(mCodecName + " doesn't support " + colorFormatToString(
mEncCfgParams[0].mColorFormat, mEncCfgParams[0].mInputBitDepth),
hasSupportForColorFormat(mCodecName, mMime, mEncCfgParams[0].mColorFormat));
}
boolean cddSupportedMime = PROFILE_LEVEL_CDD.get(mMime) != null;
int[] profileCdd = new int[0];
int levelCdd = 0;
if (cddSupportedMime) {
Pair<int[], Integer> cddProfileLevel = PROFILE_LEVEL_CDD.get(mMime);
profileCdd = cddProfileLevel.first;
levelCdd = cddProfileLevel.second;
}
{
mActiveRawRes = EncoderInput.getRawResource(mEncCfgParams[0]);
assertNotNull("no raw resource found for testing config : "
+ mActiveEncCfg + mTestConfig + mTestEnv, mActiveRawRes);
setUpSource(mActiveRawRes.mFileName);
mSaveToMem = true;
mOutputBuff = new OutputManager();
mCodec = MediaCodec.createByCodecName(mCodecName);
MediaCodecInfo.CodecCapabilities codecCapabilities =
mCodec.getCodecInfo().getCapabilitiesForType(mMime);
for (EncoderConfigParams cfg : mEncCfgParams) {
mActiveEncCfg = cfg;
MediaFormat format = cfg.getFormat();
if (!codecCapabilities.isFormatSupported(format)) {
if (cddSupportedMime) {
boolean shallSupportProfileLevel = false;
if (mIsAudio) {
for (int cddProfile : profileCdd) {
if (cfg.mProfile == cddProfile) {
shallSupportProfileLevel = true;
break;
}
}
} else if (cfg.mProfile == profileCdd[0] && cfg.mLevel <= levelCdd) {
shallSupportProfileLevel = true;
}
if (shallSupportProfileLevel) {
ArrayList<MediaFormat> formats = new ArrayList<>();
formats.add(format);
assertFalse(String.format("No components present on the device supports"
+ " cdd required profile:- %d, level:- %d, encode format:- %s",
cfg.mProfile, cfg.mLevel, format),
selectCodecs(mMime, formats, null, false).isEmpty());
}
Log.d(LOG_TAG, mCodecName + " doesn't support format: " + format);
}
continue;
}
// Verify if device has an equivalent decoder for the current format
{
ArrayList<MediaFormat> formatList = new ArrayList<>();
formatList.add(format);
assertTrue("Device advertises support for encoding " + format
+ " but cannot decode it. \n" + mTestConfig + mTestEnv,
selectCodecs(mMime, formatList, null, false).size() > 0);
}
mOutputBuff.reset();
configureCodec(format, false, true, true);
mCodec.start();
doWork(5);
queueEOS();
waitForAllOutputs();
MediaFormat outFormat = mCodec.getOutputFormat();
/* TODO(b/147348711) */
if (false) mCodec.stop();
else mCodec.reset();
// TODO (b/151398466)
if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC)) {
Assume.assumeTrue("neither KEY_AAC_PROFILE nor KEY_PROFILE are present",
outFormat.containsKey(MediaFormat.KEY_AAC_PROFILE) ||
outFormat.containsKey(MediaFormat.KEY_PROFILE));
} else {
Assume.assumeTrue("KEY_PROFILE not present",
outFormat.containsKey(MediaFormat.KEY_PROFILE));
Assume.assumeTrue(outFormat.containsKey(MediaFormat.KEY_LEVEL));
}
// TODO (b/166300446) avc mime fails validation
if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
Log.w(LOG_TAG, "Skip validation for mime = " + mMime);
continue;
}
// TODO (b/166305723) hevc mime fails validation
if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
Log.w(LOG_TAG, "Skip validation for mime = " + mMime);
continue;
}
// TODO (b/166300448) h263 and mpeg4 mimes fails validation
if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_H263) ||
mMime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
Log.w(LOG_TAG, "Skip validation for mime = " + mMime);
continue;
}
// TODO (b/184889671) aac for profile AACObjectHE fails validation
// TODO (b/184890155) aac for profile AACObjectLD, AACObjectELD fails validation
if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC)) {
if (cfg.mProfile == AACObjectHE || cfg.mProfile == AACObjectELD
|| cfg.mProfile == AACObjectLD) {
Log.w(LOG_TAG, "Skip validation for mime = " + mMime + " profile "
+ cfg.mProfile);
continue;
}
}
String msg = String.format("Configured input format and received output format are "
+ "not similar. \nConfigured Input format is :- %s \nReceived Output "
+ "format is :- %s \n", format, outFormat);
assertTrue(msg + mTestConfig + mTestEnv, isFormatSimilar(format, outFormat));
for (int muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_FIRST;
muxerFormat <= MediaMuxer.OutputFormat.MUXER_OUTPUT_LAST; muxerFormat++) {
if (!isMediaTypeContainerPairValid(mMime, muxerFormat)) continue;
ByteBuffer mBuff = mOutputBuff.getBuffer();
String tmpPath = getTempFilePath((cfg.mInputBitDepth == 10) ? "10bit" : "");
muxOutput(tmpPath, muxerFormat, outFormat, mBuff, mInfoList);
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(tmpPath);
assertEquals("Should be only 1 track \n" + mTestConfig + mTestEnv, 1,
extractor.getTrackCount());
MediaFormat extractedFormat = extractor.getTrackFormat(0);
if (!isFormatSimilar(outFormat, extractedFormat)) {
msg = " Input format and extracted format are not similar. "
+ "\n Muxer input format :- " + outFormat
+ "\n Extracted format :- " + extractedFormat
+ "\n Muxer writer :- " + muxerFormat + "\n" + mTestConfig
+ mTestEnv;
fail(msg);
}
extractor.release();
new File(tmpPath).delete();
}
}
mCodec.release();
}
}
}