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