jfilo-ebay
Committed by GitHub

Allow registration of external cameras implementations (#345)

* Allow registration of external cameras implementations

* Update livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/CameraCapturerUtils.kt

* Update livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/CameraCapturerUtils.kt

* Remove internal and suppress tags to make camera capturers public

* Update CameraEventsDispatchHandler.kt

* spotless

---------

Co-authored-by: davidliu <davidliu@deviange.net>
... ... @@ -36,18 +36,45 @@ import livekit.org.webrtc.VideoCapturer
*/
object CameraCapturerUtils {
private val cameraProviders = mutableListOf<CameraProvider>().apply {
add(createCamera1Provider())
add(createCamera2Provider())
}
/**
* Create a CameraEnumerator based on platform capabilities.
*
* If available, creates an enumerator that uses Camera2. If not,
* a Camera1 enumerator is created.
* Register external camera provider
*/
fun registerCameraProvider(cameraProvider: CameraProvider) {
LKLog.d { "Registering camera provider: Camera version:${cameraProvider.cameraVersion}" }
cameraProviders.add(cameraProvider)
}
/**
* Unregister external camera provider
*/
fun unregisterCameraProvider(cameraProvider: CameraProvider) {
LKLog.d { "Removing camera provider: Camera version:${cameraProvider.cameraVersion}" }
cameraProviders.remove(cameraProvider)
}
/**
* Obtain a CameraEnumerator based on platform capabilities.
*/
fun createCameraEnumerator(context: Context): CameraEnumerator {
return if (Camera2Enumerator.isSupported(context)) {
Camera2Enumerator(context)
} else {
Camera1Enumerator(true)
}
return getCameraProvider(context).provideEnumerator(context)
}
/**
* Create a CameraProvider based on platform capabilities.
*
* Picks CameraProvider of highest available version that is supported on device
*/
private fun getCameraProvider(context: Context): CameraProvider {
return cameraProviders
.sortedByDescending { it.cameraVersion }
.first {
it.isSupported(context)
}
}
/**
... ... @@ -57,8 +84,7 @@ object CameraCapturerUtils {
context: Context,
options: LocalVideoTrackOptions,
): Pair<VideoCapturer, LocalVideoTrackOptions>? {
val cameraEnumerator = createCameraEnumerator(context)
val pair = createCameraCapturer(context, cameraEnumerator, options)
val pair = createCameraCapturer(context, getCameraProvider(context), options)
if (pair == null) {
LKLog.d { "Failed to open camera" }
... ... @@ -69,52 +95,82 @@ object CameraCapturerUtils {
private fun createCameraCapturer(
context: Context,
enumerator: CameraEnumerator,
provider: CameraProvider,
options: LocalVideoTrackOptions,
): Pair<VideoCapturer, LocalVideoTrackOptions>? {
val cameraEventsDispatchHandler = CameraEventsDispatchHandler()
val targetDeviceName = enumerator.findCamera(options.deviceId, options.position) ?: return null
val targetVideoCapturer = enumerator.createCapturer(targetDeviceName, cameraEventsDispatchHandler)
val cameraEnumerator = provider.provideEnumerator(context)
val targetDeviceName = cameraEnumerator.findCamera(options.deviceId, options.position) ?: return null
val targetVideoCapturer = provider.provideCapturer(context, options, cameraEventsDispatchHandler)
// back fill any missing information
val newOptions = options.copy(
deviceId = targetDeviceName,
position = enumerator.getCameraPosition(targetDeviceName),
position = cameraEnumerator.getCameraPosition(targetDeviceName),
)
if (targetVideoCapturer is Camera1Capturer) {
// Cache supported capture formats ahead of time to avoid future camera locks.
Camera1Helper.getSupportedFormats(Camera1Helper.getCameraId(newOptions.deviceId))
return Pair(
Camera1CapturerWithSize(
targetVideoCapturer,
targetDeviceName,
cameraEventsDispatchHandler,
),
newOptions,
)
if (targetVideoCapturer !is VideoCapturerWithSize) {
LKLog.w { "unknown CameraCapturer class: ${targetVideoCapturer.javaClass.canonicalName}. Reported dimensions may be inaccurate." }
}
return Pair(
targetVideoCapturer,
newOptions,
)
}
if (targetVideoCapturer is Camera2Capturer) {
return Pair(
Camera2CapturerWithSize(
targetVideoCapturer,
context.getSystemService(Context.CAMERA_SERVICE) as CameraManager,
targetDeviceName,
cameraEventsDispatchHandler,
),
newOptions,
private fun createCamera1Provider() = object : CameraProvider {
private val enumerator by lazy { Camera1Enumerator(true) }
override val cameraVersion = 1
override fun provideEnumerator(context: Context) = enumerator
override fun provideCapturer(
context: Context,
options: LocalVideoTrackOptions,
eventsHandler: CameraEventsDispatchHandler,
): VideoCapturer {
val targetDeviceName = enumerator.findCamera(options.deviceId, options.position)
// Cache supported capture formats ahead of time to avoid future camera locks.
Camera1Helper.getSupportedFormats(Camera1Helper.getCameraId(targetDeviceName))
val targetVideoCapturer = enumerator.createCapturer(targetDeviceName, eventsHandler)
return Camera1CapturerWithSize(
targetVideoCapturer as Camera1Capturer,
targetDeviceName,
eventsHandler,
)
}
LKLog.w { "unknown CameraCapturer class: ${targetVideoCapturer.javaClass.canonicalName}. Reported dimensions may be inaccurate." }
if (targetVideoCapturer != null) {
return Pair(
targetVideoCapturer,
newOptions,
override fun isSupported(context: Context) = true
}
private fun createCamera2Provider() = object : CameraProvider {
private var enumerator: Camera2Enumerator? = null
override val cameraVersion = 2
override fun provideEnumerator(context: Context): CameraEnumerator =
enumerator ?: Camera2Enumerator(context).also {
enumerator = it
}
override fun provideCapturer(
context: Context,
options: LocalVideoTrackOptions,
eventsHandler: CameraEventsDispatchHandler,
): VideoCapturer {
val enumerator = provideEnumerator(context)
val targetDeviceName = enumerator.findCamera(options.deviceId, options.position)
val targetVideoCapturer = enumerator.createCapturer(targetDeviceName, eventsHandler)
return Camera2CapturerWithSize(
targetVideoCapturer as Camera2Capturer,
context.getSystemService(Context.CAMERA_SERVICE) as CameraManager,
targetDeviceName,
eventsHandler,
)
}
return null
override fun isSupported(context: Context) = Camera2Enumerator.isSupported(context)
}
/**
... ... @@ -180,4 +236,11 @@ object CameraCapturerUtils {
}
return null
}
interface CameraProvider {
val cameraVersion: Int
fun provideEnumerator(context: Context): CameraEnumerator
fun provideCapturer(context: Context, options: LocalVideoTrackOptions, eventsHandler: CameraEventsDispatchHandler): VideoCapturer
fun isSupported(context: Context): Boolean
}
}
... ...
... ... @@ -20,10 +20,8 @@ import livekit.org.webrtc.CameraVideoCapturer.CameraEventsHandler
/**
* Dispatches CameraEventsHandler callbacks to registered handlers.
*
* @suppress
*/
internal class CameraEventsDispatchHandler : CameraEventsHandler {
class CameraEventsDispatchHandler : CameraEventsHandler {
private val handlers = mutableSetOf<CameraEventsHandler>()
@Synchronized
... ...
... ... @@ -19,18 +19,11 @@ package io.livekit.android.room.track.video
import android.hardware.camera2.CameraManager
import livekit.org.webrtc.*
/**
* @suppress
*/
internal interface VideoCapturerWithSize : VideoCapturer {
interface VideoCapturerWithSize : VideoCapturer {
fun findCaptureFormat(width: Int, height: Int): Size
}
/**
* @suppress
*/
internal abstract class CameraCapturerWithSize(
abstract class CameraCapturerWithSize(
val cameraEventsDispatchHandler: CameraEventsDispatchHandler,
) : VideoCapturerWithSize
... ...