Sessions et requêtes de capture d'appareil photo

Remarque:Cette page fait référence au package Camera2. Nous vous recommandons d'utiliser CameraX, sauf si votre application nécessite des fonctionnalités spécifiques de base de Camera2. 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 notamment qu'un flux unique, des images séquentielles de l'appareil photo à partir d'un CameraDevice, est optimisé pour une tâche spécifique, comme afficher un viseur, tandis que d'autres peuvent servir à prendre une photo ou à enregistrer une vidéo. Les flux agissent comme des pipelines parallèles qui traitent les trames brutes. qui sortent de la caméra,une image à la fois:

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

Le traitement en parallèle suggère qu'il peut exister des limites de performances selon la puissance de traitement disponible du CPU, du GPU ou d'un autre processeur. Si un le pipeline ne peut pas suivre le rythme des frames entrants, il commence à les supprimer.

Chaque pipeline a son propre format de sortie. Les données brutes qui arrivent sont automatiquement format de sortie par logique implicite associées à chaque pipeline. Le CameraDevice utilisé tout au long des exemples de code 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 à ce CameraDevice. Un CameraDevice doit recevoir une configuration de frame pour chaque frame brut à l'aide de CameraCaptureSession. La spécifie les attributs de l'appareil photo : autofocus, ouverture, effets, et l'exposition. En raison de contraintes matérielles, une seule configuration dans le capteur de l'appareil photo à un moment donné. C'est ce que l'on appelle active.

Cependant, les cas d'utilisation des flux améliorent et étendent les façons précédentes d'utiliser CameraDevice. pour diffuser les sessions de capture, ce qui vous permet d'optimiser le flux de la caméra un cas d'utilisation particulier. Par exemple, il peut améliorer l'autonomie de la batterie lors de l'optimisation appels vidéo.

Un CameraCaptureSession décrit tous les pipelines possibles liés au CameraDevice Lorsqu'une session est créée, vous ne pouvez pas ajouter ni supprimer de pipelines. Le CameraCaptureSession gère une file d'attente de CaptureRequest, qui deviennent la configuration active.

Un CaptureRequest ajoute une configuration à la file d'attente et en sélectionne une, de plus de l'un ou l'ensemble des pipelines disponibles pour recevoir une trame du CameraDevice Vous pouvez envoyer de nombreuses demandes de capture tout au long de la durée de vie d'une capture session. Chaque requête peut modifier la configuration active et l'ensemble des résultats qui reçoivent l'image brute.

Utiliser des cas d'utilisation de flux pour de meilleures performances

Les cas d'utilisation des flux permettent d'améliorer les performances de la capture Camera2 sessions. Elles donnent au dispositif matériel plus d'informations pour régler les paramètres, ce qui améliore l'expérience de la caméra pour votre tâche spécifique.

Ce permet à l'appareil photo d'optimiser les pipelines logiciels et matériels de la caméra en fonction de scénarios utilisateur pour chaque flux. Pour en savoir plus sur l'utilisation du flux Pour les cas, consultez setStreamUseCase.

Les cas d'utilisation des flux vous permettent de spécifier l'utilisation d'un flux de caméra spécifique plus de détails, en plus de définir un modèle dans CameraDevice.createCaptureRequest() Cela permet au matériel de l'appareil photo d'optimiser tels que les réglages, le mode du capteur ou les réglages du capteur de l'appareil photo, 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 équivaut à ne pas en définissant un cas d'utilisation de flux.

  • PREVIEW: recommandé pour l'analyse d'images dans le viseur ou dans l'application.

  • STILL_CAPTURE: optimisé pour la capture haute résolution de haute qualité, et non devrait maintenir des fréquences d'images semblables à celles d'un aperçu.

  • VIDEO_RECORD: optimisé pour la capture vidéo de haute qualité, y compris des images de haute qualité stabilisation de l'image, si elle est compatible avec l'appareil et activée par l'application. Cette option peut produire des frames de sortie avec un décalage important par rapport au temps réel, pour permettre une stabilisation ou un autre traitement de haute qualité.

  • VIDEO_CALL: recommandé pour les utilisations de longue durée des caméras lorsqu'une consommation électrique problème.

  • PREVIEW_VIDEO_STILL: recommandé pour les applications de réseaux sociaux ou l'utilisation d'un seul flux cas d'utilisation. 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 votre application peut écrire des frames de sortie. Chaque tampon représente un pipeline. Vous devez avant d'utiliser l'appareil photo pour que le framework puisse configurer aux pipelines internes de l'appareil et allouent des tampons de mémoire pour l'envoi de trames aux cibles de sortie requises.

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

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 envoyer une capture. les requêtes pour y parvenir.

La transformation appliquée aux entrées telles qu'elles sont écrites dans leur tampon est déterminé par le type de chaque cible, qui doit être Surface Le framework Android sait comment convertir une image brute de la configuration active dans un format approprié chaque cible. La conversion est contrôlée par le format et la taille en pixels du un Surface spécifique.

Le framework essaie de faire de son mieux, mais certains Surface les combinaisons de configuration peuvent ne pas fonctionner, ce qui peut entraîner des problèmes en cours de création, une erreur d'exécution est générée lorsque vous envoyez une requête ; et de dégradation des performances. Le framework fournit des garanties pour des des combinaisons de paramètres d'appareil, de surface et de requête. La documentation createCaptureSession() fournit plus d'informations.

Demandes de capture uniques

La configuration utilisée pour chaque trame est encodée dans un CaptureRequest, qui est envoyé à la caméra. Pour créer une demande de capture, vous pouvez utiliser l'un des prédéfini templates, Vous pouvez aussi utiliser TEMPLATE_MANUAL pour bénéficier d'un contrôle total. Lorsque vous choisissez vous devez fournir un ou plusieurs tampons de sortie à utiliser la demande. Vous ne pouvez utiliser que des tampons déjà définis sur la capture que vous prévoyez d'utiliser.

Les demandes de capture utilisent schéma de création et donner aux développeurs la possibilité de définir de nombreuses options, y compris exposition automatique, l'autofocus, et ouverture de l'objectif. Avant de définir un champ, assurez-vous que l'option spécifique est disponible pour la appareil en appelant CameraCharacteristics.getAvailableCaptureRequestKeys() et que la valeur souhaitée est prise en charge en vérifiant l'appareil photo comme l'exposition automatique disponible différents modes.

Créer une demande de capture pour un SurfaceView à l'aide du modèle prévisualisée sans 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);

Maintenant qu'une demande de capture est définie, vous pouvez envoyer à la session 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'une image de sortie est placée dans le tampon spécifique, une capture rappel est déclenché. Dans de nombreux cas, des rappels supplémentaires, tels que ImageReader.OnImageAvailableListener se déclenche lorsque le frame qu'il contient est traité. Elle est à vous pouvez récupérer les données d'image à partir du tampon spécifié.

Requêtes de capture répétées

Les demandes à caméra unique sont simples à utiliser, mais elles permettent d'afficher un aperçu ou une vidéo, ils ne sont pas très utiles. Dans ce cas, vous devez recevoir un un flux continu de trames, pas seulement une seule. L'extrait de code suivant montre comment ajouter demande 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);

Lorsque les requêtes sont répétées, l'appareil photo capture en continu à l'aide des paramètres du fichier CaptureRequest fourni. L'API Camera2 permet également aux utilisateurs d'enregistrer une vidéo à partir de la caméra en envoyant "CaptureRequests", comme vous le voyez ici, Exemple Camera2 sur GitHub. Il peut également restituer une vidéo au ralenti en capturant un vidéo rapide (au ralenti) avec une rafale répétée CaptureRequests comme indiqué dans l'application exemple de vidéo au ralenti de CameraX sur GitHub.

Entrelacement de 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, procédez comme suit : (par exemple, pour afficher un viseur et permettre aux utilisateurs de prendre une photo), vous n'avez pas besoin pour arrêter la requête récurrente en cours. Au lieu de cela, vous émettez une capture non récurrente pendant que la requête récurrente continue de s'exécuter.

Le tampon de sortie utilisé doit être configuré dans le cadre de la session d'appareil photo. lors de la création de la session. Les requêtes répétées ont une priorité inférieure à de requêtes uniques ou intensives, qui permettent à 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 cependant un inconvénient: vous ne savez pas exactement quand la demande unique se produit. Dans la figure suivante, si A est la demande de capture et B est la demande de capture d'image unique. Voilà comment la requête traite la file d'attente des requêtes:

Figure 2 Illustration d'une file d'attente de requêtes pour la session d'appareil photo 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 demande B et la prochaine fois que la requête A est utilisée Il se peut donc que des images soient ignorées. 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. De cette façon, lorsque Le frame de B est prêt, il est copié dans les cibles de sortie de A. C'est essentiel, par exemple, lorsque vous créez des instantanés vidéo pour maintenir une fréquence d'images stable. 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 pour ce scénario particulier, comme le déclenchement instantané.