Architettura di CameraX

Questa pagina illustra l'architettura di CameraX, compresa la sua struttura, come lavorare con l'API, come lavorare con i cicli di vita e come combinare i casi d'uso.

Struttura di CameraX

Puoi utilizzare CameraX per interfacciarti con la fotocamera di un dispositivo tramite un'astrazione chiamata caso d'uso. Sono disponibili i seguenti casi d'uso:

  • Anteprima: accetta una piattaforma per la visualizzazione di un'anteprima, come una PreviewView.
  • Analisi delle immagini: fornisce buffer accessibili alla CPU per l'analisi, come e il machine learning.
  • Acquisizione immagine: acquisisce e salva una foto.
  • Acquisizione video: acquisisci video e audio con VideoCapture

I casi d'uso possono essere combinati e attivi contemporaneamente. Ad esempio, un'app può consentire l'utente vede l'immagine che la videocamera vede usando un caso d'uso di anteprima, ha un il caso d'uso dell'analisi delle immagini che determina se le persone nella foto siano che sorridono e includi un caso d'uso di acquisizione di immagini per scattare una foto quando lo sono.

Modello API

Per utilizzare la libreria, specifica quanto segue:

  • Il caso d'uso desiderato con le opzioni di configurazione.
  • Cosa fare con i dati di output collegando i listener.
  • Il flusso previsto, ad esempio quando attivare le videocamere e quando produrre dati, legando il caso d'uso ai ciclo di vita dell'architettura Android.

Esistono due modi per scrivere un'app CameraX: CameraController (ideale se vuoi il modo più semplice per usare CameraX) o CameraProvider (ideale se richiedono una maggiore flessibilità).

Controller videocamera

Un CameraController fornisce la maggior parte delle funzionalità di base di CameraX in una singola classe. Richiede poco codice di configurazione e gestisce automaticamente l'inizializzazione della fotocamera, la gestione dei casi d'uso, la rotazione del target, il tocco per mettere a fuoco, lo zoom con due dita e altro ancora. La classe concreta che estende CameraController è LifecycleCameraController.

Kotlin

val previewView: PreviewView = viewBinding.previewView
var cameraController = LifecycleCameraController(baseContext)
cameraController.bindToLifecycle(this)
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
previewView.controller = cameraController

Java

PreviewView previewView = viewBinding.previewView;
LifecycleCameraController cameraController = new LifecycleCameraController(baseContext);
cameraController.bindToLifecycle(this);
cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA);
previewView.setController(cameraController);

I valori predefiniti di UseCase per CameraController sono Preview, ImageCapture e ImageAnalysis. Per disattivare ImageCapture o ImageAnalysis o per attivareVideoCapture, utilizza il metodo setEnabledUseCases().

Per ulteriori utilizzi di CameraController, consulta Esempio di scanner di codici QR o il Video di base su CameraController.

CameraProvider

L'CameraProvider è comunque facile da usare, ma dato che lo sviluppatore dell'app gestisce maggiore è la configurazione, ci sono più opportunità di personalizzazione della configurazione, come abilitare la rotazione dell'immagine di output o impostare il formato dell'immagine di output in ImageAnalysis. Puoi anche utilizzare un Surface personalizzato per l'anteprima della fotocamera, in modo da avere maggiore flessibilità, mentre con CameraController devi utilizzare un PreviewView. L'utilizzo del codice Surface esistente potrebbe essere utile se è già un input per altre parti della tua app.

Puoi configurare i casi d'uso utilizzando i metodi set() e finalizzarli con l'build() . Ogni oggetto caso d'uso fornisce un insieme di API specifiche per il caso d'uso. Per Ad esempio, il caso d'uso relativo all'acquisizione di immagini fornisce una chiamata al metodo takePicture().

Anziché inserire chiamate ai metodi di avvio e arresto specifici in onResume() e onPause(), l'applicazione specifica un ciclo di vita da associare alla videocamera utilizzando cameraProvider.bindToLifecycle(). Questo ciclo di vita informa quindi CameraX quando configurare la sessione di acquisizione della videocamera e assicura che lo stato della videocamera cambi in modo appropriato per adattarsi alle transizioni del ciclo di vita.

Per la procedura di implementazione di ciascun caso d'uso, consulta Implementare un'anteprima, Analizzare le immagini, Acquisizione di immagini e Acquisizione di video

Il caso d'uso di anteprima interagisce con un Surface per la visualizzazione. Le applicazioni creano il caso d'uso con le opzioni di configurazione utilizzando il seguente codice:

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();

Per altri esempi di codice, guarda l'esempio ufficiale di CameraX Google Cloud.

Cicli di vita di CameraX

CameraX osserva un ciclo di vita per determinare quando aprire la videocamera e quando creare una sessione di acquisizione e quando fermarsi e arrestarsi. Le API dei casi d'uso offrono e i callback per monitorare l'avanzamento.

Come spiegato in Combinare casi d'uso, puoi associare alcune combinazioni di casi d'uso a un singolo ciclo di vita. Se la tua app deve supportare casi d'uso che non possono essere combinati, puoi eseguire una delle seguenti operazioni:

  • Raggruppa i casi d'uso compatibili in più di un frammento e poi passa da un frammento all'altro.
  • Crea un componente personalizzato del ciclo di vita e utilizzalo per controllare manualmente la videocamera ciclo di vita

Se scolleghi i proprietari del ciclo di vita dei casi d'uso della visualizzazione e della fotocamera (ad esempio, se utilizzi un ciclo di vita personalizzato o un fragment retain), devi assicurarti che tutti i casi d'uso siano sganciati da CameraX utilizzando ProcessCameraProvider.unbindAll() o sganciando ogni caso d'uso singolarmente. In alternativa, quando vincoli a utilizzare a un ciclo di vita, puoi lasciare che CameraX gestire l'apertura e la chiusura della sessione di acquisizione e svincolare i casi d'uso.

Se tutte le funzionalità della videocamera corrispondono al ciclo di vita di un singolo un componente sensibile al ciclo di vita, come AppCompatActivity o un AppCompat frammento, quindi utilizza il ciclo di vita del componente durante l'associazione per tutti i casi d'uso desiderati si assicurerà che la funzionalità della fotocamera sia pronta quando il componente sensibile al ciclo di vita è attivo e smaltito in modo sicuro, consumare alcuna risorsa, altrimenti.

Proprietari ciclo di vita personalizzato

Per i casi avanzati, puoi creare un LifecycleOwner personalizzato per consentire alla tua app di controllare esplicitamente il ciclo di vita della sessione CameraX anziché legarlo a un LifecycleOwner Android standard.

Il seguente esempio di codice mostra come creare un LifecycleOwner personalizzato semplice:

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

Utilizzando questo LifecycleOwner, la tua app può inserire transizioni di stato nei punti desiderati del codice. Per ulteriori informazioni sull'implementazione di questa funzionalità nella tua app, consulta Implementazione di un modello LifecycleOwner.

Casi d'uso simultanei

I casi d'uso possono essere eseguiti contemporaneamente. Sebbene i casi d'uso possano essere associati in sequenza a un ciclo di vita, è meglio associarli tutti con una singola chiamata a CameraProcessProvider.bindToLifecycle(). Per ulteriori informazioni sulle best practice per le modifiche alla configurazione, consulta Gestire le modifiche alla configurazione.

Nel seguente esempio di codice, l'app specifica i due casi d'uso da creare e da eseguire contemporaneamente. Specifica anche il ciclo di vita da utilizzare in modo che si avviino e si arrestino in base al ciclo di vita.

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

CameraX consente l'uso simultaneo di un'istanza ciascuna di Preview, VideoCapture, ImageAnalysis e ImageCapture. Inoltre,

  • Ogni caso d'uso può funzionare autonomamente. Ad esempio, un'app può registrare video senza utilizzare l'anteprima.
  • Quando le estensioni sono attivate, solo ImageCapture e Preview il funzionamento della combinazione. A seconda dell'implementazione dell'OEM, potrebbe non essere possibile aggiungere anche ImageAnalysis; le estensioni non possono da abilitare per il caso d'uso VideoCapture. Controlla il Documentazione di riferimento sulle estensioni per maggiori dettagli.
  • A seconda delle funzionalità della videocamera, alcune potrebbero supportare la combinazione a modalità di risoluzione inferiori, ma non la stessa combinazione a determinate risoluzioni superiori.
  • Sui dispositivi con livello hardware della videocamera FULL o inferiore, con combinazione di Preview, VideoCapture e ImageCapture o ImageAnalysis potrebbero forzare la FotocameraX per duplicare lo stream PRIV della videocamera per Preview e VideoCapture. Questa duplicazione, chiamata condivisione dello stream, consente l'utilizzo simultaneo di queste funzionalità, ma comporta un aumento delle richieste di elaborazione. Di conseguenza, potresti riscontrare una latenza leggermente più elevata e una durata della batteria ridotta.

Il livello hardware supportato puoi essere recuperato da Camera2CameraInfo. Ad esempio, il seguente codice verifica se la fotocamera posteriore predefinita è un dispositivo LEVEL_3:

Kotlin

@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return CameraSelector.DEFAULT_BACK_CAMERA
            .filter(cameraProvider.availableCameraInfos)
            .firstOrNull()
            ?.let { Camera2CameraInfo.from(it) }
            ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
    }
    return false
}

Java

@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class)
Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA
                .filter(cameraProvider.getAvailableCameraInfos());
        if (!filteredCameraInfos.isEmpty()) {
            return Objects.equals(
                Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic(
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL),
                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3);
        }
    }
    return false;
}

Autorizzazioni

La tua app avrà bisogno dell'autorizzazione CAMERA. A salvare le immagini nei file, sarà inoltre necessario WRITE_EXTERNAL_STORAGE autorizzazione, tranne che sui dispositivi con Android 10 o versioni successive.

Per ulteriori informazioni sulla configurazione delle autorizzazioni per la tua app, leggi l'articolo Richiedere Autorizzazioni app.

Requisiti

CameraX ha i seguenti requisiti minimi di versione:

  • Livello API Android 21
  • Componenti dell'architettura Android 1.1.1

Per le attività che tengono conto del ciclo di vita, utilizza FragmentActivity oppure AppCompatActivity.

Dichiarare le dipendenze

Per aggiungere una dipendenza su CameraX, devi aggiungere il ruolo Google Maven repository al tuo progetto.

Apri il file settings.gradle del progetto e aggiungi il repository google() come mostrato di seguito:

Alla moda

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Aggiungi il seguente codice alla fine del blocco 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"
    }
}

Aggiungi quanto segue al file build.gradle di ogni modulo per un'app:

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.5.0-alpha03"
  // 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.5.0-alpha03"
    // 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}")
}

Per ulteriori informazioni su come configurare la tua app in modo che sia conforme a questi requisiti, consulta la sezione Dichiarazione delle dipendenze.

Interoperabilità di CameraX con Camera2

CameraX è basato su Camera2 e mette a disposizione metodi per leggere e persino scrivere proprietà nell'implementazione di Camera2. Per tutti i dettagli, consulta il pacchetto Interop.

Per ulteriori informazioni su come CameraX ha configurato le proprietà Camera2, utilizza Camera2CameraInfo per leggere CameraCharacteristics sottostante. Puoi anche scegliere di scrivere il testo della fotocamera sottostante in uno dei seguenti due percorsi:

Il seguente esempio di codice utilizza casi d'uso di streaming per ottimizzare per una videochiamata. Utilizza Camera2CameraInfo per recuperare se è disponibile il caso d'uso dello stream di videochiamate. Quindi, utilizza un Camera2Interop.Extender per impostare il caso d'uso del flusso sottostante.

Kotlin

// Set underlying Camera2 stream use case to optimize for video calls.

val videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong()

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
val frontCameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
            )?.contains(videoCallStreamId)
        val isFrontFacing = (cameraInfo.getLensFacing() == 
                             CameraSelector.LENS_FACING_FRONT)
        (isVideoCallStreamingSupported == true) && isFrontFacing
    }

val cameraSelector = frontCameraInfo.cameraSelector

// Start with a Preview Builder.
val previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId)

// Bind the Preview UseCase and the corresponding CameraSelector.
val preview = previewBuilder.build()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

Java

// Set underlying Camera2 stream use case to optimize for video calls.

Long videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong();

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
CameraInfo frontCameraInfo = null;
for (cameraInfo in cameraInfos) {
    Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo)
        .getCameraCharacteristic(
            CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
        );
    boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases)
                .contains(videoCallStreamId);
    boolean isFrontFacing = (cameraInfo.getLensFacing() ==
                             CameraSelector.LENS_FACING_FRONT);

    if (isVideoCallStreamingSupported && isFrontFacing) {
        frontCameraInfo = cameraInfo;
    }
}

if (frontCameraInfo == null) {
    // Handle case where video call streaming is not supported.
}

CameraSelector cameraSelector = frontCameraInfo.getCameraSelector();

// Start with a Preview Builder.
Preview.Builder previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation);

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId);

// Bind the Preview UseCase and the corresponding CameraSelector.
Preview preview = previewBuilder.build()
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

Risorse aggiuntive

Per saperne di più su CameraX, consulta le risorse aggiuntive che seguono.

Codelab

  • Iniziare a usare CameraX
  • Esempio di codice

  • Esempi di app CameraX