如何直接从Android Surface访问EGL图像以供MediaCodec视频解码器使用?

10

我目前正在编写一款安卓应用程序,需要缓存视频帧以便能够在很短的时间内来回切换。

现在,我让安卓通过向 MediaCodec 对象的 Configure 调用提供一个 Surface,并将 render 标志设置为 true ,从而解码视频帧,然后调用 releaseOutputBuffer

我发现除了对返回的字节缓冲区进行解码(其格式似乎是与设备相关的),我还可以通过将其链接到 SurfaceTexture 并将其附加到 GL_TEXTURE_EXTERNAL_OES 目标上,再将其渲染到我自己创建的 GL_TEXTURE2D 目标纹理中,从而访问解码表面数据。

我想优化这个缓存过程并能够在不同的线程上解码帧。使用当前的方法,这意味着我必须为视频解码器创建另一个 EGL 上下文,共享上下文等。

我的问题是:是否可以在不调用 updateTexImage 的情况下访问与 Surface 关联的 EGL 图像或本机缓冲区数据

这样我就可以缓存 EGL 图像(根据 EGL_ANDROID_image_native_buffer,这不需要 EGL 上下文)。这也会以 YUV 格式进行缓存,比我现在正在缓存的原始 RGB 纹理更省存储空间。

1个回答

6

简短回答:不行。

更长的回答: Surface 封装了一个缓冲区队列。(编辑:系统现在有更详细的解释在这里。)当您调用updateTexImage()时,如果有新的数据帧可用,则会删除头部的缓冲区,并将队列中的下一个缓冲区变为当前缓冲区。调用updateTexImage()是查看连续帧的必要条件;没有机制可以检查不在头部的缓冲区。

一个SurfaceTexture封装了GLConsumer的一个实例。这个消费者要求生产者(视频解码器)生成可以用作“硬件纹理”的格式数据,即设备的GL实现可以理解的东西。它可能是YUV,也可能不是。更重要的是,消费者并不要求缓冲区对“软件”可用,这意味着您不能假设可以直接访问数据 - 您需要使用GLES。(请参阅gralloc header以获取完整的标志列表。)
在这里很好的方式是能够将BufferQueue头部的缓冲区复制到一个单独的数据结构(BufferArrayList?)中而不进行格式转换,但目前(Android 4.3)没有这样的机制。我不知道比您描述的更好的方法(共享EGL上下文等)。

更新:我的办公室同事建议:使用着色器将缓冲区渲染成两个纹理,一个用于Y,另一个用于CbCr(在GLES 3中,您可以使用RG纹理)。这样可以将所有操作保留在GLES中,而不是扩展到完整的RGB。内部它会将MediaCodec输出转换为RGB,并通过它两次进行磨损,但比在用户空间中将其复制出来并自己在CPU上处理要便宜得多。


首先,非常感谢您提供的详细答案。我进行了更多的实验并尝试直接访问缓冲区。我发现我的设备上视频输出格式为0x7fa30c03,这不是AOSP中的标准格式。经过一些手动重排,我能够为每个解码帧存储一个YUV420缓存帧。也许有一个与设备无关的函数可以将字节缓冲区转换为常规yuv?我尝试了YuvImage类,但看起来这个类没有实现0x7fa30c03格式。再次感谢您的回复。 - Nico Cornelis
你现在处于非可移植设备特定领域。0x7fa30c03是专有的OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka(在native/include/media/openmax/OMX_IVCommon.h中声明); 最接近解码器库的是供应商特定的/system/lib/libI420colorconvert.so,由视频编辑器库(https://android.googlesource.com/platform/frameworks/av/+/jb-mr2-release/libvideoeditor/lvpp/I420ColorConverter.cpp)使用。框架中的大多数YUV内容都用于相机,当涉及到缓冲区格式时,相机比视频解码器更好行为。 - fadden
再次感谢您的及时回复。我想现在我会继续使用GLES路径。我再次查看了AOSP代码,发现EGL图像是使用EGL_ANDROID_image_native_buffer扩展创建的。我对本地缓冲区不是很熟悉。也许可以缓存mediacodec返回的bytebuffer,并将其包装在一个结构体中,以便在需要从缓存中检索它时作为EGLClientBuffer传递给eglCreateImage? - Nico Cornelis
1
媒体/图形驱动程序的一个怪癖是它们不保证对称性:仅仅因为它将OMX 0x7fa30c03复制到你的ByteBuffer中,并不意味着存在匹配的gralloc缓冲区格式。也许它进行了直接的memcpy,也许它调整了对齐或步幅。填充缓冲区可能在某个设备和软件版本上工作,但最多也只能算是脆弱的。这可能会引起您的兴趣:http://snorp.net/2011/12/16/android-direct-texture.html(同样,不使用公共API)。如果您想深入研究,可以尝试替换`GLConsumer`。 - fadden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接