-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement LowLightBoost Capture #236
base: main
Are you sure you want to change the base?
Conversation
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) | ||
@OptIn(BuildCompat.PrereleaseSdkCheck::class) | ||
private fun getLowLightBoostDeviceSupport() = when (BuildCompat.isAtLeastV()) { | ||
true -> cameraProvider.availableCameraInfos.map { cameraInfo -> | ||
Camera2CameraInfo | ||
.from(cameraInfo) | ||
.getCameraCharacteristic(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES) | ||
?.contains( | ||
CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY | ||
) | ||
}.any() | ||
false -> false | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be checking for support on a per-lens basis. This can be rewritten as an extension property to make it easier:
private val CameraInfo.isLowLightBoostSupported: Boolean
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
@OptIn(BuildCompat.PrereleaseSdkCheck::class)
get() = BuildCompat.isAtLeastV() &&
Camera2CameraInfo
.from(this)
.getCameraCharacteristic(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES)
?.contains(
CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY
) ?: false
Then in the CameraConstraints
(line 197), we can say:
lowLightBoostSupport = camInfo.isLowLightBoostSupported
currentSettings.update { old -> | ||
old?.copy( | ||
lowLightBoost = lowLightBoost, | ||
captureMode = CaptureMode.MULTI_STREAM, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the CaptureMode
was SINGLE_STREAM
before disabling low-light boost?
If we can only support LLB in SINGLE_STREAM
mode, then we should do something similar to what we did with CameraConstraints.supportedImageFormatsMap
where we have a Map<CaptureMode, Boolean>
. We can then determine in the UI whether we should allow users to enable LLB depending on which capture mode is enabled (otherwise we disable the button).
camera2Interop.setSessionCaptureCallback(object : CaptureCallback() { | ||
override fun onCaptureCompleted( | ||
session: CameraCaptureSession, | ||
request: CaptureRequest, | ||
result: TotalCaptureResult | ||
) { | ||
super.onCaptureCompleted(session, request, result) | ||
val boostState = result.get(CaptureResult.CONTROL_LOW_LIGHT_BOOST_STATE) | ||
if (boostState == CameraMetadata.CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE) { | ||
Log.d(TAG, "LLB Active") | ||
_lowLightBoostActiveStatus.value = true | ||
} else { | ||
Log.d(TAG, "LLB Inactive") | ||
_lowLightBoostActiveStatus.value = false | ||
} | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add this callback regardless of whether LLB is enabled. Technically the CONTROL_AE_MODE
can be set at any time (without needing to rebind use cases). So it could go from the DISABLED
-> ENABLED
state without needing to recreate / rebind Preview
.
camera2Interop.setCaptureRequestOption( | ||
CaptureRequest.CONTROL_AE_MODE, | ||
CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be done using Camera2CameraControl
rather than adding it to the Preview.Builder
. It's similar to the CameraControl.setZoomRatio
or setFlashModeInternal
that we set in the cameraProvider.runWith {}
block.
@@ -834,6 +883,34 @@ constructor( | |||
getResolutionSelector(sessionSettings.aspectRatio) | |||
) | |||
|
|||
if (currentSettings.value?.lowLightBoost == LowLightBoost.ENABLED) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than reading this from currentSettings
, lowLightBoost
should be added to TransientSessionSettings
and pulled out of settings in the the currentSettings
Flow in runCamera()
. That way we know we have a valid snapshot whenever we rebind the camera.
cameraUseCase.getZoomScale(), | ||
cameraUseCase.getLowLightBoostActiveStatus() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're going to start to run out of the ability to combine these flows if we keep adding more. combine
only has overloads for up to 5 flows (with unique types). Since zoomScale and LLBActiveStatus are both descriptive camera state, maybe we can combine them into a single data class called DescriptiveCameraState
? Then we can have a single flow for both from CameraUseCase
.
contentDescription = | ||
stringResource(id = R.string.quick_settings_lowlightboost_enabled), | ||
modifier = modifier.alpha(0.5f) | ||
modifier = modifier.alpha(if (active) 1.0f else 0.5f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disabled states should use an alpha of 0.38f
. See https://m3.material.io/foundations/interaction/states/applying-states#4aff9c51-d20f-4580-a510-862d2e25e931
@@ -73,6 +73,8 @@ interface CameraUseCase { | |||
|
|||
fun getZoomScale(): StateFlow<Float> | |||
|
|||
fun getLowLightBoostActiveStatus(): StateFlow<Boolean> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered defining an enum for this? Maybe UNSUPPORTED, ACTIVE, INACTIVE
?
That way the client only needs to observe this and not isLowLightBoostSupport
and the default can be UNSUPPORTED
rather than true
@@ -541,6 +566,10 @@ constructor( | |||
private val _zoomScale = MutableStateFlow(1f) | |||
override fun getZoomScale(): StateFlow<Float> = _zoomScale.asStateFlow() | |||
|
|||
private val _lowLightBoostActiveStatus = MutableStateFlow(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would mean that low light is active?
a) maybe the enum approach I suggested above might be better?
b) maybe starting as inactive
might be better?
c) if I got the active / inactive wrong then it might be better to type it to an enum to avoid confusion
if (currentSettings.value?.lowLightBoost == LowLightBoost.ENABLED) { | ||
Log.d(TAG, "Adding Control AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY") | ||
val camera2Interop = Camera2Interop.Extender(previewUseCaseBuilder) | ||
if (Build.VERSION.SDK_INT >= 35) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should we use BuildCompat.isAtLeastV()
instead?
result: TotalCaptureResult | ||
) { | ||
super.onCaptureCompleted(session, request, result) | ||
val boostState = result.get(CaptureResult.CONTROL_LOW_LIGHT_BOOST_STATE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this will change anything for your use case but sharing for context:
LOW_LIGHT_BOOST_STATE
is only active / inactive if CaptureResult.CONTROL_AE_MODE
is AE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY
So if the current camera feature combination wasn't supported when LLB was requested, the result may switch back to AE_MODE_ON
Or if the AE_MODE was not LLB, then maybe hide the moon icon?
Unless if the UI keeps the moon icon there but in an INACTIVE state? When pressed it will turn on LLB? But that might not be intuitive because the scene could be well lit and still be inactive so it wouldn't be clear to the user when AE_MODE is LLB or auto.
This PR contains the following.
CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE
, if active change the outlined LLB indicator to a filled indicator with full opacity