2016-06-02 14 views
7

Ho bisogno di fare un processo di sola lettura lato CPU sui dati di telecamera dal vivo (solo dal piano Y) seguito da renderlo sulla GPU. I frame non dovrebbero essere renderizzati fino al completamento dell'elaborazione (quindi non sempre voglio rendere l'ultimo frame dalla fotocamera, solo l'ultimo che il lato CPU ha finito di elaborare). Il rendering è disaccoppiato dall'elaborazione della fotocamera e punta a 60 FPS anche se i fotogrammi della videocamera arrivano a un tasso inferiore.Telecamera Zero-copia Elaborazione e rendering Pipeline su Android

C'è un relativo ma di livello superiore domanda sopra a: Lowest overhead camera to CPU to GPU approach on android

Per descrivere la configurazione corrente in un po 'più in dettaglio: abbiamo un pool di buffer app-laterale per i dati della fotocamera in cui i buffer sono o "libero", "in display" o "display in sospeso". Quando arriva un nuovo frame dalla telecamera, prendiamo un buffer libero, memorizziamo il frame (o un riferimento ad esso se i dati effettivi sono in un pool buffer fornito dal sistema), eseguiamo l'elaborazione e immagazziniamo i risultati nel buffer, quindi impostare il buffer "visualizzazione in sospeso". Nel thread di rendering se c'è un buffer "in attesa di visualizzazione" all'inizio del ciclo di rendering, ci si aggancia a quello "in visualizzazione", invece, si esegue il rendering della telecamera e si esegue il rendering dell'altro contenuto utilizzando le informazioni elaborate calcolate dallo stesso cornice della fotocamera.

Grazie alla risposta di @ fadden sulla domanda collegata sopra ora capisco la funzione "output parallelo" dell'API di Android Camera2 condivide i buffer tra le varie code di output, quindi non dovrebbe comportare alcuna copia sui dati, almeno su Android moderno.

In un commento c'era un suggerimento che potevo bloccare le uscite SurfaceTexture e ImageReader allo stesso tempo e solo "sedermi sul buffer" fino al completamento dell'elaborazione. Sfortunatamente non penso che sia applicabile nel mio caso a causa del rendering disaccoppiato che vogliamo ancora guidare a 60 FPS, e che avrà ancora bisogno di accedere al frame precedente mentre quello nuovo viene elaborato per garantire che le cose non vengano fuori sincrono.

Una soluzione che è venuta in mente è avere SurfaceTextures - uno in ciascuno dei nostri buffer lato app (attualmente ne usiamo 3). Con questo schema, quando otteniamo una nuova cornice per la fotocamera, otterremmo un buffer gratuito dal nostro pool di applicazioni. Quindi chiameremmo acquireLatestImage() su un ImageReader per ottenere i dati da elaborare e chiamare updateTexImage() su SurfaceTexture nel buffer gratuito. Al momento del rendering abbiamo solo bisogno di assicurarci che SufaceTexture dal buffer "in display" sia quello associato a GL, e tutto dovrebbe essere sincronizzato il più delle volte (come commentato da @fadden c'è una corsa tra chiamare lo updateTexImage() e lo acquireLatestImage() ma quella finestra temporale dovrebbe essere abbastanza piccola da renderla rara, ed è forse dectable e risolvibile comunque usando i timestamp nei buffer).

Rilevo nella documentazione che updateTexImage() può essere chiamato solo quando il SurfaceTexture è associato a un contesto GL, il che suggerisce che avrò bisogno di un contesto GL nel thread di elaborazione della fotocamera anche in modo che il filo della macchina fotografica può fare updateTexImage() sul SurfaceTexture nel buffer "libero" mentre il thread di rendering è ancora in grado di eseguire il rendering da SurfaceTexture dal buffer "in display".

Così, alle domande:

  1. Ti sembra un approccio ragionevole?
  2. SurfaceTextures è fondamentalmente un involucro leggero attorno al pool di buffer condiviso oppure consuma alcune risorse hardware limitate e deve essere utilizzato con moderazione?
  3. Le chiamate SurfaceTexture sono tutte abbastanza economiche che l'utilizzo di più sarà ancora una grande vittoria rispetto alla semplice copia dei dati?
  4. Il piano prevede due thread con distinti contesti GL con un SurfaceTexture diverso associato a ciascuno dei quali è probabile che funzioni o sto chiedendo un mondo di driver dolorosi e con errori?

Sembra abbastanza promettente che ho intenzione di provarlo; ma ho pensato che valesse la pena di essere qui nel caso in cui qualcuno (fondamentalmente @fadden!) fosse a conoscenza di dettagli interni che ho trascurato e che renderebbero questa idea pessima.

risposta

7

Interessante domanda.

Sfondo roba

Avendo più thread con contesti indipendenti è molto comune. Ogni app che utilizza il rendering View con accelerazione hardware ha un contesto GLES sul thread principale, quindi qualsiasi applicazione che utilizzi GLSurfaceView (o ruota la propria EGL con SurfaceView o TextureView e un thread di rendering indipendente) utilizza attivamente più contesti.

Ogni TextureView ha un SurfaceTexture al suo interno, quindi qualsiasi applicazione che utilizza più TextureView ha più SurfaceTextures su un singolo thread. (Il framework in realtà had a bug nella sua implementazione che ha causato problemi con più TextureView, ma era un problema di alto livello, non un problema di driver.)

SurfaceTexture, a/k/a GLConsumer, non fa un intero lotto di elaborazione. Quando un frame arriva dalla sorgente (nel tuo caso, la fotocamera), usa alcune funzioni EGL per "avvolgere" il buffer come una trama "esterna". Non è possibile eseguire queste operazioni EGL senza utilizzare un contesto EGL, motivo per cui SurfaceTexture deve essere collegato a uno e perché non è possibile inserire un nuovo frame in una texture se il contesto errato è corrente. Puoi vedere da the implementation of updateTexImage() che sta facendo un sacco di cose arcane con code di buffer e trame e trame, ma nessuna di queste richiede la copia dei dati dei pixel. L'unica risorsa di sistema che stai davvero legando è la RAM, che non è irrilevante se stai catturando immagini ad alta risoluzione.

Connessioni

contesto Un EGL possono essere spostati tra fili, ma può essere solo "corrente" a un thread alla volta. L'accesso simultaneo da più thread richiederebbe molta sincronizzazione indesiderata. Un determinato thread ha solo un contesto "corrente". L'API OpenGL si è evoluta da single-threading con stato globale a multi-thread e, invece di riscrivere l'API, ha semplicemente spostato lo stato nell'archiviazione thread-locale ... da qui la nozione di "corrente".

È possibile creare contesti EGL che condividono alcune cose tra di loro, comprese le trame, ma se questi contesti sono su fili diversi è necessario fare molta attenzione quando le trame vengono aggiornate. Grafika fornisce un bell'esempio di getting it wrong.

SurfaceTextures si basa su BufferQueues, che hanno una struttura produttore-consumatore. La cosa divertente di SurfaceTextures è che includono entrambi i lati, in modo da poter alimentare i dati su un lato e rimuoverli dall'altro all'interno di un singolo processo (diversamente da, ad esempio, SurfaceView, dove il consumatore è lontano). Come tutti gli oggetti di Surface, sono costruiti su Binder IPC, quindi puoi alimentare Surface da un thread e in modo sicuro updateTexImage() in un thread o processo diverso. L'API è organizzata in modo tale da creare SurfaceTexture dal lato del consumatore (il tuo processo) e quindi passare un riferimento al produttore (ad esempio, la fotocamera, che viene eseguita principalmente nel processo mediaserver).

Attuazione

Ti indurre un gruppo di testa, se siete costantemente collegare e scollegare BufferQueues. Quindi, se si desidera che tre SurfaceTexture ricevano i buffer, sarà necessario collegarli tutti e tre all'output di Camera2 e lasciare che tutti ricevano il "buffer broadcast". Quindi tu updateTexImage() in modo round robin. Poiché il BufferQueue di SurfaceTexture viene eseguito in modalità "asincrona", è necessario ottenere sempre il fotogramma più recente ad ogni chiamata, senza necessità di "drenare" una coda.

Questa disposizione non è stata realmente possibile fino a quando le modifiche dell'uscita multipla BufferQueue di Lollipop e l'introduzione di Camera2, quindi non so se qualcuno ha già provato questo approccio prima.

Tutti i SurfaceTextures verrebbero collegati allo stesso contesto EGL, idealmente in un thread diverso dal thread di visualizzazione dell'interfaccia utente, in modo da non dover combattere su ciò che è corrente. Se si desidera accedere alla trama da un secondo contesto in un thread diverso, è necessario utilizzare le chiamate API SurfaceTexture attach/detach, che supportano in modo esplicito questo approccio:

Un nuovo oggetto tessitura OpenGL ES è creato e popolato con la cornice dell'immagine SurfaceTexture che era corrente al momento dell'ultima chiamata a detachFromGLContext().

Ricordare che la commutazione di contesti EGL è un'operazione lato utente e non ha alcuna influenza sulla connessione alla telecamera, che è un'operazione lato produttore. L'overhead coinvolto nello spostamento di SurfaceTexture tra i contesti dovrebbe essere minore - inferiore a updateTexImage() - ma è necessario eseguire le normali operazioni per garantire la sincronizzazione durante la comunicazione tra thread.

Peccato che ImageReader manchi di una chiamata getTimestamp(), in quanto ciò semplificherebbe notevolmente la corrispondenza dei buffer dalla telecamera.

Conclusione

Utilizzando più SurfaceTextures a buffer di uscita è possibile ma difficile. Vedo un potenziale vantaggio per un approccio di ping-pong buffer, in cui un ST viene utilizzato per ricevere un frame in thread/context A mentre l'altro ST è usato per il rendering in thread/context B, ma dal momento che stai operando in real tempo non penso che ci sia valore nel buffering aggiuntivo a meno che non stiate provando ad aggiustare i tempi.

Come sempre, la lettura Android System-Level Graphics Architecture doc è consigliata.

+0

Grazie per l'ottima spiegazione, e sembra un approccio praticabile. Probabilmente lo deriderò nel fine settimana. Probabilmente lo metterà su github – tangobravo

+0

Con "i soliti passi per garantire la sincronizzazione" intendi solo che il thread che chiama 'updateTexImage()' deve aver chiamato 'detachFromGLContext()' prima che il thread di rendering provi ad allegarlo per il rendering? Nessuno dei requisiti 'glFinish()' che sarebbero necessari se i contesti fossero impostati per condividere le trame come menzionato nel bug di grafika che hai collegato? – tangobravo

+0

C'è ['getTimestamp()'] (https://developer.android.com/reference/android/media/Image.html#getTimestamp()) su Immagine dall'API 19. Mi aspetto che restituisca lo stesso timestamp come SurfaceTexture ma controlleremo che quando provo l'approccio. – tangobravo

Problemi correlati