Skip to content

Commit 1fe33a9

Browse files
committed
feat: Do not recreate camera preview on each focus change
1 parent cdc86f7 commit 1fe33a9

File tree

3 files changed

+52
-65
lines changed

3 files changed

+52
-65
lines changed

app/app/src/main/java/com/octo4a/camera/CameraService.kt

+46-59
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import android.util.Range
2222
import android.util.Size
2323
import android.view.Surface
2424
import androidx.annotation.RequiresApi
25+
import androidx.camera.camera2.interop.Camera2CameraControl
2526
import androidx.camera.camera2.interop.Camera2CameraInfo
2627
import androidx.camera.camera2.interop.Camera2Interop
28+
import androidx.camera.camera2.interop.CaptureRequestOptions
2729
import androidx.camera.core.*
2830
import androidx.camera.lifecycle.ProcessCameraProvider
2931
import androidx.core.app.ActivityCompat
@@ -112,33 +114,6 @@ class CameraService : LifecycleService(), MJpegFrameProvider {
112114
return minFocalLength
113115
}
114116

115-
@SuppressLint("UnsafeExperimentalUsageError")
116-
private fun <T> setFpsAndAutofocus(builder: T) where T : ExtendableBuilder<*>, T : Any {
117-
val ext: Camera2Interop.Extender<*> = Camera2Interop.Extender(builder)
118-
if (_cameraSettings.disableAF) {
119-
ext.setCaptureRequestOption(
120-
CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF)
121-
ext.setCaptureRequestOption(
122-
CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
123-
ext.setCaptureRequestOption(CaptureRequest.LENS_FOCUS_DISTANCE, 0f)
124-
} else if (_cameraSettings.manualAF) {
125-
ext.setCaptureRequestOption(
126-
CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF)
127-
ext.setCaptureRequestOption(
128-
CaptureRequest.LENS_FOCUS_DISTANCE, _cameraSettings.manualAFValue)
129-
}
130-
else {
131-
ext.setCaptureRequestOption(
132-
CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO)
133-
134-
}
135-
136-
if (_fpsLimit > 0) {
137-
ext.setCaptureRequestOption(
138-
CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range<Int>(_fpsLimit, _fpsLimit))
139-
}
140-
}
141-
142117
private val _cameraSelector by lazy {
143118
CameraSelector.Builder()
144119
.apply {
@@ -150,24 +125,20 @@ class CameraService : LifecycleService(), MJpegFrameProvider {
150125
.build()
151126
}
152127

153-
private var _cameraPreview: Preview? = null
154-
155-
private fun createCameraPreview(): Preview {
156-
val builder =
157-
Preview.Builder()
158-
.setTargetResolution(
159-
Size.parseSize(_cameraSettings.selectedVideoResolution ?: "1280x720"))
160-
.setTargetRotation(getSettingsRotation())
161-
162-
setFpsAndAutofocus(builder)
128+
private val _cameraPreview by lazy {
129+
val builder =
130+
Preview.Builder()
131+
.setTargetResolution(
132+
Size.parseSize(_cameraSettings.selectedVideoResolution ?: "1280x720"))
133+
.setTargetRotation(getSettingsRotation())
163134

164-
val ret = builder.build()
165-
_cameraBoundUseCases[ret] =
166-
CompletableInitState(
167-
InitState.NOT_INITIALIZED,
168-
callback = ::torchControlCallback,
169-
unbindDelayMs = UNBIND_STREAM_DELAY_MS)
170-
return ret
135+
val ret = builder.build()
136+
_cameraBoundUseCases[ret] =
137+
CompletableInitState(
138+
InitState.NOT_INITIALIZED,
139+
callback = ::torchControlCallback,
140+
unbindDelayMs = UNBIND_STREAM_DELAY_MS)
141+
ret
171142
}
172143

173144
private val _imageCapture by lazy {
@@ -196,8 +167,6 @@ class CameraService : LifecycleService(), MJpegFrameProvider {
196167
.setTargetRotation(getSettingsRotation())
197168
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
198169

199-
setFpsAndAutofocus(builder)
200-
201170
val ret = builder.build()
202171
_cameraBoundUseCases[ret] =
203172
CompletableInitState(
@@ -419,27 +388,44 @@ class CameraService : LifecycleService(), MJpegFrameProvider {
419388
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
420389
fun onStop() {
421390
_logger.log(this) { "Preview has stopped" }
422-
_cameraPreview?.apply {
423-
deinitUseCase(this)
424-
}
391+
deinitUseCase(_cameraPreview)
425392
lifecycleOwner.lifecycle.removeObserver(this)
426393
}
427394
})
428395
}
429396

430-
fun getPreview(): Preview? {
431-
_cameraPreview?.apply {
432-
if (_cameraBoundUseCases[this]!!.refcnt > 0) {
433-
// Deinit camera's usecase if one exists
434-
deinitUseCase(this)
397+
@SuppressLint("UnsafeExperimentalUsageError", "RestrictedApi")
398+
fun updateCameraParameters() {
399+
getCameraControl()?.apply {
400+
val control = Camera2CameraControl.from(this)
401+
val ext = CaptureRequestOptions.Builder()
402+
if (_cameraSettings.disableAF) {
403+
ext.setCaptureRequestOption(
404+
CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF)
405+
ext.setCaptureRequestOption(
406+
CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
407+
ext.setCaptureRequestOption(CaptureRequest.LENS_FOCUS_DISTANCE, 0f)
408+
} else if (_cameraSettings.manualAF) {
409+
ext.setCaptureRequestOption(
410+
CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF)
411+
ext.setCaptureRequestOption(
412+
CaptureRequest.LENS_FOCUS_DISTANCE, _cameraSettings.manualAFValue)
413+
}
414+
else {
415+
ext.setCaptureRequestOption(
416+
CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO)
435417
}
436-
}
437418

438-
if (_cameraPreview == null) {
439-
_cameraPreview = createCameraPreview()
440-
}
419+
if (_fpsLimit > 0) {
420+
ext.setCaptureRequestOption(
421+
CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Range<Int>(_fpsLimit, _fpsLimit))
422+
}
441423

442-
if (!initUseCase(_cameraPreview!!, block = true)) {
424+
control.setCaptureRequestOptions(ext.build())
425+
}
426+
}
427+
fun getPreview(): Preview? {
428+
if (!initUseCase(_cameraPreview, block = true)) {
443429
return null
444430
}
445431

@@ -535,6 +521,7 @@ class CameraService : LifecycleService(), MJpegFrameProvider {
535521
initState.cameraControl = camera?.cameraControl
536522
initState.setState(InitState.INITIALIZED)
537523
initState.minFocalLength = null
524+
updateCameraParameters()
538525

539526
try {
540527
val cameraInfo = camera?.cameraInfo!!

app/app/src/main/java/com/octo4a/ui/fragments/CameraPreviewDialogFragment.kt

+5-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.view.LayoutInflater
1313
import android.view.View
1414
import android.view.ViewGroup
1515
import androidx.annotation.RequiresApi
16+
import androidx.camera.core.Preview
1617
import androidx.core.view.isGone
1718
import androidx.fragment.app.DialogFragment
1819
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -36,6 +37,7 @@ class CameraPreviewDialogFragment : DialogFragment() {
3637
private var _cameraService: CameraService? = null
3738
private val mainPreferences: MainPreferences by inject()
3839
private val logger: LoggerRepository by inject()
40+
private var _cameraPreview: Preview? = null
3941

4042
private val cameraServiceConnection = object : ServiceConnection {
4143
override fun onServiceConnected(className: ComponentName, service: IBinder) {
@@ -88,21 +90,18 @@ class CameraPreviewDialogFragment : DialogFragment() {
8890
}
8991

9092
private fun reinitPreview() {
91-
view?.apply {
92-
val preview = _cameraService!!.getPreview()
93-
preview?.setSurfaceProvider(previewView.surfaceProvider)
94-
}
93+
_cameraService?.updateCameraParameters()
9594
}
9695

9796
private fun initializeWithService() {
98-
val preview = _cameraService!!.getPreview()
97+
_cameraPreview = _cameraService!!.getPreview()
9998
_cameraService?.hookPreviewLifecycleObserver(this)
10099
logger.log { "init with service" }
101100
val minFocalLength = _cameraService?.getCameraMinFocalLength()
102101
val supportsManualFocus = minFocalLength != null && minFocalLength > 0f
103102

104103
view?.apply {
105-
preview?.setSurfaceProvider(previewView.surfaceProvider)
104+
_cameraPreview?.setSurfaceProvider(previewView.surfaceProvider)
106105
manualFocusSlider?.isGone = !(mainPreferences.manualAF && supportsManualFocus)
107106
manualFocusCheckbox?.isGone = !supportsManualFocus
108107
manualFocusCheckbox?.isChecked = supportsManualFocus && mainPreferences.manualAF

app/app/src/main/res/layout/dialog_camera_preview.xml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<androidx.camera.view.PreviewView
1010
android:layout_marginHorizontal="0dp"
1111
android:layout_width="match_parent"
12+
android:layout_marginTop="16dp"
1213
app:scaleType="fillCenter"
1314
app:implementationMode="compatible"
1415
android:layout_height="280dp"

0 commit comments

Comments
 (0)