davidliu
Committed by GitHub

Fix CameraXSession not setting the target capture format correctly (#652)

* Fix CameraXSession not setting the target capture format correctly, and remove unused usecase

* changeset and spotless
  1 +---
  2 +"client-sdk-android": patch
  3 +---
  4 +
  5 +Fix CameraXSession not setting the target capture format correctly
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.
@@ -32,10 +32,12 @@ import androidx.camera.camera2.interop.Camera2CameraInfo @@ -32,10 +32,12 @@ import androidx.camera.camera2.interop.Camera2CameraInfo
32 import androidx.camera.camera2.interop.Camera2Interop 32 import androidx.camera.camera2.interop.Camera2Interop
33 import androidx.camera.core.Camera 33 import androidx.camera.core.Camera
34 import androidx.camera.core.CameraSelector 34 import androidx.camera.core.CameraSelector
35 -import androidx.camera.core.ImageAnalysis 35 +import androidx.camera.core.ExtendableBuilder
36 import androidx.camera.core.Preview 36 import androidx.camera.core.Preview
37 import androidx.camera.core.Preview.SurfaceProvider 37 import androidx.camera.core.Preview.SurfaceProvider
38 import androidx.camera.core.UseCase 38 import androidx.camera.core.UseCase
  39 +import androidx.camera.core.resolutionselector.ResolutionSelector
  40 +import androidx.camera.core.resolutionselector.ResolutionStrategy
39 import androidx.camera.lifecycle.ProcessCameraProvider 41 import androidx.camera.lifecycle.ProcessCameraProvider
40 import androidx.core.content.ContextCompat 42 import androidx.core.content.ContextCompat
41 import androidx.lifecycle.LifecycleOwner 43 import androidx.lifecycle.LifecycleOwner
@@ -154,9 +156,6 @@ internal constructor( @@ -154,9 +156,6 @@ internal constructor(
154 } ?: request.willNotProvideSurface() 156 } ?: request.willNotProvideSurface()
155 } 157 }
156 158
157 - // Set image analysis - camera params  
158 - val imageAnalysis = setImageAnalysis()  
159 -  
160 // Select camera by ID 159 // Select camera by ID
161 val cameraSelector = CameraSelector.Builder() 160 val cameraSelector = CameraSelector.Builder()
162 .addCameraFilter { cameraInfo -> cameraInfo.filter { Camera2CameraInfo.from(it).cameraId == cameraId } } 161 .addCameraFilter { cameraInfo -> cameraInfo.filter { Camera2CameraInfo.from(it).cameraId == cameraId } }
@@ -166,6 +165,17 @@ internal constructor( @@ -166,6 +165,17 @@ internal constructor(
166 ContextCompat.getMainExecutor(context).execute { 165 ContextCompat.getMainExecutor(context).execute {
167 // Preview 166 // Preview
168 val preview = Preview.Builder() 167 val preview = Preview.Builder()
  168 + .setResolutionSelector(
  169 + ResolutionSelector.Builder()
  170 + .setResolutionStrategy(
  171 + ResolutionStrategy(
  172 + Size(captureFormat?.width ?: width, captureFormat?.height ?: height),
  173 + ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER,
  174 + ),
  175 + )
  176 + .build(),
  177 + )
  178 + .applyCameraSettings()
169 .build() 179 .build()
170 .also { 180 .also {
171 it.setSurfaceProvider(surfaceProvider) 181 it.setSurfaceProvider(surfaceProvider)
@@ -178,7 +188,6 @@ internal constructor( @@ -178,7 +188,6 @@ internal constructor(
178 camera = cameraProvider.bindToLifecycle( 188 camera = cameraProvider.bindToLifecycle(
179 lifecycleOwner, 189 lifecycleOwner,
180 cameraSelector, 190 cameraSelector,
181 - imageAnalysis,  
182 preview, 191 preview,
183 *useCases, 192 *useCases,
184 ) 193 )
@@ -195,36 +204,34 @@ internal constructor( @@ -195,36 +204,34 @@ internal constructor(
195 ) 204 )
196 } 205 }
197 206
198 - private fun setImageAnalysis() = ImageAnalysis.Builder()  
199 - .setTargetResolution(Size(captureFormat?.width ?: width, captureFormat?.height ?: height))  
200 - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).apply {  
201 - val cameraExtender = Camera2Interop.Extender(this)  
202 - captureFormat?.let { captureFormat ->  
203 - cameraExtender.setCaptureRequestOption(  
204 - CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,  
205 - Range(  
206 - captureFormat.framerate.min / fpsUnitFactor,  
207 - captureFormat.framerate.max / fpsUnitFactor,  
208 - ),  
209 - )  
210 - cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)  
211 - cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_AE_LOCK, false)  
212 - when (stabilizationMode) {  
213 - StabilizationMode.OPTICAL -> {  
214 - cameraExtender.setCaptureRequestOption(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, LENS_OPTICAL_STABILIZATION_MODE_ON)  
215 - cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF)  
216 - } 207 + private fun <T> ExtendableBuilder<T>.applyCameraSettings(): ExtendableBuilder<T> {
  208 + val cameraExtender = Camera2Interop.Extender(this)
  209 + val captureFormat = this@CameraXSession.captureFormat ?: return this
  210 + cameraExtender.setCaptureRequestOption(
  211 + CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
  212 + Range(
  213 + captureFormat.framerate.min / fpsUnitFactor,
  214 + captureFormat.framerate.max / fpsUnitFactor,
  215 + ),
  216 + )
  217 + cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
  218 + cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_AE_LOCK, false)
  219 + when (stabilizationMode) {
  220 + StabilizationMode.OPTICAL -> {
  221 + cameraExtender.setCaptureRequestOption(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, LENS_OPTICAL_STABILIZATION_MODE_ON)
  222 + cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF)
  223 + }
217 224
218 - StabilizationMode.VIDEO -> {  
219 - cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_ON)  
220 - cameraExtender.setCaptureRequestOption(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, LENS_OPTICAL_STABILIZATION_MODE_OFF)  
221 - } 225 + StabilizationMode.VIDEO -> {
  226 + cameraExtender.setCaptureRequestOption(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_ON)
  227 + cameraExtender.setCaptureRequestOption(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, LENS_OPTICAL_STABILIZATION_MODE_OFF)
  228 + }
222 229
223 - else -> Unit  
224 - } 230 + else -> {
225 } 231 }
226 } 232 }
227 - .build() 233 + return this
  234 + }
228 235
229 private fun stopInternal() { 236 private fun stopInternal() {
230 Logging.d(TAG, "Stop internal") 237 Logging.d(TAG, "Stop internal")
@@ -25,7 +25,15 @@ data class LocalVideoTrackOptions( @@ -25,7 +25,15 @@ data class LocalVideoTrackOptions(
25 * will prefer a camera according to [position] 25 * will prefer a camera according to [position]
26 */ 26 */
27 val deviceId: String? = null, 27 val deviceId: String? = null,
  28 + /**
  29 + * The camera position to use.
  30 + */
28 val position: CameraPosition? = CameraPosition.FRONT, 31 val position: CameraPosition? = CameraPosition.FRONT,
  32 + /**
  33 + * Video capture options such as resolution and framerate.
  34 + *
  35 + * See [VideoPreset169] or [VideoPreset43] for preset parameters for common resolutions.
  36 + */
29 val captureParams: VideoCaptureParameter = VideoPreset169.H720.capture, 37 val captureParams: VideoCaptureParameter = VideoPreset169.H720.capture,
30 ) 38 )
31 39