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.media.MediaCodec;
20 import android.media.MediaCodecInfo;
21 import android.media.MediaExtractor;
22 import android.media.MediaFormat;
23 import android.util.Log;
24 import android.view.Surface;
25 
26 import androidx.test.filters.LargeTest;
27 import androidx.test.filters.SmallTest;
28 
29 import org.junit.Assume;
30 import org.junit.Ignore;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 import org.junit.runners.Parameterized;
34 
35 import java.io.File;
36 import java.io.FileInputStream;
37 import java.io.IOException;
38 import java.nio.ByteBuffer;
39 import java.nio.ByteOrder;
40 import java.nio.channels.FileChannel;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collection;
44 import java.util.List;
45 
46 import static org.junit.Assert.assertEquals;
47 import static org.junit.Assert.assertTrue;
48 
49 /**
50  * Validate decode functionality of listed decoder components
51  *
52  * The test aims to test all decoders advertised in MediaCodecList. Hence we are not using
53  * MediaCodecList#findDecoderForFormat to create codec. Further, it can so happen that the
54  * test clip chosen is not supported by component (codecCapabilities.isFormatSupported()
55  * fails), then it is better to replace the clip but not skip testing the component. The idea
56  * of these tests are not to cover CDD requirements but to test components and their plugins
57  */
58 @RunWith(Parameterized.class)
59 public class CodecDecoderTest extends CodecDecoderTestBase {
60     private static final String LOG_TAG = CodecDecoderTest.class.getSimpleName();
61     private static final float RMS_ERROR_TOLERANCE = 1.05f;        // 5%
62 
63     private final String mRefFile;
64     private final String mReconfigFile;
65     private final float mRmsError;
66     private final long mRefCRC;
67 
CodecDecoderTest(String decoder, String mime, String testFile, String refFile, String reconfigFile, float rmsError, long refCRC)68     public CodecDecoderTest(String decoder, String mime, String testFile, String refFile,
69             String reconfigFile, float rmsError, long refCRC) {
70         super(decoder, mime, testFile);
71         mRefFile = refFile;
72         mReconfigFile = reconfigFile;
73         mRmsError = rmsError;
74         mRefCRC = refCRC;
75     }
76 
setUpAudioReference(String file)77     static short[] setUpAudioReference(String file) throws IOException {
78         File refFile = new File(file);
79         short[] refData;
80         try (FileInputStream refStream = new FileInputStream(refFile)) {
81             FileChannel fileChannel = refStream.getChannel();
82             int length = (int) refFile.length();
83             ByteBuffer refBuffer = ByteBuffer.allocate(length);
84             refBuffer.order(ByteOrder.LITTLE_ENDIAN);
85             fileChannel.read(refBuffer);
86             refData = new short[length / 2];
87             refBuffer.position(0);
88             for (int i = 0; i < length / 2; i++) {
89                 refData[i] = refBuffer.getShort();
90             }
91         }
92         return refData;
93     }
94 
createSubFrames(ByteBuffer buffer, int sfCount)95     private ArrayList<MediaCodec.BufferInfo> createSubFrames(ByteBuffer buffer, int sfCount) {
96         int size = (int) mExtractor.getSampleSize();
97         if (size < 0) return null;
98         mExtractor.readSampleData(buffer, 0);
99         long pts = mExtractor.getSampleTime();
100         int flags = mExtractor.getSampleFlags();
101         if (size < sfCount) sfCount = size;
102         ArrayList<MediaCodec.BufferInfo> list = new ArrayList<>();
103         int offset = 0;
104         for (int i = 0; i < sfCount; i++) {
105             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
106             info.offset = offset;
107             info.presentationTimeUs = pts;
108             info.flags = 0;
109             if ((flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
110                 info.flags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
111             }
112             if ((flags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
113                 info.flags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
114             }
115             if (i != sfCount - 1) {
116                 info.size = size / sfCount;
117                 info.flags |= MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME;
118             } else {
119                 info.size = size - offset;
120             }
121             list.add(info);
122             offset += info.size;
123         }
124         return list;
125     }
126 
127     @Parameterized.Parameters(name = "{index}({0}_{1})")
input()128     public static Collection<Object[]> input() {
129         final boolean isEncoder = false;
130         final boolean needAudio = true;
131         final boolean needVideo = true;
132         // mime, testClip, referenceClip, reconfigureTestClip, refRmsError, refCRC32
133         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
134                 {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_8kHz_lame_cbr.mp3",
135                         "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_lame_vbr.mp3", 91.022f, -1L},
136                 {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_2ch_44kHz_lame_cbr.mp3",
137                         "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_lame_vbr.mp3", 103.60f, -1L},
138                 {MediaFormat.MIMETYPE_AUDIO_AMR_WB, "bbb_1ch_16kHz_16kbps_amrwb.3gp",
139                         "bbb_1ch_16kHz_s16le.raw", "bbb_1ch_16kHz_23kbps_amrwb.3gp", 2393.598f,
140                         -1L},
141                 {MediaFormat.MIMETYPE_AUDIO_AMR_NB, "bbb_1ch_8kHz_10kbps_amrnb.3gp",
142                         "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_8kbps_amrnb.3gp", -1.0f, -1L},
143                 {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_1ch_16kHz_flac.mka",
144                         "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_flac.mka", 0.0f, -1L},
145                 {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_2ch_44kHz_flac.mka",
146                         "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_flac.mka", 0.0f, -1L},
147                 {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_1ch_16kHz.wav", "bbb_1ch_16kHz_s16le.raw",
148                         "bbb_2ch_44kHz.wav", 0.0f, -1L},
149                 {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_2ch_44kHz.wav", "bbb_2ch_44kHz_s16le.raw",
150                         "bbb_1ch_16kHz.wav", 0.0f, -1L},
151                 {MediaFormat.MIMETYPE_AUDIO_G711_ALAW, "bbb_1ch_8kHz_alaw.wav",
152                         "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_8kHz_alaw.wav", 23.08678f, -1L},
153                 {MediaFormat.MIMETYPE_AUDIO_G711_MLAW, "bbb_1ch_8kHz_mulaw.wav",
154                         "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_8kHz_mulaw.wav", 24.4131f, -1L},
155                 {MediaFormat.MIMETYPE_AUDIO_MSGSM, "bbb_1ch_8kHz_gsm.wav",
156                         "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_gsm.wav", 946.02698f, -1L},
157                 {MediaFormat.MIMETYPE_AUDIO_VORBIS, "bbb_1ch_16kHz_vorbis.mka",
158                         "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_vorbis.mka", -1.0f, -1L},
159                 {MediaFormat.MIMETYPE_AUDIO_OPUS, "bbb_2ch_48kHz_opus.mka",
160                         "bbb_2ch_48kHz_s16le.raw", "bbb_1ch_48kHz_opus.mka", -1.0f, -1L},
161                 {MediaFormat.MIMETYPE_AUDIO_AAC, "bbb_1ch_16kHz_aac.mp4",
162                         "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_aac.mp4", -1.0f, -1L},
163                 {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_340x280_768kbps_30fps_mpeg2.mp4", null,
164                         "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f, -1L},
165                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_340x280_768kbps_30fps_avc.mp4", null,
166                         "bbb_520x390_1mbps_30fps_avc.mp4", -1.0f, 1746312400L},
167                 {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_520x390_1mbps_30fps_hevc.mp4", null,
168                         "bbb_340x280_768kbps_30fps_hevc.mp4", -1.0f, 3061322606L},
169                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4",
170                         null, "bbb_176x144_192kbps_15fps_mpeg4.mp4", -1.0f, -1L},
171                 {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp",
172                         null, "bbb_176x144_192kbps_10fps_h263.3gp", -1.0f, -1L},
173                 {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_340x280_768kbps_30fps_vp8.webm", null,
174                         "bbb_520x390_1mbps_30fps_vp8.webm", -1.0f, 2030620796L},
175                 {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_340x280_768kbps_30fps_vp9.webm", null,
176                         "bbb_520x390_1mbps_30fps_vp9.webm", -1.0f, 4122701060L},
177                 {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_340x280_768kbps_30fps_av1.mp4", null,
178                         "bbb_520x390_1mbps_30fps_av1.mp4", -1.0f, 400672933L},
179         });
180         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, true);
181     }
182 
nativeTestSimpleDecode(String decoder, Surface surface, String mime, String testFile, String refFile, float rmsError, long checksum)183     private native boolean nativeTestSimpleDecode(String decoder, Surface surface, String mime,
184             String testFile, String refFile, float rmsError, long checksum);
185 
verify(OutputManager outBuff, String refFile, float rmsError, long refCRC)186     static void verify(OutputManager outBuff, String refFile, float rmsError, long refCRC)
187             throws IOException {
188         if (rmsError >= 0) {
189             short[] refData = setUpAudioReference(mInpPrefix + refFile);
190             float currError = outBuff.getRmsError(refData);
191             float errMargin = rmsError * RMS_ERROR_TOLERANCE;
192             assertTrue(String.format("%s rms error too high ref/exp/got %f/%f/%f", refFile,
193                     rmsError, errMargin, currError), currError <= errMargin);
194         } else if (refCRC >= 0) {
195             assertEquals("checksum mismatch", refCRC, outBuff.getCheckSumImage());
196         }
197     }
198 
199     /**
200      * Tests decoder for combinations:
201      * 1. Codec Sync Mode, Signal Eos with Last frame
202      * 2. Codec Sync Mode, Signal Eos Separately
203      * 3. Codec Async Mode, Signal Eos with Last frame
204      * 4. Codec Async Mode, Signal Eos Separately
205      * In all these scenarios, Timestamp ordering is verified, For audio the Rms of output has to be
206      * within the allowed tolerance. The output has to be consistent (not flaky) in all runs.
207      */
208     @LargeTest
209     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleDecode()210     public void testSimpleDecode() throws IOException, InterruptedException {
211         MediaFormat format = setUpSource(mTestFile);
212         boolean[] boolStates = {true, false};
213         mSaveToMem = true;
214         OutputManager ref = new OutputManager();
215         OutputManager test = new OutputManager();
216         {
217             mCodec = MediaCodec.createByCodecName(mCodecName);
218             assertTrue("codec name act/got: " + mCodec.getName() + '/' + mCodecName,
219                     mCodec.getName().equals(mCodecName));
220             assertTrue("error! codec canonical name is null",
221                     mCodec.getCanonicalName() != null && !mCodec.getCanonicalName().isEmpty());
222             validateMetrics(mCodecName);
223             int loopCounter = 0;
224             for (boolean eosType : boolStates) {
225                 for (boolean isAsync : boolStates) {
226                     boolean validateFormat = true;
227                     String log = String.format("codec: %s, file: %s, mode: %s, eos type: %s:: ",
228                             mCodecName, mTestFile, (isAsync ? "async" : "sync"),
229                             (eosType ? "eos with last frame" : "eos separate"));
230                     mOutputBuff = loopCounter == 0 ? ref : test;
231                     mOutputBuff.reset();
232                     mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
233                     configureCodec(format, isAsync, eosType, false);
234                     MediaFormat defFormat = mCodec.getOutputFormat();
235                     if (isFormatSimilar(format, defFormat)) {
236                         if (ENABLE_LOGS) {
237                             Log.d("Input format is same as default for format for %s", mCodecName);
238                         }
239                         validateFormat = false;
240                     }
241                     mCodec.start();
242                     doWork(Integer.MAX_VALUE);
243                     queueEOS();
244                     waitForAllOutputs();
245                     validateMetrics(mCodecName, format);
246                     /* TODO(b/147348711) */
247                     if (false) mCodec.stop();
248                     else mCodec.reset();
249                     assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
250                     assertTrue(log + "no input sent", 0 != mInputCount);
251                     assertTrue(log + "output received", 0 != mOutputCount);
252                     if (loopCounter != 0) {
253                         assertTrue(log + "decoder output is flaky", ref.equals(test));
254                     } else {
255                         if (mIsAudio) {
256                             assertTrue(log + " pts is not strictly increasing",
257                                     ref.isPtsStrictlyIncreasing(mPrevOutputPts));
258                         } else {
259                             assertTrue(
260                                     log + " input pts list and output pts list are not identical",
261                                     ref.isOutPtsListIdenticalToInpPtsList(false));
262                         }
263                     }
264                     if (validateFormat) {
265                         assertTrue(log + "not received format change",
266                                 mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
267                                         mSignalledOutFormatChanged);
268                         assertTrue(log + "configured format and output format are not similar",
269                                 isFormatSimilar(format,
270                                         mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
271                                                 mOutFormat));
272                     }
273                     loopCounter++;
274                 }
275             }
276             mCodec.release();
277             if (mSaveToMem) verify(mOutputBuff, mRefFile, mRmsError, mRefCRC);
278             assertTrue(nativeTestSimpleDecode(mCodecName, null, mMime, mInpPrefix + mTestFile,
279                     mInpPrefix + mRefFile, mRmsError, ref.getCheckSumBuffer()));
280         }
281         mExtractor.release();
282     }
283 
284     /**
285      * Tests flush when codec is in sync and async mode. In these scenarios, Timestamp
286      * ordering is verified. The output has to be consistent (not flaky) in all runs
287      */
288     @Ignore("TODO(b/147576107)")
289     @LargeTest
290     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testFlush()291     public void testFlush() throws IOException, InterruptedException {
292         MediaFormat format = setUpSource(mTestFile);
293         mExtractor.release();
294         mCsdBuffers.clear();
295         for (int i = 0; ; i++) {
296             String csdKey = "csd-" + i;
297             if (format.containsKey(csdKey)) {
298                 mCsdBuffers.add(format.getByteBuffer(csdKey));
299             } else break;
300         }
301         final long pts = 500000;
302         final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
303         boolean[] boolStates = {true, false};
304         OutputManager test = new OutputManager();
305         {
306             decodeToMemory(mTestFile, mCodecName, pts, mode, Integer.MAX_VALUE);
307             OutputManager ref = mOutputBuff;
308             if (mIsAudio) {
309                 assertTrue("reference output pts is not strictly increasing",
310                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
311             } else {
312                 assertTrue("input pts list and output pts list are not identical",
313                         ref.isOutPtsListIdenticalToInpPtsList(false));
314             }
315             mOutputBuff = test;
316             setUpSource(mTestFile);
317             mCodec = MediaCodec.createByCodecName(mCodecName);
318             for (boolean isAsync : boolStates) {
319                 String log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
320                         mTestFile, (isAsync ? "async" : "sync"));
321                 mExtractor.seekTo(0, mode);
322                 configureCodec(format, isAsync, true, false);
323                 MediaFormat defFormat = mCodec.getOutputFormat();
324                 boolean validateFormat = true;
325                 if (isFormatSimilar(format, defFormat)) {
326                     if (ENABLE_LOGS) {
327                         Log.d("Input format is same as default for format for %s", mCodecName);
328                     }
329                     validateFormat = false;
330                 }
331                 mCodec.start();
332 
333                 /* test flush in running state before queuing input */
334                 flushCodec();
335                 if (mIsCodecInAsyncMode) mCodec.start();
336                 queueCodecConfig(); /* flushed codec too soon after start, resubmit csd */
337 
338                 doWork(1);
339                 flushCodec();
340                 if (mIsCodecInAsyncMode) mCodec.start();
341                 queueCodecConfig(); /* flushed codec too soon after start, resubmit csd */
342 
343                 mExtractor.seekTo(0, mode);
344                 test.reset();
345                 doWork(23);
346                 assertTrue(log + " pts is not strictly increasing",
347                         test.isPtsStrictlyIncreasing(mPrevOutputPts));
348 
349                 boolean checkMetrics = (mOutputCount != 0);
350 
351                 /* test flush in running state */
352                 flushCodec();
353                 if (checkMetrics) validateMetrics(mCodecName, format);
354                 if (mIsCodecInAsyncMode) mCodec.start();
355                 mSaveToMem = true;
356                 test.reset();
357                 mExtractor.seekTo(pts, mode);
358                 doWork(Integer.MAX_VALUE);
359                 queueEOS();
360                 waitForAllOutputs();
361                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
362                 assertTrue(log + "no input sent", 0 != mInputCount);
363                 assertTrue(log + "output received", 0 != mOutputCount);
364                 assertTrue(log + "decoder output is flaky", ref.equals(test));
365 
366                 /* test flush in eos state */
367                 flushCodec();
368                 if (mIsCodecInAsyncMode) mCodec.start();
369                 test.reset();
370                 mExtractor.seekTo(pts, mode);
371                 doWork(Integer.MAX_VALUE);
372                 queueEOS();
373                 waitForAllOutputs();
374                 /* TODO(b/147348711) */
375                 if (false) mCodec.stop();
376                 else mCodec.reset();
377                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
378                 assertTrue(log + "no input sent", 0 != mInputCount);
379                 assertTrue(log + "output received", 0 != mOutputCount);
380                 assertTrue(log + "decoder output is flaky", ref.equals(test));
381                 if (validateFormat) {
382                     assertTrue(log + "not received format change",
383                             mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
384                                     mSignalledOutFormatChanged);
385                     assertTrue(log + "configured format and output format are not similar",
386                             isFormatSimilar(format,
387                                     mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
388                                             mOutFormat));
389                 }
390                 mSaveToMem = false;
391             }
392             mCodec.release();
393             mExtractor.release();
394         }
395     }
396 
nativeTestFlush(String decoder, Surface surface, String mime, String testFile)397     private native boolean nativeTestFlush(String decoder, Surface surface, String mime,
398             String testFile);
399 
400     @Ignore("TODO(b/147576107)")
401     @LargeTest
402     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testFlushNative()403     public void testFlushNative() {
404         {
405             assertTrue(nativeTestFlush(mCodecName, null, mMime, mInpPrefix + mTestFile));
406         }
407     }
408 
409     /**
410      * Tests reconfigure when codec is in sync and async mode. In these scenarios, Timestamp
411      * ordering is verified. The output has to be consistent (not flaky) in all runs
412      */
413     @LargeTest
414     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testReconfigure()415     public void testReconfigure() throws IOException, InterruptedException {
416         Assume.assumeTrue("Test needs Android 11", IS_AT_LEAST_R);
417 
418         MediaFormat format = setUpSource(mTestFile);
419         mExtractor.release();
420         MediaFormat newFormat = setUpSource(mReconfigFile);
421         mExtractor.release();
422         final long startTs = 0;
423         final long seekTs = 500000;
424         final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
425         boolean[] boolStates = {true, false};
426         OutputManager test = new OutputManager();
427         {
428             decodeToMemory(mTestFile, mCodecName, startTs, mode, Integer.MAX_VALUE);
429             OutputManager ref = mOutputBuff;
430             decodeToMemory(mReconfigFile, mCodecName, seekTs, mode, Integer.MAX_VALUE);
431             OutputManager configRef = mOutputBuff;
432             if (mIsAudio) {
433                 assertTrue("reference output pts is not strictly increasing",
434                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
435                 assertTrue("config reference output pts is not strictly increasing",
436                         configRef.isPtsStrictlyIncreasing(mPrevOutputPts));
437             } else {
438                 assertTrue("input pts list and reference pts list are not identical",
439                         ref.isOutPtsListIdenticalToInpPtsList(false));
440                 assertTrue("input pts list and reconfig ref output pts list are not identical",
441                         ref.isOutPtsListIdenticalToInpPtsList(false));
442             }
443             mOutputBuff = test;
444             mCodec = MediaCodec.createByCodecName(mCodecName);
445             for (boolean isAsync : boolStates) {
446                 setUpSource(mTestFile);
447                 String log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
448                         mTestFile, (isAsync ? "async" : "sync"));
449                 mExtractor.seekTo(startTs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
450                 configureCodec(format, isAsync, true, false);
451                 MediaFormat defFormat = mCodec.getOutputFormat();
452                 boolean validateFormat = true;
453                 if (isFormatSimilar(format, defFormat)) {
454                     if (ENABLE_LOGS) {
455                         Log.d("Input format is same as default for format for %s", mCodecName);
456                     }
457                     validateFormat = false;
458                 }
459 
460                 /* test reconfigure in stopped state */
461                 reConfigureCodec(format, !isAsync, false, false);
462                 mCodec.start();
463 
464                 /* test reconfigure in running state before queuing input */
465                 reConfigureCodec(format, !isAsync, false, false);
466                 mCodec.start();
467                 doWork(23);
468 
469                 if (mOutputCount != 0) {
470                     if (validateFormat) {
471                         assertTrue(log + "not received format change",
472                                 mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
473                                         mSignalledOutFormatChanged);
474                         assertTrue(log + "configured format and output format are not similar",
475                                 isFormatSimilar(format,
476                                         mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
477                                                 mOutFormat));
478                     }
479                     validateMetrics(mCodecName, format);
480                 }
481 
482                 /* test reconfigure codec in running state */
483                 reConfigureCodec(format, isAsync, true, false);
484                 mCodec.start();
485                 mSaveToMem = true;
486                 test.reset();
487                 mExtractor.seekTo(startTs, mode);
488                 doWork(Integer.MAX_VALUE);
489                 queueEOS();
490                 waitForAllOutputs();
491                 if (mSaveToMem) verify(mOutputBuff, mRefFile, mRmsError, mRefCRC);
492                 /* TODO(b/147348711) */
493                 if (false) mCodec.stop();
494                 else mCodec.reset();
495                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
496                 assertTrue(log + "no input sent", 0 != mInputCount);
497                 assertTrue(log + "output received", 0 != mOutputCount);
498                 assertTrue(log + "decoder output is flaky", ref.equals(test));
499                 if (validateFormat) {
500                     assertTrue(log + "not received format change",
501                             mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
502                                     mSignalledOutFormatChanged);
503                     assertTrue(log + "configured format and output format are not similar",
504                             isFormatSimilar(format,
505                                     mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
506                                             mOutFormat));
507                 }
508 
509                 /* test reconfigure codec at eos state */
510                 reConfigureCodec(format, !isAsync, false, false);
511                 mCodec.start();
512                 test.reset();
513                 mExtractor.seekTo(startTs, mode);
514                 doWork(Integer.MAX_VALUE);
515                 queueEOS();
516                 waitForAllOutputs();
517                 if (mSaveToMem) verify(mOutputBuff, mRefFile, mRmsError, mRefCRC);
518                 /* TODO(b/147348711) */
519                 if (false) mCodec.stop();
520                 else mCodec.reset();
521                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
522                 assertTrue(log + "no input sent", 0 != mInputCount);
523                 assertTrue(log + "output received", 0 != mOutputCount);
524                 assertTrue(log + "decoder output is flaky", ref.equals(test));
525                 if (validateFormat) {
526                     assertTrue(log + "not received format change",
527                             mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
528                                     mSignalledOutFormatChanged);
529                     assertTrue(log + "configured format and output format are not similar",
530                             isFormatSimilar(format,
531                                     mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
532                                             mOutFormat));
533                 }
534                 mExtractor.release();
535 
536                 /* test reconfigure codec for new file */
537                 setUpSource(mReconfigFile);
538                 log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
539                         mReconfigFile, (isAsync ? "async" : "sync"));
540                 reConfigureCodec(newFormat, isAsync, false, false);
541                 if (isFormatSimilar(newFormat, defFormat)) {
542                     if (ENABLE_LOGS) {
543                         Log.d("Input format is same as default for format for %s", mCodecName);
544                     }
545                     validateFormat = false;
546                 }
547                 mCodec.start();
548                 test.reset();
549                 mExtractor.seekTo(seekTs, mode);
550                 doWork(Integer.MAX_VALUE);
551                 queueEOS();
552                 waitForAllOutputs();
553                 validateMetrics(mCodecName, newFormat);
554                 /* TODO(b/147348711) */
555                 if (false) mCodec.stop();
556                 else mCodec.reset();
557                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
558                 assertTrue(log + "no input sent", 0 != mInputCount);
559                 assertTrue(log + "output received", 0 != mOutputCount);
560                 assertTrue(log + "decoder output is flaky", configRef.equals(test));
561                 if (validateFormat) {
562                     assertTrue(log + "not received format change",
563                             mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
564                                     mSignalledOutFormatChanged);
565                     assertTrue(log + "configured format and output format are not similar",
566                             isFormatSimilar(newFormat,
567                                     mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
568                                             mOutFormat));
569                 }
570                 mSaveToMem = false;
571                 mExtractor.release();
572             }
573             mCodec.release();
574         }
575     }
576 
577     /**
578      * Tests decoder for only EOS frame
579      */
580     @SmallTest
581     @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
testOnlyEos()582     public void testOnlyEos() throws IOException, InterruptedException {
583         MediaFormat format = setUpSource(mTestFile);
584         boolean[] boolStates = {true, false};
585         OutputManager ref = new OutputManager();
586         OutputManager test = new OutputManager();
587         mSaveToMem = true;
588         {
589             mCodec = MediaCodec.createByCodecName(mCodecName);
590             int loopCounter = 0;
591             for (boolean isAsync : boolStates) {
592                 String log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
593                         mTestFile, (isAsync ? "async" : "sync"));
594                 configureCodec(format, isAsync, false, false);
595                 mOutputBuff = loopCounter == 0 ? ref : test;
596                 mOutputBuff.reset();
597                 mCodec.start();
598                 queueEOS();
599                 waitForAllOutputs();
600                 mCodec.stop();
601                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
602                 if (loopCounter != 0) {
603                     assertTrue(log + "decoder output is flaky", ref.equals(test));
604                 } else {
605                     if (mIsAudio) {
606                         assertTrue(log + " pts is not strictly increasing",
607                                 ref.isPtsStrictlyIncreasing(mPrevOutputPts));
608                     } else {
609                         assertTrue(
610                                 log + " input pts list and output pts list are not identical",
611                                 ref.isOutPtsListIdenticalToInpPtsList(false));
612                     }
613                 }
614                 loopCounter++;
615             }
616             mCodec.release();
617         }
618         mExtractor.release();
619     }
620 
nativeTestOnlyEos(String decoder, String mime, String testFile)621     private native boolean nativeTestOnlyEos(String decoder, String mime, String testFile);
622 
623     @SmallTest
624     @Test
testOnlyEosNative()625     public void testOnlyEosNative() {
626         {
627             assertTrue(nativeTestOnlyEos(mCodecName, mMime, mInpPrefix + mTestFile));
628         }
629     }
630 
631     /**
632      * Test Decoder by Queuing CSD separately
633      */
634     @LargeTest
635     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleDecodeQueueCSD()636     public void testSimpleDecodeQueueCSD() throws IOException, InterruptedException {
637         MediaFormat format = setUpSource(mTestFile);
638         if (!hasCSD(format)) {
639             mExtractor.release();
640             return;
641         }
642         ArrayList<MediaFormat> formats = new ArrayList<>();
643         formats.add(format);
644         formats.add(new MediaFormat(format));
645         for (int i = 0; ; i++) {
646             String csdKey = "csd-" + i;
647             if (format.containsKey(csdKey)) {
648                 mCsdBuffers.add(format.getByteBuffer(csdKey).duplicate());
649                 format.removeKey(csdKey);
650             } else break;
651         }
652         boolean[] boolStates = {true, false};
653         mSaveToMem = true;
654         OutputManager ref = new OutputManager();
655         OutputManager test = new OutputManager();
656         {
657             mCodec = MediaCodec.createByCodecName(mCodecName);
658             int loopCounter = 0;
659             for (int i = 0; i < formats.size(); i++) {
660                 MediaFormat fmt = formats.get(i);
661                 for (boolean eosMode : boolStates) {
662                     for (boolean isAsync : boolStates) {
663                         boolean validateFormat = true;
664                         String log = String.format("codec: %s, file: %s, mode: %s, eos type: %s:: ",
665                                 mCodecName, mTestFile, (isAsync ? "async" : "sync"),
666                                 (eosMode ? "eos with last frame" : "eos separate"));
667                         mOutputBuff = loopCounter == 0 ? ref : test;
668                         mOutputBuff.reset();
669                         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
670                         configureCodec(fmt, isAsync, eosMode, false);
671                         MediaFormat defFormat = mCodec.getOutputFormat();
672                         if (isFormatSimilar(defFormat, format)) {
673                             if (ENABLE_LOGS) {
674                                 Log.d("Input format is same as default for format for %s",
675                                         mCodecName);
676                             }
677                             validateFormat = false;
678                         }
679                         mCodec.start();
680                         if (i == 0) queueCodecConfig();
681                         doWork(Integer.MAX_VALUE);
682                         queueEOS();
683                         waitForAllOutputs();
684                         validateMetrics(mCodecName);
685                         /* TODO(b/147348711) */
686                         if (false) mCodec.stop();
687                         else mCodec.reset();
688                         assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
689                         assertTrue(log + "no input sent", 0 != mInputCount);
690                         assertTrue(log + "output received", 0 != mOutputCount);
691                         if (loopCounter != 0) {
692                             assertTrue(log + "decoder output is flaky", ref.equals(test));
693                         } else {
694                             if (mIsAudio) {
695                                 assertTrue(log + " pts is not strictly increasing",
696                                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
697                             } else {
698                                 assertTrue(
699                                         log + " input pts list and output pts list are not identical",
700                                         ref.isOutPtsListIdenticalToInpPtsList(false));
701                             }
702                         }
703                         if (validateFormat) {
704                             assertTrue(log + "not received format change",
705                                     mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
706                                             mSignalledOutFormatChanged);
707                             assertTrue(log + "configured format and output format are not similar",
708                                     isFormatSimilar(format,
709                                             mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
710                                                     mOutFormat));
711                         }
712                         loopCounter++;
713                     }
714                 }
715             }
716             mCodec.release();
717         }
718         mExtractor.release();
719     }
720 
nativeTestSimpleDecodeQueueCSD(String decoder, String mime, String testFile)721     private native boolean nativeTestSimpleDecodeQueueCSD(String decoder, String mime,
722             String testFile);
723 
724     @LargeTest
725     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleDecodeQueueCSDNative()726     public void testSimpleDecodeQueueCSDNative() throws IOException {
727         MediaFormat format = setUpSource(mTestFile);
728         if (!hasCSD(format)) {
729             mExtractor.release();
730             return;
731         }
732         {
733             assertTrue(nativeTestSimpleDecodeQueueCSD(mCodecName, mMime, mInpPrefix + mTestFile));
734         }
735         mExtractor.release();
736     }
737 
738     /**
739      * Test decoder for partial frame
740      */
741     @LargeTest
742     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testDecodePartialFrame()743     public void testDecodePartialFrame() throws IOException, InterruptedException {
744         Assume.assumeTrue(isFeatureSupported(mCodecName, mMime,
745                 MediaCodecInfo.CodecCapabilities.FEATURE_PartialFrame));
746         MediaFormat format = setUpSource(mTestFile);
747         boolean[] boolStates = {true, false};
748         int frameLimit = 10;
749         ByteBuffer buffer = ByteBuffer.allocate(4 * 1024 * 1024);
750         OutputManager test = new OutputManager();
751         {
752             decodeToMemory(mTestFile, mCodecName, 0, MediaExtractor.SEEK_TO_CLOSEST_SYNC,
753                     frameLimit);
754             mCodec = MediaCodec.createByCodecName(mCodecName);
755             OutputManager ref = mOutputBuff;
756             if (mIsAudio) {
757                 assertTrue("reference output pts is not strictly increasing",
758                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
759             } else {
760                 assertTrue("input pts list and output pts list are not identical",
761                         ref.isOutPtsListIdenticalToInpPtsList(false));
762             }
763             mSaveToMem = true;
764             mOutputBuff = test;
765             for (boolean isAsync : boolStates) {
766                 String log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
767                         mTestFile, (isAsync ? "async" : "sync"));
768                 mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
769                 test.reset();
770                 configureCodec(format, isAsync, true, false);
771                 mCodec.start();
772                 doWork(frameLimit - 1);
773                 ArrayList<MediaCodec.BufferInfo> list = createSubFrames(buffer, 4);
774                 assertTrue("no sub frames in list received for " + mTestFile,
775                         list != null && list.size() > 0);
776                 doWork(buffer, list);
777                 queueEOS();
778                 waitForAllOutputs();
779                 /* TODO(b/147348711) */
780                 if (false) mCodec.stop();
781                 else mCodec.reset();
782                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
783                 assertTrue(log + "no input sent", 0 != mInputCount);
784                 assertTrue(log + "output received", 0 != mOutputCount);
785                 assertTrue(log + "decoder output is not consistent with ref", ref.equals(test));
786             }
787             mCodec.release();
788         }
789         mExtractor.release();
790     }
791 }
792