Skip to content

Adopt CameraX Feature Group API#522

Open
Zeronfinity wants to merge 6 commits into
mainfrom
tahsinm/cxfg-adoption
Open

Adopt CameraX Feature Group API#522
Zeronfinity wants to merge 6 commits into
mainfrom
tahsinm/cxfg-adoption

Conversation

@Zeronfinity
Copy link
Copy Markdown
Collaborator

Description

This PR adopts the new CameraX Feature Group API to validate feature combinations and dynamically update system constraints. This ensures that the app only allows supported combinations of settings (e.g., HDR, FPS, Stabilization), preventing runtime failures on devices that do not support specific combinations (like HDR + 60 FPS on Pixel 9).

Key Changes

  • CameraX Update: Updated CameraX dependencies to 1.6.0-SNAPSHOT to access the stable Feature Groups API.
  • Feature Mapping: Added FeatureGroupData to map JCA internal models (DynamicRange, VideoQuality, StabilizationMode, etc.) to CameraX's GroupableFeature.
  • Dynamic Constraints: Implemented updateSystemConstraintsByFeatureGroups in CameraXCameraSystem. This method asynchronously validates the current session settings against the device capabilities and filters out unsupported options for other settings (e.g., if 60 FPS is selected, it might disable HDR if they can't be used together).
  • Refactoring: Migrated from UseCaseGroup to SessionConfig for single camera session creation to align with the new API usage and allow for isSessionConfigSupported checks.

Bug Fixes & Enhancements

  • Fixes a crash/black screen issue on Pixel 9 devices when enabling both HDR and 60 FPS.
  • Stream mode is now selectable based on whether supported, rather being always disabled, when HDR is enabled.
  • Feature combinations can now be supported more often (due to the additional guaranteed combinations from feature combination query API), e.g. UHD recording with single stream mode in Samsung S23U.

Known Limitations & Future Work

System constraints not updating for multiple updates from setting screen

The system constraints are currently updated only when the camera session is initialized or re-created (e.g., upon returning to the preview screen). This means that if a user sets feature options (like UHD and 60 FPS) directly from the settings menu without navigating back to the preview screen, the system constraints is not re-evaluated.
Consequently, it allows users to select a combination of features that are not mutually supported by the device, which can lead to issues when they eventually navigate back to the preview screen.

TODO: Addressing this limitation requires further architectural refactoring to trigger constraint updates dynamically upon any individual setting change. Or, the constraints repository should be updated with the info of all possible constraints at initialization, rather than only the settings state navigable from the current settings state.

Features incompatible with CameraX Feature Group API

CameraX depends on the Camera2 feature combination query (FCQ) API which guarantees support only a limited set of features. So, the scenarios using features incompatible with the FCQ API do not get much benefits from this PR.

Asynchronous system constraints update

System constraints are updated asynchronously, creating a window where a user might enable an unsupported combination of features before the update completes.

Future Work: This could be addressed by waiting for the constraints update to finish and restricting setting changes until it is complete.

Tests

  • Added FeatureGroupDataTest (unit tests) to verify mapping logic between JCA models and CameraX GroupableFeature.
  • Added setMultipleFeatures_systemConstraintsUpdatedAndFeaturesSetIfSupported to CameraXCameraSystemTest (integration test) to verify that the camera system dynamically updates constraints and sets features correctly based on CameraX feature group compatibility.

Additional Context

Related Issues

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements CameraX feature groups to handle compatibility between settings such as HDR, frame rate, and stabilization. It introduces FeatureGroupHandler to filter system constraints and refactors CameraSession to use a SessionConfig containing required feature groups. The PR also updates the project to CameraX 1.6.0-SNAPSHOT and compile SDK 36. Feedback was provided to ensure manual Job cleanup via try-finally and to restrict the visibility of new components to internal as per the style guide.

Comment on lines +411 to +441
val job = Job()

val sessionConfig = with(
defaultCameraSessionContext.copy(
transientSettings = MutableStateFlow(transientSettings).asStateFlow()
)
) {
val videoCaptureUseCase =
createVideoUseCase(
cameraInfo,
cameraAppSettings.aspectRatio,
cameraAppSettings.captureMode,
backgroundDispatcher,
cameraAppSettings.targetFrameRate.takeIfNoFeatureGroup(sessionSettings),
cameraAppSettings.stabilizationMode.takeIfNoFeatureGroup(sessionSettings),
cameraAppSettings.dynamicRange.takeIfNoFeatureGroup(sessionSettings),
cameraAppSettings.videoQuality.takeIfNoFeatureGroup(sessionSettings)
)

createSessionConfig(
cameraConstraints = cameraConstraints,
initialTransientSettings = transientSettings,
videoCaptureUseCase = videoCaptureUseCase,
sessionSettings = sessionSettings,
sessionScope = CoroutineScope(defaultDispatcher + job)
)
}

return cameraInfo.isSessionConfigSupported(sessionConfig).apply {
job.cancel()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The manual Job created here should be cancelled within a finally block to ensure it is always cleaned up, even if isSessionConfigSupported throws an exception or the calling coroutine is cancelled. This prevents potential resource leaks from background tasks (such as camera effects) launched within the temporary sessionScope.

Additionally, using try-finally ensures that the job is cancelled immediately upon completion or failure of the capability check.

        val job = Job()
        return try {
            val sessionConfig = with(
                defaultCameraSessionContext.copy(
                    transientSettings = MutableStateFlow(transientSettings).asStateFlow()
                )
            ) {
                val videoCaptureUseCase =
                    createVideoUseCase(
                        cameraInfo,
                        cameraAppSettings.aspectRatio,
                        cameraAppSettings.captureMode,
                        backgroundDispatcher,
                        cameraAppSettings.targetFrameRate.takeIfNoFeatureGroup(sessionSettings),
                        cameraAppSettings.stabilizationMode.takeIfNoFeatureGroup(sessionSettings),
                        cameraAppSettings.dynamicRange.takeIfNoFeatureGroup(sessionSettings),
                        cameraAppSettings.videoQuality.takeIfNoFeatureGroup(sessionSettings)
                    )

                createSessionConfig(
                    cameraConstraints = cameraConstraints,
                    initialTransientSettings = transientSettings,
                    videoCaptureUseCase = videoCaptureUseCase,
                    sessionSettings = sessionSettings,
                    sessionScope = CoroutineScope(defaultDispatcher + job)
                )
            }
            cameraInfo.isSessionConfigSupported(sessionConfig)
        } finally {
            job.cancel()
        }

* This allows internal JCA models like [DynamicRange] and [VideoQuality] to be mapped to CameraX
* [GroupableFeature]s for compatibility checking.
*/
sealed interface FeatureGroupability<out T> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This interface and its associated extension functions (lines 97, 109, 124, 138, 156) are only used within the core:camera module. Following the repository style guide (rule 22), they should use the most restrictive visibility modifier possible, which is internal in this case.

Suggested change
sealed interface FeatureGroupability<out T> {
internal sealed interface FeatureGroupability<out T> {
References
  1. Ensure all new functions, properties, and classes use the most restrictive visibility modifier possible (e.g., private, internal) while still allowing necessary access. (link)

*
* This allows the dynamic range to be used in CameraX feature group compatibility checks.
*/
fun DynamicRange.toFeatureGroupability(): FeatureGroupability<DynamicRange> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

As noted previously, these extension functions should be marked as internal to adhere to the repository style guide regarding proper visibility modifiers.

Suggested change
fun DynamicRange.toFeatureGroupability(): FeatureGroupability<DynamicRange> {
internal fun DynamicRange.toFeatureGroupability(): FeatureGroupability<DynamicRange> {
References
  1. Ensure all new functions, properties, and classes use the most restrictive visibility modifier possible (e.g., private, internal) while still allowing necessary access. (link)

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.

Adopt CameraX Feature Groups API in JCA Camera session failure with HDR + 60 FPS

3 participants