Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package androidx.camera.integration.antelope.cameracontrollers |
| 18 | |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 19 | import android.graphics.SurfaceTexture |
| 20 | import android.hardware.camera2.CameraCaptureSession |
| 21 | import android.hardware.camera2.CameraDevice |
| 22 | import android.hardware.camera2.CaptureRequest |
| 23 | import android.view.ViewGroup |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 24 | import androidx.camera.camera2.Camera2Config |
| 25 | import androidx.camera.core.CameraX |
| 26 | import androidx.camera.core.ImageCapture |
| 27 | import androidx.camera.core.ImageCaptureConfig |
| 28 | import androidx.camera.core.Preview |
| 29 | import androidx.camera.core.PreviewConfig |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame^] | 30 | import androidx.camera.core.impl.utils.executor.CameraXExecutors |
| 31 | import androidx.camera.integration.antelope.CameraParams |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 32 | import androidx.camera.integration.antelope.CameraXImageAvailableListener |
| 33 | import androidx.camera.integration.antelope.CustomLifecycle |
| 34 | import androidx.camera.integration.antelope.FocusMode |
| 35 | import androidx.camera.integration.antelope.MainActivity |
| 36 | import androidx.camera.integration.antelope.MainActivity.Companion.logd |
| 37 | import androidx.camera.integration.antelope.PrefHelper |
| 38 | import androidx.camera.integration.antelope.TestConfig |
| 39 | import androidx.camera.integration.antelope.TestType |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame^] | 40 | import androidx.lifecycle.LifecycleOwner |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 41 | |
| 42 | /** |
| 43 | * Opens the camera using the Camera X API and starts the open counter. The open call will complete |
| 44 | * in the DeviceStateCallback asynchronously. For switch tests, the camera id will be swizzling so |
| 45 | * the original camera id is saved. |
| 46 | * |
| 47 | * CameraX manages its lifecycle internally, for the purpose of repeated testing, Antelope uses a |
| 48 | * custom lifecycle to allow for starting new tests cleanly which is started here. |
| 49 | * |
| 50 | * All the needed Cmaera X use cases should be bound before starting the lifecycle. Depending on |
| 51 | * the test, bind either the preview case, or both the preview and image capture case. |
| 52 | */ |
| 53 | internal fun cameraXOpenCamera( |
| 54 | activity: MainActivity, |
| 55 | params: CameraParams, |
| 56 | testConfig: TestConfig |
| 57 | ) { |
| 58 | |
| 59 | try { |
| 60 | // TODO make the switch test methodology more robust and handle physical cameras |
| 61 | // Currently we swap out the ids behind the scenes |
| 62 | // This requires to save the actual camera id for after the test |
| 63 | if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) || |
| 64 | (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) { |
| 65 | testConfig.switchTestRealCameraId = params.id // Save the actual camera ID |
| 66 | params.id = testConfig.switchTestCurrentCamera |
| 67 | } |
| 68 | |
| 69 | params.cameraXDeviceStateCallback = CameraXDeviceStateCallback(params, activity, testConfig) |
| 70 | params.cameraXPreviewSessionStateCallback = |
| 71 | CameraXPreviewSessionStateCallback(activity, params, testConfig) |
| 72 | |
| 73 | if (params.cameraXDeviceStateCallback != null && |
| 74 | params.cameraXPreviewSessionStateCallback != null) { |
| 75 | params.cameraXPreviewConfig = |
| 76 | cameraXPreviewUseCaseBuilder(params.id, testConfig.focusMode, |
| 77 | params.cameraXDeviceStateCallback!!, |
| 78 | params.cameraXPreviewSessionStateCallback!!) |
| 79 | } |
| 80 | |
| 81 | if (!params.cameraXLifecycle.isFinished()) { |
| 82 | logd("Lifecycle not finished, finishing it.") |
| 83 | params.cameraXLifecycle.pauseAndStop() |
| 84 | params.cameraXLifecycle.finish() |
| 85 | } |
| 86 | params.cameraXLifecycle = CustomLifecycle() |
| 87 | |
| 88 | val lifecycleOwner: LifecycleOwner = params.cameraXLifecycle |
| 89 | val previewUseCase = Preview(params.cameraXPreviewConfig) |
| 90 | |
| 91 | // Set preview to observe the surface texture |
| 92 | activity.runOnUiThread { |
| 93 | previewUseCase.setOnPreviewOutputUpdateListener { |
| 94 | viewFinderOutput: Preview.PreviewOutput? -> |
| 95 | if (viewFinderOutput?.surfaceTexture != null) { |
| 96 | if (!isCameraSurfaceTextureReleased(viewFinderOutput.surfaceTexture)) { |
| 97 | // View swizzling required to for the view hierarchy to update correctly |
| 98 | val viewGroup = params.cameraXPreviewTexture?.parent as ViewGroup |
| 99 | viewGroup.removeView(params.cameraXPreviewTexture) |
| 100 | viewGroup.addView(params.cameraXPreviewTexture) |
| 101 | params.cameraXPreviewTexture?.surfaceTexture = |
| 102 | viewFinderOutput.surfaceTexture |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | when (testConfig.currentRunningTest) { |
| 109 | // Only the preview is required |
| 110 | TestType.PREVIEW, |
| 111 | TestType.SWITCH_CAMERA, |
| 112 | TestType.MULTI_SWITCH -> { |
| 113 | params.timer.openStart = System.currentTimeMillis() |
| 114 | activity.runOnUiThread { |
| 115 | CameraX.bindToLifecycle(lifecycleOwner, previewUseCase) |
| 116 | params.cameraXLifecycle.start() |
| 117 | } |
| 118 | } |
| 119 | else -> { |
| 120 | // Both preview and image capture are needed |
| 121 | params.cameraXCaptureSessionCallback = |
| 122 | CameraXCaptureSessionCallback(activity, params, testConfig) |
| 123 | |
| 124 | if (params.cameraXDeviceStateCallback != null && |
| 125 | params.cameraXCaptureSessionCallback != null) { |
| 126 | params.cameraXCaptureConfig = |
| 127 | cameraXImageCaptureUseCaseBuilder(params.id, testConfig.focusMode, |
| 128 | params.cameraXDeviceStateCallback!!, |
| 129 | params.cameraXCaptureSessionCallback!!) |
| 130 | } |
| 131 | |
| 132 | params.cameraXImageCaptureUseCase = ImageCapture(params.cameraXCaptureConfig) |
| 133 | |
| 134 | params.timer.openStart = System.currentTimeMillis() |
| 135 | activity.runOnUiThread { |
| 136 | CameraX.bindToLifecycle(lifecycleOwner, previewUseCase, |
| 137 | params.cameraXImageCaptureUseCase) |
| 138 | params.cameraXLifecycle.start() |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | } catch (e: Exception) { |
| 143 | MainActivity.logd("cameraXOpenCamera exception: " + params.id) |
| 144 | e.printStackTrace() |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * End Camera X custom lifecycle, unbind use cases, and start timing the camera close. |
| 150 | */ |
| 151 | internal fun closeCameraX(activity: MainActivity, params: CameraParams, testConfig: TestConfig) { |
| 152 | logd("In closecameraX, camera: " + params.id + ", test: " + testConfig.currentRunningTest) |
| 153 | |
| 154 | params.timer.cameraCloseStart = System.currentTimeMillis() |
| 155 | |
| 156 | if (!params.cameraXLifecycle.isFinished()) { |
| 157 | params.cameraXLifecycle.pauseAndStop() |
| 158 | params.cameraXLifecycle.finish() |
| 159 | |
| 160 | // CameraX calls need to be on the main thread |
| 161 | activity.run { |
| 162 | CameraX.unbindAll() |
| 163 | } |
| 164 | } |
| 165 | if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) || |
| 166 | (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) { |
| 167 | params.id = testConfig.switchTestRealCameraId // Restore the actual camera ID |
| 168 | } |
| 169 | |
| 170 | params.isOpen = false |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Proceed to take and measure a still image capture. |
| 175 | */ |
| 176 | internal fun cameraXTakePicture( |
| 177 | activity: MainActivity, |
| 178 | params: CameraParams, |
| 179 | testConfig: TestConfig |
| 180 | ) { |
| 181 | if (params.cameraXLifecycle.isFinished()) { |
| 182 | cameraXAbort(activity, params, testConfig) |
| 183 | return |
| 184 | } |
| 185 | |
| 186 | logd("CameraX TakePicture: capture start.") |
| 187 | |
| 188 | // Pause in multi-captures to make sure HDR routines don't get overloaded |
| 189 | logd("CameraX TakePicture. Pausing for " + |
| 190 | PrefHelper.getPreviewBuffer(activity) + "ms to let preview run.") |
| 191 | params.timer.previewFillStart = System.currentTimeMillis() |
| 192 | Thread.sleep(PrefHelper.getPreviewBuffer(activity)) |
| 193 | params.timer.previewFillEnd = System.currentTimeMillis() |
| 194 | |
| 195 | params.timer.captureStart = System.currentTimeMillis() |
| 196 | params.timer.autofocusStart = System.currentTimeMillis() |
| 197 | params.timer.autofocusEnd = System.currentTimeMillis() |
| 198 | |
| 199 | logd("Capture timer started: " + params.timer.captureStart) |
| 200 | activity.runOnUiThread { |
| 201 | params.cameraXImageCaptureUseCase |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame^] | 202 | .takePicture( |
| 203 | CameraXExecutors.mainThreadExecutor(), |
| 204 | CameraXImageAvailableListener(activity, params, testConfig) |
| 205 | ) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 206 | } |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * An abort request has been received. Abandon everything |
| 211 | */ |
| 212 | internal fun cameraXAbort(activity: MainActivity, params: CameraParams, testConfig: TestConfig) { |
| 213 | closeCameraX(activity, params, testConfig) |
| 214 | return |
| 215 | } |
| 216 | |
| 217 | /** |
| 218 | * Try to determine if a SurfaceTexture is released. |
| 219 | * |
| 220 | * Prior to SDK 26 there was not built in mechanism for this. This method relies on expected |
| 221 | * exceptions being thrown if a released SurfaceTexture is updated. |
| 222 | */ |
| 223 | private fun isCameraSurfaceTextureReleased(texture: SurfaceTexture): Boolean { |
| 224 | var released = false |
| 225 | |
| 226 | if (26 <= android.os.Build.VERSION.SDK_INT) { |
| 227 | released = texture.isReleased |
| 228 | } else { |
| 229 | // WARNING: This relies on some implementation details of the SurfaceTexture native code. |
| 230 | // If the SurfaceTexture is released, we should get a RuntimeException. If not, we should |
| 231 | // get an IllegalStateException since we are not in the same EGL context as the camera. |
| 232 | var exception: Exception? = null |
| 233 | try { |
| 234 | texture.updateTexImage() |
| 235 | } catch (e: IllegalStateException) { |
| 236 | logd("in isCameraSurfaceTextureReleased: IllegalStateException: " + e.message) |
| 237 | exception = e |
| 238 | released = false |
| 239 | } catch (e: RuntimeException) { |
| 240 | logd("in isCameraSurfaceTextureReleased: RuntimeException: " + e.message) |
| 241 | exception = e |
| 242 | released = true |
| 243 | } |
| 244 | |
| 245 | if (!released && exception == null) { |
| 246 | throw RuntimeException("Unable to determine if SurfaceTexture is released") |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | logd("The camera texture is: " + if (released) "RELEASED" else "NOT RELEASED") |
| 251 | |
| 252 | return released |
| 253 | } |
| 254 | |
| 255 | /** |
| 256 | * Setup the Camera X preview use case |
| 257 | */ |
| 258 | private fun cameraXPreviewUseCaseBuilder( |
| 259 | id: String, |
| 260 | focusMode: FocusMode, |
| 261 | deviceStateCallback: CameraDevice.StateCallback, |
| 262 | sessionCaptureStateCallback: CameraCaptureSession.StateCallback |
| 263 | ): PreviewConfig { |
| 264 | |
| 265 | // TODO: As of 0.3.0 CameraX can only use front and back cameras. Update in future versions |
| 266 | val cameraXcameraID = if (id.equals("0")) CameraX.LensFacing.BACK else CameraX.LensFacing.FRONT |
| 267 | val configBuilder = PreviewConfig.Builder() |
| 268 | .setLensFacing(cameraXcameraID) |
| 269 | Camera2Config.Extender(configBuilder) |
| 270 | .setDeviceStateCallback(deviceStateCallback) |
| 271 | .setSessionStateCallback(sessionCaptureStateCallback) |
| 272 | .setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, |
| 273 | when (focusMode) { |
| 274 | FocusMode.AUTO -> CaptureRequest.CONTROL_AF_MODE_AUTO |
| 275 | FocusMode.CONTINUOUS -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE |
| 276 | FocusMode.FIXED -> CaptureRequest.CONTROL_AF_MODE_AUTO |
| 277 | }) |
| 278 | return configBuilder.build() |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * Setup the Camera X image capture use case |
| 283 | */ |
| 284 | private fun cameraXImageCaptureUseCaseBuilder( |
| 285 | id: String, |
| 286 | focusMode: FocusMode, |
| 287 | deviceStateCallback: |
| 288 | CameraDevice.StateCallback, |
| 289 | sessionCaptureCallback: CameraCaptureSession.CaptureCallback |
| 290 | ): ImageCaptureConfig { |
| 291 | |
| 292 | // TODO: As of 0.3.0 CameraX can only use front and back cameras. Update in future versions |
| 293 | val cameraXcameraID = if (id.equals("0")) CameraX.LensFacing.BACK else CameraX.LensFacing.FRONT |
| 294 | |
| 295 | val configBuilder = ImageCaptureConfig.Builder() |
| 296 | .setLensFacing(cameraXcameraID) |
| 297 | .setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY) |
| 298 | Camera2Config.Extender(configBuilder) |
| 299 | .setDeviceStateCallback(deviceStateCallback) |
| 300 | .setSessionCaptureCallback(sessionCaptureCallback) |
| 301 | .setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, |
| 302 | when (focusMode) { |
| 303 | FocusMode.AUTO -> CaptureRequest.CONTROL_AF_MODE_AUTO |
| 304 | FocusMode.CONTINUOUS -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE |
| 305 | FocusMode.FIXED -> CaptureRequest.CONTROL_AF_MODE_AUTO |
| 306 | }) |
| 307 | |
| 308 | return configBuilder.build() |
| 309 | } |