Committed by
GitHub
Support for selecting cameras by their physical id (#668)
* Support selecting physical camera This commit adds support for selecting a specific physical camera when multiple physical cameras are available under a single logical camera. This is particularly relevant for devices with multi-camera systems. * Return early * Fix typo * Add comment * Fix comment * Make function private * add changeset --------- Co-authored-by: davidliu <davidliu@deviange.net>
正在显示
5 个修改的文件
包含
50 行增加
和
6 行删除
.changeset/sixty-snakes-own.md
0 → 100644
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2024 LiveKit, Inc. | 2 | + * Copyright 2024-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -37,6 +37,7 @@ internal class CameraXCapturer( | @@ -37,6 +37,7 @@ internal class CameraXCapturer( | ||
| 37 | cameraName: String?, | 37 | cameraName: String?, |
| 38 | eventsHandler: CameraVideoCapturer.CameraEventsHandler?, | 38 | eventsHandler: CameraVideoCapturer.CameraEventsHandler?, |
| 39 | private val useCases: Array<out UseCase> = emptyArray(), | 39 | private val useCases: Array<out UseCase> = emptyArray(), |
| 40 | + var physicalCameraId: String? = null, | ||
| 40 | ) : CameraCapturer(cameraName, eventsHandler, CameraXEnumerator(context, lifecycleOwner)) { | 41 | ) : CameraCapturer(cameraName, eventsHandler, CameraXEnumerator(context, lifecycleOwner)) { |
| 41 | 42 | ||
| 42 | @FlowObservable | 43 | @FlowObservable |
| @@ -93,6 +94,7 @@ internal class CameraXCapturer( | @@ -93,6 +94,7 @@ internal class CameraXCapturer( | ||
| 93 | height, | 94 | height, |
| 94 | framerate, | 95 | framerate, |
| 95 | useCases, | 96 | useCases, |
| 97 | + physicalCameraId, | ||
| 96 | ) | 98 | ) |
| 97 | } | 99 | } |
| 98 | } | 100 | } |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2024 LiveKit, Inc. | 2 | + * Copyright 2024-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -35,13 +35,14 @@ class CameraXEnumerator( | @@ -35,13 +35,14 @@ class CameraXEnumerator( | ||
| 35 | context: Context, | 35 | context: Context, |
| 36 | private val lifecycleOwner: LifecycleOwner, | 36 | private val lifecycleOwner: LifecycleOwner, |
| 37 | private val useCases: Array<out UseCase> = emptyArray(), | 37 | private val useCases: Array<out UseCase> = emptyArray(), |
| 38 | + var physicalCameraId: String? = null, | ||
| 38 | ) : Camera2Enumerator(context) { | 39 | ) : Camera2Enumerator(context) { |
| 39 | 40 | ||
| 40 | override fun createCapturer( | 41 | override fun createCapturer( |
| 41 | deviceName: String?, | 42 | deviceName: String?, |
| 42 | eventsHandler: CameraVideoCapturer.CameraEventsHandler?, | 43 | eventsHandler: CameraVideoCapturer.CameraEventsHandler?, |
| 43 | ): CameraVideoCapturer { | 44 | ): CameraVideoCapturer { |
| 44 | - return CameraXCapturer(context, lifecycleOwner, deviceName, eventsHandler, useCases) | 45 | + return CameraXCapturer(context, lifecycleOwner, deviceName, eventsHandler, useCases, physicalCameraId) |
| 45 | } | 46 | } |
| 46 | 47 | ||
| 47 | companion object { | 48 | companion object { |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -18,6 +18,7 @@ package livekit.org.webrtc | @@ -18,6 +18,7 @@ package livekit.org.webrtc | ||
| 18 | 18 | ||
| 19 | import android.content.Context | 19 | import android.content.Context |
| 20 | import android.hardware.camera2.CameraManager | 20 | import android.hardware.camera2.CameraManager |
| 21 | +import android.os.Build | ||
| 21 | import androidx.camera.camera2.interop.ExperimentalCamera2Interop | 22 | import androidx.camera.camera2.interop.ExperimentalCamera2Interop |
| 22 | import androidx.camera.core.UseCase | 23 | import androidx.camera.core.UseCase |
| 23 | import androidx.lifecycle.Lifecycle | 24 | import androidx.lifecycle.Lifecycle |
| @@ -61,12 +62,21 @@ class CameraXHelper { | @@ -61,12 +62,21 @@ class CameraXHelper { | ||
| 61 | eventsHandler: CameraEventsDispatchHandler, | 62 | eventsHandler: CameraEventsDispatchHandler, |
| 62 | ): VideoCapturer { | 63 | ): VideoCapturer { |
| 63 | val enumerator = provideEnumerator(context) | 64 | val enumerator = provideEnumerator(context) |
| 64 | - val targetDeviceName = enumerator.findCamera(options.deviceId, options.position) | 65 | + val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager |
| 66 | + val deviceId = options.deviceId | ||
| 67 | + var targetDeviceName: String? = null | ||
| 68 | + if (deviceId != null) { | ||
| 69 | + targetDeviceName = findCameraById(cameraManager, deviceId) | ||
| 70 | + } | ||
| 71 | + if (targetDeviceName == null) { | ||
| 72 | + // Fallback to enumerator.findCamera which can't find camera by physical id but it will choose the closest one. | ||
| 73 | + targetDeviceName = enumerator.findCamera(deviceId, options.position) | ||
| 74 | + } | ||
| 65 | val targetVideoCapturer = enumerator.createCapturer(targetDeviceName, eventsHandler) as CameraXCapturer | 75 | val targetVideoCapturer = enumerator.createCapturer(targetDeviceName, eventsHandler) as CameraXCapturer |
| 66 | 76 | ||
| 67 | return CameraXCapturerWithSize( | 77 | return CameraXCapturerWithSize( |
| 68 | targetVideoCapturer, | 78 | targetVideoCapturer, |
| 69 | - context.getSystemService(Context.CAMERA_SERVICE) as CameraManager, | 79 | + cameraManager, |
| 70 | targetDeviceName, | 80 | targetDeviceName, |
| 71 | eventsHandler, | 81 | eventsHandler, |
| 72 | ) | 82 | ) |
| @@ -75,6 +85,23 @@ class CameraXHelper { | @@ -75,6 +85,23 @@ class CameraXHelper { | ||
| 75 | override fun isSupported(context: Context): Boolean { | 85 | override fun isSupported(context: Context): Boolean { |
| 76 | return Camera2Enumerator.isSupported(context) && lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED) | 86 | return Camera2Enumerator.isSupported(context) && lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED) |
| 77 | } | 87 | } |
| 88 | + | ||
| 89 | + private fun findCameraById(cameraManager: CameraManager, deviceId: String): String? { | ||
| 90 | + for (id in cameraManager.cameraIdList) { | ||
| 91 | + if (id == deviceId) return id // This means the provided id is logical id. | ||
| 92 | + | ||
| 93 | + val characteristics = cameraManager.getCameraCharacteristics(id) | ||
| 94 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | ||
| 95 | + val ids = characteristics.physicalCameraIds | ||
| 96 | + if (ids.contains(deviceId)) { | ||
| 97 | + // This means the provided id is physical id. | ||
| 98 | + enumerator?.physicalCameraId = deviceId | ||
| 99 | + return id // This is its logical id. | ||
| 100 | + } | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + return null | ||
| 104 | + } | ||
| 78 | } | 105 | } |
| 79 | 106 | ||
| 80 | private fun getSupportedFormats( | 107 | private fun getSupportedFormats( |
| @@ -24,6 +24,7 @@ import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ | @@ -24,6 +24,7 @@ import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ | ||
| 24 | import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF | 24 | import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF |
| 25 | import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON | 25 | import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON |
| 26 | import android.hardware.camera2.CaptureRequest | 26 | import android.hardware.camera2.CaptureRequest |
| 27 | +import android.os.Build | ||
| 27 | import android.os.Handler | 28 | import android.os.Handler |
| 28 | import android.util.Range | 29 | import android.util.Range |
| 29 | import android.util.Size | 30 | import android.util.Size |
| @@ -61,6 +62,7 @@ internal constructor( | @@ -61,6 +62,7 @@ internal constructor( | ||
| 61 | private val height: Int, | 62 | private val height: Int, |
| 62 | private val frameRate: Int, | 63 | private val frameRate: Int, |
| 63 | private val useCases: Array<out UseCase> = emptyArray(), | 64 | private val useCases: Array<out UseCase> = emptyArray(), |
| 65 | + var physicalCameraId: String? = null, | ||
| 64 | ) : CameraSession { | 66 | ) : CameraSession { |
| 65 | 67 | ||
| 66 | private var state = SessionState.RUNNING | 68 | private var state = SessionState.RUNNING |
| @@ -206,6 +208,13 @@ internal constructor( | @@ -206,6 +208,13 @@ internal constructor( | ||
| 206 | 208 | ||
| 207 | private fun <T> ExtendableBuilder<T>.applyCameraSettings(): ExtendableBuilder<T> { | 209 | private fun <T> ExtendableBuilder<T>.applyCameraSettings(): ExtendableBuilder<T> { |
| 208 | val cameraExtender = Camera2Interop.Extender(this) | 210 | val cameraExtender = Camera2Interop.Extender(this) |
| 211 | + | ||
| 212 | + physicalCameraId?.let { physicalId -> | ||
| 213 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | ||
| 214 | + cameraExtender.setPhysicalCameraId(physicalId) | ||
| 215 | + } | ||
| 216 | + } | ||
| 217 | + | ||
| 209 | val captureFormat = this@CameraXSession.captureFormat ?: return this | 218 | val captureFormat = this@CameraXSession.captureFormat ?: return this |
| 210 | cameraExtender.setCaptureRequestOption( | 219 | cameraExtender.setCaptureRequestOption( |
| 211 | CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, | 220 | CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, |
-
请 注册 或 登录 后发表评论