Remarque : Cette page fait référence au package Camera2. À moins que votre application ne nécessite des fonctionnalités spécifiques de bas niveau de Camera2, nous vous recommandons d'utiliser CameraX. CameraX et Camera2 sont compatibles avec Android 5.0 (niveau d'API 21) ou version ultérieure.
Un même appareil Android peut être équipé de plusieurs caméras. Chaque caméra est un CameraDevice, et un CameraDevice peut générer plusieurs flux simultanément.
Cela permet d'optimiser un flux (les images séquentielles de la caméra provenant d'un CameraDevice) pour une tâche spécifique, comme l'affichage d'un viseur, tandis que d'autres flux peuvent être utilisés pour prendre une photo ou enregistrer une vidéo. Les flux agissent comme des pipelines parallèles qui traitent les images brutes provenant de la caméra, une image à la fois :
Le traitement parallèle suggère qu'il peut y avoir des limites de performances en fonction de la puissance de traitement disponible du processeur, du GPU ou d'un autre processeur. Si un pipeline ne parvient pas à suivre le rythme des frames entrants, il commence à les supprimer.
Chaque pipeline a son propre format de sortie. Les données brutes entrantes sont automatiquement transformées au format de sortie approprié par une logique implicite associée à chaque pipeline. La valeur CameraDevice utilisée dans les exemples de code de cette page n'est pas spécifique. Vous devez donc d'abord énumérer toutes les caméras disponibles avant de continuer.
Vous pouvez utiliser le CameraDevice pour créer un CameraCaptureSession, qui est spécifique à ce CameraDevice. Un CameraDevice doit recevoir une configuration de frame pour chaque frame brut à l'aide de CameraCaptureSession. La configuration spécifie les attributs de l'appareil photo, tels que l'autofocus, l'ouverture, les effets et l'exposition. En raison de contraintes matérielles, une seule configuration est active dans le capteur de l'appareil photo à un moment donné. Il s'agit de la configuration active.
Toutefois, les cas d'utilisation de flux améliorent et étendent les anciennes méthodes d'utilisation de CameraDevice pour les sessions de capture de flux, ce qui vous permet d'optimiser le flux de la caméra pour votre cas d'utilisation particulier. Par exemple, il peut améliorer l'autonomie de la batterie lors de l'optimisation des appels vidéo.
Un CameraCaptureSession décrit tous les pipelines possibles liés à CameraDevice. Une fois qu'une session est créée, vous ne pouvez pas ajouter ni supprimer de pipelines.
CameraCaptureSession gère une file d'attente de CaptureRequest, qui deviennent la configuration active.
Un CaptureRequest ajoute une configuration à la file d'attente et sélectionne un, plusieurs ou tous les pipelines disponibles pour recevoir une image du CameraDevice. Vous pouvez envoyer de nombreuses requêtes de capture pendant la durée de vie d'une session de capture. Chaque requête peut modifier la configuration active et l'ensemble de pipelines de sortie qui reçoivent l'image brute.
Utiliser des cas d'utilisation de flux pour de meilleures performances
Les cas d'utilisation de flux permettent d'améliorer les performances des sessions de capture Camera2. Ils fournissent à l'appareil plus d'informations pour régler les paramètres, ce qui améliore l'expérience de l'appareil photo pour votre tâche spécifique.
Cela permet à l'appareil photo d'optimiser les pipelines matériels et logiciels de l'appareil photo en fonction des scénarios utilisateur pour chaque flux. Pour en savoir plus sur les cas d'utilisation de flux, consultez setStreamUseCase.
Les cas d'utilisation de flux vous permettent de spécifier plus en détail comment un flux de caméra particulier est utilisé, en plus de définir un modèle dans CameraDevice.createCaptureRequest(). Cela permet au matériel de la caméra d'optimiser les paramètres, tels que le réglage, le mode du capteur ou les paramètres du capteur de la caméra, en fonction des compromis de qualité ou de latence adaptés à des cas d'utilisation spécifiques.
Voici quelques cas d'utilisation des flux :
DEFAULT: couvre tous les comportements existants de l'application. Cela revient à ne définir aucun cas d'utilisation de flux.PREVIEW: recommandé pour l'analyse d'images dans l'application ou dans le viseur.STILL_CAPTURE: optimisé pour une capture haute résolution de haute qualité, et ne devrait pas maintenir des fréquences d'images semblables à celles de l'aperçu.VIDEO_RECORD: optimisé pour la capture vidéo de haute qualité, y compris la stabilisation d'image de haute qualité, si elle est compatible avec l'appareil et activée par l'application. Cette option peut produire des images de sortie avec un décalage important par rapport au temps réel, afin de permettre une stabilisation ou un autre traitement de la plus haute qualité.VIDEO_CALL: recommandé pour les utilisations prolongées de la caméra lorsque la consommation d'énergie est un problème.PREVIEW_VIDEO_STILL: recommandé pour les applications de réseaux sociaux ou les cas d'utilisation de flux unique. Il s'agit d'un flux polyvalent.VENDOR_START: utilisé pour les cas d'utilisation définis par l'OEM.
Créer une CameraCaptureSession
Pour créer une session de caméra, fournissez-lui un ou plusieurs tampons de sortie dans lesquels votre application peut écrire des images de sortie. Chaque tampon représente un pipeline. Vous devez effectuer cette opération avant de commencer à utiliser la caméra afin que le framework puisse configurer les pipelines internes de l'appareil et allouer des tampons de mémoire pour envoyer des images aux cibles de sortie nécessaires.
L'extrait de code suivant montre comment préparer une session de caméra avec deux tampons de sortie, l'un appartenant à un SurfaceView et l'autre à un ImageReader. L'ajout du cas d'utilisation de flux PREVIEW à previewSurface et du cas d'utilisation de flux STILL_CAPTURE à imReaderSurface permet au matériel de l'appareil d'optimiser encore davantage ces flux.
// Retrieve the target surfaces, which might be coming from a number of places: // 1. SurfaceView, if you want to display the image directly to the user // 2. ImageReader, if you want to read each frame or perform frame-by-frame // analysis // 3. OpenGL Texture or TextureView, although discouraged for maintainability // reasons // 4. RenderScript.Allocation, if you want to do parallel processing val surfaceView = findViewById<SurfaceView>( // ... ) val imageReader = ImageReader.newInstance( // ... ) // Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated(). val previewSurface = surfaceView.holder.surface val imReaderSurface = imageReader.surface val targets = listOf(previewSurface, imReaderSurface) // Create a capture session using the predefined targets; this also involves // defining the session state callback to be notified of when the session is // ready. // Setup Stream Use Case while setting up your Output Configuration. @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun configureSession(device: CameraDevice, targets: List<Surface>) { val configs = mutableListOf<OutputConfiguration>() val streamUseCase = CameraMetadata .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL targets.forEach { val config = OutputConfiguration(it) config.streamUseCase = streamUseCase.toLong() configs.add(config) } // ... device.createCaptureSession(session) }
À ce stade, vous n'avez pas défini la configuration active de la caméra. Une fois la session configurée, vous pouvez créer et envoyer des requêtes de capture pour ce faire.
La transformation appliquée aux entrées lorsqu'elles sont écrites dans leur tampon est déterminée par le type de chaque cible, qui doit être un Surface. Le framework Android sait comment convertir une image brute de la configuration active dans un format adapté à chaque cible. La conversion est contrôlée par le format de pixel et la taille de l'élément Surface concerné.
Le framework fait de son mieux, mais certaines combinaisons de configuration Surface peuvent ne pas fonctionner, ce qui peut entraîner des problèmes tels que la non-création de la session, une erreur d'exécution lors de l'envoi d'une requête ou une dégradation des performances. Le framework fournit des garanties pour des combinaisons spécifiques d'appareils, de surfaces et de paramètres de requête. Pour en savoir plus, consultez la documentation sur createCaptureSession().
CaptureRequests uniques
La configuration utilisée pour chaque frame est encodée dans un CaptureRequest, qui est envoyé à l'appareil photo. Pour créer une requête de capture, vous pouvez utiliser l'un des modèles prédéfinis ou TEMPLATE_MANUAL pour un contrôle total. Lorsque vous choisissez un modèle, vous devez fournir un ou plusieurs tampons de sortie à utiliser avec la requête. Vous ne pouvez utiliser que les tampons qui ont déjà été définis sur la session de capture que vous souhaitez utiliser.
Les requêtes de capture utilisent un modèle de compilateur et permettent aux développeurs de définir de nombreuses options, y compris l'exposition automatique, l'autofocus et l'ouverture de l'objectif. Avant de définir un champ, assurez-vous que l'option spécifique est disponible pour l'appareil en appelant CameraCharacteristics.getAvailableCaptureRequestKeys() et que la valeur choisie est compatible en vérifiant la caractéristique appropriée de l'appareil photo, comme les modes d'exposition automatique disponibles.
Pour créer une requête de capture pour un SurfaceView à l'aide du modèle conçu pour l'aperçu sans aucune modification, utilisez CameraDevice.TEMPLATE_PREVIEW :
val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequest.addTarget(previewSurface)
Une fois la requête de capture définie, vous pouvez l'envoyer à la session de l'appareil photo :
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest() // The first null argument corresponds to the capture callback, which you // provide if you want to retrieve frame metadata or keep track of failed capture // requests that can indicate dropped frames; the second null argument // corresponds to the Handler used by the asynchronous callback, which falls // back to the current thread's looper if null. session.capture(captureRequest.build(), null, null)
Lorsqu'une image de sortie est placée dans le tampon spécifique, un rappel de capture est déclenché. Dans de nombreux cas, des rappels supplémentaires, tels que ImageReader.OnImageAvailableListener, sont déclenchés lorsque le frame qu'il contient est traité. C'est à ce moment-là que vous pouvez récupérer les données d'image du tampon spécifié.
Répéter les requêtes CaptureRequest
Les requêtes d'appareil photo unique sont simples à effectuer, mais elles ne sont pas très utiles pour afficher un aperçu en direct ou une vidéo. Dans ce cas, vous devez recevoir un flux continu d'images, et pas seulement une seule. L'extrait de code suivant montre comment ajouter une requête répétée à la session :
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest() // This keeps sending the capture request as frequently as possible until // the session is torn down or session.stopRepeating() is called. session.setRepeatingRequest(captureRequest.build(), null, null)
Une requête de capture répétée permet à l'appareil photo de capturer des images en continu à l'aide des paramètres du CaptureRequest fourni. L'API Camera2 permet également aux utilisateurs d'enregistrer des vidéos à partir de l'appareil photo en envoyant des CaptureRequests répétés, comme illustré dans ce exemple de dépôt Camera2 sur GitHub. Il peut également afficher une vidéo au ralenti en capturant une vidéo à grande vitesse (au ralenti) à l'aide d'une rafale répétée CaptureRequests, comme illustré dans l'application exemple de vidéo au ralenti Camera2 sur GitHub.
Entrelacer les requêtes CaptureRequest
Pour envoyer une deuxième requête de capture alors que la requête de capture répétée est active (par exemple, pour afficher un viseur et permettre aux utilisateurs de prendre une photo), vous n'avez pas besoin d'arrêter la requête répétée en cours. À la place, vous émettez une requête de capture non répétée pendant que la requête répétée continue de s'exécuter.
Tout tampon de sortie utilisé doit être configuré dans le cadre de la session de l'appareil photo lors de la création de la session. Les requêtes répétées ont une priorité inférieure à celle des requêtes à image unique ou en rafale, ce qui permet à l'exemple suivant de fonctionner :
val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback // Create the repeating request and dispatch it. val repeatingRequest = session.device.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW ) repeatingRequest.addTarget(previewSurface) session.setRepeatingRequest(repeatingRequest.build(), null, null) // Some time later... // Create the single request and dispatch it. // NOTE: This can disrupt the ongoing repeating request momentarily. val singleRequest = session.device.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE ) singleRequest.addTarget(imReaderSurface) session.capture(singleRequest.build(), null, null)
Cette approche présente toutefois un inconvénient : vous ne savez pas exactement quand la requête unique se produit. Dans la figure suivante, si A est la requête de capture répétée et B est la requête de capture d'une seule image, voici comment la session traite la file d'attente des requêtes :
Il n'y a aucune garantie concernant la latence entre la dernière requête répétée de A avant l'activation de la requête B et la prochaine utilisation de A. Vous risquez donc de rencontrer des images ignorées. Voici quelques mesures que vous pouvez prendre pour atténuer ce problème :
Ajoutez les cibles de sortie de la requête A à la requête B. Ainsi, lorsque le frame de B est prêt, il est copié dans les cibles de sortie de A. Par exemple, cela est essentiel pour maintenir une fréquence d'images stable lors de la capture d'instantanés vidéo. Dans le code précédent, vous ajoutez
singleRequest.addTarget(previewSurface)avant de créer la requête.Utilisez une combinaison de modèles conçus pour fonctionner dans ce scénario particulier, comme le délai d'obturation nul.