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 |
Scott Nien | 240ad8f | 2019-10-15 11:50:35 +0800 | [diff] [blame] | 22 | import android.util.Log |
Xi Zhang | d948fa8 | 2019-11-04 13:17:05 -0800 | [diff] [blame] | 23 | import android.util.Size |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 24 | import android.view.ViewGroup |
weginlee | 5a8d821 | 2019-10-23 19:11:33 +0800 | [diff] [blame] | 25 | import androidx.annotation.experimental.UseExperimental |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 26 | import androidx.camera.camera2.Camera2Config |
weginlee | 5a8d821 | 2019-10-23 19:11:33 +0800 | [diff] [blame] | 27 | import androidx.camera.camera2.ExperimentalCamera2Interop |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 28 | import androidx.camera.core.CameraSelector |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 29 | import androidx.camera.core.ImageCapture |
| 30 | import androidx.camera.core.ImageCaptureConfig |
leo huang | 0e830e9 | 2019-10-24 16:10:40 +0800 | [diff] [blame] | 31 | import androidx.camera.core.LensFacing |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 32 | import androidx.camera.core.Preview |
Xi Zhang | d948fa8 | 2019-11-04 13:17:05 -0800 | [diff] [blame] | 33 | import androidx.camera.core.PreviewSurfaceProviders |
| 34 | import androidx.camera.core.PreviewSurfaceProviders.createSurfaceTextureProvider |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 35 | import androidx.camera.core.impl.utils.executor.CameraXExecutors |
| 36 | import androidx.camera.integration.antelope.CameraParams |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 37 | import androidx.camera.integration.antelope.CameraXImageAvailableListener |
| 38 | import androidx.camera.integration.antelope.CustomLifecycle |
| 39 | import androidx.camera.integration.antelope.FocusMode |
| 40 | import androidx.camera.integration.antelope.MainActivity |
| 41 | import androidx.camera.integration.antelope.MainActivity.Companion.logd |
| 42 | import androidx.camera.integration.antelope.PrefHelper |
| 43 | import androidx.camera.integration.antelope.TestConfig |
| 44 | import androidx.camera.integration.antelope.TestType |
Franklin Wu | 4d89be1 | 2019-10-23 15:38:43 -0700 | [diff] [blame] | 45 | import androidx.camera.lifecycle.LifecycleCameraProvider |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 46 | import androidx.lifecycle.LifecycleOwner |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 47 | |
| 48 | /** |
| 49 | * Opens the camera using the Camera X API and starts the open counter. The open call will complete |
| 50 | * in the DeviceStateCallback asynchronously. For switch tests, the camera id will be swizzling so |
| 51 | * the original camera id is saved. |
| 52 | * |
| 53 | * CameraX manages its lifecycle internally, for the purpose of repeated testing, Antelope uses a |
| 54 | * custom lifecycle to allow for starting new tests cleanly which is started here. |
| 55 | * |
| 56 | * All the needed Cmaera X use cases should be bound before starting the lifecycle. Depending on |
| 57 | * the test, bind either the preview case, or both the preview and image capture case. |
| 58 | */ |
| 59 | internal fun cameraXOpenCamera( |
| 60 | activity: MainActivity, |
| 61 | params: CameraParams, |
| 62 | testConfig: TestConfig |
| 63 | ) { |
| 64 | |
| 65 | try { |
| 66 | // TODO make the switch test methodology more robust and handle physical cameras |
| 67 | // Currently we swap out the ids behind the scenes |
| 68 | // This requires to save the actual camera id for after the test |
| 69 | if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) || |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 70 | (testConfig.currentRunningTest == TestType.MULTI_SWITCH) |
| 71 | ) { |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 72 | testConfig.switchTestRealCameraId = params.id // Save the actual camera ID |
| 73 | params.id = testConfig.switchTestCurrentCamera |
| 74 | } |
| 75 | |
| 76 | params.cameraXDeviceStateCallback = CameraXDeviceStateCallback(params, activity, testConfig) |
| 77 | params.cameraXPreviewSessionStateCallback = |
| 78 | CameraXPreviewSessionStateCallback(activity, params, testConfig) |
| 79 | |
| 80 | if (params.cameraXDeviceStateCallback != null && |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 81 | params.cameraXPreviewSessionStateCallback != null |
| 82 | ) { |
| 83 | params.cameraXPreviewBuilder = |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 84 | cameraXPreviewUseCaseBuilder( |
| 85 | testConfig.focusMode, |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 86 | params.cameraXDeviceStateCallback!!, |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 87 | params.cameraXPreviewSessionStateCallback!! |
| 88 | ) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 89 | } |
| 90 | |
| 91 | if (!params.cameraXLifecycle.isFinished()) { |
| 92 | logd("Lifecycle not finished, finishing it.") |
| 93 | params.cameraXLifecycle.pauseAndStop() |
| 94 | params.cameraXLifecycle.finish() |
| 95 | } |
| 96 | params.cameraXLifecycle = CustomLifecycle() |
| 97 | |
| 98 | val lifecycleOwner: LifecycleOwner = params.cameraXLifecycle |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 99 | val previewUseCase = params.cameraXPreviewBuilder.build() |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 100 | |
| 101 | // Set preview to observe the surface texture |
| 102 | activity.runOnUiThread { |
Xi Zhang | d948fa8 | 2019-11-04 13:17:05 -0800 | [diff] [blame] | 103 | previewUseCase.previewSurfaceCallback = createSurfaceTextureProvider( |
| 104 | object : PreviewSurfaceProviders.SurfaceTextureCallback { |
Xi Zhang | a3762c5 | 2019-10-22 08:09:32 -0700 | [diff] [blame] | 105 | |
Xi Zhang | d948fa8 | 2019-11-04 13:17:05 -0800 | [diff] [blame] | 106 | override fun onSafeToRelease(surfaceTexture: SurfaceTexture) { |
| 107 | surfaceTexture.release() |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 108 | } |
Xi Zhang | d948fa8 | 2019-11-04 13:17:05 -0800 | [diff] [blame] | 109 | |
| 110 | override fun onSurfaceTextureReady( |
| 111 | surfaceTexture: SurfaceTexture, |
| 112 | resolution: Size |
| 113 | ) { |
| 114 | if (!isCameraSurfaceTextureReleased(surfaceTexture)) { |
| 115 | val viewGroup = params.cameraXPreviewTexture?.parent as ViewGroup |
| 116 | viewGroup.removeView(params.cameraXPreviewTexture) |
| 117 | viewGroup.addView(params.cameraXPreviewTexture) |
| 118 | params.cameraXPreviewTexture?.surfaceTexture = surfaceTexture |
| 119 | } |
| 120 | } |
| 121 | }) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 122 | } |
| 123 | |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 124 | // TODO: As of 0.3.0 CameraX can only use front and back cameras. |
| 125 | // Update in future versions |
| 126 | val cameraXcameraID = if (params.id.equals("0")) LensFacing.BACK else LensFacing.FRONT |
| 127 | val cameraSelector = CameraSelector.Builder().requireLensFacing(cameraXcameraID).build() |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 128 | when (testConfig.currentRunningTest) { |
| 129 | // Only the preview is required |
| 130 | TestType.PREVIEW, |
| 131 | TestType.SWITCH_CAMERA, |
| 132 | TestType.MULTI_SWITCH -> { |
| 133 | params.timer.openStart = System.currentTimeMillis() |
| 134 | activity.runOnUiThread { |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 135 | LifecycleCameraProvider.bindToLifecycle( |
| 136 | lifecycleOwner, cameraSelector, |
| 137 | previewUseCase |
| 138 | ) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 139 | params.cameraXLifecycle.start() |
| 140 | } |
| 141 | } |
| 142 | else -> { |
| 143 | // Both preview and image capture are needed |
| 144 | params.cameraXCaptureSessionCallback = |
| 145 | CameraXCaptureSessionCallback(activity, params, testConfig) |
| 146 | |
| 147 | if (params.cameraXDeviceStateCallback != null && |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 148 | params.cameraXCaptureSessionCallback != null |
| 149 | ) { |
| 150 | params.cameraXCaptureBuilder = |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 151 | cameraXImageCaptureUseCaseBuilder( |
| 152 | testConfig.focusMode, |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 153 | params.cameraXDeviceStateCallback!!, |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 154 | params.cameraXCaptureSessionCallback!! |
| 155 | ) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 156 | } |
| 157 | |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 158 | params.cameraXImageCaptureUseCase = params.cameraXCaptureBuilder.build() |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 159 | |
| 160 | params.timer.openStart = System.currentTimeMillis() |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 161 | |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 162 | activity.runOnUiThread { |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 163 | LifecycleCameraProvider.bindToLifecycle( |
| 164 | lifecycleOwner, cameraSelector, |
| 165 | previewUseCase, params.cameraXImageCaptureUseCase |
| 166 | ) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 167 | params.cameraXLifecycle.start() |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | } catch (e: Exception) { |
| 172 | MainActivity.logd("cameraXOpenCamera exception: " + params.id) |
| 173 | e.printStackTrace() |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * End Camera X custom lifecycle, unbind use cases, and start timing the camera close. |
| 179 | */ |
| 180 | internal fun closeCameraX(activity: MainActivity, params: CameraParams, testConfig: TestConfig) { |
| 181 | logd("In closecameraX, camera: " + params.id + ", test: " + testConfig.currentRunningTest) |
| 182 | |
| 183 | params.timer.cameraCloseStart = System.currentTimeMillis() |
| 184 | |
| 185 | if (!params.cameraXLifecycle.isFinished()) { |
| 186 | params.cameraXLifecycle.pauseAndStop() |
| 187 | params.cameraXLifecycle.finish() |
| 188 | |
| 189 | // CameraX calls need to be on the main thread |
Scott Nien | 240ad8f | 2019-10-15 11:50:35 +0800 | [diff] [blame] | 190 | activity.runOnUiThread { |
Franklin Wu | 4d89be1 | 2019-10-23 15:38:43 -0700 | [diff] [blame] | 191 | LifecycleCameraProvider.unbindAll() |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 192 | } |
| 193 | } |
| 194 | if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) || |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 195 | (testConfig.currentRunningTest == TestType.MULTI_SWITCH) |
| 196 | ) { |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 197 | params.id = testConfig.switchTestRealCameraId // Restore the actual camera ID |
| 198 | } |
| 199 | |
| 200 | params.isOpen = false |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * Proceed to take and measure a still image capture. |
| 205 | */ |
| 206 | internal fun cameraXTakePicture( |
| 207 | activity: MainActivity, |
| 208 | params: CameraParams, |
| 209 | testConfig: TestConfig |
| 210 | ) { |
| 211 | if (params.cameraXLifecycle.isFinished()) { |
| 212 | cameraXAbort(activity, params, testConfig) |
| 213 | return |
| 214 | } |
| 215 | |
| 216 | logd("CameraX TakePicture: capture start.") |
| 217 | |
| 218 | // Pause in multi-captures to make sure HDR routines don't get overloaded |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 219 | logd( |
| 220 | "CameraX TakePicture. Pausing for " + |
| 221 | PrefHelper.getPreviewBuffer(activity) + "ms to let preview run." |
| 222 | ) |
Xi Zhang | a3762c5 | 2019-10-22 08:09:32 -0700 | [diff] [blame] | 223 | |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 224 | params.timer.previewFillStart = System.currentTimeMillis() |
| 225 | Thread.sleep(PrefHelper.getPreviewBuffer(activity)) |
| 226 | params.timer.previewFillEnd = System.currentTimeMillis() |
| 227 | |
| 228 | params.timer.captureStart = System.currentTimeMillis() |
| 229 | params.timer.autofocusStart = System.currentTimeMillis() |
| 230 | params.timer.autofocusEnd = System.currentTimeMillis() |
| 231 | |
| 232 | logd("Capture timer started: " + params.timer.captureStart) |
| 233 | activity.runOnUiThread { |
| 234 | params.cameraXImageCaptureUseCase |
WenHung_Teng | 5cc433b | 2019-08-26 11:40:45 +0800 | [diff] [blame] | 235 | .takePicture( |
| 236 | CameraXExecutors.mainThreadExecutor(), |
| 237 | CameraXImageAvailableListener(activity, params, testConfig) |
| 238 | ) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 239 | } |
| 240 | } |
| 241 | |
| 242 | /** |
| 243 | * An abort request has been received. Abandon everything |
| 244 | */ |
| 245 | internal fun cameraXAbort(activity: MainActivity, params: CameraParams, testConfig: TestConfig) { |
| 246 | closeCameraX(activity, params, testConfig) |
| 247 | return |
| 248 | } |
| 249 | |
| 250 | /** |
| 251 | * Try to determine if a SurfaceTexture is released. |
| 252 | * |
| 253 | * Prior to SDK 26 there was not built in mechanism for this. This method relies on expected |
| 254 | * exceptions being thrown if a released SurfaceTexture is updated. |
| 255 | */ |
| 256 | private fun isCameraSurfaceTextureReleased(texture: SurfaceTexture): Boolean { |
| 257 | var released = false |
| 258 | |
| 259 | if (26 <= android.os.Build.VERSION.SDK_INT) { |
| 260 | released = texture.isReleased |
| 261 | } else { |
| 262 | // WARNING: This relies on some implementation details of the SurfaceTexture native code. |
| 263 | // If the SurfaceTexture is released, we should get a RuntimeException. If not, we should |
| 264 | // get an IllegalStateException since we are not in the same EGL context as the camera. |
| 265 | var exception: Exception? = null |
| 266 | try { |
| 267 | texture.updateTexImage() |
| 268 | } catch (e: IllegalStateException) { |
| 269 | logd("in isCameraSurfaceTextureReleased: IllegalStateException: " + e.message) |
| 270 | exception = e |
| 271 | released = false |
| 272 | } catch (e: RuntimeException) { |
| 273 | logd("in isCameraSurfaceTextureReleased: RuntimeException: " + e.message) |
| 274 | exception = e |
| 275 | released = true |
| 276 | } |
| 277 | |
| 278 | if (!released && exception == null) { |
| 279 | throw RuntimeException("Unable to determine if SurfaceTexture is released") |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | logd("The camera texture is: " + if (released) "RELEASED" else "NOT RELEASED") |
| 284 | |
| 285 | return released |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * Setup the Camera X preview use case |
| 290 | */ |
weginlee | 5a8d821 | 2019-10-23 19:11:33 +0800 | [diff] [blame] | 291 | @UseExperimental(markerClass = ExperimentalCamera2Interop::class) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 292 | private fun cameraXPreviewUseCaseBuilder( |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 293 | focusMode: FocusMode, |
| 294 | deviceStateCallback: CameraDevice.StateCallback, |
| 295 | sessionCaptureStateCallback: CameraCaptureSession.StateCallback |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 296 | ): Preview.Builder { |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 297 | |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 298 | val configBuilder = Preview.Builder() |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 299 | Camera2Config.Extender(configBuilder) |
| 300 | .setDeviceStateCallback(deviceStateCallback) |
| 301 | .setSessionStateCallback(sessionCaptureStateCallback) |
Scott Nien | 240ad8f | 2019-10-15 11:50:35 +0800 | [diff] [blame] | 302 | // TODO(b/142915154): Enables focusMode when CameraX support direct AF mode setting. |
| 303 | |
| 304 | // Prints a log to suppress "fix Parameter 'focusMode' is never used" build error" |
| 305 | Log.d("Antelope", "focusMode($focusMode) Not enabled.") |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 306 | return configBuilder |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 307 | } |
| 308 | |
| 309 | /** |
| 310 | * Setup the Camera X image capture use case |
| 311 | */ |
weginlee | 5a8d821 | 2019-10-23 19:11:33 +0800 | [diff] [blame] | 312 | @UseExperimental(markerClass = ExperimentalCamera2Interop::class) |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 313 | private fun cameraXImageCaptureUseCaseBuilder( |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 314 | focusMode: FocusMode, |
| 315 | deviceStateCallback: |
| 316 | CameraDevice.StateCallback, |
| 317 | sessionCaptureCallback: CameraCaptureSession.CaptureCallback |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 318 | ): ImageCaptureConfig.Builder { |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 319 | |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 320 | val configBuilder = ImageCaptureConfig.Builder() |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 321 | .setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY) |
| 322 | Camera2Config.Extender(configBuilder) |
| 323 | .setDeviceStateCallback(deviceStateCallback) |
| 324 | .setSessionCaptureCallback(sessionCaptureCallback) |
Scott Nien | 240ad8f | 2019-10-15 11:50:35 +0800 | [diff] [blame] | 325 | // TODO(b/142915154): Enables focusMode when CameraX support direct AF mode setting. |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 326 | |
Scott Nien | 240ad8f | 2019-10-15 11:50:35 +0800 | [diff] [blame] | 327 | // Prints a log to suppress "fix Parameter 'focusMode' is never used" build error" |
| 328 | Log.d("Antelope", "focusMode($focusMode) Not enabled.") |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame^] | 329 | return configBuilder |
Emilie Roberts | e128705 | 2019-04-25 15:17:10 -0700 | [diff] [blame] | 330 | } |