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