Architettura di CameraX

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

Struttura CameraX

Puoi usare 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, ad esempio un PreviewView.
  • Analisi delle immagini: fornisce buffer accessibili dalla CPU per l'analisi, ad esempio per il machine learning.
  • Acquisizione immagine: consente di acquisire e salvare 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 all'utente di visualizzare l'immagine inquadrata dalla fotocamera utilizzando un caso d'uso in anteprima, avere un caso d'uso di analisi delle immagini che determina se le persone nella foto sorridono e includere un caso d'uso di acquisizione di immagini per scattare una foto in un secondo momento.

Modello API

Per lavorare con la libreria, devi specificare 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 abilitare le fotocamere e quando produrre dati, associando il caso d'uso ai cicli di vita dell'architettura Android.

Esistono due modi per scrivere un'app CameraX: CameraController (ottima se vuoi usare il modo più semplice per usare CameraX) o CameraProvider (ottima se hai bisogno di più flessibilità).

Controller per fotocamera

Un elemento CameraController offre la maggior parte delle funzionalità di base di CameraX in un'unica classe. Richiede un codice di configurazione minimo e gestisce automaticamente l'inizializzazione della fotocamera, la gestione dei casi d'uso, la rotazione della destinazione, il tocco per mettere a fuoco, lo pizzico per lo zoom e altro ancora. La classe di calcestruzzo che si 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 dei UseCase per CameraController sono Preview, ImageCapture e ImageAnalysis. Per disattivare ImageCapture o ImageAnalysis oppure per attivare VideoCapture, utilizza il metodo setEnabledUseCases().

Per ulteriori utilizzi di CameraController, guarda l'esempio di scansione di codici QR o guarda il video di base di CameraController.

Fornitore Fotocamera

CameraProvider è ancora facile da usare, ma poiché lo sviluppatore dell'app gestisce una maggior parte della configurazione, esistono più opportunità di personalizzarla, ad esempio abilitare la rotazione dell'immagine di output o impostare il formato dell'immagine di output in ImageAnalysis. Inoltre, puoi usare un elemento Surface personalizzato per l'anteprima della fotocamera per una maggiore flessibilità, mentre con CameraController devi usare un PreviewView. L'utilizzo del codice Surface esistente potrebbe essere utile se è già un input in altre parti dell'app.

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

Invece di un'applicazione che effettua chiamate specifiche ai metodi di avvio e arresto in onResume() e onPause(), l'applicazione specifica un ciclo di vita a cui associare la videocamera utilizzando cameraProvider.bindToLifecycle(). Questo ciclo di vita indica a CameraX quando configurare la sessione di acquisizione e garantisce che lo stato della videocamera cambi in modo appropriato in base alle transizioni del ciclo di vita.

Per la procedura di implementazione per ogni caso d'uso, consulta Implementazione di un'anteprima, Analisi di immagini, Acquisizione di immagini e Acquisizione video

Il caso d'uso in anteprima interagisce con una Surface per la visualizzazione. Le applicazioni creano il caso d'uso con 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 codici di esempio, consulta l'app di esempio ufficiale CameraX.

Cicli di vita di CameraX

CameraX osserva un ciclo di vita per determinare quando aprire la fotocamera, quando creare una sessione di acquisizione e quando arrestarla e spegnerla. Le API dei casi d'uso forniscono chiamate e callback di metodi per monitorare l'avanzamento.

Come spiegato in Combinare i 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:

  • Raggruppa i casi d'uso compatibili in più di un frammento e poi passa da un frammento all'altro
  • Creando un componente del ciclo di vita personalizzato e usalo per controllare manualmente il ciclo di vita della videocamera

Se disaccoppi i proprietari del ciclo di vita dei casi d'uso di vista e videocamera (ad esempio, se utilizzi un ciclo di vita personalizzato o un frammento di conservazione), devi assicurarti che tutti i casi d'uso non siano associati a CameraX utilizzando ProcessCameraProvider.unbindAll() o annullando l'associazione di ogni caso d'uso singolarmente. In alternativa, quando associ i casi d'uso a un ciclo di vita, puoi consentire a CameraX di gestire l'apertura e la chiusura della sessione di acquisizione e lo scollegamento dei casi d'uso.

Se tutte le funzionalità della videocamera corrispondono al ciclo di vita di un singolo componente sensibile al ciclo di vita, come un frammento AppCompatActivity o AppCompat, utilizzando il ciclo di vita di quel componente quando associ tutti i casi d'uso desiderati, la funzionalità della videocamera sarà pronta quando il componente sensibile al ciclo di vita è attivo ed eliminato in modo sicuro, altrimenti senza consumare risorse.

Proprietari cicli di vita personalizzati

Nei casi più avanzati, puoi creare una LifecycleOwner personalizzata per consentire alla tua app di controllare esplicitamente il ciclo di vita della sessione CameraX, invece di associarla a un LifecycleOwner standard di Android.

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

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

Con questo LifecycleOwner, la tua app può inserire le transizioni di stato nel codice nei punti desiderati. Per saperne di più sull'implementazione di questa funzionalità nell'app, consulta Implementazione di un proprietario di ciclo di vita personalizzato.

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 associare tutti i casi d'uso 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 ed eseguire contemporaneamente. Specifica inoltre il ciclo di vita da utilizzare per entrambi i casi d'uso, in modo che vengano avviati e arrestati 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));
}

È garantito che le seguenti combinazioni di configurazione siano supportate (quando è richiesta l'anteprima o l'acquisizione video, ma non entrambe contemporaneamente):

Preview o Video Capture Acquisizione di immagini Analisi Descrizioni
Fornisci all'utente un'anteprima o registra un video, scatta una foto e analizza lo stream di immagini.
  Scatta una foto e analizza lo stream di immagini.
  Fornisci all'utente un'anteprima o registra un video e scatta una foto.
  Fornisci all'utente un'anteprima o registra un video e analizza lo stream di immagini.

Quando sono necessarie sia l'anteprima che l'acquisizione video, le seguenti combinazioni di casi d'uso sono supportate in modo condizionale:

Anteprima Acquisizione video Acquisizione di immagini Analisi Requisito speciale
    Garantito per tutte le videocamere
  Fotocamera LIMITATA (o migliore).
  Fotocamera LEVEL_3 (o migliore).

Inoltre,

  • Ogni caso d'uso può funzionare in modo indipendente. Ad esempio, un'app può registrare video senza usare l'anteprima.
  • Quando le estensioni sono attivate, è garantito che solo la combinazione ImageCapture e Preview funzioni. A seconda dell'implementazione OEM, potrebbe non essere possibile aggiungere anche ImageAnalysis; le estensioni non possono essere abilitate per il caso d'uso VideoCapture. Per ulteriori dettagli, consulta la documentazione di riferimento sulle estensioni.
  • A seconda delle capacità della fotocamera, alcune videocamere potrebbero supportare la combinazione con modalità di risoluzione più bassa, ma non possono supportare la stessa combinazione con risoluzioni più elevate.

Il livello hardware supportato può 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\ 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 dovrà avere l'autorizzazione CAMERA. Per salvare le immagini nei file, sarà necessaria anche l'autorizzazione WRITE_EXTERNAL_STORAGE, ad eccezione dei dispositivi con Android 10 o versioni successive.

Per ulteriori informazioni sulla configurazione delle autorizzazioni per la tua app, consulta 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à incentrate sul ciclo di vita, utilizza FragmentActivity o AppCompatActivity.

Dichiara le dipendenze

Per aggiungere una dipendenza a CameraX, devi aggiungere il repository Maven di Google al tuo progetto.

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

trendy

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

Kotlin

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

Aggiungi quanto segue alla fine del blocco Android:

trendy

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:

trendy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.4.0-alpha05"
  // The following line is optional, as the core library is included indirectly by camera-camera2
  implementation "androidx.camera:camera-core:${camerax_version}"
  implementation "androidx.camera:camera-camera2:${camerax_version}"
  // If you want to additionally use the CameraX Lifecycle library
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  // If you want to additionally use the CameraX VideoCapture library
  implementation "androidx.camera:camera-video:${camerax_version}"
  // If you want to additionally use the CameraX View class
  implementation "androidx.camera:camera-view:${camerax_version}"
  // If you want to additionally add CameraX ML Kit Vision Integration
  implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
  // If you want to additionally use the CameraX Extensions library
  implementation "androidx.camera:camera-extensions:${camerax_version}"
}

Kotlin

dependencies {
    // CameraX core library using the camera2 implementation
    val camerax_version = "1.4.0-alpha05"
    // The following line is optional, as the core library is included indirectly by camera-camera2
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    // If you want to additionally use the CameraX Lifecycle library
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    // If you want to additionally use the CameraX VideoCapture library
    implementation("androidx.camera:camera-video:${camerax_version}")
    // If you want to additionally use the CameraX View class
    implementation("androidx.camera:camera-view:${camerax_version}")
    // If you want to additionally add CameraX ML Kit Vision Integration
    implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
    // If you want to additionally use the CameraX Extensions library
    implementation("androidx.camera:camera-extensions:${camerax_version}")
}

Per saperne di più sulla configurazione dell'app in modo che sia conforme a questi requisiti, consulta Dichiarare le dipendenze.

Interoperabilità di CameraX con Camera2

CameraX è basata su Camera2 e CameraX espone modi per leggere e persino scrivere le proprietà nell'implementazione Camera2. Per informazioni dettagliate, consulta il Pacchetto Interop.

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

Il seguente esempio di codice utilizza casi d'uso di stream per l'ottimizzazione di una videochiamata. Utilizza Camera2CameraInfo per recuperare se il caso d'uso dello stream di videochiamate è disponibile. Quindi, utilizza una 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 seguenti risorse aggiuntive.

Codelab

  • Introduzione a FotocameraX
  • Esempio di codice

  • Esempi di app CameraX