Sessioni e richieste di acquisizione della videocamera

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 usare CameraX. Sia CameraX che Camera2 supportano Android 5.0 (livello API 21) e versioni successive.

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

Uno dei motivi per farlo è fare in modo che uno stream, i fotogrammi sequenziali della videocamera da un CameraDevice, è ottimizzato per un'attività specifica, come la visualizzazione un mirino, mentre altri potrebbero essere usati per scattare una foto o per realizzare un video I flussi agiscono come pipeline parallele che elaborano i frame non elaborati. che esce dalla fotocamera,un fotogramma alla volta:

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

L'elaborazione parallela suggerisce che potrebbero esserci limiti di prestazioni a seconda la potenza di elaborazione disponibile dalla CPU, dalla GPU o dall'altro processore. Se la pipeline non riesce a stare al passo con i frame in entrata e inizia a interromperli.

Ogni pipeline ha il proprio formato di output. I dati non elaborati in entrata automaticamente trasformati in formato di output per logica implicita associati a ogni pipeline. Il valore CameraDevice utilizzato in questa pagina esempi di codice non sono specifici, quindi devi prima enumerare tutte le videocamere disponibili prima di procedere.

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

Tuttavia, i casi d'uso di Stream migliorano ed estendono le modalità di utilizzo precedenti di CameraDevice per le sessioni di acquisizione dello streaming, che ti consentono di ottimizzare lo streaming della videocamera caso d'uso specifico. Ad esempio, può migliorare la durata della batteria durante l'ottimizzazione videochiamate.

Un CameraCaptureSession descrive tutte le possibili pipeline associate all'oggetto CameraDevice. Quando viene creata una sessione, non puoi aggiungere o rimuovere pipeline. CameraCaptureSession mantiene una coda di CaptureRequest, che diventano la configurazione attiva.

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

Utilizzare i casi d'uso di Stream per migliorare le prestazioni

I casi d'uso di streaming sono un modo per migliorare le prestazioni dell'acquisizione di Fotocamera 2 sessioni. Offrono al dispositivo hardware più informazioni per regolare i parametri, che offre una migliore esperienza con la videocamera per la tua attività specifica.

Questo Consente alla videocamera di ottimizzare le pipeline hardware e software della videocamera in base agli scenari utente per ogni stream. Per ulteriori informazioni sull'utilizzo dello stream Richieste di assistenza, vedi setStreamUseCase.

I casi d'uso di streaming consentono di specificare come viene utilizzato lo stream di una videocamera maggiori dettagli, oltre a impostare un modello CameraDevice.createCaptureRequest(). Ciò consente all'hardware della videocamera di ottimizzare come la regolazione, la modalità o le impostazioni del sensore della fotocamera, in base a di latenza e qualità adatti a casi d'uso specifici.

I casi d'uso degli stream includono:

  • DEFAULT: copre tutti i comportamenti dell'applicazione esistenti. Equivale a non l'impostazione di qualsiasi caso d'uso di stream.

  • PREVIEW: consigliata per il mirino o l'analisi delle immagini in-app.

  • STILL_CAPTURE: l'obiettivo è ottimizzato per acquisizioni in alta qualità e ad alta risoluzione, ma non si prevede che mantengano frequenze fotogrammi simili a quelle delle anteprime.

  • VIDEO_RECORD: Ottimizzata per l'acquisizione di video di alta qualità, anche di alta qualità stabilizzazione dell'immagine, se supportata dal dispositivo e abilitata dall'applicazione. Questa opzione potrebbe produrre frame di output con un ritardo sostanziale rispetto al tempo reale, per consentire una stabilizzazione o altre elaborazioni di alta qualità.

  • VIDEO_CALL: consigliata per fotocamere di lunga durata in cui il consumo di energia è elevato problema.

  • PREVIEW_VIDEO_STILL: consigliata per le app di social media o l'uso di singoli stream d'uso diversi. È uno stream polivalente.

  • VENDOR_START: per casi d'uso definiti dagli OEM.

Crea un Camera CaptureSession

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

Il seguente snippet di codice mostra come preparare una sessione della videocamera con buffer di output, uno appartenente a un SurfaceView e un altro a un ImageReader. Aggiunta del caso d'uso dello stream PREVIEW a previewSurface e Utilizzo dello streaming STILL_CAPTURE L'impostazione case to imReaderSurface consente all'hardware del dispositivo di ottimizzare questi stream anche ulteriormente.

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 acquisizioni richiede molto lavoro.

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

Il framework cerca di dare il meglio, ma alcune Surface di configurazione potrebbero non funzionare, causando problemi come la sessione non viene creato, viene generato un errore di runtime quando invii una richiesta un peggioramento delle prestazioni. Il framework fornisce garanzie per specifici combinazioni di parametri di dispositivo, superficie e richiesta. La documentazione per createCaptureSession() fornisce ulteriori informazioni.

Richieste di acquisizione singole

La configurazione utilizzata per ogni frame è codificata in un CaptureRequest, che viene inviati alla fotocamera. Per creare una richiesta di acquisizione, puoi utilizzare una delle seguenti opzioni predefinita modelli, oppure TEMPLATE_MANUAL per avere il controllo completo. Quando scegli un devi fornire uno o più buffer di output da utilizzare la richiesta. Puoi usare solo i buffer già definiti nell'acquisizione che intendi utilizzare.

Le richieste di acquisizione utilizzano un pattern del builder e offrono agli sviluppatori l'opportunità di impostare molte diverse tra cui esposizione automatica, messa a fuoco automatica, e Apertura dell'obiettivo. Prima di impostare un campo, assicurati che sia disponibile l'opzione specifica per il parametro dispositivo chiamando CameraCharacteristics.getAvailableCaptureRequestKeys() e che il valore desiderato sia supportato controllando la fotocamera appropriata come l'esposizione automatica disponibile diverse.

Per creare una richiesta di acquisizione per un SurfaceView utilizzando il modello progettata per un'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);

Dopo aver definito una richiesta di acquisizione, ora puoi inviare 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 da richiamare viene attivato. In molti casi, i callback aggiuntivi, come ImageReader.OnImageAvailableListener, viene attivato quando il frame che contiene viene elaborato. È alle ore questo punto che è possibile recuperare i dati di immagine dal buffer specificato.

Richieste di acquisizione ripetute

Le richieste con una singola videocamera sono semplici da eseguire, ma per mostrare un'immagine un'anteprima o un video, non sono molto utili. In questo caso, devi ricevere un flusso continuo di frame, non solo uno. Il seguente snippet di codice mostra come aggiungere un richiesta ripetuta 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 consente alla fotocamera di acquisire continuamente immagini utilizzando le impostazioni nel CaptureRequest fornito. L'API Camera2 consente inoltre agli utenti di acquisire video dalla fotocamera inviando si ripete CaptureRequests come visto in questa Esempio di Camera2 su GitHub. Può anche eseguire il rendering di video in slow motion registrando un'immagine video ad alta velocità (slow motion) con serie di foto a raffica CaptureRequests come mostrato nell'app di esempio video in slow motion di Camera2 su GitHub.

Interfoliazione delle richieste di acquisizione

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 ricorrente in corso. Devi inviare un'acquisizione non ripetuta mentre 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 a richieste a frame singolo o di burst, che consentono 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);

Questo approccio presenta però uno svantaggio: non sai esattamente quando quando si verifica la singola richiesta. Nella figura seguente, se A è la si ripete richiesta di acquisizione e B è la richiesta di acquisizione di frame singolo, questa è la modalità di questa sessione elabora la coda di richieste:

Figura 2. Illustrazione di una coda di richieste per la sessione di 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 volta successiva che verrà utilizzato A di nuovo, quindi è possibile che alcuni frame vengano saltati. Ci sono alcuni aspetti per mitigare il problema:

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

  • Usa una combinazione di modelli adatti a questo particolare scenario, ad esempio zero-shutter-lag.