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 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 1233 mSawInputEOS = true; 1234 } 1235 } 1236 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)1237 void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 1238 if (info.size > 0 && mSaveToMem) { 1239 ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex); 1240 flattenBufferInfo(info, mIsAudio); 1241 mOutputBuff.checksum(flatBuffer, flatBuffer.limit()); 1242 if (mIsAudio) { 1243 mOutputBuff.checksum(buf, info.size); 1244 mOutputBuff.saveToMemory(buf, info); 1245 } else { 1246 // tests both getOutputImage and getOutputBuffer. Can do time division 1247 // multiplexing but lets allow it for now 1248 MediaFormat format = mCodec.getOutputFormat(); 1249 int width = format.getInteger(MediaFormat.KEY_WIDTH); 1250 int height = format.getInteger(MediaFormat.KEY_HEIGHT); 1251 int stride = format.getInteger(MediaFormat.KEY_STRIDE); 1252 mOutputBuff.checksum(buf, info.size, width, height, stride); 1253 1254 Image img = mCodec.getOutputImage(bufferIndex); 1255 assertTrue(img != null); 1256 mOutputBuff.checksum(img); 1257 } 1258 } 1259 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 1260 mSawOutputEOS = true; 1261 } 1262 if (ENABLE_LOGS) { 1263 Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " + 1264 info.size + " timestamp: " + info.presentationTimeUs); 1265 } 1266 if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 1267 mOutputBuff.saveOutPTS(info.presentationTimeUs); 1268 mOutputCount++; 1269 } 1270 mCodec.releaseOutputBuffer(bufferIndex, false); 1271 } 1272 doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list)1273 void doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list) 1274 throws InterruptedException { 1275 int frameCount = 0; 1276 if (mIsCodecInAsyncMode) { 1277 // output processing after queuing EOS is done in waitForAllOutputs() 1278 while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < list.size()) { 1279 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); 1280 if (element != null) { 1281 int bufferID = element.first; 1282 MediaCodec.BufferInfo info = element.second; 1283 if (info != null) { 1284 dequeueOutput(bufferID, info); 1285 } else { 1286 enqueueInput(bufferID, buffer, list.get(frameCount)); 1287 frameCount++; 1288 } 1289 } 1290 } 1291 } else { 1292 MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); 1293 // output processing after queuing EOS is done in waitForAllOutputs() 1294 while (!mSawInputEOS && frameCount < list.size()) { 1295 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); 1296 if (outputBufferId >= 0) { 1297 dequeueOutput(outputBufferId, outInfo); 1298 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 1299 mOutFormat = mCodec.getOutputFormat(); 1300 mSignalledOutFormatChanged = true; 1301 } 1302 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); 1303 if (inputBufferId != -1) { 1304 enqueueInput(inputBufferId, buffer, list.get(frameCount)); 1305 frameCount++; 1306 } 1307 } 1308 } 1309 } 1310 queueCodecConfig()1311 void queueCodecConfig() throws InterruptedException { 1312 if (mIsCodecInAsyncMode) { 1313 for (mCurrCsdIdx = 0; !mAsyncHandle.hasSeenError() && mCurrCsdIdx < mCsdBuffers.size(); 1314 mCurrCsdIdx++) { 1315 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput(); 1316 if (element != null) { 1317 enqueueCodecConfig(element.first); 1318 } 1319 } 1320 } else { 1321 for (mCurrCsdIdx = 0; mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) { 1322 enqueueCodecConfig(mCodec.dequeueInputBuffer(-1)); 1323 } 1324 } 1325 } 1326 decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit)1327 void decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit) 1328 throws IOException, InterruptedException { 1329 mSaveToMem = true; 1330 mOutputBuff = new OutputManager(); 1331 mCodec = MediaCodec.createByCodecName(decoder); 1332 MediaFormat format = setUpSource(file); 1333 configureCodec(format, false, true, false); 1334 mCodec.start(); 1335 mExtractor.seekTo(pts, mode); 1336 doWork(frameLimit); 1337 queueEOS(); 1338 waitForAllOutputs(); 1339 mCodec.stop(); 1340 mCodec.release(); 1341 mExtractor.release(); 1342 mSaveToMem = false; 1343 } 1344 1345 @Override validateMetrics(String decoder, MediaFormat format)1346 PersistableBundle validateMetrics(String decoder, MediaFormat format) { 1347 PersistableBundle metrics = super.validateMetrics(decoder, format); 1348 assertTrue(metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE).equals(mMime)); 1349 assertTrue(metrics.getInt(MediaCodec.MetricsConstants.ENCODER) == 0); 1350 return metrics; 1351 } 1352 validateColorAspects(String decoder, String parent, String name, int range, int standard, int transfer, boolean ignoreColorBox)1353 void validateColorAspects(String decoder, String parent, String name, int range, int standard, 1354 int transfer, boolean ignoreColorBox) 1355 throws IOException, InterruptedException { 1356 mOutputBuff = new OutputManager(); 1357 MediaFormat format = setUpSource(parent, name); 1358 if (ignoreColorBox) { 1359 format.removeKey(MediaFormat.KEY_COLOR_RANGE); 1360 format.removeKey(MediaFormat.KEY_COLOR_STANDARD); 1361 format.removeKey(MediaFormat.KEY_COLOR_TRANSFER); 1362 } 1363 if (decoder == null) { 1364 MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 1365 decoder = codecList.findDecoderForFormat(format); 1366 } 1367 mCodec = MediaCodec.createByCodecName(decoder); 1368 configureCodec(format, true, true, false); 1369 mCodec.start(); 1370 doWork(1); 1371 queueEOS(); 1372 waitForAllOutputs(); 1373 validateColorAspects(mCodec.getOutputFormat(), range, standard, transfer); 1374 mCodec.stop(); 1375 mCodec.release(); 1376 mExtractor.release(); 1377 } 1378 } 1379 1380 class CodecEncoderTestBase extends CodecTestBase { 1381 private static final String LOG_TAG = CodecEncoderTestBase.class.getSimpleName(); 1382 1383 // files are in WorkDir.getMediaDirString(); 1384 private static final String mInputAudioFile = "bbb_2ch_44kHz_s16le.raw"; 1385 private static final String mInputVideoFile = "bbb_cif_yuv420p_30fps.yuv"; 1386 private final int INP_FRM_WIDTH = 352; 1387 private final int INP_FRM_HEIGHT = 288; 1388 1389 final String mMime; 1390 final int[] mBitrates; 1391 final int[] mEncParamList1; 1392 final int[] mEncParamList2; 1393 1394 final String mInputFile; 1395 byte[] mInputData; 1396 int mNumBytesSubmitted; 1397 long mInputOffsetPts; 1398 1399 ArrayList<MediaFormat> mFormats; 1400 ArrayList<MediaCodec.BufferInfo> mInfoList; 1401 1402 int mWidth, mHeight; 1403 int mFrameRate; 1404 int mMaxBFrames; 1405 int mChannels; 1406 int mSampleRate; 1407 CodecEncoderTestBase(String encoder, String mime, int[] bitrates, int[] encoderInfo1, int[] encoderInfo2)1408 CodecEncoderTestBase(String encoder, String mime, int[] bitrates, int[] encoderInfo1, 1409 int[] encoderInfo2) { 1410 mMime = mime; 1411 mCodecName = encoder; 1412 mBitrates = bitrates; 1413 mEncParamList1 = encoderInfo1; 1414 mEncParamList2 = encoderInfo2; 1415 mFormats = new ArrayList<>(); 1416 mInfoList = new ArrayList<>(); 1417 mWidth = INP_FRM_WIDTH; 1418 mHeight = INP_FRM_HEIGHT; 1419 if (mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) mFrameRate = 12; 1420 else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_H263)) mFrameRate = 12; 1421 else mFrameRate = 30; 1422 mMaxBFrames = 0; 1423 mChannels = 1; 1424 mSampleRate = 8000; 1425 mAsyncHandle = new CodecAsyncHandler(); 1426 mIsAudio = mMime.startsWith("audio/"); 1427 mInputFile = mIsAudio ? mInputAudioFile : mInputVideoFile; 1428 } 1429 1430 /** 1431 * Selects encoder input color format in byte buffer mode. As of now ndk tests support only 1432 * 420p, 420sp. COLOR_FormatYUV420Flexible although can represent any form of yuv, it doesn't 1433 * work in ndk due to lack of AMediaCodec_GetInputImage() 1434 */ findByteBufferColorFormat(String encoder, String mime)1435 static int findByteBufferColorFormat(String encoder, String mime) throws IOException { 1436 MediaCodec codec = MediaCodec.createByCodecName(encoder); 1437 MediaCodecInfo.CodecCapabilities cap = codec.getCodecInfo().getCapabilitiesForType(mime); 1438 int colorFormat = -1; 1439 for (int c : cap.colorFormats) { 1440 if (c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar || 1441 c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) { 1442 Log.v(LOG_TAG, "selecting color format: " + c); 1443 colorFormat = c; 1444 break; 1445 } 1446 } 1447 codec.release(); 1448 return colorFormat; 1449 } 1450 1451 @Override resetContext(boolean isAsync, boolean signalEOSWithLastFrame)1452 void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 1453 super.resetContext(isAsync, signalEOSWithLastFrame); 1454 mNumBytesSubmitted = 0; 1455 mInputOffsetPts = 0; 1456 } 1457 1458 @Override flushCodec()1459 void flushCodec() { 1460 super.flushCodec(); 1461 if (mIsAudio) { 1462 mInputOffsetPts = 1463 (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mSampleRate); 1464 } else { 1465 mInputOffsetPts = (mInputCount + 5) * 1000000L / mFrameRate; 1466 } 1467 mPrevOutputPts = mInputOffsetPts - 1; 1468 mNumBytesSubmitted = 0; 1469 } 1470 setUpSource(String srcFile)1471 void setUpSource(String srcFile) throws IOException { 1472 String inpPath = mInpPrefix + srcFile; 1473 try (FileInputStream fInp = new FileInputStream(inpPath)) { 1474 int size = (int) new File(inpPath).length(); 1475 mInputData = new byte[size]; 1476 fInp.read(mInputData, 0, size); 1477 } 1478 } 1479 fillImage(Image image)1480 void fillImage(Image image) { 1481 Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888); 1482 int imageWidth = image.getWidth(); 1483 int imageHeight = image.getHeight(); 1484 Image.Plane[] planes = image.getPlanes(); 1485 int offset = mNumBytesSubmitted; 1486 for (int i = 0; i < planes.length; ++i) { 1487 ByteBuffer buf = planes[i].getBuffer(); 1488 int width = imageWidth; 1489 int height = imageHeight; 1490 int tileWidth = INP_FRM_WIDTH; 1491 int tileHeight = INP_FRM_HEIGHT; 1492 int rowStride = planes[i].getRowStride(); 1493 int pixelStride = planes[i].getPixelStride(); 1494 if (i != 0) { 1495 width = imageWidth / 2; 1496 height = imageHeight / 2; 1497 tileWidth = INP_FRM_WIDTH / 2; 1498 tileHeight = INP_FRM_HEIGHT / 2; 1499 } 1500 if (pixelStride == 1) { 1501 if (width == rowStride && width == tileWidth && height == tileHeight) { 1502 buf.put(mInputData, offset, width * height); 1503 } else { 1504 for (int z = 0; z < height; z += tileHeight) { 1505 int rowsToCopy = Math.min(height - z, tileHeight); 1506 for (int y = 0; y < rowsToCopy; y++) { 1507 for (int x = 0; x < width; x += tileWidth) { 1508 int colsToCopy = Math.min(width - x, tileWidth); 1509 buf.position((z + y) * rowStride + x); 1510 buf.put(mInputData, offset + y * tileWidth, colsToCopy); 1511 } 1512 } 1513 } 1514 } 1515 } else { 1516 // do it pixel-by-pixel 1517 for (int z = 0; z < height; z += tileHeight) { 1518 int rowsToCopy = Math.min(height - z, tileHeight); 1519 for (int y = 0; y < rowsToCopy; y++) { 1520 int lineOffset = (z + y) * rowStride; 1521 for (int x = 0; x < width; x += tileWidth) { 1522 int colsToCopy = Math.min(width - x, tileWidth); 1523 for (int w = 0; w < colsToCopy; w++) { 1524 buf.position(lineOffset + (x + w) * pixelStride); 1525 buf.put(mInputData[offset + y * tileWidth + w]); 1526 } 1527 } 1528 } 1529 } 1530 } 1531 offset += tileWidth * tileHeight; 1532 } 1533 } 1534 fillByteBuffer(ByteBuffer inputBuffer)1535 void fillByteBuffer(ByteBuffer inputBuffer) { 1536 int offset = 0, frmOffset = mNumBytesSubmitted; 1537 for (int plane = 0; plane < 3; plane++) { 1538 int width = mWidth; 1539 int height = mHeight; 1540 int tileWidth = INP_FRM_WIDTH; 1541 int tileHeight = INP_FRM_HEIGHT; 1542 if (plane != 0) { 1543 width = mWidth / 2; 1544 height = mHeight / 2; 1545 tileWidth = INP_FRM_WIDTH / 2; 1546 tileHeight = INP_FRM_HEIGHT / 2; 1547 } 1548 for (int k = 0; k < height; k += tileHeight) { 1549 int rowsToCopy = Math.min(height - k, tileHeight); 1550 for (int j = 0; j < rowsToCopy; j++) { 1551 for (int i = 0; i < width; i += tileWidth) { 1552 int colsToCopy = Math.min(width - i, tileWidth); 1553 inputBuffer.position(offset + (k + j) * width + i); 1554 inputBuffer.put(mInputData, frmOffset + j * tileWidth, colsToCopy); 1555 } 1556 } 1557 } 1558 offset += width * height; 1559 frmOffset += tileWidth * tileHeight; 1560 } 1561 } 1562 enqueueInput(int bufferIndex)1563 void enqueueInput(int bufferIndex) { 1564 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 1565 if (mNumBytesSubmitted >= mInputData.length) { 1566 enqueueEOS(bufferIndex); 1567 } else { 1568 int size; 1569 int flags = 0; 1570 long pts = mInputOffsetPts; 1571 if (mIsAudio) { 1572 pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate); 1573 size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted); 1574 inputBuffer.put(mInputData, mNumBytesSubmitted, size); 1575 if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) { 1576 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 1577 mSawInputEOS = true; 1578 } 1579 mNumBytesSubmitted += size; 1580 } else { 1581 pts += mInputCount * 1000000L / mFrameRate; 1582 size = mWidth * mHeight * 3 / 2; 1583 int frmSize = INP_FRM_WIDTH * INP_FRM_HEIGHT * 3 / 2; 1584 if (mNumBytesSubmitted + frmSize > mInputData.length) { 1585 fail("received partial frame to encode"); 1586 } else { 1587 Image img = mCodec.getInputImage(bufferIndex); 1588 if (img != null) { 1589 fillImage(img); 1590 } else { 1591 if (mWidth == INP_FRM_WIDTH && mHeight == INP_FRM_HEIGHT) { 1592 inputBuffer.put(mInputData, mNumBytesSubmitted, size); 1593 } else { 1594 fillByteBuffer(inputBuffer); 1595 } 1596 } 1597 } 1598 if (mNumBytesSubmitted + frmSize >= mInputData.length && mSignalEOSWithLastFrame) { 1599 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 1600 mSawInputEOS = true; 1601 } 1602 mNumBytesSubmitted += frmSize; 1603 } 1604 if (ENABLE_LOGS) { 1605 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts + 1606 " flags: " + flags); 1607 } 1608 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags); 1609 mOutputBuff.saveInPTS(pts); 1610 mInputCount++; 1611 } 1612 } 1613 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)1614 void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 1615 if (ENABLE_LOGS) { 1616 Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " + 1617 info.size + " timestamp: " + info.presentationTimeUs); 1618 } 1619 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 1620 mSawOutputEOS = true; 1621 } 1622 if (info.size > 0) { 1623 if (mSaveToMem) { 1624 MediaCodec.BufferInfo copy = new MediaCodec.BufferInfo(); 1625 copy.set(mOutputBuff.getOutStreamSize(), info.size, info.presentationTimeUs, 1626 info.flags); 1627 mInfoList.add(copy); 1628 1629 ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex); 1630 mOutputBuff.saveToMemory(buf, info); 1631 } 1632 if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 1633 mOutputBuff.saveOutPTS(info.presentationTimeUs); 1634 mOutputCount++; 1635 } 1636 } 1637 mCodec.releaseOutputBuffer(bufferIndex, false); 1638 } 1639 1640 @Override validateMetrics(String codec, MediaFormat format)1641 PersistableBundle validateMetrics(String codec, MediaFormat format) { 1642 PersistableBundle metrics = super.validateMetrics(codec, format); 1643 assertTrue(metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE).equals(mMime)); 1644 assertTrue(metrics.getInt(MediaCodec.MetricsConstants.ENCODER) == 1); 1645 return metrics; 1646 } 1647 setUpParams(int limit)1648 void setUpParams(int limit) { 1649 int count = 0; 1650 for (int bitrate : mBitrates) { 1651 if (mIsAudio) { 1652 for (int rate : mEncParamList1) { 1653 for (int channels : mEncParamList2) { 1654 MediaFormat format = new MediaFormat(); 1655 format.setString(MediaFormat.KEY_MIME, mMime); 1656 if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) { 1657 format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, bitrate); 1658 } else { 1659 format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); 1660 } 1661 format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate); 1662 format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels); 1663 mFormats.add(format); 1664 count++; 1665 if (count >= limit) return; 1666 } 1667 } 1668 } else { 1669 assertTrue("Wrong number of height, width parameters", 1670 mEncParamList1.length == mEncParamList2.length); 1671 for (int i = 0; i < mEncParamList1.length; i++) { 1672 MediaFormat format = new MediaFormat(); 1673 format.setString(MediaFormat.KEY_MIME, mMime); 1674 format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); 1675 format.setInteger(MediaFormat.KEY_WIDTH, mEncParamList1[i]); 1676 format.setInteger(MediaFormat.KEY_HEIGHT, mEncParamList2[i]); 1677 format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate); 1678 format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, mMaxBFrames); 1679 format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f); 1680 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 1681 MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); 1682 mFormats.add(format); 1683 count++; 1684 if (count >= limit) return; 1685 } 1686 } 1687 } 1688 } 1689 encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format, boolean saveToMem)1690 void encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format, 1691 boolean saveToMem) throws IOException, InterruptedException { 1692 mSaveToMem = saveToMem; 1693 mOutputBuff = new OutputManager(); 1694 mInfoList.clear(); 1695 mCodec = MediaCodec.createByCodecName(encoder); 1696 setUpSource(file); 1697 configureCodec(format, false, true, true); 1698 if (mIsAudio) { 1699 mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); 1700 mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); 1701 } else { 1702 mWidth = format.getInteger(MediaFormat.KEY_WIDTH); 1703 mHeight = format.getInteger(MediaFormat.KEY_HEIGHT); 1704 } 1705 mCodec.start(); 1706 doWork(frameLimit); 1707 queueEOS(); 1708 waitForAllOutputs(); 1709 mCodec.stop(); 1710 mCodec.release(); 1711 mSaveToMem = false; 1712 } 1713 decodeElementaryStream(String decoder, MediaFormat format, ByteBuffer elementaryStream, ArrayList<MediaCodec.BufferInfo> infos)1714 ByteBuffer decodeElementaryStream(String decoder, MediaFormat format, 1715 ByteBuffer elementaryStream, ArrayList<MediaCodec.BufferInfo> infos) 1716 throws IOException, InterruptedException { 1717 String mime = format.getString(MediaFormat.KEY_MIME); 1718 CodecDecoderTestBase cdtb = new CodecDecoderTestBase(decoder, mime, null); 1719 cdtb.mOutputBuff = new OutputManager(); 1720 cdtb.mSaveToMem = true; 1721 cdtb.mCodec = MediaCodec.createByCodecName(decoder); 1722 cdtb.mCodec.configure(format, null, null, 0); 1723 cdtb.mCodec.start(); 1724 cdtb.doWork(elementaryStream, infos); 1725 cdtb.queueEOS(); 1726 cdtb.waitForAllOutputs(); 1727 cdtb.mCodec.stop(); 1728 cdtb.mCodec.release(); 1729 return cdtb.mOutputBuff.getBuffer(); 1730 } 1731 } 1732