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:
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:
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.