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