1 /* 2 * Copyright (C) 2021 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.mediapc.cts; 18 19 import static android.media.MediaCodecInfo.CodecCapabilities; 20 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; 21 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible; 22 import static android.mediapc.cts.common.CodecMetrics.getMetrics; 23 24 import static org.junit.Assert.assertNotNull; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 28 import android.graphics.ImageFormat; 29 import android.media.Image; 30 import android.media.MediaCodec; 31 import android.media.MediaCodecInfo; 32 import android.media.MediaCodecList; 33 import android.media.MediaCrypto; 34 import android.media.MediaDrm; 35 import android.media.MediaExtractor; 36 import android.media.MediaFormat; 37 import android.media.NotProvisionedException; 38 import android.media.ResourceBusyException; 39 import android.mediapc.cts.common.CodecMetrics; 40 import android.os.Build; 41 import android.util.Log; 42 import android.util.Pair; 43 import android.view.Surface; 44 45 import androidx.annotation.NonNull; 46 import androidx.test.platform.app.InstrumentationRegistry; 47 48 import org.junit.Assert; 49 50 import java.io.File; 51 import java.io.FileInputStream; 52 import java.io.IOException; 53 import java.nio.ByteBuffer; 54 import java.util.ArrayList; 55 import java.util.HashSet; 56 import java.util.LinkedList; 57 import java.util.Map; 58 import java.util.Set; 59 import java.util.UUID; 60 import java.util.concurrent.Callable; 61 import java.util.concurrent.locks.Condition; 62 import java.util.concurrent.locks.Lock; 63 import java.util.concurrent.locks.ReentrantLock; 64 import java.util.function.Consumer; 65 import java.util.regex.Pattern; 66 67 class CodecAsyncHandler extends MediaCodec.Callback { 68 private static final String LOG_TAG = CodecAsyncHandler.class.getSimpleName(); 69 private final Lock mLock = new ReentrantLock(); 70 private final Condition mCondition = mLock.newCondition(); 71 private final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mCbInputQueue; 72 private final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mCbOutputQueue; 73 private MediaFormat mOutFormat; 74 private boolean mSignalledOutFormatChanged; 75 private volatile boolean mSignalledError; 76 CodecAsyncHandler()77 CodecAsyncHandler() { 78 mCbInputQueue = new LinkedList<>(); 79 mCbOutputQueue = new LinkedList<>(); 80 mSignalledError = false; 81 mSignalledOutFormatChanged = false; 82 } 83 clearQueues()84 void clearQueues() { 85 mLock.lock(); 86 mCbInputQueue.clear(); 87 mCbOutputQueue.clear(); 88 mLock.unlock(); 89 } 90 resetContext()91 void resetContext() { 92 clearQueues(); 93 mOutFormat = null; 94 mSignalledOutFormatChanged = false; 95 mSignalledError = false; 96 } 97 98 @Override onInputBufferAvailable(@onNull MediaCodec codec, int bufferIndex)99 public void onInputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex) { 100 assertTrue(bufferIndex >= 0); 101 mLock.lock(); 102 mCbInputQueue.add(new Pair<>(bufferIndex, (MediaCodec.BufferInfo) null)); 103 mCondition.signalAll(); 104 mLock.unlock(); 105 } 106 107 @Override onOutputBufferAvailable(@onNull MediaCodec codec, int bufferIndex, @NonNull MediaCodec.BufferInfo info)108 public void onOutputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex, 109 @NonNull MediaCodec.BufferInfo info) { 110 assertTrue(bufferIndex >= 0); 111 mLock.lock(); 112 mCbOutputQueue.add(new Pair<>(bufferIndex, info)); 113 mCondition.signalAll(); 114 mLock.unlock(); 115 } 116 117 @Override onError(@onNull MediaCodec codec, MediaCodec.CodecException e)118 public void onError(@NonNull MediaCodec codec, MediaCodec.CodecException e) { 119 mLock.lock(); 120 mSignalledError = true; 121 mCondition.signalAll(); 122 mLock.unlock(); 123 Log.e(LOG_TAG, "received media codec error : " + e.getMessage()); 124 } 125 126 @Override onOutputFormatChanged(@onNull MediaCodec codec, @NonNull MediaFormat format)127 public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) { 128 mOutFormat = format; 129 mSignalledOutFormatChanged = true; 130 Log.i(LOG_TAG, "Output format changed: " + format.toString()); 131 } 132 setCallBack(MediaCodec codec, boolean isCodecInAsyncMode)133 void setCallBack(MediaCodec codec, boolean isCodecInAsyncMode) { 134 if (isCodecInAsyncMode) { 135 codec.setCallback(this); 136 } else { 137 codec.setCallback(null); 138 } 139 } 140 getOutput()141 Pair<Integer, MediaCodec.BufferInfo> getOutput() throws InterruptedException { 142 Pair<Integer, MediaCodec.BufferInfo> element = null; 143 mLock.lock(); 144 while (!mSignalledError) { 145 if (mCbOutputQueue.isEmpty()) { 146 mCondition.await(); 147 } else { 148 element = mCbOutputQueue.remove(0); 149 break; 150 } 151 } 152 mLock.unlock(); 153 return element; 154 } 155 getWork()156 Pair<Integer, MediaCodec.BufferInfo> getWork() throws InterruptedException { 157 Pair<Integer, MediaCodec.BufferInfo> element = null; 158 mLock.lock(); 159 while (!mSignalledError) { 160 if (mCbInputQueue.isEmpty() && mCbOutputQueue.isEmpty()) { 161 mCondition.await(); 162 } else { 163 if (!mCbOutputQueue.isEmpty()) { 164 element = mCbOutputQueue.remove(0); 165 break; 166 } 167 if (!mCbInputQueue.isEmpty()) { 168 element = mCbInputQueue.remove(0); 169 break; 170 } 171 } 172 } 173 mLock.unlock(); 174 return element; 175 } 176 hasSeenError()177 boolean hasSeenError() { 178 return mSignalledError; 179 } 180 hasOutputFormatChanged()181 boolean hasOutputFormatChanged() { 182 return mSignalledOutFormatChanged; 183 } 184 getOutputFormat()185 MediaFormat getOutputFormat() { 186 return mOutFormat; 187 } 188 } 189 190 abstract class CodecTestBase { 191 private static final String LOG_TAG = CodecTestBase.class.getSimpleName(); 192 static final boolean ENABLE_LOGS = false; 193 static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000; 194 static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000; 195 static final int SELECT_ALL = 0; // Select all codecs 196 static final int SELECT_HARDWARE = 1; // Select Hardware codecs only 197 static final int SELECT_SOFTWARE = 2; // Select Software codecs only 198 static final int SELECT_AUDIO = 3; // Select Audio codecs only 199 static final int SELECT_VIDEO = 4; // Select Video codecs only 200 // Maintain Timeouts in sync with their counterpart in NativeMediaCommon.h 201 static final long Q_DEQ_TIMEOUT_US = 5000; // block at most 5ms while looking for io buffers 202 static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error 203 static final String mInpPrefix = WorkDir.getMediaDirString(); 204 public static final MediaCodecList MCL_ALL = new MediaCodecList(MediaCodecList.ALL_CODECS); 205 public static final String CODEC_FILTER_KEY = "codec-filter"; 206 public static final String CODEC_PREFIX_KEY = "codec-prefix"; 207 public static final String MEDIA_TYPE_PREFIX_KEY = "media-type-prefix"; 208 public static Pattern codecFilter; 209 public static String codecPrefix; 210 public static String mediaTypePrefix; 211 212 CodecAsyncHandler mAsyncHandle; 213 boolean mIsCodecInAsyncMode; 214 boolean mSawInputEOS; 215 boolean mSawOutputEOS; 216 boolean mSignalEOSWithLastFrame; 217 int mInputCount; 218 int mOutputCount; 219 long mPrevOutputPts; 220 boolean mSignalledOutFormatChanged; 221 MediaFormat mOutFormat; 222 boolean mIsAudio; 223 224 MediaCodec mCodec; 225 Surface mSurface; 226 227 static { 228 android.os.Bundle args = InstrumentationRegistry.getArguments(); 229 codecPrefix = args.getString(CODEC_PREFIX_KEY); 230 mediaTypePrefix = args.getString(MEDIA_TYPE_PREFIX_KEY); 231 String codecFilterStr = args.getString(CODEC_FILTER_KEY); 232 if (codecFilterStr != null) { 233 codecFilter = Pattern.compile(codecFilterStr); 234 } 235 } 236 enqueueInput(int bufferIndex)237 abstract void enqueueInput(int bufferIndex) throws IOException; 238 239 // Callback for each time the output count changes. 240 // This can be used to measure codec performance. 241 Consumer<Integer> mOutputCountListener; 242 243 // must not be called during doWork setOutputCountListener(Consumer<Integer> listener)244 void setOutputCountListener(Consumer<Integer> listener) { 245 mOutputCountListener = listener; 246 } 247 248 /** 249 * Called to handle a dequeued output buffer. 250 * 251 * We account for EOS and the number of full output frames. 252 */ dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)253 protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 254 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 255 mSawOutputEOS = true; 256 } 257 258 int outputCount = mOutputCount; 259 // handle output count prior to releasing the buffer as that can take time 260 if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 261 mOutputCount++; 262 if (mOutputCountListener != null) { 263 mOutputCountListener.accept(mOutputCount); 264 } 265 } 266 releaseOutput(outputCount, bufferIndex, info); 267 } 268 269 /** 270 * Called to handle releasing an output buffer. 271 */ releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)272 abstract void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info); 273 274 /** 275 * Called to handle releasing an output buffer. 276 * 277 * @param outputCount total count of full output frames prior to 278 * this point (not including this buffer). 279 */ releaseOutput(int outputCount, int bufferIndex, MediaCodec.BufferInfo info)280 protected void releaseOutput(int outputCount, int bufferIndex, MediaCodec.BufferInfo info) { 281 releaseOutput(bufferIndex, info); 282 } 283 configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)284 void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, 285 boolean isEncoder) throws Exception { 286 resetContext(isAsync, signalEOSWithLastFrame); 287 mAsyncHandle.setCallBack(mCodec, isAsync); 288 // signalEOS flag has nothing to do with configure. We are using this flag to try all 289 // available configure apis 290 if (signalEOSWithLastFrame) { 291 mCodec.configure(format, mSurface, null, 292 isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0); 293 } else { 294 mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0, 295 null); 296 } 297 } 298 resetContext(boolean isAsync, boolean signalEOSWithLastFrame)299 void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 300 mAsyncHandle.resetContext(); 301 mIsCodecInAsyncMode = isAsync; 302 mSawInputEOS = false; 303 mSawOutputEOS = false; 304 mSignalEOSWithLastFrame = signalEOSWithLastFrame; 305 mInputCount = 0; 306 mOutputCount = 0; 307 mPrevOutputPts = Long.MIN_VALUE; 308 mSignalledOutFormatChanged = false; 309 } 310 enqueueEOS(int bufferIndex)311 void enqueueEOS(int bufferIndex) { 312 if (!mSawInputEOS) { 313 mCodec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 314 mSawInputEOS = true; 315 if (ENABLE_LOGS) { 316 Log.v(LOG_TAG, "Queued End of Stream"); 317 } 318 } 319 } 320 doWork(int frameLimit)321 void doWork(int frameLimit) throws InterruptedException, IOException { 322 int frameCount = 0; 323 if (mIsCodecInAsyncMode) { 324 // dequeue output after inputEOS is expected to be done in waitForAllOutputs() 325 while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < frameLimit) { 326 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); 327 if (element != null) { 328 int bufferID = element.first; 329 MediaCodec.BufferInfo info = element.second; 330 if (info != null) { 331 // <id, info> corresponds to output callback. Handle it accordingly 332 dequeueOutput(bufferID, info); 333 } else { 334 // <id, null> corresponds to input callback. Handle it accordingly 335 enqueueInput(bufferID); 336 frameCount++; 337 } 338 } 339 } 340 } else { 341 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 342 // dequeue output after inputEOS is expected to be done in waitForAllOutputs() 343 while (!mSawInputEOS && frameCount < frameLimit) { 344 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 345 if (outputBufferId >= 0) { 346 dequeueOutput(outputBufferId, outInfo); 347 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 348 mOutFormat = mCodec.getOutputFormat(); 349 mSignalledOutFormatChanged = true; 350 } 351 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); 352 if (inputBufferId != -1) { 353 enqueueInput(inputBufferId); 354 frameCount++; 355 } 356 } 357 } 358 } 359 queueEOS()360 void queueEOS() throws InterruptedException { 361 if (mIsCodecInAsyncMode) { 362 while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) { 363 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); 364 if (element != null) { 365 int bufferID = element.first; 366 MediaCodec.BufferInfo info = element.second; 367 if (info != null) { 368 dequeueOutput(bufferID, info); 369 } else { 370 enqueueEOS(element.first); 371 } 372 } 373 } 374 } else { 375 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 376 while (!mSawInputEOS) { 377 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 378 if (outputBufferId >= 0) { 379 dequeueOutput(outputBufferId, outInfo); 380 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 381 mOutFormat = mCodec.getOutputFormat(); 382 mSignalledOutFormatChanged = true; 383 } 384 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); 385 if (inputBufferId != -1) { 386 enqueueEOS(inputBufferId); 387 } 388 } 389 } 390 } 391 waitForAllOutputs()392 void waitForAllOutputs() throws InterruptedException { 393 if (mIsCodecInAsyncMode) { 394 while (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) { 395 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput(); 396 if (element != null) { 397 dequeueOutput(element.first, element.second); 398 } 399 } 400 } else { 401 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 402 while (!mSawOutputEOS) { 403 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 404 if (outputBufferId >= 0) { 405 dequeueOutput(outputBufferId, outInfo); 406 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 407 mOutFormat = mCodec.getOutputFormat(); 408 mSignalledOutFormatChanged = true; 409 } 410 } 411 } 412 } 413 selectCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder)414 static ArrayList<String> selectCodecs(String mediaType, ArrayList<MediaFormat> formats, 415 String[] features, boolean isEncoder) { 416 return selectCodecs(mediaType, formats, features, isEncoder, SELECT_ALL); 417 } 418 selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder)419 static ArrayList<String> selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats, 420 String[] features, boolean isEncoder) { 421 return selectHardwareCodecs(mediaType, formats, features, isEncoder, false); 422 } 423 selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder, boolean allCodecs)424 static ArrayList<String> selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats, 425 String[] features, boolean isEncoder, boolean allCodecs) { 426 return selectCodecs(mediaType, formats, features, isEncoder, SELECT_HARDWARE, allCodecs); 427 } 428 selectCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder, int selectCodecOption)429 static ArrayList<String> selectCodecs(String mediaType, ArrayList<MediaFormat> formats, 430 String[] features, boolean isEncoder, int selectCodecOption) { 431 return selectCodecs(mediaType, formats, features, isEncoder, selectCodecOption, false); 432 } 433 selectCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder, int selectCodecOption, boolean allCodecs)434 static ArrayList<String> selectCodecs(String mediaType, ArrayList<MediaFormat> formats, 435 String[] features, boolean isEncoder, int selectCodecOption, boolean allCodecs) { 436 int kind = allCodecs ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; 437 MediaCodecList codecList = new MediaCodecList(kind); 438 MediaCodecInfo[] codecInfos = codecList.getCodecInfos(); 439 ArrayList<String> listOfCodecs = new ArrayList<>(); 440 for (MediaCodecInfo codecInfo : codecInfos) { 441 if (codecInfo.isEncoder() != isEncoder) continue; 442 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue; 443 if (selectCodecOption == SELECT_HARDWARE && !codecInfo.isHardwareAccelerated()) 444 continue; 445 else if (selectCodecOption == SELECT_SOFTWARE && !codecInfo.isSoftwareOnly()) 446 continue; 447 String[] types = codecInfo.getSupportedTypes(); 448 for (String type : types) { 449 if (type.equalsIgnoreCase(mediaType)) { 450 boolean isOk = true; 451 MediaCodecInfo.CodecCapabilities codecCapabilities = 452 codecInfo.getCapabilitiesForType(type); 453 if (formats != null) { 454 for (MediaFormat format : formats) { 455 if (!codecCapabilities.isFormatSupported(format)) { 456 isOk = false; 457 break; 458 } 459 } 460 } 461 if (features != null) { 462 for (String feature : features) { 463 if (!codecCapabilities.isFeatureSupported(feature)) { 464 isOk = false; 465 break; 466 } 467 } 468 } 469 if (isOk) listOfCodecs.add(codecInfo.getName()); 470 } 471 } 472 } 473 return listOfCodecs; 474 } 475 getMediaTypesOfAvailableCodecs(int codecAV, int codecType)476 static Set<String> getMediaTypesOfAvailableCodecs(int codecAV, int codecType) { 477 MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 478 MediaCodecInfo[] codecInfos = codecList.getCodecInfos(); 479 Set<String> listOfMediaTypes = new HashSet<>(); 480 for (MediaCodecInfo codecInfo : codecInfos) { 481 if (codecType == SELECT_HARDWARE && !codecInfo.isHardwareAccelerated()) { 482 continue; 483 } 484 if (codecType == SELECT_SOFTWARE && !codecInfo.isSoftwareOnly()) { 485 continue; 486 } 487 String[] types = codecInfo.getSupportedTypes(); 488 for (String type : types) { 489 if (codecAV == SELECT_AUDIO && !type.startsWith("audio/")) { 490 continue; 491 } 492 if (codecAV == SELECT_VIDEO && !type.startsWith("video/")) { 493 continue; 494 } 495 listOfMediaTypes.add(type); 496 } 497 } 498 return listOfMediaTypes; 499 } 500 501 /** 502 * Returns MediaCodecInfo for the given codec name 503 */ getCodecInfo(String codecName)504 public static MediaCodecInfo getCodecInfo(String codecName) { 505 for (MediaCodecInfo info : MCL_ALL.getCodecInfos()) { 506 if (info.getName().equals(codecName)) { 507 return info; 508 } 509 } 510 return null; 511 } 512 513 /** 514 * Return CodecCapabilities for the given codec name 515 */ getCodecCapabilities(String codecName, String mediaType)516 public static CodecCapabilities getCodecCapabilities(String codecName, String mediaType) { 517 MediaCodecInfo.CodecCapabilities codecCapabilities = 518 getCodecInfo(codecName).getCapabilitiesForType(mediaType); 519 return codecCapabilities; 520 521 } 522 523 /** 524 * Checks if the codec supports all the given formats 525 */ areFormatsSupported(String codecName, ArrayList<MediaFormat> formats)526 public static boolean areFormatsSupported(String codecName, ArrayList<MediaFormat> formats) { 527 boolean isSupported = true; 528 MediaCodecInfo info = getCodecInfo(codecName); 529 if (info == null) { 530 return false; 531 } 532 for (MediaFormat format : formats) { 533 String mediaType = format.getString(MediaFormat.KEY_MIME); 534 MediaCodecInfo.CodecCapabilities codecCapabilities = 535 info.getCapabilitiesForType(mediaType); 536 if (!codecCapabilities.isFormatSupported(format)) { 537 Log.d(LOG_TAG, "Codec: " + codecName + " doesn't support format: " + format); 538 return false; 539 } 540 } 541 return true; 542 } 543 } 544 545 class CodecDecoderTestBase extends CodecTestBase { 546 private static final String LOG_TAG = CodecDecoderTestBase.class.getSimpleName(); 547 // Widevine Content Protection Identifier https://dashif.org/identifiers/content_protection/ 548 public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); 549 550 String mMediaType; 551 String mTestFile; 552 boolean mIsInterlaced; 553 boolean mSecureMode; 554 byte[] mSessionID; 555 556 ArrayList<ByteBuffer> mCsdBuffers; 557 558 MediaExtractor mExtractor; 559 MediaDrm mDrm = null; 560 MediaCrypto mCrypto = null; 561 CodecDecoderTestBase(String mediaType, String testFile, boolean secureMode)562 CodecDecoderTestBase(String mediaType, String testFile, boolean secureMode) { 563 mMediaType = mediaType; 564 mTestFile = testFile; 565 mAsyncHandle = new CodecAsyncHandler(); 566 mCsdBuffers = new ArrayList<>(); 567 mIsAudio = mMediaType.startsWith("audio/"); 568 mSecureMode = secureMode; 569 } 570 CodecDecoderTestBase(String mediaType, String testFile)571 CodecDecoderTestBase(String mediaType, String testFile) { 572 this(mediaType, testFile, false); 573 } 574 setUpSource(String srcFile)575 MediaFormat setUpSource(String srcFile) throws IOException { 576 return setUpSource(mInpPrefix, srcFile); 577 } 578 hasCSD(MediaFormat format)579 boolean hasCSD(MediaFormat format) { 580 return format.containsKey("csd-0"); 581 } 582 openSession(MediaDrm drm)583 private byte[] openSession(MediaDrm drm) { 584 byte[] sessionId = null; 585 int retryCount = 3; 586 while (retryCount-- > 0) { 587 try { 588 sessionId = drm.openSession(); 589 break; 590 } catch (NotProvisionedException eNotProvisioned) { 591 Log.i(LOG_TAG, "Missing certificate, provisioning"); 592 try { 593 final ProvisionRequester provisionRequester = new ProvisionRequester(drm); 594 provisionRequester.send(); 595 } catch (Exception e) { 596 Log.e(LOG_TAG, "Provisioning fails because " + e.toString()); 597 } 598 } catch (ResourceBusyException eResourceBusy) { 599 Log.w(LOG_TAG, "Resource busy in openSession, retrying..."); 600 try { 601 Thread.sleep(1000); 602 } catch (Exception ignored) { 603 } 604 } 605 } 606 return sessionId; 607 } 608 configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder, String serverURL)609 void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, 610 boolean isEncoder, String serverURL) throws Exception { 611 resetContext(isAsync, signalEOSWithLastFrame); 612 mAsyncHandle.setCallBack(mCodec, isAsync); 613 if (mSecureMode && serverURL != null) { 614 if (mDrm == null) { 615 mDrm = new MediaDrm(WIDEVINE_UUID); 616 } 617 if (mCrypto == null) { 618 mSessionID = openSession(mDrm); 619 assertNotNull("Failed to provision device.", mSessionID); 620 mCrypto = new MediaCrypto(WIDEVINE_UUID, mSessionID); 621 } 622 mCodec.configure(format, mSurface, mCrypto, 623 isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0); 624 625 Map<UUID, byte[]> psshInfo = mExtractor.getPsshInfo(); 626 byte[] emeInitData = null; 627 628 // TODO(b/230682028) Remove the following once webm extractor returns PSSH info for VP9 629 if (psshInfo == null && mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) { 630 if (format.getInteger(MediaFormat.KEY_HEIGHT) == 1080) { 631 emeInitData = new byte[]{8, 1, 18, 1, 51, 26, 13, 119, 105, 100, 101, 118, 632 105, 110, 101, 95, 116, 101, 115, 116, 34, 10, 50, 48, 49, 53, 633 95, 116, 101, 97, 114, 115, 42, 2, 72, 68}; 634 } else if (format.getInteger(MediaFormat.KEY_HEIGHT) == 2160) { 635 emeInitData = new byte[]{8, 1, 18, 1, 56, 26, 13, 119, 105, 100, 101, 118, 636 105, 110, 101, 95, 116, 101, 115, 116, 34, 10, 50, 48, 49, 53, 637 95, 116, 101, 97, 114, 115, 42, 4, 85, 72, 68, 49}; 638 } else { 639 fail("unable to get pssh info for the given resolution in vp9"); 640 } 641 } else { 642 assertNotNull("Extractor is missing pssh info", psshInfo); 643 emeInitData = psshInfo.get(WIDEVINE_UUID); 644 } 645 assertNotNull("Extractor pssh info is missing data for scheme: " + WIDEVINE_UUID, 646 emeInitData); 647 KeyRequester requester = 648 new KeyRequester(mDrm, mSessionID, MediaDrm.KEY_TYPE_STREAMING, mMediaType, 649 emeInitData, serverURL, WIDEVINE_UUID); 650 requester.send(); 651 return; 652 } 653 // signalEOS flag has nothing to do with configure. We are using this flag to try all 654 // available configure apis 655 if (signalEOSWithLastFrame) { 656 mCodec.configure(format, mSurface, null, 657 isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0); 658 } else { 659 mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0, 660 null); 661 } 662 } 663 configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)664 void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, 665 boolean isEncoder) throws Exception { 666 configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder, null); 667 } 668 setUpSource(String prefix, String srcFile)669 MediaFormat setUpSource(String prefix, String srcFile) throws IOException { 670 mExtractor = new MediaExtractor(); 671 mExtractor.setDataSource(prefix + srcFile); 672 for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) { 673 MediaFormat format = mExtractor.getTrackFormat(trackID); 674 if (mMediaType.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) { 675 mExtractor.selectTrack(trackID); 676 if (!mIsAudio) { 677 if (mSurface == null) { 678 // COLOR_FormatYUV420Flexible must be supported by all components 679 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible); 680 } else { 681 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatSurface); 682 } 683 } 684 // TODO: determine this from the extractor format when it becomes exposed. 685 mIsInterlaced = srcFile.contains("_interlaced_"); 686 return format; 687 } 688 } 689 fail("No track with mediaType: " + mMediaType + " found in file: " + srcFile); 690 return null; 691 } 692 enqueueInput(int bufferIndex)693 void enqueueInput(int bufferIndex) { 694 if (mExtractor.getSampleSize() < 0) { 695 enqueueEOS(bufferIndex); 696 } else { 697 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 698 int size = mExtractor.readSampleData(inputBuffer, 0); 699 long pts = mExtractor.getSampleTime(); 700 int extractorFlags = mExtractor.getSampleFlags(); 701 int codecFlags = 0; 702 if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) { 703 codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME; 704 } 705 MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo(); 706 boolean isEncrypted = mExtractor.getSampleCryptoInfo(info); 707 if (!mExtractor.advance() && mSignalEOSWithLastFrame) { 708 codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 709 mSawInputEOS = true; 710 } 711 if (mSecureMode && isEncrypted) { 712 mCodec.queueSecureInputBuffer(bufferIndex, 0, info, pts, codecFlags); 713 } else { 714 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags); 715 } 716 if (size > 0 && (codecFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 717 mInputCount++; 718 } 719 } 720 } 721 releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)722 void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info) { 723 mCodec.releaseOutputBuffer(bufferIndex, false); 724 } 725 } 726 727 class CodecEncoderTestBase extends CodecTestBase { 728 private static final String LOG_TAG = CodecEncoderTestBase.class.getSimpleName(); 729 730 // files are in WorkDir.getMediaDirString(); 731 private static final String INPUT_AUDIO_FILE = "bbb_2ch_44kHz_s16le.raw"; 732 private static final String INPUT_VIDEO_FILE = "bbb_cif_yuv420p_30fps.yuv"; 733 private final int INP_FRM_WIDTH = 352; 734 private final int INP_FRM_HEIGHT = 288; 735 736 final String mMediaType; 737 final String mInputFile; 738 byte[] mInputData; 739 int mNumBytesSubmitted; 740 long mInputOffsetPts; 741 742 int mWidth, mHeight; 743 int mFrameRate; 744 int mMaxBFrames; 745 int mChannels; 746 int mSampleRate; 747 CodecEncoderTestBase(String mediaType)748 CodecEncoderTestBase(String mediaType) { 749 mMediaType = mediaType; 750 mWidth = INP_FRM_WIDTH; 751 mHeight = INP_FRM_HEIGHT; 752 mChannels = 1; 753 mSampleRate = 8000; 754 mFrameRate = 30; 755 mMaxBFrames = 0; 756 if (mediaType.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) mFrameRate = 12; 757 else if (mediaType.equals(MediaFormat.MIMETYPE_VIDEO_H263)) mFrameRate = 12; 758 mAsyncHandle = new CodecAsyncHandler(); 759 mIsAudio = mMediaType.startsWith("audio/"); 760 mInputFile = mIsAudio ? INPUT_AUDIO_FILE : INPUT_VIDEO_FILE; 761 } 762 763 @Override resetContext(boolean isAsync, boolean signalEOSWithLastFrame)764 void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 765 super.resetContext(isAsync, signalEOSWithLastFrame); 766 mNumBytesSubmitted = 0; 767 mInputOffsetPts = 0; 768 } 769 setUpSource(String srcFile)770 void setUpSource(String srcFile) throws IOException { 771 String inpPath = mInpPrefix + srcFile; 772 try (FileInputStream fInp = new FileInputStream(inpPath)) { 773 int size = (int) new File(inpPath).length(); 774 mInputData = new byte[size]; 775 fInp.read(mInputData, 0, size); 776 } 777 } 778 fillImage(Image image)779 void fillImage(Image image) { 780 Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888); 781 int imageWidth = image.getWidth(); 782 int imageHeight = image.getHeight(); 783 Image.Plane[] planes = image.getPlanes(); 784 int offset = mNumBytesSubmitted; 785 for (int i = 0; i < planes.length; ++i) { 786 ByteBuffer buf = planes[i].getBuffer(); 787 int width = imageWidth; 788 int height = imageHeight; 789 int tileWidth = INP_FRM_WIDTH; 790 int tileHeight = INP_FRM_HEIGHT; 791 int rowStride = planes[i].getRowStride(); 792 int pixelStride = planes[i].getPixelStride(); 793 if (i != 0) { 794 width = imageWidth / 2; 795 height = imageHeight / 2; 796 tileWidth = INP_FRM_WIDTH / 2; 797 tileHeight = INP_FRM_HEIGHT / 2; 798 } 799 if (pixelStride == 1) { 800 if (width == rowStride && width == tileWidth && height == tileHeight) { 801 buf.put(mInputData, offset, width * height); 802 } else { 803 for (int z = 0; z < height; z += tileHeight) { 804 int rowsToCopy = Math.min(height - z, tileHeight); 805 for (int y = 0; y < rowsToCopy; y++) { 806 for (int x = 0; x < width; x += tileWidth) { 807 int colsToCopy = Math.min(width - x, tileWidth); 808 buf.position((z + y) * rowStride + x); 809 buf.put(mInputData, offset + y * tileWidth, colsToCopy); 810 } 811 } 812 } 813 } 814 } else { 815 // do it pixel-by-pixel 816 for (int z = 0; z < height; z += tileHeight) { 817 int rowsToCopy = Math.min(height - z, tileHeight); 818 for (int y = 0; y < rowsToCopy; y++) { 819 int lineOffset = (z + y) * rowStride; 820 for (int x = 0; x < width; x += tileWidth) { 821 int colsToCopy = Math.min(width - x, tileWidth); 822 for (int w = 0; w < colsToCopy; w++) { 823 buf.position(lineOffset + (x + w) * pixelStride); 824 buf.put(mInputData[offset + y * tileWidth + w]); 825 } 826 } 827 } 828 } 829 } 830 offset += tileWidth * tileHeight; 831 } 832 } 833 fillByteBuffer(ByteBuffer inputBuffer)834 void fillByteBuffer(ByteBuffer inputBuffer) { 835 int offset = 0, frmOffset = mNumBytesSubmitted; 836 for (int plane = 0; plane < 3; plane++) { 837 int width = mWidth; 838 int height = mHeight; 839 int tileWidth = INP_FRM_WIDTH; 840 int tileHeight = INP_FRM_HEIGHT; 841 if (plane != 0) { 842 width = mWidth / 2; 843 height = mHeight / 2; 844 tileWidth = INP_FRM_WIDTH / 2; 845 tileHeight = INP_FRM_HEIGHT / 2; 846 } 847 for (int k = 0; k < height; k += tileHeight) { 848 int rowsToCopy = Math.min(height - k, tileHeight); 849 for (int j = 0; j < rowsToCopy; j++) { 850 for (int i = 0; i < width; i += tileWidth) { 851 int colsToCopy = Math.min(width - i, tileWidth); 852 inputBuffer.position(offset + (k + j) * width + i); 853 inputBuffer.put(mInputData, frmOffset + j * tileWidth, colsToCopy); 854 } 855 } 856 } 857 offset += width * height; 858 frmOffset += tileWidth * tileHeight; 859 } 860 } 861 enqueueInput(int bufferIndex)862 void enqueueInput(int bufferIndex) { 863 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 864 if (mNumBytesSubmitted >= mInputData.length) { 865 enqueueEOS(bufferIndex); 866 } else { 867 int size; 868 int flags = 0; 869 long pts = mInputOffsetPts; 870 if (mIsAudio) { 871 pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate); 872 size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted); 873 inputBuffer.put(mInputData, mNumBytesSubmitted, size); 874 if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) { 875 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 876 mSawInputEOS = true; 877 } 878 mNumBytesSubmitted += size; 879 } else { 880 pts += mInputCount * 1000000L / mFrameRate; 881 size = mWidth * mHeight * 3 / 2; 882 int frmSize = INP_FRM_WIDTH * INP_FRM_HEIGHT * 3 / 2; 883 if (mNumBytesSubmitted + frmSize > mInputData.length) { 884 fail("received partial frame to encode"); 885 } else { 886 Image img = mCodec.getInputImage(bufferIndex); 887 if (img != null) { 888 fillImage(img); 889 } else { 890 if (mWidth == INP_FRM_WIDTH && mHeight == INP_FRM_HEIGHT) { 891 inputBuffer.put(mInputData, mNumBytesSubmitted, size); 892 } else { 893 fillByteBuffer(inputBuffer); 894 } 895 } 896 } 897 if (mNumBytesSubmitted + frmSize >= mInputData.length && mSignalEOSWithLastFrame) { 898 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 899 mSawInputEOS = true; 900 } 901 mNumBytesSubmitted += frmSize; 902 } 903 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags); 904 mInputCount++; 905 } 906 } 907 releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)908 void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info) { 909 mCodec.releaseOutputBuffer(bufferIndex, false); 910 } 911 } 912 913 /** 914 * The following class decodes the given testFile using decoder created by the given decoderName 915 * in surface mode(uses PersistentInputSurface) and returns the achieved fps for decoding. 916 */ 917 class Decode extends CodecDecoderTestBase implements Callable<CodecMetrics> { 918 private static final String LOG_TAG = Decode.class.getSimpleName(); 919 920 final String mDecoderName; 921 static final long EACH_FRAME_TIME_INTERVAL_US = 1000000 / 30; 922 static final String WIDEVINE_LICENSE_SERVER_URL = "https://proxy.uat.widevine.com/proxy"; 923 static final String PROVIDER = "widevine_test"; 924 final String mServerURL = 925 String.format("%s?video_id=%s&provider=%s", WIDEVINE_LICENSE_SERVER_URL, 926 "GTS_HW_SECURE_ALL", PROVIDER); 927 final boolean mIsAsync; 928 private int mInitialFramesToIgnoreCount = 1; 929 private long mStartTimeMillis = 0; 930 private long mEndTimeMillis = 0; 931 932 double mFrameDrops; 933 long mRenderedStartTimeUs; 934 Decode(String mediaType, String testFile, String decoderName, boolean isAsync)935 Decode(String mediaType, String testFile, String decoderName, boolean isAsync) { 936 this(mediaType, testFile,decoderName, isAsync, false); 937 } 938 Decode(String mediaType, String testFile, String decoderName, boolean isAsync, boolean secureMode)939 Decode(String mediaType, String testFile, String decoderName, boolean isAsync, 940 boolean secureMode) { 941 super(mediaType, testFile); 942 mDecoderName = decoderName; 943 mSurface = MediaCodec.createPersistentInputSurface(); 944 mIsAsync = isAsync; 945 mSecureMode = secureMode; 946 } 947 setInitialFramesToIgnoreCount(int count)948 public void setInitialFramesToIgnoreCount(int count) { 949 mInitialFramesToIgnoreCount = count; 950 } 951 952 // measure throughput at the output port onOutputCountListener(int count)953 private void onOutputCountListener(int count) { 954 // keep the timestamp of the last output frame 955 mEndTimeMillis = System.currentTimeMillis(); 956 957 // don't count the time for the initial frames that are ignored 958 if (count == mInitialFramesToIgnoreCount) { 959 mStartTimeMillis = mEndTimeMillis; 960 } 961 } 962 getRenderedTimeUs(int frameIndex)963 private long getRenderedTimeUs(int frameIndex) { 964 return mRenderedStartTimeUs + frameIndex * EACH_FRAME_TIME_INTERVAL_US; 965 } 966 967 @Override dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)968 protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 969 evalFrameDropsWhileDequeue(bufferIndex, info, mMediaType); 970 } 971 evalFrameDropsWhileDequeue(int bufferIndex, MediaCodec.BufferInfo info, String mediaType)972 private void evalFrameDropsWhileDequeue(int bufferIndex, MediaCodec.BufferInfo info, 973 String mediaType) { 974 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 975 mSawOutputEOS = true; 976 } 977 978 int outputCount = mOutputCount; 979 long nowUs = System.nanoTime() / 1000; 980 int initialDelay = mediaType.equals(MediaFormat.MIMETYPE_VIDEO_AV1) ? 8 : 0; 981 982 if (outputCount == 0) { 983 // delay rendering the first frame by the specific delay 984 mRenderedStartTimeUs = nowUs + initialDelay * EACH_FRAME_TIME_INTERVAL_US; 985 } 986 987 if (nowUs > getRenderedTimeUs(outputCount + 1)) { 988 // If the current sample timeStamp is greater than the actual presentation timeStamp 989 // of the next sample, we will consider it as a frame drop and don't render. 990 mFrameDrops++; 991 mCodec.releaseOutputBuffer(bufferIndex, false); 992 } else if (nowUs > getRenderedTimeUs(outputCount)) { 993 // If the current sample timeStamp is greater than the actual presentation timeStamp 994 // of the current sample, we can render it. 995 mCodec.releaseOutputBuffer(bufferIndex, true); 996 } else { 997 // If the current sample timestamp is less than the actual presentation timeStamp, 998 // We are okay with directly rendering the sample if we are less by not more than 999 // half of one sample duration. Otherwise we sleep for how much more we are less 1000 // than the half of one sample duration. 1001 if ((getRenderedTimeUs(outputCount) - nowUs) > (EACH_FRAME_TIME_INTERVAL_US / 2)) { 1002 try { 1003 Thread.sleep(((getRenderedTimeUs(outputCount) - nowUs) 1004 - (EACH_FRAME_TIME_INTERVAL_US / 2)) / 1000); 1005 } catch (InterruptedException e) { 1006 Thread.currentThread().interrupt(); // Restore the interrupted status 1007 throw new RuntimeException("the thread caught an interrupted exception" 1008 + "instead of sleeping before rendering the sample timestamp" + e); 1009 } 1010 } 1011 mCodec.releaseOutputBuffer(bufferIndex, true); 1012 } 1013 1014 if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 1015 mOutputCount++; 1016 if (mOutputCountListener != null) { 1017 mOutputCountListener.accept(mOutputCount); 1018 } 1019 } 1020 } 1021 1022 @Override resetContext(boolean isAsync, boolean signalEOSWithLastFrame)1023 void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 1024 mFrameDrops = 0; 1025 mRenderedStartTimeUs = 0; 1026 super.resetContext(isAsync, signalEOSWithLastFrame); 1027 } 1028 doDecode()1029 public CodecMetrics doDecode() throws Exception { 1030 MediaFormat format = setUpSource(mTestFile); 1031 ArrayList<MediaFormat> formats = new ArrayList<>(); 1032 formats.add(format); 1033 // If the decoder doesn't support the formats, then return 0 to indicate that decode failed 1034 if (!areFormatsSupported(mDecoderName, formats)) { 1035 return getMetrics(0.0, 0.0); 1036 } 1037 mCodec = MediaCodec.createByCodecName(mDecoderName); 1038 mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); 1039 configureCodec(format, mIsAsync, false, false, mServerURL); 1040 // TODO(b/251003943) Remove once Surface from SurfaceView is used for secure decoders 1041 try { 1042 mCodec.start(); 1043 } catch (Exception e) { 1044 Log.e(LOG_TAG, "Stopping the test because codec.start() failed.", e); 1045 mCodec.release(); 1046 return getMetrics(0.0, 0.0); 1047 } 1048 1049 // capture timestamps at receipt of output buffers 1050 setOutputCountListener(i -> onOutputCountListener(i)); 1051 1052 doWork(Integer.MAX_VALUE); 1053 queueEOS(); 1054 waitForAllOutputs(); 1055 1056 mCodec.stop(); 1057 mCodec.release(); 1058 mExtractor.release(); 1059 if (mCrypto != null) { 1060 mCrypto.release(); 1061 } 1062 if (mDrm != null) { 1063 mDrm.close(); 1064 } 1065 double fps = (mOutputCount - mInitialFramesToIgnoreCount) / 1066 ((mEndTimeMillis - mStartTimeMillis) / 1000.0); 1067 Log.d(LOG_TAG, "Decode MediaType: " + mMediaType + " Decoder: " + mDecoderName + 1068 " Achieved fps: " + fps); 1069 return getMetrics(fps, mFrameDrops / 30); 1070 } 1071 1072 @Override call()1073 public CodecMetrics call() throws Exception { 1074 try { 1075 return doDecode(); 1076 } catch (Exception e) { 1077 Log.d(LOG_TAG, "Decode MediaType: " + mMediaType + " Decoder: " + mDecoderName 1078 + " Failed due to: " + e); 1079 return getMetrics(-1.0, 0.0); 1080 } 1081 } 1082 } 1083 1084 /** 1085 * The following class decodes the given testFile using decoder created by the given decoderName 1086 * in surface mode(uses given valid surface) and render the output to surface. 1087 */ 1088 class DecodeToSurface extends Decode { 1089 DecodeToSurface(String mediaType, String testFile, String decoderName, Surface surface, boolean isAsync)1090 DecodeToSurface(String mediaType, String testFile, String decoderName, Surface surface, 1091 boolean isAsync) { 1092 super(mediaType, testFile, decoderName, isAsync); 1093 mSurface = surface; 1094 } 1095 releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)1096 void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info) { 1097 mCodec.releaseOutputBuffer(bufferIndex, true); 1098 } 1099 } 1100 1101 /** 1102 * The following class encodes a YUV video file to a given mediaType using encoder created by the 1103 * given encoderName and configuring to 30fps format. 1104 */ 1105 class Encode extends CodecEncoderTestBase implements Callable<CodecMetrics> { 1106 private static final String LOG_TAG = Encode.class.getSimpleName(); 1107 1108 private final String mEncoderName; 1109 private final boolean mIsAsync; 1110 private final int mBitrate; 1111 1112 private int mInitialFramesToIgnoreCount = 1; 1113 private long mStartTimeMillis = 0; 1114 private long mEndTimeMillis = 0; 1115 Encode(String mediaType, String encoderName, boolean isAsync, int height, int width, int frameRate, int bitrate)1116 Encode(String mediaType, String encoderName, boolean isAsync, int height, int width, 1117 int frameRate, int bitrate) { 1118 super(mediaType); 1119 mEncoderName = encoderName; 1120 mIsAsync = isAsync; 1121 mFrameRate = frameRate; 1122 mBitrate = bitrate; 1123 mHeight = height; 1124 mWidth = width; 1125 } 1126 setInitialFramesToIgnoreCount(int count)1127 public void setInitialFramesToIgnoreCount(int count) { 1128 mInitialFramesToIgnoreCount = count; 1129 } 1130 1131 // measure throughput at the output port onOutputCountListener(int count)1132 private void onOutputCountListener(int count) { 1133 // keep the timestamp of the last output frame 1134 mEndTimeMillis = System.currentTimeMillis(); 1135 1136 // don't count the time for the initial frames that are ignored 1137 if (count == mInitialFramesToIgnoreCount) { 1138 mStartTimeMillis = mEndTimeMillis; 1139 } 1140 } 1141 setUpFormat()1142 private MediaFormat setUpFormat() { 1143 MediaFormat format = new MediaFormat(); 1144 format.setString(MediaFormat.KEY_MIME, mMediaType); 1145 format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate); 1146 format.setInteger(MediaFormat.KEY_WIDTH, mWidth); 1147 format.setInteger(MediaFormat.KEY_HEIGHT, mHeight); 1148 format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate); 1149 format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0); 1150 format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f); 1151 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 1152 MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); 1153 return format; 1154 } 1155 1156 doEncode()1157 public CodecMetrics doEncode() throws Exception { 1158 MediaFormat format = setUpFormat(); 1159 mWidth = format.getInteger(MediaFormat.KEY_WIDTH); 1160 mHeight = format.getInteger(MediaFormat.KEY_HEIGHT); 1161 setUpSource(mInputFile); 1162 mCodec = MediaCodec.createByCodecName(mEncoderName); 1163 configureCodec(format, mIsAsync, false, true); 1164 mCodec.start(); 1165 1166 // capture timestamps at receipt of output buffers 1167 setOutputCountListener(i -> onOutputCountListener(i)); 1168 1169 doWork(Integer.MAX_VALUE); 1170 queueEOS(); 1171 waitForAllOutputs(); 1172 1173 mCodec.stop(); 1174 mCodec.release(); 1175 double fps = (mOutputCount - mInitialFramesToIgnoreCount) / 1176 ((mEndTimeMillis - mStartTimeMillis) / 1000.0); 1177 Log.d(LOG_TAG, "Encode MediaType: " + mMediaType + " Encoder: " + mEncoderName + 1178 " Achieved fps: " + fps); 1179 return getMetrics(fps, 0.0); 1180 } 1181 1182 @Override call()1183 public CodecMetrics call() throws Exception { 1184 return doEncode(); 1185 } 1186 } 1187