Skip to content

Commit c3d3de4

Browse files
committed
fix(core): camera: dispatch task with mutex to a specific dispatcher
1 parent 99146cd commit c3d3de4

File tree

3 files changed

+296
-242
lines changed

3 files changed

+296
-242
lines changed

core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/CameraSource.kt

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.asStateFlow
4343
import kotlinx.coroutines.launch
4444
import kotlinx.coroutines.sync.Mutex
4545
import kotlinx.coroutines.sync.withLock
46+
import kotlinx.coroutines.withContext
4647

4748
/**
4849
* Camera source implementation.
@@ -54,7 +55,8 @@ internal class CameraSource(
5455
override val cameraId: String,
5556
dispatcherProvider: CameraDispatcherProvider,
5657
) : ICameraSourceInternal, ICameraSource, AbstractPreviewableSource() {
57-
private val coroutineScope = CoroutineScope(dispatcherProvider.default)
58+
private val defaultDispatcher = dispatcherProvider.default
59+
private val coroutineScope = CoroutineScope(defaultDispatcher)
5860

5961
private val manager = context.cameraManager
6062
private val characteristics = manager.getCameraCharacteristics(cameraId)
@@ -197,39 +199,51 @@ internal class CameraSource(
197199
}
198200

199201
@RequiresPermission(Manifest.permission.CAMERA)
200-
override suspend fun startPreview(): Unit = previewMutex.withLock {
201-
startPreviewUnsafe()
202+
override suspend fun startPreview() {
203+
withContext(defaultDispatcher) {
204+
previewMutex.withLock {
205+
startPreviewUnsafe()
206+
}
207+
}
202208
}
203209

204210
/**
205211
* Starts video preview on [previewSurface].
206212
*/
207213
@RequiresPermission(Manifest.permission.CAMERA)
208-
override suspend fun startPreview(previewSurface: Surface) = previewMutex.withLock {
209-
if (isPreviewingFlow.value) {
210-
Logger.w(TAG, "Camera is already previewing")
211-
return
214+
override suspend fun startPreview(previewSurface: Surface) {
215+
withContext(defaultDispatcher) {
216+
previewMutex.withLock {
217+
if (isPreviewingFlow.value) {
218+
Logger.w(TAG, "Camera is already previewing")
219+
return@withContext
220+
}
221+
setPreview(previewSurface)
222+
startPreviewUnsafe()
223+
}
212224
}
213-
setPreview(previewSurface)
214-
startPreviewUnsafe()
215225
}
216226

217227
@SuppressLint("MissingPermission")
218-
override suspend fun stopPreview() = previewMutex.withLock {
219-
Logger.d(TAG, "Stopping preview")
220-
if (!isPreviewingFlow.value) {
221-
Logger.w(TAG, "Camera is not previewing")
222-
return
223-
}
228+
override suspend fun stopPreview() {
229+
withContext(defaultDispatcher) {
230+
previewMutex.withLock {
231+
Logger.d(TAG, "Stopping preview")
232+
if (!isPreviewingFlow.value) {
233+
Logger.w(TAG, "Camera is not previewing")
234+
return@withContext
235+
}
224236

225-
try {
226-
executeIfCameraPermission {
227-
controller.removeTarget(PREVIEW_NAME)
237+
try {
238+
executeIfCameraPermission {
239+
controller.removeTarget(PREVIEW_NAME)
240+
}
241+
} catch (t: Throwable) {
242+
Logger.w(TAG, "Failed to stop preview: $t")
243+
} finally {
244+
_isPreviewingFlow.emit(false)
245+
}
228246
}
229-
} catch (t: Throwable) {
230-
Logger.w(TAG, "Failed to stop preview: $t")
231-
} finally {
232-
_isPreviewingFlow.emit(false)
233247
}
234248
}
235249

core/src/main/java/io/github/thibaultbee/streampack/core/elements/sources/video/camera/controllers/CameraController.kt

Lines changed: 75 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.asStateFlow
3636
import kotlinx.coroutines.launch
3737
import kotlinx.coroutines.sync.Mutex
3838
import kotlinx.coroutines.sync.withLock
39+
import kotlinx.coroutines.withContext
3940

4041
/**
4142
* Encapsulates device controller and session controller.
@@ -49,7 +50,8 @@ internal class CameraController(
4950
) {
5051
private val sessionCompat = CameraCaptureSessionCompatBuilder.build(dispatcherProvider)
5152

52-
private val coroutineScope = CoroutineScope(dispatcherProvider.default)
53+
private val defaultDispatcher = dispatcherProvider.default
54+
private val coroutineScope = CoroutineScope(defaultDispatcher)
5355
private var isActiveJob: Job? = null
5456

5557
private var deviceController: CameraDeviceController? = null
@@ -70,26 +72,26 @@ internal class CameraController(
7072
/**
7173
* Whether the current capture session has the given output.
7274
*/
73-
suspend fun hasOutput(output: CameraSurface): Boolean {
74-
return controllerMutex.withLock { outputs.values.contains(output) }
75+
suspend fun hasOutput(output: CameraSurface) = withContext(defaultDispatcher) {
76+
controllerMutex.withLock { outputs.values.contains(output) }
7577
}
7678

7779
/**
7880
* Whether the current capture session has the given output.
7981
*
8082
* @param name The name of the output to check
8183
*/
82-
suspend fun hasOutput(name: String): Boolean {
83-
return controllerMutex.withLock { outputs.keys.contains(name) }
84+
suspend fun hasOutput(name: String) = withContext(defaultDispatcher) {
85+
controllerMutex.withLock { outputs.keys.contains(name) }
8486
}
8587

8688
/**
8789
* Gets an output from the current capture session.
8890
*
8991
* @param name The name of the output to get
9092
*/
91-
suspend fun getOutput(name: String): CameraSurface? {
92-
return controllerMutex.withLock { outputs[name] }
93+
suspend fun getOutput(name: String) = withContext(defaultDispatcher) {
94+
controllerMutex.withLock { outputs[name] }
9395
}
9496

9597
/**
@@ -101,13 +103,15 @@ internal class CameraController(
101103
@RequiresPermission(Manifest.permission.CAMERA)
102104
suspend fun addOutput(output: CameraSurface) {
103105
require(output.surface.isValid) { "Output is invalid: $output" }
104-
controllerMutex.withLock {
105-
if (outputs.values.contains(output)) {
106-
return
107-
}
108-
outputs[output.name] = output
109-
if (isActiveFlow.value) {
110-
restartSessionUnsafe()
106+
withContext(defaultDispatcher) {
107+
controllerMutex.withLock {
108+
if (outputs.values.contains(output)) {
109+
return@withContext
110+
}
111+
outputs[output.name] = output
112+
if (isActiveFlow.value) {
113+
restartSessionUnsafe()
114+
}
111115
}
112116
}
113117
}
@@ -119,11 +123,13 @@ internal class CameraController(
119123
*/
120124
@RequiresPermission(Manifest.permission.CAMERA)
121125
suspend fun removeOutput(name: String) {
122-
controllerMutex.withLock {
123-
val needRestart = outputs.containsKey(name) && isActiveFlow.value
124-
outputs.remove(name) != null
125-
if (needRestart) {
126-
restartSessionUnsafe()
126+
withContext(defaultDispatcher) {
127+
controllerMutex.withLock {
128+
val needRestart = outputs.containsKey(name) && isActiveFlow.value
129+
outputs.remove(name) != null
130+
if (needRestart) {
131+
restartSessionUnsafe()
132+
}
127133
}
128134
}
129135
}
@@ -195,18 +201,6 @@ internal class CameraController(
195201
}
196202
}
197203

198-
/**
199-
* Restarts the current capture session.
200-
*
201-
* The current capture session is closed and a new one is created with the same outputs.
202-
*/
203-
@RequiresPermission(Manifest.permission.CAMERA)
204-
private suspend fun restartSession() {
205-
controllerMutex.withLock {
206-
restartSessionUnsafe()
207-
}
208-
}
209-
210204
/**
211205
* Restarts the current capture session.
212206
*
@@ -241,8 +235,8 @@ internal class CameraController(
241235
* @return true if the target has been added, false otherwise
242236
*/
243237
@RequiresPermission(Manifest.permission.CAMERA)
244-
suspend fun addTarget(name: String): Boolean {
245-
return controllerMutex.withLock {
238+
suspend fun addTarget(name: String) = withContext(defaultDispatcher) {
239+
controllerMutex.withLock {
246240
val sessionController = getSessionController()
247241
sessionController.addTarget(name)
248242
}
@@ -255,8 +249,8 @@ internal class CameraController(
255249
* @return true if the target has been added, false otherwise
256250
*/
257251
@RequiresPermission(Manifest.permission.CAMERA)
258-
suspend fun addTarget(target: CameraSurface): Boolean {
259-
return controllerMutex.withLock {
252+
suspend fun addTarget(target: CameraSurface) = withContext(defaultDispatcher) {
253+
controllerMutex.withLock {
260254
val sessionController = getSessionController()
261255
sessionController.addTarget(target)
262256
}
@@ -269,8 +263,8 @@ internal class CameraController(
269263
* @return true if the targets have been added, false otherwise
270264
*/
271265
@RequiresPermission(Manifest.permission.CAMERA)
272-
suspend fun addTargets(targets: List<CameraSurface>): Boolean {
273-
return controllerMutex.withLock {
266+
suspend fun addTargets(targets: List<CameraSurface>) = withContext(defaultDispatcher) {
267+
controllerMutex.withLock {
274268
val sessionController = getSessionController()
275269
sessionController.addTargets(targets)
276270
}
@@ -283,11 +277,13 @@ internal class CameraController(
283277
*/
284278
@RequiresPermission(Manifest.permission.CAMERA)
285279
suspend fun removeTarget(target: CameraSurface) {
286-
controllerMutex.withLock {
287-
val sessionController = getSessionController()
288-
sessionController.removeTarget(target)
289-
if (sessionController.isEmpty()) {
290-
closeControllers()
280+
withContext(defaultDispatcher) {
281+
controllerMutex.withLock {
282+
val sessionController = getSessionController()
283+
sessionController.removeTarget(target)
284+
if (sessionController.isEmpty()) {
285+
closeControllers()
286+
}
291287
}
292288
}
293289
}
@@ -299,11 +295,13 @@ internal class CameraController(
299295
*/
300296
@RequiresPermission(Manifest.permission.CAMERA)
301297
suspend fun removeTarget(name: String) {
302-
controllerMutex.withLock {
303-
val sessionController = getSessionController()
304-
sessionController.removeTarget(name)
305-
if (sessionController.isEmpty()) {
306-
closeControllers()
298+
withContext(defaultDispatcher) {
299+
controllerMutex.withLock {
300+
val sessionController = getSessionController()
301+
sessionController.removeTarget(name)
302+
if (sessionController.isEmpty()) {
303+
closeControllers()
304+
}
307305
}
308306
}
309307
}
@@ -335,19 +333,21 @@ internal class CameraController(
335333
* @param fpsRange The fps range
336334
*/
337335
suspend fun setFps(fps: Int) {
338-
controllerMutex.withLock {
339-
if (this.fps == fps) {
340-
return
341-
}
336+
withContext(defaultDispatcher) {
337+
controllerMutex.withLock {
338+
if (this@CameraController.fps == fps) {
339+
return@withContext
340+
}
342341

343-
this.fps = fps
342+
this@CameraController.fps = fps
344343

345-
if (isActiveFlow.value) {
346-
val range = fpsRange
347-
val minFrameDuration = 1_000_000_000 / range.upper.toLong()
348-
setSetting(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, range)
349-
setSetting(CaptureRequest.SENSOR_FRAME_DURATION, minFrameDuration)
350-
setRepeatingSession()
344+
if (isActiveFlow.value) {
345+
val range = fpsRange
346+
val minFrameDuration = 1_000_000_000 / range.upper.toLong()
347+
setSetting(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, range)
348+
setSetting(CaptureRequest.SENSOR_FRAME_DURATION, minFrameDuration)
349+
setRepeatingSession()
350+
}
351351
}
352352
}
353353
}
@@ -359,17 +359,19 @@ internal class CameraController(
359359
*/
360360
@RequiresPermission(Manifest.permission.CAMERA)
361361
suspend fun setDynamicRangeProfile(dynamicRangeProfile: DynamicRangeProfile) {
362-
val needRestart = controllerMutex.withLock {
363-
if (this.dynamicRangeProfile == dynamicRangeProfile) {
364-
return
365-
}
366-
Logger.d(TAG, "Setting dynamic range profile to $dynamicRangeProfile")
362+
withContext(defaultDispatcher) {
363+
controllerMutex.withLock {
364+
if (this@CameraController.dynamicRangeProfile == dynamicRangeProfile) {
365+
return@withContext
366+
}
367+
Logger.d(TAG, "Setting dynamic range profile to $dynamicRangeProfile")
367368

368-
this.dynamicRangeProfile = dynamicRangeProfile
369-
isActiveFlow.value
370-
}
371-
if (needRestart) {
372-
restartSession()
369+
this@CameraController.dynamicRangeProfile = dynamicRangeProfile
370+
371+
if (isActiveFlow.value) {
372+
restartSessionUnsafe()
373+
}
374+
}
373375
}
374376
}
375377

@@ -398,9 +400,11 @@ internal class CameraController(
398400
}
399401

400402
suspend fun release() {
401-
controllerMutex.withLock {
402-
closeControllers()
403-
sessionController = null
403+
withContext(defaultDispatcher) {
404+
controllerMutex.withLock {
405+
closeControllers()
406+
sessionController = null
407+
}
404408
}
405409
outputs.clear()
406410
sessionCompat.release()

0 commit comments

Comments
 (0)