Skip to content
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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

yasith
Copy link
Member

@yasith yasith commented Jun 25, 2024

This PR contains the following.

  • Low Light Boost(LLB) feature toggle is enabled, if the device supports LLB
  • When LLB toggle is turned on, switch to multi-stream mode, and enable LLB capture with Camera2Interop. Show the outlined LLB indicator with 50% opacity
  • Query for the LLB active status CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE, if active change the outlined LLB indicator to a filled indicator with full opacity

Comment on lines 228 to 241
@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
}

Copy link
Collaborator

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,
Copy link
Collaborator

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).

Comment on lines +894 to +910
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
}
}
})
Copy link
Collaborator

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.

Comment on lines +890 to +893
camera2Interop.setCaptureRequestOption(
CaptureRequest.CONTROL_AE_MODE,
CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY
)
Copy link
Collaborator

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) {
Copy link
Collaborator

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.

Comment on lines +96 to +97
cameraUseCase.getZoomScale(),
cameraUseCase.getLowLightBoostActiveStatus()
Copy link
Collaborator

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -73,6 +73,8 @@ interface CameraUseCase {

fun getZoomScale(): StateFlow<Float>

fun getLowLightBoostActiveStatus(): StateFlow<Boolean>
Copy link
Collaborator

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)
Copy link
Collaborator

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) {
Copy link
Collaborator

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)
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants