Sessioni e richieste di acquisizione della videocamera

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

Un singolo dispositivo Android può avere più fotocamere. Ogni videocamera è una CameraDevice e una CameraDevice può produrre più di uno stream contemporaneamente.

Uno dei motivi per farlo è che uno stream, i fotogrammi sequenziali delle videocamere provenienti da CameraDevice, venga ottimizzato per un'attività specifica, come la visualizzazione di un mirino, mentre altri possono essere utilizzati per scattare una foto o registrare un video.Gli stream fungono da pipeline parallele che elaborano i fotogrammi non elaborati che escono dalla videocamera,un fotogramma alla volta:

Figura 1. Illustrazione tratta della creazione di un'app Fotocamera universale (Google I/O '18)

L'elaborazione parallela suggerisce la presenza di limiti di prestazioni a seconda della potenza di elaborazione disponibile da CPU, GPU o altro processore. Se una pipeline non riesce a tenere il passo con i frame in arrivo, inizia a eliminarli.

Ogni pipeline ha il proprio formato di output. I dati non elaborati in arrivo vengono trasformati automaticamente nel formato di output appropriato dalla logica implicita associata a ogni pipeline. Il CameraDevice utilizzato in tutti gli esempi di codice di questa pagina non è specifico, quindi enumera tutte le videocamere disponibili prima di procedere.

Puoi utilizzare CameraDevice per creare un CameraCaptureSession, che è specifico per questo CameraDevice. Un CameraDevice deve ricevere una configurazione di frame per ogni frame non elaborato utilizzando CameraCaptureSession. La configurazione specifica gli attributi della fotocamera come messa a fuoco automatica, apertura, effetti ed esposizione. A causa di vincoli hardware, in un determinato momento è attiva una sola configurazione nel sensore della videocamera, ovvero la configurazione attiva.

Tuttavia, i casi d'uso dello streaming migliorano ed estendono i metodi precedenti di utilizzo di CameraDevice per l'acquisizione dello streaming, consentendoti di ottimizzare lo streaming della videocamera per il caso d'uso specifico. Ad esempio, può aumentare la durata della batteria quando si ottimizzano le videochiamate.

Un CameraCaptureSession descrive tutte le possibili pipeline associate a CameraDevice. Una volta creata una sessione, non puoi aggiungere o rimuovere pipeline. CameraCaptureSession mantiene una coda di CaptureRequest, che diventano la configurazione attiva.

Un elemento CaptureRequest aggiunge una configurazione alla coda e seleziona una, più di una o tutte le pipeline disponibili per ricevere un frame da CameraDevice. Puoi inviare molte richieste di acquisizione nel corso di una sessione di acquisizione. Ogni richiesta può modificare la configurazione attiva e l'insieme di pipeline di output che ricevono l'immagine non elaborata.

Usa i casi d'uso di Stream per migliorare il rendimento

I casi d'uso dello streaming consentono di migliorare le prestazioni delle sessioni di acquisizione Fotocamera2. Forniscono al dispositivo hardware maggiori informazioni per regolare i parametri e offrire così una migliore esperienza con la videocamera per l'attività specifica.

Ciò consente alla videocamera di ottimizzare le pipeline hardware e software della videocamera in base agli scenari utente per ogni stream. Per ulteriori informazioni sui casi d'uso dei flussi, consulta setStreamUseCase.

I casi d'uso dei flussi di dati consentono di specificare come viene utilizzato lo stream della videocamera in modo più dettagliato, oltre a impostare un modello in CameraDevice.createCaptureRequest(). Ciò consente all'hardware della videocamera di ottimizzare i parametri, come la regolazione, la modalità del sensore o le impostazioni del sensore della videocamera, in base a compromessi in termini di qualità o latenza adatti a casi d'uso specifici.

I casi d'uso degli stream includono:

  • DEFAULT: copre tutto il comportamento delle applicazioni esistenti. Equivale a non impostare nessun caso d'uso per i flussi.

  • PREVIEW: consigliato per l'analisi del mirino o delle immagini in-app.

  • STILL_CAPTURE: ottimizzato per acquisizioni di alta qualità e ad alta risoluzione e non si prevede di mantenere una frequenza fotogrammi simile all'anteprima.

  • VIDEO_RECORD: ottimizzato per l'acquisizione di video di alta qualità, inclusa la stabilizzazione delle immagini, se supportata dal dispositivo e attivata dall'applicazione. Questa opzione potrebbe produrre frame di output con un ritardo sostanziale rispetto al tempo reale, per consentire una stabilizzazione di massima qualità o altre elaborazioni.

  • VIDEO_CALL: consigliato per gli utilizzi prolungati della videocamera in cui il consumo di energia è problematico.

  • PREVIEW_VIDEO_STILL: consigliato per i casi d'uso di app di social media o di stream singoli. È uno stream multiuso.

  • VENDOR_START: utilizzato per casi d'uso definiti dall'OEM.

Creazione di una sessione Camera CaptureSession

Per creare una sessione della videocamera, fornisci uno o più buffer di output in cui l'app può scrivere frame di output. Ogni buffer rappresenta una pipeline. Devi eseguire questa operazione prima di iniziare a utilizzare la videocamera, in modo che il framework possa configurare le pipeline interne del dispositivo e allocare i buffer di memoria per l'invio dei frame alle destinazioni di output necessarie.

Il seguente snippet di codice mostra come preparare una sessione della videocamera con due buffer di output, uno appartenente a SurfaceView e l'altro a ImageReader. L'aggiunta del caso d'uso dello stream PREVIEW a previewSurface e al caso d'uso dello stream STILL_CAPTURE a imReaderSurface consente all'hardware del dispositivo di ottimizzare ulteriormente questi stream.

Kotlin


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>){
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    ...
    device.createCaptureSession(session)
}

Java


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
      analysis
// 3. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

A questo punto, non hai definito la configurazione attiva della videocamera. Una volta configurata la sessione, puoi creare e inviare richieste di acquisizione a tale scopo.

La trasformazione applicata agli input quando vengono scritti nel buffer è determinata dal tipo di ciascun target, che deve essere un elemento Surface. Il framework Android sa come convertire un'immagine non elaborata della configurazione attiva in un formato appropriato per ogni destinazione. La conversione è controllata dal formato in pixel e dalle dimensioni del particolare Surface.

Il framework cerca di fare del suo meglio, ma alcune combinazioni di configurazione Surface potrebbero non funzionare, causando problemi come la mancata creazione della sessione, la generazione di un errore di runtime quando invii una richiesta o un peggioramento delle prestazioni. Il framework fornisce garanzie per combinazioni specifiche di parametri per dispositivo, piattaforma e richiesta. La documentazione relativa a createCaptureSession() fornisce ulteriori informazioni.

Richieste Capture singole

La configurazione utilizzata per ogni frame è codificata in un valore CaptureRequest, che viene inviato alla videocamera. Per creare una richiesta di acquisizione, puoi utilizzare uno dei modelli predefiniti oppure TEMPLATE_MANUAL per avere il controllo completo. Quando scegli un modello, devi fornire uno o più buffer di output da utilizzare con la richiesta. Puoi utilizzare solo i buffer già definiti nella sessione di acquisizione che intendi utilizzare.

Le richieste di acquisizione utilizzano uno pattern del costruttore e offrono agli sviluppatori l'opportunità di impostare molte opzioni diverse, tra cui esposizione automatica, messa a fuoco automatica e apertura dell'obiettivo. Prima di impostare un campo, assicurati che l'opzione specifica sia disponibile per il dispositivo chiamando CameraCharacteristics.getAvailableCaptureRequestKeys() e che il valore desiderato sia supportato controllando le caratteristiche della fotocamera appropriate, ad esempio le modalità di esposizione automatica disponibili.

Per creare una richiesta di acquisizione per un elemento SurfaceView utilizzando il modello progettato per l'anteprima senza alcuna modifica, utilizza CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

Una volta definita una richiesta di acquisizione, ora puoi inviarla alla sessione della videocamera:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed
// capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null);

Quando un frame di output viene inserito nel buffer specifico, viene attivato un callback di acquisizione. In molti casi, quando viene elaborato il frame che contiene, vengono attivati altri callback, come ImageReader.OnImageAvailableListener. A questo punto è possibile recuperare i dati dell'immagine dal buffer specificato.

Ripeti richieste di acquisizione

Le richieste con una sola videocamera sono semplici da eseguire, ma non sono molto utili per mostrare un'anteprima in tempo reale o un video. In questo caso, devi ricevere un flusso continuo di frame, non solo uno. Il seguente snippet di codice mostra come aggiungere una richiesta di ripetizione alla sessione:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null);

Una richiesta di acquisizione ripetuta fa sì che la fotocamera acquisisca continuamente immagini utilizzando le impostazioni nell'CaptureRequest fornito. L'API Camera2 consente inoltre agli utenti di acquisire il video dalla videocamera inviando ripetendo CaptureRequests come mostrato in questo repository di esempio Camera2 su GitHub. Può anche eseguire il rendering di video in slow motion acquisendo un video ad alta velocità (slow motion) usando una serie di foto a raffica ripetuta CaptureRequests, come illustrato nell'app di esempio di video in slow motion Camera2 su GitHub.

Interleave CaptureRequests

Per inviare una seconda richiesta di acquisizione mentre la richiesta di acquisizione ripetuta è attiva, ad esempio per visualizzare un mirino e consentire agli utenti di scattare una foto, non è necessario interrompere la richiesta ripetuta in corso. ma invii una richiesta di acquisizione non ripetuta mentre la richiesta ripetuta continua a essere eseguita.

Qualsiasi buffer di output utilizzato deve essere configurato come parte della sessione della videocamera quando la sessione viene creata per la prima volta. Le richieste ripetute hanno una priorità inferiore rispetto alle richieste a frame singolo o burst, il che consente il funzionamento del seguente esempio:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

Tuttavia, questo approccio presenta uno svantaggio: non sai esattamente quando si verifica la singola richiesta. Nella figura seguente, se A è la richiesta di acquisizione ricorrente e B è la richiesta di acquisizione a frame singolo, questo è il modo in cui la sessione elabora la coda delle richieste:

Figura 2. Illustrazione di una coda di richieste per la sessione della videocamera in corso

Non esiste alcuna garanzia per la latenza tra l'ultima richiesta ripetuta da A prima dell'attivazione della richiesta B e la prossima volta che A viene utilizzato di nuovo, quindi potresti notare alcuni frame ignorati. Ecco alcune cose che puoi fare per mitigare questo problema:

  • Aggiungi i target di output dalla richiesta A alla richiesta B. In questo modo, quando il frame di B è pronto, viene copiato nei target di output di A. Ad esempio, questo è essenziale quando si creano snapshot video per mantenere una frequenza fotogrammi stabile. Nel codice precedente, aggiungi singleRequest.addTarget(previewSurface) prima di generare la richiesta.

  • Utilizza una combinazione di modelli adatti a questo particolare scenario, ad esempio nessun tempo di attesa.