Sessions et requêtes de capture d'appareil photo

Remarque:Cette page fait référence au package Camera2. Sauf si votre application 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 doté de plusieurs caméras. Chaque caméra est une CameraDevice, et une CameraDevice peut générer plusieurs flux simultanément.

L'une des raisons est qu'un flux, les images séquentielles provenant d'un CameraDevice, est optimisé pour une tâche spécifique, comme l'affichage d'un viseur, tandis que d'autres peuvent être utilisés pour prendre une photo ou pour effectuer un enregistrement vidéo.Les flux agissent comme des pipelines parallèles qui traitent les images brutes sortant de l'appareil photo,une image à la fois:

Figure 1 Illustration tirée de la création d'une application d'appareil photo universelle (Google I/O 2018)

Le traitement en 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 les frames entrants, il commence à les abandonner.

Chaque pipeline possède son propre format de sortie. Les données brutes entrantes sont automatiquement transformées au format de sortie approprié par la logique implicite associée à chaque pipeline. Le CameraDevice utilisé 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 CameraDevice pour créer un CameraCaptureSession, qui est spécifique à cet CameraDevice. Un CameraDevice doit recevoir une configuration de frame pour chaque frame brut à l'aide de CameraCaptureSession. Cette 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 à un moment donné dans le capteur de l'appareil photo. C'est ce que l'on appelle la configuration active.

Toutefois, les cas d'utilisation de flux améliorent et étendent les méthodes précédentes d'utilisation de CameraDevice pour diffuser des 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, cela 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. Lors de la création d'une session, 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 trame de CameraDevice. Vous pouvez envoyer de nombreuses requêtes de capture au cours d'une session de capture. Chaque requête peut modifier la configuration active et l'ensemble des 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. Elles donnent à l'appareil physique 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 à la caméra d'optimiser les pipelines matériels et logiciels de la caméra en fonction de scénarios utilisateur pour chaque flux. Pour en savoir plus sur les cas d'utilisation des flux, consultez setStreamUseCase.

Les cas d'utilisation de flux vous permettent de spécifier plus en détail la manière dont un flux de caméra spécifique est utilisé, en plus de définir un modèle dans CameraDevice.createCaptureRequest(). Cela permet au matériel de l'appareil photo d'optimiser des paramètres tels que le réglage, le mode du capteur ou les paramètres du capteur de l'appareil photo, en fonction de compromis en termes de qualité ou de latence, adaptés à des cas d'utilisation spécifiques.

Voici quelques cas d'utilisation de 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 avec le viseur ou dans l'application.

  • STILL_CAPTURE: optimisé pour la capture haute résolution de haute qualité et ne doit pas maintenir des fréquences d'images semblables à celles des aperçus.

  • VIDEO_RECORD: optimisé pour la capture vidéo de haute qualité, y compris la stabilisation d'image de haute qualité, si la fonctionnalité 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 haute qualité.

  • VIDEO_CALL: recommandé pour les utilisations de longue durée de la caméra où la consommation d'énergie est problématique.

  • PREVIEW_VIDEO_STILL: recommandé pour les applications de réseaux sociaux ou les cas d'utilisation de flux unique. C'est une diffusion polyvalente.

  • VENDOR_START: utilisé pour les cas d'utilisation définis par l'OEM

Créer une CameraCaptureSession

Pour créer une session d'appareil photo, 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 l'appareil photo afin que le framework puisse configurer les pipelines internes de l'appareil et allouer des tampons de mémoire pour l'envoi des trames aux cibles de sortie nécessaires.

L'extrait de code suivant montre comment préparer une session d'appareil photo avec deux tampons de sortie, l'un appartenant à un SurfaceView et l'autre à ImageReader. L'ajout du cas d'utilisation du flux PREVIEW à previewSurface et du cas d'utilisation du flux STILL_CAPTURE à imReaderSurface permet au matériel de l'appareil d'optimiser encore davantage ces flux.

Kotlin


// 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)
}

Java


// 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. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// 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
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        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 distribuer des requêtes de capture à cet effet.

La transformation appliquée aux entrées telles qu'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 la Surface spécifique.

Le framework tente de donner le meilleur d'eux-mêmes, 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, la génération d'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 de paramètres d'appareil, de surface et de requête. Pour en savoir plus, consultez la documentation concernant createCaptureSession().

Requêtes de capture uniques

La configuration utilisée pour chaque image est encodée dans un CaptureRequest, envoyé à la caméra. Pour créer une requête de capture, vous pouvez utiliser l'un des modèles prédéfinis ou utiliser 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 des tampons déjà définis sur la session de capture que vous souhaitez utiliser.

Les requêtes de capture utilisent un schéma de compilateur et permettent aux développeurs de définir de nombreuses options différentes, y compris l'exposition automatique, la mise au point automatique 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 souhaitée est acceptée en vérifiant les caractéristiques appropriées de l'appareil photo, telles que 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 la prévisualisation sans aucune modification, utilisez CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

Après avoir défini une requête de capture, vous pouvez maintenant l'envoyer à la session de caméra:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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'un frame de sortie est placé 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 stade que vous pouvez récupérer les données d'image à partir du tampon spécifié.

Répéter des requêtes de capture

Les demandes de caméra unique sont simples à réaliser, 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 de trames, et non un flux unique. L'extrait de code suivant montre comment ajouter une requête répétée à la session:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // 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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // 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 oblige l'appareil photo à capturer en continu des images à l'aide des paramètres du CaptureRequest fourni. L'API Camera2 permet également aux utilisateurs d'enregistrer une vidéo à partir de la caméra en envoyant la répétition CaptureRequests, comme indiqué dans l'exemple de dépôt Camera2 sur GitHub. Il peut également afficher une vidéo au ralenti en capturant une vidéo à haute vitesse (ralentissement) à l'aide de la répétition des rafales CaptureRequests, comme indiqué dans l'application exemple de vidéo au ralenti de Camera2 sur GitHub.

Entrelacement des requêtes de capture

Pour envoyer une deuxième requête de capture lorsque 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écurrente en cours. Au lieu de cela, vous émettez une requête de capture non répétitive pendant que la requête répétée continue de s'exécuter.

Tout tampon de sortie utilisé doit être configuré dans la session de caméra au moment de sa création. Les requêtes répétées ont une priorité inférieure à celle des requêtes à une seule image ou en rafale, ce qui permet à l'exemple suivant de fonctionner:

Kotlin

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)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().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
CaptureRequest.Builder singleRequest =
session.getDevice().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 correspond à la requête de capture répétée et B à la requête de capture d'une seule image, voici comment la session traite la file d'attente de requêtes:

Figure 2 Illustration d'une file d'attente de requêtes pour la session de caméra en cours

La latence n'est pas garantie entre la dernière requête répétée de A avant l'activation de la requête B et la prochaine fois que A sera à nouveau utilisé. Il est donc possible que des frames soient ignorés. Il y a certaines choses que vous pouvez faire pour atténuer ce problème:

  • Ajoutez les cibles de sortie de la requête A à la requête B. Ainsi, lorsque la trame de B est prête, elle est copiée dans les cibles de sortie de A. C'est essentiel, par exemple, lorsque vous créez des instantanés vidéo afin de maintenir une fréquence d'images stable. Dans le code précédent, vous devez ajouter 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, tels qu'un déclenchement sans délai de l'obturateur.