Objectifs pour appareil photo et fonctionnalités

Remarque : Cette page fait référence au package Camera2. À moins que votre application ne nécessite des fonctionnalités spécifiques de bas niveau de Camera2, nous vous recommandons d'utiliser CameraX. CameraX et Camera2 sont compatibles avec Android 5.0 (niveau d'API 21) ou version ultérieure.

De nombreux appareils Android modernes sont équipés de deux caméras ou plus à l'avant, à l'arrière ou des deux côtés. Chaque objectif peut avoir des capacités uniques, telles que la capture en rafale, le contrôle manuel ou le suivi du mouvement. Une application de dépôt de chèques peut n'utiliser que la première caméra arrière, tandis qu'une application de réseaux sociaux peut utiliser par défaut une caméra avant, mais donner aux utilisateurs la possibilité de basculer entre tous les objectifs disponibles. Il peut également mémoriser leurs choix.

Cette page explique comment lister les objectifs de l'appareil photo et leurs fonctionnalités afin que vous puissiez décider dans votre application quel objectif utiliser dans une situation donnée. L'extrait de code suivant récupère la liste de toutes les caméras et les parcourt :

Kotlin

try {
    val cameraIdList = cameraManager.cameraIdList // may be empty

    // iterate over available camera devices
    for (cameraId in cameraIdList) {
        val characteristics = cameraManager.getCameraCharacteristics(cameraId)
        val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
        val cameraCapabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        val isBackwardCompatible = cameraCapabilities?.contains(
            CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        ...
    }
} catch (e: CameraAccessException) {
    e.message?.let { Log.e(TAG, it) }
    ...
}

Java

try {
    String[] cameraIdList = cameraManager.getCameraIdList(); // may be empty

    // iterate over available camera devices
    for (String cameraId : cameraIdList) {
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
        int cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
        int[] cameraCapabilities =
            characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        boolean isBackwardCompatible = false;
        for (int capability : cameraCapabilities) {
            if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                isBackwardCompatible = true;
                break;
            }
        }
        ...
    }
} catch (CameraAccessException e) {
    Log.e(TAG, e.getMessage());
    ...
}

La variable cameraLensFacing décrit la direction vers laquelle la caméra est orientée par rapport à l'écran de l'appareil. Elle peut prendre l'une des valeurs suivantes :

Pour en savoir plus sur la configuration orientée vers l'objectif, consultez CameraCharacteristics.LENS_FACING.

La variable cameraCapabilities de l'exemple de code précédent contient des informations sur diverses capacités, y compris si la caméra est capable de produire des images standards en sortie (par opposition, par exemple, aux données du capteur de profondeur uniquement). Vous pouvez vérifier si CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE fait partie des fonctionnalités listées de la caméra, qui sont stockées sous forme d'indicateur dans isBackwardCompatible.

Choisir des valeurs par défaut judicieuses

Dans votre application, vous souhaitez probablement ouvrir une caméra spécifique par défaut (si elle est disponible). Par exemple, une application de selfie ouvre probablement la caméra frontale, tandis qu'une application de réalité augmentée peut commencer par la caméra arrière. La fonction suivante renvoie la première caméra orientée dans une direction donnée :

Kotlin

fun getFirstCameraIdFacing(cameraManager: CameraManager,
                           facing: Int = CameraMetadata.LENS_FACING_BACK): String? {
    try {
        // Get list of all compatible cameras
        val cameraIds = cameraManager.cameraIdList.filter {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
            capabilities?.contains(
                    CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        }

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration
        cameraIds.forEach {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) {
                return it
            }
        }

        // If no camera matched desired orientation, return the first one from the list
        return cameraIds.firstOrNull()
    } catch (e: CameraAccessException) {
        e.message?.let { Log.e(TAG, it) }
    }
}

Java

public String getFirstCameraIdFacing(CameraManager cameraManager, @Nullable Integer facing) {
    if (facing == null) facing = CameraMetadata.LENS_FACING_BACK;
    String cameraId = null;

    try {
        // Get a list of all compatible cameras
        String[] cameraIdList = cameraManager.getCameraIdList();

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration and backward compatibility
        for (String id : cameraIdList) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
                        && characteristics.get(CameraCharacteristics.LENS_FACING).equals(facing)) {
                    cameraId = id;
                    break;
                }
            }
        }

        // If no camera matches the desired orientation, return the first one from the list
        cameraId = cameraIdList[0];
    } catch (CameraAccessException e) {
        Log.e(TAG, "getFirstCameraIdFacing: " + e.getMessage());
    }

    return cameraId;
}

Activer le changement de caméra

De nombreuses applications de caméras permettent aux utilisateurs de passer d'une caméra à l'autre :

Figure 1 Bouton de changement de caméra dans l'application Appareil photo Google

De nombreux appareils sont équipés de plusieurs caméras orientées dans la même direction. Certaines sont même équipées de caméras USB externes. Pour fournir aux utilisateurs une UI qui leur permet de basculer entre différentes caméras orientées, choisissez la première caméra disponible pour chaque configuration possible de l'objectif.

Bien qu'il n'existe pas de logique universelle pour sélectionner la caméra suivante, le code suivant fonctionne pour la plupart des cas d'utilisation :

Kotlin

fun filterCompatibleCameras(cameraIds: Array<String>,
                            cameraManager: CameraManager): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)?.contains(
                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
    }
}

fun filterCameraIdsFacing(cameraIds: List<String>, cameraManager: CameraManager,
                          facing: Int): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.LENS_FACING) == facing
    }
}

fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? {
    // Get all front, back and external cameras in 3 separate lists
    val cameraIds = filterCompatibleCameras(cameraManager.cameraIdList, cameraManager)
    val backCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK)
    val frontCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT)
    val externalCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL)

    // The recommended order of iteration is: all external, first back, first front
    val allCameras = (externalCameras + listOf(
            backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull()

    // Get the index of the currently selected camera in the list
    val cameraIndex = allCameras.indexOf(currCameraId)

    // The selected camera may not be in the list, for example it could be an
    // external camera that has been removed by the user
    return if (cameraIndex == -1) {
        // Return the first camera from the list
        allCameras.getOrNull(0)
    } else {
        // Return the next camera from the list, wrap around if necessary
        allCameras.getOrNull((cameraIndex + 1) % allCameras.size)
    }
}

Java

public List<String> filterCompatibleCameras(CameraManager cameraManager, String[] cameraIds) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                    compatibleCameras.add(id);
                }
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCompatibleCameras: " + e.getMessage());
    }

    return compatibleCameras;
}

public List<String> filterCameraIdsFacing(CameraManager cameraManager, List<String> cameraIds, int lensFacing) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == lensFacing) {
                compatibleCameras.add(id);
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCameraIdsFacing: " + e.getMessage());
    }

    return compatibleCameras;
}

public String getNextCameraId(CameraManager cameraManager, @Nullable String currentCameraId) {
    String nextCameraId = null;

    try {
        // Get all front, back, and external cameras in 3 separate lists
        List<String> compatibleCameraIds = filterCompatibleCameras(cameraManager, cameraManager.getCameraIdList());
        List<String> backCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_BACK);
        List<String> frontCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_FRONT);
        List<String>externalCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_EXTERNAL);

        // The recommended order of iteration is: all external, first back, first front
        List<String> allCameras = new ArrayList<>(externalCameras);
        if (!backCameras.isEmpty()) allCameras.add(backCameras.get(0));
        if (!frontCameras.isEmpty()) allCameras.add(frontCameras.get(0));

        // Get the index of the currently selected camera in the list
        int cameraIndex = allCameras.indexOf(currentCameraId);

        // The selected camera may not be in the list, for example it could be an
        // external camera that has been removed by the user
        if (cameraIndex == -1) {
            // Return the first camera from the list
            nextCameraId = !allCameras.isEmpty() ? allCameras.get(0) : null;
        else {
            if (!allCameras.isEmpty()) {
                // Return the next camera from the list, wrap around if necessary
                nextCameraId = allCameras.get((cameraIndex + 1) % allCameras.size());
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "getNextCameraId: " + e.getMessage());
    }

    return nextCameraId;
}

Ce code fonctionne pour un grand nombre d'appareils avec de nombreuses configurations différentes. Pour en savoir plus sur la prise en compte des cas extrêmes, consultez CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA.

Créer des applications compatibles

Pour les applications qui utilisent encore l'API Camera obsolète, le nombre de caméras renvoyé par Camera.getNumberOfCameras() dépend de l'implémentation de l'OEM. S'il existe une multicaméra logique dans le système, cette méthode n'exposera qu'une seule caméra pour chaque caméra logique et groupe de caméras physiques sous-jacent, afin de maintenir la rétrocompatibilité de l'application. Utilisez l'API Camera2 pour afficher toutes les caméras.

Pour en savoir plus sur les orientations de la caméra, consultez Camera.CameraInfo.orientation.

En général, utilisez l'API Camera.getCameraInfo() pour interroger tous les orientation d'appareil photo et n'exposez qu'un seul appareil photo pour chaque orientation disponible aux utilisateurs qui passent d'un appareil photo à l'autre.

S'adapter à tous les types d'appareils

Ne partez pas du principe que votre application s'exécute toujours sur un appareil portable doté d'une ou deux caméras. Choisissez plutôt les caméras les plus appropriées pour l'application. Si vous n'avez pas besoin d'une caméra spécifique, sélectionnez la première caméra orientée dans la direction souhaitée. Si la caméra orientée dans une certaine direction n'est pas disponible, déterminez si l'utilisateur peut terminer son parcours avec une autre caméra. Ne limitez pas la disponibilité de votre application sur certains appareils en fonction du matériel de l'appareil photo. Si une caméra externe est connectée, l'utilisateur la préfère probablement comme caméra par défaut.