1 /* 2 * Copyright 2015 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 android.media.misc.cts; 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 29 import java.io.IOException; 30 import java.util.ArrayList; 31 32 public class ResourceManagerTestActivityBase extends Activity { 33 public static final int TYPE_NONSECURE = 0; 34 public static final int TYPE_SECURE = 1; 35 public static final int TYPE_MIX = 2; 36 private static final int FRAME_RATE = 10; 37 // 10 seconds between I-frames 38 private static final int IFRAME_INTERVAL = 10; 39 protected static final int MAX_INSTANCES = 32; 40 // Less important codec of value 100. 41 private static final int CODEC_IMPORTANCE_100 = 100; 42 private static final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 43 44 private boolean mIsEncoder = false; 45 protected boolean mChangingCodecImportance = false; 46 private boolean mUseCodecImportanceAtConfig = false; 47 private boolean mUseCodecImportanceLater = false; 48 private int mWidth = 0; 49 private int mHeight = 0; 50 protected String TAG; 51 private String mMime = MediaFormat.MIMETYPE_VIDEO_AVC; 52 private String mCodecName = "none"; 53 54 private ArrayList<MediaCodec> mCodecs = new ArrayList<MediaCodec>(); 55 private MediaCodec mFirstMediaCodec; 56 57 private class TestCodecCallback extends MediaCodec.Callback { 58 @Override onInputBufferAvailable(MediaCodec codec, int index)59 public void onInputBufferAvailable(MediaCodec codec, int index) { 60 Log.v(TAG, "onInputBufferAvailable " + codec.toString()); 61 } 62 63 @Override onOutputBufferAvailable( MediaCodec codec, int index, MediaCodec.BufferInfo info)64 public void onOutputBufferAvailable( 65 MediaCodec codec, int index, MediaCodec.BufferInfo info) { 66 Log.v(TAG, "onOutputBufferAvailable " + codec.toString()); 67 } 68 69 @Override onError(MediaCodec codec, MediaCodec.CodecException e)70 public void onError(MediaCodec codec, MediaCodec.CodecException e) { 71 int error = e.getErrorCode(); 72 Log.e(TAG, "onError " + codec.toString() + " errorCode " + error); 73 if (mChangingCodecImportance && error == MediaCodec.CodecException.ERROR_RECLAIMED) { 74 if (mFirstMediaCodec == codec) { 75 mGotReclaimedException = true; 76 Log.d(TAG, "Codec " + codec + " Was expected to be Reclaimed"); 77 } 78 codec.release(); 79 } 80 } 81 82 @Override onOutputFormatChanged(MediaCodec codec, MediaFormat format)83 public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { 84 Log.v(TAG, "onOutputFormatChanged " + codec.toString()); 85 } 86 } 87 88 private MediaCodec.Callback mCallback = new TestCodecCallback(); 89 getTestFormat(CodecCapabilities caps, boolean securePlayback, boolean highResolution)90 private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback, 91 boolean highResolution) { 92 VideoCapabilities vcaps = caps.getVideoCapabilities(); 93 int bitrate = 0; 94 95 if (highResolution) { 96 if (mWidth == 0 || mHeight == 0) { 97 mWidth = vcaps.getSupportedWidths().getUpper(); 98 mHeight = vcaps.getSupportedHeightsFor(mWidth).getUpper(); 99 } 100 bitrate = vcaps.getBitrateRange().getUpper(); 101 } else { 102 if (mWidth == 0 || mHeight == 0) { 103 mWidth = vcaps.getSupportedWidths().getLower(); 104 mHeight = vcaps.getSupportedHeightsFor(mWidth).getLower(); 105 } 106 bitrate = vcaps.getBitrateRange().getLower(); 107 } 108 109 MediaFormat format = MediaFormat.createVideoFormat(mMime, mWidth, mHeight); 110 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]); 111 format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); 112 format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 113 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 114 format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback); 115 116 if (mIsEncoder) { 117 // TODO: Facilitate the verification of reclaim when the codec is configured 118 // in realtime and non-realtime priorities. 119 // format.setInteger(MediaFormat.KEY_PRIORITY, 1); 120 // format.setInteger(MediaFormat.KEY_PRIORITY, 0); 121 // TODO: Make sure this color format is supported by the encoder 122 // If not, pick one that is supported. 123 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 124 CodecCapabilities.COLOR_FormatYUV420Flexible); 125 } 126 return format; 127 } 128 getCodecInfo(boolean securePlayback)129 private MediaCodecInfo getCodecInfo(boolean securePlayback) { 130 if (mCodecName.equals("none")) { 131 // We don't know the codec name yet, so look for a decoder 132 // that supports the mime type. 133 return getDecoderInfo(securePlayback); 134 } 135 136 // We already know the codec name, so return the info directly. 137 for (MediaCodecInfo info : sMCL.getCodecInfos()) { 138 if (info.getName().equals(mCodecName)) { 139 mIsEncoder = info.isEncoder(); 140 return info; 141 } 142 } 143 144 return null; 145 } 146 getDecoderInfo(boolean securePlayback)147 private MediaCodecInfo getDecoderInfo(boolean securePlayback) { 148 MediaCodecInfo fallbackInfo = null; 149 150 for (MediaCodecInfo info : sMCL.getCodecInfos()) { 151 if (info.isEncoder()) { 152 // Skip through encoders. 153 continue; 154 } 155 CodecCapabilities caps; 156 try { 157 caps = info.getCapabilitiesForType(mMime); 158 boolean securePlaybackSupported = 159 caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); 160 boolean securePlaybackRequired = 161 caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback); 162 if ((securePlayback && securePlaybackSupported) || 163 (!securePlayback && !securePlaybackRequired) ) { 164 Log.d(TAG, "securePlayback " + securePlayback + " will use " + info.getName()); 165 } else { 166 Log.d(TAG, "securePlayback " + securePlayback + " skip " + info.getName()); 167 // If in case there is no secure decoder, use the first 168 // one as fallback. 169 if (fallbackInfo == null) { 170 fallbackInfo = info; 171 } 172 continue; 173 } 174 } catch (IllegalArgumentException e) { 175 // mime is not supported 176 continue; 177 } 178 return info; 179 } 180 181 return fallbackInfo; 182 } 183 allocateCodecs(int max)184 protected int allocateCodecs(int max) { 185 Bundle extras = getIntent().getExtras(); 186 int type = TYPE_NONSECURE; 187 boolean highResolution = false; 188 boolean isResolutionSet = true; 189 if (extras != null) { 190 type = extras.getInt("test-type", type); 191 // Check if codec name has been passed. 192 mCodecName = extras.getString("name", mCodecName); 193 // Check if mime has been passed. 194 mMime = extras.getString("mime", mMime); 195 // Check if resolution has been passed. 196 mWidth = extras.getInt("width"); 197 mHeight = extras.getInt("height"); 198 if (mWidth == 0 || mHeight == 0) { 199 // Either no resolution has been passed or its invalid. 200 isResolutionSet = false; 201 // See if the high-resolution flag has been set. 202 highResolution = extras.getBoolean("high-resolution", highResolution); 203 } else if (mHeight >= 1080) { 204 highResolution = true; 205 } 206 207 // See if we need to set codec-importance during config. 208 mUseCodecImportanceAtConfig = extras.getBoolean("codec-importance-at-config", false); 209 if (!mUseCodecImportanceAtConfig) { 210 // See if we need to set codec-importance later (using setParameters) 211 mUseCodecImportanceLater = extras.getBoolean("codec-importance-later", false); 212 } 213 // Setting this flag to track that we get an expected reclaim on expected codec. 214 mChangingCodecImportance = mUseCodecImportanceAtConfig || mUseCodecImportanceLater; 215 } 216 217 boolean shouldSkip = false; 218 boolean securePlayback; 219 if (type == TYPE_NONSECURE || type == TYPE_MIX) { 220 securePlayback = false; 221 MediaCodecInfo info = getCodecInfo(securePlayback); 222 if (info != null) { 223 allocateCodecs(max, info, securePlayback, highResolution); 224 } else { 225 shouldSkip = true; 226 } 227 } 228 229 if (!shouldSkip) { 230 if (type == TYPE_SECURE || type == TYPE_MIX) { 231 if (!isResolutionSet && type == TYPE_MIX) { 232 // clear previously set resolutions by the unsecure codecs, 233 // so that we read the secure codec resolutions again. 234 mWidth = 0; 235 mHeight = 0; 236 } 237 securePlayback = true; 238 MediaCodecInfo info = getCodecInfo(securePlayback); 239 if (info != null) { 240 allocateCodecs(max, info, securePlayback, highResolution); 241 } else { 242 shouldSkip = true; 243 } 244 } 245 } 246 247 if (shouldSkip) { 248 Log.d(TAG, "test skipped as there's no supported codec."); 249 finishWithResult(ResourceManagerStubActivity.RESULT_CODE_NO_DECODER); 250 } 251 252 Log.d(TAG, "allocateCodecs(" + mCodecName + ":" + mMime + ":" + mWidth 253 + "x" + mHeight + ") returned " + mCodecs.size()); 254 return mCodecs.size(); 255 } 256 changeCodecImportance(MediaCodec codec, int importance)257 private void changeCodecImportance(MediaCodec codec, int importance) { 258 final Bundle params = new Bundle(); 259 params.putInt(MediaFormat.KEY_IMPORTANCE, importance); 260 codec.setParameters(params); 261 } 262 allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback, boolean highResolution)263 protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback, 264 boolean highResolution) { 265 mCodecName = info.getName(); 266 int flag = mIsEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0; 267 CodecCapabilities caps = info.getCapabilitiesForType(mMime); 268 MediaFormat format = getTestFormat(caps, securePlayback, highResolution); 269 MediaCodec codec = null; 270 boolean firstCodec = true; 271 boolean loweredFirstCodecImportance = false; 272 boolean lastCodecAttemptWithImportance = false; 273 boolean loweredLastCodecImportance = false; 274 for (int i = mCodecs.size(); i < max; ++i) { 275 try { 276 Log.d(TAG, "Create codec " + mCodecName + " #" + i); 277 codec = MediaCodec.createByCodecName(mCodecName); 278 codec.setCallback(mCallback); 279 Log.d(TAG, "Configure codec " + format); 280 281 // if it's the first codec and if we are to set the importance while configuring, 282 // then set the same through MediaFormat with codec-importance as a lesser value. 283 if (firstCodec && mUseCodecImportanceAtConfig) { 284 format.setInteger(MediaFormat.KEY_IMPORTANCE, CODEC_IMPORTANCE_100); 285 mFirstMediaCodec = codec; 286 } 287 // The last codec creation failed because of insufficient resources. 288 // So, lets attempt to create one last codec with lesser importance (100) 289 // and expect it to fail as well. 290 if (lastCodecAttemptWithImportance) { 291 format.setInteger(MediaFormat.KEY_IMPORTANCE, CODEC_IMPORTANCE_100); 292 loweredLastCodecImportance = true; 293 } 294 codec.configure(format, null, null, flag); 295 296 // We don't want to lower the importance for other codecs. 297 // So, remove it from the format, if it were set above. 298 // The remaining codecs will have the default codec-importance as highest (0). 299 if (firstCodec && mUseCodecImportanceAtConfig) { 300 format.removeKey(MediaFormat.KEY_IMPORTANCE); 301 firstCodec = false; 302 } 303 Log.d(TAG, "Start codec " + format); 304 codec.start(); 305 mCodecs.add(codec); 306 codec = null; 307 } catch (IllegalArgumentException e) { 308 Log.d(TAG, "IllegalArgumentException " + e.getMessage()); 309 break; 310 } catch (IOException e) { 311 Log.d(TAG, "IOException " + e.getMessage()); 312 break; 313 } catch (MediaCodec.CodecException e) { 314 int error = e.getErrorCode(); 315 Log.d(TAG, "CodecException 0x" + Integer.toHexString(error)); 316 if (mUseCodecImportanceLater && !loweredFirstCodecImportance 317 && error == MediaCodec.CodecException.ERROR_INSUFFICIENT_RESOURCE) { 318 // Making sure we have at least one codec started. 319 Log.d(TAG, "Make Codec 0 less important so that it will be reclaimed"); 320 if (i > 0) { 321 mFirstMediaCodec = mCodecs.get(0); 322 changeCodecImportance(mFirstMediaCodec, CODEC_IMPORTANCE_100); 323 // We are doing it only once. 324 loweredFirstCodecImportance = true; 325 continue; 326 } else { 327 // We don't have any codecs to lower the importance. 328 break; 329 } 330 } else if (!loweredLastCodecImportance && mChangingCodecImportance) { 331 // The last codec start failed because of insufficient resources. 332 // So, lets attempt to create one last codec with lesser importance (100) 333 // and expect it to fail as well. 334 Log.d(TAG, "Attempt creating less important codec and expect it to fail"); 335 lastCodecAttemptWithImportance = true; 336 continue; 337 } else { 338 break; 339 } 340 } finally { 341 if (codec != null) { 342 Log.d(TAG, "release codec"); 343 codec.release(); 344 codec = null; 345 } 346 } 347 } 348 } 349 finishWithResult(int result)350 protected void finishWithResult(int result) { 351 for (int i = 0; i < mCodecs.size(); ++i) { 352 Log.d(TAG, "release codec #" + i + " : " + mCodecs.get(i).toString()); 353 mCodecs.get(i).release(); 354 } 355 mCodecs.clear(); 356 setResult(result); 357 finish(); 358 Log.d(TAG, "activity finished with: " + result); 359 } 360 doUseCodecs()361 private void doUseCodecs() { 362 int current = 0; 363 try { 364 for (current = 0; current < mCodecs.size(); ++current) { 365 mCodecs.get(current).getName(); 366 } 367 } catch (MediaCodec.CodecException e) { 368 Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode())); 369 if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) { 370 Log.d(TAG, "Remove codec " + current + " from the list"); 371 mCodecs.get(current).release(); 372 mCodecs.remove(current); 373 mGotReclaimedException = true; 374 mUseCodecs = false; 375 } 376 return; 377 } 378 } 379 380 protected boolean mWaitForReclaim = true; 381 private Thread mWorkerThread; 382 private volatile boolean mUseCodecs = true; 383 private volatile boolean mGotReclaimedException = false; useCodecs()384 protected void useCodecs() { 385 mWorkerThread = new Thread(new Runnable() { 386 @Override 387 public void run() { 388 long start = System.currentTimeMillis(); 389 long timeSinceStartedMs = 0; 390 while (mUseCodecs && (timeSinceStartedMs < 15000)) { // timeout in 15s 391 doUseCodecs(); 392 try { 393 Thread.sleep(50 /* millis */); 394 } catch (InterruptedException e) {} 395 timeSinceStartedMs = System.currentTimeMillis() - start; 396 } 397 if (mGotReclaimedException) { 398 Log.d(TAG, "Got expected reclaim exception."); 399 finishWithResult(RESULT_OK); 400 } else { 401 Log.d(TAG, "Stopped without getting reclaim exception."); 402 // if the test is supposed to wait for reclaim event then this is a failure, 403 // otherwise this is a pass. 404 finishWithResult(mWaitForReclaim ? RESULT_CANCELED : RESULT_OK); 405 } 406 } 407 }); 408 mWorkerThread.start(); 409 } 410 411 @Override onDestroy()412 protected void onDestroy() { 413 Log.d(TAG, "onDestroy called."); 414 super.onDestroy(); 415 } 416 } 417