Android 上最低的摄像头到 CPU 到 GPU 的方法

Posted

技术标签:

【中文标题】Android 上最低的摄像头到 CPU 到 GPU 的方法【英文标题】:Lowest overhead camera to CPU to GPU approach on android 【发布时间】:2016-09-27 09:18:12 【问题描述】:

我的应用程序需要先在 CPU 上对实时摄像机帧进行一些处理,然后再在 GPU 上渲染它们。 GPU 上还渲染了一些其他的东西,这取决于 CPU 处理的结果;因此,保持一切同步很重要,这样在 CPU 处理该帧的结果也可用之前,我们不会在 GPU 上渲染帧本身。

问题是在 android 上最低开销的方法是什么?

在我的例子中,CPU 处理只需要一张灰度图像,因此 Y 平面被打包的 YUV 格式是理想的(并且往往也与相机设备的本机格式很好地匹配)。 NV12、NV21 或全平面 YUV 都可以提供理想的低开销灰度访问,因此在 CPU 方面是首选。

在原始相机 API 中,setPreviewCallbackWithBuffer() 是将数据放到 CPU 上进行处理的唯一合理方法。这使 Y 平面分开,因此非常适合 CPU 处理。让 OpenGL 可以使用此帧以低开销的方式进行渲染是更具挑战性的方面。最后,我编写了一个 NEON 颜色转换例程来输出 RGB565,并使用 glTexSubImage2d 将其在 GPU 上可用。这首先是在 Nexus 1 时间范围内实现的,即使是 320x240 glTexSubImage2d 调用也需要 50 毫秒的 CPU 时间(我认为尝试进行纹理调配的驱动程序很差 - 这在后来的系统更新中得到了显着改善)。

过去,我研究了 eglImage 扩展之类的东西,但对于用户应用来说,它们似乎不可用或没有足够的文档记录。我稍微了解了内部 android GraphicsBuffer 类,但理想情况下希望留在受支持的公共 API 的世界中。

android.hardware.camera2 API 承诺能够将 ImageReader 和 SurfaceTexture 附加到捕获会话。不幸的是,我在这里看不到任何确保正确顺序管道的方法 - 在 CPU 处理完之前推迟调用 updateTexImage() 很容易,但是如果在该处理期间有另一帧到达,那么 updateTexImage() 将直接跳到最新的框架。似乎对于多个输出,每个队列中都会有独立的帧副本,理想情况下我希望避免。

理想情况下,这就是我想要的:

    相机驱动程序用最新帧填充一些内存 CPU 获得指向内存中数据的指针,无需复制即可读取 Y 数据 当帧准备好时,CPU 处理数据并在我的代码中设置一个标志 开始渲染帧时,检查新帧是否准备就绪 调用一些 API 来绑定与 GL 纹理相同的内存 当一个较新的帧准备就绪时,将保存前一帧的缓冲区释放回池中

我看不到在 android 上使用公共 API 完全实现零复制样式的方法,但最接近的方法是什么?

我尝试过的一件疯狂的事情似乎可行,但没有记录:ANativeWindow NDK API 可以接受数据 NV12 格式,即使适当的格式常量不是公共标头中的格式常量之一。这允许通过 memcpy() 用 NV12 数据填充 SurfaceTexture,以避免 CPU 端颜色转换和 glTexImage2d 中驱动程序端发生的任何混乱。这仍然是数据的额外副本,尽管感觉它应该是不必要的,并且再次因为它没有记录可能不适用于所有设备。支持的顺序零拷贝相机 -> ImageReader -> SurfaceTexture 或等效的将是完美的。

【问题讨论】:

【参考方案1】:

处理视频的最有效方法是完全避免使用 CPU,但听起来这不是您的选择。公共 API 通常适用于在硬件中完成所有工作,因为这是框架本身所需要的,尽管 RenderScript 有一些路径。 (我假设您已经看到使用片段着色器的Grafika filter demo。)

访问 CPU 上的数据过去意味着缓慢的相机 API 或使用 GraphicBuffer 和相对晦涩的 EGL 函数(例如this question)。 ImageReader 的目的是提供对来自相机的 YUV 数据的零拷贝访问。

您不能真正序列化 Camera -> ImageReader -> SurfaceTexture,因为 ImageReader 没有“转发缓冲区”API。这是不幸的,因为这会使这变得微不足道。您可以尝试复制 SurfaceTexture 所做的事情,使用 EGL 函数将缓冲区打包为外部纹理,但您再次进入非公共 GraphicBuffer-land,我担心缓冲区的所有权/生命周期问题。

我不确定并行路径如何帮助您(Camera2 -> ImageReader、Camera2 -> SurfaceTexture),因为发送到 SurfaceTexture 的内容不会有您的修改。 FWIW,它不涉及额外的副本——在 Lollipop 或其附近,更新了 BufferQueue 以允许单个缓冲区在多个队列中移动。

完全有可能我还没有看到一些花哨的新 API,但据我所知,您的 ANativeWindow 方法可能是赢家。我怀疑您最好使用其中一种相机格式(YV12 或 NV21)而不是 NV12,但我不确定。

FWIW,如果您的处理时间过长,您将丢帧,但除非您的处理不均匀(某些帧比其他帧花费的时间长得多),否则无论如何您都必须丢帧。再次进入非公共 API 领域,您可以将 SurfaceTexture 切换为“同步”模式,但如果缓冲区已满,您仍然会丢帧。

【讨论】:

很好的答案,谢谢。 CPU 端实际上根本没有写入缓冲区,它基本上是一个 AR 应用程序,需要在 CPU 上计算帧中的对象运动,以正确渲染覆盖在 GPU 上正确位置的内容。所以并行路径会很好,但我认为缺少的部分是在 SurfaceTexture 中获得正确的缓冲区; “最新的 CPU 已处理”而不是“队列中的最新”。 我昨天看了一点 RenderScript 文档。我怀疑它会起作用,但如果有可能有一个分配是 USAGE_IO_INPUT | USAGE_IO_OUTPUT | USAGE_SCRIPT 那么它可能允许它既是相机的目的地又是 SurfaceTexture 的源,CPU 控制缓冲区在管道中移动的时间。 RenderScript NDK 标头在分配上有一个 getPointer() 函数,因此应该能够在 C++ NDK 代码中处理(不幸的是,标头不在最新的 NDK 版本中,所以不确定问题所在)。 我对您的并行管道没有很好的印象,但您似乎想同时调用 updateTexImage()acquireLatestImage(),然后坐在CPU 工作时锁存的缓冲区。这不是没有比赛的,因为新的相机框架可能会在两次通话之间降落,但这应该很少见,可以通过密切关注时钟来缓解。 USAGE_IO_INPUT 和 USAGE_IO_OUTPUT 不能在单个 RenderScript 分配中一起使用。我认为您必须在这里使用两个分配:Camera2 -> Allocation ->(处理)-> Allocation -> SurfaceTexture。你有没有尝试过类似github.com/googlesamples/android-HdrViewfinder 这里有一个后续问题,概述和方法可能有效:***.com/questions/37592934/…

以上是关于Android 上最低的摄像头到 CPU 到 GPU 的方法的主要内容,如果未能解决你的问题,请参考以下文章

如何将 SD 卡上的 3gp 文件上传到服务器?

Android 巧影-v4.3.0.10337.GP 破解版

Android快速实现动态模糊效果

Android音视频预览摄像头画面

Android音视频预览摄像头画面

来自 Android 设备摄像头的视频流