Camera2: Add reprocess timestamps test
Verify that reprocess request's shutter timestamp, result's
sensor timestamp, and output image's timestamp all match
the reprocess input's timestamp.
Bug: 21112186
Change-Id: I81fa5ecda53b5059ff4ed92cb0b325dceaba52d2
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 f062545..423577e 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -32,7 +32,6 @@
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.cts.helpers.CameraUtils;
-import android.util.Size;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
@@ -41,6 +40,8 @@
import android.media.Image.Plane;
import android.os.Handler;
import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
import android.view.Surface;
import com.android.ex.camera2.blocking.BlockingCameraManager;
@@ -315,13 +316,21 @@
new LinkedBlockingQueue<TotalCaptureResult>();
private final LinkedBlockingQueue<CaptureFailure> mFailureQueue =
new LinkedBlockingQueue<>();
+ // Pair<CaptureRequest, Long> is a pair of capture request and timestamp.
+ private final LinkedBlockingQueue<Pair<CaptureRequest, Long>> mCaptureStartQueue =
+ new LinkedBlockingQueue<>();
private AtomicLong mNumFramesArrived = new AtomicLong(0);
@Override
public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
- long timestamp, long frameNumber)
- {
+ long timestamp, long frameNumber) {
+ try {
+ mCaptureStartQueue.put(new Pair(request, timestamp));
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException(
+ "Can't handle InterruptedException in onCaptureStarted");
+ }
}
@Override
@@ -519,6 +528,37 @@
return failures;
}
+ /**
+ * Wait until the capture start of a request and expected timestamp arrives or it times
+ * out after a number of capture starts.
+ *
+ * @param request The request for the capture start to wait for.
+ * @param timestamp The timestamp for the capture start to wait for.
+ * @param numCaptureStartsWait The number of capture start events to wait for before timing
+ * out.
+ */
+ public void waitForCaptureStart(CaptureRequest request, Long timestamp,
+ int numCaptureStartsWait) throws Exception {
+ Pair<CaptureRequest, Long> expectedShutter = new Pair<>(request, timestamp);
+
+ int i = 0;
+ do {
+ Pair<CaptureRequest, Long> shutter = mCaptureStartQueue.poll(
+ CAPTURE_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ if (shutter == null) {
+ throw new TimeoutRuntimeException("Unable to get any more capture start " +
+ "event after waiting for " + CAPTURE_RESULT_TIMEOUT_MS + " ms.");
+ } else if (expectedShutter.equals(shutter)) {
+ return;
+ }
+
+ } while (i++ < numCaptureStartsWait);
+
+ throw new TimeoutRuntimeException("Unable to get the expected capture start " +
+ "event after waiting for " + numCaptureStartsWait + " capture starts");
+ }
+
public boolean hasMoreResults()
{
return mQueue.isEmpty();
@@ -528,6 +568,7 @@
mQueue.clear();
mNumFramesArrived.getAndSet(0);
mFailureQueue.clear();
+ mCaptureStartQueue.clear();
}
}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
index 4061412..a115fbe 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
@@ -77,7 +77,8 @@
SINGLE_SHOT,
BURST,
MIXED_BURST,
- ABORT_CAPTURE
+ ABORT_CAPTURE,
+ TIMESTAMPS
}
/**
@@ -366,6 +367,35 @@
}
/**
+ * Test reprocess timestamps for the largest input and output sizes for each supported format.
+ */
+ public void testReprocessTimestamps() throws Exception {
+ for (String id : mCameraIds) {
+ if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
+ continue;
+ }
+
+ try {
+ // open Camera device
+ openDevice(id);
+
+ int[] supportedInputFormats =
+ mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input);
+ for (int inputFormat : supportedInputFormats) {
+ int[] supportedReprocessOutputFormats =
+ mStaticInfo.getValidOutputFormatsForInput(inputFormat);
+ for (int reprocessOutputFormat : supportedReprocessOutputFormats) {
+ testReprocessingMaxSizes(id, inputFormat, reprocessOutputFormat,
+ /*previewSize*/null, CaptureTestCase.TIMESTAMPS);
+ }
+ }
+ } finally {
+ closeDevice();
+ }
+ }
+ }
+
+ /**
* Test the input format and output format with the largest input and output sizes.
*/
private void testBasicReprocessing(String cameraId, int inputFormat,
@@ -400,6 +430,10 @@
testReprocessAbort(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize,
reprocessOutputFormat);
break;
+ case TIMESTAMPS:
+ testReprocessTimestamps(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize,
+ reprocessOutputFormat);
+ break;
default:
throw new IllegalArgumentException("Invalid test case");
}
@@ -742,6 +776,89 @@
}
/**
+ * Test timestamps for reprocess requests. Reprocess request's shutter timestamp, result's
+ * sensor timestamp, and output image's timestamp should match the reprocess input's timestamp.
+ */
+ private void testReprocessTimestamps(String cameraId, Size inputSize, int inputFormat,
+ Size reprocessOutputSize, int reprocessOutputFormat) throws Exception {
+ if (VERBOSE) {
+ Log.v(TAG, "testReprocessTimestamps: cameraId: " + cameraId + " inputSize: " +
+ inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " +
+ reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat);
+ }
+
+ try {
+ setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat,
+ NUM_REPROCESS_CAPTURES);
+ setupReprocessableSession(/*previewSurface*/null, NUM_REPROCESS_CAPTURES);
+
+ // Prepare reprocess capture requests.
+ ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(NUM_REPROCESS_CAPTURES);
+ ArrayList<Long> expectedTimestamps = new ArrayList<>(NUM_REPROCESS_CAPTURES);
+
+ for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+ TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+ /*inputResult*/null);
+
+ mImageWriter.queueInputImage(
+ mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS));
+ CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result);
+ if (mShareOneImageReader) {
+ builder.addTarget(mFirstImageReader.getSurface());
+ } else {
+ builder.addTarget(mSecondImageReader.getSurface());
+ }
+ reprocessRequests.add(builder.build());
+ // Reprocess result's timestamp should match input image's timestamp.
+ expectedTimestamps.add(result.get(CaptureResult.SENSOR_TIMESTAMP));
+ }
+
+ // Submit reprocess requests.
+ SimpleCaptureCallback captureCallback = new SimpleCaptureCallback();
+ mSession.captureBurst(reprocessRequests, captureCallback, mHandler);
+
+ // Verify we get the expected timestamps.
+ for (int i = 0; i < reprocessRequests.size(); i++) {
+ captureCallback.waitForCaptureStart(reprocessRequests.get(i),
+ expectedTimestamps.get(i), CAPTURE_TIMEOUT_FRAMES);
+ }
+
+ TotalCaptureResult[] reprocessResults =
+ captureCallback.getTotalCaptureResultsForRequests(reprocessRequests,
+ CAPTURE_TIMEOUT_FRAMES);
+
+ for (int i = 0; i < expectedTimestamps.size(); i++) {
+ // Verify the result timestamps match the input image's timestamps.
+ long expected = expectedTimestamps.get(i);
+ long timestamp = reprocessResults[i].get(CaptureResult.SENSOR_TIMESTAMP);
+ assertEquals("Reprocess result timestamp (" + timestamp + ") doesn't match input " +
+ "image's timestamp (" + expected + ")", expected, timestamp);
+
+ // Verify the reprocess output image timestamps match the input image's timestamps.
+ Image image;
+ if (mShareOneImageReader) {
+ image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
+ } else {
+ image = mSecondImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
+ }
+ timestamp = image.getTimestamp();
+ image.close();
+
+ assertEquals("Reprocess output timestamp (" + timestamp + ") doesn't match input " +
+ "image's timestamp (" + expected + ")", expected, timestamp);
+ }
+
+ // Make sure all input surfaces are released.
+ for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+ mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS);
+ }
+ } finally {
+ closeReprossibleSession();
+ closeImageReaders();
+ }
+ }
+
+ /**
* Set up two image readers: one for regular capture (used for reprocess input) and one for
* reprocess capture.
*/