1 /*
2  * Copyright (C) 2013 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.media.codec.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 
21 import android.media.MediaCodec;
22 import android.media.MediaCodecList;
23 import android.media.MediaFormat;
24 import android.media.cts.MediaHeavyPresubmitTest;
25 import android.media.cts.TestArgs;
26 import android.platform.test.annotations.AppModeFull;
27 import android.util.Log;
28 
29 import com.android.compatibility.common.util.ApiTest;
30 import com.android.compatibility.common.util.MediaUtils;
31 
32 import org.junit.Assume;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.junit.runners.Parameterized;
36 
37 import java.io.File;
38 import java.nio.ByteBuffer;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.List;
43 
44 /**
45  * Verification test for video encoder and decoder.
46  *
47  * A raw yv12 stream is encoded at various settings and written to an IVF
48  * file. Encoded stream bitrate and key frame interval are checked against target values.
49  * The stream is later decoded by video decoder to verify frames are decodable and to
50  * calculate PSNR values for various bitrates.
51  */
52 @MediaHeavyPresubmitTest
53 @AppModeFull(reason = "TODO: evaluate and port to instant")
54 @RunWith(Parameterized.class)
55 public class VideoCodecTest extends VideoCodecTestBase {
56 
57     private static final String ENCODED_IVF_BASE = "football";
58     private static final String INPUT_YUV = null;
59     private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
60             ENCODED_IVF_BASE + "_out.yuv";
61 
62     // YUV stream properties.
63     private static final int WIDTH = 320;
64     private static final int HEIGHT = 240;
65     private static final int FPS = 30;
66     // Default encoding bitrate.
67     private static final int BITRATE = 400000;
68     // List of bitrates used in quality and basic bitrate tests.
69     private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
70     // Maximum allowed bitrate variation from the target value.
71     // Keep in sync with the variation at libmediandkjni/native_media_utils.h
72     // used in some tests along with BITRATE
73     private static final double MAX_BITRATE_VARIATION = 0.2;
74     // The tolerance varies by the bitrate, because lower bitrates interact with
75     // video quality standards introduced in Android 12.
76     private static final double[] MAX_CBR_BITRATE_VARIATIONS = { 0.20, 0.20, 0.20, 0.20 };
77     private static final double[] MAX_VBR_BITRATE_VARIATIONS = { 0.50, 0.30, 0.30, 0.30 };
78     // Average PSNR values for reference Google Video codec for the above bitrates.
79     private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
80     // Minimum PSNR values for reference Google Video codec for the above bitrates.
81     private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
82     // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
83     private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
84     // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
85     private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
86     // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
87     // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
88     // buffer dequeue timeout.
89     private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 1.5;
90     // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
91     // comparing to the encoder running in a callee's thread.
92     private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
93     // Maximum allowed average key frame interval variation from the target value.
94     private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
95     // Maximum allowed key frame interval variation from the target value.
96     private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
97 
98     @Parameterized.Parameter(0)
99     public String mCodecName;
100 
101     @Parameterized.Parameter(1)
102     public String mCodecMimeType;
103 
104     @Parameterized.Parameter(2)
105     public int mBitRateMode;
106 
prepareParamList(List<Object[]> exhaustiveArgsList)107     static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
108         final List<Object[]> argsList = new ArrayList<>();
109         int argLength = exhaustiveArgsList.get(0).length;
110         for (Object[] arg : exhaustiveArgsList) {
111             String mediaType = (String)arg[0];
112             if (TestArgs.shouldSkipMediaType(mediaType)) {
113                 continue;
114             }
115             String[] encodersForMime = MediaUtils.getEncoderNamesForMime(mediaType);
116             for (String encoder : encodersForMime) {
117                 if (TestArgs.shouldSkipCodec(encoder)) {
118                     continue;
119                 }
120                 Object[] testArgs = new Object[argLength + 1];
121                 testArgs[0] = encoder;
122                 System.arraycopy(arg, 0, testArgs, 1, argLength);
123                 argsList.add(testArgs);
124             }
125         }
126         return argsList;
127     }
128 
129     @Parameterized.Parameters(name = "{index}_{0}_{1}_{2}")
input()130     public static Collection<Object[]> input() {
131         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
132                 {VP8_MIME, VIDEO_ControlRateConstant},
133                 {VP8_MIME, VIDEO_ControlRateVariable},
134                 {VP9_MIME, VIDEO_ControlRateConstant},
135                 {VP9_MIME, VIDEO_ControlRateVariable},
136                 {AVC_MIME, VIDEO_ControlRateConstant},
137                 {AVC_MIME, VIDEO_ControlRateVariable},
138                 {HEVC_MIME, VIDEO_ControlRateConstant},
139                 {HEVC_MIME, VIDEO_ControlRateVariable},
140                 {AV1_MIME, VIDEO_ControlRateConstant},
141                 {AV1_MIME, VIDEO_ControlRateVariable},
142         });
143         return prepareParamList(exhaustiveArgsList);
144     }
145 
146     /**
147      * A basic test for Video encoder.
148      *
149      * Encodes 9 seconds of raw stream with default configuration options,
150      * and then decodes it to verify the bitstream.
151      * Verifies the average bitrate is within allowed MAX_BITRATE_VARIATIONS[] of
152      * the target value.
153      */
internalTestBasic(String codecName, String codecMimeType, int bitRateMode)154     private void internalTestBasic(String codecName, String codecMimeType, int bitRateMode)
155             throws Exception {
156         int encodeSeconds = 9;
157         boolean skipped = true;
158 
159         for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
160             int targetBitrate = TEST_BITRATES_SET[i];
161 
162             EncoderOutputStreamParameters params = getDefaultEncodingParameters(
163                     INPUT_YUV,
164                     ENCODED_IVF_BASE,
165                     codecName,
166                     codecMimeType,
167                     encodeSeconds,
168                     WIDTH,
169                     HEIGHT,
170                     FPS,
171                     bitRateMode,
172                     targetBitrate,
173                     true);
174             ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
175             VideoEncodeOutput videoEncodeOutput = encode(params, codecConfigs);
176             ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
177             if (bufInfo == null) {
178                 continue;
179             }
180             skipped = false;
181 
182             VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
183 
184             if (params.bitrateType == VIDEO_ControlRateConstant) {
185                 /* Constant bitrate -- variation applies to both over/under */
186                 double allowedVariance = MAX_CBR_BITRATE_VARIATIONS[i];
187                 assertEquals("Stream bitrate " + statistics.mAverageBitrate +
188                     " differs from the target " + targetBitrate
189                     + " by more than " + allowedVariance * targetBitrate,
190                     targetBitrate, statistics.mAverageBitrate,
191                     allowedVariance * targetBitrate);
192             } else if (params.bitrateType == VIDEO_ControlRateVariable
193                             && statistics.mAverageBitrate > targetBitrate) {
194                 /* VIDEO_ControlRateVariable mode only checks over-run */
195                 double allowedVariance = MAX_VBR_BITRATE_VARIATIONS[i];
196                 assertEquals("Stream bitrate " + statistics.mAverageBitrate
197                     + " above target " + targetBitrate
198                     + " by more than " + allowedVariance * targetBitrate,
199                     targetBitrate, statistics.mAverageBitrate,
200                     allowedVariance * targetBitrate);
201             }
202 
203             decode(params.outputIvfFilename, null, codecMimeType, FPS, codecConfigs);
204         }
205 
206         if (skipped) {
207             Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
208         }
209     }
210 
211     /**
212      * Asynchronous encoding test for Video encoder.
213      *
214      * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
215      * Checks the PSNR difference between the encoded and decoded output and reference yuv input
216      * does not change much for two different ways of the encoder call.
217      */
internalTestAsyncEncoding(String codecName, String codecMimeType, int bitRateMode)218     private void internalTestAsyncEncoding(String codecName, String codecMimeType, int bitRateMode)
219             throws Exception {
220         int encodeSeconds = 9;
221 
222         // First test the encoder running in a looper thread with buffer callbacks enabled.
223         boolean syncEncoding = false;
224         EncoderOutputStreamParameters params = getDefaultEncodingParameters(
225                 INPUT_YUV,
226                 ENCODED_IVF_BASE,
227                 codecName,
228                 codecMimeType,
229                 encodeSeconds,
230                 WIDTH,
231                 HEIGHT,
232                 FPS,
233                 bitRateMode,
234                 BITRATE,
235                 syncEncoding);
236         ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
237         VideoEncodeOutput videoEncodeOutput = encodeAsync(params, codecConfigs);
238         ArrayList<MediaCodec.BufferInfo> bufInfos = videoEncodeOutput.bufferInfo;
239         if (bufInfos == null) {
240             Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
241             return;
242         }
243         computeEncodingStatistics(bufInfos);
244         decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
245         VideoDecodingStatistics statisticsAsync = computeDecodingStatistics(
246                 params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
247                 params.frameWidth, params.frameHeight);
248 
249 
250         // Test the encoder running in a callee's thread.
251         syncEncoding = true;
252         params = getDefaultEncodingParameters(
253                 INPUT_YUV,
254                 ENCODED_IVF_BASE,
255                 codecName,
256                 codecMimeType,
257                 encodeSeconds,
258                 WIDTH,
259                 HEIGHT,
260                 FPS,
261                 bitRateMode,
262                 BITRATE,
263                 syncEncoding);
264         codecConfigs.clear();
265         videoEncodeOutput = encode(params, codecConfigs);
266         bufInfos = videoEncodeOutput.bufferInfo;
267         if (bufInfos == null) {
268             Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
269             return;
270         }
271         computeEncodingStatistics(bufInfos);
272         decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
273         VideoDecodingStatistics statisticsSync = computeDecodingStatistics(
274                 params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
275                 params.frameWidth, params.frameHeight);
276 
277         // Check PSNR difference.
278         Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
279                 ". Sync: " + statisticsSync.mAveragePSNR);
280         Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
281                 ". Sync: " + statisticsSync.mMinimumPSNR);
282         if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
283             MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
284             (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
285             MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
286             throw new RuntimeException("Difference between PSNRs for async and sync encoders");
287         }
288     }
289 
290     /**
291      * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
292      *
293      * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
294      * The test does not verify the output stream.
295      */
internalTestSyncFrame(String codecName, String codecMimeType, int bitRateMode, boolean useNdk)296     private void internalTestSyncFrame(String codecName, String codecMimeType, int bitRateMode,
297             boolean useNdk) throws Exception {
298         int encodeSeconds = 9;
299 
300         EncoderOutputStreamParameters params = getDefaultEncodingParameters(
301                 INPUT_YUV,
302                 ENCODED_IVF_BASE,
303                 codecName,
304                 codecMimeType,
305                 encodeSeconds,
306                 WIDTH,
307                 HEIGHT,
308                 FPS,
309                 bitRateMode,
310                 BITRATE,
311                 true);
312         params.syncFrameInterval = encodeSeconds * FPS;
313         params.syncForceFrameInterval = FPS;
314         params.useNdk = useNdk;
315         VideoEncodeOutput videoEncodeOutput = encode(params);
316         ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
317         if (bufInfo == null) {
318             Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
319             return;
320         }
321 
322         VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
323 
324         // First check if we got expected number of key frames.
325         int actualKeyFrames = statistics.mKeyFrames.size();
326         if (actualKeyFrames != encodeSeconds) {
327             throw new RuntimeException("Number of key frames " + actualKeyFrames +
328                     " is different from the expected " + encodeSeconds);
329         }
330 
331         // Check key frame intervals:
332         // Average value should be within +/- 1 frame of the target value,
333         // maximum value should not be greater than target value + 3,
334         // and minimum value should not be less that target value - 3.
335         if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
336             MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
337             (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
338             (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
339             throw new RuntimeException(
340                     "Key frame intervals are different from the expected " + FPS);
341         }
342     }
343 
344     /**
345      * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
346      *
347      * Run the the encoder for 12 seconds. Request changes to the
348      * bitrate after 6 seconds and ensure the encoder responds.
349      */
internalTestDynamicBitrateChange(String codecName, String codecMimeType, int bitRateMode, boolean useNdk)350     private void internalTestDynamicBitrateChange(String codecName, String codecMimeType,
351             int bitRateMode, boolean useNdk) throws Exception {
352         int encodeSeconds = 12;    // Encoding sequence duration in seconds.
353         int[] bitrateTargetValues = { 400000, 800000 };  // List of bitrates to test.
354 
355         EncoderOutputStreamParameters params = getDefaultEncodingParameters(
356                 INPUT_YUV,
357                 ENCODED_IVF_BASE,
358                 codecName,
359                 codecMimeType,
360                 encodeSeconds,
361                 WIDTH,
362                 HEIGHT,
363                 FPS,
364                 bitRateMode,
365                 bitrateTargetValues[0],
366                 true);
367 
368         // Number of seconds for each bitrate
369         int stepSeconds = encodeSeconds / bitrateTargetValues.length;
370         // Fill the bitrates values.
371         params.bitrateSet = new int[encodeSeconds * FPS];
372         for (int i = 0; i < bitrateTargetValues.length ; i++) {
373             Arrays.fill(params.bitrateSet,
374                     i * encodeSeconds * FPS / bitrateTargetValues.length,
375                     (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
376                     bitrateTargetValues[i]);
377         }
378 
379         params.useNdk = useNdk;
380         VideoEncodeOutput videoEncodeOutput = encode(params);
381         ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
382         if (bufInfo == null) {
383             Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
384             return;
385         }
386 
387         VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
388 
389         // Calculate actual average bitrates  for every [stepSeconds] second.
390         int[] bitrateActualValues = new int[bitrateTargetValues.length];
391         for (int i = 0; i < bitrateTargetValues.length ; i++) {
392             bitrateActualValues[i] = 0;
393             for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
394                 bitrateActualValues[i] += statistics.mBitrates.get(j);
395             }
396             bitrateActualValues[i] /= stepSeconds;
397             Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
398                     ". Target: " + bitrateTargetValues[i]);
399 
400             // Compare actual bitrate values to make sure at least same increasing/decreasing
401             // order as the target bitrate values.
402             for (int j = 0; j < i; j++) {
403                 long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
404                 long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
405                 if (differenceTarget * differenceActual < 0) {
406                     throw new RuntimeException("Target bitrates: " +
407                             bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
408                             ". Actual bitrates: "
409                             + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
410                 }
411             }
412         }
413     }
414 
415      /**
416       * Check if encoder and decoder can run simultaneously on different threads.
417       *
418       * Encodes and decodes 9 seconds of raw stream sequentially in CBR mode,
419       * and then run parallel encoding and decoding of the same streams.
420       * Compares average bitrate and PSNR for sequential and parallel runs.
421       */
internalTestParallelEncodingAndDecoding(String codecName, String codecMimeType)422      private void internalTestParallelEncodingAndDecoding(String codecName, String codecMimeType)
423              throws Exception {
424          // check for encoder up front, as by the time we detect lack of
425          // encoder support, we may have already started decoding.
426          MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
427          MediaFormat format = MediaFormat.createVideoFormat(codecMimeType, WIDTH, HEIGHT);
428          if (mcl.findEncoderForFormat(format) == null) {
429              Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
430              return;
431          }
432 
433          int encodeSeconds = 9;
434          final int[] bitrate = new int[1];
435          final double[] psnr = new double[1];
436          final Exception[] exceptionEncoder = new Exception[1];
437          final Exception[] exceptionDecoder = new Exception[1];
438          final EncoderOutputStreamParameters params = getDefaultEncodingParameters(
439                  INPUT_YUV,
440                  ENCODED_IVF_BASE,
441                  codecName,
442                  codecMimeType,
443                  encodeSeconds,
444                  WIDTH,
445                  HEIGHT,
446                  FPS,
447                  VIDEO_ControlRateConstant,
448                  BITRATE,
449                  true);
450          final String inputIvfFilename = params.outputIvfFilename;
451          final ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
452 
453          Runnable runEncoder = new Runnable() {
454              public void run() {
455                  try {
456                      ArrayList<MediaCodec.BufferInfo> bufInfo;
457                      if (codecConfigs.isEmpty()) {
458                          VideoEncodeOutput videoEncodeOutput = encode(params, codecConfigs);
459                          bufInfo = videoEncodeOutput.bufferInfo;
460                      } else {
461                          VideoEncodeOutput videoEncodeOutput = encode(params);
462                          bufInfo = videoEncodeOutput.bufferInfo;
463                      }
464                      VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
465                      bitrate[0] = statistics.mAverageBitrate;
466                  } catch (Exception e) {
467                      Log.e(TAG, "Encoder error: " + e.toString());
468                      exceptionEncoder[0] = e;
469                  }
470              }
471          };
472          Runnable runDecoder = new Runnable() {
473              public void run() {
474                  try {
475                      decode(inputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
476                      VideoDecodingStatistics statistics = computeDecodingStatistics(
477                             params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
478                             params.frameWidth, params.frameHeight);
479                      psnr[0] = statistics.mAveragePSNR;
480                  } catch (Exception e) {
481                      Log.e(TAG, "Decoder error: " + e.toString());
482                      exceptionDecoder[0] = e;
483                  }
484              }
485          };
486 
487          // Sequential encoding and decoding.
488          runEncoder.run();
489          if (exceptionEncoder[0] != null) {
490              throw exceptionEncoder[0];
491          }
492          int referenceBitrate = bitrate[0];
493          runDecoder.run();
494          if (exceptionDecoder[0] != null) {
495              throw exceptionDecoder[0];
496          }
497          double referencePsnr = psnr[0];
498 
499          // Parallel encoding and decoding.
500          params.outputIvfFilename = SDCARD_DIR + File.separator + ENCODED_IVF_BASE + "_copy.ivf";
501          Thread threadEncoder = new Thread(runEncoder);
502          Thread threadDecoder = new Thread(runDecoder);
503          threadEncoder.start();
504          threadDecoder.start();
505          threadEncoder.join();
506          threadDecoder.join();
507          if (exceptionEncoder[0] != null) {
508              throw exceptionEncoder[0];
509          }
510          if (exceptionDecoder[0] != null) {
511              throw exceptionDecoder[0];
512          }
513 
514          // Compare bitrates and PSNRs for sequential and parallel cases.
515          Log.d(TAG, "Sequential bitrate: " + referenceBitrate + ". PSNR: " + referencePsnr);
516          Log.d(TAG, "Parallel bitrate: " + bitrate[0] + ". PSNR: " + psnr[0]);
517          assertEquals("Bitrate for sequenatial encoding" + referenceBitrate +
518                  " is different from parallel encoding " + bitrate[0],
519                  referenceBitrate, bitrate[0], MAX_BITRATE_VARIATION * referenceBitrate);
520          assertEquals("PSNR for sequenatial encoding" + referencePsnr +
521                  " is different from parallel encoding " + psnr[0],
522                  referencePsnr, psnr[0], MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE);
523      }
524 
525 
526     /**
527      * Check the encoder quality for various bitrates by calculating PSNR
528      *
529      * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
530      * for each encoded stream.
531      * Video streams with higher bitrates should have higher PSNRs.
532      * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
533      */
internalTestEncoderQuality(String codecName, String codecMimeType, int bitRateMode)534     private void internalTestEncoderQuality(String codecName, String codecMimeType, int bitRateMode)
535             throws Exception {
536         int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
537         double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
538         double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
539         boolean[] completed = new boolean[TEST_BITRATES_SET.length];
540         boolean skipped = true;
541 
542         // Run platform specific encoder for different bitrates
543         // and compare PSNR of codec with PSNR of reference Google codec.
544         for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
545             EncoderOutputStreamParameters params = getDefaultEncodingParameters(
546                     INPUT_YUV,
547                     ENCODED_IVF_BASE,
548                     codecName,
549                     codecMimeType,
550                     encodeSeconds,
551                     WIDTH,
552                     HEIGHT,
553                     FPS,
554                     bitRateMode,
555                     TEST_BITRATES_SET[i],
556                     true);
557             ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
558             if (encode(params, codecConfigs) == null) {
559                 // parameters not supported, try other bitrates
560                 completed[i] = false;
561                 continue;
562             }
563             completed[i] = true;
564             skipped = false;
565 
566             decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
567             VideoDecodingStatistics statistics = computeDecodingStatistics(
568                     params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
569                     params.frameWidth, params.frameHeight);
570             psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
571             psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
572         }
573 
574         if (skipped) {
575             Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
576             return;
577         }
578 
579         // First do an initial check - higher bitrates should results in higher PSNR.
580         for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
581             if (!completed[i]) {
582                 continue;
583             }
584             for (int j = 0; j < i; j++) {
585                 if (!completed[j]) {
586                     continue;
587                 }
588                 double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
589                 double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
590                 if (differenceBitrate * differencePSNR < 0) {
591                     throw new RuntimeException("Target bitrates: " +
592                             TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
593                             ". Actual PSNRs: "
594                             + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
595                 }
596             }
597         }
598 
599         // Then compare average and minimum PSNR of platform codec with reference Google codec -
600         // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
601         // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
602         // These PSNR difference numbers are arbitrary for now, will need further estimation
603         // when more devices with HW video codec will appear.
604         for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
605             if (!completed[i]) {
606                 continue;
607             }
608 
609             Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
610             Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
611                     REFERENCE_MINIMUM_PSNR[i]);
612             Log.d(TAG, "Platform:  Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
613                     psnrPlatformCodecMin[i]);
614             if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
615                     MAX_AVERAGE_PSNR_DIFFERENCE) {
616                 throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
617                         " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
618                         " for bitrate " + TEST_BITRATES_SET[i]);
619             }
620             if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
621                     MAX_MINIMUM_PSNR_DIFFERENCE) {
622                 throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
623                         " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
624                         " for bitrate " + TEST_BITRATES_SET[i]);
625             }
626         }
627     }
628 
629     @ApiTest(apis = {"android.media.MediaFormat#KEY_BITRATE_MODE",
630             "android.media.MediaFormat#KEY_BIT_RATE",
631             "android.media.MediaFormat#KEY_COLOR_FORMAT",
632             "android.media.MediaFormat#KEY_FRAME_RATE",
633             "android.media.MediaFormat#KEY_I_FRAME_INTERVAL"})
634     @Test
testBasic()635     public void testBasic() throws Exception {
636         internalTestBasic(mCodecName, mCodecMimeType, mBitRateMode);
637     }
638 
639     @ApiTest(apis = {"android.media.MediaFormat#KEY_BITRATE_MODE",
640             "android.media.MediaFormat#KEY_BIT_RATE",
641             "android.media.MediaFormat#KEY_COLOR_FORMAT",
642             "android.media.MediaFormat#KEY_FRAME_RATE",
643             "android.media.MediaFormat#KEY_I_FRAME_INTERVAL"})
644     @Test
testAsyncEncode()645     public void testAsyncEncode() throws Exception {
646         internalTestAsyncEncoding(mCodecName, mCodecMimeType, mBitRateMode);
647     }
648 
649     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_REQUEST_SYNC_FRAME")
650     @Test
testSyncFrame()651     public void testSyncFrame() throws Exception {
652         internalTestSyncFrame(mCodecName, mCodecMimeType, mBitRateMode, false);
653     }
654 
655     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_REQUEST_SYNC_FRAME")
656     @Test
testSyncFrameNdk()657     public void testSyncFrameNdk() throws Exception {
658         internalTestSyncFrame(mCodecName, mCodecMimeType, mBitRateMode, true);
659     }
660 
661     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_VIDEO_BITRATE")
662     @Test
testDynamicBitrateChange()663     public void testDynamicBitrateChange() throws Exception {
664         internalTestDynamicBitrateChange(mCodecName, mCodecMimeType, mBitRateMode, false);
665     }
666 
667     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_VIDEO_BITRATE")
668     @Test
testDynamicBitrateChangeNdk()669     public void testDynamicBitrateChangeNdk() throws Exception {
670         internalTestDynamicBitrateChange(mCodecName, mCodecMimeType, mBitRateMode, true);
671     }
672 
673     @ApiTest(apis = {"android.media.MediaFormat#KEY_BITRATE_MODE",
674             "android.media.MediaFormat#KEY_BIT_RATE",
675             "android.media.MediaFormat#KEY_COLOR_FORMAT",
676             "android.media.MediaFormat#KEY_FRAME_RATE",
677             "android.media.MediaFormat#KEY_I_FRAME_INTERVAL"})
678     @Test
testEncoderQuality()679     public void testEncoderQuality() throws Exception {
680         internalTestEncoderQuality(mCodecName, mCodecMimeType, mBitRateMode);
681     }
682 
683     @ApiTest(apis = {"android.media.MediaFormat#KEY_BITRATE_MODE",
684             "android.media.MediaFormat#KEY_BIT_RATE",
685             "android.media.MediaFormat#KEY_COLOR_FORMAT",
686             "android.media.MediaFormat#KEY_FRAME_RATE",
687             "android.media.MediaFormat#KEY_I_FRAME_INTERVAL"})
688     @Test
testParallelEncodingAndDecoding()689     public void testParallelEncodingAndDecoding() throws Exception {
690         Assume.assumeTrue("Parallel Encode Decode test is run only for VBR mode",
691                 mBitRateMode == VIDEO_ControlRateVariable);
692         internalTestParallelEncodingAndDecoding(mCodecName, mCodecMimeType);
693     }
694 }
695 
696