2016-05-29 40 views
6

La mia applicazione deve eseguire alcune elaborazioni su fotogrammi videocamera dal vivo sulla CPU, prima di renderle sulla GPU. Ci sono anche altre cose che vengono renderizzate sulla GPU che dipende dai risultati dell'elaborazione della CPU; quindi è importante mantenere tutto sincronizzato in modo da non rendere il frame stesso sulla GPU finché non sono disponibili anche i risultati dell'elaborazione della CPU per quel frame.Telecamera da soffitto più bassa per approccio da CPU a GPU su Android

La domanda è qual è l'approccio di overhead più basso per questo su Android?

L'elaborazione della CPU nel mio caso richiede solo un'immagine in scala di grigi, quindi un formato YUV in cui è imballato il piano Y è l'ideale (e tende ad essere una buona corrispondenza con il formato nativo dei dispositivi della fotocamera). NV12, NV21 o YUV completamente planare forniranno tutti un accesso low-overhead ideale a scala di grigi, quindi sarebbe preferibile sul lato CPU.

Nell'API della telecamera originale il setPreviewCallbackWithBuffer() era l'unico modo ragionevole per ottenere dati nella CPU per l'elaborazione. Questo aveva il piano Y separato, quindi era l'ideale per l'elaborazione della CPU. Rendere questo frame disponibile per OpenGL per il rendering in un basso overhead era l'aspetto più impegnativo. Alla fine ho scritto una routine di conversione del colore NEON per emettere RGB565 e usare semplicemente glTexSubImage2d per renderlo disponibile sulla GPU. Questo è stato implementato per la prima volta nel periodo Nexus 1, in cui anche una chiamata glTexSubImage2d di 320x240 ha impiegato 50 ms di tempo CPU (presumibilmente i driver poveri tentano di fare il swizzling della trama - questo è stato migliorato in modo significativo in un aggiornamento del sistema in seguito).

Nel corso della giornata ho esaminato le estensioni di eglImage, ma non sembrano disponibili o sufficientemente documentate per le app degli utenti. Ho dato un piccolo sguardo alle classi interne di GraphicsBuffer Android, ma idealmente voglio rimanere nel mondo delle API pubbliche supportate.

L'API android.hardware.camera2 era promettente per poter collegare sia ImageReader che SurfaceTexture a una sessione di acquisizione. Sfortunatamente non riesco a vedere alcun modo di garantire la giusta pipeline sequenziale qui: tenere fuori chiamata updateTexImage() finché la CPU non è stata elaborata è abbastanza facile, ma se un altro frame è arrivato durante tale elaborazione, updateTexImage() salterà direttamente all'ultimo telaio. Sembra anche con più uscite ci saranno copie indipendenti dei frame in ciascuna delle code che idealmente vorrei evitare.

Idealmente questo è ciò che vorrei:

  1. Driver fotocamera riempie certa memoria con le ultime telaio
  2. CPU ottiene puntatore ai dati in memoria, può leggere i dati Y senza una copia compiuti
  3. CPU gestisce dati e imposta un flag nel mio codice, quando telaio è pronto
  4. quando si inizia a rendere un telaio, controllare se un nuovo telaio è pronto
  5. chiamata alcune API di impegnare la stessa memoria come GL trama
  6. Quando un frame più recente è pronto, rilasciare il buffer tenendo il fotogramma precedente indietro nella piscina

non riesco a vedere un modo di fare esattamente questo stile di zero-copy con API pubblica su Android, ma ciò che è il più vicino che è possibile ottenere?

Una cosa pazzesca che ho provato sembra funzionare, ma non è documentata: l'API NDK ANativeWindow può accettare il formato di dati NV12, anche se la costante di formato appropriata non è tra quelle presenti nelle intestazioni pubbliche.Ciò consente a SurfaceTexture di essere riempito con i dati NV12 da memcpy() per evitare la conversione del colore sul lato CPU e qualsiasi swizzling che si verifica lato driver in glTexImage2d. Questa è ancora una copia extra dei dati, anche se sembra che non dovrebbe essere necessaria, e ancora una volta in quanto non documentata potrebbe non funzionare su tutti i dispositivi. Una fotocamera sequenziale a copia zero supportata -> ImageReader -> SurfaceTexture o equivalente sarebbe perfetta.

risposta

5

Il modo più efficiente per elaborare il video è evitare del tutto la CPU, ma sembra che non sia un'opzione per voi. Le API pubbliche sono generalmente orientate a fare tutto nell'hardware, poiché è ciò di cui ha bisogno il framework stesso, sebbene ci siano alcuni percorsi per RenderScript. (Sto assumendo che hai visto la Grafika filter demo che utilizza shader frammento.)

Accesso ai dati sulla CPU utilizzata per indicare le API della fotocamera lente o lavorare con GraphicBuffer e funzioni relativamente oscuri EGL (ad esempio this question). Il punto di ImageReader era di fornire accesso zero-copia ai dati YUV dalla fotocamera.

Non è possibile serializzare realmente Camera -> ImageReader -> SurfaceTexture poiché ImageReader non ha un'API "inoltra il buffer". Il che è sfortunato, in quanto ciò renderebbe questo banale. Potresti provare a replicare ciò che fa SurfaceTexture, usando le funzioni EGL per impacchettare il buffer come una trama esterna, ma di nuovo sei in GraphicBuffer-land non pubblico, e mi preoccupo dei problemi di proprietà/durata del buffer.

Non sono sicuro di come i percorsi paralleli possano aiutarvi (Camera2 -> ImageReader, Camera2 -> SurfaceTexture), poiché ciò che viene inviato a SurfaceTexture non avrebbe le vostre modifiche. FWIW, non implica una copia aggiuntiva - in Lollipop o nei dintorni, BufferQueue è stato aggiornato per consentire ai singoli buffer di spostarsi attraverso più code.

È del tutto possibile che ci siano nuove fantasiose API che non ho ancora visto, ma da quello che so che il tuo approccio ANativeWindow è probabilmente il vincitore. Sospetto che starai meglio con uno dei formati Camera (YV12 o NV21) rispetto a NV12, ma non lo so per certo.

FWIW, si eliminano i fotogrammi se l'elaborazione richiede troppo tempo, ma a meno che l'elaborazione non sia irregolare (alcuni fotogrammi richiedono molto più tempo di altri) è necessario eliminare i fotogrammi, a prescindere da cosa. Entrando nuovamente nel regno delle API non pubbliche, puoi passare a SurfaceTexture in modalità "sincrona", ma se i tuoi buffer si riempiono, stai ancora perdendo i frame.

+0

Ottima risposta, grazie. Il lato CPU in realtà non sta scrivendo sul buffer, è fondamentalmente un'app AR che ha bisogno di calcolare il movimento dell'oggetto nel frame sulla CPU per rendere correttamente il contenuto sovrapposto nella giusta posizione sulla GPU. Quindi i percorsi paralleli andrebbero bene, ma penso che la parte che manca sia ottenere il buffer giusto in SurfaceTexture; la "ultima CPU elaborata" anziché "l'ultima in coda". – tangobravo

+0

Ho esaminato i documenti RenderScript un po 'ieri. Dubito che funzionerà, ma se è possibile avere un'allocazione che è USAGE_IO_INPUT | USAGE_IO_OUTPUT | USAGE_SCRIPT che potrebbe consentire di essere sia la destinazione per la telecamera sia la sorgente per SurfaceTexture, con la CPU in controllo della temporizzazione del buffer che si muove attraverso la pipe. Le intestazioni NDK di RenderScript hanno una funzione getPointer() sull'allocazione, quindi dovrebbe essere in grado di elaborare in codice C++ NDK (sfortunatamente le intestazioni non sono nell'ultima versione di NDK, quindi non si è sicuri di quale sia il problema). – tangobravo

+0

Non ho una buona immagine mentale della tua pipeline parallela, ma sembra che tu voglia chiamare 'updateTexImage()' e 'acquireLatestImage()' allo stesso tempo, e poi semplicemente sederti sul buffer latched mentre la CPU fa il suo lavoro. Non è esente da corse, in quanto una nuova cornice potrebbe atterrare tra le due chiamate, ma ciò dovrebbe essere raro e può essere mitigato tenendo d'occhio l'orologio. – fadden

Problemi correlati