tan*_*avo 10 c++ android opengl-es android-camera2
我需要对实时摄像机数据(仅来自Y平面)执行CPU端只读过程,然后在GPU上进行渲染.在处理完成之前不应渲染帧(因此我并不总是希望从摄像机渲染最新帧,只是CPU端已完成处理的最新帧).渲染与相机处理分离,即使相机帧以低于此速率的速度到达,目标也是60 FPS.
在Android上有一个相关但更高级别的问题:最低开销的相机到CPU到GPU的方法
更详细地描述当前设置:我们有一个用于摄像机数据的应用程序端缓冲池,其中缓冲区是"空闲","显示"或"待处理显示".当来自摄像机的新帧到达时,我们获取一个空闲缓冲区,在那里存储帧(或者如果实际数据在某个系统提供的缓冲池中,则对它进行引用),进行处理并将结果存储在缓冲区中,然后设置缓冲区"待处理显示".在渲染器线程中,如果在渲染循环开始处有任何缓冲区"待处理显示",我们将其锁定为"显示"中的一个,渲染相机,并使用从其计算的处理信息渲染其他内容相机框架.
感谢@ fadden对上面链接的问题的回复,我现在明白了android camera2 API的"并行输出"功能在各种输出队列之间共享缓冲区,因此不应该涉及数据上的任何副本,至少在现代android上.
在评论中有一个建议,我可以同时锁定SurfaceTexture和ImageReader输出,只是"坐在缓冲区",直到处理完成.不幸的是,我不认为这适用于我的情况,因为我们仍然希望以60 FPS驱动的解耦渲染,并且仍然需要访问前一帧,同时处理新帧以确保无法获得不同步.
我想到的一个解决方案是拥有多个SurfaceTextures - 我们每个应用程序端缓冲区中都有一个(我们目前使用3个).使用该方案,当我们获得一个新的相机帧时,我们将从我们的应用程序池中获得一个空闲缓冲区.然后我们调用acquireLatestImage()
ImageReader获取要处理的数据,并updateTexImage()
在空闲缓冲区中调用SurfaceTexture.在渲染时我们只需要确保来自"in display"缓冲区的SufaceTexture是绑定到GL的那个,并且所有内容都应该在大多数时间同步(因为@fadden评论说在调用updateTexImage()
和之间存在争用acquireLatestImage()
但是时间窗口应该足够小以使其变得罕见,并且无论如何使用缓冲区中的时间戳可能是可行的和可修复的).
我注意到文档updateTexImage()
只能在SurfaceTexture绑定到GL上下文时调用,这表明我在相机处理线程中也需要GL上下文,因此相机线程可以updateTexImage()
在"免费"缓冲区中的SurfaceTexture上执行而渲染线程仍然能够从"显示"缓冲区中的SurfaceTexture渲染.
所以,对于问题:
这听起来很有希望,我会试一试; 但是,如果有人(基本上是@fadden!)知道任何我忽略的内部细节会让这个想法变得糟糕,那么我觉得值得一试.
fad*_*den 10
有趣的问题.
背景的东西
拥有独立上下文的多个线程非常常见.每个使用硬件加速视图渲染的应用程序在主线程上都有一个GLES上下文,因此任何使用GLSurfaceView的应用程序(或使用SurfaceView或TextureView以及独立的渲染线程滚动自己的EGL)都会主动使用多个上下文.
每个TextureView里面都有一个SurfaceTexture,因此任何使用多个TextureViews的应用程序在一个线程上都有多个SurfaceTextures.(该框架实际上在其实现中存在一个错误,导致多个TextureView出现问题,但这是一个高级问题,而不是驱动程序问题.)
SurfaceTexture,a/k/a GLConsumer,不进行大量处理.当帧从源(在您的情况下,相机)到达时,它使用一些EGL函数将缓冲区"包裹"为"外部"纹理.如果没有EGL上下文,你就无法进行这些EGL操作,这就是为什么SurfaceTexture必须附加到一个,以及为什么如果错误的上下文是最新的,你不能将新帧放入纹理中.从实现中updateTexImage()
可以看出,它使用缓冲区队列,纹理和栅栏做了很多神秘的事情,但没有一个需要复制像素数据.你真正想要的唯一系统资源是RAM,如果你正在捕获高分辨率的图像,这是不可忽视的.
连接
EGL上下文可以在线程之间移动,但一次只能在一个线程上"当前".来自多个线程的同时访问需要大量不期望的同步.给定线程只有一个"当前"上下文.OpenGL API从具有全局状态的单线程演变为多线程,而不是重写API,它们只是将状态推入线程局部存储......因此"当前"的概念.
可以创建在它们之间共享某些东西的EGL上下文,包括纹理,但如果这些上下文在不同的线程上,则在更新纹理时必须非常小心.格拉菲卡提供了一个错误的好例子.
SurfaceTextures构建在BufferQueues之上,BufferQueues具有生产者 - 消费者结构.SurfaceTextures的有趣之处在于它们包含两面,因此您可以在一个方面提供数据并在一个流程中将其拉出另一个(不像SurfaceView,消费者很远).像所有Surface材料一样,它们构建在Binder IPC之上,因此您可以从一个线程中提供Surface,并安全地updateTexImage()
在不同的线程(或进程)中.安排API,以便在消费者一侧(您的过程)创建SurfaceTexture,然后将引用传递给生产者(例如主要在该mediaserver
过程中运行的相机).
履行
如果你经常连接和断开BufferQueues,你会产生大量的开销.因此,如果您希望有三个SurfaceTextures接收缓冲区,则需要将所有三个连接到Camera2的输出,并让所有它们接收"缓冲区广播".然后你updateTexImage()
以循环方式.由于SurfaceTexture的BufferQueue以"异步"模式运行,因此每次调用时应始终获得最新的帧,而不需要"排空"队列.
在Lollipop时代的BufferQueue多输出改变和Camera2的引入之前,这种安排是不可能的,所以我不知道是否有人之前尝试过这种方法.
所有SurfaceTextures都将附加到相同的EGL上下文中,理想情况下是在View UI线程以外的线程中,因此您不必为当前的内容而战.如果要从不同线程中的第二个上下文访问纹理,则需要使用SurfaceTexture attach/detach API调用,它明确支持此方法:
创建一个新的OpenGL ES纹理对象,并使用最后一次调用detachFromGLContext()时当前的SurfaceTexture图像帧进行填充.
请记住,切换EGL上下文是一个消费者端操作,并且与摄像机的连接无关,这是一个生产者端操作.在上下文之间移动SurfaceTexture所涉及的开销应该很小 - 小于updateTexImage()
- 但是你需要采取通常的步骤来确保线程之间进行通信时的同步.
这太糟糕了,ImageReader缺少一个getTimestamp()
调用,因为这会极大地简化摄像头的匹配缓冲.
结论
使用多个SurfaceTextures缓冲输出是可能的,但很棘手.我可以看到乒乓缓冲区方法的潜在优势,其中一个ST用于接收线程/上下文A中的帧,而另一个ST用于在线程/上下文B中进行渲染,但是因为您在实际操作中时间我不认为额外的缓冲是有价值的,除非你试图填补时间.
与往常一样,建议阅读Android系统级图形架构文档.