1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.mediav2.cts;
18 
19 import android.content.pm.PackageManager;
20 import android.graphics.ImageFormat;
21 import android.graphics.Rect;
22 import android.media.Image;
23 import android.media.MediaCodec;
24 import android.media.MediaCodecInfo;
25 import android.media.MediaCodecList;
26 import android.media.MediaExtractor;
27 import android.media.MediaFormat;
28 import android.os.Build;
29 import android.os.PersistableBundle;
30 import android.util.Log;
31 import android.util.Pair;
32 import android.view.Surface;
33 
34 import androidx.annotation.NonNull;
35 import androidx.test.platform.app.InstrumentationRegistry;
36 
37 import org.junit.Assert;
38 import org.junit.Before;
39 
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.IOException;
43 import java.nio.ByteBuffer;
44 import java.nio.ByteOrder;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Set;
54 import java.util.concurrent.locks.Condition;
55 import java.util.concurrent.locks.Lock;
56 import java.util.concurrent.locks.ReentrantLock;
57 import java.util.zip.CRC32;
58 
59 import com.android.compatibility.common.util.ApiLevelUtil;
60 
61 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
62 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
63 import static org.junit.Assert.assertEquals;
64 import static org.junit.Assert.assertTrue;
65 import static org.junit.Assert.fail;
66 
67 class CodecAsyncHandler extends MediaCodec.Callback {
68     private static final String LOG_TAG = CodecAsyncHandler.class.getSimpleName();
69     private final Lock mLock = new ReentrantLock();
70     private final Condition mCondition = mLock.newCondition();
71     private final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mCbInputQueue;
72     private final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mCbOutputQueue;
73     private MediaFormat mOutFormat;
74     private boolean mSignalledOutFormatChanged;
75     private volatile boolean mSignalledError;
76 
CodecAsyncHandler()77     CodecAsyncHandler() {
78         mCbInputQueue = new LinkedList<>();
79         mCbOutputQueue = new LinkedList<>();
80         mSignalledError = false;
81         mSignalledOutFormatChanged = false;
82     }
83 
clearQueues()84     void clearQueues() {
85         mLock.lock();
86         mCbInputQueue.clear();
87         mCbOutputQueue.clear();
88         mLock.unlock();
89     }
90 
resetContext()91     void resetContext() {
92         clearQueues();
93         mOutFormat = null;
94         mSignalledOutFormatChanged = false;
95         mSignalledError = false;
96     }
97 
98     @Override
onInputBufferAvailable(@onNull MediaCodec codec, int bufferIndex)99     public void onInputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex) {
100         assertTrue(bufferIndex >= 0);
101         mLock.lock();
102         mCbInputQueue.add(new Pair<>(bufferIndex, (MediaCodec.BufferInfo) null));
103         mCondition.signalAll();
104         mLock.unlock();
105     }
106 
107     @Override
onOutputBufferAvailable(@onNull MediaCodec codec, int bufferIndex, @NonNull MediaCodec.BufferInfo info)108     public void onOutputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex,
109             @NonNull MediaCodec.BufferInfo info) {
110         assertTrue(bufferIndex >= 0);
111         mLock.lock();
112         mCbOutputQueue.add(new Pair<>(bufferIndex, info));
113         mCondition.signalAll();
114         mLock.unlock();
115     }
116 
117     @Override
onError(@onNull MediaCodec codec, MediaCodec.CodecException e)118     public void onError(@NonNull MediaCodec codec, MediaCodec.CodecException e) {
119         mLock.lock();
120         mSignalledError = true;
121         mCondition.signalAll();
122         mLock.unlock();
123         Log.e(LOG_TAG, "received media codec error : " + e.getMessage());
124     }
125 
126     @Override
onOutputFormatChanged(@onNull MediaCodec codec, @NonNull MediaFormat format)127     public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
128         mOutFormat = format;
129         mSignalledOutFormatChanged = true;
130         Log.i(LOG_TAG, "Output format changed: " + format.toString());
131     }
132 
setCallBack(MediaCodec codec, boolean isCodecInAsyncMode)133     void setCallBack(MediaCodec codec, boolean isCodecInAsyncMode) {
134         if (isCodecInAsyncMode) {
135             codec.setCallback(this);
136         } else {
137             codec.setCallback(null);
138         }
139     }
140 
getInput()141     Pair<Integer, MediaCodec.BufferInfo> getInput() throws InterruptedException {
142         Pair<Integer, MediaCodec.BufferInfo> element = null;
143         mLock.lock();
144         while (!mSignalledError) {
145             if (mCbInputQueue.isEmpty()) {
146                 mCondition.await();
147             } else {
148                 element = mCbInputQueue.remove(0);
149                 break;
150             }
151         }
152         mLock.unlock();
153         return element;
154     }
155 
getOutput()156     Pair<Integer, MediaCodec.BufferInfo> getOutput() throws InterruptedException {
157         Pair<Integer, MediaCodec.BufferInfo> element = null;
158         mLock.lock();
159         while (!mSignalledError) {
160             if (mCbOutputQueue.isEmpty()) {
161                 mCondition.await();
162             } else {
163                 element = mCbOutputQueue.remove(0);
164                 break;
165             }
166         }
167         mLock.unlock();
168         return element;
169     }
170 
getWork()171     Pair<Integer, MediaCodec.BufferInfo> getWork() throws InterruptedException {
172         Pair<Integer, MediaCodec.BufferInfo> element = null;
173         mLock.lock();
174         while (!mSignalledError) {
175             if (mCbInputQueue.isEmpty() && mCbOutputQueue.isEmpty()) {
176                 mCondition.await();
177             } else {
178                 if (!mCbOutputQueue.isEmpty()) {
179                     element = mCbOutputQueue.remove(0);
180                     break;
181                 }
182                 if (!mCbInputQueue.isEmpty()) {
183                     element = mCbInputQueue.remove(0);
184                     break;
185                 }
186             }
187         }
188         mLock.unlock();
189         return element;
190     }
191 
isInputQueueEmpty()192     boolean isInputQueueEmpty() {
193         mLock.lock();
194         boolean isEmpty = mCbInputQueue.isEmpty();
195         mLock.unlock();
196         return isEmpty;
197     }
198 
hasSeenError()199     boolean hasSeenError() {
200         return mSignalledError;
201     }
202 
hasOutputFormatChanged()203     boolean hasOutputFormatChanged() {
204         return mSignalledOutFormatChanged;
205     }
206 
getOutputFormat()207     MediaFormat getOutputFormat() {
208         return mOutFormat;
209     }
210 }
211 
212 class OutputManager {
213     private static final String LOG_TAG = OutputManager.class.getSimpleName();
214     private byte[] memory;
215     private int memIndex;
216     private CRC32 mCrc32UsingImage;
217     private CRC32 mCrc32UsingBuffer;
218     private ArrayList<Long> inpPtsList;
219     private ArrayList<Long> outPtsList;
220 
OutputManager()221     OutputManager() {
222         memory = new byte[1024];
223         memIndex = 0;
224         mCrc32UsingImage = new CRC32();
225         mCrc32UsingBuffer = new CRC32();
226         inpPtsList = new ArrayList<>();
227         outPtsList = new ArrayList<>();
228     }
229 
saveInPTS(long pts)230     void saveInPTS(long pts) {
231         // Add only Unique timeStamp, discarding any duplicate frame / non-display frame
232         if (!inpPtsList.contains(pts)) {
233             inpPtsList.add(pts);
234         }
235     }
236 
saveOutPTS(long pts)237     void saveOutPTS(long pts) {
238         outPtsList.add(pts);
239     }
240 
isPtsStrictlyIncreasing(long lastPts)241     boolean isPtsStrictlyIncreasing(long lastPts) {
242         boolean res = true;
243         for (int i = 0; i < outPtsList.size(); i++) {
244             if (lastPts < outPtsList.get(i)) {
245                 lastPts = outPtsList.get(i);
246             } else {
247                 Log.e(LOG_TAG, "Timestamp ordering check failed: last timestamp: " + lastPts +
248                         " current timestamp:" + outPtsList.get(i));
249                 res = false;
250                 break;
251             }
252         }
253         return res;
254     }
255 
isOutPtsListIdenticalToInpPtsList(boolean requireSorting)256     boolean isOutPtsListIdenticalToInpPtsList(boolean requireSorting) {
257         boolean res;
258         Collections.sort(inpPtsList);
259         if (requireSorting) {
260             Collections.sort(outPtsList);
261         }
262         if (outPtsList.size() != inpPtsList.size()) {
263             Log.e(LOG_TAG, "input and output presentation timestamp list sizes are not identical" +
264                     "exp/rec" + inpPtsList.size() + '/' + outPtsList.size());
265             return false;
266         } else {
267             int count = 0;
268             for (int i = 0; i < outPtsList.size(); i++) {
269                 if (!outPtsList.get(i).equals(inpPtsList.get(i))) {
270                     count ++;
271                     Log.e(LOG_TAG, "input output pts mismatch, exp/rec " + outPtsList.get(i) + '/' +
272                             inpPtsList.get(i));
273                     if (count == 20) {
274                         Log.e(LOG_TAG, "stopping after 20 mismatches, ...");
275                         break;
276                     }
277                 }
278             }
279             res = (count == 0);
280         }
281         return res;
282     }
283 
getOutStreamSize()284     int getOutStreamSize() {
285         return memIndex;
286     }
287 
checksum(ByteBuffer buf, int size)288     void checksum(ByteBuffer buf, int size) {
289         checksum(buf, size, 0, 0, 0);
290     }
291 
checksum(ByteBuffer buf, int size, int width, int height, int stride)292     void checksum(ByteBuffer buf, int size, int width, int height, int stride) {
293         int cap = buf.capacity();
294         assertTrue("checksum() params are invalid: size = " + size + " cap = " + cap,
295                 size > 0 && size <= cap);
296         if (buf.hasArray()) {
297             if (width > 0 && height > 0 && stride > 0) {
298                 int offset = buf.position() + buf.arrayOffset();
299                 byte[] bb = new byte[width * height];
300                 for (int i = 0; i < height; ++i) {
301                     System.arraycopy(buf.array(), offset, bb, i * width, width);
302                     offset += stride;
303                 }
304                 mCrc32UsingBuffer.update(bb, 0, width * height);
305             } else {
306                 mCrc32UsingBuffer.update(buf.array(), buf.position() + buf.arrayOffset(), size);
307             }
308         } else if (width > 0 && height > 0 && stride > 0) {
309             // Checksum only the Y plane
310             int pos = buf.position();
311             int offset = pos;
312             byte[] bb = new byte[width * height];
313             for (int i = 0; i < height; ++i) {
314                 buf.position(offset);
315                 buf.get(bb, i * width, width);
316                 offset += stride;
317             }
318             mCrc32UsingBuffer.update(bb, 0, width * height);
319             buf.position(pos);
320         } else {
321             int pos = buf.position();
322             final int rdsize = Math.min(4096, size);
323             byte[] bb = new byte[rdsize];
324             int chk;
325             for (int i = 0; i < size; i += chk) {
326                 chk = Math.min(rdsize, size - i);
327                 buf.get(bb, 0, chk);
328                 mCrc32UsingBuffer.update(bb, 0, chk);
329             }
330             buf.position(pos);
331         }
332     }
333 
checksum(Image image)334     void checksum(Image image) {
335         int format = image.getFormat();
336         assertEquals("unexpected image format", ImageFormat.YUV_420_888, format);
337 
338         Rect cropRect = image.getCropRect();
339         int imageWidth = cropRect.width();
340         int imageHeight = cropRect.height();
341         assertTrue("unexpected image dimensions", imageWidth > 0 && imageHeight > 0);
342 
343         int imageLeft = cropRect.left;
344         int imageTop = cropRect.top;
345         Image.Plane[] planes = image.getPlanes();
346         for (int i = 0; i < planes.length; ++i) {
347             ByteBuffer buf = planes[i].getBuffer();
348             int width, height, rowStride, pixelStride, x, y, left, top;
349             rowStride = planes[i].getRowStride();
350             pixelStride = planes[i].getPixelStride();
351             if (i == 0) {
352                 width = imageWidth;
353                 height = imageHeight;
354                 left = imageLeft;
355                 top = imageTop;
356             } else {
357                 width = imageWidth / 2;
358                 height = imageHeight / 2;
359                 left = imageLeft / 2;
360                 top = imageTop / 2;
361             }
362             int cropOffset = left + top * rowStride;
363             // local contiguous pixel buffer
364             byte[] bb = new byte[width * height];
365             if (buf.hasArray()) {
366                 byte[] b = buf.array();
367                 int offs = buf.arrayOffset() + cropOffset;
368                 if (pixelStride == 1) {
369                     for (y = 0; y < height; ++y) {
370                         System.arraycopy(b, offs + y * rowStride, bb, y * width, width);
371                     }
372                 } else {
373                     // do it pixel-by-pixel
374                     for (y = 0; y < height; ++y) {
375                         int lineOffset = offs + y * rowStride;
376                         for (x = 0; x < width; ++x) {
377                             bb[y * width + x] = b[lineOffset + x * pixelStride];
378                         }
379                     }
380                 }
381             } else { // almost always ends up here due to direct buffers
382                 int base = buf.position();
383                 int pos = base + cropOffset;
384                 if (pixelStride == 1) {
385                     for (y = 0; y < height; ++y) {
386                         buf.position(pos + y * rowStride);
387                         buf.get(bb, y * width, width);
388                     }
389                 } else {
390                     // local line buffer
391                     byte[] lb = new byte[rowStride];
392                     // do it pixel-by-pixel
393                     for (y = 0; y < height; ++y) {
394                         buf.position(pos + y * rowStride);
395                         // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes
396                         buf.get(lb, 0, pixelStride * (width - 1) + 1);
397                         for (x = 0; x < width; ++x) {
398                             bb[y * width + x] = lb[x * pixelStride];
399                         }
400                     }
401                 }
402                 buf.position(base);
403             }
404             mCrc32UsingImage.update(bb, 0, width * height);
405         }
406     }
407 
saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info)408     void saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info) {
409         if (memIndex + info.size >= memory.length) {
410             memory = Arrays.copyOf(memory, memIndex + info.size);
411         }
412         buf.position(info.offset);
413         buf.get(memory, memIndex, info.size);
414         memIndex += info.size;
415     }
416 
position(int index)417     void position(int index) {
418         if (index < 0 || index >= memory.length) index = 0;
419         memIndex = index;
420     }
421 
getBuffer()422     ByteBuffer getBuffer() {
423         return ByteBuffer.wrap(memory);
424     }
425 
reset()426     void reset() {
427         position(0);
428         mCrc32UsingImage.reset();
429         mCrc32UsingBuffer.reset();
430         inpPtsList.clear();
431         outPtsList.clear();
432     }
433 
getRmsError(short[] refData)434     float getRmsError(short[] refData) {
435         long totalErrorSquared = 0;
436         assertTrue(0 == (memIndex & 1));
437         short[] shortData = new short[memIndex / 2];
438         ByteBuffer.wrap(memory, 0, memIndex).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer()
439                 .get(shortData);
440         if (refData.length != shortData.length) return Float.MAX_VALUE;
441         for (int i = 0; i < shortData.length; i++) {
442             int d = shortData[i] - refData[i];
443             totalErrorSquared += d * d;
444         }
445         long avgErrorSquared = (totalErrorSquared / shortData.length);
446         return (float) Math.sqrt(avgErrorSquared);
447     }
448 
getCheckSumImage()449     long getCheckSumImage() {
450         return mCrc32UsingImage.getValue();
451     }
452 
getCheckSumBuffer()453     long getCheckSumBuffer() {
454         return mCrc32UsingBuffer.getValue();
455     }
456 
457     @Override
equals(Object o)458     public boolean equals(Object o) {
459         if (this == o) return true;
460         if (o == null || getClass() != o.getClass()) return false;
461         OutputManager that = (OutputManager) o;
462         // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
463         // produce multiple progressive frames?) For now, do not verify timestamps.
464         boolean isEqual = this.equalsInterlaced(o);
465         if (!outPtsList.equals(that.outPtsList)) {
466             isEqual = false;
467             Log.e(LOG_TAG, "ref and test presentation timestamp mismatch");
468         }
469         return isEqual;
470     }
471 
equalsInterlaced(Object o)472     public boolean equalsInterlaced(Object o) {
473         if (this == o) return true;
474         if (o == null || getClass() != o.getClass()) return false;
475         OutputManager that = (OutputManager) o;
476         boolean isEqual = true;
477         if (mCrc32UsingImage.getValue() != that.mCrc32UsingImage.getValue()) {
478             isEqual = false;
479             Log.e(LOG_TAG, "ref and test crc32 checksums calculated using image mismatch " +
480                           mCrc32UsingImage.getValue() + '/' + that.mCrc32UsingImage.getValue());
481         }
482         if (mCrc32UsingBuffer.getValue() != that.mCrc32UsingBuffer.getValue()) {
483             isEqual = false;
484             Log.e(LOG_TAG, "ref and test crc32 checksums calculated using buffer mismatch " +
485                           mCrc32UsingBuffer.getValue() + '/' + that.mCrc32UsingBuffer.getValue());
486             if (memIndex == that.memIndex) {
487                 int count = 0;
488                 for (int i = 0; i < memIndex; i++) {
489                     if (memory[i] != that.memory[i]) {
490                         count++;
491                         if (count < 20) {
492                             Log.d(LOG_TAG, "sample at " + i + " exp/got:: " + memory[i] + '/' +
493                                     that.memory[i]);
494                         }
495                     }
496                 }
497                 if (count != 0) {
498                     Log.e(LOG_TAG, "ref and test o/p samples mismatch " + count);
499                 }
500             } else {
501                 Log.e(LOG_TAG, "ref and test o/p sizes mismatch " + memIndex + '/' + that.memIndex);
502             }
503         }
504         return isEqual;
505     }
506 }
507 
508 abstract class CodecTestBase {
509     public static final boolean IS_AT_LEAST_R = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
510     private static final String LOG_TAG = CodecTestBase.class.getSimpleName();
511 
512     static final String CODEC_PREFIX_KEY = "codec-prefix";
513     static final String MIME_SEL_KEY = "mime-sel";
514     static final Map<String, String> codecSelKeyMimeMap = new HashMap<>();
515     static final boolean ENABLE_LOGS = false;
516     static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000;
517     static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000;
518     static final int UNSPECIFIED = 0;
519     static final int CODEC_ALL = 0; // All codecs should support
520     static final int CODEC_ANY = 1; // Atleast one codec should support
521     static final int CODEC_OPTIONAL = 2; // Codec support is optional
522     // Maintain Timeouts in sync with their counterpart in NativeMediaCommon.h
523     static final long Q_DEQ_TIMEOUT_US = 5000; // block at most 5ms while looking for io buffers
524     static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error
525     static final String INVALID_CODEC = "unknown.codec_";
526     static final String mInpPrefix = WorkDir.getMediaDirString();
527     static final PackageManager pm =
528             InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
529     static String mimeSelKeys;
530     static String codecPrefix;
531 
532     CodecAsyncHandler mAsyncHandle;
533     boolean mIsCodecInAsyncMode;
534     boolean mSawInputEOS;
535     boolean mSawOutputEOS;
536     boolean mSignalEOSWithLastFrame;
537     int mInputCount;
538     int mOutputCount;
539     long mPrevOutputPts;
540     boolean mSignalledOutFormatChanged;
541     MediaFormat mOutFormat;
542     boolean mIsAudio;
543 
544     boolean mSaveToMem;
545     OutputManager mOutputBuff;
546 
547     String mCodecName;
548     MediaCodec mCodec;
549     Surface mSurface;
550 
551     static {
552         System.loadLibrary("ctsmediav2codec_jni");
553 
554         codecSelKeyMimeMap.put("vp8", MediaFormat.MIMETYPE_VIDEO_VP8);
555         codecSelKeyMimeMap.put("vp9", MediaFormat.MIMETYPE_VIDEO_VP9);
556         codecSelKeyMimeMap.put("av1", MediaFormat.MIMETYPE_VIDEO_AV1);
557         codecSelKeyMimeMap.put("avc", MediaFormat.MIMETYPE_VIDEO_AVC);
558         codecSelKeyMimeMap.put("hevc", MediaFormat.MIMETYPE_VIDEO_HEVC);
559         codecSelKeyMimeMap.put("mpeg4", MediaFormat.MIMETYPE_VIDEO_MPEG4);
560         codecSelKeyMimeMap.put("h263", MediaFormat.MIMETYPE_VIDEO_H263);
561         codecSelKeyMimeMap.put("mpeg2", MediaFormat.MIMETYPE_VIDEO_MPEG2);
562         codecSelKeyMimeMap.put("vraw", MediaFormat.MIMETYPE_VIDEO_RAW);
563         codecSelKeyMimeMap.put("amrnb", MediaFormat.MIMETYPE_AUDIO_AMR_NB);
564         codecSelKeyMimeMap.put("amrwb", MediaFormat.MIMETYPE_AUDIO_AMR_WB);
565         codecSelKeyMimeMap.put("mp3", MediaFormat.MIMETYPE_AUDIO_MPEG);
566         codecSelKeyMimeMap.put("aac", MediaFormat.MIMETYPE_AUDIO_AAC);
567         codecSelKeyMimeMap.put("vorbis", MediaFormat.MIMETYPE_AUDIO_VORBIS);
568         codecSelKeyMimeMap.put("opus", MediaFormat.MIMETYPE_AUDIO_OPUS);
569         codecSelKeyMimeMap.put("g711alaw", MediaFormat.MIMETYPE_AUDIO_G711_ALAW);
570         codecSelKeyMimeMap.put("g711mlaw", MediaFormat.MIMETYPE_AUDIO_G711_MLAW);
571         codecSelKeyMimeMap.put("araw", MediaFormat.MIMETYPE_AUDIO_RAW);
572         codecSelKeyMimeMap.put("flac", MediaFormat.MIMETYPE_AUDIO_FLAC);
573         codecSelKeyMimeMap.put("gsm", MediaFormat.MIMETYPE_AUDIO_MSGSM);
574 
575         android.os.Bundle args = InstrumentationRegistry.getArguments();
576         mimeSelKeys = args.getString(MIME_SEL_KEY);
577         codecPrefix = args.getString(CODEC_PREFIX_KEY);
578     }
579 
isTv()580     static boolean isTv() {
581         return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
582     }
583 
hasMicrophone()584     static boolean hasMicrophone() {
585         return pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
586     }
587 
hasCamera()588     static boolean hasCamera() {
589         return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
590     }
591 
isWatch()592     static boolean isWatch() {
593         return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
594     }
595 
isAutomotive()596     static boolean isAutomotive() {
597         return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
598     }
599 
isPc()600     static boolean isPc() {
601         return pm.hasSystemFeature(PackageManager.FEATURE_PC);
602     }
603 
hasAudioOutput()604     static boolean hasAudioOutput() {
605         return pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
606     }
607 
isHandheld()608     static boolean isHandheld() {
609         // handheld nature is not exposed to package manager, for now
610         // we check for touchscreen and NOT watch and NOT tv and NOT pc
611         return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) && !isWatch() && !isTv() &&
612                 !isAutomotive() && !isPc();
613     }
614 
hasDecoder(String mime)615     static boolean hasDecoder(String mime) {
616         return CodecTestBase.selectCodecs(mime, null, null, false).size() != 0;
617     }
618 
hasEncoder(String mime)619     static boolean hasEncoder(String mime) {
620         return CodecTestBase.selectCodecs(mime, null, null, true).size() != 0;
621     }
622 
isFeatureSupported(String name, String mime, String feature)623     static boolean isFeatureSupported(String name, String mime, String feature) throws IOException {
624         MediaCodec codec = MediaCodec.createByCodecName(name);
625         MediaCodecInfo.CodecCapabilities codecCapabilities =
626                 codec.getCodecInfo().getCapabilitiesForType(mime);
627         boolean isSupported = codecCapabilities.isFeatureSupported(feature);
628         codec.release();
629         return isSupported;
630     }
631 
areFormatsSupported(String name, String mime, ArrayList<MediaFormat> formats)632     static boolean areFormatsSupported(String name, String mime, ArrayList<MediaFormat> formats)
633             throws IOException {
634         MediaCodec codec = MediaCodec.createByCodecName(name);
635         MediaCodecInfo.CodecCapabilities codecCapabilities =
636                 codec.getCodecInfo().getCapabilitiesForType(mime);
637         boolean isSupported = true;
638         if (formats != null) {
639             for (int i = 0; i < formats.size() && isSupported; i++) {
640                 isSupported = codecCapabilities.isFormatSupported(formats.get(i));
641             }
642         }
643         codec.release();
644         return isSupported;
645     }
646 
compileRequiredMimeList(boolean isEncoder, boolean needAudio, boolean needVideo)647     static ArrayList<String> compileRequiredMimeList(boolean isEncoder, boolean needAudio,
648             boolean needVideo) {
649         Set<String> list = new HashSet<>();
650         if (!isEncoder) {
651             if (hasAudioOutput() && needAudio) {
652                 // sec 5.1.2
653                 list.add(MediaFormat.MIMETYPE_AUDIO_AAC);
654                 list.add(MediaFormat.MIMETYPE_AUDIO_FLAC);
655                 list.add(MediaFormat.MIMETYPE_AUDIO_MPEG);
656                 list.add(MediaFormat.MIMETYPE_AUDIO_VORBIS);
657                 list.add(MediaFormat.MIMETYPE_AUDIO_RAW);
658                 list.add(MediaFormat.MIMETYPE_AUDIO_OPUS);
659             }
660             if (isHandheld() || isTv() || isAutomotive()) {
661                 // sec 2.2.2, 2.3.2, 2.5.2
662                 if (needAudio) {
663                     list.add(MediaFormat.MIMETYPE_AUDIO_AAC);
664                 }
665                 if (needVideo) {
666                     list.add(MediaFormat.MIMETYPE_VIDEO_AVC);
667                     list.add(MediaFormat.MIMETYPE_VIDEO_MPEG4);
668                     list.add(MediaFormat.MIMETYPE_VIDEO_H263);
669                     list.add(MediaFormat.MIMETYPE_VIDEO_VP8);
670                     list.add(MediaFormat.MIMETYPE_VIDEO_VP9);
671                 }
672             }
673             if (isHandheld()) {
674                 // sec 2.2.2
675                 if (needAudio) {
676                     list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
677                     list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
678                 }
679                 if (needVideo) {
680                     list.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
681                 }
682             }
683             if (isTv() && needVideo) {
684                 // sec 2.3.2
685                 list.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
686                 list.add(MediaFormat.MIMETYPE_VIDEO_MPEG2);
687             }
688         } else {
689             if (hasMicrophone() && needAudio) {
690                 // sec 5.1.1
691                 // TODO(b/154423550)
692                 // list.add(MediaFormat.MIMETYPE_AUDIO_RAW);
693                 list.add(MediaFormat.MIMETYPE_AUDIO_FLAC);
694                 list.add(MediaFormat.MIMETYPE_AUDIO_OPUS);
695             }
696             if (isHandheld() || isTv() || isAutomotive()) {
697                 // sec 2.2.2, 2.3.2, 2.5.2
698                 if (needAudio) {
699                     list.add(MediaFormat.MIMETYPE_AUDIO_AAC);
700                 }
701                 if (needVideo) {
702                     list.add(MediaFormat.MIMETYPE_VIDEO_AVC);
703                     list.add(MediaFormat.MIMETYPE_VIDEO_VP8);
704                 }
705             }
706             if (isHandheld() && needAudio) {
707                 // sec 2.2.2
708                 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
709                 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
710             }
711         }
712         return new ArrayList<>(list);
713     }
714 
compileCompleteTestMimeList(boolean isEncoder, boolean needAudio, boolean needVideo)715     static ArrayList<String> compileCompleteTestMimeList(boolean isEncoder, boolean needAudio,
716             boolean needVideo) {
717         ArrayList<String> mimes = new ArrayList<>();
718         if (mimeSelKeys == null) {
719             ArrayList<String> cddRequiredMimeList =
720                     compileRequiredMimeList(isEncoder, needAudio, needVideo);
721             MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
722             MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
723             for (MediaCodecInfo codecInfo : codecInfos) {
724                 if (codecInfo.isEncoder() != isEncoder) continue;
725                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
726                 String[] types = codecInfo.getSupportedTypes();
727                 for (String type : types) {
728                     if (!needAudio && type.startsWith("audio/")) continue;
729                     if (!needVideo && type.startsWith("video/")) continue;
730                     if (!mimes.contains(type)) {
731                         mimes.add(type);
732                     }
733                 }
734             }
735             // TODO(b/154423708): add checks for video o/p port and display length >= 2.5"
736             /* sec 5.2: device implementations include an embedded screen display with the
737             diagonal length of at least 2.5inches or include a video output port or declare the
738             support of a camera */
739             if (isEncoder && hasCamera() && needVideo &&
740                     !mimes.contains(MediaFormat.MIMETYPE_VIDEO_AVC) &&
741                     !mimes.contains(MediaFormat.MIMETYPE_VIDEO_VP8)) {
742                 // Add required cdd mimes here so that respective codec tests fail.
743                 mimes.add(MediaFormat.MIMETYPE_VIDEO_AVC);
744                 mimes.add(MediaFormat.MIMETYPE_VIDEO_VP8);
745                 Log.e(LOG_TAG,"device must support at least one of VP8 or AVC video encoders");
746             }
747             for (String mime : cddRequiredMimeList) {
748                 if (!mimes.contains(mime)) {
749                     // Add required cdd mimes here so that respective codec tests fail.
750                     mimes.add(mime);
751                     Log.e(LOG_TAG, "no codec found for mime " + mime + " as required by cdd");
752                 }
753             }
754         } else {
755             for (Map.Entry<String, String> entry : codecSelKeyMimeMap.entrySet()) {
756                 String key = entry.getKey();
757                 String value = entry.getValue();
758                 if (mimeSelKeys.contains(key) && !mimes.contains(value)) mimes.add(value);
759             }
760         }
761         return mimes;
762     }
763 
prepareParamList(List<Object[]> exhaustiveArgsList, boolean isEncoder, boolean needAudio, boolean needVideo, boolean mustTestAllCodecs)764     static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList, boolean isEncoder,
765             boolean needAudio, boolean needVideo, boolean mustTestAllCodecs) {
766         ArrayList<String> mimes = compileCompleteTestMimeList(isEncoder, needAudio, needVideo);
767         ArrayList<String> cddRequiredMimeList =
768                 compileRequiredMimeList(isEncoder, needAudio, needVideo);
769         final List<Object[]> argsList = new ArrayList<>();
770         int argLength = exhaustiveArgsList.get(0).length;
771         for (String mime : mimes) {
772             ArrayList<String> totalListOfCodecs = selectCodecs(mime, null, null, isEncoder);
773             ArrayList<String> listOfCodecs = new ArrayList<>();
774             if (codecPrefix != null) {
775                 for (String codec : totalListOfCodecs) {
776                     if (codec.startsWith(codecPrefix)) {
777                         listOfCodecs.add(codec);
778                     }
779                 }
780             } else {
781                 listOfCodecs = totalListOfCodecs;
782             }
783             if (mustTestAllCodecs && listOfCodecs.size() == 0 && codecPrefix == null) {
784                 listOfCodecs.add(INVALID_CODEC + mime);
785             }
786             boolean miss = true;
787             for (Object[] arg : exhaustiveArgsList) {
788                 if (mime.equals(arg[0])) {
789                     for (String codec : listOfCodecs) {
790                         Object[] arg_ = new Object[argLength + 1];
791                         arg_[0] = codec;
792                         System.arraycopy(arg, 0, arg_, 1, argLength);
793                         argsList.add(arg_);
794                     }
795                     miss = false;
796                 }
797             }
798             if (miss && mustTestAllCodecs) {
799                 if (!cddRequiredMimeList.contains(mime)) {
800                     Log.w(LOG_TAG, "no test vectors available for optional mime type " + mime);
801                     continue;
802                 }
803                 for (String codec : listOfCodecs) {
804                     Object[] arg_ = new Object[argLength + 1];
805                     arg_[0] = codec;
806                     arg_[1] = mime;
807                     System.arraycopy(exhaustiveArgsList.get(0), 1, arg_, 2, argLength - 1);
808                     argsList.add(arg_);
809                 }
810             }
811         }
812         return argsList;
813     }
814 
enqueueInput(int bufferIndex)815     abstract void enqueueInput(int bufferIndex) throws IOException;
816 
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)817     abstract void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info);
818 
configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)819     void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
820             boolean isEncoder) {
821         resetContext(isAsync, signalEOSWithLastFrame);
822         mAsyncHandle.setCallBack(mCodec, isAsync);
823         // signalEOS flag has nothing to do with configure. We are using this flag to try all
824         // available configure apis
825         if (signalEOSWithLastFrame) {
826             mCodec.configure(format, mSurface, null,
827                     isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
828         } else {
829             mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0,
830                     null);
831         }
832         if (ENABLE_LOGS) {
833             Log.v(LOG_TAG, "codec configured");
834         }
835     }
836 
flushCodec()837     void flushCodec() {
838         mCodec.flush();
839         // TODO(b/147576107): is it ok to clearQueues right away or wait for some signal
840         mAsyncHandle.clearQueues();
841         mSawInputEOS = false;
842         mSawOutputEOS = false;
843         mInputCount = 0;
844         mOutputCount = 0;
845         mPrevOutputPts = Long.MIN_VALUE;
846         if (ENABLE_LOGS) {
847             Log.v(LOG_TAG, "codec flushed");
848         }
849     }
850 
reConfigureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)851     void reConfigureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
852             boolean isEncoder) {
853         /* TODO(b/147348711) */
854         if (false) mCodec.stop();
855         else mCodec.reset();
856         configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder);
857     }
858 
resetContext(boolean isAsync, boolean signalEOSWithLastFrame)859     void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
860         mAsyncHandle.resetContext();
861         mIsCodecInAsyncMode = isAsync;
862         mSawInputEOS = false;
863         mSawOutputEOS = false;
864         mSignalEOSWithLastFrame = signalEOSWithLastFrame;
865         mInputCount = 0;
866         mOutputCount = 0;
867         mPrevOutputPts = Long.MIN_VALUE;
868         mSignalledOutFormatChanged = false;
869     }
870 
enqueueEOS(int bufferIndex)871     void enqueueEOS(int bufferIndex) {
872         if (!mSawInputEOS) {
873             mCodec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
874             mSawInputEOS = true;
875             if (ENABLE_LOGS) {
876                 Log.v(LOG_TAG, "Queued End of Stream");
877             }
878         }
879     }
880 
doWork(int frameLimit)881     void doWork(int frameLimit) throws InterruptedException, IOException {
882         int frameCount = 0;
883         if (mIsCodecInAsyncMode) {
884             // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
885             while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < frameLimit) {
886                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
887                 if (element != null) {
888                     int bufferID = element.first;
889                     MediaCodec.BufferInfo info = element.second;
890                     if (info != null) {
891                         // <id, info> corresponds to output callback. Handle it accordingly
892                         dequeueOutput(bufferID, info);
893                     } else {
894                         // <id, null> corresponds to input callback. Handle it accordingly
895                         enqueueInput(bufferID);
896                         frameCount++;
897                     }
898                 }
899             }
900         } else {
901             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
902             // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
903             while (!mSawInputEOS && frameCount < frameLimit) {
904                 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
905                 if (outputBufferId >= 0) {
906                     dequeueOutput(outputBufferId, outInfo);
907                 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
908                     mOutFormat = mCodec.getOutputFormat();
909                     mSignalledOutFormatChanged = true;
910                 }
911                 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
912                 if (inputBufferId != -1) {
913                     enqueueInput(inputBufferId);
914                     frameCount++;
915                 }
916             }
917         }
918     }
919 
queueEOS()920     void queueEOS() throws InterruptedException {
921         if (mIsCodecInAsyncMode) {
922             while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) {
923                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
924                 if (element != null) {
925                     int bufferID = element.first;
926                     MediaCodec.BufferInfo info = element.second;
927                     if (info != null) {
928                         dequeueOutput(bufferID, info);
929                     } else {
930                         enqueueEOS(element.first);
931                     }
932                 }
933             }
934         } else {
935             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
936             while (!mSawInputEOS) {
937                 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
938                 if (outputBufferId >= 0) {
939                     dequeueOutput(outputBufferId, outInfo);
940                 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
941                     mOutFormat = mCodec.getOutputFormat();
942                     mSignalledOutFormatChanged = true;
943                 }
944                 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
945                 if (inputBufferId != -1) {
946                     enqueueEOS(inputBufferId);
947                 }
948             }
949         }
950     }
951 
waitForAllOutputs()952     void waitForAllOutputs() throws InterruptedException {
953         if (mIsCodecInAsyncMode) {
954             while (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) {
955                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
956                 if (element != null) {
957                     dequeueOutput(element.first, element.second);
958                 }
959             }
960         } else {
961             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
962             while (!mSawOutputEOS) {
963                 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
964                 if (outputBufferId >= 0) {
965                     dequeueOutput(outputBufferId, outInfo);
966                 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
967                     mOutFormat = mCodec.getOutputFormat();
968                     mSignalledOutFormatChanged = true;
969                 }
970             }
971         }
972     }
973 
selectCodecs(String mime, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder)974     static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats,
975             String[] features, boolean isEncoder) {
976         MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
977         MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
978         ArrayList<String> listOfCodecs = new ArrayList<>();
979         for (MediaCodecInfo codecInfo : codecInfos) {
980             if (codecInfo.isEncoder() != isEncoder) continue;
981             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
982             String[] types = codecInfo.getSupportedTypes();
983             for (String type : types) {
984                 if (type.equalsIgnoreCase(mime)) {
985                     boolean isOk = true;
986                     MediaCodecInfo.CodecCapabilities codecCapabilities =
987                             codecInfo.getCapabilitiesForType(type);
988                     if (formats != null) {
989                         for (MediaFormat format : formats) {
990                             if (!codecCapabilities.isFormatSupported(format)) {
991                                 isOk = false;
992                                 break;
993                             }
994                         }
995                     }
996                     if (features != null) {
997                         for (String feature : features) {
998                             if (!codecCapabilities.isFeatureSupported(feature)) {
999                                 isOk = false;
1000                                 break;
1001                             }
1002                         }
1003                     }
1004                     if (isOk) listOfCodecs.add(codecInfo.getName());
1005                 }
1006             }
1007         }
1008         return listOfCodecs;
1009     }
1010 
getWidth(MediaFormat format)1011     static int getWidth(MediaFormat format) {
1012         int width = format.getInteger(MediaFormat.KEY_WIDTH, -1);
1013         if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
1014             width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
1015         }
1016         return width;
1017     }
1018 
getHeight(MediaFormat format)1019     static int getHeight(MediaFormat format) {
1020         int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1);
1021         if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
1022             height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
1023         }
1024         return height;
1025     }
1026 
isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat)1027     boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) {
1028         if (inpFormat == null || outFormat == null) return false;
1029         String inpMime = inpFormat.getString(MediaFormat.KEY_MIME);
1030         String outMime = outFormat.getString(MediaFormat.KEY_MIME);
1031         // not comparing input and output mimes because for a codec, mime is raw on one side and
1032         // encoded type on the other
1033         if (outMime.startsWith("audio/")) {
1034             return inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1) ==
1035                     outFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -2) &&
1036                     inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1) ==
1037                             outFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -2) &&
1038                     inpMime.startsWith("audio/");
1039         } else if (outMime.startsWith("video/")) {
1040             return getWidth(inpFormat) == getWidth(outFormat) &&
1041                     getHeight(inpFormat) == getHeight(outFormat) && inpMime.startsWith("video/");
1042         }
1043         return true;
1044     }
1045 
validateMetrics(String codec)1046     PersistableBundle validateMetrics(String codec) {
1047         PersistableBundle metrics = mCodec.getMetrics();
1048         assertTrue("metrics is null", metrics != null);
1049         assertTrue(metrics.getString(MediaCodec.MetricsConstants.CODEC).equals(codec));
1050         if (mIsAudio) {
1051             assertTrue(metrics.getString(MediaCodec.MetricsConstants.MODE)
1052                     .equals(MediaCodec.MetricsConstants.MODE_AUDIO));
1053         } else {
1054             assertTrue(metrics.getString(MediaCodec.MetricsConstants.MODE)
1055                     .equals(MediaCodec.MetricsConstants.MODE_VIDEO));
1056         }
1057         return metrics;
1058     }
1059 
validateMetrics(String codec, MediaFormat format)1060     PersistableBundle validateMetrics(String codec, MediaFormat format) {
1061         PersistableBundle metrics = validateMetrics(codec);
1062         if (!mIsAudio) {
1063             assertTrue(metrics.getInt(MediaCodec.MetricsConstants.WIDTH) == getWidth(format));
1064             assertTrue(metrics.getInt(MediaCodec.MetricsConstants.HEIGHT) == getHeight(format));
1065         }
1066         assertTrue(metrics.getInt(MediaCodec.MetricsConstants.SECURE) == 0);
1067         return metrics;
1068     }
1069 
validateColorAspects(MediaFormat fmt, int range, int standard, int transfer)1070     void validateColorAspects(MediaFormat fmt, int range, int standard, int transfer) {
1071         int colorRange = fmt.getInteger(MediaFormat.KEY_COLOR_RANGE, UNSPECIFIED);
1072         int colorStandard = fmt.getInteger(MediaFormat.KEY_COLOR_STANDARD, UNSPECIFIED);
1073         int colorTransfer = fmt.getInteger(MediaFormat.KEY_COLOR_TRANSFER, UNSPECIFIED);
1074         if (range > UNSPECIFIED) {
1075             assertEquals("color range mismatch ", range, colorRange);
1076         }
1077         if (standard > UNSPECIFIED) {
1078             assertEquals("color standard mismatch ", standard, colorStandard);
1079         }
1080         if (transfer > UNSPECIFIED) {
1081             assertEquals("color transfer mismatch ", transfer, colorTransfer);
1082         }
1083     }
1084 
setUpSurface(CodecTestActivity activity)1085     public void setUpSurface(CodecTestActivity activity) throws InterruptedException {
1086         activity.waitTillSurfaceIsCreated();
1087         mSurface = activity.getSurface();
1088         assertTrue("Surface created is null.", mSurface != null);
1089         assertTrue("Surface created is invalid.", mSurface.isValid());
1090     }
1091 
tearDownSurface()1092     public void tearDownSurface() {
1093         if (mSurface != null) {
1094             mSurface.release();
1095             mSurface = null;
1096         }
1097     }
1098 
1099     @Before
isCodecNameValid()1100     public void isCodecNameValid() {
1101         if (mCodecName != null && mCodecName.startsWith(INVALID_CODEC)) {
1102             fail("no valid component available for current test ");
1103         }
1104     }
1105 }
1106 
1107 class CodecDecoderTestBase extends CodecTestBase {
1108     private static final String LOG_TAG = CodecDecoderTestBase.class.getSimpleName();
1109 
1110     String mMime;
1111     String mTestFile;
1112     boolean mIsInterlaced;
1113 
1114     ArrayList<ByteBuffer> mCsdBuffers;
1115     private int mCurrCsdIdx;
1116 
1117     private ByteBuffer flatBuffer = ByteBuffer.allocate(4 * Integer.BYTES);
1118 
1119     MediaExtractor mExtractor;
1120 
CodecDecoderTestBase(String codecName, String mime, String testFile)1121     CodecDecoderTestBase(String codecName, String mime, String testFile) {
1122         mCodecName = codecName;
1123         mMime = mime;
1124         mTestFile = testFile;
1125         mAsyncHandle = new CodecAsyncHandler();
1126         mCsdBuffers = new ArrayList<>();
1127         mIsAudio = mMime.startsWith("audio/");
1128     }
1129 
setUpSource(String srcFile)1130     MediaFormat setUpSource(String srcFile) throws IOException {
1131         return setUpSource(mInpPrefix, srcFile);
1132     }
1133 
setUpSource(String prefix, String srcFile)1134     MediaFormat setUpSource(String prefix, String srcFile) throws IOException {
1135         mExtractor = new MediaExtractor();
1136         mExtractor.setDataSource(prefix + srcFile);
1137         for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) {
1138             MediaFormat format = mExtractor.getTrackFormat(trackID);
1139             if (mMime.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) {
1140                 mExtractor.selectTrack(trackID);
1141                 if (!mIsAudio) {
1142                     if (mSurface == null) {
1143                         // COLOR_FormatYUV420Flexible must be supported by all components
1144                         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
1145                     } else {
1146                         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatSurface);
1147                     }
1148                 }
1149                 // TODO: determine this from the extractor format when it becomes exposed.
1150                 mIsInterlaced = srcFile.contains("_interlaced_");
1151                 return format;
1152             }
1153         }
1154         fail("No track with mime: " + mMime + " found in file: " + srcFile);
1155         return null;
1156     }
1157 
hasCSD(MediaFormat format)1158     boolean hasCSD(MediaFormat format) {
1159         return format.containsKey("csd-0");
1160     }
1161 
flattenBufferInfo(MediaCodec.BufferInfo info, boolean isAudio)1162     void flattenBufferInfo(MediaCodec.BufferInfo info, boolean isAudio) {
1163         if (isAudio) {
1164             flatBuffer.putInt(info.size);
1165         }
1166         flatBuffer.putInt(info.flags & ~MediaCodec.BUFFER_FLAG_END_OF_STREAM)
1167                 .putLong(info.presentationTimeUs);
1168         flatBuffer.flip();
1169     }
1170 
enqueueCodecConfig(int bufferIndex)1171     void enqueueCodecConfig(int bufferIndex) {
1172         ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
1173         ByteBuffer csdBuffer = mCsdBuffers.get(mCurrCsdIdx);
1174         inputBuffer.put((ByteBuffer) csdBuffer.rewind());
1175         mCodec.queueInputBuffer(bufferIndex, 0, csdBuffer.limit(), 0,
1176                 MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
1177         if (ENABLE_LOGS) {
1178             Log.v(LOG_TAG, "queued csd: id: " + bufferIndex + " size: " + csdBuffer.limit());
1179         }
1180     }
1181 
enqueueInput(int bufferIndex)1182     void enqueueInput(int bufferIndex) {
1183         if (mExtractor.getSampleSize() < 0) {
1184             enqueueEOS(bufferIndex);
1185         } else {
1186             ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
1187             mExtractor.readSampleData(inputBuffer, 0);
1188             int size = (int) mExtractor.getSampleSize();
1189             long pts = mExtractor.getSampleTime();
1190             int extractorFlags = mExtractor.getSampleFlags();
1191             int codecFlags = 0;
1192             if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
1193                 codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
1194             }
1195             if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
1196                 codecFlags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
1197             }
1198             if (!mExtractor.advance() && mSignalEOSWithLastFrame) {
1199                 codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
1200                 mSawInputEOS = true;
1201             }
1202             if (ENABLE_LOGS) {
1203                 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts +
1204                         " flags: " + codecFlags);
1205             }
1206             mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags);
1207             if (size > 0 && (codecFlags & (MediaCodec.BUFFER_FLAG_CODEC_CONFIG |
1208                     MediaCodec.BUFFER_FLAG_PARTIAL_FRAME)) == 0) {
1209                 mOutputBuff.saveInPTS(pts);
1210                 mInputCount++;
1211             }
1212         }
1213     }
1214 
enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info)1215     void enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info) {
1216         ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
1217         buffer.position(info.offset);
1218         for (int i = 0; i < info.size; i++) {
1219             inputBuffer.put(buffer.get());
1220         }
1221         if (ENABLE_LOGS) {
1222             Log.v(LOG_TAG, "input: id: " + bufferIndex + " flags: " + info.flags + " size: " +
1223                     info.size + " timestamp: " + info.presentationTimeUs);
1224         }
1225         mCodec.queueInputBuffer(bufferIndex, 0, info.size, info.presentationTimeUs,
1226                 info.flags);
1227         if (info.size > 0 && ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) &&
1228                 ((info.flags & MediaCodec.BUFFER_FLAG_PARTIAL_FRAME) == 0)) {
1229             mOutputBuff.saveInPTS(info.presentationTimeUs);
1230             mInputCount++;
1231         }
1232     }
1233 
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)1234     void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
1235         if (info.size > 0 && mSaveToMem) {
1236             ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
1237             flattenBufferInfo(info, mIsAudio);
1238             mOutputBuff.checksum(flatBuffer, flatBuffer.limit());
1239             if (mIsAudio) {
1240                 mOutputBuff.checksum(buf, info.size);
1241                 mOutputBuff.saveToMemory(buf, info);
1242             } else {
1243                 // tests both getOutputImage and getOutputBuffer. Can do time division
1244                 // multiplexing but lets allow it for now
1245                 MediaFormat format = mCodec.getOutputFormat();
1246                 int width = format.getInteger(MediaFormat.KEY_WIDTH);
1247                 int height = format.getInteger(MediaFormat.KEY_HEIGHT);
1248                 int stride = format.getInteger(MediaFormat.KEY_STRIDE);
1249                 mOutputBuff.checksum(buf, info.size, width, height, stride);
1250 
1251                 Image img = mCodec.getOutputImage(bufferIndex);
1252                 assertTrue(img != null);
1253                 mOutputBuff.checksum(img);
1254             }
1255         }
1256         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
1257             mSawOutputEOS = true;
1258         }
1259         if (ENABLE_LOGS) {
1260             Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
1261                     info.size + " timestamp: " + info.presentationTimeUs);
1262         }
1263         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
1264             mOutputBuff.saveOutPTS(info.presentationTimeUs);
1265             mOutputCount++;
1266         }
1267         mCodec.releaseOutputBuffer(bufferIndex, false);
1268     }
1269 
doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list)1270     void doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list)
1271             throws InterruptedException {
1272         int frameCount = 0;
1273         if (mIsCodecInAsyncMode) {
1274             // output processing after queuing EOS is done in waitForAllOutputs()
1275             while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < list.size()) {
1276                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
1277                 if (element != null) {
1278                     int bufferID = element.first;
1279                     MediaCodec.BufferInfo info = element.second;
1280                     if (info != null) {
1281                         dequeueOutput(bufferID, info);
1282                     } else {
1283                         enqueueInput(bufferID, buffer, list.get(frameCount));
1284                         frameCount++;
1285                     }
1286                 }
1287             }
1288         } else {
1289             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
1290             // output processing after queuing EOS is done in waitForAllOutputs()
1291             while (!mSawInputEOS && frameCount < list.size()) {
1292                 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
1293                 if (outputBufferId >= 0) {
1294                     dequeueOutput(outputBufferId, outInfo);
1295                 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
1296                     mOutFormat = mCodec.getOutputFormat();
1297                     mSignalledOutFormatChanged = true;
1298                 }
1299                 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
1300                 if (inputBufferId != -1) {
1301                     enqueueInput(inputBufferId, buffer, list.get(frameCount));
1302                     frameCount++;
1303                 }
1304             }
1305         }
1306     }
1307 
queueCodecConfig()1308     void queueCodecConfig() throws InterruptedException {
1309         if (mIsCodecInAsyncMode) {
1310             for (mCurrCsdIdx = 0; !mAsyncHandle.hasSeenError() && mCurrCsdIdx < mCsdBuffers.size();
1311                  mCurrCsdIdx++) {
1312                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput();
1313                 if (element != null) {
1314                     enqueueCodecConfig(element.first);
1315                 }
1316             }
1317         } else {
1318             for (mCurrCsdIdx = 0; mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) {
1319                 enqueueCodecConfig(mCodec.dequeueInputBuffer(-1));
1320             }
1321         }
1322     }
1323 
decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit)1324     void decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit)
1325             throws IOException, InterruptedException {
1326         mSaveToMem = true;
1327         mOutputBuff = new OutputManager();
1328         mCodec = MediaCodec.createByCodecName(decoder);
1329         MediaFormat format = setUpSource(file);
1330         configureCodec(format, false, true, false);
1331         mCodec.start();
1332         mExtractor.seekTo(pts, mode);
1333         doWork(frameLimit);
1334         queueEOS();
1335         waitForAllOutputs();
1336         mCodec.stop();
1337         mCodec.release();
1338         mExtractor.release();
1339         mSaveToMem = false;
1340     }
1341 
1342     @Override
validateMetrics(String decoder, MediaFormat format)1343     PersistableBundle validateMetrics(String decoder, MediaFormat format) {
1344         PersistableBundle metrics = super.validateMetrics(decoder, format);
1345         assertTrue(metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE).equals(mMime));
1346         assertTrue(metrics.getInt(MediaCodec.MetricsConstants.ENCODER) == 0);
1347         return metrics;
1348     }
1349 
validateColorAspects(String decoder, String parent, String name, int range, int standard, int transfer, boolean ignoreColorBox)1350     void validateColorAspects(String decoder, String parent, String name, int range, int standard,
1351             int transfer, boolean ignoreColorBox)
1352             throws IOException, InterruptedException {
1353         mOutputBuff = new OutputManager();
1354         MediaFormat format = setUpSource(parent, name);
1355         if (ignoreColorBox) {
1356             format.removeKey(MediaFormat.KEY_COLOR_RANGE);
1357             format.removeKey(MediaFormat.KEY_COLOR_STANDARD);
1358             format.removeKey(MediaFormat.KEY_COLOR_TRANSFER);
1359         }
1360         if (decoder == null) {
1361             MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
1362             decoder = codecList.findDecoderForFormat(format);
1363         }
1364         mCodec = MediaCodec.createByCodecName(decoder);
1365         configureCodec(format, true, true, false);
1366         mCodec.start();
1367         doWork(1);
1368         queueEOS();
1369         waitForAllOutputs();
1370         validateColorAspects(mCodec.getOutputFormat(), range, standard, transfer);
1371         mCodec.stop();
1372         mCodec.release();
1373         mExtractor.release();
1374     }
1375 }
1376 
1377 class CodecEncoderTestBase extends CodecTestBase {
1378     private static final String LOG_TAG = CodecEncoderTestBase.class.getSimpleName();
1379 
1380     // files are in WorkDir.getMediaDirString();
1381     private static final String mInputAudioFile = "bbb_2ch_44kHz_s16le.raw";
1382     private static final String mInputVideoFile = "bbb_cif_yuv420p_30fps.yuv";
1383     private final int INP_FRM_WIDTH = 352;
1384     private final int INP_FRM_HEIGHT = 288;
1385 
1386     final String mMime;
1387     final int[] mBitrates;
1388     final int[] mEncParamList1;
1389     final int[] mEncParamList2;
1390 
1391     final String mInputFile;
1392     byte[] mInputData;
1393     int mNumBytesSubmitted;
1394     long mInputOffsetPts;
1395 
1396     ArrayList<MediaFormat> mFormats;
1397     ArrayList<MediaCodec.BufferInfo> mInfoList;
1398 
1399     int mWidth, mHeight;
1400     int mFrameRate;
1401     int mMaxBFrames;
1402     int mChannels;
1403     int mSampleRate;
1404 
CodecEncoderTestBase(String encoder, String mime, int[] bitrates, int[] encoderInfo1, int[] encoderInfo2)1405     CodecEncoderTestBase(String encoder, String mime, int[] bitrates, int[] encoderInfo1,
1406             int[] encoderInfo2) {
1407         mMime = mime;
1408         mCodecName = encoder;
1409         mBitrates = bitrates;
1410         mEncParamList1 = encoderInfo1;
1411         mEncParamList2 = encoderInfo2;
1412         mFormats = new ArrayList<>();
1413         mInfoList = new ArrayList<>();
1414         mWidth = INP_FRM_WIDTH;
1415         mHeight = INP_FRM_HEIGHT;
1416         if (mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) mFrameRate = 12;
1417         else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_H263)) mFrameRate = 12;
1418         else mFrameRate = 30;
1419         mMaxBFrames = 0;
1420         mChannels = 1;
1421         mSampleRate = 8000;
1422         mAsyncHandle = new CodecAsyncHandler();
1423         mIsAudio = mMime.startsWith("audio/");
1424         mInputFile = mIsAudio ? mInputAudioFile : mInputVideoFile;
1425     }
1426 
1427     /**
1428      * Selects encoder input color format in byte buffer mode. As of now ndk tests support only
1429      * 420p, 420sp. COLOR_FormatYUV420Flexible although can represent any form of yuv, it doesn't
1430      * work in ndk due to lack of AMediaCodec_GetInputImage()
1431      */
findByteBufferColorFormat(String encoder, String mime)1432     static int findByteBufferColorFormat(String encoder, String mime) throws IOException {
1433         MediaCodec codec = MediaCodec.createByCodecName(encoder);
1434         MediaCodecInfo.CodecCapabilities cap = codec.getCodecInfo().getCapabilitiesForType(mime);
1435         int colorFormat = -1;
1436         for (int c : cap.colorFormats) {
1437             if (c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar ||
1438                     c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) {
1439                 Log.v(LOG_TAG, "selecting color format: " + c);
1440                 colorFormat = c;
1441                 break;
1442             }
1443         }
1444         codec.release();
1445         return colorFormat;
1446     }
1447 
1448     @Override
resetContext(boolean isAsync, boolean signalEOSWithLastFrame)1449     void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
1450         super.resetContext(isAsync, signalEOSWithLastFrame);
1451         mNumBytesSubmitted = 0;
1452         mInputOffsetPts = 0;
1453     }
1454 
1455     @Override
flushCodec()1456     void flushCodec() {
1457         super.flushCodec();
1458         if (mIsAudio) {
1459             mInputOffsetPts =
1460                     (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mSampleRate);
1461         } else {
1462             mInputOffsetPts = (mInputCount + 5) * 1000000L / mFrameRate;
1463         }
1464         mPrevOutputPts = mInputOffsetPts - 1;
1465         mNumBytesSubmitted = 0;
1466     }
1467 
setUpSource(String srcFile)1468     void setUpSource(String srcFile) throws IOException {
1469         String inpPath = mInpPrefix + srcFile;
1470         try (FileInputStream fInp = new FileInputStream(inpPath)) {
1471             int size = (int) new File(inpPath).length();
1472             mInputData = new byte[size];
1473             fInp.read(mInputData, 0, size);
1474         }
1475     }
1476 
fillImage(Image image)1477     void fillImage(Image image) {
1478         Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888);
1479         int imageWidth = image.getWidth();
1480         int imageHeight = image.getHeight();
1481         Image.Plane[] planes = image.getPlanes();
1482         int offset = mNumBytesSubmitted;
1483         for (int i = 0; i < planes.length; ++i) {
1484             ByteBuffer buf = planes[i].getBuffer();
1485             int width = imageWidth;
1486             int height = imageHeight;
1487             int tileWidth = INP_FRM_WIDTH;
1488             int tileHeight = INP_FRM_HEIGHT;
1489             int rowStride = planes[i].getRowStride();
1490             int pixelStride = planes[i].getPixelStride();
1491             if (i != 0) {
1492                 width = imageWidth / 2;
1493                 height = imageHeight / 2;
1494                 tileWidth = INP_FRM_WIDTH / 2;
1495                 tileHeight = INP_FRM_HEIGHT / 2;
1496             }
1497             if (pixelStride == 1) {
1498                 if (width == rowStride && width == tileWidth && height == tileHeight) {
1499                     buf.put(mInputData, offset, width * height);
1500                 } else {
1501                     for (int z = 0; z < height; z += tileHeight) {
1502                         int rowsToCopy = Math.min(height - z, tileHeight);
1503                         for (int y = 0; y < rowsToCopy; y++) {
1504                             for (int x = 0; x < width; x += tileWidth) {
1505                                 int colsToCopy = Math.min(width - x, tileWidth);
1506                                 buf.position((z + y) * rowStride + x);
1507                                 buf.put(mInputData, offset + y * tileWidth, colsToCopy);
1508                             }
1509                         }
1510                     }
1511                 }
1512             } else {
1513                 // do it pixel-by-pixel
1514                 for (int z = 0; z < height; z += tileHeight) {
1515                     int rowsToCopy = Math.min(height - z, tileHeight);
1516                     for (int y = 0; y < rowsToCopy; y++) {
1517                         int lineOffset = (z + y) * rowStride;
1518                         for (int x = 0; x < width; x += tileWidth) {
1519                             int colsToCopy = Math.min(width - x, tileWidth);
1520                             for (int w = 0; w < colsToCopy; w++) {
1521                                 buf.position(lineOffset + (x + w) * pixelStride);
1522                                 buf.put(mInputData[offset + y * tileWidth + w]);
1523                             }
1524                         }
1525                     }
1526                 }
1527             }
1528             offset += tileWidth * tileHeight;
1529         }
1530     }
1531 
fillByteBuffer(ByteBuffer inputBuffer)1532     void fillByteBuffer(ByteBuffer inputBuffer) {
1533         int offset = 0, frmOffset = mNumBytesSubmitted;
1534         for (int plane = 0; plane < 3; plane++) {
1535             int width = mWidth;
1536             int height = mHeight;
1537             int tileWidth = INP_FRM_WIDTH;
1538             int tileHeight = INP_FRM_HEIGHT;
1539             if (plane != 0) {
1540                 width = mWidth / 2;
1541                 height = mHeight / 2;
1542                 tileWidth = INP_FRM_WIDTH / 2;
1543                 tileHeight = INP_FRM_HEIGHT / 2;
1544             }
1545             for (int k = 0; k < height; k += tileHeight) {
1546                 int rowsToCopy = Math.min(height - k, tileHeight);
1547                 for (int j = 0; j < rowsToCopy; j++) {
1548                     for (int i = 0; i < width; i += tileWidth) {
1549                         int colsToCopy = Math.min(width - i, tileWidth);
1550                         inputBuffer.position(offset + (k + j) * width + i);
1551                         inputBuffer.put(mInputData, frmOffset + j * tileWidth, colsToCopy);
1552                     }
1553                 }
1554             }
1555             offset += width * height;
1556             frmOffset += tileWidth * tileHeight;
1557         }
1558     }
1559 
enqueueInput(int bufferIndex)1560     void enqueueInput(int bufferIndex) {
1561         ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
1562         if (mNumBytesSubmitted >= mInputData.length) {
1563             enqueueEOS(bufferIndex);
1564         } else {
1565             int size;
1566             int flags = 0;
1567             long pts = mInputOffsetPts;
1568             if (mIsAudio) {
1569                 pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate);
1570                 size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted);
1571                 inputBuffer.put(mInputData, mNumBytesSubmitted, size);
1572                 if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) {
1573                     flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
1574                     mSawInputEOS = true;
1575                 }
1576                 mNumBytesSubmitted += size;
1577             } else {
1578                 pts += mInputCount * 1000000L / mFrameRate;
1579                 size = mWidth * mHeight * 3 / 2;
1580                 int frmSize = INP_FRM_WIDTH * INP_FRM_HEIGHT * 3 / 2;
1581                 if (mNumBytesSubmitted + frmSize > mInputData.length) {
1582                     fail("received partial frame to encode");
1583                 } else {
1584                     Image img = mCodec.getInputImage(bufferIndex);
1585                     if (img != null) {
1586                         fillImage(img);
1587                     } else {
1588                         if (mWidth == INP_FRM_WIDTH && mHeight == INP_FRM_HEIGHT) {
1589                             inputBuffer.put(mInputData, mNumBytesSubmitted, size);
1590                         } else {
1591                             fillByteBuffer(inputBuffer);
1592                         }
1593                     }
1594                 }
1595                 if (mNumBytesSubmitted + frmSize >= mInputData.length && mSignalEOSWithLastFrame) {
1596                     flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
1597                     mSawInputEOS = true;
1598                 }
1599                 mNumBytesSubmitted += frmSize;
1600             }
1601             if (ENABLE_LOGS) {
1602                 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts +
1603                         " flags: " + flags);
1604             }
1605             mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags);
1606             mOutputBuff.saveInPTS(pts);
1607             mInputCount++;
1608         }
1609     }
1610 
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)1611     void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
1612         if (ENABLE_LOGS) {
1613             Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
1614                     info.size + " timestamp: " + info.presentationTimeUs);
1615         }
1616         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
1617             mSawOutputEOS = true;
1618         }
1619         if (info.size > 0) {
1620             if (mSaveToMem) {
1621                 MediaCodec.BufferInfo copy = new MediaCodec.BufferInfo();
1622                 copy.set(mOutputBuff.getOutStreamSize(), info.size, info.presentationTimeUs,
1623                         info.flags);
1624                 mInfoList.add(copy);
1625 
1626                 ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
1627                 mOutputBuff.saveToMemory(buf, info);
1628             }
1629             if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
1630                 mOutputBuff.saveOutPTS(info.presentationTimeUs);
1631                 mOutputCount++;
1632             }
1633         }
1634         mCodec.releaseOutputBuffer(bufferIndex, false);
1635     }
1636 
1637     @Override
validateMetrics(String codec, MediaFormat format)1638     PersistableBundle validateMetrics(String codec, MediaFormat format) {
1639         PersistableBundle metrics = super.validateMetrics(codec, format);
1640         assertTrue(metrics.getString(MediaCodec.MetricsConstants.MIME_TYPE).equals(mMime));
1641         assertTrue(metrics.getInt(MediaCodec.MetricsConstants.ENCODER) == 1);
1642         return metrics;
1643     }
1644 
setUpParams(int limit)1645     void setUpParams(int limit) {
1646         int count = 0;
1647         for (int bitrate : mBitrates) {
1648             if (mIsAudio) {
1649                 for (int rate : mEncParamList1) {
1650                     for (int channels : mEncParamList2) {
1651                         MediaFormat format = new MediaFormat();
1652                         format.setString(MediaFormat.KEY_MIME, mMime);
1653                         if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
1654                             format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, bitrate);
1655                         } else {
1656                             format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
1657                         }
1658                         format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate);
1659                         format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels);
1660                         mFormats.add(format);
1661                         count++;
1662                         if (count >= limit) return;
1663                     }
1664                 }
1665             } else {
1666                 assertTrue("Wrong number of height, width parameters",
1667                         mEncParamList1.length == mEncParamList2.length);
1668                 for (int i = 0; i < mEncParamList1.length; i++) {
1669                     MediaFormat format = new MediaFormat();
1670                     format.setString(MediaFormat.KEY_MIME, mMime);
1671                     format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
1672                     format.setInteger(MediaFormat.KEY_WIDTH, mEncParamList1[i]);
1673                     format.setInteger(MediaFormat.KEY_HEIGHT, mEncParamList2[i]);
1674                     format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
1675                     format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, mMaxBFrames);
1676                     format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
1677                     format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
1678                             MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
1679                     mFormats.add(format);
1680                     count++;
1681                     if (count >= limit) return;
1682                 }
1683             }
1684         }
1685     }
1686 
encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format, boolean saveToMem)1687     void encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format,
1688             boolean saveToMem) throws IOException, InterruptedException {
1689         mSaveToMem = saveToMem;
1690         mOutputBuff = new OutputManager();
1691         mInfoList.clear();
1692         mCodec = MediaCodec.createByCodecName(encoder);
1693         setUpSource(file);
1694         configureCodec(format, false, true, true);
1695         if (mIsAudio) {
1696             mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
1697             mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
1698         } else {
1699             mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
1700             mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
1701         }
1702         mCodec.start();
1703         doWork(frameLimit);
1704         queueEOS();
1705         waitForAllOutputs();
1706         mCodec.stop();
1707         mCodec.release();
1708         mSaveToMem = false;
1709     }
1710 
decodeElementaryStream(String decoder, MediaFormat format, ByteBuffer elementaryStream, ArrayList<MediaCodec.BufferInfo> infos)1711     ByteBuffer decodeElementaryStream(String decoder, MediaFormat format,
1712             ByteBuffer elementaryStream, ArrayList<MediaCodec.BufferInfo> infos)
1713             throws IOException, InterruptedException {
1714         String mime = format.getString(MediaFormat.KEY_MIME);
1715         CodecDecoderTestBase cdtb = new CodecDecoderTestBase(decoder, mime, null);
1716         cdtb.mOutputBuff = new OutputManager();
1717         cdtb.mSaveToMem = true;
1718         cdtb.mCodec = MediaCodec.createByCodecName(decoder);
1719         cdtb.mCodec.configure(format, null, null, 0);
1720         cdtb.mCodec.start();
1721         cdtb.doWork(elementaryStream, infos);
1722         cdtb.queueEOS();
1723         cdtb.waitForAllOutputs();
1724         cdtb.mCodec.stop();
1725         cdtb.mCodec.release();
1726         return cdtb.mOutputBuff.getBuffer();
1727     }
1728 }
1729