1 /* 2 * Copyright (C) 2022 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.common.cts; 18 19 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; 20 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible; 21 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010; 22 import static android.media.MediaMuxer.OutputFormat.MUXER_OUTPUT_FIRST; 23 import static android.media.MediaMuxer.OutputFormat.MUXER_OUTPUT_LAST; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 30 import android.annotation.NonNull; 31 import android.graphics.ImageFormat; 32 import android.media.AudioFormat; 33 import android.media.Image; 34 import android.media.MediaCodec; 35 import android.media.MediaCodecInfo; 36 import android.media.MediaFormat; 37 import android.media.MediaMuxer; 38 import android.os.PersistableBundle; 39 import android.util.Log; 40 41 import com.android.compatibility.common.util.Preconditions; 42 43 import org.junit.After; 44 import org.junit.Before; 45 46 import java.io.File; 47 import java.io.FileInputStream; 48 import java.io.IOException; 49 import java.nio.ByteBuffer; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.Collections; 53 import java.util.List; 54 55 /** 56 * Wrapper class for trying and testing encoder components. 57 */ 58 public class CodecEncoderTestBase extends CodecTestBase { 59 private static final String LOG_TAG = CodecEncoderTestBase.class.getSimpleName(); 60 61 protected final EncoderConfigParams[] mEncCfgParams; 62 63 protected EncoderConfigParams mActiveEncCfg; 64 protected RawResource mActiveRawRes; 65 protected boolean mIsLoopBack; 66 protected int mLoopBackFrameLimit; 67 68 protected byte[] mInputData; 69 protected int mInputBufferReadOffset; 70 protected int mNumBytesSubmitted; 71 protected long mInputOffsetPts; 72 73 protected ArrayList<MediaCodec.BufferInfo> mInfoList = new ArrayList<>(); 74 75 protected boolean mMuxOutput; 76 protected String mMuxedOutputFile; 77 protected MediaMuxer mMuxer; 78 protected int mTrackID = -1; 79 CodecEncoderTestBase(String encoder, String mediaType, EncoderConfigParams[] encCfgParams, String allTestParams)80 public CodecEncoderTestBase(String encoder, String mediaType, 81 EncoderConfigParams[] encCfgParams, String allTestParams) { 82 super(encoder, mediaType, allTestParams); 83 mEncCfgParams = encCfgParams; 84 } 85 86 private static final List<String> MEDIATYPE_LIST_FOR_TYPE_MP4 = new ArrayList<>( 87 Arrays.asList(MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263, 88 MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC, 89 MediaFormat.MIMETYPE_AUDIO_AAC)); 90 static { 91 if (CodecTestBase.IS_AT_LEAST_U) { 92 MEDIATYPE_LIST_FOR_TYPE_MP4.add(MediaFormat.MIMETYPE_VIDEO_AV1); 93 } 94 } 95 private static final List<String> MEDIATYPE_LIST_FOR_TYPE_WEBM = 96 Arrays.asList(MediaFormat.MIMETYPE_VIDEO_VP8, MediaFormat.MIMETYPE_VIDEO_VP9, 97 MediaFormat.MIMETYPE_AUDIO_VORBIS, MediaFormat.MIMETYPE_AUDIO_OPUS); 98 private static final List<String> MEDIATYPE_LIST_FOR_TYPE_3GP = 99 Arrays.asList(MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263, 100 MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_AUDIO_AAC, 101 MediaFormat.MIMETYPE_AUDIO_AMR_NB, MediaFormat.MIMETYPE_AUDIO_AMR_WB); 102 private static final List<String> MEDIATYPE_LIST_FOR_TYPE_OGG = 103 Collections.singletonList(MediaFormat.MIMETYPE_AUDIO_OPUS); 104 105 public static final float ACCEPTABLE_WIRELESS_TX_QUALITY = 20.0f; // psnr in dB 106 public static final float ACCEPTABLE_AV_SYNC_ERROR = 22.0f; // duration in ms 107 108 /** 109 * Selects encoder input color format in byte buffer mode. As of now ndk tests support only 110 * 420p, 420sp. COLOR_FormatYUV420Flexible although can represent any form of yuv, it doesn't 111 * work in ndk due to lack of AMediaCodec_GetInputImage() 112 */ findByteBufferColorFormat(String encoder, String mediaType)113 public static int findByteBufferColorFormat(String encoder, String mediaType) 114 throws IOException { 115 MediaCodec codec = MediaCodec.createByCodecName(encoder); 116 MediaCodecInfo.CodecCapabilities cap = 117 codec.getCodecInfo().getCapabilitiesForType(mediaType); 118 int colorFormat = -1; 119 for (int c : cap.colorFormats) { 120 if (c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar 121 || c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) { 122 Log.v(LOG_TAG, "selecting color format: " + c); 123 colorFormat = c; 124 break; 125 } 126 } 127 codec.release(); 128 return colorFormat; 129 } 130 muxOutput(String filePath, int muxerFormat, MediaFormat format, ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> infos)131 public static void muxOutput(String filePath, int muxerFormat, MediaFormat format, 132 ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> infos) throws IOException { 133 MediaMuxer muxer = null; 134 try { 135 muxer = new MediaMuxer(filePath, muxerFormat); 136 int trackID = muxer.addTrack(format); 137 muxer.start(); 138 for (MediaCodec.BufferInfo info : infos) { 139 if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 140 muxer.writeSampleData(trackID, buffer, info); 141 } 142 } 143 muxer.stop(); 144 } finally { 145 if (muxer != null) muxer.release(); 146 } 147 } 148 isMediaTypeContainerPairValid(String mediaType, int format)149 public static boolean isMediaTypeContainerPairValid(String mediaType, int format) { 150 boolean result = false; 151 if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) { 152 result = MEDIATYPE_LIST_FOR_TYPE_MP4.contains(mediaType) 153 || mediaType.startsWith("application/"); 154 } else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM) { 155 result = MEDIATYPE_LIST_FOR_TYPE_WEBM.contains(mediaType); 156 } else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) { 157 result = MEDIATYPE_LIST_FOR_TYPE_3GP.contains(mediaType); 158 } else if (format == MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG) { 159 result = MEDIATYPE_LIST_FOR_TYPE_OGG.contains(mediaType); 160 } 161 return result; 162 } 163 getMuxerFormatForMediaType(String mediaType)164 public static int getMuxerFormatForMediaType(String mediaType) { 165 for (int muxFormat = MUXER_OUTPUT_FIRST; muxFormat <= MUXER_OUTPUT_LAST; muxFormat++) { 166 if (isMediaTypeContainerPairValid(mediaType, muxFormat)) { 167 return muxFormat; 168 } 169 } 170 fail("no configured muxer support for " + mediaType); 171 return MUXER_OUTPUT_LAST; 172 } 173 getTempFilePath(String infix)174 public static String getTempFilePath(String infix) throws IOException { 175 return File.createTempFile("tmp" + infix, ".bin").getAbsolutePath(); 176 } 177 validateEncodedPSNR(String inpMediaType, String inpFile, String outMediaType, String outFile, boolean allowInpResize, boolean allowInpLoopBack, double perFramePsnrThreshold)178 public static void validateEncodedPSNR(String inpMediaType, String inpFile, 179 String outMediaType, String outFile, boolean allowInpResize, boolean allowInpLoopBack, 180 double perFramePsnrThreshold) throws IOException, InterruptedException { 181 CompareStreams cs = new CompareStreams(inpMediaType, inpFile, outMediaType, outFile, 182 allowInpResize, allowInpLoopBack); 183 validateEncodedPSNR(cs.getFramesPSNR(), perFramePsnrThreshold); 184 cs.cleanUp(); 185 } 186 validateEncodedPSNR(RawResource inp, String outMediaType, String outFile, boolean allowInpResize, boolean allowInpLoopBack, double perFramePsnrThreshold)187 public static void validateEncodedPSNR(RawResource inp, String outMediaType, String outFile, 188 boolean allowInpResize, boolean allowInpLoopBack, double perFramePsnrThreshold) 189 throws IOException, InterruptedException { 190 CompareStreams cs = new CompareStreams(inp, outMediaType, outFile, allowInpResize, 191 allowInpLoopBack); 192 validateEncodedPSNR(cs.getFramesPSNR(), perFramePsnrThreshold); 193 cs.cleanUp(); 194 } 195 validateEncodedPSNR(@onNull ArrayList<double[]> framesPSNR, double perFramePsnrThreshold)196 public static void validateEncodedPSNR(@NonNull ArrayList<double[]> framesPSNR, 197 double perFramePsnrThreshold) { 198 StringBuilder msg = new StringBuilder(); 199 boolean isOk = true; 200 for (int j = 0; j < framesPSNR.size(); j++) { 201 double[] framePSNR = framesPSNR.get(j); 202 // https://www.itu.int/dms_pub/itu-t/opb/tut/T-TUT-ASC-2020-HSTP1-PDF-E.pdf 203 // weighted psnr (6 * psnrY + psnrU + psnrV) / 8; 204 // stronger weighting of luma PSNR is to compensate for the fact that most of the 205 // bits are used to describe luma information 206 double weightPSNR = (6 * framePSNR[0] + framePSNR[1] + framePSNR[2]) / 8; 207 if (weightPSNR < perFramePsnrThreshold) { 208 msg.append(String.format( 209 "Frame %d - PSNR Y: %f, PSNR U: %f, PSNR V: %f, Weighted PSNR: %f < " 210 + "Threshold %f \n", 211 j, framePSNR[0], framePSNR[1], framePSNR[2], weightPSNR, 212 perFramePsnrThreshold)); 213 isOk = false; 214 } 215 } 216 assertTrue("Encountered frames with PSNR less than configured threshold " 217 + perFramePsnrThreshold + "dB \n" + msg, isOk); 218 } 219 bitRateModeToString(int mode)220 public static String bitRateModeToString(int mode) { 221 switch (mode) { 222 case MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR: 223 return "cbr"; 224 case MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR: 225 return "vbr"; 226 case MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ: 227 return "cq"; 228 case MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR_FD: 229 return "cbrwithfd"; 230 default: 231 return "unknown"; 232 } 233 } 234 rangeToString(int range)235 public static String rangeToString(int range) { 236 switch (range) { 237 case UNSPECIFIED: 238 return "unspecified"; 239 case MediaFormat.COLOR_RANGE_FULL: 240 return "full"; 241 case MediaFormat.COLOR_RANGE_LIMITED: 242 return "limited"; 243 default: 244 return "unknown"; 245 } 246 } 247 colorStandardToString(int standard)248 public static String colorStandardToString(int standard) { 249 switch (standard) { 250 case UNSPECIFIED: 251 return "unspecified"; 252 case MediaFormat.COLOR_STANDARD_BT709: 253 return "bt709"; 254 case MediaFormat.COLOR_STANDARD_BT601_PAL: 255 return "bt601pal"; 256 case MediaFormat.COLOR_STANDARD_BT601_NTSC: 257 return "bt601ntsc"; 258 case MediaFormat.COLOR_STANDARD_BT2020: 259 return "bt2020"; 260 default: 261 return "unknown"; 262 } 263 } 264 colorTransferToString(int transfer)265 public static String colorTransferToString(int transfer) { 266 switch (transfer) { 267 case UNSPECIFIED: 268 return "unspecified"; 269 case MediaFormat.COLOR_TRANSFER_LINEAR: 270 return "linear"; 271 case MediaFormat.COLOR_TRANSFER_SDR_VIDEO: 272 return "sdr"; 273 case MediaFormat.COLOR_TRANSFER_HLG: 274 return "hlg"; 275 case MediaFormat.COLOR_TRANSFER_ST2084: 276 return "st2084"; 277 default: 278 return "unknown"; 279 } 280 } 281 colorFormatToString(int colorFormat, int bitDepth)282 public static String colorFormatToString(int colorFormat, int bitDepth) { 283 switch (colorFormat) { 284 case COLOR_FormatYUV420Flexible: 285 return "yuv420flexible"; 286 case COLOR_FormatYUVP010: 287 return "yuvp010"; 288 case COLOR_FormatSurface: 289 if (bitDepth == 8) { 290 return "surfacergb888"; 291 } else if (bitDepth == 10) { 292 return "surfaceabgr2101010"; 293 } else { 294 return "unknown"; 295 } 296 default: 297 return "unknown"; 298 } 299 } 300 audioEncodingToString(int enc)301 public static String audioEncodingToString(int enc) { 302 switch (enc) { 303 case AudioFormat.ENCODING_INVALID: 304 return "invalid"; 305 case AudioFormat.ENCODING_PCM_16BIT: 306 return "pcm16"; 307 case AudioFormat.ENCODING_PCM_FLOAT: 308 return "pcmfloat"; 309 default: 310 return "unknown"; 311 } 312 } 313 314 @Before setUpCodecEncoderTestBase()315 public void setUpCodecEncoderTestBase() { 316 assertTrue("Testing a mediaType that is neither audio nor video is not supported \n" 317 + mTestConfig, mIsAudio || mIsVideo); 318 } 319 deleteMuxedFile()320 public void deleteMuxedFile() { 321 if (mMuxedOutputFile != null) { 322 File file = new File(mMuxedOutputFile); 323 if (file.exists()) { 324 assertTrue("unable to delete file " + mMuxedOutputFile, file.delete()); 325 } 326 mMuxedOutputFile = null; 327 } 328 } 329 330 @After tearDown()331 public void tearDown() { 332 if (mMuxer != null) { 333 mMuxer.release(); 334 mMuxer = null; 335 } 336 deleteMuxedFile(); 337 } 338 339 @Override resetContext(boolean isAsync, boolean signalEOSWithLastFrame)340 protected void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 341 super.resetContext(isAsync, signalEOSWithLastFrame); 342 mInputBufferReadOffset = 0; 343 mNumBytesSubmitted = 0; 344 mInputOffsetPts = 0; 345 mInfoList.clear(); 346 } 347 setUpSource(String inpPath)348 protected void setUpSource(String inpPath) throws IOException { 349 Preconditions.assertTestFileExists(inpPath); 350 try (FileInputStream fInp = new FileInputStream(inpPath)) { 351 int size = (int) new File(inpPath).length(); 352 mInputData = new byte[size]; 353 fInp.read(mInputData, 0, size); 354 } 355 } 356 fillImage(Image image)357 protected void fillImage(Image image) { 358 int format = image.getFormat(); 359 assertTrue("unexpected image format \n" + mTestConfig + mTestEnv, 360 format == ImageFormat.YUV_420_888 || format == ImageFormat.YCBCR_P010); 361 int bytesPerSample = (ImageFormat.getBitsPerPixel(format) * 2) / (8 * 3); // YUV420 362 assertEquals("Invalid bytes per sample \n" + mTestConfig + mTestEnv, bytesPerSample, 363 mActiveRawRes.mBytesPerSample); 364 365 int imageWidth = image.getWidth(); 366 int imageHeight = image.getHeight(); 367 Image.Plane[] planes = image.getPlanes(); 368 int offset = mInputBufferReadOffset; 369 for (int i = 0; i < planes.length; ++i) { 370 ByteBuffer buf = planes[i].getBuffer(); 371 int width = imageWidth; 372 int height = imageHeight; 373 int tileWidth = mActiveRawRes.mWidth; 374 int tileHeight = mActiveRawRes.mHeight; 375 int rowStride = planes[i].getRowStride(); 376 int pixelStride = planes[i].getPixelStride(); 377 if (i != 0) { 378 width = imageWidth / 2; 379 height = imageHeight / 2; 380 tileWidth = mActiveRawRes.mWidth / 2; 381 tileHeight = mActiveRawRes.mHeight / 2; 382 } 383 if (pixelStride == bytesPerSample) { 384 if (width == rowStride && width == tileWidth && height == tileHeight) { 385 buf.put(mInputData, offset, width * height * bytesPerSample); 386 } else { 387 for (int z = 0; z < height; z += tileHeight) { 388 int rowsToCopy = Math.min(height - z, tileHeight); 389 for (int y = 0; y < rowsToCopy; y++) { 390 for (int x = 0; x < width; x += tileWidth) { 391 int colsToCopy = Math.min(width - x, tileWidth); 392 buf.position((z + y) * rowStride + x * bytesPerSample); 393 buf.put(mInputData, offset + y * tileWidth * bytesPerSample, 394 colsToCopy * bytesPerSample); 395 } 396 } 397 } 398 } 399 } else { 400 // do it pixel-by-pixel 401 for (int z = 0; z < height; z += tileHeight) { 402 int rowsToCopy = Math.min(height - z, tileHeight); 403 for (int y = 0; y < rowsToCopy; y++) { 404 int lineOffset = (z + y) * rowStride; 405 for (int x = 0; x < width; x += tileWidth) { 406 int colsToCopy = Math.min(width - x, tileWidth); 407 for (int w = 0; w < colsToCopy; w++) { 408 for (int bytePos = 0; bytePos < bytesPerSample; bytePos++) { 409 buf.position(lineOffset + (x + w) * pixelStride + bytePos); 410 buf.put(mInputData[offset + y * tileWidth * bytesPerSample 411 + w * bytesPerSample + bytePos]); 412 } 413 } 414 } 415 } 416 } 417 } 418 offset += tileWidth * tileHeight * bytesPerSample; 419 } 420 } 421 fillByteBuffer(ByteBuffer inputBuffer)422 void fillByteBuffer(ByteBuffer inputBuffer) { 423 int offset = 0, frmOffset = mInputBufferReadOffset; 424 for (int plane = 0; plane < 3; plane++) { 425 int width = mActiveEncCfg.mWidth; 426 int height = mActiveEncCfg.mHeight; 427 int tileWidth = mActiveRawRes.mWidth; 428 int tileHeight = mActiveRawRes.mHeight; 429 if (plane != 0) { 430 width = mActiveEncCfg.mWidth / 2; 431 height = mActiveEncCfg.mHeight / 2; 432 tileWidth = mActiveRawRes.mWidth / 2; 433 tileHeight = mActiveRawRes.mHeight / 2; 434 } 435 for (int k = 0; k < height; k += tileHeight) { 436 int rowsToCopy = Math.min(height - k, tileHeight); 437 for (int j = 0; j < rowsToCopy; j++) { 438 for (int i = 0; i < width; i += tileWidth) { 439 int colsToCopy = Math.min(width - i, tileWidth); 440 inputBuffer.position( 441 offset + (k + j) * width * mActiveRawRes.mBytesPerSample 442 + i * mActiveRawRes.mBytesPerSample); 443 inputBuffer.put(mInputData, 444 frmOffset + j * tileWidth * mActiveRawRes.mBytesPerSample, 445 colsToCopy * mActiveRawRes.mBytesPerSample); 446 } 447 } 448 } 449 offset += width * height * mActiveRawRes.mBytesPerSample; 450 frmOffset += tileWidth * tileHeight * mActiveRawRes.mBytesPerSample; 451 } 452 } 453 enqueueInput(int bufferIndex)454 protected void enqueueInput(int bufferIndex) { 455 if (mIsLoopBack && mInputBufferReadOffset >= mInputData.length) { 456 mInputBufferReadOffset = 0; 457 } 458 ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex); 459 if (mInputBufferReadOffset >= mInputData.length) { 460 enqueueEOS(bufferIndex); 461 } else { 462 int size; 463 int flags = 0; 464 long pts = mInputOffsetPts; 465 if (mIsAudio) { 466 pts += mNumBytesSubmitted * 1000000L / ((long) mActiveRawRes.mBytesPerSample 467 * mActiveEncCfg.mChannelCount * mActiveEncCfg.mSampleRate); 468 size = Math.min(inputBuffer.capacity(), mInputData.length - mInputBufferReadOffset); 469 assertEquals(0, size % ((long) mActiveRawRes.mBytesPerSample 470 * mActiveEncCfg.mChannelCount)); 471 inputBuffer.put(mInputData, mInputBufferReadOffset, size); 472 if (mSignalEOSWithLastFrame) { 473 if (mIsLoopBack ? (mInputCount + 1 >= mLoopBackFrameLimit) : 474 (mInputBufferReadOffset + size >= mInputData.length)) { 475 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 476 mSawInputEOS = true; 477 } 478 } 479 mInputBufferReadOffset += size; 480 } else { 481 pts += mInputCount * 1000000L / mActiveEncCfg.mFrameRate; 482 size = mActiveRawRes.mBytesPerSample * mActiveEncCfg.mWidth * mActiveEncCfg.mHeight 483 * 3 / 2; 484 int frmSize = mActiveRawRes.mBytesPerSample * mActiveRawRes.mWidth 485 * mActiveRawRes.mHeight * 3 / 2; 486 if (mInputBufferReadOffset + frmSize > mInputData.length) { 487 fail("received partial frame to encode \n" + mTestConfig + mTestEnv); 488 } else { 489 Image img = mCodec.getInputImage(bufferIndex); 490 assertNotNull("getInputImage() expected to return non-null for video \n" 491 + mTestConfig + mTestEnv, img); 492 fillImage(img); 493 } 494 if (mSignalEOSWithLastFrame) { 495 if (mIsLoopBack ? (mInputCount + 1 >= mLoopBackFrameLimit) : 496 (mInputBufferReadOffset + frmSize >= mInputData.length)) { 497 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 498 mSawInputEOS = true; 499 } 500 } 501 mInputBufferReadOffset += frmSize; 502 } 503 mNumBytesSubmitted += size; 504 if (ENABLE_LOGS) { 505 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts 506 + " flags: " + flags); 507 } 508 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags); 509 mOutputBuff.saveInPTS(pts); 510 mInputCount++; 511 } 512 } 513 dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)514 protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 515 if (ENABLE_LOGS) { 516 Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " 517 + info.size + " timestamp: " + info.presentationTimeUs); 518 } 519 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 520 mSawOutputEOS = true; 521 } 522 if (info.size > 0) { 523 ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex); 524 if (mSaveToMem) { 525 MediaCodec.BufferInfo copy = new MediaCodec.BufferInfo(); 526 copy.set(mOutputBuff.getOutStreamSize(), info.size, info.presentationTimeUs, 527 info.flags); 528 mInfoList.add(copy); 529 530 mOutputBuff.checksum(buf, info); 531 mOutputBuff.saveToMemory(buf, info); 532 } 533 if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 534 mOutputBuff.saveOutPTS(info.presentationTimeUs); 535 mOutputCount++; 536 } 537 if (mMuxer != null) { 538 if (mTrackID == -1) { 539 mTrackID = mMuxer.addTrack(mCodec.getOutputFormat()); 540 mMuxer.start(); 541 } 542 if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 543 mMuxer.writeSampleData(mTrackID, buf, info); 544 } 545 } 546 } 547 mCodec.releaseOutputBuffer(bufferIndex, false); 548 } 549 550 @Override doWork(int frameLimit)551 protected void doWork(int frameLimit) throws IOException, InterruptedException { 552 mLoopBackFrameLimit = frameLimit; 553 if (mMuxOutput) { 554 int muxerFormat = getMuxerFormatForMediaType(mMediaType); 555 mMuxedOutputFile = getTempFilePath((mActiveEncCfg.mInputBitDepth == 10) ? "10bit" : ""); 556 mMuxer = new MediaMuxer(mMuxedOutputFile, muxerFormat); 557 } 558 super.doWork(frameLimit); 559 } 560 561 @Override waitForAllOutputs()562 protected void waitForAllOutputs() throws InterruptedException { 563 super.waitForAllOutputs(); 564 if (mMuxOutput) { 565 if (mTrackID != -1) { 566 mMuxer.stop(); 567 mTrackID = -1; 568 } 569 if (mMuxer != null) { 570 mMuxer.release(); 571 mMuxer = null; 572 } 573 } 574 } 575 576 @Override validateMetrics(String codec, MediaFormat format)577 protected PersistableBundle validateMetrics(String codec, MediaFormat format) { 578 PersistableBundle metrics = super.validateMetrics(codec, format); 579 assertEquals("error! metrics#MetricsConstants.MIME_TYPE is not as expected \n" + mTestConfig 580 + mTestEnv, metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE), mMediaType); 581 assertEquals("error! metrics#MetricsConstants.ENCODER is not as expected \n" + mTestConfig 582 + mTestEnv, 1, metrics.getInt(MediaCodec.MetricsConstants.ENCODER)); 583 return metrics; 584 } 585 encodeToMemory(String encoder, EncoderConfigParams cfg, RawResource res, OutputManager outputBuff, int frameLimit, boolean saveToMem, boolean muxOutput)586 public void encodeToMemory(String encoder, EncoderConfigParams cfg, RawResource res, 587 OutputManager outputBuff, int frameLimit, boolean saveToMem, boolean muxOutput) 588 throws IOException, InterruptedException { 589 mSaveToMem = saveToMem; 590 mMuxOutput = muxOutput; 591 mOutputBuff = outputBuff; 592 mInfoList.clear(); 593 mActiveEncCfg = cfg; 594 mActiveRawRes = res; 595 mCodec = MediaCodec.createByCodecName(encoder); 596 setUpSource(mActiveRawRes.mFileName); 597 configureCodec(mActiveEncCfg.getFormat(), false, true, true); 598 mCodec.start(); 599 doWork(frameLimit); 600 queueEOS(); 601 waitForAllOutputs(); 602 mCodec.stop(); 603 mCodec.release(); 604 mActiveRawRes = null; 605 mActiveEncCfg = null; 606 mSaveToMem = false; 607 mMuxOutput = false; 608 } 609 encodeToMemory(String encoder, EncoderConfigParams cfg, RawResource res, int frameLimit, boolean saveToMem, boolean muxOutput)610 public void encodeToMemory(String encoder, EncoderConfigParams cfg, RawResource res, 611 int frameLimit, boolean saveToMem, boolean muxOutput) 612 throws IOException, InterruptedException { 613 encodeToMemory(encoder, cfg, res, new OutputManager(), frameLimit, saveToMem, muxOutput); 614 } 615 setLoopBack(boolean loopBack)616 public void setLoopBack(boolean loopBack) { 617 mIsLoopBack = loopBack; 618 } 619 getMuxedOutputFilePath()620 public String getMuxedOutputFilePath() { 621 return mMuxedOutputFile; 622 } 623 validateTestState()624 protected void validateTestState() { 625 super.validateTestState(); 626 if ((mIsAudio || (mIsVideo && mActiveEncCfg.mMaxBFrames == 0)) 627 && !mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts)) { 628 fail("Output timestamps are not strictly increasing \n" + mTestConfig + mTestEnv 629 + mOutputBuff.getErrMsg()); 630 } 631 if (mIsVideo) { 632 if (!mOutputBuff.isOutPtsListIdenticalToInpPtsList((mActiveEncCfg.mMaxBFrames != 0))) { 633 fail("Input pts list and Output pts list are not identical \n" + mTestConfig 634 + mTestEnv + mOutputBuff.getErrMsg()); 635 } 636 } 637 } 638 } 639