Add Image Capture Stress test for Camera2 Extensions
- stress test image capture for each supported camera extension and camera lens id
- open a camera extension session
- take multiple pictures during the session and validate that the picture is captured correctly
- close the session
Bug: b/255959084
Test: Add new Camera2ExtensionsImageCaptureStressTest
Change-Id: I758dca89fb02fc565d14ef6c95bf6f10fa2aedca
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/camera2extensions/Camera2ExtensionsImageCaptureStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/camera2extensions/Camera2ExtensionsImageCaptureStressTest.kt
new file mode 100644
index 0000000..f9d8b9d
--- /dev/null
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/camera2extensions/Camera2ExtensionsImageCaptureStressTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.extensions.camera2extensions
+
+import android.content.Context
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraExtensionSession
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.params.OutputConfiguration
+import android.media.ImageReader
+import android.view.Surface
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.integration.extensions.util.Camera2ExtensionsTestUtil
+import androidx.camera.integration.extensions.util.Camera2ExtensionsTestUtil.assumeCameraExtensionSupported
+import androidx.camera.integration.extensions.util.Camera2ExtensionsTestUtil.createCaptureImageReader
+import androidx.camera.integration.extensions.util.Camera2ExtensionsTestUtil.openCameraDevice
+import androidx.camera.integration.extensions.util.Camera2ExtensionsTestUtil.openExtensionSession
+import androidx.camera.integration.extensions.util.Camera2ExtensionsTestUtil.takePicture
+import androidx.camera.integration.extensions.util.assertImageIsValid
+import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.StressTestRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Stress test to verify that the camera can successfully capture images for all supported
+ * extension modes for each cameras ID.
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = 31)
+class Camera2ExtensionsImageCaptureStressTest(private val config: CameraIdExtensionModePair) {
+ @get:Rule
+ val useCamera =
+ CameraUtil.grantCameraPermissionAndPreTest(
+ CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
+ )
+
+ companion object {
+ @ClassRule
+ @JvmField val stressTest = StressTestRule()
+
+ @Parameterized.Parameters(name = "config = {0}")
+ @JvmStatic
+ fun parameters() = Camera2ExtensionsTestUtil.getAllCameraIdExtensionModeCombinations()
+ }
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+
+ private lateinit var cameraDevice: CameraDevice
+ private lateinit var imageReader: ImageReader
+ private lateinit var captureSurface: Surface
+ private lateinit var extensionSession: CameraExtensionSession
+
+ @Before
+ fun setUp(): Unit = runBlocking {
+ assumeTrue(Camera2ExtensionsTestUtil.isTargetDeviceExcludedForExtensionsTest())
+
+ val (cameraId, extensionMode) = config
+
+ val extensionsCharacteristics = cameraManager.getCameraExtensionCharacteristics(cameraId)
+ assumeCameraExtensionSupported(extensionMode, extensionsCharacteristics)
+
+ cameraDevice = openCameraDevice(cameraManager, cameraId)
+ imageReader = createCaptureImageReader(extensionsCharacteristics, extensionMode)
+ captureSurface = imageReader.surface
+ val outputConfigurationCapture = OutputConfiguration(captureSurface)
+ extensionSession = openExtensionSession(
+ cameraDevice,
+ extensionMode,
+ listOf(outputConfigurationCapture)
+ )
+ assertThat(extensionSession).isNotNull()
+ }
+
+ @After
+ fun tearDown() {
+ if (::extensionSession.isInitialized) {
+ extensionSession.close()
+ }
+
+ if (::cameraDevice.isInitialized) {
+ cameraDevice.close()
+ }
+
+ if (::imageReader.isInitialized) {
+ imageReader.close()
+ }
+
+ if (::captureSurface.isInitialized) {
+ captureSurface.release()
+ }
+ }
+
+ @Test
+ fun captureImage(): Unit = runBlocking {
+ repeat(Camera2ExtensionsTestUtil.getStressTestRepeatingCount()) {
+ val image = takePicture(cameraDevice, extensionSession, imageReader)
+ assertThat(image).isNotNull()
+
+ image?.let {
+ assertThat(it.timestamp).isGreaterThan(0)
+ assertImageIsValid(it, imageReader.width, imageReader.height)
+ }
+
+ image!!.close()
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/Camera2ExtensionsTestUtil.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/Camera2ExtensionsTestUtil.kt
index 18051b9..429f3c8 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/Camera2ExtensionsTestUtil.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/Camera2ExtensionsTestUtil.kt
@@ -22,6 +22,7 @@
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraExtensionCharacteristics
import android.hardware.camera2.CameraExtensionSession
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CaptureRequest
@@ -88,15 +89,7 @@
verifyOutput: Boolean = false
) {
val extensionsCharacteristics = cameraManager.getCameraExtensionCharacteristics(cameraId)
- assumeTrue(extensionsCharacteristics.supportedExtensions.contains(extensionMode))
- assumeTrue(
- extensionsCharacteristics
- .getExtensionSupportedSizes(extensionMode, SurfaceTexture::class.java).isNotEmpty()
- )
- assumeTrue(
- extensionsCharacteristics
- .getExtensionSupportedSizes(extensionMode, ImageFormat.JPEG).isNotEmpty()
- )
+ assumeCameraExtensionSupported(extensionMode, extensionsCharacteristics)
// Preview surface
val previewSize = extensionsCharacteristics
@@ -119,11 +112,7 @@
val previewSurface = Surface(surfaceTextureHolder.surfaceTexture)
// Still capture surface
- val captureSize = extensionsCharacteristics
- .getExtensionSupportedSizes(extensionMode, ImageFormat.JPEG)
- .maxBy { it.width * it.height }
- val imageReader = ImageReader
- .newInstance(captureSize.width, captureSize.height, ImageFormat.JPEG, 2)
+ val imageReader = createCaptureImageReader(extensionsCharacteristics, extensionMode)
val captureSurface = imageReader.surface
val cameraDevice = openCameraDevice(cameraManager, cameraId)
@@ -197,9 +186,39 @@
}
/**
+ * Check if the device supports the [extensionMode] and other extension specific characteristics
+ * required for testing. Halt the test if any criteria is not satisfied.
+ */
+ fun assumeCameraExtensionSupported(
+ extensionMode: Int,
+ extensionsCharacteristics: CameraExtensionCharacteristics
+ ) {
+ assumeTrue(extensionsCharacteristics.supportedExtensions.contains(extensionMode))
+ assumeTrue(
+ extensionsCharacteristics
+ .getExtensionSupportedSizes(extensionMode, SurfaceTexture::class.java).isNotEmpty()
+ )
+ assumeTrue(
+ extensionsCharacteristics
+ .getExtensionSupportedSizes(extensionMode, ImageFormat.JPEG).isNotEmpty()
+ )
+ }
+
+ fun createCaptureImageReader(
+ extensionsCharacteristics: CameraExtensionCharacteristics,
+ extensionMode: Int
+ ): ImageReader {
+ val captureSize = extensionsCharacteristics
+ .getExtensionSupportedSizes(extensionMode, ImageFormat.JPEG)
+ .maxBy { it.width * it.height }
+ return ImageReader
+ .newInstance(captureSize.width, captureSize.height, ImageFormat.JPEG, 2)
+ }
+
+ /**
* Open the camera device and return the [CameraDevice] instance.
*/
- private suspend fun openCameraDevice(
+ suspend fun openCameraDevice(
cameraManager: CameraManager,
cameraId: String
): CameraDevice {
@@ -228,7 +247,7 @@
/**
* Open the [CameraExtensionSession] and return the instance.
*/
- private suspend fun openExtensionSession(
+ suspend fun openExtensionSession(
cameraDevice: CameraDevice,
extensionMode: Int,
outputConfigs: List<OutputConfiguration>
@@ -256,7 +275,11 @@
return deferred.await()
}
- private suspend fun takePicture(
+ /**
+ * Take a picture with the provided [session] and output the contents to the [imageReader]. The
+ * latest image written to the [imageReader] is returned.
+ */
+ suspend fun takePicture(
cameraDevice: CameraDevice,
session: CameraExtensionSession,
imageReader: ImageReader
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/ImageCaptureTestUtil.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/ImageCaptureTestUtil.kt
new file mode 100644
index 0000000..1875421
--- /dev/null
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/ImageCaptureTestUtil.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.extensions.util
+
+import android.graphics.BitmapFactory
+import android.graphics.ImageFormat
+import android.media.Image
+import com.google.common.truth.Truth.assertThat
+import junit.framework.TestCase.assertNotNull
+import junit.framework.TestCase.assertTrue
+
+/**
+ * Validate the image can be correctly decoded from a jpeg format to a bitmap.
+ */
+fun assertImageIsValid(image: Image, width: Int, height: Int) {
+ assertThat(image.width).isEqualTo(width)
+ assertThat(image.height).isEqualTo(height)
+ assertThat(image.format).isEqualTo(ImageFormat.JPEG)
+
+ val data = imageData(image)
+ assertTrue("Invalid image data", data.isNotEmpty())
+
+ val bmpOptions = BitmapFactory.Options().apply {
+ inJustDecodeBounds = true
+ }
+
+ BitmapFactory.decodeByteArray(data, 0, data.size, bmpOptions)
+
+ assertThat(width).isEqualTo(bmpOptions.outWidth)
+ assertThat(height).isEqualTo(bmpOptions.outHeight)
+
+ val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
+ assertNotNull("Decoding jpeg failed", bitmap)
+}
+
+private fun imageData(image: Image): ByteArray {
+ val planes = image.planes
+ assertTrue("Fail to get image planes", planes != null && planes.isNotEmpty())
+
+ val buffer = planes[0].buffer
+ assertNotNull("Fail to get jpeg ByteBuffer", buffer)
+
+ val data = ByteArray(buffer.remaining())
+ buffer.get(data)
+ buffer.rewind()
+ return data
+}
\ No newline at end of file