Obiettivi e funzionalità della fotocamera

Nota: questa pagina si riferisce al pacchetto Camera2. A meno che la tua app non richieda funzionalità specifiche di basso livello di Camera2, ti consigliamo di utilizzare CameraX. Sia CameraX sia Camera2 supportano Android 5.0 (livello API 21) e versioni successive.

Molti dispositivi Android moderni hanno due o più fotocamere sulla parte anteriore, posteriore o su entrambi i lati del dispositivo. Ogni obiettivo può avere funzionalità uniche, come l'acquisizione di scatti in sequenza, il controllo manuale o il rilevamento del movimento. Un'app per depositare assegni potrebbe utilizzare solo la prima fotocamera posteriore, mentre un'app di social media potrebbe utilizzare per impostazione predefinita una fotocamera anteriore, ma dare agli utenti la possibilità di passare da un obiettivo all'altro. Può anche ricordare le loro scelte.

Questa pagina spiega come elencare gli obiettivi della fotocamera e le relative funzionalità in modo da poter prendere decisioni all'interno dell'app su quale obiettivo utilizzare in una determinata situazione. Il seguente snippet di codice recupera un elenco di tutte le fotocamere e le scorre:

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 variabile cameraLensFacing descrive la direzione in cui è rivolta la fotocamera rispetto allo schermo del dispositivo e ha uno dei seguenti valori:

Per ulteriori informazioni sulla configurazione dell'obiettivo, consulta CameraCharacteristics.LENS_FACING.

La variabile cameraCapabilities dell'esempio di codice precedente contiene informazioni su varie funzionalità, tra cui se la fotocamera è in grado di produrre frame standard come output (a differenza, ad esempio, solo dei dati del sensore di profondità). Puoi verificare se CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE è una delle funzionalità elencate della fotocamera, memorizzata come flag in isBackwardCompatible.

Scegli impostazioni predefinite sensate

Nella tua app, probabilmente vuoi aprire una fotocamera specifica per impostazione predefinita (se disponibile). Ad esempio, un'app per selfie probabilmente apre la fotocamera anteriore, mentre un'app di realtà aumentata potrebbe iniziare con la fotocamera posteriore. La seguente funzione restituisce la prima fotocamera rivolta in una determinata direzione:

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;
}

Attiva il cambio di fotocamera

Molte app per la fotocamera offrono agli utenti la possibilità di passare da una fotocamera all'altra:

Figura 1. Pulsante di cambio fotocamera nell'app Google Fotocamera

Molti dispositivi hanno più fotocamere rivolte nella stessa direzione. Alcuni hanno persino fotocamere USB esterne. Per fornire agli utenti un'interfaccia utente che consenta loro di passare da una fotocamera all'altra, scegli la prima fotocamera disponibile per ogni possibile configurazione dell'obiettivo.

Sebbene non esista una logica universale per la selezione della fotocamera successiva, il seguente codice funziona per la maggior parte dei casi d'uso:

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;
}

Questo codice funziona per un'ampia gamma di dispositivi con molte configurazioni diverse. Per ulteriori informazioni sulla gestione dei casi limite, consulta CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA.

Crea app compatibili

Per le app che utilizzano ancora l'API Camera obsoleta, il numero di fotocamere che Camera.getNumberOfCameras() restituisce dipende dall'implementazione OEM. Se nel sistema è presente una fotocamera multipla logica, per mantenere la compatibilità con le versioni precedenti dell'app, questo metodo esporrà una sola fotocamera per ogni gruppo di fotocamere fisiche logiche e sottostanti. Utilizza l'API Camera2 per visualizzare tutte le fotocamere.

Per ulteriori informazioni di base sugli orientamenti della fotocamera, consulta Camera.CameraInfo.orientation.

In generale, utilizza l' Camera.getCameraInfo() API per eseguire query su tutti gli orientation della fotocamera, ed esporre una sola fotocamera per ogni orientamento disponibile agli utenti che passano da una fotocamera all'altra.

Adatta a tutti i tipi di dispositivi

Non dare per scontato che la tua app venga sempre eseguita su un dispositivo portatile con una o due fotocamere. Scegli invece le fotocamere più adatte all'app. Se non hai bisogno di una fotocamera specifica, seleziona la prima fotocamera rivolta nella direzione desiderata. Se la fotocamera rivolta in una determinata direzione non è disponibile, valuta se l'utente può completare il percorso con un'altra fotocamera. Non limitare la disponibilità della tua app su determinati dispositivi in base all'hardware della fotocamera. Se è collegata una fotocamera esterna, l'utente probabilmente la preferisce come predefinita.