1 /* 2 * Copyright 2021 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.camera2.pipe.testing 18 19 import android.content.Context 20 import android.hardware.camera2.CaptureResult 21 import android.media.ImageReader 22 import androidx.camera.camera2.pipe.CameraGraph 23 import androidx.camera.camera2.pipe.CameraId 24 import androidx.camera.camera2.pipe.CameraMetadata 25 import androidx.camera.camera2.pipe.CameraPipe 26 import androidx.camera.camera2.pipe.CameraTimestamp 27 import androidx.camera.camera2.pipe.CaptureSequences.invokeOnRequest 28 import androidx.camera.camera2.pipe.FrameMetadata 29 import androidx.camera.camera2.pipe.FrameNumber 30 import androidx.camera.camera2.pipe.GraphState.GraphStateError 31 import androidx.camera.camera2.pipe.Metadata 32 import androidx.camera.camera2.pipe.OutputId 33 import androidx.camera.camera2.pipe.Request 34 import androidx.camera.camera2.pipe.RequestFailure 35 import androidx.camera.camera2.pipe.StreamId 36 import androidx.camera.camera2.pipe.media.ImageSource 37 import kotlinx.atomicfu.atomic 38 import kotlinx.coroutines.test.TestScope 39 40 /** 41 * This class creates a [CameraPipe] and [CameraGraph] instance using a [FakeCameraBackend]. 42 * 43 * The CameraGraphSimulator is primarily intended to be used within a Kotlin `runTest` block, and 44 * must be created with a coroutine scope by invoking [CameraGraphSimulator.create] and passing the 45 * coroutine scope. This ensures that the created objects, dispatchers, and scopes correctly inherit 46 * from the parent [TestScope]. 47 * 48 * The simulator does not make (many) assumptions about how the simulator will be used, and for this 49 * reason it does not automatically put the underlying graph into a "started" state. In most cases, 50 * the test will need start the [CameraGraph], [simulateCameraStarted], and either configure 51 * surfaces for the [CameraGraph] or call [initializeSurfaces] to put the graph into a state where 52 * it is able to send and simulate interactions with the camera. This mirrors the normal lifecycle 53 * of a [CameraGraph]. Tests using CameraGraphSimulators should also close them after they've 54 * completed their use of the simulator. 55 */ 56 public class CameraGraphSimulator 57 internal constructor( 58 private val cameraMetadata: CameraMetadata, 59 private val cameraController: CameraControllerSimulator, 60 private val fakeImageReaders: FakeImageReaders, 61 private val fakeImageSources: FakeImageSources, 62 private val realCameraGraph: CameraGraph, 63 public val config: CameraGraph.Config, 64 ) : CameraGraph by realCameraGraph, AutoCloseable { 65 66 @Deprecated("CameraGraphSimulator directly implements CameraGraph") 67 public val cameraGraph: CameraGraph 68 get() = this 69 70 public companion object { 71 /** 72 * Create a CameraGraphSimulator using the current [TestScope] provided by a Kotlin 73 * `runTest` block. This will create the [CameraPipe] and [CameraGraph] using the parent 74 * test scope, which helps ensure all long running operations are wrapped up by the time the 75 * test completes and allows the test to provide more fine grained control over the 76 * interactions. 77 */ createnull78 public fun create( 79 testScope: TestScope, 80 testContext: Context, 81 cameraMetadata: CameraMetadata, 82 graphConfig: CameraGraph.Config 83 ): CameraGraphSimulator { 84 val cameraPipeSimulator = 85 CameraPipeSimulator.create(testScope, testContext, listOf(cameraMetadata)) 86 return cameraPipeSimulator.createCameraGraphSimulator(graphConfig) 87 } 88 } 89 90 init { <lambda>null91 check(config.camera == cameraMetadata.camera) { 92 "CameraGraphSimulator must be creating with a camera id that matches the provided " + 93 "cameraMetadata! Received ${config.camera}, but expected " + 94 "${cameraMetadata.camera}" 95 } 96 } 97 98 private val closed = atomic(false) 99 100 private val frameClockNanos = atomic(0L) 101 private val frameCounter = atomic(0L) 102 private val pendingFrameQueue = mutableListOf<FrameSimulator>() 103 private val fakeSurfaces = FakeSurfaces() 104 105 /** Return true if this [CameraGraphSimulator] has been closed. */ 106 public val isClosed: Boolean 107 get() = closed.value 108 closenull109 override fun close() { 110 if (closed.compareAndSet(expect = false, update = true)) { 111 realCameraGraph.close() 112 fakeSurfaces.close() 113 } 114 } 115 simulateCameraStartednull116 public fun simulateCameraStarted() { 117 check(!closed.value) { "Cannot call simulateCameraStarted on $this after close." } 118 cameraController.simulateCameraStarted() 119 } 120 simulateCameraStoppednull121 public fun simulateCameraStopped() { 122 check(!closed.value) { "Cannot call simulateCameraStopped on $this after close." } 123 cameraController.simulateCameraStopped() 124 } 125 simulateCameraModifiednull126 public fun simulateCameraModified() { 127 check(!closed.value) { "Cannot call simulateCameraModified on $this after close." } 128 cameraController.simulateCameraModified() 129 } 130 simulateCameraErrornull131 public fun simulateCameraError(graphStateError: GraphStateError) { 132 check(!closed.value) { "Cannot call simulateCameraError on $this after close." } 133 cameraController.simulateCameraError(graphStateError) 134 } 135 136 /** 137 * Configure all streams in the CameraGraph with fake surfaces that match the size of the first 138 * output stream. 139 */ initializeSurfacesnull140 public fun initializeSurfaces() { 141 check(!closed.value) { 142 "Cannot call simulateFakeSurfaceConfiguration on $this after close." 143 } 144 for (stream in streams.streams) { 145 val imageSource = fakeImageSources[stream.id] 146 if (imageSource != null) { 147 println("Using FakeImageSource ${imageSource.surface} for ${stream.id}") 148 continue 149 } 150 151 val imageReader = fakeImageReaders[stream.id] 152 if (imageReader != null) { 153 println("Using FakeImageReader ${imageReader.surface} for ${stream.id}") 154 realCameraGraph.setSurface(stream.id, imageReader.surface) 155 continue 156 } 157 158 // Pick the smallest output (This matches the behavior of MultiResolutionImageReader) 159 val minOutput = stream.outputs.minBy { it.size.width * it.size.height } 160 val surface = fakeSurfaces.createFakeSurface(minOutput.size) 161 162 println("Using Fake $surface for ${stream.id}") 163 realCameraGraph.setSurface(stream.id, surface) 164 } 165 } 166 simulateNextFramenull167 public fun simulateNextFrame( 168 advanceClockByNanos: Long = 33_366_666 // (2_000_000_000 / (60 / 1.001)) 169 ): FrameSimulator = 170 generateNextFrame().also { 171 val clockNanos = frameClockNanos.addAndGet(advanceClockByNanos) 172 it.simulateStarted(clockNanos) 173 } 174 generateNextFramenull175 private fun generateNextFrame(): FrameSimulator { 176 val captureSequenceProcessor = cameraController.currentCaptureSequenceProcessor 177 check(captureSequenceProcessor != null) { 178 "simulateCameraStarted() must be called before frames can be created!" 179 } 180 181 // This checks the pending frame queue and polls for the next request. If no request is 182 // available it will suspend until the next interaction with the request processor. 183 if (pendingFrameQueue.isEmpty()) { 184 val captureSequence = captureSequenceProcessor.nextCaptureSequence() 185 checkNotNull(captureSequence) { 186 "Failed to simulate a CaptureSequence from $captureSequenceProcessor! Make sure " + 187 "Requests have been submitted or that the repeating Request has been set." 188 } 189 190 // Each sequence is processed as a group, and if a sequence contains multiple requests 191 // the list of requests is processed in order before polling the next sequence. 192 for (request in captureSequence.captureRequestList) { 193 pendingFrameQueue.add(FrameSimulator(request, captureSequence)) 194 } 195 } 196 return pendingFrameQueue.removeAt(0) 197 } 198 199 /** Utility function to simulate the production of a [FakeImage]s for one or more streams. */ simulateImagenull200 public fun simulateImage( 201 streamId: StreamId, 202 imageTimestamp: Long, 203 outputId: OutputId? = null, 204 ) { 205 check(simulateImageInternal(streamId, outputId, imageTimestamp)) { 206 "Failed to simulate image for $streamId on $this!" 207 } 208 } 209 210 /** 211 * Utility function to simulate the production of [FakeImage]s for all outputs on a specific 212 * [request]. Use [simulateImage] to directly control simulation of individual outputs. 213 * [physicalCameraId] should be used to select the correct output id when simulating images from 214 * multi-resolution [ImageReader]s and [ImageSource]s 215 */ simulateImagesnull216 public fun simulateImages( 217 request: Request, 218 imageTimestamp: Long, 219 physicalCameraId: CameraId? = null 220 ) { 221 var imageSimulated = false 222 for (streamId in request.streams) { 223 val outputId = 224 if (physicalCameraId == null) { 225 streams.outputs.single().id 226 } else { 227 streams[streamId]?.outputs?.find { it.camera == physicalCameraId }?.id 228 } 229 val success = simulateImageInternal(streamId, outputId, imageTimestamp) 230 imageSimulated = imageSimulated || success 231 } 232 233 check(imageSimulated) { 234 "Failed to simulate images for $request!" + 235 "No matching FakeImageReaders or FakeImageSources were found." 236 } 237 } 238 simulateImageInternalnull239 private fun simulateImageInternal( 240 streamId: StreamId, 241 outputId: OutputId?, 242 imageTimestamp: Long 243 ): Boolean { 244 val stream = streams[streamId] 245 checkNotNull(stream) { "Cannot simulate an image for invalid $streamId on $this!" } 246 // Prefer to simulate images directly on the imageReader if possible, and then 247 // defer to the imageSource if an imageReader does not exist. 248 val imageReader = fakeImageReaders[streamId] 249 if (imageReader != null) { 250 imageReader.simulateImage(imageTimestamp = imageTimestamp, outputId = outputId) 251 return true 252 } else { 253 val fakeImageSource = fakeImageSources[streamId] 254 if (fakeImageSource != null) { 255 fakeImageSource.simulateImage(timestamp = imageTimestamp, outputId = outputId) 256 return true 257 } 258 } 259 return false 260 } 261 toStringnull262 override fun toString(): String { 263 return "CameraGraphSimulator($realCameraGraph)" 264 } 265 266 /** 267 * A [FrameSimulator] allows a test to synchronously invoke callbacks. A single request can 268 * generate multiple captures (eg, if used as a repeating request). A [FrameSimulator] allows a 269 * test to control exactly one of those captures. This means that a new simulator is created for 270 * each frame, and allows tests to simulate unusual ordering or delays that may appear under 271 * real conditions. 272 */ 273 public inner class FrameSimulator 274 internal constructor( 275 public val request: Request, 276 public val requestSequence: FakeCaptureSequence, 277 ) { 278 private val requestMetadata = requestSequence.requestMetadata[request]!! 279 280 public val frameNumber: FrameNumber = FrameNumber(frameCounter.incrementAndGet()) 281 public var timestampNanos: Long? = null 282 simulateStartednull283 public fun simulateStarted(timestampNanos: Long) { 284 this.timestampNanos = timestampNanos 285 286 requestSequence.invokeOnRequest(requestMetadata) { 287 it.onStarted(requestMetadata, frameNumber, CameraTimestamp(timestampNanos)) 288 } 289 } 290 simulatePartialCaptureResultnull291 public fun simulatePartialCaptureResult( 292 resultMetadata: Map<CaptureResult.Key<*>, Any?>, 293 extraResultMetadata: Map<Metadata.Key<*>, Any?> = emptyMap(), 294 extraMetadata: Map<*, Any?> = emptyMap<Any, Any>() 295 ) { 296 val metadata = 297 createFakeMetadataFor( 298 resultMetadata = resultMetadata, 299 extraResultMetadata = extraResultMetadata, 300 extraMetadata = extraMetadata 301 ) 302 303 requestSequence.invokeOnRequest(requestMetadata) { 304 it.onPartialCaptureResult(requestMetadata, frameNumber, metadata) 305 } 306 } 307 simulateTotalCaptureResultnull308 public fun simulateTotalCaptureResult( 309 resultMetadata: Map<CaptureResult.Key<*>, Any?>, 310 extraResultMetadata: Map<Metadata.Key<*>, Any?> = emptyMap(), 311 extraMetadata: Map<*, Any?> = emptyMap<Any, Any>(), 312 physicalResultMetadata: Map<CameraId, Map<CaptureResult.Key<*>, Any?>> = emptyMap() 313 ) { 314 val metadata = 315 createFakeMetadataFor( 316 resultMetadata = resultMetadata, 317 extraResultMetadata = extraResultMetadata, 318 extraMetadata = extraMetadata 319 ) 320 val frameInfo = 321 FakeFrameInfo( 322 metadata = metadata, 323 requestMetadata, 324 createFakePhysicalMetadata(physicalResultMetadata) 325 ) 326 327 requestSequence.invokeOnRequest(requestMetadata) { 328 it.onTotalCaptureResult(requestMetadata, frameNumber, frameInfo) 329 } 330 } 331 simulateCompletenull332 public fun simulateComplete( 333 resultMetadata: Map<CaptureResult.Key<*>, Any?>, 334 extraResultMetadata: Map<Metadata.Key<*>, Any?> = emptyMap(), 335 extraMetadata: Map<*, Any?> = emptyMap<Any, Any>(), 336 physicalResultMetadata: Map<CameraId, Map<CaptureResult.Key<*>, Any?>> = emptyMap() 337 ) { 338 val metadata = 339 createFakeMetadataFor( 340 resultMetadata = resultMetadata, 341 extraResultMetadata = extraResultMetadata, 342 extraMetadata = extraMetadata 343 ) 344 val frameInfo = 345 FakeFrameInfo( 346 metadata = metadata, 347 requestMetadata, 348 createFakePhysicalMetadata(physicalResultMetadata) 349 ) 350 351 requestSequence.invokeOnRequest(requestMetadata) { 352 it.onComplete(requestMetadata, frameNumber, frameInfo) 353 } 354 } 355 simulateFailurenull356 public fun simulateFailure(requestFailure: RequestFailure) { 357 requestSequence.invokeOnRequest(requestMetadata) { 358 it.onFailed(requestMetadata, frameNumber, requestFailure) 359 } 360 } 361 simulateBufferLossnull362 public fun simulateBufferLoss(streamId: StreamId) { 363 requestSequence.invokeOnRequest(requestMetadata) { 364 it.onBufferLost(requestMetadata, frameNumber, streamId) 365 } 366 } 367 simulateAbortnull368 public fun simulateAbort() { 369 requestSequence.invokeOnRequest(requestMetadata) { it.onAborted(request) } 370 } 371 simulateImagenull372 public fun simulateImage( 373 streamId: StreamId, 374 imageTimestamp: Long? = null, 375 outputId: OutputId? = null, 376 ) { 377 val timestamp = imageTimestamp ?: timestampNanos 378 checkNotNull(timestamp) { 379 "Cannot simulate an image without a timestamp! Provide an " + 380 "imageTimestamp or call simulateStarted before simulateImage." 381 } 382 this@CameraGraphSimulator.simulateImage(streamId, timestamp, outputId) 383 } 384 385 /** 386 * Utility function to simulate the production of [FakeImage]s for all outputs on a Frame. 387 * Use [simulateImage] to directly control simulation of each individual image. 388 */ simulateImagesnull389 public fun simulateImages( 390 imageTimestamp: Long? = null, 391 physicalCameraId: CameraId? = null 392 ) { 393 val timestamp = imageTimestamp ?: timestampNanos 394 checkNotNull(timestamp) { 395 "Cannot simulate an image without a timestamp! Provide an " + 396 "imageTimestamp or call simulateStarted before simulateImage." 397 } 398 this@CameraGraphSimulator.simulateImages(request, timestamp, physicalCameraId) 399 } 400 createFakePhysicalMetadatanull401 private fun createFakePhysicalMetadata( 402 physicalResultMetadata: Map<CameraId, Map<CaptureResult.Key<*>, Any?>> 403 ): Map<CameraId, FrameMetadata> { 404 val resultMap = mutableMapOf<CameraId, FrameMetadata>() 405 for ((k, v) in physicalResultMetadata) { 406 resultMap[k] = createFakeMetadataFor(v) 407 } 408 return resultMap 409 } 410 createFakeMetadataFornull411 private fun createFakeMetadataFor( 412 resultMetadata: Map<CaptureResult.Key<*>, Any?>, 413 extraResultMetadata: Map<Metadata.Key<*>, Any?> = emptyMap(), 414 extraMetadata: Map<*, Any?> = emptyMap<Any, Any>(), 415 ): FakeFrameMetadata = 416 FakeFrameMetadata( 417 camera = cameraMetadata.camera, 418 frameNumber = frameNumber, 419 resultMetadata = resultMetadata.toMap(), 420 extraResultMetadata = extraResultMetadata.toMap(), 421 extraMetadata = extraMetadata.toMap() 422 ) 423 } 424 } 425