Architecture de CameraX

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

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". Elle peut ensuite exploiter un cas d'utilisation "Analyse des images" qui détermine si les personnes sur la photo sourient. Enfin, elle peut inclure un cas d'utilisation "Capture d'image" pour prendre une photo dès qu'elles sourient.

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.

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.

Exemple de modèle d'API

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 accepter les cas d'utilisation qui ne peuvent ê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. Il 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 et Preview est assurée de fonctionner. En fonction de l'intégration OEM, il n'est pas toujours possible d'ajouter ImageAnalysis. Les extensions ne peuvent être activées pour le cas d'utilisation VideoCapture. 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\ 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 application :

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.3.0-alpha05"
  // 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.3.0-alpha05"
    // 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 application afin de répondre à ces exigences, consultez Déclarer des dépendances.

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