Zero-copia de procesamiento de la cámara y la prestación de tuberías en Android
Tengo que hacer un lado de la CPU de sólo lectura en los datos de cámara en vivo (desde sólo el plano Y), seguido por la representación en la GPU. Los fotogramas no deben renderizarse hasta que se complete el procesamiento (por lo que no siempre quiero procesar el último fotograma de la cámara, sólo el último que haya finalizado el procesamiento). La renderización está desacoplada del procesamiento de la cámara y apunta a 60 FPS incluso si los fotogramas de la cámara llegan a una velocidad inferior a la de la cámara.
Hay una cuestión relacionada, pero de nivel superior en: La cámara aérea más baja de la CPU a GPU enfoque en android
- Pasar datos adicionales para fragmentar el shader a través de VBO - DynamicSpriteBatch
- Uso de opengl para renderizar cada elemento en un listview en android
- GLES10.glGetIntegerv devuelve 0 sólo en Lollipop
- Diferencia entre glOrthof y glViewPort
- Cómo medir y mejorar el uso de la batería en el iPhone / iPad juego (Android también)
Para describir la configuración actual con un poco más de detalle: tenemos un grupo de búfer del lado de la aplicación para datos de cámara donde los búferes están "libres", "en pantalla" o "pendiente de visualización". Cuando un nuevo marco de la cámara llega, capturamos un búfer libre, almacenamos el marco (o una referencia a él si los datos reales están en algún grupo de búfer provisto por el sistema), procesamos y almacenamos los resultados en el búfer, A continuación, establezca el búfer "pendiente de visualización". En el subproceso de renderizado si hay algún búfer "pendiente de visualización" al inicio del bucle de renderización, lo encerramos para que sea el "en pantalla" en su lugar, renderizar la cámara y renderizar el otro contenido usando la información procesada calculada a partir de la misma Marco de la cámara.
Gracias a la respuesta de @fadden sobre la cuestión relacionada arriba, ahora entiendo la característica de "salida paralela" de la cámara androide2, la API comparte los búferes entre las distintas colas de salida, por lo que no debería implicar ninguna copia en los datos, al menos en el androide moderno.
En un comentario, hubo una sugerencia de que podía bloquear las salidas SurfaceTexture y ImageReader al mismo tiempo y simplemente "sentarse en el búfer" hasta que el procesamiento se haya completado. Desafortunadamente no creo que sea aplicable en mi caso debido a la renderización desacoplada que todavía queremos manejar a 60 FPS, y que todavía necesitará acceso al marco anterior mientras se está procesando el nuevo para asegurar que las cosas no se obtengan Fuera de sincronización.
Una solución que ha venido a la mente es tener múltiples SurfaceTextures – uno en cada uno de nuestros buffers del lado de la aplicación (actualmente utilizamos 3). Con ese esquema cuando obtenemos un nuevo marco de cámara, obtendríamos un búfer libre de nuestro grupo de aplicaciones. Entonces llamamos a acquireLatestImage()
en un ImageReader para obtener los datos para el procesamiento, y llamamos a updateTexImage()
en SurfaceTexture en el búfer libre. En el tiempo de render sólo necesitamos asegurarnos de que el SufaceTexture del buffer "in display" es el que está enlazado a GL, y todo debería estar sincronizado la mayor parte del tiempo (como @fadden comentó que hay una carrera entre llamar a updateTexImage()
Y acquireLatestImage()
pero esa ventana de tiempo debe ser lo suficientemente pequeña para que sea raro, y es quizás dectable y fijable de todos modos utilizando las marcas de tiempo en los búferes).
Observo en los documentos que updateTexImage()
sólo se puede llamar cuando SurfaceTexture está enlazado a un contexto GL, lo que sugiere que necesitaré un contexto GL en el hilo de procesamiento de la cámara también para que el hilo de la cámara pueda hacer updateTexImage()
en SurfaceTexture En el búfer "libre" mientras que el subproceso de renderización todavía es capaz de renderizar desde SurfaceTexture desde el búfer "in display".
Así pues, a las preguntas:
- ¿Esto parece un acercamiento sensible?
- ¿Son SurfaceTextures básicamente un envoltorio ligero alrededor del búfer compartido, o consumen algún recurso de hardware limitado y deben usarse con moderación?
- ¿Son las llamadas de SurfaceTexture lo suficientemente baratas como para que usar varias de ellas siga siendo una gran victoria sobre la simple copia de los datos?
- ¿Es el plan de tener dos hilos con distintos contextos de GL con una superficie diferente vinculados en cada uno de ellos probablemente para trabajar o estoy pidiendo un mundo de dolor y los conductores de buggy?
Suena bastante prometedor que voy a darle un ir; Pero pensé que valía la pena preguntar aquí en caso de que alguien (básicamente @fadden!) Sabe de los detalles internos que he pasado por alto que haría que esta una mala idea.
- Fuente de luz dentro de una habitación que actúa inesperadamente
- Android Opengl ES Número máximo de texturas
- Problema en OpenGLRenderer: ruta de acceso demasiado grande para que se preste a una textura
- ¿Cómo acceder a OpenGL ES 2 a través de C ++ / NDK si EGL_NATIVE_RENDERABLE no es compatible?
- GLSurfaceView procesa continuamente a pesar de cambiar el modo de render
- Patrones para el desarrollo de juegos Android?
- OpenGL en Android versus iOS: optimizaciones, y donde difieren
- Transformaciones de matrices Android de OpenGL ES
Interesante pregunta.
Cosas de fondo
Tener múltiples subprocesos con contextos independientes es muy común. Todas las aplicaciones que utilizan renderizado de visualización acelerado por hardware tienen un contexto GLES en el subproceso principal, por lo que cualquier aplicación que utilice GLSurfaceView (o su propio EGL con un SurfaceView o TextureView y un hilo de renderización independiente) está activamente utilizando varios contextos.
Cada TextureView tiene una SurfaceTexture dentro de ella, por lo que cualquier aplicación que utiliza múltiples TextureViews tiene múltiples SurfaceTextures en un solo hilo. (El framework tuvo un error en su implementación que causó problemas con múltiples TextureViews, pero eso era un problema de alto nivel, no un problema de controlador).
SurfaceTexture, a / k / a GLConsumer, no hace un montón de procesamiento. Cuando un marco llega de la fuente (en su caso, la cámara), utiliza algunas funciones EGL para "envolver" el búfer como una textura "externa". No puede realizar estas operaciones EGL sin un contexto EGL para trabajar, por lo que SurfaceTexture tiene que estar conectado a una, y por qué no puede poner un nuevo marco en una textura si el contexto incorrecto es actual. Puedes ver desde la implementación de updateTexImage()
que está haciendo un montón de cosas arcanas con colas de búfer y texturas y vallas, pero ninguna de ellas requiere copiar datos de píxeles. El único recurso del sistema que realmente estás atando es RAM, que no es despreciable si estás captando imágenes de alta resolución.
Conexiones
Un contexto EGL se puede mover entre los subprocesos, pero sólo puede ser "actual" en un subproceso a la vez. El acceso simultáneo desde varios hilos requeriría mucha sincronización indeseable. Un hilo dado tiene sólo un contexto "actual". La API de OpenGL evolucionó de single-threaded con estado global a multi-threaded, y en lugar de volver a escribir la API que sólo empujó el estado en el hilo de almacenamiento local … de ahí la noción de "actual".
Es posible crear contextos EGL que compartan ciertas cosas entre ellos, incluidas las texturas, pero si estos contextos están en diferentes hilos, hay que tener mucho cuidado cuando se actualizan las texturas. Grafika proporciona un buen ejemplo de cómo equivocarse .
SurfaceTextures se construyen en la parte superior de BufferQueues, que tienen una estructura productor-consumidor. Lo divertido de SurfaceTextures es que incluyen ambos lados, por lo que puede alimentar datos en un lado y extraer el otro en un solo proceso (a diferencia, por ejemplo, SurfaceView, donde el consumidor está lejos). Al igual que todas las cosas de la superficie, se construyen en la parte superior de Binder IPC, por lo que puede alimentar la superficie de un hilo, y con seguridad updateTexImage()
en un hilo diferente (o proceso). El API está organizado de tal manera que crea la SurfaceTexture en el lado del consumidor (su proceso) y luego pasa una referencia al productor (por ejemplo, cámara, que se ejecuta principalmente en el proceso mediaserver
).
Implementación
Inducirá un montón de gastos generales si está constantemente conectando y desconectando BufferQueues. Así que si quieres tener tres SurfaceTextures recibiendo búferes, necesitarás conectar los tres a la salida de Camera2 y dejar que todos ellos reciban la "emisión de búfer". A continuación, updateTexImage()
de una manera round-robin. Dado que BufferQueue de SurfaceTexture se ejecuta en modo "asíncrono", siempre debe obtener el marco más nuevo con cada llamada, sin necesidad de "drenar" una cola.
Esta disposición no era realmente posible hasta que el Lollipop-era BufferQueue cambios de la salida múltiple y la introducción de Camera2, así que no sé si alguien ha intentado este acercamiento antes.
Todos los SurfaceTextures se unirían al mismo contexto de EGL, idealmente en un hilo que no sea el subproceso de interfaz de usuario de View, por lo que no tiene que luchar por lo que es actual. Si desea acceder a la textura de un segundo contexto en un subproceso diferente, deberá utilizar las llamadas de API de Attache / Desacoplamiento de SurfaceTexture, que apoyan explícitamente este enfoque:
Se crea un nuevo objeto de textura OpenGL ES y se rellena con el fotograma de la imagen SurfaceTexture que estaba actual en el momento de la última llamada a detachFromGLContext ().
Recuerde que la conmutación de contextos EGL es una operación del lado del consumidor, y no tiene relación con la conexión a la cámara, que es una operación del lado del productor. La sobrecarga que implica mover una SurfaceTexture entre contextos debe ser menor que updateTexImage()
, pero es necesario que updateTexImage()
los pasos habituales para garantizar la sincronización al comunicarse entre los subprocesos.
Es muy malo ImageReader carece de una llamada getTimestamp()
, ya que simplificaría en gran medida la coincidencia de buffers de la cámara.
Conclusión
El uso de SurfaceTextures múltiple para almacenar en búfer la salida es posible pero difícil. Puedo ver una ventaja potencial a un acercamiento del amortiguador del ping-pong, donde un ST se utiliza para recibir un marco en hilo / contexto A mientras que el otro ST se utiliza para renderizar en hilo / contexto B, pero puesto que usted está funcionando en real Tiempo que no creo que haya valor en el almacenamiento en búfer adicional a menos que usted está tratando de pad fuera de la sincronización.
Como siempre, se recomienda leer el documento de arquitectura de gráficos de nivel de sistema de Android .
- ¿Cómo hacer un emulador para la resolución específica del dispositivo (HTC Cha-Cha)?
- Vincular la vista personalizada de Android a una xml de diseño específico (¿es posible?)