Migrer Camera1 vers CameraX

Si votre application utilise la classe Camera d'origine ("Camera1"), qui est obsolète depuis Android 5.0 (niveau d'API 21), nous vous recommandons vivement de passer à une API d'appareil photo Android moderne. Android propose CameraX (API d'appareil photo Jetpack robuste et standardisée) et Camera2 (API framework de bas niveau). Dans la grande majorité des cas, nous vous conseillons de migrer votre application vers CameraX. Pourquoi ?

  • Simplicité d'utilisation : CameraX gère les détails de bas niveau. Vous pouvez donc passer plus de temps à concevoir une application qui se démarque et moins de temps à créer une expérience photo en partant de zéro.
  • Fragmentation automatique : CameraX réduit les coûts de maintenance à long terme et le code spécifique à l'appareil, offrant ainsi des expériences de meilleure qualité aux utilisateurs. Pour en savoir plus à ce sujet, consultez notre article de blog intitulé Better Device Compatibility with CameraX (Améliorer la compatibilité des appareils avec CameraX).
  • Fonctionnalités avancées : CameraX a été soigneusement conçu pour faciliter l'intégration de fonctionnalités avancées dans votre application. Par exemple, vous pouvez aisément appliquer des fonctionnalités Bokeh, Retouche du visage, HDR (High Dynamic Range) et le mode de capture nocturne à faible luminosité à vos photos grâce aux extensions CameraX.
  • Facilité de mise à jour : Android publie de nouvelles fonctionnalités et corrections de bugs tout au long de l'année pour CameraX. En migrant vers CameraX, votre application bénéficie des dernières technologies photo d'Android à chaque version de CameraX, et pas seulement à la sortie des versions annuelles d'Android.

Dans ce guide, vous trouverez des scénarios courants pour les applications photo. Chaque scénario comprend une implémentation de Camera1 et une implémentation de CameraX à titre de comparaison.

Lors d'une migration, vous avez parfois besoin de plus de flexibilité pour intégrer un codebase existant. Tous les codes CameraX figurant dans ce guide comportent une implémentation CameraController (idéale si vous souhaitez utiliser CameraX le plus simplement possible) et une implémentation CameraProvider (idéale si vous avez besoin de plus de flexibilité). Pour vous aider à choisir la solution la mieux adaptée à vos besoins, découvrez leurs avantages ci-dessous :

CameraController

CameraProvider

Code de configuration minime Davantage de contrôle
Permettre à CameraX de gérer une plus grande partie du processus de configuration signifie que des fonctionnalités comme la mise au point en appuyant sur l'aperçu et le pincement pour zoomer fonctionnent automatiquement Étant donné que le développeur de l'application gère la configuration, il dispose de davantage de possibilités pour personnaliser la configuration (par exemple, activer la rotation de l'image de sortie ou définir le format de l'image de sortie dans ImageAnalysis)
L'affichage obligatoire du PreviewView pour l'aperçu de l'appareil photo permet à CameraX d'offrir une intégration parfaite et complète. C'est déjà le cas avec l'intégration de ML Kit, qui peut mapper les coordonnées des résultats du modèle de ML (les cadres de délimitation des visages, par exemple) directement sur les coordonnées de l'aperçu La possibilité d'utiliser une "Surface" personnalisée pour l'aperçu de l'appareil photo offre plus de flexibilité, par exemple lorsque vous avez recours au code "Surface" existant comme entrée pour d'autres parties de votre application

Si vous rencontrez des difficultés pour effectuer la migration, contactez-nous via le groupe de discussion CameraX.

Avant la migration

Comparer l'utilisation de CameraX et Camera1

Bien que le code puisse sembler différent, les concepts sous-jacents de Camera1 et de CameraX sont très similaires. CameraX extrait les fonctionnalités courantes de l'appareil photo dans des cas d'utilisation. Par conséquent, de nombreuses tâches laissées au développeur dans Camera1 sont gérées automatiquement par CameraX. Il existe quatre UseCase dans CameraX, que vous pouvez utiliser pour diverses tâches d'appareil photo : Preview. ImageCapture, VideoCapture et ImageAnalysis.

Un exemple de gestion des détails de bas niveau par CameraX pour les développeurs est le ViewPort partagé entre les UseCase actifs. Cela permet de garantir que tous les UseCase voient exactement les mêmes pixels. Dans Camera1, vous devez gérer vous-même ces détails. En raison des différents formats de capteurs et d'écrans des appareils photo, il peut être difficile de vous assurer que l'aperçu correspond aux photos et aux vidéos prises.

Autre exemple : CameraX gère automatiquement les rappels Lifecycle au niveau de l'instance Lifecycle que vous lui transmettez. Autrement dit, CameraX gère la connexion de votre application à l'appareil photo tout au long du cycle de vie de l'activité Android, y compris dans les cas suivants : arrêt de l'appareil photo lorsque l'application passe en arrière-plan, suppression de l'aperçu de l'appareil photo lorsque l'écran n'a plus besoin de l'afficher, et interruption de l'aperçu de l'appareil photo lorsqu'une autre activité est prioritaire, comme un appel vidéo entrant.

Enfin, CameraX gère la rotation et la mise à l'échelle sans que vous ayez besoin de code supplémentaire. Dans le cas d'une Activity dont l'orientation est déverrouillée, la configuration du UseCase est appliquée à chaque rotation de l'appareil, car le système détruit et recrée l'Activity lors des changements d'orientation. Par conséquent, les UseCases définissent à chaque fois leur rotation cible pour qu'elle corresponde à l'orientation de l'écran par défaut. En savoir plus sur les rotations dans CameraX

Avant d'entrer dans les détails, voici un aperçu général des UseCase de CameraX et de la manière dont une application Camera1 fonctionnerait. Notez que les concepts de CameraX sont en bleu et les concepts de Camera1 sont en vert.

CameraX

Configuration de CameraController/CameraProvider
Aperçu ImageCapture VideoCapture ImageAnalysis
Gérer la surface d'aperçu et la configurer sur l'appareil photo Définir PictureCallback et appeler takePicture() sur l'appareil photo Gérer la configuration de l'appareil photo et de MediaRecorder dans un ordre spécifique Code d'analyse personnalisé basé sur la surface d'aperçu
Code spécifique à l'appareil
Gestion de la rotation et de la mise à l'échelle des appareils
Gestion des sessions photo (sélection de l'appareil photo, gestion du cycle de vie)

Camera1

Compatibilité et performances dans CameraX

CameraX est compatible avec les appareils équipés d'Android 5.0 (niveau d'API 21) ou version ultérieure, soit plus de 98 % des appareils Android existants. CameraX est conçu pour gérer automatiquement les différences entre les appareils, ce qui réduit le besoin de code spécifique à l'appareil dans votre application. De plus, nous testons plus de 150 appareils physiques sur toutes les versions d'Android depuis la version 5.0 dans le cadre de notre Test Lab de CameraX. Vous pouvez consulter la liste complète des appareils actuellement disponibles dans le Test Lab.

CameraX utilise un Executor pour piloter la pile de l'appareil photo. Vous pouvez spécifier votre propre exécuteur sur CameraX si votre application a des exigences spécifiques en termes de threads. Si vous ne le spécifiez pas, CameraX crée et utilise par défaut un Executor interne optimisé. De nombreuses API de la plate-forme sur lesquelles CameraX repose requièrent un dispositif de communication inter-processus (IPC) avec du matériel dont la réponse peut parfois prendre des centaines de millisecondes. Pour cette raison, CameraX n'appelle ces API qu'à partir de threads en arrière-plan, ce qui garantit que le thread principal n'est pas bloqué et que l'UI reste fluide. En savoir plus sur les threads

Si le marché cible de votre application inclut des appareils d'entrée de gamme, CameraX offre un moyen de réduire le temps de configuration à l'aide d'un limiteur d'appareil photo. Étant donné que le processus de connexion aux composants matériels peut prendre du temps, en particulier sur les appareils d'entrée de gamme, vous pouvez spécifier l'ensemble d'appareils photo dont votre application a besoin. CameraX ne se connectera à ces appareils photo que lors de la configuration. Par exemple, si l'application n'utilise que les appareils photo arrière, elle peut définir cette configuration avec DEFAULT_BACK_CAMERA. Ensuite, CameraX évitera d'initialiser les appareils photo frontaux pour réduire la latence.

Concepts du développement Android

Ce guide part du principe que vous possédez des connaissances générales en développement Android. Au-delà des principes de base, voici quelques concepts qu'il est utile de comprendre avant de passer au code :

  • La liaison de vue génère une classe de liaison pour vos fichiers de mise en page XML, ce qui vous permet de référencer facilement les vues dans les activités, comme illustré dans plusieurs extraits de code ci-dessous. Il existe des différences entre la liaison de vue et findViewById() (méthode précédente permettant de référencer des vues), mais dans le code ci-dessous, vous devriez pouvoir remplacer les lignes de liaison des vues par un appel findViewById() similaire.
  • Les coroutines asynchrones représentent un modèle de conception de simultanéité ajouté dans Kotlin 1.3. Elles peuvent être utilisées pour gérer les méthodes CameraX qui renvoient un ListenableFuture. Le processus est simplifié grâce à la bibliothèque Jetpack Concurrent à partir de la version 1.1.0. Pour ajouter une coroutine asynchrone à votre application, procédez comme suit :
    1. Ajoutez implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") à votre fichier Gradle.
    2. Placez le code CameraX renvoyant un ListenableFuture dans un bloc launch ou une fonction de suspension.
    3. Ajoutez un appel await() à l'appel de fonction qui renvoie un ListenableFuture.
    4. Pour mieux comprendre le fonctionnement des coroutines, consultez le guide Démarrer une coroutine.

Migrer des scénarios courants

Cette section explique comment migrer des scénarios courants de Camera1 vers CameraX. Chaque scénario couvre une implémentation de Camera1, une implémentation de CameraX CameraProvider et une implémentation de CameraX CameraController.

Sélectionner un appareil photo

Dans votre application photo, vous pouvez d'abord proposer un moyen de sélectionner différents appareils photo.

Camera1

Dans Camera1, vous pouvez appeler Camera.open() sans paramètre pour ouvrir le premier appareil photo arrière, ou transmettre un ID entier pour l'appareil photo que vous voulez ouvrir. Voici un exemple :

// Camera1: select a camera from id.

// Note: opening the camera is a non-trivial task, and it shouldn't be
// called from the main thread, unlike CameraX calls, which can be
// on the main thread since CameraX kicks off background threads
// internally as needed.

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        camera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(TAG, "failed to open camera", e)
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    camera?.release()
    camera = null
}

CameraX : CameraController

Dans CameraX, la sélection de l'appareil photo est gérée par la classe CameraSelector. CameraX facilite l'utilisation de l'appareil photo par défaut, qui est un cas courant. Vous pouvez indiquer si vous souhaitez utiliser l'appareil photo avant par défaut ou l'appareil photo arrière par défaut. De plus, l'objet CameraControl de CameraX vous permet de définir facilement le niveau de zoom de votre application. Par conséquent, si elle s'exécute sur un appareil compatible avec les appareils photo logiques, elle passera sur l'objectif approprié.

Voici le code CameraX permettant d'utiliser l'appareil photo arrière par défaut avec un CameraController :

// CameraX: select a camera with CameraController

var cameraController = LifecycleCameraController(baseContext)
val selector = CameraSelector.Builder()
    .requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraController.cameraSelector = selector

CameraX : CameraProvider

Voici un exemple de sélection de l'appareil photo avant par défaut avec un CameraProvider (l'appareil photo avant ou arrière peut être utilisé avec un CameraController ou un CameraProvider) :

// CameraX: select a camera with CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Set up UseCases (more on UseCases in later scenarios)
    var useCases:Array = ...

    // Set the cameraSelector to use the default front-facing (selfie)
    // camera.
    val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

Si vous souhaitez contrôler l'appareil photo sélectionné, cela est également possible dans CameraX si vous utilisez un CameraProvider en appelant getAvailableCameraInfos(). Vous générez ainsi un objet CameraInfo permettant de vérifier certaines propriétés de l'appareil photo, telles que isFocusMeteringSupported(). Vous pouvez ensuite le convertir en CameraSelector pour l'utiliser comme dans les exemples ci-dessus avec la méthode CameraInfo.getCameraSelector().

Pour obtenir plus d'informations sur chaque appareil photo, utilisez la classe Camera2CameraInfo. Appelez getCameraCharacteristic() avec une touche correspondant aux données d'appareil photo souhaitées. Consultez la classe CameraCharacteristics pour obtenir la liste de toutes les touches que vous pouvez interroger.

Voici un exemple utilisant une fonction checkFocalLength() personnalisée que vous pouvez définir vous-même :

// CameraX: get a cameraSelector for first camera that matches the criteria
// defined in checkFocalLength().

val cameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val focalLengths = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
            )
        return checkFocalLength(focalLengths)
    }
val cameraSelector = cameraInfo.getCameraSelector()

Afficher un aperçu

La plupart des applications photo doivent à un moment donné afficher le flux de l'appareil photo à l'écran. Avec Camera1, vous devez gérer correctement les rappels de cycle de vie et déterminer la rotation ainsi que la mise à l'échelle de l'aperçu.

De plus, dans Camera1, vous devez choisir entre utiliser un TextureView ou un SurfaceView comme surface d'aperçu. Ces deux options impliquent des compromis. Dans les deux cas, Camera1 nécessite que vous gériez la rotation et la mise à l'échelle de manière appropriée. En revanche, le PreviewView de CameraX comporte des implémentations sous-jacentes pour un TextureView et un SurfaceView. CameraX détermine l'implémentation la plus adaptée en fonction de facteurs tels que le type d'appareil et la version d'Android sur laquelle votre application est exécutée. Si l'une de ces deux implémentations est compatible, vous pouvez déclarer votre préférence avec PreviewView.ImplementationMode. L'option COMPATIBLE utilise un TextureView pour l'aperçu, et la PERFORMANCE utilise un SurfaceView (si possible).

Camera1

Pour afficher un aperçu, vous devez écrire votre propre classe Preview avec une implémentation de l'interface android.view.SurfaceHolder.Callback, qui sert à transmettre les données d'image depuis les composants matériels de l'appareil photo à l'application. Ensuite, avant de pouvoir lancer l'aperçu de l'image en direct, la classe Preview doit être transmise à l'objet Camera.

// Camera1: set up a camera preview.

class Preview(
        context: Context,
        private val camera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val holder: SurfaceHolder = holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera
        // where to draw the preview.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "error setting camera preview", e)
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int,
                                w: Int, h: Int) {
        // If your preview can change or rotate, take care of those
        // events here. Make sure to stop the preview before resizing
        // or reformatting it.
        if (holder.surface == null) {
            return  // The preview surface does not exist.
        }

        // Stop preview before making changes.
        try {
            camera.stopPreview()
        } catch (e: Exception) {
            // Tried to stop a non-existent preview; nothing to do.
        }

        // Set preview size and make any resize, rotate or
        // reformatting changes here.

        // Start preview with new settings.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "error starting camera preview", e)
            }
        }
    }
}

class CameraActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding
    private var camera: Camera? = null
    private var preview: Preview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create an instance of Camera.
        camera = getCameraInstance()

        preview = camera?.let {
            // Create the Preview view.
            Preview(this, it)
        }

        // Set the Preview view as the content of the activity.
        val cameraPreview: FrameLayout = viewBinding.cameraPreview
        cameraPreview.addView(preview)
    }
}

CameraX : CameraController

En tant que développeur, vous avez beaucoup moins à gérer dans CameraX. Si vous utilisez un CameraController, vous devez également utiliser un PreviewView. Autrement dit, le UseCase Preview est implicite, ce qui rend la configuration beaucoup moins fastidieuse :

// CameraX: set up a camera preview with a CameraController.

class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create the CameraController and set it on the previewView.
        var cameraController = LifecycleCameraController(baseContext)
        cameraController.bindToLifecycle(this)
        val previewView: PreviewView = viewBinding.cameraPreview
        previewView.controller = cameraController
    }
}

CameraX : CameraProvider

Avec le CameraProvider de CameraX, vous n'avez pas besoin d'utiliser de PreviewView et cela simplifie considérablement la configuration de l'aperçu par rapport à Camera1. À des fins de démonstration, cet exemple utilise un PreviewView, mais vous pouvez écrire un SurfaceProvider personnalisé à transmettre au setSurfaceProvider() si vos besoins sont plus complexes.

Ici, le UseCase Preview n'est pas implicite comme avec le CameraController. Vous devez donc le configurer :

// CameraX: set up a camera preview with a CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Create Preview UseCase.
    val preview = Preview.Builder()
        .build()
        .also {
            it.setSurfaceProvider(
                viewBinding.viewFinder.surfaceProvider
            )
        }

    // Select default back camera.
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera() in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

Appuyer pour effectuer la mise au point

Lorsque l'aperçu de l'appareil photo s'affiche à l'écran, une commande courante consiste à effectuer la mise au point lorsque l'utilisateur appuie sur l'aperçu.

Camera1

Pour implémenter la fonctionnalité de mise au point via l'appui sur l'aperçu dans Camera1, vous devez calculer la zone de mise au point optimale (Area) pour indiquer où le Camera doit tenter d'effectuer cette opération. Cet Area est transmis dans setFocusAreas(). De plus, vous devez définir un mode de mise au point compatible sur le Camera. La zone de mise au point n'a d'effet que si le mode de mise au point actuel est FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_VIDEO ou FOCUS_MODE_CONTINUOUS_PICTURE.

Chaque Area est un rectangle avec une pondération spécifiée. La pondération est une valeur comprise entre 1 et 1 000, utilisée pour définir la priorité des zones de mise au point (Areas) si plusieurs sont définies. Comme cet exemple n'utilise qu'un seul Area, la valeur de pondération n'a pas d'importance. Les coordonnées de la plage du rectangle vont de -1 000 à 1 000. Le point supérieur gauche correspond à (-1 000, -1 000). Le point inférieur droit correspond à (1 000, 1 000). La direction dépend de l'orientation du capteur, c'est-à-dire de ce qu'il perçoit. L'orientation n'est pas affectée par la rotation ou la duplication d'écran de Camera.setDisplayOrientation(). Vous devez donc convertir les coordonnées de l'événement tactile en coordonnées de capteur.

// Camera1: implement tap-to-focus.

class TapToFocusHandler : Camera.AutoFocusCallback {
    private fun handleFocus(event: MotionEvent) {
        val camera = camera ?: return
        val parameters = try {
            camera.getParameters()
        } catch (e: RuntimeException) {
            return
        }

        // Cancel previous auto-focus function, if one was in progress.
        camera.cancelAutoFocus()

        // Create focus Area.
        val rect = calculateFocusAreaCoordinates(event.x, event.y)
        val weight = 1  // This value's not important since there's only 1 Area.
        val focusArea = Camera.Area(rect, weight)

        // Set the focus parameters.
        parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO)
        parameters.setFocusAreas(listOf(focusArea))

        // Set the parameters back on the camera and initiate auto-focus.
        camera.setParameters(parameters)
        camera.autoFocus(this)
    }

    private fun calculateFocusAreaCoordinates(x: Int, y: Int) {
        // Define the size of the Area to be returned. This value
        // should be optimized for your app.
        val focusAreaSize = 100

        // You must define functions to rotate and scale the x and y values to
        // be values between 0 and 1, where (0, 0) is the upper left-hand side
        // of the preview, and (1, 1) is the lower right-hand side.
        val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000
        val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000

        // Calculate the values for left, top, right, and bottom of the Rect to
        // be returned. If the Rect would extend beyond the allowed values of
        // (-1000, -1000, 1000, 1000), then crop the values to fit inside of
        // that boundary.
        val left = max(normalizedX - (focusAreaSize / 2), -1000)
        val top = max(normalizedY - (focusAreaSize / 2), -1000)
        val right = min(left + focusAreaSize, 1000)
        val bottom = min(top + focusAreaSize, 1000)

        return Rect(left, top, left + focusAreaSize, top + focusAreaSize)
    }

    override fun onAutoFocus(focused: Boolean, camera: Camera) {
        if (!focused) {
            Log.d(TAG, "tap-to-focus failed")
        }
    }
}

CameraX : CameraController

Le CameraController écoute les événements tactiles du PreviewView pour gérer automatiquement la mise au point via l'appui sur l'aperçu. Vous pouvez activer ou désactiver la fonctionnalité de mise au point via l'appui sur l'aperçu avec setTapToFocusEnabled(), puis vérifier la valeur avec le getter isTapToFocusEnabled() correspondant.

La méthode getTapToFocusState() renvoie un objet LiveData pour suivre les modifications de l'état de mise au point sur le CameraController.

// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView,
// with handlers you can define for focused, not focused, and failed states.

val tapToFocusStateObserver = Observer { state ->
    when (state) {
        CameraController.TAP_TO_FOCUS_NOT_STARTED ->
            Log.d(TAG, "tap-to-focus init")
        CameraController.TAP_TO_FOCUS_STARTED ->
            Log.d(TAG, "tap-to-focus started")
        CameraController.TAP_TO_FOCUS_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focus successful)")
        CameraController.TAP_TO_FOCUS_NOT_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focused unsuccessful)")
        CameraController.TAP_TO_FOCUS_FAILED ->
            Log.d(TAG, "tap-to-focus failed")
    }
}

cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)

CameraX : CameraProvider

Avec un CameraProvider, une configuration est nécessaire pour que la mise au point via un appui sur l'aperçu fonctionne. Cet exemple suppose que vous utilisez PreviewView. Dans le cas contraire, vous devez adapter la logique à appliquer à votre Surface personnalisée.

Voici la procédure à suivre lorsque vous utilisez le PreviewView :

  1. Configurez un détecteur de gestes pour gérer les événements de type "Appuyer".
  2. Avec cet événement, créez un MeteringPoint à l'aide de MeteringPointFactory.createPoint().
  3. Avec le MeteringPoint, créez un FocusMeteringAction.
  4. Avec l'objet CameraControl sur votre Camera (renvoyé par bindToLifecycle()), appelez startFocusAndMetering() en transmettant le FocusMeteringAction.
  5. (Facultatif) Répondez au FocusMeteringResult.
  6. Configurez le détecteur de gestes pour qu'il réponde aux événements tactiles dans PreviewView.setOnTouchListener().
// CameraX: implement tap-to-focus with CameraProvider.

// Define a gesture detector to respond to tap events and call
// startFocusAndMetering on CameraControl. If you want to use a
// coroutine with await() to check the result of focusing, see the
// "Android development concepts" section above.
val gestureDetector = GestureDetectorCompat(context,
    object : SimpleOnGestureListener() {
        override fun onSingleTapUp(e: MotionEvent): Boolean {
            val previewView = previewView ?: return
            val camera = camera ?: return
            val meteringPointFactory = previewView.meteringPointFactory
            val focusPoint = meteringPointFactory.createPoint(e.x, e.y)
            val meteringAction = FocusMeteringAction
                .Builder(meteringPoint).build()
            lifecycleScope.launch {
                val focusResult = camera.cameraControl
                    .startFocusAndMetering(meteringAction).await()
                if (!result.isFocusSuccessful()) {
                    Log.d(TAG, "tap-to-focus failed")
                }
            }
        }
    }
)

...

// Set the gestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    // See pinch-to-zooom scenario for scaleGestureDetector definition.
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

Pincer pour zoomer

Faire un zoom avant ou arrière dans un aperçu est une autre manipulation directe couramment utilisée dans l'aperçu de l'appareil photo. Comme de plus en plus d'appareils photo sont intégrés aux appareils, les utilisateurs s'attendent également à ce que l'objectif offrant la meilleure distance focale soit automatiquement sélectionné une fois la fonctionnalité de zoom déclenchée.

Camera1

Il existe deux façons de zoomer à l'aide de Camera1. La méthode Camera.startSmoothZoom() s'exécute du niveau de zoom actuel au niveau de zoom que vous transmettez. La méthode Camera.Parameters.setZoom() passe directement au niveau de zoom que vous transmettez. Avant d'utiliser l'une de ces méthodes, appelez respectivement isSmoothZoomSupported() ou isZoomSupported() pour vous assurer que les méthodes de zoom associées dont vous avez besoin sont disponibles sur votre appareil photo.

Pour implémenter le pincement pour zoomer, cet exemple utilise setZoom(), car l'écouteur tactile de la surface d'aperçu déclenche en continu des événements lorsque le geste de pincement se produit. Par conséquent, il met immédiatement à jour le niveau de zoom à chaque fois. La classe ZoomTouchListener est spécifiée ci-dessous et doit être définie comme rappel de l'écouteur tactile de la surface d'aperçu.

// Camera1: implement pinch-to-zoom.

// Define a scale gesture detector to respond to pinch events and call
// setZoom on Camera.Parameters.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : ScaleGestureDetector.OnScaleGestureListener {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return false
            val parameters = try {
                camera.parameters
            } catch (e: RuntimeException) {
                return false
            }

            // In case there is any focus happening, stop it.
            camera.cancelAutoFocus()

            // Set the zoom level on the Camera.Parameters, and set
            // the Parameters back onto the Camera.
            val currentZoom = parameters.zoom
            parameters.setZoom(detector.scaleFactor * currentZoom)
        camera.setParameters(parameters)
            return true
        }
    }
)

// Define a View.OnTouchListener to attach to your preview view.
class ZoomTouchListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean =
        scaleGestureDetector.onTouchEvent(event)
}

// Set a ZoomTouchListener to handle touch events on your preview view
// if zoom is supported by the current camera.
if (camera.getParameters().isZoomSupported()) {
    view.setOnTouchListener(ZoomTouchListener())
}

CameraX : CameraController

Semblable à la fonctionnalité de mise au point via l'appui sur l'aperçu, le CameraController écoute les événements tactiles de PreviewView afin de gérer le pincement pour zoomer automatiquement. Vous pouvez activer et désactiver la fonctionnalité Pincer pour zoomer avec setPinchToZoomEnabled(), puis vérifier la valeur avec le getter isPinchToZoomEnabled() correspondant.

La méthode getZoomState() renvoie un objet LiveData pour suivre les modifications apportées au ZoomState sur le CameraController.

// CameraX: track the state of pinch-to-zoom over the Lifecycle of
// a PreviewView, logging the linear zoom ratio.

val pinchToZoomStateObserver = Observer { state ->
    val zoomRatio = state.getZoomRatio()
    Log.d(TAG, "ptz-zoom-ratio $zoomRatio")
}

cameraController.getZoomState().observe(this, pinchToZoomStateObserver)

CameraX : CameraProvider

Pour que le pincement permette de zoomer avec le CameraProvider, vous devez configurer certains paramètres. Si vous n'utilisez pas le PreviewView, vous devez adapter la logique à votre Surface personnalisé.

Voici la procédure à suivre lorsque vous utilisez le PreviewView :

  1. Configurez un détecteur de gestes à l'échelle pour gérer les événements de pincement.
  2. Obtenez la valeur ZoomState à partir de l'objet Camera.CameraInfo, où l'instance Camera est renvoyée lorsque vous appelez bindToLifecycle().
  3. Si le ZoomState a une valeur zoomRatio, enregistrez-la sous le format de zoom actuel. S'il n'y a pas de zoomRatio pour le ZoomState, utilisez le ratio de zoom par défaut de l'appareil photo (1.0).
  4. Utilisez le produit du rapport de zoom actuel avec le scaleFactor pour déterminer le nouveau ratio de zoom, puis transmettez ce résultat dans CameraControl.setZoomRatio().
  5. Configurez le détecteur de gestes pour qu'il réponde aux événements tactiles dans PreviewView.setOnTouchListener().
// CameraX: implement pinch-to-zoom with CameraProvider.

// Define a scale gesture detector to respond to pinch events and call
// setZoomRatio on CameraControl.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : SimpleOnGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return
            val zoomState = camera.cameraInfo.zoomState
            val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f
            camera.cameraControl.setZoomRatio(
                detector.scaleFactor * currentZoomRatio
            )
        }
    }
)

...

// Set the scaleGestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        // See pinch-to-zooom scenario for gestureDetector definition.
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

Prendre une photo

Cette section explique comment déclencher la prise de photos, que ce soit lorsque vous appuyez sur le bouton de l'obturateur, à la fin du retardateur ou lors de tout autre événement de votre choix.

Camera1

Dans Camera1, vous définissez d'abord un Camera.PictureCallback pour gérer les données d'image lorsqu'elles sont demandées. Voici un exemple simple de PictureCallback pour gérer les données d'une image JPEG :

// Camera1: define a Camera.PictureCallback to handle JPEG data.

private val picture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG,
              "error creating media file, check storage permissions")
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "file not found", e)
    } catch (e: IOException) {
        Log.d(TAG, "error accessing file", e)
    }
}

Ensuite, chaque fois que vous souhaitez prendre une photo, appelez la méthode takePicture() sur votre instance Camera. Cette méthode takePicture() comporte trois paramètres distincts pour différents types de données. Le premier paramètre correspond à un objet ShutterCallback (qui n'est pas défini dans cet exemple). Le deuxième paramètre permet à un PictureCallback de gérer les données brutes (non compressées) de l'appareil photo. Le troisième paramètre est celui utilisé dans cet exemple, car il s'agit d'un PictureCallback qui gère les données de l'image JPEG.

// Camera1: call takePicture on Camera instance, passing our PictureCallback.

camera?.takePicture(null, null, picture)

CameraX : CameraController

Le CameraController de CameraX conserve la simplicité de Camera1 pour la capture d'image en implémentant sa propre méthode takePicture(). Définissez ici une fonction permettant de configurer une entrée MediaStore et de prendre une photo qui y sera enregistrée.

// CameraX: define a function that uses CameraController to take a photo.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun takePhoto() {
   // Create time stamped name and MediaStore entry.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
       if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
       }
   }

   // Create output options object which contains file + metadata.
   val outputOptions = ImageCapture.OutputFileOptions
       .Builder(context.getContentResolver(),
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
       .build()

   // Set up image capture listener, which is triggered after photo has
   // been taken.
   cameraController.takePicture(
       outputOptions,
       ContextCompat.getMainExecutor(this),
       object : ImageCapture.OnImageSavedCallback {
           override fun onError(e: ImageCaptureException) {
               Log.e(TAG, "photo capture failed", e)
           }

           override fun onImageSaved(
               output: ImageCapture.OutputFileResults
           ) {
               val msg = "Photo capture succeeded: ${output.savedUri}"
               Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
               Log.d(TAG, msg)
           }
       }
   )
}

CameraX : CameraProvider

Prendre une photo avec le CameraProvider fonctionne presque de la même manière qu'avec le CameraController, mais vous devez d'abord créer et lier un UseCase ImageCapture pour avoir un objet avec lequel appeler takePicture() :

// CameraX: create and bind an ImageCapture UseCase.

// Make a reference to the ImageCapture UseCase at a scope that can be accessed
// throughout the camera logic in your app.
private var imageCapture: ImageCapture? = null

...

// Create an ImageCapture instance (can be added with other
// UseCase definitions).
imageCapture = ImageCapture.Builder().build()

...

// Bind UseCases to camera (adding imageCapture along with preview here, but
// preview is not required to use imageCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, imageCapture)

Chaque fois que vous souhaitez prendre une photo, vous pouvez appeler ImageCapture.takePicture(). Consultez le code CameraController de cette section pour voir un exemple complet de la fonction takePhoto().

// CameraX: define a function that uses CameraController to take a photo.

private fun takePhoto() {
    // Get a stable reference of the modifiable ImageCapture UseCase.
    val imageCapture = imageCapture ?: return

    ...

    // Call takePicture on imageCapture instance.
    imageCapture.takePicture(
        ...
    )
}

Enregistrer une vidéo

Enregistrer une vidéo est bien plus complexe que les scénarios vus jusqu'à présent. Chaque partie du processus doit être configurée correctement, généralement dans un ordre particulier. Vous devrez peut-être également vérifier que la vidéo et l'audio sont synchronisés ou traiter des incohérences supplémentaires au niveau des appareils.

Comme vous le verrez, CameraX gère une grande partie de cette complexité pour vous.

Camera1

La capture vidéo à l'aide de Camera1 nécessite de gérer minutieusement le Camera et le MediaRecorder, et les méthodes doivent être appelées dans un ordre spécifique. Vous devez suivre cet ordre pour que votre application fonctionne comme prévu :

  1. Ouvrez l'appareil photo.
  2. Préparez et démarrez un aperçu (si votre application montre la vidéo en cours d'enregistrement, ce qui est généralement le cas).
  3. Déverrouillez l'appareil photo pour que MediaRecorder puisse l'utiliser en appelant Camera.unlock().
  4. Configurez l'enregistrement en appelant les méthodes suivantes sur MediaRecorder :
    1. Connectez votre instance Camera à setCamera(camera).
    2. Appelez setAudioSource(MediaRecorder.AudioSource.CAMCORDER).
    3. Appelez setVideoSource(MediaRecorder.VideoSource.CAMERA).
    4. Appelez setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) pour définir la qualité. Consultez la section sur CamcorderProfile pour vous familiariser avec toutes les options de qualité.
    5. Appelez setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()).
    6. Si votre application dispose d'un aperçu de la vidéo, appelez setPreviewDisplay(preview?.holder?.surface).
    7. Appelez setOutputFormat(MediaRecorder.OutputFormat.MPEG_4).
    8. Appelez setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT).
    9. Appelez setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT).
    10. Appelez prepare() pour finaliser la configuration de votre MediaRecorder.
  5. Pour commencer l'enregistrement, appelez MediaRecorder.start().
  6. Pour arrêter l'enregistrement, appelez ces méthodes. Là encore, suivez l'ordre exact :
    1. Appelez MediaRecorder.stop().
    2. Vous pouvez également supprimer la configuration MediaRecorder actuelle en appelant MediaRecorder.reset().
    3. Appelez MediaRecorder.release().
    4. Verrouillez l'appareil photo pour que les futures sessions MediaRecorder puissent l'utiliser en appelant Camera.lock().
  7. Pour arrêter l'aperçu, appelez Camera.stopPreview().
  8. Enfin, pour libérer le Camera afin que d'autres processus puissent l'utiliser, appelez Camera.release().

Voici toutes ces étapes combinées :

// Camera1: set up a MediaRecorder and a function to start and stop video
// recording.

// Make a reference to the MediaRecorder at a scope that can be accessed
// throughout the camera logic in your app.
private var mediaRecorder: MediaRecorder? = null
private var isRecording = false

...

private fun prepareMediaRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    // Unlock and set camera to MediaRecorder.
    camera?.unlock()

    mediaRecorder?.run {
        setCamera(camera)

        // Set the audio and video sources.
        setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
        setVideoSource(MediaRecorder.VideoSource.CAMERA)

        // Set a CamcorderProfile (requires API Level 8 or higher).
        setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

        // Set the output file.
        setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

        // Set the preview output.
        setPreviewDisplay(preview?.holder?.surface)

        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)

        // Prepare configured MediaRecorder.
        return try {
            prepare()
            true
        } catch (e: IllegalStateException) {
            Log.d(TAG, "preparing MediaRecorder failed", e)
            releaseMediaRecorder()
            false
        } catch (e: IOException) {
            Log.d(TAG, "setting MediaRecorder file failed", e)
            releaseMediaRecorder()
            false
        }
    }
    return false
}

private fun releaseMediaRecorder() {
    mediaRecorder?.reset()
    mediaRecorder?.release()
    mediaRecorder = null
    camera?.lock()
}

private fun startStopVideo() {
    if (isRecording) {
        // Stop recording and release camera.
        mediaRecorder?.stop()
        releaseMediaRecorder()
        camera?.lock()
        isRecording = false

        // This is a good place to inform user that video recording has stopped.
    } else {
        // Initialize video camera.
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared, now
            // you can start recording.
            mediaRecorder?.start()
            isRecording = true

            // This is a good place to inform the user that recording has
            // started.
        } else {
            // Prepare didn't work, release the camera.
            releaseMediaRecorder()

            // Inform user here.
        }
    }
}

CameraX : CameraController

Avec le CameraController de CameraX, vous pouvez activer/désactiver les UseCases ImageCapture, VideoCapture et ImageAnalysis de manière indépendante, tant que la liste des UseCases peut être utilisée simultanément. Les UseCases ImageCapture et ImageAnalysis sont activés par défaut. Vous n'avez donc pas besoin d'appeler setEnabledUseCases() pour prendre une photo.

Si vous souhaitez utiliser un CameraController pour enregistrer une vidéo, vous devez d'abord recourir à setEnabledUseCases() pour autoriser le UseCase VideoCapture.

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

Lorsque vous souhaitez commencer à enregistrer une vidéo, vous pouvez appeler la fonction CameraController.startRecording(). Cette fonction permet de stocker la vidéo enregistrée dans un File, comme vous pouvez le voir dans l'exemple ci-dessous. En outre, vous devez transmettre un Executor et une classe qui implémente OnVideoSavedCallback pour gérer les rappels de réussite et d'erreur. À la fin de l'enregistrement, appelez CameraController.stopRecording().

Remarque : Si vous utilisez CameraX 1.3.0-alpha02 ou une version ultérieure, un paramètre AudioConfig supplémentaire vous permet d'activer ou de désactiver l'enregistrement audio au niveau de la vidéo. Pour activer l'enregistrement audio, vous devez vous assurer que vous disposez des autorisations d'accès au micro. De plus, la méthode stopRecording() est supprimée dans la version 1.3.0-alpha02, et startRecording() renvoie un objet Recording qui peut être utilisé pour suspendre, reprendre et arrêter l'enregistrement vidéo.

// CameraX: implement video capture with CameraController.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

// Define a VideoSaveCallback class for handling success and error states.
class VideoSaveCallback : OnVideoSavedCallback {
    override fun onVideoSaved(outputFileResults: OutputFileResults) {
        val msg = "Video capture succeeded: ${outputFileResults.savedUri}"
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        Log.d(TAG, msg)
    }

    override fun onError(videoCaptureError: Int, message: String,
                         cause: Throwable?) {
        Log.d(TAG, "error saving video: $message", cause)
    }
}

private fun startStopVideo() {
    if (cameraController.isRecording()) {
        // Stop the current recording session.
        cameraController.stopRecording()
        return
    }

    // Define the File options for saving the video.
    val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
        .format(System.currentTimeMillis())

    val outputFileOptions = OutputFileOptions
        .Builder(File(this.filesDir, name))
        .build()

    // Call startRecording on the CameraController.
    cameraController.startRecording(
        outputFileOptions,
        ContextCompat.getMainExecutor(this),
        VideoSaveCallback()
    )
}

CameraX : CameraProvider

Si vous utilisez un CameraProvider, vous devez créer un UseCase VideoCapture et transmettre un objet Recorder. Sur le Recorder.Builder, vous pouvez définir la qualité vidéo et, éventuellement, un FallbackStrategy, qui gère les cas où un appareil ne remplit pas les spécifications de qualité souhaitées. Liez ensuite l'instance VideoCapture au CameraProvider avec vos autres UseCases.

// CameraX: create and bind a VideoCapture UseCase with CameraProvider.

// Make a reference to the VideoCapture UseCase and Recording at a
// scope that can be accessed throughout the camera logic in your app.
private lateinit var videoCapture: VideoCapture
private var recording: Recording? = null

...

// Create a Recorder instance to set on a VideoCapture instance (can be
// added with other UseCase definitions).
val recorder = Recorder.Builder()
    .setQualitySelector(QualitySelector.from(Quality.FHD))
    .build()
videoCapture = VideoCapture.withOutput(recorder)

...

// Bind UseCases to camera (adding videoCapture along with preview here, but
// preview is not required to use videoCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, videoCapture)

À ce stade, le Recorder est accessible sur la propriété videoCapture.output. Le Recorder peut démarrer des enregistrements vidéo stockés dans un File, un ParcelFileDescriptor ou un MediaStore. Cet exemple utilise MediaStore.

Sur le Recorder, il existe plusieurs méthodes à appeler pour le préparer. Appelez prepareRecording() pour définir les options de sortie de MediaStore. Si votre application est autorisée à utiliser le micro de l'appareil, appelez également withAudioEnabled(). Ensuite, appelez start() pour commencer l'enregistrement, en transmettant un contexte et un écouteur d'événements Consumer<VideoRecordEvent> afin de gérer les événements d'enregistrement vidéo. En cas de succès, la réponse Recording renvoyée pourra être utilisée pour suspendre, reprendre ou arrêter l'enregistrement.

// CameraX: implement video capture with CameraProvider.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun startStopVideo() {
   val videoCapture = this.videoCapture ?: return

   if (recording != null) {
       // Stop the current recording session.
       recording.stop()
       recording = null
       return
   }

   // Create and start a new recording session.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
       .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
       }
   }

   val mediaStoreOutputOptions = MediaStoreOutputOptions
       .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
       .setContentValues(contentValues)
       .build()

   recording = videoCapture.output
       .prepareRecording(this, mediaStoreOutputOptions)
       .withAudioEnabled()
       .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
           when(recordEvent) {
               is VideoRecordEvent.Start -> {
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.stop_capture)
                       isEnabled = true
                   }
               }
               is VideoRecordEvent.Finalize -> {
                   if (!recordEvent.hasError()) {
                       val msg = "Video capture succeeded: " +
                           "${recordEvent.outputResults.outputUri}"
                       Toast.makeText(
                           baseContext, msg, Toast.LENGTH_SHORT
                       ).show()
                       Log.d(TAG, msg)
                   } else {
                       recording?.close()
                       recording = null
                       Log.e(TAG, "video capture ends with error",
                             recordEvent.error)
                   }
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.start_capture)
                       isEnabled = true
                   }
               }
           }
       }
}

Ressources supplémentaires

Le dépôt GitHub d'exemples d'appareils photo contient plusieurs applications CameraX complètes. Ces exemples permettent de comprendre comment les scénarios de ce guide s'intègrent à une application Android complète.

Si vous avez besoin d'aide supplémentaire pour passer à CameraX ou si vous avez des questions concernant la suite d'API Android Camera, contactez-nous via le groupe de discussion CameraX.