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