[go: nahoru, domu]

blob: 9f6147287993f8a06e963cb43eed90024f7d94d3 [file] [log] [blame]
/*
* Copyright 2019 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.antelope.cameracontrollers
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraDevice
import android.util.Log
import android.view.Surface
import android.view.ViewGroup
import androidx.annotation.experimental.UseExperimental
import androidx.camera.camera2.interop.Camera2Interop
import androidx.camera.camera2.ExperimentalCamera2Interop
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.LensFacing
import androidx.camera.core.Preview
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.integration.antelope.CameraParams
import androidx.camera.integration.antelope.CameraXImageAvailableListener
import androidx.camera.integration.antelope.CustomLifecycle
import androidx.camera.integration.antelope.FocusMode
import androidx.camera.integration.antelope.MainActivity
import androidx.camera.integration.antelope.MainActivity.Companion.logd
import androidx.camera.integration.antelope.PrefHelper
import androidx.camera.integration.antelope.TestConfig
import androidx.camera.integration.antelope.TestType
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.lifecycle.LifecycleOwner
import com.google.common.util.concurrent.Futures
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
/**
* Opens the camera using the Camera X API and starts the open counter. The open call will complete
* in the DeviceStateCallback asynchronously. For switch tests, the camera id will be swizzling so
* the original camera id is saved.
*
* CameraX manages its lifecycle internally, for the purpose of repeated testing, Antelope uses a
* custom lifecycle to allow for starting new tests cleanly which is started here.
*
* All the needed Cmaera X use cases should be bound before starting the lifecycle. Depending on
* the test, bind either the preview case, or both the preview and image capture case.
*/
internal fun cameraXOpenCamera(
activity: MainActivity,
params: CameraParams,
testConfig: TestConfig
) {
try {
// TODO make the switch test methodology more robust and handle physical cameras
// Currently we swap out the ids behind the scenes
// This requires to save the actual camera id for after the test
if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
(testConfig.currentRunningTest == TestType.MULTI_SWITCH)
) {
testConfig.switchTestRealCameraId = params.id // Save the actual camera ID
params.id = testConfig.switchTestCurrentCamera
}
params.cameraXDeviceStateCallback = CameraXDeviceStateCallback(params, activity, testConfig)
params.cameraXPreviewSessionStateCallback =
CameraXPreviewSessionStateCallback(activity, params, testConfig)
if (params.cameraXDeviceStateCallback != null &&
params.cameraXPreviewSessionStateCallback != null
) {
params.cameraXPreviewBuilder =
cameraXPreviewUseCaseBuilder(
testConfig.focusMode,
params.cameraXDeviceStateCallback!!,
params.cameraXPreviewSessionStateCallback!!
)
}
if (!params.cameraXLifecycle.isFinished()) {
logd("Lifecycle not finished, finishing it.")
params.cameraXLifecycle.pauseAndStop()
params.cameraXLifecycle.finish()
}
params.cameraXLifecycle = CustomLifecycle()
val lifecycleOwner: LifecycleOwner = params.cameraXLifecycle
val previewUseCase = params.cameraXPreviewBuilder.build()
// Set preview to observe the surface texture
activity.runOnUiThread {
previewUseCase.previewSurfaceProvider =
Preview.PreviewSurfaceProvider { resolution, surfaceReleaseFuture ->
// Create the SurfaceTexture and Surface
val surfaceTexture = SurfaceTexture(0)
surfaceTexture.setDefaultBufferSize(resolution.width, resolution.height)
surfaceTexture.detachFromGLContext()
val surface = Surface(surfaceTexture)
// Attach the SurfaceTexture on the TextureView
if (!isCameraSurfaceTextureReleased(surfaceTexture)) {
val viewGroup = params.cameraXPreviewTexture?.parent as ViewGroup
viewGroup.removeView(params.cameraXPreviewTexture)
viewGroup.addView(params.cameraXPreviewTexture)
params.cameraXPreviewTexture?.surfaceTexture = surfaceTexture
}
// Release the SurfaceTexture and Surface once camera is done with it
surfaceReleaseFuture.addListener(
Runnable {
surface.release()
surfaceTexture.release()
},
CameraXExecutors.directExecutor()
)
// Surface provided to camera for producing buffers into
Futures.immediateFuture(surface)
}
}
// TODO: As of 0.3.0 CameraX can only use front and back cameras.
// Update in future versions
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
val cameraXcameraID = if (params.id.equals("0")) LensFacing.BACK else LensFacing.FRONT
val cameraSelector = CameraSelector.Builder().requireLensFacing(cameraXcameraID).build()
when (testConfig.currentRunningTest) {
// Only the preview is required
TestType.PREVIEW,
TestType.SWITCH_CAMERA,
TestType.MULTI_SWITCH -> {
params.timer.openStart = System.currentTimeMillis()
GlobalScope.launch(Dispatchers.Main) {
val cameraProvider = cameraProviderFuture.await()
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector,
previewUseCase
)
params.cameraXLifecycle.start()
}
}
else -> {
// Both preview and image capture are needed
params.cameraXCaptureSessionCallback =
CameraXCaptureSessionCallback(activity, params, testConfig)
if (params.cameraXDeviceStateCallback != null &&
params.cameraXCaptureSessionCallback != null
) {
params.cameraXCaptureBuilder =
cameraXImageCaptureUseCaseBuilder(
testConfig.focusMode,
params.cameraXDeviceStateCallback!!,
params.cameraXCaptureSessionCallback!!
)
}
params.cameraXImageCaptureUseCase = params.cameraXCaptureBuilder.build()
params.timer.openStart = System.currentTimeMillis()
GlobalScope.launch(Dispatchers.Main) {
val cameraProvider = cameraProviderFuture.await()
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector,
previewUseCase, params.cameraXImageCaptureUseCase
)
params.cameraXLifecycle.start()
}
}
}
} catch (e: Exception) {
MainActivity.logd("cameraXOpenCamera exception: " + params.id)
e.printStackTrace()
}
}
/**
* End Camera X custom lifecycle, unbind use cases, and start timing the camera close.
*/
internal fun closeCameraX(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
logd("In closecameraX, camera: " + params.id + ", test: " + testConfig.currentRunningTest)
params.timer.cameraCloseStart = System.currentTimeMillis()
if (!params.cameraXLifecycle.isFinished()) {
params.cameraXLifecycle.pauseAndStop()
params.cameraXLifecycle.finish()
// CameraX calls need to be on the main thread
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
GlobalScope.launch(Dispatchers.Main) {
val cameraProvider = cameraProviderFuture.await()
cameraProvider.unbindAll()
}
}
if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
(testConfig.currentRunningTest == TestType.MULTI_SWITCH)
) {
params.id = testConfig.switchTestRealCameraId // Restore the actual camera ID
}
params.isOpen = false
}
/**
* Proceed to take and measure a still image capture.
*/
internal fun cameraXTakePicture(
activity: MainActivity,
params: CameraParams,
testConfig: TestConfig
) {
if (params.cameraXLifecycle.isFinished()) {
cameraXAbort(activity, params, testConfig)
return
}
logd("CameraX TakePicture: capture start.")
// Pause in multi-captures to make sure HDR routines don't get overloaded
logd(
"CameraX TakePicture. Pausing for " +
PrefHelper.getPreviewBuffer(activity) + "ms to let preview run."
)
params.timer.previewFillStart = System.currentTimeMillis()
Thread.sleep(PrefHelper.getPreviewBuffer(activity))
params.timer.previewFillEnd = System.currentTimeMillis()
params.timer.captureStart = System.currentTimeMillis()
params.timer.autofocusStart = System.currentTimeMillis()
params.timer.autofocusEnd = System.currentTimeMillis()
logd("Capture timer started: " + params.timer.captureStart)
activity.runOnUiThread {
params.cameraXImageCaptureUseCase
.takePicture(
CameraXExecutors.mainThreadExecutor(),
CameraXImageAvailableListener(activity, params, testConfig)
)
}
}
/**
* An abort request has been received. Abandon everything
*/
internal fun cameraXAbort(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
closeCameraX(activity, params, testConfig)
return
}
/**
* Try to determine if a SurfaceTexture is released.
*
* Prior to SDK 26 there was not built in mechanism for this. This method relies on expected
* exceptions being thrown if a released SurfaceTexture is updated.
*/
private fun isCameraSurfaceTextureReleased(texture: SurfaceTexture): Boolean {
var released = false
if (26 <= android.os.Build.VERSION.SDK_INT) {
released = texture.isReleased
} else {
// WARNING: This relies on some implementation details of the SurfaceTexture native code.
// If the SurfaceTexture is released, we should get a RuntimeException. If not, we should
// get an IllegalStateException since we are not in the same EGL context as the camera.
var exception: Exception? = null
try {
texture.updateTexImage()
} catch (e: IllegalStateException) {
logd("in isCameraSurfaceTextureReleased: IllegalStateException: " + e.message)
exception = e
released = false
} catch (e: RuntimeException) {
logd("in isCameraSurfaceTextureReleased: RuntimeException: " + e.message)
exception = e
released = true
}
if (!released && exception == null) {
throw RuntimeException("Unable to determine if SurfaceTexture is released")
}
}
logd("The camera texture is: " + if (released) "RELEASED" else "NOT RELEASED")
return released
}
/**
* Setup the Camera X preview use case
*/
@UseExperimental(markerClass = ExperimentalCamera2Interop::class)
private fun cameraXPreviewUseCaseBuilder(
focusMode: FocusMode,
deviceStateCallback: CameraDevice.StateCallback,
sessionCaptureStateCallback: CameraCaptureSession.StateCallback
): Preview.Builder {
val configBuilder = Preview.Builder()
Camera2Interop.Extender(configBuilder)
.setDeviceStateCallback(deviceStateCallback)
.setSessionStateCallback(sessionCaptureStateCallback)
// TODO(b/142915154): Enables focusMode when CameraX support direct AF mode setting.
// Prints a log to suppress "fix Parameter 'focusMode' is never used" build error"
Log.d("Antelope", "focusMode($focusMode) Not enabled.")
return configBuilder
}
/**
* Setup the Camera X image capture use case
*/
@UseExperimental(markerClass = ExperimentalCamera2Interop::class)
private fun cameraXImageCaptureUseCaseBuilder(
focusMode: FocusMode,
deviceStateCallback:
CameraDevice.StateCallback,
sessionCaptureCallback: CameraCaptureSession.CaptureCallback
): ImageCapture.Builder {
val configBuilder = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CaptureMode.MAXIMIZE_QUALITY)
Camera2Interop.Extender(configBuilder)
.setDeviceStateCallback(deviceStateCallback)
.setSessionCaptureCallback(sessionCaptureCallback)
// TODO(b/142915154): Enables focusMode when CameraX support direct AF mode setting.
// Prints a log to suppress "fix Parameter 'focusMode' is never used" build error"
Log.d("Antelope", "focusMode($focusMode) Not enabled.")
return configBuilder
}