Camera2: Mandatory stream configuration combinations test
Also mark this test as a known failure, since currently it
freezes CTS runs on some devices.
Bug: 16899526
Change-Id: I0e7560b9df004d74546a594efc423d8460f28a31
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index 0ab6acb..23c7437 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -54,5 +54,11 @@
"android.openglperf.cts.GlVboPerfTest#testVboWithVaryingIndexBufferNumbers"
],
bug: 17394321
-}
+},
+{
+ description: "this test deadlocks on some devices, freezing CTS runs, marking known failure until resolved",
+ names: [
+ "android.hardware.camera2.cts.RobustnessTest#testMandatoryOutputCombinations"
+ ],
+ bug: 16899526
]
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
index 5b0f0fd..c394b47 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -16,17 +16,31 @@
package android.hardware.camera2.cts;
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.cts.RobustnessTest.MaxOutputSizes.*;
+
+import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.media.CamcorderProfile;
+import android.media.ImageReader;
import android.util.Log;
+import android.util.Size;
import android.view.Surface;
import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
@@ -78,4 +92,388 @@
}
}
}
+
+ /**
+ * Test for making sure the required output combinations for each hardware level and capability
+ * work as expected.
+ */
+ public void testMandatoryOutputCombinations() throws Exception {
+ /**
+ * Tables for maximum sizes to try for each hardware level and capability.
+ *
+ * Keep in sync with the tables in
+ * frameworks/base/core/java/android/hardware/camera2/CameraDevice.java#createCaptureSession
+ *
+ * Each row of the table is a set of (format, max resolution) pairs, using the below consts
+ */
+
+ // Enum values are defined in MaxOutputSizes
+ final int[][] LEGACY_COMBINATIONS = {
+ {PRIV, MAXIMUM}, // Simple preview, GPU video processing, or no-preview video recording
+ {JPEG, MAXIMUM}, // No-viewfinder still image capture
+ {YUV, MAXIMUM}, // In-application video/image processing
+ {PRIV, PREVIEW, JPEG, MAXIMUM}, // Standard still imaging.
+ {YUV, PREVIEW, JPEG, MAXIMUM}, // In-app processing plus still capture.
+ {PRIV, PREVIEW, PRIV, PREVIEW}, // Standard recording.
+ {PRIV, PREVIEW, YUV, PREVIEW}, // Preview plus in-app processing.
+ {PRIV, PREVIEW, YUV, PREVIEW, JPEG, MAXIMUM} // Still capture plus in-app processing.
+ };
+
+ final int[][] LIMITED_COMBINATIONS = {
+ {PRIV, PREVIEW, PRIV, RECORD }, // High-resolution video recording with preview.
+ {PRIV, PREVIEW, YUV , RECORD }, // High-resolution in-app video processing with preview.
+ {YUV , PREVIEW, YUV , RECORD }, // Two-input in-app video processing.
+ {PRIV, PREVIEW, PRIV, RECORD, JPEG, RECORD }, // High-resolution recording with video snapshot.
+ {PRIV, PREVIEW, YUV, RECORD, JPEG, RECORD }, // High-resolution in-app processing with video snapshot.
+ {YUV , PREVIEW, YUV, PREVIEW, JPEG, MAXIMUM } // Two-input in-app processing with still capture.
+ };
+
+ final int[][] FULL_COMBINATIONS = {
+ {PRIV, PREVIEW, PRIV, MAXIMUM }, // Maximum-resolution GPU processing with preview.
+ {PRIV, PREVIEW, YUV, MAXIMUM }, // Maximum-resolution in-app processing with preview.
+ {YUV, PREVIEW, YUV, MAXIMUM }, // Maximum-resolution two-input in-app processsing.
+ {PRIV, PREVIEW, PRIV, PREVIEW, JPEG, MAXIMUM }, //Video recording with maximum-size video snapshot.
+ {YUV, VGA, PRIV, PREVIEW, YUV, MAXIMUM }, // Standard video recording plus maximum-resolution in-app processing.
+ {YUV, VGA, YUV, PREVIEW, YUV, MAXIMUM } // Preview plus two-input maximum-resolution in-app processing.
+ };
+
+ final int[][] RAW_COMBINATIONS = {
+ {RAW, MAXIMUM }, // No-preview DNG capture.
+ {PRIV, PREVIEW, RAW, MAXIMUM }, // Standard DNG capture.
+ {YUV, PREVIEW, RAW, MAXIMUM }, // In-app processing plus DNG capture.
+ {PRIV, PREVIEW, PRIV, PREVIEW, RAW, MAXIMUM}, // Video recording with DNG capture.
+ {PRIV, PREVIEW, YUV, PREVIEW, RAW, MAXIMUM}, // Preview with in-app processing and DNG capture.
+ {YUV, PREVIEW, YUV, PREVIEW, RAW, MAXIMUM}, // Two-input in-app processing plus DNG capture.
+ {PRIV, PREVIEW, JPEG, MAXIMUM, RAW, MAXIMUM}, // Still capture with simultaneous JPEG and DNG.
+ {YUV, PREVIEW, JPEG, MAXIMUM, RAW, MAXIMUM} // In-app processing with simultaneous JPEG and DNG.
+ };
+
+ final int[][][] TABLES =
+ { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS };
+
+ // Sanity check the tables
+ int tableIdx = 0;
+ for (int[][] table : TABLES) {
+ int rowIdx = 0;
+ for (int[] row : table) {
+ assertTrue(String.format("Odd number of entries for table %d row %d: %s ",
+ tableIdx, rowIdx, Arrays.toString(row)),
+ (row.length % 2) == 0);
+ for (int i = 0; i < row.length; i += 2) {
+ int format = row[i];
+ int maxSize = row[i + 1];
+ assertTrue(String.format("table %d row %d index %d format not valid: %d",
+ tableIdx, rowIdx, i, format),
+ format == PRIV || format == JPEG || format == YUV || format == RAW);
+ assertTrue(String.format("table %d row %d index %d max size not valid: %d",
+ tableIdx, rowIdx, i + 1, maxSize),
+ maxSize == PREVIEW || maxSize == RECORD ||
+ maxSize == MAXIMUM || maxSize == VGA);
+ }
+ rowIdx++;
+ }
+ tableIdx++;
+ }
+
+ for (String id : mCameraIds) {
+
+ // Find the concrete max sizes for each format/resolution combination
+
+ CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(id);
+
+ MaxOutputSizes maxSizes = new MaxOutputSizes(cc, id);
+
+ final StaticMetadata staticInfo = new StaticMetadata(cc);
+
+ openDevice(id);
+
+ // Always run legacy-level tests
+
+ for (int[] config : LEGACY_COMBINATIONS) {
+ testOutputCombination(id, config, maxSizes);
+ }
+
+ // Then run higher-level tests if applicable
+
+ if (!staticInfo.isHardwareLevelLegacy()) {
+
+ // If not legacy, at least limited, so run limited-level tests
+
+ for (int[] config : LIMITED_COMBINATIONS) {
+ testOutputCombination(id, config, maxSizes);
+ }
+
+ // Check for FULL and RAW and run those if appropriate
+
+ if (staticInfo.isHardwareLevelFull()) {
+ for (int[] config : FULL_COMBINATIONS) {
+ testOutputCombination(id, config, maxSizes);
+ }
+ }
+
+ if (staticInfo.isCapabilitySupported(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+ for (int[] config : RAW_COMBINATIONS) {
+ testOutputCombination(id, config, maxSizes);
+ }
+ }
+ }
+
+ closeDevice(id);
+ }
+ }
+
+ /**
+ * Simple holder for resolutions to use for different camera outputs and size limits.
+ */
+ static class MaxOutputSizes {
+ // Format shorthands
+ static final int PRIV = -1;
+ static final int JPEG = ImageFormat.JPEG;
+ static final int YUV = ImageFormat.YUV_420_888;
+ static final int RAW = ImageFormat.RAW_SENSOR;
+
+ // Max resolution indices
+ static final int PREVIEW = 0;
+ static final int RECORD = 1;
+ static final int MAXIMUM = 2;
+ static final int VGA = 3;
+ static final int RESOLUTION_COUNT = 4;
+
+ public MaxOutputSizes(CameraCharacteristics cc, String cameraId) {
+ StreamConfigurationMap configs =
+ cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size[] privSizes = configs.getOutputSizes(SurfaceTexture.class);
+ Size[] yuvSizes = configs.getOutputSizes(ImageFormat.YUV_420_888);
+ Size[] jpegSizes = configs.getOutputSizes(ImageFormat.JPEG);
+ Size[] rawSizes = configs.getOutputSizes(ImageFormat.RAW_SENSOR);
+
+ maxRawSize = (rawSizes != null) ? CameraTestUtils.getMaxSize(rawSizes) : null;
+
+ maxPrivSizes[PREVIEW] = getMaxSize(privSizes, PREVIEW_SIZE_BOUND);
+ maxYuvSizes[PREVIEW] = getMaxSize(yuvSizes, PREVIEW_SIZE_BOUND);
+ maxJpegSizes[PREVIEW] = getMaxSize(jpegSizes, PREVIEW_SIZE_BOUND);
+
+ maxPrivSizes[RECORD] = getMaxRecordingSize(cameraId);
+ maxYuvSizes[RECORD] = getMaxRecordingSize(cameraId);
+ maxJpegSizes[RECORD] = getMaxRecordingSize(cameraId);
+
+ maxPrivSizes[MAXIMUM] = CameraTestUtils.getMaxSize(privSizes);
+ maxYuvSizes[MAXIMUM] = CameraTestUtils.getMaxSize(yuvSizes);
+ maxJpegSizes[MAXIMUM] = CameraTestUtils.getMaxSize(jpegSizes);
+
+ // Must always be supported, add unconditionally
+ final Size vgaSize = new Size(640, 480);
+ maxPrivSizes[VGA] = vgaSize;
+ maxJpegSizes[VGA] = vgaSize;
+ maxYuvSizes[VGA] = vgaSize;
+ }
+
+ public final Size[] maxPrivSizes = new Size[RESOLUTION_COUNT];
+ public final Size[] maxJpegSizes = new Size[RESOLUTION_COUNT];
+ public final Size[] maxYuvSizes = new Size[RESOLUTION_COUNT];
+ public final Size maxRawSize;
+
+ static public String configToString(int[] config) {
+ StringBuilder b = new StringBuilder("{ ");
+ for (int i = 0; i < config.length; i += 2) {
+ int format = config[i];
+ int sizeLimit = config[i + 1];
+ switch (format) {
+ case PRIV:
+ b.append("[PRIV, ");
+ break;
+ case JPEG:
+ b.append("[JPEG, ");
+ break;
+ case YUV:
+ b.append("[YUV, ");
+ break;
+ case RAW:
+ b.append("[RAW, ");
+ break;
+ default:
+ b.append("[UNK, ");
+ break;
+ }
+ switch (sizeLimit) {
+ case PREVIEW:
+ b.append("PREVIEW] ");
+ break;
+ case RECORD:
+ b.append("RECORD] ");
+ break;
+ case MAXIMUM:
+ b.append("MAXIMUM] ");
+ break;
+ case VGA:
+ b.append("VGA] ");
+ break;
+ default:
+ b.append("UNK] ");
+ break;
+ }
+ }
+ b.append("}");
+ return b.toString();
+ }
+ }
+
+ private void testOutputCombination(String cameraId, int[] config, MaxOutputSizes maxSizes)
+ throws Exception {
+
+ Log.i(TAG, String.format("Testing Camera %s, config %s",
+ cameraId, MaxOutputSizes.configToString(config)));
+
+ final int TIMEOUT_FOR_RESULT_MS = 1000;
+ final int MIN_RESULT_COUNT = 3;
+
+ // Set up outputs
+ List<Object> outputTargets = new ArrayList<>();
+ List<Surface> outputSurfaces = new ArrayList<>();
+ for (int i = 0; i < config.length; i += 2) {
+ int format = config[i];
+ int sizeLimit = config[i + 1];
+
+ switch (format) {
+ case PRIV: {
+ Size targetSize = maxSizes.maxPrivSizes[sizeLimit];
+ SurfaceTexture target = new SurfaceTexture(/*random int*/1);
+ target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
+ outputTargets.add(target);
+ outputSurfaces.add(new Surface(target));
+ break;
+ }
+ case JPEG: {
+ Size targetSize = maxSizes.maxJpegSizes[sizeLimit];
+ ImageReader target = ImageReader.newInstance(
+ targetSize.getWidth(), targetSize.getHeight(), JPEG, MIN_RESULT_COUNT);
+ outputTargets.add(target);
+ outputSurfaces.add(target.getSurface());
+ break;
+ }
+ case YUV: {
+ Size targetSize = maxSizes.maxYuvSizes[sizeLimit];
+ ImageReader target = ImageReader.newInstance(
+ targetSize.getWidth(), targetSize.getHeight(), YUV, MIN_RESULT_COUNT);
+ outputTargets.add(target);
+ outputSurfaces.add(target.getSurface());
+ break;
+ }
+ case RAW: {
+ Size targetSize = maxSizes.maxRawSize;
+ ImageReader target = ImageReader.newInstance(
+ targetSize.getWidth(), targetSize.getHeight(), RAW, MIN_RESULT_COUNT);
+ outputTargets.add(target);
+ outputSurfaces.add(target.getSurface());
+ break;
+ }
+ default:
+ fail("Unknown output format " + format);
+ }
+ }
+
+ boolean haveSession = false;
+ try {
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+ for (Surface s : outputSurfaces) {
+ requestBuilder.addTarget(s);
+ }
+
+ CameraCaptureSession.CaptureCallback mockCaptureCallback =
+ mock(CameraCaptureSession.CaptureCallback.class);
+
+ createSession(outputSurfaces);
+ haveSession = true;
+ CaptureRequest request = requestBuilder.build();
+ mCameraSession.setRepeatingRequest(request, mockCaptureCallback, mHandler);
+
+ verify(mockCaptureCallback,
+ timeout(TIMEOUT_FOR_RESULT_MS * MIN_RESULT_COUNT).atLeast(MIN_RESULT_COUNT))
+ .onCaptureCompleted(
+ eq(mCameraSession),
+ eq(request),
+ isA(TotalCaptureResult.class));
+ verify(mockCaptureCallback, never()).
+ onCaptureFailed(
+ eq(mCameraSession),
+ eq(request),
+ isA(CaptureFailure.class));
+
+ } catch (Throwable e) {
+ mCollector.addMessage(String.format("Output combination %s failed due to: %s",
+ MaxOutputSizes.configToString(config), e.getMessage()));
+ }
+ if (haveSession) {
+ try {
+ Log.i(TAG, String.format("Done with camera %s, config %s, closing session",
+ cameraId, MaxOutputSizes.configToString(config)));
+ stopCapture(/*fast*/false);
+ } catch (Throwable e) {
+ mCollector.addMessage(
+ String.format("Closing down for output combination %s failed due to: %s",
+ MaxOutputSizes.configToString(config), e.getMessage()));
+ }
+ }
+ }
+
+ private static Size getMaxRecordingSize(String cameraId) {
+ int id = Integer.valueOf(cameraId);
+
+ int quality =
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_2160P) ?
+ CamcorderProfile.QUALITY_2160P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_1080P) ?
+ CamcorderProfile.QUALITY_1080P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_720P) ?
+ CamcorderProfile.QUALITY_720P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_480P) ?
+ CamcorderProfile.QUALITY_480P :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_QVGA) ?
+ CamcorderProfile.QUALITY_QVGA :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_CIF) ?
+ CamcorderProfile.QUALITY_CIF :
+ CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_QCIF) ?
+ CamcorderProfile.QUALITY_QCIF :
+ -1;
+
+ assertTrue("No recording supported for camera id " + cameraId, quality != -1);
+
+ CamcorderProfile maxProfile = CamcorderProfile.get(id, quality);
+ return new Size(maxProfile.videoFrameWidth, maxProfile.videoFrameHeight);
+ }
+
+ /**
+ * Get maximum size in list that's equal or smaller to than the bound.
+ * Returns null if no size is smaller than or equal to the bound.
+ */
+ private static Size getMaxSize(Size[] sizes, Size bound) {
+ if (sizes == null || sizes.length == 0) {
+ throw new IllegalArgumentException("sizes was empty");
+ }
+
+ Size sz = null;
+ for (Size size : sizes) {
+ if (size.getWidth() <= bound.getWidth() && size.getHeight() <= bound.getHeight()) {
+
+ if (sz == null) {
+ sz = size;
+ } else {
+ long curArea = sz.getWidth() * (long) sz.getHeight();
+ long newArea = size.getWidth() * (long) size.getHeight();
+ if ( newArea > curArea ) {
+ sz = size;
+ }
+ }
+ }
+ }
+
+ assertTrue("No size under bound found: " + Arrays.toString(sizes) + " bound " + bound,
+ sz != null);
+
+ return sz;
+ }
+
}