Cette page présente l'architecture de CameraX, y compris sa structure, et explique comment utiliser l'API, les cycles de vie et comment combiner des cas d'utilisation.
Structure de CameraX
Vous pouvez utiliser CameraX pour faire l'interface avec la caméra d'un appareil via une abstraction appelée "cas d'utilisation". Les cas d'utilisation suivants sont disponibles :
- Preview (Aperçu) : accepte une surface pour y afficher un aperçu, comme une
PreviewView
. - Image analysis (Analyse des images) : fournit des tampons accessibles par le processeur pour l'analyse, par exemple pour le machine learning.
- Image capture (Capture d'image) : prend et enregistre une photo.
- Video capture (Capture vidéo) : capture un contenu vidéo et audio avec
VideoCapture
.
Les cas d'utilisation peuvent être combinés et actifs en même temps. Par exemple, une application peut permettre à l'utilisateur de visualiser l'image que voit la caméra à l'aide d'un cas d'utilisation "Aperçu", exploiter un cas d'utilisation "Analyse des images" qui détermine si les personnes sur la photo sourient, et inclure un cas d'utilisation "Capture d'image" pour prendre une photo dès qu'elles le font.
Modèle d'API
Pour utiliser la bibliothèque, vous devez spécifier les éléments suivants :
- Le cas d'utilisation souhaité ainsi que les options de configuration ;
- Quoi faire des données de sortie en joignant des écouteurs ;
- Le flux visé, par exemple à quels moments activer les caméras et produire des données, en liant le cas d'utilisation aux cycles de vie des composants de l'architecture Android.
Il existe deux façons d'écrire une application CameraX : CameraController
(idéal pour une utilisation la plus simple possible de CameraX) ou CameraProvider
(si vous avez besoin de plus de flexibilité).
CameraController
Un CameraController
fournit la plupart des fonctionnalités de base de CameraX dans une seule classe. Il nécessite peu de code de configuration et gère automatiquement l'initialisation de l'appareil photo, la gestion des cas d'utilisation, la rotation cible, la mise au point en appuyant sur l'aperçu, le pincement pour zoomer, etc. La classe concrète qui étend CameraController
est LifecycleCameraController
.
Kotlin
val previewView: PreviewView = viewBinding.previewView var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA previewView.controller = cameraController
Java
PreviewView previewView = viewBinding.previewView; LifecycleCameraController cameraController = new LifecycleCameraController(baseContext); cameraController.bindToLifecycle(this); cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA); previewView.setController(cameraController);
Les cas d'utilisation (UseCase
) par défaut pour CameraController
sont Preview
, ImageCapture
et ImageAnalysis
. Pour désactiver ImageCapture
ou ImageAnalysis
, ou encore pour activer VideoCapture
, utilisez la méthode setEnabledUseCases()
.
Pour découvrir d'autres utilisations de CameraController
, consultez l'exemple de lecteur de code QR ou la vidéo présentant les principes de base de CameraController
.
CameraProvider
Un CameraProvider
reste facile à utiliser, mais étant donné que le développeur de l'application gère une plus grande partie de la configuration, les possibilités de personnalisation sont plus nombreuses. Par exemple, il est possible d'activer la rotation de l'image de sortie ou de définir le format de l'image de sortie dans ImageAnalysis
. Vous pouvez également utiliser une Surface
personnalisée pour l'aperçu de l'appareil photo pour plus de flexibilité, tandis qu'avec CameraController, vous devez utiliser une PreviewView
. L'utilisation du code Surface
existant peut être utile s'il s'agit déjà d'une entrée dans d'autres parties de votre application.
Vous configurez les cas d'utilisation à l'aide des méthodes set()
et les finalisez avec la méthode build()
. Chaque objet de cas d'utilisation fournit un ensemble d'API spécifiques à chaque cas d'utilisation. Par exemple, le cas d'utilisation "Capture d'image" fournit un appel de la méthode takePicture()
.
Au lieu de placer des appels de méthode spécifiques de démarrage et d'arrêt dans onResume()
et onPause()
, l'application indique un cycle de vie auquel associer la caméra à l'aide de cameraProvider.bindToLifecycle()
.
Ce cycle de vie indique ensuite à CameraX à quel moment configurer la session de capture et garantit que l'état de la caméra change de manière appropriée pour coïncider avec ses propres transitions.
Pour connaître les étapes à suivre afin d'implémenter chaque cas d'utilisation, consultez Intégrer un aperçu, Analyser les images, Capture d'image et Capture vidéo.
Le cas d'utilisation "Aperçu" interagit avec une Surface
pour l'affichage. Les applications créent le cas d'utilisation avec des options de configuration à l'aide du code suivant :
Kotlin
val preview = Preview.Builder().build() val viewFinder: PreviewView = findViewById(R.id.previewView) // The use case is bound to an Android Lifecycle with the following code val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // PreviewView creates a surface provider and is the recommended provider preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
Java
Preview preview = new Preview.Builder().build(); PreviewView viewFinder = findViewById(R.id.view_finder); // The use case is bound to an Android Lifecycle with the following code Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview); // PreviewView creates a surface provider, using a Surface from a different // kind of view will require you to implement your own surface provider. preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();
Pour d'autres exemples de code, consultez l'application exemple officielle de CameraX.
Cycles de vie de CameraX
CameraX observe un cycle de vie pour déterminer à quels moments ouvrir la caméra, créer une session de capture et s'arrêter, puis s'éteindre. Les API de cas d'utilisation fournissent des appels de méthode et des rappels pour surveiller la progression.
Comme expliqué dans Combiner les cas d'utilisation, vous pouvez lier des combinaisons de cas d'utilisation à un seul cycle de vie. Si votre application doit prendre en charge des cas d'utilisation qui ne peuvent pas être combinés, vous pouvez effectuer l'une des opérations suivantes :
- Regrouper des cas d'utilisation compatibles dans plusieurs fragments, puis passer d'un fragment à un autre ;
- Créer un composant de cycle de vie personnalisé et l'utiliser pour contrôler manuellement le cycle de vie de la caméra.
Si vous dissociez les propriétaires du cycle de vie de vos cas d'utilisation "Vue" et "Caméra" (par exemple, si vous utilisez un cycle de vie personnalisé ou un fragment de conservation), vous devez vous assurer que tous les cas d'utilisation sont déliés de CameraX à l'aide de ProcessCameraProvider.unbindAll()
ou de façon individuelle. Autrement, lorsque vous liez des cas d'utilisation à un cycle de vie, vous pouvez aussi laisser CameraX s'occuper d'ouvrir et de fermer la session de capture et de délier les cas d'utilisation.
Imaginons que le fonctionnement tout entier de votre caméra corresponde au cycle de vie d'un seul composant sensible au cycle de vie, tel qu'un fragment AppCompatActivity
ou AppCompat
. Dans ce cas, le fait d'utiliser le cycle de vie de ce composant au moment de lier tous les cas d'utilisation souhaités garantira que la caméra sera prête à fonctionner lorsque le composant sensible au cycle de vie sera actif, et qu'elle sera sinon supprimée de manière sécurisée pour ne pas consommer de ressources.
LifecycleOwners personnalisés
Pour les cas avancés, vous pouvez créer un LifecycleOwner
personnalisé pour permettre à votre application de contrôler de manière explicite le cycle de vie de la session CameraX au lieu de l'associer à un LifecycleOwner
Android standard.
L'exemple de code suivant illustre comment créer un LifecycleOwner personnalisé simple :
Kotlin
class CustomLifecycle : LifecycleOwner { private val lifecycleRegistry: LifecycleRegistry init { lifecycleRegistry = LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED) } ... fun doOnResume() { lifecycleRegistry.markState(State.RESUMED) } ... override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class CustomLifecycle implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; public CustomLifecycle() { lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } ... public void doOnResume() { lifecycleRegistry.markState(State.RESUMED); } ... public Lifecycle getLifecycle() { return lifecycleRegistry; } }
À l'aide de ce LifecycleOwner
, votre application peut placer des transitions d'état aux points souhaités dans son code. Pour savoir comment intégrer ce fonctionnement dans votre application, consultez Intégrer un LifecycleOwner personnalisé.
Cas d'utilisation simultanés
Les cas d'utilisation peuvent être exécutés simultanément. Bien que les cas d'utilisation puissent être liés de manière séquentielle à un cycle de vie, il est préférable de tous les lier à l'aide d'un seul appel à CameraProcessProvider.bindToLifecycle()
. Pour en savoir plus sur les bonnes pratiques concernant les modifications de configuration, consultez Gérer les modifications de configuration.
Dans l'exemple de code suivant, l'application précise les deux cas d'utilisation à créer et à exécuter simultanément. Elle précise aussi le cycle de vie à utiliser pour les deux cas d'utilisation, de sorte qu'ils démarrent et s'arrêtent en fonction du cycle de vie.
Kotlin
private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Camera provider is now guaranteed to be available val cameraProvider = cameraProviderFuture.get() // Set up the preview use case to display camera preview. val preview = Preview.Builder().build() // Set up the capture use case to allow users to take photos. imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() // Choose the camera by requiring a lens facing val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() // Attach use cases to the camera with the same lifecycle owner val camera = cameraProvider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture) // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()) }, ContextCompat.getMainExecutor(this)) }
Java
private ImageCapture imageCapture; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PreviewView previewView = findViewById(R.id.previewView); ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // Camera provider is now guaranteed to be available ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); // Set up the view finder use case to display camera preview Preview preview = new Preview.Builder().build(); // Set up the capture use case to allow users to take photos imageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build(); // Choose the camera by requiring a lens facing CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(lensFacing) .build(); // Attach use cases to the camera with the same lifecycle owner Camera camera = cameraProvider.bindToLifecycle( ((LifecycleOwner) this), cameraSelector, preview, imageCapture); // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()); } catch (InterruptedException | ExecutionException e) { // Currently no exceptions thrown. cameraProviderFuture.get() // shouldn't block since the listener is being called, so no need to // handle InterruptedException. } }, ContextCompat.getMainExecutor(this)); }
Les combinaisons de configuration suivantes sont compatibles (lorsque la prévisualisation ou la capture vidéo sont requises, mais pas en même temps) :
Aperçu ou capture vidéo | Capture d'image | Analyse | Descriptions |
---|---|---|---|
Proposer un aperçu ou enregistrer une vidéo, prendre une photo et analyser le flux d'images. | |||
Prendre une photo et analyser le flux d'images. | |||
Proposer un aperçu ou enregistrer une vidéo, et prendre une photo. | |||
Proposer un aperçu ou enregistrer une vidéo, et analyser le flux d'images. |
Lorsque l'aperçu et la capture vidéo sont nécessaires, les combinaisons de cas d'utilisation suivantes sont compatibles sous conditions :
Aperçu | Capture vidéo | Capture d'image | Analyse | Exigence particulière |
---|---|---|---|---|
Garanti pour toutes les caméras | ||||
Caméra LIMITED (ou mieux). | ||||
Caméra LEVEL_3 (ou mieux). |
De plus :
- Chaque cas d'utilisation peut fonctionner seul. Par exemple, une application peut enregistrer une vidéo sans utiliser l'aperçu.
- Lorsque les extensions sont activées, seule la combinaison de
ImageCapture
etPreview
est assurée de fonctionner. En fonction de l'intégration OEM, il n'est pas toujours possible d'ajouterImageAnalysis
. Les extensions ne peuvent être activées pour le cas d'utilisationVideoCapture
. Pour en savoir plus, consultez les documents de référence concernant les extensions. - Suivant leur capacité, il est possible que certaines caméras acceptent la combinaison dans des modes de résolution inférieure, mais ne puissent pas l'accepter pour des résolutions plus élevées.
Le niveau de matériel compatible peut être récupéré à partir de Camera2CameraInfo
. Par exemple, le code suivant vérifie si la caméra arrière par défaut est un appareil LEVEL_3
:
Kotlin
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.availableCameraInfos) .firstOrNull() ?.let { Camera2CameraInfo.from(it) } ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 } return false }
Java
@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class) Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.getAvailableCameraInfos()); if (!filteredCameraInfos.isEmpty()) { return Objects.equals( Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL), CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3); } } return false; }
Autorisations
Votre appli aura besoin de l'autorisation de CAMERA
. Pour enregistrer des images dans des fichiers, vous devez également disposer de l'autorisation WRITE_EXTERNAL_STORAGE
, sauf sur les appareils équipés d'Android version 10 ou ultérieure.
Pour en savoir plus sur la configuration des autorisations pour votre application, consultez Demander des autorisations pour une application.
Exigences
Les exigences minimales de version de CameraX sont les suivantes :
- Niveau 21 d'API Android
- Composants d'architecture Android 1.1.1
Pour les activités sensibles au cycle de vie, utilisez FragmentActivity
ou AppCompatActivity
.
Déclarer des dépendances
Pour ajouter une dépendance à CameraX, vous devez ajouter le dépôt Maven de Google à votre projet.
Ouvrez le fichier settings.gradle
de votre projet et ajoutez le dépôt google()
comme illustré ci-dessous :
Groovy
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Kotlin
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Ajoutez le code suivant à la fin du bloc Android :
Groovy
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Kotlin
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Ajoutez le code suivant au fichier build.gradle
de chaque module pour une appli :
Groovy
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.4.0-rc01" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" // If you want to additionally use the CameraX Lifecycle library implementation "androidx.camera:camera-lifecycle:${camerax_version}" // If you want to additionally use the CameraX VideoCapture library implementation "androidx.camera:camera-video:${camerax_version}" // If you want to additionally use the CameraX View class implementation "androidx.camera:camera-view:${camerax_version}" // If you want to additionally add CameraX ML Kit Vision Integration implementation "androidx.camera:camera-mlkit-vision:${camerax_version}" // If you want to additionally use the CameraX Extensions library implementation "androidx.camera:camera-extensions:${camerax_version}" }
Kotlin
dependencies { // CameraX core library using the camera2 implementation val camerax_version = "1.4.0-rc01" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation("androidx.camera:camera-core:${camerax_version}") implementation("androidx.camera:camera-camera2:${camerax_version}") // If you want to additionally use the CameraX Lifecycle library implementation("androidx.camera:camera-lifecycle:${camerax_version}") // If you want to additionally use the CameraX VideoCapture library implementation("androidx.camera:camera-video:${camerax_version}") // If you want to additionally use the CameraX View class implementation("androidx.camera:camera-view:${camerax_version}") // If you want to additionally add CameraX ML Kit Vision Integration implementation("androidx.camera:camera-mlkit-vision:${camerax_version}") // If you want to additionally use the CameraX Extensions library implementation("androidx.camera:camera-extensions:${camerax_version}") }
Pour savoir comment configurer votre appli afin de répondre à ces exigences, consultez Déclarer des dépendances.
Interopérabilité de CameraX avec Camera2
CameraX est basé sur Camera2, et CameraX propose des moyens de lire et même d'écrire des propriétés dans l'implémentation de Camera2. Pour en savoir plus, consultez le package d'interopérabilité.
Pour en savoir plus sur la configuration des propriétés de Camera2 par CameraX, utilisez Camera2CameraInfo
afin de lire les CameraCharacteristics
sous-jacentes. Vous pouvez également choisir d'écrire les propriétés sous-jacentes de Camera2 de deux manières :
Utilisez
Camera2CameraControl
, qui vous permet de définir des propriétés sur laCaptureRequest
sous-jacente, comme le mode de mise au point automatique.Étendez un
UseCase
CameraX avec unCamera2Interop.Extender
. Cela vous permet de définir des propriétés sur la CaptureRequest, tout commeCamera2CameraControl
. Cela vous offre également des commandes supplémentaires, par exemple pour définir le cas d'utilisation du flux afin d'optimiser la caméra pour votre scénario d'utilisation. Pour en savoir plus, consultez Utiliser des cas d'utilisation de flux pour de meilleures performances.
L'exemple de code suivant utilise des cas d'utilisation de flux pour optimiser un appel vidéo.
Utilisez Camera2CameraInfo
pour vérifier si le cas d'utilisation du flux d'appel vidéo est disponible. Utilisez ensuite un Camera2Interop.Extender
pour définir le cas d'utilisation du flux sous-jacent.
Kotlin
// Set underlying Camera2 stream use case to optimize for video calls. val videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() // Check available CameraInfos to find the first one that supports // the video call stream use case. val frontCameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES )?.contains(videoCallStreamId) val isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) (isVideoCallStreamingSupported == true) && isFrontFacing } val cameraSelector = frontCameraInfo.cameraSelector // Start with a Preview Builder. val previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation) // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) // Bind the Preview UseCase and the corresponding CameraSelector. val preview = previewBuilder.build() camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Java
// Set underlying Camera2 stream use case to optimize for video calls. Long videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong(); // Check available CameraInfos to find the first one that supports // the video call stream use case. List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos(); CameraInfo frontCameraInfo = null; for (cameraInfo in cameraInfos) { Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES ); boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases) .contains(videoCallStreamId); boolean isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT); if (isVideoCallStreamingSupported && isFrontFacing) { frontCameraInfo = cameraInfo; } } if (frontCameraInfo == null) { // Handle case where video call streaming is not supported. } CameraSelector cameraSelector = frontCameraInfo.getCameraSelector(); // Start with a Preview Builder. Preview.Builder previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation); // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId); // Bind the Preview UseCase and the corresponding CameraSelector. Preview preview = previewBuilder.build() Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Ressources supplémentaires
Pour en savoir plus sur CameraX, consultez les ressources supplémentaires suivantes.
Atelier de programmation
Exemple de code