blob: 1cb3584ed50a5f02340e59998969d8c60fd0d359 [file] [log] [blame]
/*
* Copyright 2023 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.misc.cts;
import android.app.Activity;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.VideoCapabilities;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.os.Bundle;
import android.util.Log;
import java.io.IOException;
import java.util.Vector;
public class ResourceManagerCodecActivity extends Activity {
private static final String TAG = "ResourceManagerCodecActivity";
private static final int MAX_INSTANCES = 32;
private static final int FRAME_RATE = 30;
private static final int IFRAME_INTERVAL = 10;
private boolean mHighResolution = false;
private volatile boolean mGotReclaimedException = false;
private int mWidth = 0;
private int mHeight = 0;
private int mBitrate = 0;
private String mMime = MediaFormat.MIMETYPE_VIDEO_AVC;
private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>();
private Thread mWorkerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate called.");
super.onCreate(savedInstanceState);
// Making this as a background Activity
// so that high priority Activities can reclaim codec from this.
moveTaskToBack(true);
Bundle extras = getIntent().getExtras();
if (extras != null) {
mHighResolution = extras.getBoolean("high-resolution", mHighResolution);
mMime = extras.getString("mime", mMime);
}
if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
// As we haven't reached the limit with MAX_INSTANCES,
// no need to wait for reclaim exception.
Log.d(TAG, "We may not get reclaim event");
}
useCodecs();
}
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy called.");
super.onDestroy();
}
// MediaCodec callback
private class TestCodecCallback extends MediaCodec.Callback {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec, int index, MediaCodec.BufferInfo info) {
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode());
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
Log.d(TAG, "onOutputFormatChanged " + codec.toString());
}
}
private MediaCodec.Callback mCallback = new TestCodecCallback();
// Get a HW Codec info for a given mime (mMime, which is either AVC or HEVC)
private MediaCodecInfo getCodecInfo(boolean lookForDecoder) {
MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo info : mcl.getCodecInfos()) {
if (info.isSoftwareOnly()) {
// not testing the sw codecs for now as currently there are't
// any limit on how many concurrent sw codecs can be created.
// Allowing too many codecs may lead system into low memory
// situation and lmkd will kill the test activity and eventually
// failing the test case.
continue;
}
boolean isEncoder = info.isEncoder();
if (lookForDecoder && isEncoder) {
// Looking for a decoder, but found an encoder.
// Skip it
continue;
}
if (!lookForDecoder && !isEncoder) {
// Looking for an encoder, but found a decoder.
// Skip it
continue;
}
CodecCapabilities caps;
try {
caps = info.getCapabilitiesForType(mMime);
} catch (IllegalArgumentException e) {
// mime is not supported
continue;
}
return info;
}
return null;
}
private MediaFormat createVideoFormat(MediaCodecInfo info) {
CodecCapabilities caps = info.getCapabilitiesForType(mMime);
VideoCapabilities vcaps = caps.getVideoCapabilities();
if (mHighResolution) {
mWidth = vcaps.getSupportedWidths().getUpper();
mHeight = vcaps.getSupportedHeightsFor(mWidth).getUpper();
mBitrate = vcaps.getBitrateRange().getUpper();
} else {
mWidth = vcaps.getSupportedWidths().getLower();
mHeight = vcaps.getSupportedHeightsFor(mWidth).getLower();
mBitrate = vcaps.getBitrateRange().getLower();
}
Log.d(TAG, "Mime: " + mMime + " Resolution: " + mWidth + "x" + mHeight
+ " Bitrate: " + mBitrate);
MediaFormat format = MediaFormat.createVideoFormat(mMime, mWidth, mHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
return format;
}
private MediaFormat createVideoEncoderFormat(MediaCodecInfo info) {
MediaFormat format = createVideoFormat(info);
format.setInteger("profile", 1);
format.setInteger("level", 1);
format.setInteger("priority", 1);
format.setInteger("color-format", CodecCapabilities.COLOR_FormatYUV420Flexible);
format.setInteger("bitrate-mode", 0);
format.setInteger("quality", 1);
return format;
}
// Allocates at most max number of codecs
protected int allocateCodecs(int max) {
boolean shouldSkip = false;
MediaCodecInfo info = getCodecInfo(true);
if (info != null) {
// Try allocating max number of decoders first.
String name = info.getName();
MediaFormat decoderFormat = createVideoFormat(info);
allocateCodecs(max, name, decoderFormat, true);
// Try allocating max number of encoder next.
info = getCodecInfo(false);
if (info != null) {
name = info.getName();
MediaFormat encoderFormat = createVideoEncoderFormat(info);
allocateCodecs(max, name, encoderFormat, false);
}
} else {
shouldSkip = true;
}
if (shouldSkip) {
Log.d(TAG, "test skipped as there's no supported codec.");
finishWithResult(ResourceManagerStubActivity.RESULT_CODE_NO_DECODER);
}
Log.d(TAG, "allocateCodecs returned " + mCodecs.size());
return mCodecs.size();
}
protected void allocateCodecs(int max, String name, MediaFormat format, boolean decoder) {
MediaCodec codec = null;
max += mCodecs.size();
int flag = decoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE;
for (int i = mCodecs.size(); i < max; ++i) {
try {
Log.d(TAG, "Create codec " + name + " #" + i);
codec = MediaCodec.createByCodecName(name);
codec.setCallback(mCallback);
Log.d(TAG, "Configure Codec: " + format);
codec.configure(format, null, null, flag);
Log.d(TAG, "Start codec ");
codec.start();
mCodecs.add(codec);
codec = null;
} catch (IllegalArgumentException e) {
Log.d(TAG, "IllegalArgumentException " + e.getMessage());
break;
} catch (IOException e) {
Log.d(TAG, "IOException " + e.getMessage());
break;
} catch (MediaCodec.CodecException e) {
Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
break;
} finally {
if (codec != null) {
Log.d(TAG, "release codec");
codec.release();
codec = null;
}
}
}
}
protected void finishWithResult(int result) {
for (int i = 0; i < mCodecs.size(); ++i) {
Log.d(TAG, "release codec #" + i);
mCodecs.get(i).release();
}
mCodecs.clear();
setResult(result);
finish();
Log.d(TAG, "Activity finished with: " + result);
}
private boolean doUseCodecs() {
int current = 0;
try {
for (current = 0; current < mCodecs.size(); ++current) {
mCodecs.get(current).getName();
}
} catch (MediaCodec.CodecException e) {
Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode()));
if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) {
Log.d(TAG, "Remove codec " + current + " from the list");
mCodecs.get(current).release();
mCodecs.remove(current);
mGotReclaimedException = true;
}
return false;
}
return true;
}
protected void useCodecs() {
mWorkerThread = new Thread(new Runnable() {
@Override
public void run() {
Log.i(TAG, "Started the thread");
long start = System.currentTimeMillis();
long timeSinceStartedMs = 0;
boolean success = true;
while (success && (timeSinceStartedMs < 15000)) { // timeout in 15s
success = doUseCodecs();
try {
// wait for 50ms before calling doUseCodecs again.
Thread.sleep(50 /* millis */);
} catch (InterruptedException e) { }
timeSinceStartedMs = System.currentTimeMillis() - start;
}
if (mGotReclaimedException) {
Log.d(TAG, "Got expected reclaim exception.");
// As expected a Codec was reclaimed from this (background) Activity.
// So, finish with success.
finishWithResult(RESULT_OK);
} else if (success) {
Log.d(TAG, "No codec reclaim exception, but codec operations successful.");
// Though we were expecting reclaim event, it could be possible that
// oem was able to allocate another codec (had enough resources) for the
// foreground app. In those case, we need to pass this.
finishWithResult(RESULT_OK);
} else {
Log.d(TAG, "Stopped with an unexpected codec exception.");
// We were expecting reclaim event OR codec operations to be successful.
// In neither of the case, some unexpected error happened.
// So, fail the case.
finishWithResult(RESULT_CANCELED);
}
}
});
mWorkerThread.start();
}
}