1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tests.transcoding; 18 19 import android.app.Activity; 20 import android.media.MediaCodec; 21 import android.media.MediaCodecInfo; 22 import android.media.MediaCodecInfo.CodecCapabilities; 23 import android.media.MediaCodecInfo.VideoCapabilities; 24 import android.media.MediaCodecList; 25 import android.media.MediaFormat; 26 import android.os.Bundle; 27 import android.util.Log; 28 import java.io.IOException; 29 import java.util.Vector; 30 31 public class ResourcePolicyTestActivity extends Activity { 32 public static final int TYPE_NONSECURE = 0; 33 public static final int TYPE_SECURE = 1; 34 public static final int TYPE_MIX = 2; 35 36 protected String TAG; 37 private static final int FRAME_RATE = 10; 38 private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames 39 private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC; 40 private static final int TIMEOUT_MS = 5000; 41 42 private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>(); 43 44 private class TestCodecCallback extends MediaCodec.Callback { 45 @Override onInputBufferAvailable(MediaCodec codec, int index)46 public void onInputBufferAvailable(MediaCodec codec, int index) { 47 Log.d(TAG, "onInputBufferAvailable " + codec.toString()); 48 } 49 50 @Override onOutputBufferAvailable( MediaCodec codec, int index, MediaCodec.BufferInfo info)51 public void onOutputBufferAvailable( 52 MediaCodec codec, int index, MediaCodec.BufferInfo info) { 53 Log.d(TAG, "onOutputBufferAvailable " + codec.toString()); 54 } 55 56 @Override onError(MediaCodec codec, MediaCodec.CodecException e)57 public void onError(MediaCodec codec, MediaCodec.CodecException e) { 58 Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode()); 59 } 60 61 @Override onOutputFormatChanged(MediaCodec codec, MediaFormat format)62 public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { 63 Log.d(TAG, "onOutputFormatChanged " + codec.toString()); 64 } 65 } 66 67 private MediaCodec.Callback mCallback = new TestCodecCallback(); 68 getTestFormat(CodecCapabilities caps, boolean securePlayback)69 private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) { 70 VideoCapabilities vcaps = caps.getVideoCapabilities(); 71 int width = vcaps.getSupportedWidths().getLower(); 72 int height = vcaps.getSupportedHeightsFor(width).getLower(); 73 int bitrate = vcaps.getBitrateRange().getLower(); 74 75 MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height); 76 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]); 77 format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); 78 format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 79 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 80 format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback); 81 return format; 82 } 83 getTestCodecInfo(boolean securePlayback)84 private MediaCodecInfo getTestCodecInfo(boolean securePlayback) { 85 // Use avc decoder for testing. 86 boolean isEncoder = false; 87 88 MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS); 89 for (MediaCodecInfo info : mcl.getCodecInfos()) { 90 if (info.isEncoder() != isEncoder) { 91 continue; 92 } 93 CodecCapabilities caps; 94 try { 95 caps = info.getCapabilitiesForType(MIME); 96 boolean securePlaybackSupported = 97 caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); 98 boolean securePlaybackRequired = 99 caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback); 100 if ((securePlayback && securePlaybackSupported) 101 || (!securePlayback && !securePlaybackRequired)) { 102 Log.d(TAG, "securePlayback " + securePlayback + " will use " + info.getName()); 103 } else { 104 Log.d(TAG, "securePlayback " + securePlayback + " skip " + info.getName()); 105 continue; 106 } 107 } catch (IllegalArgumentException e) { 108 // mime is not supported 109 continue; 110 } 111 return info; 112 } 113 114 return null; 115 } 116 allocateCodecs(int max)117 protected int allocateCodecs(int max) { 118 Bundle extras = getIntent().getExtras(); 119 int type = TYPE_NONSECURE; 120 if (extras != null) { 121 type = extras.getInt("test-type", type); 122 Log.d(TAG, "type is: " + type); 123 } 124 125 boolean shouldSkip = false; 126 boolean securePlayback; 127 if (type == TYPE_NONSECURE || type == TYPE_MIX) { 128 securePlayback = false; 129 MediaCodecInfo info = getTestCodecInfo(securePlayback); 130 if (info != null) { 131 allocateCodecs(max, info, securePlayback); 132 } else { 133 shouldSkip = true; 134 } 135 } 136 137 if (!shouldSkip) { 138 if (type == TYPE_SECURE || type == TYPE_MIX) { 139 securePlayback = true; 140 MediaCodecInfo info = getTestCodecInfo(securePlayback); 141 if (info != null) { 142 allocateCodecs(max, info, securePlayback); 143 } else { 144 shouldSkip = true; 145 } 146 } 147 } 148 149 if (shouldSkip) { 150 Log.d(TAG, "test skipped as there's no supported codec."); 151 finishWithResult(RESULT_OK); 152 } 153 154 Log.d(TAG, "allocateCodecs returned " + mCodecs.size()); 155 return mCodecs.size(); 156 } 157 allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback)158 protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) { 159 String name = info.getName(); 160 CodecCapabilities caps = info.getCapabilitiesForType(MIME); 161 MediaFormat format = getTestFormat(caps, securePlayback); 162 MediaCodec codec = null; 163 for (int i = mCodecs.size(); i < max; ++i) { 164 try { 165 Log.d(TAG, "Create codec " + name + " #" + i); 166 codec = MediaCodec.createByCodecName(name); 167 codec.setCallback(mCallback); 168 Log.d(TAG, "Configure codec " + format); 169 codec.configure(format, null, null, 0); 170 Log.d(TAG, "Start codec " + format); 171 codec.start(); 172 mCodecs.add(codec); 173 codec = null; 174 } catch (IllegalArgumentException e) { 175 Log.d(TAG, "IllegalArgumentException " + e.getMessage()); 176 break; 177 } catch (IOException e) { 178 Log.d(TAG, "IOException " + e.getMessage()); 179 break; 180 } catch (MediaCodec.CodecException e) { 181 Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode())); 182 break; 183 } finally { 184 if (codec != null) { 185 Log.d(TAG, "release codec"); 186 codec.release(); 187 codec = null; 188 } 189 } 190 } 191 } 192 finishWithResult(int result)193 protected void finishWithResult(int result) { 194 for (int i = 0; i < mCodecs.size(); ++i) { 195 Log.d(TAG, "release codec #" + i); 196 mCodecs.get(i).release(); 197 } 198 mCodecs.clear(); 199 setResult(result); 200 finish(); 201 Log.d(TAG, "activity finished"); 202 } 203 doUseCodecs()204 private void doUseCodecs() { 205 int current = 0; 206 try { 207 for (current = 0; current < mCodecs.size(); ++current) { 208 mCodecs.get(current).getName(); 209 } 210 } catch (MediaCodec.CodecException e) { 211 Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode())); 212 if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) { 213 Log.d(TAG, "Remove codec " + current + " from the list"); 214 mCodecs.get(current).release(); 215 mCodecs.remove(current); 216 mGotReclaimedException = true; 217 mUseCodecs = false; 218 } 219 return; 220 } 221 } 222 223 private Thread mWorkerThread; 224 private volatile boolean mUseCodecs = true; 225 private volatile boolean mGotReclaimedException = false; useCodecs()226 protected void useCodecs() { 227 mWorkerThread = new Thread(new Runnable() { 228 @Override 229 public void run() { 230 long start = System.currentTimeMillis(); 231 long timeSinceStartedMs = 0; 232 while (mUseCodecs && (timeSinceStartedMs < TIMEOUT_MS)) { 233 doUseCodecs(); 234 try { 235 Thread.sleep(50 /* millis */); 236 } catch (InterruptedException e) { 237 } 238 timeSinceStartedMs = System.currentTimeMillis() - start; 239 } 240 if (mGotReclaimedException) { 241 Log.d(TAG, "Got expected reclaim exception."); 242 } 243 finishWithResult(RESULT_OK); 244 } 245 }); 246 mWorkerThread.start(); 247 } 248 249 private static final int MAX_INSTANCES = 32; 250 251 @Override onCreate(Bundle savedInstanceState)252 protected void onCreate(Bundle savedInstanceState) { 253 TAG = "ResourcePolicyTestActivity"; 254 255 Log.d(TAG, "onCreate called."); 256 super.onCreate(savedInstanceState); 257 258 if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) { 259 // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception. 260 //mWaitForReclaim = false; 261 Log.d(TAG, "Didn't hit resource limitation"); 262 } 263 264 useCodecs(); 265 } 266 267 @Override onDestroy()268 protected void onDestroy() { 269 Log.d(TAG, "onDestroy called."); 270 super.onDestroy(); 271 } 272 } 273