• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.mediapc.cts;
18 
19 import static android.media.MediaCodecInfo.CodecCapabilities;
20 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
21 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
22 import static android.mediapc.cts.common.CodecMetrics.getMetrics;
23 
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 
28 import android.graphics.ImageFormat;
29 import android.media.Image;
30 import android.media.MediaCodec;
31 import android.media.MediaCodecInfo;
32 import android.media.MediaCodecList;
33 import android.media.MediaCrypto;
34 import android.media.MediaDrm;
35 import android.media.MediaExtractor;
36 import android.media.MediaFormat;
37 import android.media.NotProvisionedException;
38 import android.media.ResourceBusyException;
39 import android.mediapc.cts.common.CodecMetrics;
40 import android.os.Build;
41 import android.util.Log;
42 import android.util.Pair;
43 import android.view.Surface;
44 
45 import androidx.annotation.NonNull;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 
48 import org.junit.Assert;
49 
50 import java.io.File;
51 import java.io.FileInputStream;
52 import java.io.IOException;
53 import java.nio.ByteBuffer;
54 import java.util.ArrayList;
55 import java.util.HashSet;
56 import java.util.LinkedList;
57 import java.util.Map;
58 import java.util.Set;
59 import java.util.UUID;
60 import java.util.concurrent.Callable;
61 import java.util.concurrent.locks.Condition;
62 import java.util.concurrent.locks.Lock;
63 import java.util.concurrent.locks.ReentrantLock;
64 import java.util.function.Consumer;
65 import java.util.regex.Pattern;
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 
getOutput()141     Pair<Integer, MediaCodec.BufferInfo> getOutput() throws InterruptedException {
142         Pair<Integer, MediaCodec.BufferInfo> element = null;
143         mLock.lock();
144         while (!mSignalledError) {
145             if (mCbOutputQueue.isEmpty()) {
146                 mCondition.await();
147             } else {
148                 element = mCbOutputQueue.remove(0);
149                 break;
150             }
151         }
152         mLock.unlock();
153         return element;
154     }
155 
getWork()156     Pair<Integer, MediaCodec.BufferInfo> getWork() throws InterruptedException {
157         Pair<Integer, MediaCodec.BufferInfo> element = null;
158         mLock.lock();
159         while (!mSignalledError) {
160             if (mCbInputQueue.isEmpty() && mCbOutputQueue.isEmpty()) {
161                 mCondition.await();
162             } else {
163                 if (!mCbOutputQueue.isEmpty()) {
164                     element = mCbOutputQueue.remove(0);
165                     break;
166                 }
167                 if (!mCbInputQueue.isEmpty()) {
168                     element = mCbInputQueue.remove(0);
169                     break;
170                 }
171             }
172         }
173         mLock.unlock();
174         return element;
175     }
176 
hasSeenError()177     boolean hasSeenError() {
178         return mSignalledError;
179     }
180 
hasOutputFormatChanged()181     boolean hasOutputFormatChanged() {
182         return mSignalledOutFormatChanged;
183     }
184 
getOutputFormat()185     MediaFormat getOutputFormat() {
186         return mOutFormat;
187     }
188 }
189 
190 abstract class CodecTestBase {
191     private static final String LOG_TAG = CodecTestBase.class.getSimpleName();
192     static final boolean ENABLE_LOGS = false;
193     static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000;
194     static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000;
195     static final int SELECT_ALL = 0; // Select all codecs
196     static final int SELECT_HARDWARE = 1; // Select Hardware codecs only
197     static final int SELECT_SOFTWARE = 2; // Select Software codecs only
198     static final int SELECT_AUDIO = 3; // Select Audio codecs only
199     static final int SELECT_VIDEO = 4; // Select Video codecs only
200     // Maintain Timeouts in sync with their counterpart in NativeMediaCommon.h
201     static final long Q_DEQ_TIMEOUT_US = 5000; // block at most 5ms while looking for io buffers
202     static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error
203     static final String mInpPrefix = WorkDir.getMediaDirString();
204     public static final MediaCodecList MCL_ALL = new MediaCodecList(MediaCodecList.ALL_CODECS);
205     public static final String CODEC_FILTER_KEY = "codec-filter";
206     public static final String CODEC_PREFIX_KEY = "codec-prefix";
207     public static final String MEDIA_TYPE_PREFIX_KEY = "media-type-prefix";
208     public static Pattern codecFilter;
209     public static String codecPrefix;
210     public static String mediaTypePrefix;
211 
212     CodecAsyncHandler mAsyncHandle;
213     boolean mIsCodecInAsyncMode;
214     boolean mSawInputEOS;
215     boolean mSawOutputEOS;
216     boolean mSignalEOSWithLastFrame;
217     int mInputCount;
218     int mOutputCount;
219     long mPrevOutputPts;
220     boolean mSignalledOutFormatChanged;
221     MediaFormat mOutFormat;
222     boolean mIsAudio;
223 
224     MediaCodec mCodec;
225     Surface mSurface;
226 
227     static {
228         android.os.Bundle args = InstrumentationRegistry.getArguments();
229         codecPrefix = args.getString(CODEC_PREFIX_KEY);
230         mediaTypePrefix = args.getString(MEDIA_TYPE_PREFIX_KEY);
231         String codecFilterStr = args.getString(CODEC_FILTER_KEY);
232         if (codecFilterStr != null) {
233             codecFilter = Pattern.compile(codecFilterStr);
234         }
235     }
236 
enqueueInput(int bufferIndex)237     abstract void enqueueInput(int bufferIndex) throws IOException;
238 
239     // Callback for each time the output count changes.
240     // This can be used to measure codec performance.
241     Consumer<Integer> mOutputCountListener;
242 
243     // must not be called during doWork
setOutputCountListener(Consumer<Integer> listener)244     void setOutputCountListener(Consumer<Integer> listener) {
245         mOutputCountListener = listener;
246     }
247 
248     /**
249      * Called to handle a dequeued output buffer.
250      *
251      * We account for EOS and the number of full output frames.
252      */
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)253     protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
254         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
255             mSawOutputEOS = true;
256         }
257 
258         int outputCount = mOutputCount;
259         // handle output count prior to releasing the buffer as that can take time
260         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
261             mOutputCount++;
262             if (mOutputCountListener != null) {
263                 mOutputCountListener.accept(mOutputCount);
264             }
265         }
266         releaseOutput(outputCount, bufferIndex, info);
267     }
268 
269     /**
270      * Called to handle releasing an output buffer.
271      */
releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)272     abstract void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info);
273 
274     /**
275      * Called to handle releasing an output buffer.
276      *
277      * @param outputCount total count of full output frames prior to
278      *                    this point (not including this buffer).
279      */
releaseOutput(int outputCount, int bufferIndex, MediaCodec.BufferInfo info)280     protected void releaseOutput(int outputCount, int bufferIndex, MediaCodec.BufferInfo info) {
281         releaseOutput(bufferIndex, info);
282     }
283 
configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)284     void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
285             boolean isEncoder) throws Exception {
286         resetContext(isAsync, signalEOSWithLastFrame);
287         mAsyncHandle.setCallBack(mCodec, isAsync);
288         // signalEOS flag has nothing to do with configure. We are using this flag to try all
289         // available configure apis
290         if (signalEOSWithLastFrame) {
291             mCodec.configure(format, mSurface, null,
292                     isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
293         } else {
294             mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0,
295                     null);
296         }
297     }
298 
resetContext(boolean isAsync, boolean signalEOSWithLastFrame)299     void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
300         mAsyncHandle.resetContext();
301         mIsCodecInAsyncMode = isAsync;
302         mSawInputEOS = false;
303         mSawOutputEOS = false;
304         mSignalEOSWithLastFrame = signalEOSWithLastFrame;
305         mInputCount = 0;
306         mOutputCount = 0;
307         mPrevOutputPts = Long.MIN_VALUE;
308         mSignalledOutFormatChanged = false;
309     }
310 
enqueueEOS(int bufferIndex)311     void enqueueEOS(int bufferIndex) {
312         if (!mSawInputEOS) {
313             mCodec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
314             mSawInputEOS = true;
315             if (ENABLE_LOGS) {
316                 Log.v(LOG_TAG, "Queued End of Stream");
317             }
318         }
319     }
320 
doWork(int frameLimit)321     void doWork(int frameLimit) throws InterruptedException, IOException {
322         int frameCount = 0;
323         if (mIsCodecInAsyncMode) {
324             // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
325             while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < frameLimit) {
326                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
327                 if (element != null) {
328                     int bufferID = element.first;
329                     MediaCodec.BufferInfo info = element.second;
330                     if (info != null) {
331                         // <id, info> corresponds to output callback. Handle it accordingly
332                         dequeueOutput(bufferID, info);
333                     } else {
334                         // <id, null> corresponds to input callback. Handle it accordingly
335                         enqueueInput(bufferID);
336                         frameCount++;
337                     }
338                 }
339             }
340         } else {
341             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
342             // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
343             while (!mSawInputEOS && frameCount < frameLimit) {
344                 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
345                 if (outputBufferId >= 0) {
346                     dequeueOutput(outputBufferId, outInfo);
347                 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
348                     mOutFormat = mCodec.getOutputFormat();
349                     mSignalledOutFormatChanged = true;
350                 }
351                 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
352                 if (inputBufferId != -1) {
353                     enqueueInput(inputBufferId);
354                     frameCount++;
355                 }
356             }
357         }
358     }
359 
queueEOS()360     void queueEOS() throws InterruptedException {
361         if (mIsCodecInAsyncMode) {
362             while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) {
363                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
364                 if (element != null) {
365                     int bufferID = element.first;
366                     MediaCodec.BufferInfo info = element.second;
367                     if (info != null) {
368                         dequeueOutput(bufferID, info);
369                     } else {
370                         enqueueEOS(element.first);
371                     }
372                 }
373             }
374         } else {
375             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
376             while (!mSawInputEOS) {
377                 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
378                 if (outputBufferId >= 0) {
379                     dequeueOutput(outputBufferId, outInfo);
380                 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
381                     mOutFormat = mCodec.getOutputFormat();
382                     mSignalledOutFormatChanged = true;
383                 }
384                 int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
385                 if (inputBufferId != -1) {
386                     enqueueEOS(inputBufferId);
387                 }
388             }
389         }
390     }
391 
waitForAllOutputs()392     void waitForAllOutputs() throws InterruptedException {
393         if (mIsCodecInAsyncMode) {
394             while (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) {
395                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
396                 if (element != null) {
397                     dequeueOutput(element.first, element.second);
398                 }
399             }
400         } else {
401             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
402             while (!mSawOutputEOS) {
403                 int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
404                 if (outputBufferId >= 0) {
405                     dequeueOutput(outputBufferId, outInfo);
406                 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
407                     mOutFormat = mCodec.getOutputFormat();
408                     mSignalledOutFormatChanged = true;
409                 }
410             }
411         }
412     }
413 
selectCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder)414     static ArrayList<String> selectCodecs(String mediaType, ArrayList<MediaFormat> formats,
415             String[] features, boolean isEncoder) {
416         return selectCodecs(mediaType, formats, features, isEncoder, SELECT_ALL);
417     }
418 
selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder)419     static ArrayList<String> selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats,
420             String[] features, boolean isEncoder) {
421         return selectHardwareCodecs(mediaType, formats, features, isEncoder, false);
422     }
423 
selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder, boolean allCodecs)424     static ArrayList<String> selectHardwareCodecs(String mediaType, ArrayList<MediaFormat> formats,
425             String[] features, boolean isEncoder, boolean allCodecs) {
426         return selectCodecs(mediaType, formats, features, isEncoder, SELECT_HARDWARE, allCodecs);
427     }
428 
selectCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder, int selectCodecOption)429     static ArrayList<String> selectCodecs(String mediaType, ArrayList<MediaFormat> formats,
430             String[] features, boolean isEncoder, int selectCodecOption) {
431         return selectCodecs(mediaType, formats, features, isEncoder, selectCodecOption, false);
432     }
433 
selectCodecs(String mediaType, ArrayList<MediaFormat> formats, String[] features, boolean isEncoder, int selectCodecOption, boolean allCodecs)434     static ArrayList<String> selectCodecs(String mediaType, ArrayList<MediaFormat> formats,
435             String[] features, boolean isEncoder, int selectCodecOption, boolean allCodecs) {
436         int kind = allCodecs ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS;
437         MediaCodecList codecList = new MediaCodecList(kind);
438         MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
439         ArrayList<String> listOfCodecs = new ArrayList<>();
440         for (MediaCodecInfo codecInfo : codecInfos) {
441             if (codecInfo.isEncoder() != isEncoder) continue;
442             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
443             if (selectCodecOption == SELECT_HARDWARE && !codecInfo.isHardwareAccelerated())
444                 continue;
445             else if (selectCodecOption == SELECT_SOFTWARE && !codecInfo.isSoftwareOnly())
446                 continue;
447             String[] types = codecInfo.getSupportedTypes();
448             for (String type : types) {
449                 if (type.equalsIgnoreCase(mediaType)) {
450                     boolean isOk = true;
451                     MediaCodecInfo.CodecCapabilities codecCapabilities =
452                             codecInfo.getCapabilitiesForType(type);
453                     if (formats != null) {
454                         for (MediaFormat format : formats) {
455                             if (!codecCapabilities.isFormatSupported(format)) {
456                                 isOk = false;
457                                 break;
458                             }
459                         }
460                     }
461                     if (features != null) {
462                         for (String feature : features) {
463                             if (!codecCapabilities.isFeatureSupported(feature)) {
464                                 isOk = false;
465                                 break;
466                             }
467                         }
468                     }
469                     if (isOk) listOfCodecs.add(codecInfo.getName());
470                 }
471             }
472         }
473         return listOfCodecs;
474     }
475 
getMediaTypesOfAvailableCodecs(int codecAV, int codecType)476     static Set<String> getMediaTypesOfAvailableCodecs(int codecAV, int codecType) {
477         MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
478         MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
479         Set<String> listOfMediaTypes = new HashSet<>();
480         for (MediaCodecInfo codecInfo : codecInfos) {
481             if (codecType == SELECT_HARDWARE && !codecInfo.isHardwareAccelerated()) {
482                 continue;
483             }
484             if (codecType == SELECT_SOFTWARE && !codecInfo.isSoftwareOnly()) {
485                 continue;
486             }
487             String[] types = codecInfo.getSupportedTypes();
488             for (String type : types) {
489                 if (codecAV == SELECT_AUDIO && !type.startsWith("audio/")) {
490                     continue;
491                 }
492                 if (codecAV == SELECT_VIDEO && !type.startsWith("video/")) {
493                     continue;
494                 }
495                 listOfMediaTypes.add(type);
496             }
497         }
498         return listOfMediaTypes;
499     }
500 
501     /**
502      * Returns MediaCodecInfo for the given codec name
503      */
getCodecInfo(String codecName)504     public static MediaCodecInfo getCodecInfo(String codecName) {
505         for (MediaCodecInfo info : MCL_ALL.getCodecInfos()) {
506             if (info.getName().equals(codecName)) {
507                 return info;
508             }
509         }
510         return null;
511     }
512 
513     /**
514      * Return CodecCapabilities for the given codec name
515      */
getCodecCapabilities(String codecName, String mediaType)516     public static CodecCapabilities getCodecCapabilities(String codecName, String mediaType) {
517         MediaCodecInfo.CodecCapabilities codecCapabilities =
518                 getCodecInfo(codecName).getCapabilitiesForType(mediaType);
519         return codecCapabilities;
520 
521     }
522 
523     /**
524      * Checks if the codec supports all the given formats
525      */
areFormatsSupported(String codecName, ArrayList<MediaFormat> formats)526     public static boolean areFormatsSupported(String codecName, ArrayList<MediaFormat> formats) {
527         boolean isSupported = true;
528         MediaCodecInfo info = getCodecInfo(codecName);
529         if (info == null) {
530             return false;
531         }
532         for (MediaFormat format : formats) {
533             String mediaType = format.getString(MediaFormat.KEY_MIME);
534             MediaCodecInfo.CodecCapabilities codecCapabilities =
535                     info.getCapabilitiesForType(mediaType);
536             if (!codecCapabilities.isFormatSupported(format)) {
537                 Log.d(LOG_TAG, "Codec: " + codecName + " doesn't support format: " + format);
538                 return false;
539             }
540         }
541         return true;
542     }
543 }
544 
545 class CodecDecoderTestBase extends CodecTestBase {
546     private static final String LOG_TAG = CodecDecoderTestBase.class.getSimpleName();
547     // Widevine Content Protection Identifier https://dashif.org/identifiers/content_protection/
548     public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
549 
550     String mMediaType;
551     String mTestFile;
552     boolean mIsInterlaced;
553     boolean mSecureMode;
554     byte[] mSessionID;
555 
556     ArrayList<ByteBuffer> mCsdBuffers;
557 
558     MediaExtractor mExtractor;
559     MediaDrm mDrm = null;
560     MediaCrypto mCrypto = null;
561 
CodecDecoderTestBase(String mediaType, String testFile, boolean secureMode)562     CodecDecoderTestBase(String mediaType, String testFile, boolean secureMode) {
563         mMediaType = mediaType;
564         mTestFile = testFile;
565         mAsyncHandle = new CodecAsyncHandler();
566         mCsdBuffers = new ArrayList<>();
567         mIsAudio = mMediaType.startsWith("audio/");
568         mSecureMode = secureMode;
569     }
570 
CodecDecoderTestBase(String mediaType, String testFile)571     CodecDecoderTestBase(String mediaType, String testFile) {
572         this(mediaType, testFile, false);
573     }
574 
setUpSource(String srcFile)575     MediaFormat setUpSource(String srcFile) throws IOException {
576         return setUpSource(mInpPrefix, srcFile);
577     }
578 
hasCSD(MediaFormat format)579     boolean hasCSD(MediaFormat format) {
580         return format.containsKey("csd-0");
581     }
582 
openSession(MediaDrm drm)583     private byte[] openSession(MediaDrm drm) {
584         byte[] sessionId = null;
585         int retryCount = 3;
586         while (retryCount-- > 0) {
587             try {
588                 sessionId = drm.openSession();
589                 break;
590             } catch (NotProvisionedException eNotProvisioned) {
591                 Log.i(LOG_TAG, "Missing certificate, provisioning");
592                 try {
593                     final ProvisionRequester provisionRequester = new ProvisionRequester(drm);
594                     provisionRequester.send();
595                 } catch (Exception e) {
596                     Log.e(LOG_TAG, "Provisioning fails because " + e.toString());
597                 }
598             } catch (ResourceBusyException eResourceBusy) {
599                 Log.w(LOG_TAG, "Resource busy in openSession, retrying...");
600                 try {
601                     Thread.sleep(1000);
602                 } catch (Exception ignored) {
603                 }
604             }
605         }
606         return sessionId;
607     }
608 
configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder, String serverURL)609     void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
610             boolean isEncoder, String serverURL) throws Exception {
611         resetContext(isAsync, signalEOSWithLastFrame);
612         mAsyncHandle.setCallBack(mCodec, isAsync);
613         if (mSecureMode && serverURL != null) {
614             if (mDrm == null) {
615                 mDrm = new MediaDrm(WIDEVINE_UUID);
616             }
617             if (mCrypto == null) {
618                 mSessionID = openSession(mDrm);
619                 assertNotNull("Failed to provision device.", mSessionID);
620                 mCrypto = new MediaCrypto(WIDEVINE_UUID, mSessionID);
621             }
622             mCodec.configure(format, mSurface, mCrypto,
623                     isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
624 
625             Map<UUID, byte[]> psshInfo = mExtractor.getPsshInfo();
626             byte[] emeInitData = null;
627 
628             // TODO(b/230682028) Remove the following once webm extractor returns PSSH info for VP9
629             if (psshInfo == null && mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
630                 if (format.getInteger(MediaFormat.KEY_HEIGHT) == 1080) {
631                     emeInitData = new byte[]{8, 1, 18, 1, 51, 26, 13, 119, 105, 100, 101, 118,
632                             105, 110, 101, 95, 116, 101, 115, 116, 34, 10, 50, 48, 49, 53,
633                             95, 116, 101, 97, 114, 115, 42, 2, 72, 68};
634                 } else if (format.getInteger(MediaFormat.KEY_HEIGHT) == 2160) {
635                     emeInitData = new byte[]{8, 1, 18, 1, 56, 26, 13, 119, 105, 100, 101, 118,
636                             105, 110, 101, 95, 116, 101, 115, 116, 34, 10, 50, 48, 49, 53,
637                             95, 116, 101, 97, 114, 115, 42, 4, 85, 72, 68, 49};
638                 } else {
639                     fail("unable to get pssh info for the given resolution in vp9");
640                 }
641             } else {
642                 assertNotNull("Extractor is missing pssh info", psshInfo);
643                 emeInitData = psshInfo.get(WIDEVINE_UUID);
644             }
645             assertNotNull("Extractor pssh info is missing data for scheme: " + WIDEVINE_UUID,
646                     emeInitData);
647             KeyRequester requester =
648                     new KeyRequester(mDrm, mSessionID, MediaDrm.KEY_TYPE_STREAMING, mMediaType,
649                             emeInitData, serverURL, WIDEVINE_UUID);
650             requester.send();
651             return;
652         }
653         // signalEOS flag has nothing to do with configure. We are using this flag to try all
654         // available configure apis
655         if (signalEOSWithLastFrame) {
656             mCodec.configure(format, mSurface, null,
657                     isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
658         } else {
659             mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0,
660                     null);
661         }
662     }
663 
configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame, boolean isEncoder)664     void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
665             boolean isEncoder) throws Exception {
666         configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder, null);
667     }
668 
setUpSource(String prefix, String srcFile)669     MediaFormat setUpSource(String prefix, String srcFile) throws IOException {
670         mExtractor = new MediaExtractor();
671         mExtractor.setDataSource(prefix + srcFile);
672         for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) {
673             MediaFormat format = mExtractor.getTrackFormat(trackID);
674             if (mMediaType.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) {
675                 mExtractor.selectTrack(trackID);
676                 if (!mIsAudio) {
677                     if (mSurface == null) {
678                         // COLOR_FormatYUV420Flexible must be supported by all components
679                         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
680                     } else {
681                         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatSurface);
682                     }
683                 }
684                 // TODO: determine this from the extractor format when it becomes exposed.
685                 mIsInterlaced = srcFile.contains("_interlaced_");
686                 return format;
687             }
688         }
689         fail("No track with mediaType: " + mMediaType + " found in file: " + srcFile);
690         return null;
691     }
692 
enqueueInput(int bufferIndex)693     void enqueueInput(int bufferIndex) {
694         if (mExtractor.getSampleSize() < 0) {
695             enqueueEOS(bufferIndex);
696         } else {
697             ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
698             int size = mExtractor.readSampleData(inputBuffer, 0);
699             long pts = mExtractor.getSampleTime();
700             int extractorFlags = mExtractor.getSampleFlags();
701             int codecFlags = 0;
702             if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
703                 codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
704             }
705             MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo();
706             boolean isEncrypted = mExtractor.getSampleCryptoInfo(info);
707             if (!mExtractor.advance() && mSignalEOSWithLastFrame) {
708                 codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
709                 mSawInputEOS = true;
710             }
711             if (mSecureMode && isEncrypted) {
712                 mCodec.queueSecureInputBuffer(bufferIndex, 0, info, pts, codecFlags);
713             } else {
714                 mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags);
715             }
716             if (size > 0 && (codecFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
717                 mInputCount++;
718             }
719         }
720     }
721 
releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)722     void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info) {
723         mCodec.releaseOutputBuffer(bufferIndex, false);
724     }
725 }
726 
727 class CodecEncoderTestBase extends CodecTestBase {
728     private static final String LOG_TAG = CodecEncoderTestBase.class.getSimpleName();
729 
730     // files are in WorkDir.getMediaDirString();
731     private static final String INPUT_AUDIO_FILE = "bbb_2ch_44kHz_s16le.raw";
732     private static final String INPUT_VIDEO_FILE = "bbb_cif_yuv420p_30fps.yuv";
733     private final int INP_FRM_WIDTH = 352;
734     private final int INP_FRM_HEIGHT = 288;
735 
736     final String mMediaType;
737     final String mInputFile;
738     byte[] mInputData;
739     int mNumBytesSubmitted;
740     long mInputOffsetPts;
741 
742     int mWidth, mHeight;
743     int mFrameRate;
744     int mMaxBFrames;
745     int mChannels;
746     int mSampleRate;
747 
CodecEncoderTestBase(String mediaType)748     CodecEncoderTestBase(String mediaType) {
749         mMediaType = mediaType;
750         mWidth = INP_FRM_WIDTH;
751         mHeight = INP_FRM_HEIGHT;
752         mChannels = 1;
753         mSampleRate = 8000;
754         mFrameRate = 30;
755         mMaxBFrames = 0;
756         if (mediaType.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) mFrameRate = 12;
757         else if (mediaType.equals(MediaFormat.MIMETYPE_VIDEO_H263)) mFrameRate = 12;
758         mAsyncHandle = new CodecAsyncHandler();
759         mIsAudio = mMediaType.startsWith("audio/");
760         mInputFile = mIsAudio ? INPUT_AUDIO_FILE : INPUT_VIDEO_FILE;
761     }
762 
763     @Override
resetContext(boolean isAsync, boolean signalEOSWithLastFrame)764     void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
765         super.resetContext(isAsync, signalEOSWithLastFrame);
766         mNumBytesSubmitted = 0;
767         mInputOffsetPts = 0;
768     }
769 
setUpSource(String srcFile)770     void setUpSource(String srcFile) throws IOException {
771         String inpPath = mInpPrefix + srcFile;
772         try (FileInputStream fInp = new FileInputStream(inpPath)) {
773             int size = (int) new File(inpPath).length();
774             mInputData = new byte[size];
775             fInp.read(mInputData, 0, size);
776         }
777     }
778 
fillImage(Image image)779     void fillImage(Image image) {
780         Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888);
781         int imageWidth = image.getWidth();
782         int imageHeight = image.getHeight();
783         Image.Plane[] planes = image.getPlanes();
784         int offset = mNumBytesSubmitted;
785         for (int i = 0; i < planes.length; ++i) {
786             ByteBuffer buf = planes[i].getBuffer();
787             int width = imageWidth;
788             int height = imageHeight;
789             int tileWidth = INP_FRM_WIDTH;
790             int tileHeight = INP_FRM_HEIGHT;
791             int rowStride = planes[i].getRowStride();
792             int pixelStride = planes[i].getPixelStride();
793             if (i != 0) {
794                 width = imageWidth / 2;
795                 height = imageHeight / 2;
796                 tileWidth = INP_FRM_WIDTH / 2;
797                 tileHeight = INP_FRM_HEIGHT / 2;
798             }
799             if (pixelStride == 1) {
800                 if (width == rowStride && width == tileWidth && height == tileHeight) {
801                     buf.put(mInputData, offset, width * height);
802                 } else {
803                     for (int z = 0; z < height; z += tileHeight) {
804                         int rowsToCopy = Math.min(height - z, tileHeight);
805                         for (int y = 0; y < rowsToCopy; y++) {
806                             for (int x = 0; x < width; x += tileWidth) {
807                                 int colsToCopy = Math.min(width - x, tileWidth);
808                                 buf.position((z + y) * rowStride + x);
809                                 buf.put(mInputData, offset + y * tileWidth, colsToCopy);
810                             }
811                         }
812                     }
813                 }
814             } else {
815                 // do it pixel-by-pixel
816                 for (int z = 0; z < height; z += tileHeight) {
817                     int rowsToCopy = Math.min(height - z, tileHeight);
818                     for (int y = 0; y < rowsToCopy; y++) {
819                         int lineOffset = (z + y) * rowStride;
820                         for (int x = 0; x < width; x += tileWidth) {
821                             int colsToCopy = Math.min(width - x, tileWidth);
822                             for (int w = 0; w < colsToCopy; w++) {
823                                 buf.position(lineOffset + (x + w) * pixelStride);
824                                 buf.put(mInputData[offset + y * tileWidth + w]);
825                             }
826                         }
827                     }
828                 }
829             }
830             offset += tileWidth * tileHeight;
831         }
832     }
833 
fillByteBuffer(ByteBuffer inputBuffer)834     void fillByteBuffer(ByteBuffer inputBuffer) {
835         int offset = 0, frmOffset = mNumBytesSubmitted;
836         for (int plane = 0; plane < 3; plane++) {
837             int width = mWidth;
838             int height = mHeight;
839             int tileWidth = INP_FRM_WIDTH;
840             int tileHeight = INP_FRM_HEIGHT;
841             if (plane != 0) {
842                 width = mWidth / 2;
843                 height = mHeight / 2;
844                 tileWidth = INP_FRM_WIDTH / 2;
845                 tileHeight = INP_FRM_HEIGHT / 2;
846             }
847             for (int k = 0; k < height; k += tileHeight) {
848                 int rowsToCopy = Math.min(height - k, tileHeight);
849                 for (int j = 0; j < rowsToCopy; j++) {
850                     for (int i = 0; i < width; i += tileWidth) {
851                         int colsToCopy = Math.min(width - i, tileWidth);
852                         inputBuffer.position(offset + (k + j) * width + i);
853                         inputBuffer.put(mInputData, frmOffset + j * tileWidth, colsToCopy);
854                     }
855                 }
856             }
857             offset += width * height;
858             frmOffset += tileWidth * tileHeight;
859         }
860     }
861 
enqueueInput(int bufferIndex)862     void enqueueInput(int bufferIndex) {
863         ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
864         if (mNumBytesSubmitted >= mInputData.length) {
865             enqueueEOS(bufferIndex);
866         } else {
867             int size;
868             int flags = 0;
869             long pts = mInputOffsetPts;
870             if (mIsAudio) {
871                 pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate);
872                 size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted);
873                 inputBuffer.put(mInputData, mNumBytesSubmitted, size);
874                 if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) {
875                     flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
876                     mSawInputEOS = true;
877                 }
878                 mNumBytesSubmitted += size;
879             } else {
880                 pts += mInputCount * 1000000L / mFrameRate;
881                 size = mWidth * mHeight * 3 / 2;
882                 int frmSize = INP_FRM_WIDTH * INP_FRM_HEIGHT * 3 / 2;
883                 if (mNumBytesSubmitted + frmSize > mInputData.length) {
884                     fail("received partial frame to encode");
885                 } else {
886                     Image img = mCodec.getInputImage(bufferIndex);
887                     if (img != null) {
888                         fillImage(img);
889                     } else {
890                         if (mWidth == INP_FRM_WIDTH && mHeight == INP_FRM_HEIGHT) {
891                             inputBuffer.put(mInputData, mNumBytesSubmitted, size);
892                         } else {
893                             fillByteBuffer(inputBuffer);
894                         }
895                     }
896                 }
897                 if (mNumBytesSubmitted + frmSize >= mInputData.length && mSignalEOSWithLastFrame) {
898                     flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
899                     mSawInputEOS = true;
900                 }
901                 mNumBytesSubmitted += frmSize;
902             }
903             mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags);
904             mInputCount++;
905         }
906     }
907 
releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)908     void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info) {
909         mCodec.releaseOutputBuffer(bufferIndex, false);
910     }
911 }
912 
913 /**
914  * The following class decodes the given testFile using decoder created by the given decoderName
915  * in surface mode(uses PersistentInputSurface) and returns the achieved fps for decoding.
916  */
917 class Decode extends CodecDecoderTestBase implements Callable<CodecMetrics> {
918     private static final String LOG_TAG = Decode.class.getSimpleName();
919 
920     final String mDecoderName;
921     static final long EACH_FRAME_TIME_INTERVAL_US = 1000000 / 30;
922     static final String WIDEVINE_LICENSE_SERVER_URL = "https://proxy.uat.widevine.com/proxy";
923     static final String PROVIDER = "widevine_test";
924     final String mServerURL =
925             String.format("%s?video_id=%s&provider=%s", WIDEVINE_LICENSE_SERVER_URL,
926                     "GTS_HW_SECURE_ALL", PROVIDER);
927     final boolean mIsAsync;
928     private int mInitialFramesToIgnoreCount = 1;
929     private long mStartTimeMillis = 0;
930     private long mEndTimeMillis = 0;
931 
932     double mFrameDrops;
933     long mRenderedStartTimeUs;
934 
Decode(String mediaType, String testFile, String decoderName, boolean isAsync)935     Decode(String mediaType, String testFile, String decoderName, boolean isAsync) {
936         this(mediaType, testFile,decoderName, isAsync, false);
937     }
938 
Decode(String mediaType, String testFile, String decoderName, boolean isAsync, boolean secureMode)939     Decode(String mediaType, String testFile, String decoderName, boolean isAsync,
940            boolean secureMode) {
941         super(mediaType, testFile);
942         mDecoderName = decoderName;
943         mSurface = MediaCodec.createPersistentInputSurface();
944         mIsAsync = isAsync;
945         mSecureMode = secureMode;
946     }
947 
setInitialFramesToIgnoreCount(int count)948     public void setInitialFramesToIgnoreCount(int count) {
949         mInitialFramesToIgnoreCount = count;
950     }
951 
952     // measure throughput at the output port
onOutputCountListener(int count)953     private void onOutputCountListener(int count) {
954         // keep the timestamp of the last output frame
955         mEndTimeMillis = System.currentTimeMillis();
956 
957         // don't count the time for the initial frames that are ignored
958         if (count == mInitialFramesToIgnoreCount) {
959             mStartTimeMillis = mEndTimeMillis;
960         }
961     }
962 
getRenderedTimeUs(int frameIndex)963     private long getRenderedTimeUs(int frameIndex) {
964         return mRenderedStartTimeUs + frameIndex * EACH_FRAME_TIME_INTERVAL_US;
965     }
966 
967     @Override
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)968     protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
969         evalFrameDropsWhileDequeue(bufferIndex, info, mMediaType);
970     }
971 
evalFrameDropsWhileDequeue(int bufferIndex, MediaCodec.BufferInfo info, String mediaType)972     private void evalFrameDropsWhileDequeue(int bufferIndex, MediaCodec.BufferInfo info,
973                              String mediaType) {
974         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
975             mSawOutputEOS = true;
976         }
977 
978         int outputCount = mOutputCount;
979         long nowUs = System.nanoTime() / 1000;
980         int initialDelay = mediaType.equals(MediaFormat.MIMETYPE_VIDEO_AV1) ? 8 : 0;
981 
982         if (outputCount == 0) {
983             // delay rendering the first frame by the specific delay
984             mRenderedStartTimeUs = nowUs + initialDelay * EACH_FRAME_TIME_INTERVAL_US;
985         }
986 
987         if (nowUs > getRenderedTimeUs(outputCount + 1)) {
988             // If the current sample timeStamp is greater than the actual presentation timeStamp
989             // of the next sample, we will consider it as a frame drop and don't render.
990             mFrameDrops++;
991             mCodec.releaseOutputBuffer(bufferIndex, false);
992         } else if (nowUs > getRenderedTimeUs(outputCount)) {
993             // If the current sample timeStamp is greater than the actual presentation timeStamp
994             // of the current sample, we can render it.
995             mCodec.releaseOutputBuffer(bufferIndex, true);
996         } else {
997             // If the current sample timestamp is less than the actual presentation timeStamp,
998             // We are okay with directly rendering the sample if we are less by not more than
999             // half of one sample duration. Otherwise we sleep for how much more we are less
1000             // than the half of one sample duration.
1001             if ((getRenderedTimeUs(outputCount) - nowUs) > (EACH_FRAME_TIME_INTERVAL_US / 2)) {
1002                 try {
1003                     Thread.sleep(((getRenderedTimeUs(outputCount) - nowUs)
1004                             - (EACH_FRAME_TIME_INTERVAL_US / 2)) / 1000);
1005                 } catch (InterruptedException e) {
1006                     Thread.currentThread().interrupt(); // Restore the interrupted status
1007                     throw new RuntimeException("the thread caught an interrupted exception"
1008                             + "instead of sleeping before rendering the sample timestamp" + e);
1009                 }
1010             }
1011             mCodec.releaseOutputBuffer(bufferIndex, true);
1012         }
1013 
1014         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
1015             mOutputCount++;
1016             if (mOutputCountListener != null) {
1017                 mOutputCountListener.accept(mOutputCount);
1018             }
1019         }
1020     }
1021 
1022     @Override
resetContext(boolean isAsync, boolean signalEOSWithLastFrame)1023     void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
1024         mFrameDrops = 0;
1025         mRenderedStartTimeUs = 0;
1026         super.resetContext(isAsync, signalEOSWithLastFrame);
1027     }
1028 
doDecode()1029     public CodecMetrics doDecode() throws Exception {
1030         MediaFormat format = setUpSource(mTestFile);
1031         ArrayList<MediaFormat> formats = new ArrayList<>();
1032         formats.add(format);
1033         // If the decoder doesn't support the formats, then return 0 to indicate that decode failed
1034         if (!areFormatsSupported(mDecoderName, formats)) {
1035             return getMetrics(0.0, 0.0);
1036         }
1037         mCodec = MediaCodec.createByCodecName(mDecoderName);
1038         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
1039         configureCodec(format, mIsAsync, false, false, mServerURL);
1040         // TODO(b/251003943) Remove once Surface from SurfaceView is used for secure decoders
1041         try {
1042             mCodec.start();
1043         } catch (Exception e) {
1044             Log.e(LOG_TAG, "Stopping the test because codec.start() failed.", e);
1045             mCodec.release();
1046             return getMetrics(0.0, 0.0);
1047         }
1048 
1049         // capture timestamps at receipt of output buffers
1050         setOutputCountListener(i -> onOutputCountListener(i));
1051 
1052         doWork(Integer.MAX_VALUE);
1053         queueEOS();
1054         waitForAllOutputs();
1055 
1056         mCodec.stop();
1057         mCodec.release();
1058         mExtractor.release();
1059         if (mCrypto != null) {
1060             mCrypto.release();
1061         }
1062         if (mDrm != null) {
1063             mDrm.close();
1064         }
1065         double fps = (mOutputCount - mInitialFramesToIgnoreCount) /
1066                 ((mEndTimeMillis - mStartTimeMillis) / 1000.0);
1067         Log.d(LOG_TAG, "Decode MediaType: " + mMediaType + " Decoder: " + mDecoderName +
1068                 " Achieved fps: " + fps);
1069         return getMetrics(fps, mFrameDrops / 30);
1070     }
1071 
1072     @Override
call()1073     public CodecMetrics call() throws Exception {
1074         try {
1075             return doDecode();
1076         } catch (Exception e) {
1077             Log.d(LOG_TAG, "Decode MediaType: " + mMediaType + " Decoder: " + mDecoderName
1078                     + " Failed due to: " + e);
1079             return getMetrics(-1.0, 0.0);
1080         }
1081     }
1082 }
1083 
1084 /**
1085  * The following class decodes the given testFile using decoder created by the given decoderName
1086  * in surface mode(uses given valid surface) and render the output to surface.
1087  */
1088 class DecodeToSurface extends Decode {
1089 
DecodeToSurface(String mediaType, String testFile, String decoderName, Surface surface, boolean isAsync)1090     DecodeToSurface(String mediaType, String testFile, String decoderName, Surface surface,
1091             boolean isAsync) {
1092         super(mediaType, testFile, decoderName, isAsync);
1093         mSurface = surface;
1094     }
1095 
releaseOutput(int bufferIndex, MediaCodec.BufferInfo info)1096     void releaseOutput(int bufferIndex, MediaCodec.BufferInfo info) {
1097         mCodec.releaseOutputBuffer(bufferIndex, true);
1098     }
1099 }
1100 
1101 /**
1102  * The following class encodes a YUV video file to a given mediaType using encoder created by the
1103  * given encoderName and configuring to 30fps format.
1104  */
1105 class Encode extends CodecEncoderTestBase implements Callable<CodecMetrics> {
1106     private static final String LOG_TAG = Encode.class.getSimpleName();
1107 
1108     private final String mEncoderName;
1109     private final boolean mIsAsync;
1110     private final int mBitrate;
1111 
1112     private int mInitialFramesToIgnoreCount = 1;
1113     private long mStartTimeMillis = 0;
1114     private long mEndTimeMillis = 0;
1115 
Encode(String mediaType, String encoderName, boolean isAsync, int height, int width, int frameRate, int bitrate)1116     Encode(String mediaType, String encoderName, boolean isAsync, int height, int width,
1117            int frameRate, int bitrate) {
1118         super(mediaType);
1119         mEncoderName = encoderName;
1120         mIsAsync = isAsync;
1121         mFrameRate = frameRate;
1122         mBitrate = bitrate;
1123         mHeight = height;
1124         mWidth = width;
1125     }
1126 
setInitialFramesToIgnoreCount(int count)1127     public void setInitialFramesToIgnoreCount(int count) {
1128         mInitialFramesToIgnoreCount = count;
1129     }
1130 
1131     // measure throughput at the output port
onOutputCountListener(int count)1132     private void onOutputCountListener(int count) {
1133         // keep the timestamp of the last output frame
1134         mEndTimeMillis = System.currentTimeMillis();
1135 
1136         // don't count the time for the initial frames that are ignored
1137         if (count == mInitialFramesToIgnoreCount) {
1138             mStartTimeMillis = mEndTimeMillis;
1139         }
1140     }
1141 
setUpFormat()1142     private MediaFormat setUpFormat() {
1143         MediaFormat format = new MediaFormat();
1144         format.setString(MediaFormat.KEY_MIME, mMediaType);
1145         format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
1146         format.setInteger(MediaFormat.KEY_WIDTH, mWidth);
1147         format.setInteger(MediaFormat.KEY_HEIGHT, mHeight);
1148         format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
1149         format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0);
1150         format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
1151         format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
1152                 MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
1153         return format;
1154     }
1155 
1156 
doEncode()1157     public CodecMetrics doEncode() throws Exception {
1158         MediaFormat format = setUpFormat();
1159         mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
1160         mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
1161         setUpSource(mInputFile);
1162         mCodec = MediaCodec.createByCodecName(mEncoderName);
1163         configureCodec(format, mIsAsync, false, true);
1164         mCodec.start();
1165 
1166         // capture timestamps at receipt of output buffers
1167         setOutputCountListener(i -> onOutputCountListener(i));
1168 
1169         doWork(Integer.MAX_VALUE);
1170         queueEOS();
1171         waitForAllOutputs();
1172 
1173         mCodec.stop();
1174         mCodec.release();
1175         double fps = (mOutputCount - mInitialFramesToIgnoreCount) /
1176                 ((mEndTimeMillis - mStartTimeMillis) / 1000.0);
1177         Log.d(LOG_TAG, "Encode MediaType: " + mMediaType + " Encoder: " + mEncoderName +
1178                 " Achieved fps: " + fps);
1179         return getMetrics(fps, 0.0);
1180     }
1181 
1182     @Override
call()1183     public CodecMetrics call() throws Exception {
1184         return doEncode();
1185     }
1186 }
1187