Options de configuration

Vous configurez chaque cas d'utilisation de CameraX pour vérifier différents aspects des opérations de ces cas d'utilisation.

Par exemple, avec le cas d'utilisation "Capture d'image", vous pouvez définir un format cible et un mode Flash. Par exemple, le code suivant :

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

En plus des options de configuration, certains cas d'utilisation exposent des API à des paramètres dynamiques après la création du cas d'utilisation. Pour en savoir plus sur la configuration spécifique à chaque cas d'utilisation, consultez Intégrer un aperçu, Analyser des images et Capture d'image.

CameraXConfig

Pour plus de facilité, CameraX dispose de configurations par défaut, telles que des exécuteurs et des gestionnaires internes, adaptés à la plupart des cas d'utilisation. Toutefois, si votre application présente des exigences particulières ou si vous préférez personnaliser ces configurations, CameraXConfig est l'interface prévue à cet effet.

Avec CameraXConfig, une application peut effectuer les opérations suivantes :

Modèle d'utilisation

La procédure suivante décrit comment utiliser CameraXConfig :

  1. Créez un objet CameraXConfig avec vos configurations personnalisées.
  2. Intégrez l'interface CameraXConfig.Provider dans votre Application et renvoyez votre objet CameraXConfig dans getCameraXConfig().
  3. Ajoutez la classe Application à votre fichier AndroidManifest.xml, comme indiqué ici.

L'exemple de code suivant limite la journalisation de CameraX aux messages d'erreur uniquement :

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

Conservez une copie locale de l'objet CameraXConfig si votre application a besoin de connaître la configuration de CameraX après l'avoir définie.

Limiteur de caméra

Lors du premier appel de ProcessCameraProvider.getInstance(), CameraX énumère et interroge les caractéristiques des caméras disponibles sur l'appareil. CameraX doit communiquer avec les composants matériels. Ce processus peut donc prendre un certain temps pour chaque caméra, en particulier sur les appareils bas de gamme. Si votre application n'utilise que des caméras spécifiques sur l'appareil, comme la caméra avant par défaut, vous pouvez configurer CameraX de sorte qu'il ignore les autres caméras et réduire ainsi le temps de latence au démarrage des caméras utilisées par votre application.

Si le CameraSelector soumis à CameraXConfig.Builder.setAvailableCamerasLimiter() filtre une caméra, CameraX se comporte comme si cette caméra n'existait pas. Par exemple, le code suivant limite l'utilisation de la caméra arrière par défaut de l'appareil :

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

Threads

De nombreuses API de la plate-forme sur lesquelles CameraX repose requièrent un dispositif de communication inter-processus (IPC) avec du matériel dont la réponse peut parfois prendre des centaines de millisecondes. Pour cette raison, CameraX n'appelle ces API qu'à partir de threads en arrière-plan, ce qui garantit que le thread principal n'est pas bloqué et que l'UI reste fluide. CameraX gère ces threads en interne, de sorte que ce comportement semble transparent. Cependant, certaines applications nécessitent un contrôle strict des threads. CameraXConfig permet à une application de définir les threads d'arrière-plan utilisés via CameraXConfig.Builder.setCameraExecutor() et CameraXConfig.Builder.setSchedulerHandler().

Exécuteur de la caméra

L'exécuteur de la caméra est utilisé pour tous les appels d'API internes de la plate-forme de caméra, ainsi que pour les rappels de ces API. CameraX alloue et gère un élément Executor interne pour effectuer ces tâches. Toutefois, si votre application nécessite un contrôle plus strict des threads, utilisez CameraXConfig.Builder.setCameraExecutor().

Gestionnaire de programmeur

Le gestionnaire de programmeur permet de planifier des tâches internes à intervalles fixes, par exemple de réessayer d'ouvrir la caméra lorsqu'elle n'est pas disponible. Ce gestionnaire n'exécute pas les tâches et les envoie uniquement à l'exécuteur de la caméra. Il est également utilisé sur les anciennes plates-formes d'API qui requièrent Handler pour les rappels. Dans ces cas, les rappels sont toujours envoyés directement à l'exécuteur de la caméra. CameraX alloue et gère un élément HandlerThread interne pour effectuer ces tâches, mais vous pouvez le remplacer par CameraXConfig.Builder.setSchedulerHandler().

Journalisation

La journalisation de CameraX permet aux applications de filtrer les messages Logcat, car il est recommandé d'éviter les messages détaillés dans le code de production. CameraX est compatible avec quatre niveaux de journalisation, du plus détaillé au plus succinct :

  • Log.DEBUG (par défaut)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

Reportez-vous aux documents des journaux Android pour obtenir une description détaillée de ces niveaux de journalisation. Utilisez CameraXConfig.Builder.setMinimumLoggingLevel(int) pour définir le niveau de journalisation approprié pour votre application.

Sélection automatique

CameraX fournit automatiquement une fonctionnalité spécifique à l'appareil sur lequel votre application s'exécute. Par exemple, CameraX détermine automatiquement la meilleure résolution à utiliser si vous ne précisez pas de résolution ou si la résolution spécifiée n'est pas compatible. Tout cela est géré par la bibliothèque, sans besoin d'écrire du code spécifique à l'appareil.

L'objectif de CameraX est d'initialiser correctement une session d'appareil photo. Cela signifie que CameraX fait des compromis au niveau de la résolution et du format selon la capacité de l'appareil. Ce compromis peut avoir lieu pour les raisons suivantes :

  • L'appareil n'est pas compatible avec la résolution demandée.
  • L'appareil présente des problèmes de compatibilité, par exemple d'anciens appareils dont le fonctionnement correct requiert certaines résolutions.
  • Sur certains appareils, certains formats ne sont disponibles qu'avec certaines proportions.
  • L'appareil privilégie le "mod16 le plus proche" pour l'encodage JPEG ou vidéo. Pour en savoir plus, consultez SCALER_STREAM_CONFIGURATION_MAP.

Bien que CameraX crée et gère la session, vérifiez toujours les tailles d'image renvoyées dans la sortie du cas d'utilisation de votre code et apportez les ajustements nécessaires.

Rotation

Par défaut, la rotation de la caméra est définie pour correspondre à la rotation de l'écran par défaut lors de la création du cas d'utilisation. Dans ce cas par défaut, CameraX génère des sorties pour permettre à l'application de correspondre à ce que vous attendez de l'aperçu. Vous pouvez remplacer la rotation par une valeur personnalisée compatible avec les appareils multi-écrans en transmettant l'orientation d'affichage actuelle lors de la configuration des objets de cas d'utilisation ou de manière dynamique après leur création.

Votre application peut définir la rotation des cibles à l'aide des paramètres de configuration. Il peut ensuite mettre à jour les paramètres de rotation en utilisant les méthodes des API de cas d'utilisation (comme ImageAnalysis.setTargetRotation()), même lorsque le cycle de vie est en cours d'exécution. Vous pouvez utiliser cette option lorsque l'application est verrouillée en mode portrait (et donc qu'une rotation n'entraîne aucune reconfiguration), mais le cas d'utilisation photo ou analyse doit détecter la rotation actuelle de l'appareil. Par exemple, une détection de la rotation peut être nécessaire pour que les visages soient correctement orientés pour la détection de visages ou que les photos apparaissent en mode paysage ou portrait.

Les données des images capturées peuvent être stockées sans informations de rotation. Les données EXIF contiennent des informations de rotation permettant aux applications de galerie d'afficher l'image dans le bon sens après l'enregistrement.

Pour afficher les données d'aperçu avec la bonne orientation, vous pouvez utiliser la sortie de métadonnées de Preview.PreviewOutput() pour effectuer des modifications.

L'exemple de code suivant montre comment définir la rotation sur un événement d'orientation :

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

Selon la rotation définie, chaque cas d'utilisation effectue une rotation directe des données d'image ou fournit aux consommateurs des métadonnées de rotation des données d'image sans rotation.

  • Aperçu : la sortie des métadonnées est fournie afin que la rotation de la résolution cible soit connue à l'aide de Preview.getTargetRotation().
  • ImageAnalysis : la sortie de métadonnées est fournie afin que les coordonnées du tampon d'image soient connues par rapport aux coordonnées de l'écran.
  • ImageCapture : les métadonnées ou la mémoire tampon de l'image EXIF ou les deux seront modifiées pour prendre en compte le paramètre de rotation. La valeur modifiée dépend de l'intégration de HAL.

Rectangle de recadrage

Par défaut, le rectangle de recadrage correspond à celui du tampon complet. Vous pouvez le personnaliser avec ViewPort et UseCaseGroup. En regroupant les cas d'utilisation et en définissant la fenêtre d'affichage, CameraX garantit que les rectangles de recadrage de tous les cas d'utilisation du groupe pointent vers la même zone du capteur de la caméra.

L'extrait de code suivant montre comment utiliser ces deux classes :

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort définit le rectangle de tampon visible par les utilisateurs finaux. CameraX calcule ensuite le rectangle de recadrage le plus grand possible en fonction des propriétés de la fenêtre d'affichage et des cas d'utilisation associés. En règle générale, pour obtenir un effet WYSIWYG, vous pouvez configurer la fenêtre d'affichage en fonction du cas d'utilisation de l'aperçu. Un moyen simple d'obtenir la fenêtre d'affichage consiste à utiliser PreviewView.

Les extraits de code suivants montrent comment obtenir l'objet ViewPort :

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

Dans l'exemple précédent, ce que l'application obtient de l'élément ImageAnalysis et de l'élément ImageCapture correspond à ce que l'utilisateur final voit dans PreviewView, en supposant que le type d'échelle de PreviewView est défini sur la valeur par défaut, FILL_CENTER. Après avoir appliqué le recadrage et la rotation au tampon de sortie, l'image de tous les cas d'utilisation sera la même, mais peut-être avec des résolutions différentes. Pour en savoir plus sur la mise en œuvre des informations de transformation, consultez Sortie de la transformation.

Choix de la caméra

CameraX sélectionne automatiquement la meilleure caméra en fonction des exigences de votre application et des cas d'utilisation. Si vous souhaitez utiliser un appareil différent de celui sélectionné, plusieurs options s'offrent à vous :

L'exemple de code suivant montre comment créer un CameraSelector pour influencer la sélection des appareils :

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

Sélectionner plusieurs caméras simultanément

À partir de la version 1.3 de CameraX, vous pouvez également sélectionner plusieurs caméras simultanément. Par exemple, vous pouvez lier une caméra frontale et arrière pour prendre des photos ou enregistrer des vidéos simultanément depuis les deux perspectives.

Avec la fonctionnalité Caméra simultanée, l'appareil peut utiliser en même temps deux caméras avec des objectifs orientés dans des directions distinctes ou deux caméras arrière. Le bloc de code suivant montre comment définir deux caméras lors de l'appel de bindToLifecycle et comment obtenir les deux objets Camera à partir de l'objet ConcurrentCamera renvoyé.

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

Résolution de la caméra

Vous pouvez laisser CameraX définir la résolution de l'image selon les fonctionnalités de l'appareil, du niveau matériel compatible, du cas d'utilisation et du format fourni. Vous pouvez également définir une résolution cible ou un format spécifique dans les cas d'utilisation compatibles avec cette configuration.

Résolution automatique

CameraX peut déterminer automatiquement les meilleurs paramètres de résolution en fonction des cas d'utilisation spécifiés dans cameraProcessProvider.bindToLifecycle(). Dans la mesure du possible, indiquez tous les cas d'utilisation nécessaires pour une exécution simultanée dans une même session dans un seul appel bindToLifecycle(). CameraX détermine les résolutions en fonction de l'ensemble des cas d'utilisation liés en tenant compte du niveau de matériel compatible de l'appareil et de la variance spécifique (qui permet à un appareil de dépasser ou de ne pas atteindre les configurations de flux disponibles). L'objectif est de permettre à l'application de s'exécuter sur une grande variété d'appareils tout en minimisant les chemins de code propres à ceux-ci.

Le format par défaut des cas d'utilisation "Capture d'image" et "Analyse des images" est de 4:3.

Les cas d'utilisation présentent un format configurable permettant à l'application de préciser les proportions souhaitées en fonction de la mise en page de l'interface utilisateur. La sortie de CameraX est générée pour correspondre aux proportions demandées par l'appareil. Si aucune correspondance exacte de résolution n'est acceptée, celle qui remplit le plus de conditions est sélectionnée. Ainsi, l'application détermine la façon dont la caméra apparaît dans l'application, tandis que CameraX sélectionne les meilleurs paramètres de résolution de la caméra pour répondre à ce besoin sur différents appareils.

Par exemple, une application peut effectuer les opérations suivantes :

  • Définir une résolution cible de 4:3 ou 16:9 pour un cas d'utilisation
  • Spécifier une résolution personnalisée, que CameraX utilisera comme référence pour tenter de trouver la correspondance la plus proche
  • Indiquer un format de recadrage pour ImageCapture

CameraX sélectionne automatiquement les résolutions de surface interne de Camera2. Le tableau suivant présente les résolutions :

Cas d'utilisation Résolution de surface interne Résolution des données de sortie
Aperçu Format : résolution la mieux adaptée au paramètre de la cible. Résolution de surface interne. Des métadonnées sont fournies pour permettre un recadrage, une mise à l'échelle et une rotation du format cible.
Résolution par défaut : résolution d'aperçu la plus élevée ou résolution privilégiée par l'appareil correspondant au format de l'aperçu.
Résolution maximale : taille d'aperçu, qui correspond à la meilleure taille correspondant à la résolution d'écran de l'appareil ou à 1 080p (1 920 x 1 080 pixels), selon la taille la plus petite.
Analyse d'image Format : résolution la mieux adaptée au paramètre de la cible. Résolution de surface interne.
Résolution par défaut : le paramètre de résolution cible par défaut est 640 x 480. Si vous ajustez à la fois la résolution cible et le format correspondant, vous obtiendrez la meilleure résolution possible.
Résolution maximale : résolution de sortie maximale de l'appareil photo au format YUV_420_888, obtenue à partir de StreamConfigurationMap.getOutputSizes(). La résolution cible est définie par défaut sur 640 x 480. Si vous souhaitez une résolution supérieure à 640 x 480, vous devez donc utiliser setTargetResolution() et setTargetAspectRatio() pour obtenir la résolution la plus proche.
Capture d'image Proportions : format le mieux adapté au paramètre. Résolution de surface interne.
Résolution par défaut : résolution la plus élevée disponible ou résolution la plus adaptée pour l'appareil correspondant au format d'ImageCapture.
Résolution maximale : résolution de sortie maximale de la caméra au format JPEG. Utilisez StreamConfigurationMap.getOutputSizes() pour obtenir cette information.

Indiquer une résolution

Vous pouvez indiquer des résolutions spécifiques lorsque vous créez des cas d'utilisation à l'aide de la méthode setTargetResolution(Size resolution), comme l'illustre l'exemple de code suivant :

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

Vous ne pouvez pas définir à la fois un format et une résolution cibles pour le même cas d'utilisation. Cela entraînerait une erreur IllegalArgumentException lors de la création de l'objet de configuration.

Indiquez l'élément Size de la résolution dans le cadre des coordonnées après avoir effectué la rotation des tailles compatibles vers la rotation cible. Par exemple, un appareil en orientation naturelle portrait dans une rotation cible naturelle demandant une image au format portrait peut être en 480 x 640 ; après une rotation à 90 degrés et une orientation cible paysage, le même appareil peut être en 640 x 480.

La résolution cible tente de fixer une limite minimale pour la résolution de l'image. La résolution d'image réelle est la taille disponible la plus proche qui n'est pas inférieure à la résolution cible, comme déterminé par l'implémentation de la caméra.

Toutefois, si aucune résolution n'est supérieure ou égale à la résolution cible, la résolution disponible inférieure à la résolution cible la plus proche sera sélectionnée. Les résolutions avec le même format de Size fournie ont une priorité plus élevée que les résolutions de différents formats.

CameraX applique la résolution qui convient le mieux en fonction des requêtes. Si le besoin principal est de correspondre au format, indiquez uniquement setTargetAspectRatio. CameraX déterminera une résolution spécifique adaptée à l'appareil. Si le besoin principal de l'application est d'indiquer une résolution afin d'améliorer l'efficacité du traitement d'image (par exemple, une image de petite ou moyenne taille basée sur la capacité de traitement de l'appareil), utilisez setTargetResolution(Size resolution).

Si votre application requiert une résolution exacte, consultez le tableau dans createCaptureSession() pour connaître les résolutions maximales compatibles avec chaque niveau de matériel. Pour vérifier les résolutions spécifiques compatibles avec l'appareil actuel, consultez StreamConfigurationMap.getOutputSizes(int).

Si votre application s'exécute sous Android version 10 ou ultérieure, vous pouvez utiliser isSessionConfigurationSupported() pour valider une SessionConfiguration spécifique.

Contrôler la sortie de la caméra

En plus de vous permettre de configurer la sortie de la caméra selon les besoins pour chaque cas d'utilisation, CameraX intègre les interfaces suivantes pour assurer les opérations de la caméra communes à tous les cas d'utilisation liés :

  • CameraControl vous permet de configurer les fonctionnalités courantes de la caméra.
  • CameraInfo vous permet d'interroger les états de ces fonctionnalités courantes de la caméra.

Voici les fonctionnalités de la caméra compatibles avec CameraControl :

  • Zoom
  • Lampe de poche
  • Mise au point et mesures (appuyer pour effectuer la mise au point)
  • Correction d'exposition

Obtenir des instances de CameraControl et CameraInfo

Récupérez les instances de CameraControl et CameraInfo à l'aide de l'objet Camera renvoyé par ProcessCameraProvider.bindToLifecycle(). Le code suivant sert d'exemple :

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

Par exemple, vous pouvez effectuer un zoom et d'autres opérations de CameraControl après avoir appelé bindToLifecycle(). Une fois que vous avez arrêté ou détruit l'activité utilisée pour lier l'instance de caméra, CameraControl ne peut plus exécuter d'opérations et renvoie une erreur ListenableFuture.

Zoom

CameraControl propose deux méthodes pour modifier le zoom :

  • setZoomRatio() définit le zoom en fonction du ratio de zoom.

    Ce ratio doit être compris entre CameraInfo.getZoomState().getValue().getMinZoomRatio() et CameraInfo.getZoomState().getValue().getMaxZoomRatio(). Sinon, la fonction renvoie une erreur ListenableFuture.

  • setLinearZoom() définit le zoom actuel avec une valeur de zoom linéaire comprise entre 0 et 1.

    L'avantage du zoom linéaire est qu'il fait évoluer le champ de vision en fonction des changements de zoom. C'est donc idéal pour les vues Slider.

CameraInfo.getZoomState() renvoie un LiveData de l'état actuel du zoom. La valeur change lorsque la caméra est initialisée ou si le niveau de zoom est défini à l'aide de setZoomRatio() ou setLinearZoom(). L'appel de l'une ou l'autre méthode définit les valeurs de sauvegarde de ZoomState.getZoomRatio() et de ZoomState.getLinearZoom(). Cela s'avère utile si vous souhaitez afficher un texte avec un ratio de zoom à côté d'un curseur. Il vous suffit d'observer la LiveData de ZoomState pour mettre à jour les deux sans avoir à effectuer de conversion.

ListenableFuture renvoyé par les deux API permet aux applications d'être notifiées lorsqu'une demande récurrente avec la valeur de zoom spécifiée est terminée. En outre, si vous définissez une nouvelle valeur de zoom pendant que l'opération précédente est toujours en cours d'exécution, le ListenableFuture de l'opération de zoom précédente échoue immédiatement.

Lampe de poche

CameraControl.enableTorch(boolean) active ou désactive la lampe de poche.

CameraInfo.getTorchState() permet d'interroger l'état actuel de la lampe de poche. Vous pouvez vérifier la valeur renvoyée par CameraInfo.hasFlashUnit() pour déterminer si une lampe de poche est disponible. Si ce n'est pas le cas, l'appel de CameraControl.enableTorch(boolean) entraîne la fin immédiate du ListenableFuture renvoyé avec un résultat d'échec et définit l'état de la torche sur TorchState.OFF.

Lorsque la lampe de poche est activée, elle reste allumée pendant la capture photo et vidéo, quel que soit le paramètre flashMode. L'élément flashMode dans ImageCapture ne fonctionne que lorsque la lampe de poche est désactivée.

Mise au point et mesures

CameraControl.startFocusAndMetering() déclenche la mise au point automatique et la mesure de l'exposition en définissant les régions de mesure AF/AE/AWB (Automatic Focus / Automatic Exposure / Automatic White-balance) en fonction de l'action FocusMeteringAction donnée. La fonction est souvent utilisée pour intégrer la fonctionnalité "Appuyer pour effectuer la mise au point" dans de nombreuses applications de caméra.

MeteringPoint

Commencez par créer un élément MeteringPoint à l'aide de MeteringPointFactory.createPoint(float x, float y, float size). Un élément MeteringPoint représente un point unique de la caméra Surface. Il est stocké dans un format normalisé afin de pouvoir être facilement converti en coordonnées de capteurs destinées à déterminer les régions de AF/AE/AWB.

La taille de MeteringPoint varie de 0 à 1, avec une taille par défaut de 0,15 f. Lors de l'appel de MeteringPointFactory.createPoint(float x, float y, float size), CameraX crée une zone rectangulaire centrée sur (x, y) pour l'élément size fourni.

Le code suivant montre comment créer un MeteringPoint :

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering et FocusMeteringAction

Pour appeler startFocusAndMetering(), les applications doivent créer une FocusMeteringAction, qui se compose d'un ou plusieurs MeteringPoints avec des combinaisons de mode de mesure facultatives de FLAG_AF, FLAG_AE et FLAG_AWB. Le code suivant illustre cette utilisation :

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

Comme indiqué dans le code précédent, startFocusAndMetering() utilise un FocusMeteringAction composé d'un MeteringPoint pour les régions de mesure AF/AE/AWB et d'un autre MeteringPoint pour AF et AE uniquement.

En interne, CameraX la convertit en MeteringRectangles de Camera2 et définit les paramètres CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS correspondant à la demande de capture.

Tous les appareils n'étant pas compatibles avec AF/AE/AWB et plusieurs régions, CameraX exécute la FocusMeteringAction au mieux. CameraX utilise le maximum de MeteringPoints acceptés, dans l'ordre dans lequel ils ont été ajoutés. Tous les MeteringPoints ajoutés après le nombre maximal sont ignorés. Par exemple, si une FocusMeteringAction est fournie avec trois MeterPoints sur une plate-forme compatible avec seulement deux, seuls les deux premiers sont utilisés. Le dernier MeteringPoint est ignoré par CameraX.

Correction d'exposition

La correction d'exposition est utile lorsque les applications doivent ajuster les valeurs d'exposition au-delà du résultat de sortie d'exposition automatique. Les valeurs de correction d'exposition sont combinées de la manière suivante pour déterminer l'exposition nécessaire pour les conditions d'image actuelles :

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX fournit la fonction Camera.CameraControl.setExposureCompensationIndex() permettant de définir la correction d'exposition en tant que valeur d'indice.

Les valeurs d'indice positives augmentent la luminosité de l'image, tandis que les valeurs négatives la réduisent. Les applications peuvent interroger la plage acceptée par CameraInfo.ExposureState.exposureCompensationRange() décrite dans la section suivante. Si la valeur est acceptée, la valeur ListenableFuture renvoyée se termine lorsque la valeur est correctement activée dans la demande de capture. Si l'indice spécifié se trouve en dehors de la plage acceptée, setExposureCompensationIndex() entraîne la fin immédiate de l'élément ListenableFuture renvoyé, avec un échec du résultat.

CameraX ne conserve que la dernière demande setExposureCompensationIndex() en attente. Si vous appelez la fonction plusieurs fois avant que la demande précédente ne soit exécutée, elle est annulée.

L'extrait de code suivant définit un index de correction d'exposition et enregistre un rappel lorsque la demande de modification de l'exposition est exécutée :

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      …
   }, mainExecutor)
  • Camera.CameraInfo.getExposureState() récupère la valeur ExposureState actuelle, y compris :

    • La compatibilité du contrôle de la correction d'exposition
    • L'indice de correction d'exposition actuel
    • La plage d'indices de correction d'exposition
    • L'étape de correction d'exposition utilisée dans le calcul de la valeur de correction d'exposition

Par exemple, le code suivant initialise les paramètres d'une exposition SeekBar avec les valeurs ExposureState actuelles :

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

Ressources supplémentaires

Pour en savoir plus sur CameraX, consultez les ressources supplémentaires suivantes.

Atelier de programmation

  • Premiers pas avec CameraX
  • Exemple de code

  • Applications exemples de CameraX
  • Communauté de développeurs

    Groupe de discussion Android CameraX