1 /* 2 * Copyright (C) 2019 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.mediav2.cts; 18 19 import android.content.pm.PackageManager; 20 import android.graphics.ImageFormat; 21 import android.graphics.Rect; 22 import android.media.Image; 23 import android.media.MediaCodec; 24 import android.media.MediaCodecInfo; 25 import android.media.MediaCodecList; 26 import android.media.MediaExtractor; 27 import android.media.MediaFormat; 28 import android.os.Build; 29 import android.os.PersistableBundle; 30 import android.util.Log; 31 import android.util.Pair; 32 import android.view.Surface; 33 34 import androidx.annotation.NonNull; 35 import androidx.test.platform.app.InstrumentationRegistry; 36 37 import org.junit.Assert; 38 import org.junit.Before; 39 40 import java.io.File; 41 import java.io.FileInputStream; 42 import java.io.IOException; 43 import java.nio.ByteBuffer; 44 import java.nio.ByteOrder; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.LinkedList; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.concurrent.locks.Condition; 55 import java.util.concurrent.locks.Lock; 56 import java.util.concurrent.locks.ReentrantLock; 57 import java.util.zip.CRC32; 58 59 import com.android.compatibility.common.util.ApiLevelUtil; 60 61 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; 62 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible; 63 import static org.junit.Assert.assertEquals; 64 import static org.junit.Assert.assertTrue; 65 import static org.junit.Assert.fail; 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 getInput()141 Pair<Integer, MediaCodec.BufferInfo> getInput() throws InterruptedException { 142 Pair<Integer, MediaCodec.BufferInfo> element = null; 143 mLock.lock(); 144 while (!mSignalledError) { 145 if (mCbInputQueue.isEmpty()) { 146 mCondition.await(); 147 } else { 148 element = mCbInputQueue.remove(0); 149 break; 150 } 151 } 152 mLock.unlock(); 153 return element; 154 } 155 getOutput()156 Pair<Integer, MediaCodec.BufferInfo> getOutput() throws InterruptedException { 157 Pair<Integer, MediaCodec.BufferInfo> element = null; 158 mLock.lock(); 159 while (!mSignalledError) { 160 if (mCbOutputQueue.isEmpty()) { 161 mCondition.await(); 162 } else { 163 element = mCbOutputQueue.remove(0); 164 break; 165 } 166 } 167 mLock.unlock(); 168 return element; 169 } 170 getWork()171 Pair<Integer, MediaCodec.BufferInfo> getWork() throws InterruptedException { 172 Pair<Integer, MediaCodec.BufferInfo> element = null; 173 mLock.lock(); 174 while (!mSignalledError) { 175 if (mCbInputQueue.isEmpty() && mCbOutputQueue.isEmpty()) { 176 mCondition.await(); 177 } else { 178 if (!mCbOutputQueue.isEmpty()) { 179 element = mCbOutputQueue.remove(0); 180 break; 181 } 182 if (!mCbInputQueue.isEmpty()) { 183 element = mCbInputQueue.remove(0); 184 break; 185 } 186 } 187 } 188 mLock.unlock(); 189 return element; 190 } 191 isInputQueueEmpty()192 boolean isInputQueueEmpty() { 193 mLock.lock(); 194 boolean isEmpty = mCbInputQueue.isEmpty(); 195 mLock.unlock(); 196 return isEmpty; 197 } 198 hasSeenError()199 boolean hasSeenError() { 200 return mSignalledError; 201 } 202 hasOutputFormatChanged()203 boolean hasOutputFormatChanged() { 204 return mSignalledOutFormatChanged; 205 } 206 getOutputFormat()207 MediaFormat getOutputFormat() { 208 return mOutFormat; 209 } 210 } 211 212 class OutputManager { 213 private static final String LOG_TAG = OutputManager.class.getSimpleName(); 214 private byte[] memory; 215 private int memIndex; 216 private CRC32 mCrc32UsingImage; 217 private CRC32 mCrc32UsingBuffer; 218 private ArrayList<Long> inpPtsList; 219 private ArrayList<Long> outPtsList; 220 OutputManager()221 OutputManager() { 222 memory = new byte[1024]; 223 memIndex = 0; 224 mCrc32UsingImage = new CRC32(); 225 mCrc32UsingBuffer = new CRC32(); 226 inpPtsList = new ArrayList<>(); 227 outPtsList = new ArrayList<>(); 228 } 229 saveInPTS(long pts)230 void saveInPTS(long pts) { 231 // Add only Unique timeStamp, discarding any duplicate frame / non-display frame 232 if (!inpPtsList.contains(pts)) { 233 inpPtsList.add(pts); 234 } 235 } 236 saveOutPTS(long pts)237 void saveOutPTS(long pts) { 238 outPtsList.add(pts); 239 } 240 isPtsStrictlyIncreasing(long lastPts)241 boolean isPtsStrictlyIncreasing(long lastPts) { 242 boolean res = true; 243 for (int i = 0; i < outPtsList.size(); i++) { 244 if (lastPts < outPtsList.get(i)) { 245 lastPts = outPtsList.get(i); 246 } else { 247 Log.e(LOG_TAG, "Timestamp ordering check failed: last timestamp: " + lastPts + 248 " current timestamp:" + outPtsList.get(i)); 249 res = false; 250 break; 251 } 252 } 253 return res; 254 } 255 isOutPtsListIdenticalToInpPtsList(boolean requireSorting)256 boolean isOutPtsListIdenticalToInpPtsList(boolean requireSorting) { 257 boolean res; 258 Collections.sort(inpPtsList); 259 if (requireSorting) { 260 Collections.sort(outPtsList); 261 } 262 if (outPtsList.size() != inpPtsList.size()) { 263 Log.e(LOG_TAG, "input and output presentation timestamp list sizes are not identical" + 264 "exp/rec" + inpPtsList.size() + '/' + outPtsList.size()); 265 return false; 266 } else { 267 int count = 0; 268 for (int i = 0; i < outPtsList.size(); i++) { 269 if (!outPtsList.get(i).equals(inpPtsList.get(i))) { 270 count ++; 271 Log.e(LOG_TAG, "input output pts mismatch, exp/rec " + outPtsList.get(i) + '/' + 272 inpPtsList.get(i)); 273 if (count == 20) { 274 Log.e(LOG_TAG, "stopping after 20 mismatches, ..."); 275 break; 276 } 277 } 278 } 279 res = (count == 0); 280 } 281 return res; 282 } 283 getOutStreamSize()284 int getOutStreamSize() { 285 return memIndex; 286 } 287 checksum(ByteBuffer buf, int size)288 void checksum(ByteBuffer buf, int size) { 289 checksum(buf, size, 0, 0, 0); 290 } 291 checksum(ByteBuffer buf, int size, int width, int height, int stride)292 void checksum(ByteBuffer buf, int size, int width, int height, int stride) { 293 int cap = buf.capacity(); 294 assertTrue("checksum() params are invalid: size = " + size + " cap = " + cap, 295 size > 0 && size <= cap); 296 if (buf.hasArray()) { 297 if (width > 0 && height > 0 && stride > 0) { 298 int offset = buf.position() + buf.arrayOffset(); 299 byte[] bb = new byte[width * height]; 300 for (int i = 0; i < height; ++i) { 301 System.arraycopy(buf.array(), offset, bb, i * width, width); 302 offset += stride; 303 } 304 mCrc32UsingBuffer.update(bb, 0, width * height); 305 } else { 306 mCrc32UsingBuffer.update(buf.array(), buf.position() + buf.arrayOffset(), size); 307 } 308 } else if (width > 0 && height > 0 && stride > 0) { 309 // Checksum only the Y plane 310 int pos = buf.position(); 311 int offset = pos; 312 byte[] bb = new byte[width * height]; 313 for (int i = 0; i < height; ++i) { 314 buf.position(offset); 315 buf.get(bb, i * width, width); 316 offset += stride; 317 } 318 mCrc32UsingBuffer.update(bb, 0, width * height); 319 buf.position(pos); 320 } else { 321 int pos = buf.position(); 322 final int rdsize = Math.min(4096, size); 323 byte[] bb = new byte[rdsize]; 324 int chk; 325 for (int i = 0; i < size; i += chk) { 326 chk = Math.min(rdsize, size - i); 327 buf.get(bb, 0, chk); 328 mCrc32UsingBuffer.update(bb, 0, chk); 329 } 330 buf.position(pos); 331 } 332 } 333 checksum(Image image)334 void checksum(Image image) { 335 int format = image.getFormat(); 336 assertEquals("unexpected image format", ImageFormat.YUV_420_888, format); 337 338 Rect cropRect = image.getCropRect(); 339 int imageWidth = cropRect.width(); 340 int imageHeight = cropRect.height(); 341 assertTrue("unexpected image dimensions", imageWidth > 0 && imageHeight > 0); 342 343 int imageLeft = cropRect.left; 344 int imageTop = cropRect.top; 345 Image.Plane[] planes = image.getPlanes(); 346 for (int i = 0; i < planes.length; ++i) { 347 ByteBuffer buf = planes[i].getBuffer(); 348 int width, height, rowStride, pixelStride, x, y, left, top; 349 rowStride = planes[i].getRowStride(); 350 pixelStride = planes[i].getPixelStride(); 351 if (i == 0) { 352 width = imageWidth; 353 height = imageHeight; 354 left = imageLeft; 355 top = imageTop; 356 } else { 357 width = imageWidth / 2; 358 height = imageHeight / 2; 359 left = imageLeft / 2; 360 top = imageTop / 2; 361 } 362 int cropOffset = left + top * rowStride; 363 // local contiguous pixel buffer 364 byte[] bb = new byte[width * height]; 365 if (buf.hasArray()) { 366 byte[] b = buf.array(); 367 int offs = buf.arrayOffset() + cropOffset; 368 if (pixelStride == 1) { 369 for (y = 0; y < height; ++y) { 370 System.arraycopy(b, offs + y * rowStride, bb, y * width, width); 371 } 372 } else { 373 // do it pixel-by-pixel 374 for (y = 0; y < height; ++y) { 375 int lineOffset = offs + y * rowStride; 376 for (x = 0; x < width; ++x) { 377 bb[y * width + x] = b[lineOffset + x * pixelStride]; 378 } 379 } 380 } 381 } else { // almost always ends up here due to direct buffers 382 int base = buf.position(); 383 int pos = base + cropOffset; 384 if (pixelStride == 1) { 385 for (y = 0; y < height; ++y) { 386 buf.position(pos + y * rowStride); 387 buf.get(bb, y * width, width); 388 } 389 } else { 390 // local line buffer 391 byte[] lb = new byte[rowStride]; 392 // do it pixel-by-pixel 393 for (y = 0; y < height; ++y) { 394 buf.position(pos + y * rowStride); 395 // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes 396 buf.get(lb, 0, pixelStride * (width - 1) + 1); 397 for (x = 0; x < width; ++x) { 398 bb[y * width + x] = lb[x * pixelStride]; 399 } 400 } 401 } 402 buf.position(base); 403 } 404 mCrc32UsingImage.update(bb, 0, width * height); 405 } 406 } 407 saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info)408 void saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info) { 409 if (memIndex + info.size >= memory.length) { 410 memory = Arrays.copyOf(memory, memIndex + info.size); 411 } 412 buf.position(info.offset); 413 buf.get(memory, memIndex, info.size); 414 memIndex += info.size; 415 } 416 position(int index)417 void position(int index) { 418 if (index < 0 || index >= memory.length) index = 0; 419 memIndex = index; 420 } 421 getBuffer()422 ByteBuffer getBuffer() { 423 return ByteBuffer.wrap(memory); 424 } 425 reset()426 void reset() { 427 position(0); 428 mCrc32UsingImage.reset(); 429 mCrc32UsingBuffer.reset(); 430 inpPtsList.clear(); 431 outPtsList.clear(); 432 } 433 getRmsError(short[] refData)434 float getRmsError(short[] refData) { 435 long totalErrorSquared = 0; 436 assertTrue(0 == (memIndex & 1)); 437 short[] shortData = new short[memIndex / 2]; 438 ByteBuffer.wrap(memory, 0, memIndex).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer() 439 .get(shortData); 440 if (refData.length != shortData.length) return Float.MAX_VALUE; 441 for (int i = 0; i < shortData.length; i++) { 442 int d = shortData[i] - refData[i]; 443 totalErrorSquared += d * d; 444 } 445 long avgErrorSquared = (totalErrorSquared / shortData.length); 446 return (float) Math.sqrt(avgErrorSquared); 447 } 448 getCheckSumImage()449 long getCheckSumImage() { 450 return mCrc32UsingImage.getValue(); 451 } 452 getCheckSumBuffer()453 long getCheckSumBuffer() { 454 return mCrc32UsingBuffer.getValue(); 455 } 456 457 @Override equals(Object o)458 public boolean equals(Object o) { 459 if (this == o) return true; 460 if (o == null || getClass() != o.getClass()) return false; 461 OutputManager that = (OutputManager) o; 462 // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders 463 // produce multiple progressive frames?) For now, do not verify timestamps. 464 boolean isEqual = this.equalsInterlaced(o); 465 if (!outPtsList.equals(that.outPtsList)) { 466 isEqual = false; 467 Log.e(LOG_TAG, "ref and test presentation timestamp mismatch"); 468 } 469 return isEqual; 470 } 471 equalsInterlaced(Object o)472 public boolean equalsInterlaced(Object o) { 473 if (this == o) return true; 474 if (o == null || getClass() != o.getClass()) return false; 475 OutputManager that = (OutputManager) o; 476 boolean isEqual = true; 477 if (mCrc32UsingImage.getValue() != that.mCrc32UsingImage.getValue()) { 478 isEqual = false; 479 Log.e(LOG_TAG, "ref and test crc32 checksums calculated using image mismatch " + 480 mCrc32UsingImage.getValue() + '/' + that.mCrc32UsingImage.getValue()); 481 } 482 if (mCrc32UsingBuffer.getValue() != that.mCrc32UsingBuffer.getValue()) { 483 isEqual = false; 484 Log.e(LOG_TAG, "ref and test crc32 checksums calculated using buffer mismatch " + 485 mCrc32UsingBuffer.getValue() + '/' + that.mCrc32UsingBuffer.getValue()); 486 if (memIndex == that.memIndex) { 487 int count = 0; 488 for (int i = 0; i < memIndex; i++) { 489 if (memory[i] != that.memory[i]) { 490 count++; 491 if (count < 20) { 492 Log.d(LOG_TAG, "sample at " + i + " exp/got:: " + memory[i] + '/' + 493 that.memory[i]); 494 } 495 } 496 } 497 if (count != 0) { 498 Log.e(LOG_TAG, "ref and test o/p samples mismatch " + count); 499 } 500 } else { 501 Log.e(LOG_TAG, "ref and test o/p sizes mismatch " + memIndex + '/' + that.memIndex); 502 } 503 } 504 return isEqual; 505 } 506 } 507 508 abstract class CodecTestBase { 509 public static final boolean IS_AT_LEAST_R = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R); 510 private static final String LOG_TAG = CodecTestBase.class.getSimpleName(); 511 512 static final String CODEC_PREFIX_KEY = "codec-prefix"; 513 static final String MIME_SEL_KEY = "mime-sel"; 514 static final Map<String, String> codecSelKeyMimeMap = new HashMap<>(); 515 static final boolean ENABLE_LOGS = false; 516 static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000; 517 static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000; 518 static final int UNSPECIFIED = 0; 519 static final int CODEC_ALL = 0; // All codecs should support 520 static final int CODEC_ANY = 1; // Atleast one codec should support 521 static final int CODEC_OPTIONAL = 2; // Codec support is optional 522 // Maintain Timeouts in sync with their counterpart in NativeMediaCommon.h 523 static final long Q_DEQ_TIMEOUT_US = 5000; // block at most 5ms while looking for io buffers 524 static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error 525 static final String INVALID_CODEC = "unknown.codec_"; 526 static final String mInpPrefix = WorkDir.getMediaDirString(); 527 static final PackageManager pm = 528 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); 529 static String mimeSelKeys; 530 static String codecPrefix; 531 532 CodecAsyncHandler mAsyncHandle; 533 boolean mIsCodecInAsyncMode; 534 boolean mSawInputEOS; 535 boolean mSawOutputEOS; 536 boolean mSignalEOSWithLastFrame; 537 int mInputCount; 538 int mOutputCount; 539 long mPrevOutputPts; 540 boolean mSignalledOutFormatChanged; 541 MediaFormat mOutFormat; 542 boolean mIsAudio; 543 544 boolean mSaveToMem; 545 OutputManager mOutputBuff; 546 547 String mCodecName; 548 MediaCodec mCodec; 549 Surface mSurface; 550 551 static { 552 System.loadLibrary("ctsmediav2codec_jni"); 553 554 codecSelKeyMimeMap.put("vp8", MediaFormat.MIMETYPE_VIDEO_VP8); 555 codecSelKeyMimeMap.put("vp9", MediaFormat.MIMETYPE_VIDEO_VP9); 556 codecSelKeyMimeMap.put("av1", MediaFormat.MIMETYPE_VIDEO_AV1); 557 codecSelKeyMimeMap.put("avc", MediaFormat.MIMETYPE_VIDEO_AVC); 558 codecSelKeyMimeMap.put("hevc", MediaFormat.MIMETYPE_VIDEO_HEVC); 559 codecSelKeyMimeMap.put("mpeg4", MediaFormat.MIMETYPE_VIDEO_MPEG4); 560 codecSelKeyMimeMap.put("h263", MediaFormat.MIMETYPE_VIDEO_H263); 561 codecSelKeyMimeMap.put("mpeg2", MediaFormat.MIMETYPE_VIDEO_MPEG2); 562 codecSelKeyMimeMap.put("vraw", MediaFormat.MIMETYPE_VIDEO_RAW); 563 codecSelKeyMimeMap.put("amrnb", MediaFormat.MIMETYPE_AUDIO_AMR_NB); 564 codecSelKeyMimeMap.put("amrwb", MediaFormat.MIMETYPE_AUDIO_AMR_WB); 565 codecSelKeyMimeMap.put("mp3", MediaFormat.MIMETYPE_AUDIO_MPEG); 566 codecSelKeyMimeMap.put("aac", MediaFormat.MIMETYPE_AUDIO_AAC); 567 codecSelKeyMimeMap.put("vorbis", MediaFormat.MIMETYPE_AUDIO_VORBIS); 568 codecSelKeyMimeMap.put("opus", MediaFormat.MIMETYPE_AUDIO_OPUS); 569 codecSelKeyMimeMap.put("g711alaw", MediaFormat.MIMETYPE_AUDIO_G711_ALAW); 570 codecSelKeyMimeMap.put("g711mlaw", MediaFormat.MIMETYPE_AUDIO_G711_MLAW); 571 codecSelKeyMimeMap.put("araw", MediaFormat.MIMETYPE_AUDIO_RAW); 572 codecSelKeyMimeMap.put("flac", MediaFormat.MIMETYPE_AUDIO_FLAC); 573 codecSelKeyMimeMap.put("gsm", MediaFormat.MIMETYPE_AUDIO_MSGSM); 574 575 android.os.Bundle args = InstrumentationRegistry.getArguments(); 576 mimeSelKeys = args.getString(MIME_SEL_KEY); 577 codecPrefix = args.getString(CODEC_PREFIX_KEY); 578 } 579 isTv()580 static boolean isTv() { 581 return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); 582 } 583 hasMicrophone()584 static boolean hasMicrophone() { 585 return pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE); 586 } 587 hasCamera()588 static boolean hasCamera() { 589 return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); 590 } 591 isWatch()592 static boolean isWatch() { 593 return pm.hasSystemFeature(PackageManager.FEATURE_WATCH); 594 } 595 isAutomotive()596 static boolean isAutomotive() { 597 return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 598 } 599 isPc()600 static boolean isPc() { 601 return pm.hasSystemFeature(PackageManager.FEATURE_PC); 602 } 603 hasAudioOutput()604 static boolean hasAudioOutput() { 605 return pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT); 606 } 607 isHandheld()608 static boolean isHandheld() { 609 // handheld nature is not exposed to package manager, for now 610 // we check for touchscreen and NOT watch and NOT tv and NOT pc 611 return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) && !isWatch() && !isTv() && 612 !isAutomotive() && !isPc(); 613 } 614 hasDecoder(String mime)615 static boolean hasDecoder(String mime) { 616 return CodecTestBase.selectCodecs(mime, null, null, false).size() != 0; 617 } 618 hasEncoder(String mime)619 static boolean hasEncoder(String mime) { 620 return CodecTestBase.selectCodecs(mime, null, null, true).size() != 0; 621 } 622 isFeatureSupported(String name, String mime, String feature)623 static boolean isFeatureSupported(String name, String mime, String feature) throws IOException { 624 MediaCodec codec = MediaCodec.createByCodecName(name); 625 MediaCodecInfo.CodecCapabilities codecCapabilities = 626 codec.getCodecInfo().getCapabilitiesForType(mime); 627 boolean isSupported = codecCapabilities.isFeatureSupported(feature); 628 codec.release(); 629 return isSupported; 630 } 631 areFormatsSupported(String name, String mime, ArrayList<MediaFormat> formats)632 static boolean areFormatsSupported(String name, String mime, ArrayList<MediaFormat> formats) 633 throws IOException { 634 MediaCodec codec = MediaCodec.createByCodecName(name); 635 MediaCodecInfo.CodecCapabilities codecCapabilities = 636 codec.getCodecInfo().getCapabilitiesForType(mime); 637 boolean isSupported = true; 638 if (formats != null) { 639 for (int i = 0; i < formats.size() && isSupported; i++) { 640 isSupported = codecCapabilities.isFormatSupported(formats.get(i)); 641 } 642 } 643 codec.release(); 644 return isSupported; 645 } 646 compileRequiredMimeList(boolean isEncoder, boolean needAudio, boolean needVideo)647 static ArrayList<String> compileRequiredMimeList(boolean isEncoder, boolean needAudio, 648 boolean needVideo) { 649 Set<String> list = new HashSet<>(); 650 if (!isEncoder) { 651 if (hasAudioOutput() && needAudio) { 652 // sec 5.1.2 653 list.add(MediaFormat.MIMETYPE_AUDIO_AAC); 654 list.add(MediaFormat.MIMETYPE_AUDIO_FLAC); 655 list.add(MediaFormat.MIMETYPE_AUDIO_MPEG); 656 list.add(MediaFormat.MIMETYPE_AUDIO_VORBIS); 657 list.add(MediaFormat.MIMETYPE_AUDIO_RAW); 658 list.add(MediaFormat.MIMETYPE_AUDIO_OPUS); 659 } 660 if (isHandheld() || isTv() || isAutomotive()) { 661 // sec 2.2.2, 2.3.2, 2.5.2 662 if (needAudio) { 663 list.add(MediaFormat.MIMETYPE_AUDIO_AAC); 664 } 665 if (needVideo) { 666 list.add(MediaFormat.MIMETYPE_VIDEO_AVC); 667 list.add(MediaFormat.MIMETYPE_VIDEO_MPEG4); 668 list.add(MediaFormat.MIMETYPE_VIDEO_H263); 669 list.add(MediaFormat.MIMETYPE_VIDEO_VP8); 670 list.add(MediaFormat.MIMETYPE_VIDEO_VP9); 671 } 672 } 673 if (isHandheld()) { 674 // sec 2.2.2 675 if (needAudio) { 676 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB); 677 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB); 678 } 679 if (needVideo) { 680 list.add(MediaFormat.MIMETYPE_VIDEO_HEVC); 681 } 682 } 683 if (isTv() && needVideo) { 684 // sec 2.3.2 685 list.add(MediaFormat.MIMETYPE_VIDEO_HEVC); 686 list.add(MediaFormat.MIMETYPE_VIDEO_MPEG2); 687 } 688 } else { 689 if (hasMicrophone() && needAudio) { 690 // sec 5.1.1 691 // TODO(b/154423550) 692 // list.add(MediaFormat.MIMETYPE_AUDIO_RAW); 693 list.add(MediaFormat.MIMETYPE_AUDIO_FLAC); 694 list.add(MediaFormat.MIMETYPE_AUDIO_OPUS); 695 } 696 if (isHandheld() || isTv() || isAutomotive()) { 697 // sec 2.2.2, 2.3.2, 2.5.2 698 if (needAudio) { 699 list.add(MediaFormat.MIMETYPE_AUDIO_AAC); 700 } 701 if (needVideo) { 702 list.add(MediaFormat.MIMETYPE_VIDEO_AVC); 703 list.add(MediaFormat.MIMETYPE_VIDEO_VP8); 704 } 705 } 706 if (isHandheld() && needAudio) { 707 // sec 2.2.2 708 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB); 709 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB); 710 } 711 } 712 return new ArrayList<>(list); 713 } 714 compileCompleteTestMimeList(boolean isEncoder, boolean needAudio, boolean needVideo)715 static ArrayList<String> compileCompleteTestMimeList(boolean isEncoder, boolean needAudio, 716 boolean needVideo) { 717 ArrayList<String> mimes = new ArrayList<>(); 718 if (mimeSelKeys == null) { 719 ArrayList<String> cddRequiredMimeList = 720 compileRequiredMimeList(isEncoder, needAudio, needVideo); 721 MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 722 MediaCodecInfo[] codecInfos = codecList.getCodecInfos(); 723 for (MediaCodecInfo codecInfo : codecInfos) { 724 if (codecInfo.isEncoder() != isEncoder) continue; 725 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue; 726 String[] types = codecInfo.getSupportedTypes(); 727 for (String type : types) { 728 if (!needAudio && type.startsWith("audio/")) continue; 729 if (!needVideo && type.startsWith("video/")) continue; 730 if (!mimes.contains(type)) { 731 mimes.add(type); 732 } 733 } 734 } 735 // TODO(b/154423708): add checks for video o/p port and display length >= 2.5" 736 /* sec 5.2: device implementations include an embedded screen display with the 737 diagonal length of at least 2.5inches or include a video output port or declare the 738 support of a camera */ 739 if (isEncoder && hasCamera() && needVideo && 740 !mimes.contains(MediaFormat.MIMETYPE_VIDEO_AVC) && 741 !mimes.contains(MediaFormat.MIMETYPE_VIDEO_VP8)) { 742 // Add required cdd mimes here so that respective codec tests fail. 743 mimes.add(MediaFormat.MIMETYPE_VIDEO_AVC); 744 mimes.add(MediaFormat.MIMETYPE_VIDEO_VP8); 745 Log.e(LOG_TAG,"device must support at least one of VP8 or AVC video encoders"); 746 } 747 for (String mime : cddRequiredMimeList) { 748 if (!mimes.contains(mime)) { 749 // Add required cdd mimes here so that respective codec tests fail. 750 mimes.add(mime); 751 Log.e(LOG_TAG, "no codec found for mime " + mime + " as required by cdd"); 752 } 753 } 754 } else { 755 for (Map.Entry<String, String> entry : codecSelKeyMimeMap.entrySet()) { 756 String key = entry.getKey(); 757 String value = entry.getValue(); 758 if (mimeSelKeys.contains(key) && !mimes.contains(value)) mimes.add(value); 759 } 760 } 761 return mimes; 762 } 763 prepareParamList(List<Object[]> exhaustiveArgsList, boolean isEncoder, boolean needAudio, boolean needVideo, boolean mustTestAllCodecs)764 static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList, boolean isEncoder, 765 boolean needAudio, boolean needVideo, boolean mustTestAllCodecs) { 766 ArrayList<String> mimes = compileCompleteTestMimeList(isEncoder, needAudio, needVideo); 767 ArrayList<String> cddRequiredMimeList = 768 compileRequiredMimeList(isEncoder, needAudio, needVideo); 769 final List<Object[]> argsList = new ArrayList<>(); 770 int argLength = exhaustiveArgsList.get(0).length; 771 for (String mime : mimes) { 772 ArrayList<String> totalListOfCodecs = selectCodecs(mime, null, null, isEncoder); 773 ArrayList<String> listOfCodecs = new ArrayList<>(); 774 if (codecPrefix != null) { 775 for (String codec : totalListOfCodecs) { 776 if (codec.startsWith(codecPrefix)) { 777 listOfCodecs.add(codec); 778 } 779 } 780 } else { 781 listOfCodecs = totalListOfCodecs; 782 } 783 if (mustTestAllCodecs && listOfCodecs.size() == 0 && codecPrefix == null) { 784 listOfCodecs.add(INVALID_CODEC + mime); 785 } 786 boolean miss = true; 787 for (Object[] arg : exhaustiveArgsList) { 788 if (mime.equals(arg[0])) { 789 for (String codec : listOfCodecs) { 790 Object[] arg_ = new Object[argLength + 1]; 791 arg_[0] = codec; 792 System.arraycopy(arg, 0, arg_, 1, argLength); 793 argsList.add(arg_); 794 } 795 miss = false; 796 } 797 } 798 if (miss && mustTestAllCodecs) { 799 if (!cddRequiredMimeList.contains(mime)) { 800 Log.w(LOG_TAG, "no test vectors available for optional mime type " + mime); 801 continue; 802 } 803 for (String codec : listOfCodecs) { 804 Object[] arg_ = new Object[argLength + 1]; 805 arg_[0] = codec; 806 arg_[1] = mime; 807 System.arraycopy(exhaustiveArgsList.get(0), 1, arg_, 2, argLength - 1); 808 argsList.add(arg_); 809 } 810 } 811 } 812 return argsList; 813 } 814 enqueueInput(int bufferIndex)815 abstract void enqueueInput(int bufferIndex) throws IOException; 816 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)817 abstract void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info); 818 configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)819 void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, 820 boolean isEncoder) { 821 resetContext(isAsync, signalEOSWithLastFrame); 822 mAsyncHandle.setCallBack(mCodec, isAsync); 823 // signalEOS flag has nothing to do with configure. We are using this flag to try all 824 // available configure apis 825 if (signalEOSWithLastFrame) { 826 mCodec.configure(format, mSurface, null, 827 isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0); 828 } else { 829 mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0, 830 null); 831 } 832 if (ENABLE_LOGS) { 833 Log.v(LOG_TAG, "codec configured"); 834 } 835 } 836 flushCodec()837 void flushCodec() { 838 mCodec.flush(); 839 // TODO(b/147576107): is it ok to clearQueues right away or wait for some signal 840 mAsyncHandle.clearQueues(); 841 mSawInputEOS = false; 842 mSawOutputEOS = false; 843 mInputCount = 0; 844 mOutputCount = 0; 845 mPrevOutputPts = Long.MIN_VALUE; 846 if (ENABLE_LOGS) { 847 Log.v(LOG_TAG, "codec flushed"); 848 } 849 } 850 reConfigureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)851 void reConfigureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, 852 boolean isEncoder) { 853 /* TODO(b/147348711) */ 854 if (false) mCodec.stop(); 855 else mCodec.reset(); 856 configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder); 857 } 858 resetContext(boolean isAsync, boolean signalEOSWithLastFrame)859 void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 860 mAsyncHandle.resetContext(); 861 mIsCodecInAsyncMode = isAsync; 862 mSawInputEOS = false; 863 mSawOutputEOS = false; 864 mSignalEOSWithLastFrame = signalEOSWithLastFrame; 865 mInputCount = 0; 866 mOutputCount = 0; 867 mPrevOutputPts = Long.MIN_VALUE; 868 mSignalledOutFormatChanged = false; 869 } 870 enqueueEOS(int bufferIndex)871 void enqueueEOS(int bufferIndex) { 872 if (!mSawInputEOS) { 873 mCodec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 874 mSawInputEOS = true; 875 if (ENABLE_LOGS) { 876 Log.v(LOG_TAG, "Queued End of Stream"); 877 } 878 } 879 } 880 doWork(int frameLimit)881 void doWork(int frameLimit) throws InterruptedException, IOException { 882 int frameCount = 0; 883 if (mIsCodecInAsyncMode) { 884 // dequeue output after inputEOS is expected to be done in waitForAllOutputs() 885 while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < frameLimit) { 886 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); 887 if (element != null) { 888 int bufferID = element.first; 889 MediaCodec.BufferInfo info = element.second; 890 if (info != null) { 891 // <id, info> corresponds to output callback. Handle it accordingly 892 dequeueOutput(bufferID, info); 893 } else { 894 // <id, null> corresponds to input callback. Handle it accordingly 895 enqueueInput(bufferID); 896 frameCount++; 897 } 898 } 899 } 900 } else { 901 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 902 // dequeue output after inputEOS is expected to be done in waitForAllOutputs() 903 while (!mSawInputEOS && frameCount < frameLimit) { 904 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 905 if (outputBufferId >= 0) { 906 dequeueOutput(outputBufferId, outInfo); 907 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 908 mOutFormat = mCodec.getOutputFormat(); 909 mSignalledOutFormatChanged = true; 910 } 911 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); 912 if (inputBufferId != -1) { 913 enqueueInput(inputBufferId); 914 frameCount++; 915 } 916 } 917 } 918 } 919 queueEOS()920 void queueEOS() throws InterruptedException { 921 if (mIsCodecInAsyncMode) { 922 while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) { 923 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); 924 if (element != null) { 925 int bufferID = element.first; 926 MediaCodec.BufferInfo info = element.second; 927 if (info != null) { 928 dequeueOutput(bufferID, info); 929 } else { 930 enqueueEOS(element.first); 931 } 932 } 933 } 934 } else { 935 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 936 while (!mSawInputEOS) { 937 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 938 if (outputBufferId >= 0) { 939 dequeueOutput(outputBufferId, outInfo); 940 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 941 mOutFormat = mCodec.getOutputFormat(); 942 mSignalledOutFormatChanged = true; 943 } 944 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); 945 if (inputBufferId != -1) { 946 enqueueEOS(inputBufferId); 947 } 948 } 949 } 950 } 951 waitForAllOutputs()952 void waitForAllOutputs() throws InterruptedException { 953 if (mIsCodecInAsyncMode) { 954 while (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) { 955 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput(); 956 if (element != null) { 957 dequeueOutput(element.first, element.second); 958 } 959 } 960 } else { 961 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 962 while (!mSawOutputEOS) { 963 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 964 if (outputBufferId >= 0) { 965 dequeueOutput(outputBufferId, outInfo); 966 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 967 mOutFormat = mCodec.getOutputFormat(); 968 mSignalledOutFormatChanged = true; 969 } 970 } 971 } 972 } 973 selectCodecs(String mime, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder)974 static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats, 975 String[] features, boolean isEncoder) { 976 MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 977 MediaCodecInfo[] codecInfos = codecList.getCodecInfos(); 978 ArrayList<String> listOfCodecs = new ArrayList<>(); 979 for (MediaCodecInfo codecInfo : codecInfos) { 980 if (codecInfo.isEncoder() != isEncoder) continue; 981 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue; 982 String[] types = codecInfo.getSupportedTypes(); 983 for (String type : types) { 984 if (type.equalsIgnoreCase(mime)) { 985 boolean isOk = true; 986 MediaCodecInfo.CodecCapabilities codecCapabilities = 987 codecInfo.getCapabilitiesForType(type); 988 if (formats != null) { 989 for (MediaFormat format : formats) { 990 if (!codecCapabilities.isFormatSupported(format)) { 991 isOk = false; 992 break; 993 } 994 } 995 } 996 if (features != null) { 997 for (String feature : features) { 998 if (!codecCapabilities.isFeatureSupported(feature)) { 999 isOk = false; 1000 break; 1001 } 1002 } 1003 } 1004 if (isOk) listOfCodecs.add(codecInfo.getName()); 1005 } 1006 } 1007 } 1008 return listOfCodecs; 1009 } 1010 getWidth(MediaFormat format)1011 static int getWidth(MediaFormat format) { 1012 int width = format.getInteger(MediaFormat.KEY_WIDTH, -1); 1013 if (format.containsKey("crop-left") && format.containsKey("crop-right")) { 1014 width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left"); 1015 } 1016 return width; 1017 } 1018 getHeight(MediaFormat format)1019 static int getHeight(MediaFormat format) { 1020 int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1); 1021 if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) { 1022 height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top"); 1023 } 1024 return height; 1025 } 1026 isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat)1027 boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) { 1028 if (inpFormat == null || outFormat == null) return false; 1029 String inpMime = inpFormat.getString(MediaFormat.KEY_MIME); 1030 String outMime = outFormat.getString(MediaFormat.KEY_MIME); 1031 // not comparing input and output mimes because for a codec, mime is raw on one side and 1032 // encoded type on the other 1033 if (outMime.startsWith("audio/")) { 1034 return inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1) == 1035 outFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -2) && 1036 inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1) == 1037 outFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -2) && 1038 inpMime.startsWith("audio/"); 1039 } else if (outMime.startsWith("video/")) { 1040 return getWidth(inpFormat) == getWidth(outFormat) && 1041 getHeight(inpFormat) == getHeight(outFormat) && inpMime.startsWith("video/"); 1042 } 1043 return true; 1044 } 1045 validateMetrics(String codec)1046 PersistableBundle validateMetrics(String codec) { 1047 PersistableBundle metrics = mCodec.getMetrics(); 1048 assertTrue("metrics is null", metrics != null); 1049 assertTrue(metrics.getString(MediaCodec.MetricsConstants.CODEC).equals(codec)); 1050 if (mIsAudio) { 1051 assertTrue(metrics.getString(MediaCodec.MetricsConstants.MODE) 1052 .equals(MediaCodec.MetricsConstants.MODE_AUDIO)); 1053 } else { 1054 assertTrue(metrics.getString(MediaCodec.MetricsConstants.MODE) 1055 .equals(MediaCodec.MetricsConstants.MODE_VIDEO)); 1056 } 1057 return metrics; 1058 } 1059 validateMetrics(String codec, MediaFormat format)1060 PersistableBundle validateMetrics(String codec, MediaFormat format) { 1061 PersistableBundle metrics = validateMetrics(codec); 1062 if (!mIsAudio) { 1063 assertTrue(metrics.getInt(MediaCodec.MetricsConstants.WIDTH) == getWidth(format)); 1064 assertTrue(metrics.getInt(MediaCodec.MetricsConstants.HEIGHT) == getHeight(format)); 1065 } 1066 assertTrue(metrics.getInt(MediaCodec.MetricsConstants.SECURE) == 0); 1067 return metrics; 1068 } 1069 validateColorAspects(MediaFormat fmt, int range, int standard, int transfer)1070 void validateColorAspects(MediaFormat fmt, int range, int standard, int transfer) { 1071 int colorRange = fmt.getInteger(MediaFormat.KEY_COLOR_RANGE, UNSPECIFIED); 1072 int colorStandard = fmt.getInteger(MediaFormat.KEY_COLOR_STANDARD, UNSPECIFIED); 1073 int colorTransfer = fmt.getInteger(MediaFormat.KEY_COLOR_TRANSFER, UNSPECIFIED); 1074 if (range > UNSPECIFIED) { 1075 assertEquals("color range mismatch ", range, colorRange); 1076 } 1077 if (standard > UNSPECIFIED) { 1078 assertEquals("color standard mismatch ", standard, colorStandard); 1079 } 1080 if (transfer > UNSPECIFIED) { 1081 assertEquals("color transfer mismatch ", transfer, colorTransfer); 1082 } 1083 } 1084 setUpSurface(CodecTestActivity activity)1085 public void setUpSurface(CodecTestActivity activity) throws InterruptedException { 1086 activity.waitTillSurfaceIsCreated(); 1087 mSurface = activity.getSurface(); 1088 assertTrue("Surface created is null.", mSurface != null); 1089 assertTrue("Surface created is invalid.", mSurface.isValid()); 1090 } 1091 tearDownSurface()1092 public void tearDownSurface() { 1093 if (mSurface != null) { 1094 mSurface.release(); 1095 mSurface = null; 1096 } 1097 } 1098 1099 @Before isCodecNameValid()1100 public void isCodecNameValid() { 1101 if (mCodecName != null && mCodecName.startsWith(INVALID_CODEC)) { 1102 fail("no valid component available for current test "); 1103 } 1104 } 1105 } 1106 1107 class CodecDecoderTestBase extends CodecTestBase { 1108 private static final String LOG_TAG = CodecDecoderTestBase.class.getSimpleName(); 1109 1110 String mMime; 1111 String mTestFile; 1112 boolean mIsInterlaced; 1113 1114 ArrayList<ByteBuffer> mCsdBuffers; 1115 private int mCurrCsdIdx; 1116 1117 private ByteBuffer flatBuffer = ByteBuffer.allocate(4 * Integer.BYTES); 1118 1119 MediaExtractor mExtractor; 1120 CodecDecoderTestBase(String codecName, String mime, String testFile)1121 CodecDecoderTestBase(String codecName, String mime, String testFile) { 1122 mCodecName = codecName; 1123 mMime = mime; 1124 mTestFile = testFile; 1125 mAsyncHandle = new CodecAsyncHandler(); 1126 mCsdBuffers = new ArrayList<>(); 1127 mIsAudio = mMime.startsWith("audio/"); 1128 } 1129 setUpSource(String srcFile)1130 MediaFormat setUpSource(String srcFile) throws IOException { 1131 return setUpSource(mInpPrefix, srcFile); 1132 } 1133 setUpSource(String prefix, String srcFile)1134 MediaFormat setUpSource(String prefix, String srcFile) throws IOException { 1135 mExtractor = new MediaExtractor(); 1136 mExtractor.setDataSource(prefix + srcFile); 1137 for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) { 1138 MediaFormat format = mExtractor.getTrackFormat(trackID); 1139 if (mMime.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) { 1140 mExtractor.selectTrack(trackID); 1141 if (!mIsAudio) { 1142 if (mSurface == null) { 1143 // COLOR_FormatYUV420Flexible must be supported by all components 1144 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible); 1145 } else { 1146 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatSurface); 1147 } 1148 } 1149 // TODO: determine this from the extractor format when it becomes exposed. 1150 mIsInterlaced = srcFile.contains("_interlaced_"); 1151 return format; 1152 } 1153 } 1154 fail("No track with mime: " + mMime + " found in file: " + srcFile); 1155 return null; 1156 } 1157 hasCSD(MediaFormat format)1158 boolean hasCSD(MediaFormat format) { 1159 return format.containsKey("csd-0"); 1160 } 1161 flattenBufferInfo(MediaCodec.BufferInfo info, boolean isAudio)1162 void flattenBufferInfo(MediaCodec.BufferInfo info, boolean isAudio) { 1163 if (isAudio) { 1164 flatBuffer.putInt(info.size); 1165 } 1166 flatBuffer.putInt(info.flags & ~MediaCodec.BUFFER_FLAG_END_OF_STREAM) 1167 .putLong(info.presentationTimeUs); 1168 flatBuffer.flip(); 1169 } 1170 enqueueCodecConfig(int bufferIndex)1171 void enqueueCodecConfig(int bufferIndex) { 1172 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 1173 ByteBuffer csdBuffer = mCsdBuffers.get(mCurrCsdIdx); 1174 inputBuffer.put((ByteBuffer) csdBuffer.rewind()); 1175 mCodec.queueInputBuffer(bufferIndex, 0, csdBuffer.limit(), 0, 1176 MediaCodec.BUFFER_FLAG_CODEC_CONFIG); 1177 if (ENABLE_LOGS) { 1178 Log.v(LOG_TAG, "queued csd: id: " + bufferIndex + " size: " + csdBuffer.limit()); 1179 } 1180 } 1181 enqueueInput(int bufferIndex)1182 void enqueueInput(int bufferIndex) { 1183 if (mExtractor.getSampleSize() < 0) { 1184 enqueueEOS(bufferIndex); 1185 } else { 1186 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 1187 mExtractor.readSampleData(inputBuffer, 0); 1188 int size = (int) mExtractor.getSampleSize(); 1189 long pts = mExtractor.getSampleTime(); 1190 int extractorFlags = mExtractor.getSampleFlags(); 1191 int codecFlags = 0; 1192 if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) { 1193 codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME; 1194 } 1195 if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) { 1196 codecFlags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME; 1197 } 1198 if (!mExtractor.advance() && mSignalEOSWithLastFrame) { 1199 codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 1200 mSawInputEOS = true; 1201 } 1202 if (ENABLE_LOGS) { 1203 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts + 1204 " flags: " + codecFlags); 1205 } 1206 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags); 1207 if (size > 0 && (codecFlags & (MediaCodec.BUFFER_FLAG_CODEC_CONFIG | 1208 MediaCodec.BUFFER_FLAG_PARTIAL_FRAME)) == 0) { 1209 mOutputBuff.saveInPTS(pts); 1210 mInputCount++; 1211 } 1212 } 1213 } 1214 enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info)1215 void enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info) { 1216 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 1217 buffer.position(info.offset); 1218 for (int i = 0; i < info.size; i++) { 1219 inputBuffer.put(buffer.get()); 1220 } 1221 if (ENABLE_LOGS) { 1222 Log.v(LOG_TAG, "input: id: " + bufferIndex + " flags: " + info.flags + " size: " + 1223 info.size + " timestamp: " + info.presentationTimeUs); 1224 } 1225 mCodec.queueInputBuffer(bufferIndex, 0, info.size, info.presentationTimeUs, 1226 info.flags); 1227 if (info.size > 0 && ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) && 1228 ((info.flags & MediaCodec.BUFFER_FLAG_PARTIAL_FRAME) == 0)) { 1229 mOutputBuff.saveInPTS(info.presentationTimeUs); 1230 mInputCount++; 1231 } 1232 } 1233 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)1234 void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 1235 if (info.size > 0 && mSaveToMem) { 1236 ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex); 1237 flattenBufferInfo(info, mIsAudio); 1238 mOutputBuff.checksum(flatBuffer, flatBuffer.limit()); 1239 if (mIsAudio) { 1240 mOutputBuff.checksum(buf, info.size); 1241 mOutputBuff.saveToMemory(buf, info); 1242 } else { 1243 // tests both getOutputImage and getOutputBuffer. Can do time division 1244 // multiplexing but lets allow it for now 1245 MediaFormat format = mCodec.getOutputFormat(); 1246 int width = format.getInteger(MediaFormat.KEY_WIDTH); 1247 int height = format.getInteger(MediaFormat.KEY_HEIGHT); 1248 int stride = format.getInteger(MediaFormat.KEY_STRIDE); 1249 mOutputBuff.checksum(buf, info.size, width, height, stride); 1250 1251 Image img = mCodec.getOutputImage(bufferIndex); 1252 assertTrue(img != null); 1253 mOutputBuff.checksum(img); 1254 } 1255 } 1256 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 1257 mSawOutputEOS = true; 1258 } 1259 if (ENABLE_LOGS) { 1260 Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " + 1261 info.size + " timestamp: " + info.presentationTimeUs); 1262 } 1263 if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 1264 mOutputBuff.saveOutPTS(info.presentationTimeUs); 1265 mOutputCount++; 1266 } 1267 mCodec.releaseOutputBuffer(bufferIndex, false); 1268 } 1269 doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list)1270 void doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list) 1271 throws InterruptedException { 1272 int frameCount = 0; 1273 if (mIsCodecInAsyncMode) { 1274 // output processing after queuing EOS is done in waitForAllOutputs() 1275 while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < list.size()) { 1276 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); 1277 if (element != null) { 1278 int bufferID = element.first; 1279 MediaCodec.BufferInfo info = element.second; 1280 if (info != null) { 1281 dequeueOutput(bufferID, info); 1282 } else { 1283 enqueueInput(bufferID, buffer, list.get(frameCount)); 1284 frameCount++; 1285 } 1286 } 1287 } 1288 } else { 1289 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 1290 // output processing after queuing EOS is done in waitForAllOutputs() 1291 while (!mSawInputEOS && frameCount < list.size()) { 1292 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 1293 if (outputBufferId >= 0) { 1294 dequeueOutput(outputBufferId, outInfo); 1295 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 1296 mOutFormat = mCodec.getOutputFormat(); 1297 mSignalledOutFormatChanged = true; 1298 } 1299 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); 1300 if (inputBufferId != -1) { 1301 enqueueInput(inputBufferId, buffer, list.get(frameCount)); 1302 frameCount++; 1303 } 1304 } 1305 } 1306 } 1307 queueCodecConfig()1308 void queueCodecConfig() throws InterruptedException { 1309 if (mIsCodecInAsyncMode) { 1310 for (mCurrCsdIdx = 0; !mAsyncHandle.hasSeenError() && mCurrCsdIdx < mCsdBuffers.size(); 1311 mCurrCsdIdx++) { 1312 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput(); 1313 if (element != null) { 1314 enqueueCodecConfig(element.first); 1315 } 1316 } 1317 } else { 1318 for (mCurrCsdIdx = 0; mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) { 1319 enqueueCodecConfig(mCodec.dequeueInputBuffer(-1)); 1320 } 1321 } 1322 } 1323 decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit)1324 void decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit) 1325 throws IOException, InterruptedException { 1326 mSaveToMem = true; 1327 mOutputBuff = new OutputManager(); 1328 mCodec = MediaCodec.createByCodecName(decoder); 1329 MediaFormat format = setUpSource(file); 1330 configureCodec(format, false, true, false); 1331 mCodec.start(); 1332 mExtractor.seekTo(pts, mode); 1333 doWork(frameLimit); 1334 queueEOS(); 1335 waitForAllOutputs(); 1336 mCodec.stop(); 1337 mCodec.release(); 1338 mExtractor.release(); 1339 mSaveToMem = false; 1340 } 1341 1342 @Override validateMetrics(String decoder, MediaFormat format)1343 PersistableBundle validateMetrics(String decoder, MediaFormat format) { 1344 PersistableBundle metrics = super.validateMetrics(decoder, format); 1345 assertTrue(metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE).equals(mMime)); 1346 assertTrue(metrics.getInt(MediaCodec.MetricsConstants.ENCODER) == 0); 1347 return metrics; 1348 } 1349 validateColorAspects(String decoder, String parent, String name, int range, int standard, int transfer, boolean ignoreColorBox)1350 void validateColorAspects(String decoder, String parent, String name, int range, int standard, 1351 int transfer, boolean ignoreColorBox) 1352 throws IOException, InterruptedException { 1353 mOutputBuff = new OutputManager(); 1354 MediaFormat format = setUpSource(parent, name); 1355 if (ignoreColorBox) { 1356 format.removeKey(MediaFormat.KEY_COLOR_RANGE); 1357 format.removeKey(MediaFormat.KEY_COLOR_STANDARD); 1358 format.removeKey(MediaFormat.KEY_COLOR_TRANSFER); 1359 } 1360 if (decoder == null) { 1361 MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 1362 decoder = codecList.findDecoderForFormat(format); 1363 } 1364 mCodec = MediaCodec.createByCodecName(decoder); 1365 configureCodec(format, true, true, false); 1366 mCodec.start(); 1367 doWork(1); 1368 queueEOS(); 1369 waitForAllOutputs(); 1370 validateColorAspects(mCodec.getOutputFormat(), range, standard, transfer); 1371 mCodec.stop(); 1372 mCodec.release(); 1373 mExtractor.release(); 1374 } 1375 } 1376 1377 class CodecEncoderTestBase extends CodecTestBase { 1378 private static final String LOG_TAG = CodecEncoderTestBase.class.getSimpleName(); 1379 1380 // files are in WorkDir.getMediaDirString(); 1381 private static final String mInputAudioFile = "bbb_2ch_44kHz_s16le.raw"; 1382 private static final String mInputVideoFile = "bbb_cif_yuv420p_30fps.yuv"; 1383 private final int INP_FRM_WIDTH = 352; 1384 private final int INP_FRM_HEIGHT = 288; 1385 1386 final String mMime; 1387 final int[] mBitrates; 1388 final int[] mEncParamList1; 1389 final int[] mEncParamList2; 1390 1391 final String mInputFile; 1392 byte[] mInputData; 1393 int mNumBytesSubmitted; 1394 long mInputOffsetPts; 1395 1396 ArrayList<MediaFormat> mFormats; 1397 ArrayList<MediaCodec.BufferInfo> mInfoList; 1398 1399 int mWidth, mHeight; 1400 int mFrameRate; 1401 int mMaxBFrames; 1402 int mChannels; 1403 int mSampleRate; 1404 CodecEncoderTestBase(String encoder, String mime, int[] bitrates, int[] encoderInfo1, int[] encoderInfo2)1405 CodecEncoderTestBase(String encoder, String mime, int[] bitrates, int[] encoderInfo1, 1406 int[] encoderInfo2) { 1407 mMime = mime; 1408 mCodecName = encoder; 1409 mBitrates = bitrates; 1410 mEncParamList1 = encoderInfo1; 1411 mEncParamList2 = encoderInfo2; 1412 mFormats = new ArrayList<>(); 1413 mInfoList = new ArrayList<>(); 1414 mWidth = INP_FRM_WIDTH; 1415 mHeight = INP_FRM_HEIGHT; 1416 if (mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) mFrameRate = 12; 1417 else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_H263)) mFrameRate = 12; 1418 else mFrameRate = 30; 1419 mMaxBFrames = 0; 1420 mChannels = 1; 1421 mSampleRate = 8000; 1422 mAsyncHandle = new CodecAsyncHandler(); 1423 mIsAudio = mMime.startsWith("audio/"); 1424 mInputFile = mIsAudio ? mInputAudioFile : mInputVideoFile; 1425 } 1426 1427 /** 1428 * Selects encoder input color format in byte buffer mode. As of now ndk tests support only 1429 * 420p, 420sp. COLOR_FormatYUV420Flexible although can represent any form of yuv, it doesn't 1430 * work in ndk due to lack of AMediaCodec_GetInputImage() 1431 */ findByteBufferColorFormat(String encoder, String mime)1432 static int findByteBufferColorFormat(String encoder, String mime) throws IOException { 1433 MediaCodec codec = MediaCodec.createByCodecName(encoder); 1434 MediaCodecInfo.CodecCapabilities cap = codec.getCodecInfo().getCapabilitiesForType(mime); 1435 int colorFormat = -1; 1436 for (int c : cap.colorFormats) { 1437 if (c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar || 1438 c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) { 1439 Log.v(LOG_TAG, "selecting color format: " + c); 1440 colorFormat = c; 1441 break; 1442 } 1443 } 1444 codec.release(); 1445 return colorFormat; 1446 } 1447 1448 @Override resetContext(boolean isAsync, boolean signalEOSWithLastFrame)1449 void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 1450 super.resetContext(isAsync, signalEOSWithLastFrame); 1451 mNumBytesSubmitted = 0; 1452 mInputOffsetPts = 0; 1453 } 1454 1455 @Override flushCodec()1456 void flushCodec() { 1457 super.flushCodec(); 1458 if (mIsAudio) { 1459 mInputOffsetPts = 1460 (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mSampleRate); 1461 } else { 1462 mInputOffsetPts = (mInputCount + 5) * 1000000L / mFrameRate; 1463 } 1464 mPrevOutputPts = mInputOffsetPts - 1; 1465 mNumBytesSubmitted = 0; 1466 } 1467 setUpSource(String srcFile)1468 void setUpSource(String srcFile) throws IOException { 1469 String inpPath = mInpPrefix + srcFile; 1470 try (FileInputStream fInp = new FileInputStream(inpPath)) { 1471 int size = (int) new File(inpPath).length(); 1472 mInputData = new byte[size]; 1473 fInp.read(mInputData, 0, size); 1474 } 1475 } 1476 fillImage(Image image)1477 void fillImage(Image image) { 1478 Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888); 1479 int imageWidth = image.getWidth(); 1480 int imageHeight = image.getHeight(); 1481 Image.Plane[] planes = image.getPlanes(); 1482 int offset = mNumBytesSubmitted; 1483 for (int i = 0; i < planes.length; ++i) { 1484 ByteBuffer buf = planes[i].getBuffer(); 1485 int width = imageWidth; 1486 int height = imageHeight; 1487 int tileWidth = INP_FRM_WIDTH; 1488 int tileHeight = INP_FRM_HEIGHT; 1489 int rowStride = planes[i].getRowStride(); 1490 int pixelStride = planes[i].getPixelStride(); 1491 if (i != 0) { 1492 width = imageWidth / 2; 1493 height = imageHeight / 2; 1494 tileWidth = INP_FRM_WIDTH / 2; 1495 tileHeight = INP_FRM_HEIGHT / 2; 1496 } 1497 if (pixelStride == 1) { 1498 if (width == rowStride && width == tileWidth && height == tileHeight) { 1499 buf.put(mInputData, offset, width * height); 1500 } else { 1501 for (int z = 0; z < height; z += tileHeight) { 1502 int rowsToCopy = Math.min(height - z, tileHeight); 1503 for (int y = 0; y < rowsToCopy; y++) { 1504 for (int x = 0; x < width; x += tileWidth) { 1505 int colsToCopy = Math.min(width - x, tileWidth); 1506 buf.position((z + y) * rowStride + x); 1507 buf.put(mInputData, offset + y * tileWidth, colsToCopy); 1508 } 1509 } 1510 } 1511 } 1512 } else { 1513 // do it pixel-by-pixel 1514 for (int z = 0; z < height; z += tileHeight) { 1515 int rowsToCopy = Math.min(height - z, tileHeight); 1516 for (int y = 0; y < rowsToCopy; y++) { 1517 int lineOffset = (z + y) * rowStride; 1518 for (int x = 0; x < width; x += tileWidth) { 1519 int colsToCopy = Math.min(width - x, tileWidth); 1520 for (int w = 0; w < colsToCopy; w++) { 1521 buf.position(lineOffset + (x + w) * pixelStride); 1522 buf.put(mInputData[offset + y * tileWidth + w]); 1523 } 1524 } 1525 } 1526 } 1527 } 1528 offset += tileWidth * tileHeight; 1529 } 1530 } 1531 fillByteBuffer(ByteBuffer inputBuffer)1532 void fillByteBuffer(ByteBuffer inputBuffer) { 1533 int offset = 0, frmOffset = mNumBytesSubmitted; 1534 for (int plane = 0; plane < 3; plane++) { 1535 int width = mWidth; 1536 int height = mHeight; 1537 int tileWidth = INP_FRM_WIDTH; 1538 int tileHeight = INP_FRM_HEIGHT; 1539 if (plane != 0) { 1540 width = mWidth / 2; 1541 height = mHeight / 2; 1542 tileWidth = INP_FRM_WIDTH / 2; 1543 tileHeight = INP_FRM_HEIGHT / 2; 1544 } 1545 for (int k = 0; k < height; k += tileHeight) { 1546 int rowsToCopy = Math.min(height - k, tileHeight); 1547 for (int j = 0; j < rowsToCopy; j++) { 1548 for (int i = 0; i < width; i += tileWidth) { 1549 int colsToCopy = Math.min(width - i, tileWidth); 1550 inputBuffer.position(offset + (k + j) * width + i); 1551 inputBuffer.put(mInputData, frmOffset + j * tileWidth, colsToCopy); 1552 } 1553 } 1554 } 1555 offset += width * height; 1556 frmOffset += tileWidth * tileHeight; 1557 } 1558 } 1559 enqueueInput(int bufferIndex)1560 void enqueueInput(int bufferIndex) { 1561 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 1562 if (mNumBytesSubmitted >= mInputData.length) { 1563 enqueueEOS(bufferIndex); 1564 } else { 1565 int size; 1566 int flags = 0; 1567 long pts = mInputOffsetPts; 1568 if (mIsAudio) { 1569 pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate); 1570 size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted); 1571 inputBuffer.put(mInputData, mNumBytesSubmitted, size); 1572 if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) { 1573 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 1574 mSawInputEOS = true; 1575 } 1576 mNumBytesSubmitted += size; 1577 } else { 1578 pts += mInputCount * 1000000L / mFrameRate; 1579 size = mWidth * mHeight * 3 / 2; 1580 int frmSize = INP_FRM_WIDTH * INP_FRM_HEIGHT * 3 / 2; 1581 if (mNumBytesSubmitted + frmSize > mInputData.length) { 1582 fail("received partial frame to encode"); 1583 } else { 1584 Image img = mCodec.getInputImage(bufferIndex); 1585 if (img != null) { 1586 fillImage(img); 1587 } else { 1588 if (mWidth == INP_FRM_WIDTH && mHeight == INP_FRM_HEIGHT) { 1589 inputBuffer.put(mInputData, mNumBytesSubmitted, size); 1590 } else { 1591 fillByteBuffer(inputBuffer); 1592 } 1593 } 1594 } 1595 if (mNumBytesSubmitted + frmSize >= mInputData.length && mSignalEOSWithLastFrame) { 1596 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 1597 mSawInputEOS = true; 1598 } 1599 mNumBytesSubmitted += frmSize; 1600 } 1601 if (ENABLE_LOGS) { 1602 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts + 1603 " flags: " + flags); 1604 } 1605 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags); 1606 mOutputBuff.saveInPTS(pts); 1607 mInputCount++; 1608 } 1609 } 1610 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)1611 void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 1612 if (ENABLE_LOGS) { 1613 Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " + 1614 info.size + " timestamp: " + info.presentationTimeUs); 1615 } 1616 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 1617 mSawOutputEOS = true; 1618 } 1619 if (info.size > 0) { 1620 if (mSaveToMem) { 1621 MediaCodec.BufferInfo copy = new MediaCodec.BufferInfo(); 1622 copy.set(mOutputBuff.getOutStreamSize(), info.size, info.presentationTimeUs, 1623 info.flags); 1624 mInfoList.add(copy); 1625 1626 ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex); 1627 mOutputBuff.saveToMemory(buf, info); 1628 } 1629 if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 1630 mOutputBuff.saveOutPTS(info.presentationTimeUs); 1631 mOutputCount++; 1632 } 1633 } 1634 mCodec.releaseOutputBuffer(bufferIndex, false); 1635 } 1636 1637 @Override validateMetrics(String codec, MediaFormat format)1638 PersistableBundle validateMetrics(String codec, MediaFormat format) { 1639 PersistableBundle metrics = super.validateMetrics(codec, format); 1640 assertTrue(metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE).equals(mMime)); 1641 assertTrue(metrics.getInt(MediaCodec.MetricsConstants.ENCODER) == 1); 1642 return metrics; 1643 } 1644 setUpParams(int limit)1645 void setUpParams(int limit) { 1646 int count = 0; 1647 for (int bitrate : mBitrates) { 1648 if (mIsAudio) { 1649 for (int rate : mEncParamList1) { 1650 for (int channels : mEncParamList2) { 1651 MediaFormat format = new MediaFormat(); 1652 format.setString(MediaFormat.KEY_MIME, mMime); 1653 if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) { 1654 format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, bitrate); 1655 } else { 1656 format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); 1657 } 1658 format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate); 1659 format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels); 1660 mFormats.add(format); 1661 count++; 1662 if (count >= limit) return; 1663 } 1664 } 1665 } else { 1666 assertTrue("Wrong number of height, width parameters", 1667 mEncParamList1.length == mEncParamList2.length); 1668 for (int i = 0; i < mEncParamList1.length; i++) { 1669 MediaFormat format = new MediaFormat(); 1670 format.setString(MediaFormat.KEY_MIME, mMime); 1671 format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); 1672 format.setInteger(MediaFormat.KEY_WIDTH, mEncParamList1[i]); 1673 format.setInteger(MediaFormat.KEY_HEIGHT, mEncParamList2[i]); 1674 format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate); 1675 format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, mMaxBFrames); 1676 format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f); 1677 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 1678 MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); 1679 mFormats.add(format); 1680 count++; 1681 if (count >= limit) return; 1682 } 1683 } 1684 } 1685 } 1686 encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format, boolean saveToMem)1687 void encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format, 1688 boolean saveToMem) throws IOException, InterruptedException { 1689 mSaveToMem = saveToMem; 1690 mOutputBuff = new OutputManager(); 1691 mInfoList.clear(); 1692 mCodec = MediaCodec.createByCodecName(encoder); 1693 setUpSource(file); 1694 configureCodec(format, false, true, true); 1695 if (mIsAudio) { 1696 mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); 1697 mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); 1698 } else { 1699 mWidth = format.getInteger(MediaFormat.KEY_WIDTH); 1700 mHeight = format.getInteger(MediaFormat.KEY_HEIGHT); 1701 } 1702 mCodec.start(); 1703 doWork(frameLimit); 1704 queueEOS(); 1705 waitForAllOutputs(); 1706 mCodec.stop(); 1707 mCodec.release(); 1708 mSaveToMem = false; 1709 } 1710 decodeElementaryStream(String decoder, MediaFormat format, ByteBuffer elementaryStream, ArrayList<MediaCodec.BufferInfo> infos)1711 ByteBuffer decodeElementaryStream(String decoder, MediaFormat format, 1712 ByteBuffer elementaryStream, ArrayList<MediaCodec.BufferInfo> infos) 1713 throws IOException, InterruptedException { 1714 String mime = format.getString(MediaFormat.KEY_MIME); 1715 CodecDecoderTestBase cdtb = new CodecDecoderTestBase(decoder, mime, null); 1716 cdtb.mOutputBuff = new OutputManager(); 1717 cdtb.mSaveToMem = true; 1718 cdtb.mCodec = MediaCodec.createByCodecName(decoder); 1719 cdtb.mCodec.configure(format, null, null, 0); 1720 cdtb.mCodec.start(); 1721 cdtb.doWork(elementaryStream, infos); 1722 cdtb.queueEOS(); 1723 cdtb.waitForAllOutputs(); 1724 cdtb.mCodec.stop(); 1725 cdtb.mCodec.release(); 1726 return cdtb.mOutputBuff.getBuffer(); 1727 } 1728 } 1729