DecoderTest: add testLowLatencyAVC and testLowLatencyHEVC

Test: these tests
Bug: 134971304
Change-Id: Ia38833dcf49de31737bbdfe01df1daf2f302298b
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 699df64..041b112 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -40,6 +40,7 @@
 
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaPerfUtils;
 import com.android.compatibility.common.util.MediaUtils;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
@@ -56,6 +57,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.LongStream;
 
 import static android.media.MediaCodecInfo.CodecProfileLevel.*;
 
@@ -63,6 +65,7 @@
 @AppModeFull(reason = "There should be no instant apps specific behavior related to decoders")
 public class DecoderTest extends MediaPlayerTestBase {
     private static final String TAG = "DecoderTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
 
     private static final int RESET_MODE_NONE = 0;
     private static final int RESET_MODE_RECONFIGURE = 1;
@@ -3336,4 +3339,135 @@
         PackageManager pm = mContext.getPackageManager();
         return pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
     }
+
+    public void testLowLatencyAVC() throws Exception {
+        testLowLatencyVideo(
+                R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 300);
+    }
+
+    public void testLowLatencyHEVC() throws Exception {
+        testLowLatencyVideo(
+                R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz, 300);
+    }
+
+    private void testLowLatencyVideo(int testVideo, int frameCount) throws Exception {
+        AssetFileDescriptor fd = mResources.openRawResourceFd(testVideo);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        fd.close();
+
+        MediaFormat format = null;
+        int trackIndex = -1;
+        for (int i = 0; i < extractor.getTrackCount(); i++) {
+            format = extractor.getTrackFormat(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                trackIndex = i;
+                break;
+            }
+        }
+
+        assertTrue("No video track was found", trackIndex >= 0);
+
+        extractor.selectTrack(trackIndex);
+        format.setFeatureEnabled(MediaFormat.KEY_LOW_LATENCY, true /* enable */);
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        String decoderName = mcl.findDecoderForFormat(format);
+        if (decoderName == null) {
+            MediaUtils.skipTest("no low latency decoder for " + format);
+            return;
+        }
+        Log.v(TAG, "found decoder " + decoderName + " for format: " + format);
+
+        Surface surface = getActivity().getSurfaceHolder().getSurface();
+        MediaCodec decoder = MediaCodec.createByCodecName(decoderName);
+        decoder.configure(format, surface, null /* crypto */, 0 /* flags */);
+        decoder.start();
+
+        ByteBuffer[] codecInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
+        MediaFormat decoderInputFormat = decoder.getInputFormat();
+        MediaFormat decoderOutputFormat = null;
+
+        // start decoding
+        final long kTimeOutUs = 1000000;  // 1 second
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int bufferCounter = 0;
+        long[] latencyMs = new long[frameCount];
+        boolean waitingForOutput = false;
+        long startTimeMs = System.currentTimeMillis();
+        while (bufferCounter < frameCount) {
+            if (!waitingForOutput) {
+                int inputBufferId = decoder.dequeueInputBuffer(kTimeOutUs);
+                if (inputBufferId < 0) {
+                    Log.v(TAG, "no input buffer");
+                    break;
+                }
+
+                ByteBuffer dstBuf = codecInputBuffers[inputBufferId];
+
+                int sampleSize = extractor.readSampleData(dstBuf, 0 /* offset */);
+                long presentationTimeUs = 0;
+                if (sampleSize < 0) {
+                    Log.v(TAG, "had input EOS, early termination at frame " + bufferCounter);
+                    break;
+                } else {
+                    presentationTimeUs = extractor.getSampleTime();
+                }
+
+                startTimeMs = System.currentTimeMillis();
+                decoder.queueInputBuffer(
+                        inputBufferId,
+                        0 /* offset */,
+                        sampleSize,
+                        presentationTimeUs,
+                        0 /* flags */);
+
+                extractor.advance();
+                waitingForOutput = true;
+            }
+
+            int outputBufferId = decoder.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (outputBufferId >= 0) {
+                waitingForOutput = false;
+                //Log.d(TAG, "got output, size " + info.size + ", time " + info.presentationTimeUs);
+                latencyMs[bufferCounter++] = System.currentTimeMillis() - startTimeMs;
+                // TODO: render the frame and find the rendering time to calculate the total delay
+                decoder.releaseOutputBuffer(outputBufferId, false /* render */);
+            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = decoder.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                decoderOutputFormat = decoder.getOutputFormat();
+                Log.d(TAG, "output format has changed to " + decoderOutputFormat);
+            } else {
+                fail("No output buffer returned without frame delay, status " + outputBufferId);
+            }
+        }
+
+        assertTrue("No INFO_OUTPUT_FORMAT_CHANGED from decoder", decoderOutputFormat != null);
+
+        LongStream longStream = Arrays.stream(latencyMs);
+        long latencyMean = (long)longStream.average().getAsDouble();
+        long latencyMax = longStream.max().getAsLong();
+        Log.d(TAG, "latency average " + latencyMean + " ms, max " + latencyMax + " ms");
+
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, "video_decoder_latency");
+        String message = MediaPerfUtils.addPerformanceHeadersToLog(
+                log, "decoder stats: decodeTo=" + ((surface == null) ? "buffer" : "surface"),
+                0 /* round */, decoderName, format, decoderInputFormat, decoderOutputFormat);
+        log.addValue("video_res", testVideo, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("decode_to", surface == null ? "buffer" : "surface",
+                ResultType.NEUTRAL, ResultUnit.NONE);
+
+        log.addValue("average_latency", latencyMean, ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.addValue("max_latency", latencyMax, ResultType.LOWER_BETTER, ResultUnit.MS);
+
+        log.submit(getInstrumentation());
+
+        decoder.stop();
+        decoder.release();
+        extractor.release();
+    }
 }