blob: 429f3c80ee600e053917db0cce81ac41003666f5 [file] [log] [blame]
/*
* 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.content.Context
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
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
import android.hardware.camera2.TotalCaptureResult
import android.hardware.camera2.params.ExtensionSessionConfiguration
import android.hardware.camera2.params.OutputConfiguration
import android.media.Image
import android.media.ImageReader
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.integration.extensions.utils.Camera2ExtensionsUtil.AVAILABLE_CAMERA2_EXTENSION_MODES
import androidx.camera.integration.extensions.utils.CameraIdExtensionModePair
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.LabTestRule
import androidx.camera.testing.SurfaceTextureProvider
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executors
import kotlinx.coroutines.CompletableDeferred
import org.junit.Assume.assumeTrue
@RequiresApi(31)
object Camera2ExtensionsTestUtil {
private const val LAB_STRESS_TEST_OPERATION_REPEAT_COUNT = 10
private const val STRESS_TEST_OPERATION_REPEAT_COUNT = 3
const val EXTENSION_NOT_FOUND = -1
/**
* Returns whether the target device is excluded for extensions test
*/
@JvmStatic
fun isTargetDeviceExcludedForExtensionsTest(): Boolean {
// Skips Cuttlefish device since actually it is not a real marketing device which supports
// extensions and it will cause pre-submit failures.
return !Build.MODEL.contains("Cuttlefish", true)
}
@JvmStatic
fun getStressTestRepeatingCount() =
if (LabTestRule.isInLabTest()) {
LAB_STRESS_TEST_OPERATION_REPEAT_COUNT
} else {
STRESS_TEST_OPERATION_REPEAT_COUNT
}
/**
* Gets a list of all camera id and extension mode combinations.
*/
@JvmStatic
fun getAllCameraIdExtensionModeCombinations(): List<CameraIdExtensionModePair> =
CameraUtil.getBackwardCompatibleCameraIdListOrThrow().flatMap { cameraId ->
AVAILABLE_CAMERA2_EXTENSION_MODES.map { extensionMode ->
CameraIdExtensionModePair(cameraId, extensionMode)
}
}
suspend fun assertCanOpenExtensionsSession(
cameraManager: CameraManager,
cameraId: String,
extensionMode: Int,
verifyOutput: Boolean = false
) {
val extensionsCharacteristics = cameraManager.getCameraExtensionCharacteristics(cameraId)
assumeCameraExtensionSupported(extensionMode, extensionsCharacteristics)
// Preview surface
val previewSize = extensionsCharacteristics
.getExtensionSupportedSizes(extensionMode, SurfaceTexture::class.java)
.maxBy { it.width * it.height }
val deferredPreviewFrame = CompletableDeferred<SurfaceTexture>()
val executorForGL = Executors.newSingleThreadExecutor()
// Some OEM requires frames drain (updateTexImage being invoked) in SurfaceTexture,
// otherwise it might cause still capture to fail.
val surfaceTextureHolder = SurfaceTextureProvider.createAutoDrainingSurfaceTexture(
executorForGL,
previewSize.width,
previewSize.height
) {
if (!deferredPreviewFrame.isCompleted) {
deferredPreviewFrame.complete(it)
}
}
val previewSurface = Surface(surfaceTextureHolder.surfaceTexture)
// Still capture surface
val imageReader = createCaptureImageReader(extensionsCharacteristics, extensionMode)
val captureSurface = imageReader.surface
val cameraDevice = openCameraDevice(cameraManager, cameraId)
val outputConfigurationPreview = OutputConfiguration(previewSurface)
val outputConfigurationCapture = OutputConfiguration(captureSurface)
val extensionSession = openExtensionSession(
cameraDevice,
extensionMode,
listOf(outputConfigurationPreview, outputConfigurationCapture)
)
assertThat(extensionSession).isNotNull()
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
builder.addTarget(previewSurface)
extensionSession.setRepeatingRequest(builder.build(), CameraXExecutors.ioExecutor(),
object : CameraExtensionSession.ExtensionCaptureCallback() {
override fun onCaptureSequenceCompleted(
session: CameraExtensionSession,
sequenceId: Int
) {
}
override fun onCaptureStarted(
session: CameraExtensionSession,
request: CaptureRequest,
timestamp: Long
) {
}
override fun onCaptureProcessStarted(
session: CameraExtensionSession,
request: CaptureRequest
) {
}
override fun onCaptureFailed(
session: CameraExtensionSession,
request: CaptureRequest
) {
}
override fun onCaptureSequenceAborted(
session: CameraExtensionSession,
sequenceId: Int
) {
}
override fun onCaptureResultAvailable(
session: CameraExtensionSession,
request: CaptureRequest,
result: TotalCaptureResult
) {
}
})
if (verifyOutput) {
deferredPreviewFrame.await()
val image = takePicture(cameraDevice, extensionSession, imageReader)
assertThat(image).isNotNull()
image!!.close()
}
extensionSession.close()
cameraDevice.close()
imageReader.close()
previewSurface.release()
captureSurface.release()
surfaceTextureHolder.close()
executorForGL.shutdown()
}
/**
* 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.
*/
suspend fun openCameraDevice(
cameraManager: CameraManager,
cameraId: String
): CameraDevice {
val deferred = CompletableDeferred<CameraDevice>()
cameraManager.openCamera(
cameraId,
CameraXExecutors.ioExecutor(),
object : CameraDevice.StateCallback() {
override fun onOpened(cameraDevice: CameraDevice) {
deferred.complete(cameraDevice)
}
override fun onDisconnected(cameraDevice: CameraDevice) {
deferred.completeExceptionally(RuntimeException("Camera Disconnected"))
}
override fun onError(cameraDevice: CameraDevice, error: Int) {
deferred.completeExceptionally(
RuntimeException("Camera onError(error=$cameraDevice)")
)
}
})
return deferred.await()
}
/**
* Open the [CameraExtensionSession] and return the instance.
*/
suspend fun openExtensionSession(
cameraDevice: CameraDevice,
extensionMode: Int,
outputConfigs: List<OutputConfiguration>
): CameraExtensionSession {
val deferred = CompletableDeferred<CameraExtensionSession>()
val extensionSessionConfiguration = ExtensionSessionConfiguration(
extensionMode,
outputConfigs,
CameraXExecutors.ioExecutor(),
object : CameraExtensionSession.StateCallback() {
override fun onConfigured(cameraExtensionSession: CameraExtensionSession) {
deferred.complete(cameraExtensionSession)
}
override fun onConfigureFailed(session: CameraExtensionSession) {
deferred.completeExceptionally(RuntimeException("onConfigureFailed"))
}
override fun onClosed(session: CameraExtensionSession) {
}
}
)
cameraDevice.createExtensionSession(extensionSessionConfiguration)
return deferred.await()
}
/**
* 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
): Image? {
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
builder.addTarget(imageReader.surface)
val deferredCapture = CompletableDeferred<Int>()
session.capture(builder.build(), CameraXExecutors.ioExecutor(),
object : CameraExtensionSession.ExtensionCaptureCallback() {
override fun onCaptureSequenceCompleted(
session: CameraExtensionSession,
sequenceId: Int
) {
deferredCapture.complete(sequenceId)
}
override fun onCaptureStarted(
session: CameraExtensionSession,
request: CaptureRequest,
timestamp: Long
) {
}
override fun onCaptureProcessStarted(
session: CameraExtensionSession,
request: CaptureRequest
) {
}
override fun onCaptureFailed(
session: CameraExtensionSession,
request: CaptureRequest
) {
deferredCapture.completeExceptionally(RuntimeException("onCaptureFailed"))
}
override fun onCaptureSequenceAborted(
session: CameraExtensionSession,
sequenceId: Int
) {
deferredCapture.completeExceptionally(
RuntimeException("onCaptureSequenceAborted")
)
}
})
val deferredImage = CompletableDeferred<Image?>()
imageReader.setOnImageAvailableListener({
val image = imageReader.acquireNextImage()
deferredImage.complete(image)
}, Handler(Looper.getMainLooper()))
deferredCapture.await()
return deferredImage.await()
}
fun findNextSupportedCameraId(
context: Context,
currentCameraId: String,
extensionsMode: Int
): String? {
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
val supportedCameraIdList = cameraManager.cameraIdList.filter {
val characteristics = cameraManager.getCameraCharacteristics(it)
val backwardCompatible = characteristics
.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!
.toList()
.contains(CameraCharacteristics
.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE)
if (!backwardCompatible) {
return@filter false
}
val extCharacteristics = cameraManager.getCameraExtensionCharacteristics((it))
return@filter extCharacteristics.supportedExtensions.contains(extensionsMode)
}
if (supportedCameraIdList.size <= 1) {
return null
}
val currentIndex = supportedCameraIdList.indexOf(currentCameraId)
return supportedCameraIdList[(currentIndex + 1) % supportedCameraIdList.size]
} catch (e: CameraAccessException) {
}
return null
}
fun findNextEffectMode(
context: Context,
cameraId: String,
extensionsMode: Int
): Int {
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
val characteristics = cameraManager.getCameraExtensionCharacteristics((cameraId))
val supportedExtensions = ArrayList(characteristics.supportedExtensions).apply {
sort()
}
val currentIndex = supportedExtensions.indexOf(extensionsMode)
if (currentIndex >= 0 && supportedExtensions.size > 1) {
return supportedExtensions[(currentIndex + 1) % supportedExtensions.size]
}
} catch (e: CameraAccessException) {
}
return EXTENSION_NOT_FOUND
}
}