• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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