Camera2: make camera recording expected duration more accurate

Session close takes a couple of hundreds ms, as it reconfigures a stream,
which makes the expected recording duration longer than it should be. This
could cause random failure for some devices. This change uses the number of
frames produced by camera during recording to calculate the expected
recording duration, which is more objective than the existing estimate.

Bug: 18141821
Change-Id: Ia6806c6a7155a7b6014537caee0cb7724f9b2dba
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index 0c36832..303099a 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -63,6 +63,7 @@
 import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * A package private utility class for wrapping up the camera2 cts test common utility functions
@@ -199,6 +200,7 @@
     public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback {
         private final LinkedBlockingQueue<CaptureResult> mQueue =
                 new LinkedBlockingQueue<CaptureResult>();
+        private AtomicLong mNumFramesArrived = new AtomicLong(0);
 
         @Override
         public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
@@ -210,6 +212,7 @@
         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                 TotalCaptureResult result) {
             try {
+                mNumFramesArrived.incrementAndGet();
                 mQueue.put(result);
             } catch (InterruptedException e) {
                 throw new UnsupportedOperationException(
@@ -227,6 +230,10 @@
                 long frameNumber) {
         }
 
+        public long getTotalNumFrames() {
+            return mNumFramesArrived.get();
+        }
+
         public CaptureResult getCaptureResult(long timeout) {
             try {
                 CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
index 669de2d..400be6f 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -287,8 +287,9 @@
                     updatePreviewSurfaceWithVideoSize(size);
 
                     // Start recording
+                    SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
                     startSlowMotionRecording(/*useMediaRecorder*/true, videoFramerate, captureRate,
-                            fpsRange);
+                            fpsRange, resultListener);
                     long startTime = SystemClock.elapsedRealtime();
 
                     // Record certain duration.
@@ -296,10 +297,12 @@
 
                     // Stop recording and preview
                     stopRecording(/*useMediaRecorder*/true);
-                    int duration = (int) (SystemClock.elapsedRealtime() - startTime);
+                    // Convert number of frames camera produced into the duration in unit of ms.
+                    int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+                                    videoFramerate);
 
                     // Validation.
-                    validateRecording(size, duration * SLOWMO_SLOW_FACTOR);
+                    validateRecording(size, durationMs);
 
                 }
 
@@ -329,7 +332,8 @@
     }
 
     private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate,
-            int captureRate, Range<Integer> fpsRange) throws Exception {
+            int captureRate, Range<Integer> fpsRange,
+            CameraCaptureSession.CaptureCallback listener) throws Exception {
         List<Surface> outputSurfaces = new ArrayList<Surface>(2);
         assertTrue("Both preview and recording surfaces should be valid",
                 mPreviewSurface.isValid() && mRecordingSurface.isValid());
@@ -370,7 +374,7 @@
         for (int i = 0; i < slowMotionFactor - 1; i++) {
             slowMoRequests.add(recordingOnlyBuilder.build()); // Recording only.
         }
-        mSession.setRepeatingBurst(slowMoRequests, null, null);
+        mSession.setRepeatingBurst(slowMoRequests, listener, mHandler);
 
         if (useMediaRecorder) {
             mMediaRecorder.start();
@@ -415,18 +419,25 @@
             updatePreviewSurfaceWithVideoSize(videoSz);
 
             // Start recording
-            startRecording(/* useMediaRecorder */true);
-            long startTime = SystemClock.elapsedRealtime();
+            SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+            startRecording(/* useMediaRecorder */true, resultListener);
 
             // Record certain duration.
             SystemClock.sleep(RECORDING_DURATION_MS);
 
             // Stop recording and preview
             stopRecording(/* useMediaRecorder */true);
-            int duration = (int) (SystemClock.elapsedRealtime() - startTime);
+            // Convert number of frames camera produced into the duration in unit of ms.
+            int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+                            profile.videoFrameRate);
+
+            if (VERBOSE) {
+                Log.v(TAG, "video frame rate: " + profile.videoFrameRate +
+                                ", num of frames produced: " + resultListener.getTotalNumFrames());
+            }
 
             // Validation.
-            validateRecording(videoSz, duration);
+            validateRecording(videoSz, durationMs);
         }
     }
 
@@ -458,18 +469,20 @@
             updatePreviewSurfaceWithVideoSize(sz);
 
             // Start recording
-            startRecording(/* useMediaRecorder */true);
-            long startTime = SystemClock.elapsedRealtime();
+            SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+            startRecording(/* useMediaRecorder */true, resultListener);
 
             // Record certain duration.
             SystemClock.sleep(RECORDING_DURATION_MS);
 
             // Stop recording and preview
             stopRecording(/* useMediaRecorder */true);
-            int duration = (int) (SystemClock.elapsedRealtime() - startTime);
+            // Convert number of frames camera produced into the duration in unit of ms.
+            int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+                            VIDEO_FRAME_RATE);
 
             // Validation.
-            validateRecording(sz, duration);
+            validateRecording(sz, durationMs);
         }
     }
 
@@ -861,7 +874,8 @@
             // TODO: Don't skip this for video snapshot
             if (!mStaticInfo.isHardwareLevelLegacy()) {
                 assertTrue(String.format(
-                        "Video duration doesn't match: recorded %dms, expected %dms", duration,
+                        "Camera %s: Video duration doesn't match: recorded %dms, expected %dms",
+                        mCamera.getId(), duration,
                         durationMs), Math.abs(duration - durationMs) < DURATION_MARGIN_MS);
             }
         } finally {