了解Android相机SurfaceTexture和MediaCodec Surface的使用方法

8
我正在尝试理解Android中与使用MediaCodec从相机编码帧有关的图形内存使用/流,并需要了解一些我不清楚的图形、OpenGL和Android术语/概念。为此,我已阅读了Android图形架构材料、大量SO问题和大量源代码,但仍感到困惑,主要是因为似乎在不同的上下文中术语具有不同的含义。
我查看了fadden网站上的CameraToMpegTest(链接)。我的具体问题是MediaCodec :: createInputSurface()如何与Camera :: setPreviewTexture()配合使用。似乎会创建一个OpenGL纹理,然后使用它来创建一个Android SurfaceTexture ,然后可以将其传递给 setPreviewTexture()。我的具体问题:
  1. 调用setPreviewTexture()实际上是针对相机帧所在的哪个内存缓冲区进行的?
  2. 据我所知,OpenGL纹理是GPU可访问的一块内存。在Android上,这必须使用正确的使用标志使用gralloc进行分配。 Android对SurfaceTexture的描述提到它允许您“将图像流式传输到给定的OpenGL纹理”:(链接)SurfaceTexture在OpenGL纹理之上有什么作用?
  3. MediaCodec :: createInputSurface()返回一个Android Surface 。据我所知,Android Surface 表示缓冲区队列的生产者端,因此可能是多个缓冲区。 API参考 提到“表面必须使用硬件加速的API(例如OpenGL ES)进行渲染”。相机捕获的帧如何从SurfaceTexture到输入到编码器的 Surface ?我看到CameraToMpegTest以某种方式使用这个 Surface 创建了一个 EGLSurface ,但由于不了解EGL,我不明白这一部分。
  4. 有人可以澄清“render”的用法吗?我看到诸如“渲染到表面”,“渲染到屏幕”等用法,似乎可能意味着不同的事情。

编辑:对mstorsjo的回复进行后续处理:

  1. 我深入研究了Camera::setPreviewTexture()SurfaceTextureCameraService中的CameraClient::setPreviewTarget()代码,试图更好地理解其内部工作,并产生了一些问题。对于我的最初问题,即理解内存分配,似乎是SurfaceTexture创建了一个BufferQueue,而CameraService将相关的IGraphicBufferProducer传递给平台相机HAL实现。相机HAL随后可以适当地设置gralloc使用标志(例如,GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_NEVER | GRALLOC_USAGE_HW_TEXTURE),并从此BufferQueue出队缓冲区。因此,相机捕获帧的缓冲区是带有某些特殊使用标志(如GRALLOC_USAGE_HW_TEXTURE)的gralloc分配缓冲区。我在ARM平台上工作,其具有统一的内存体系结构,因此GPU和CPU可以访问相同的内存,那么GRALLOC_USAGE_HW_TEXTURE标志会对缓冲区分配产生什么影响?
  2. SurfaceTexture的OpenGL(ES)部分似乎主要是作为GLConsumer的一部分实现的,其中的魔法似乎在updateTexImage()中。是否会为OpenGL(ES)纹理分配额外的缓冲区,还是可以使用由相机填充的相同gralloc缓冲区?这里是否需要进行一些内存复制以将相机像素数据从gralloc缓冲区复制到OpenGL(ES)纹理中?我猜想我不理解调用updateTexImage()的作用。
1个回答

5
  1. 这意味着相机通过不透明的句柄提供输出帧,而不是在应用程序地址空间内的用户提供缓冲区中(如果使用setPreviewCallbacksetPreviewCallbackWithBuffer)。这个不透明的句柄,即纹理,可以在OpenGL绘图中使用。
  2. 差不多。在这种情况下,OpenGL纹理不是物理内存块,而是EGL上下文内可变内存块的句柄。在这种情况下,示例代码本身实际上并没有分配或调整纹理的大小,它只是使用glGenTextures创建了一个纹理的“名称”/句柄 - 它基本上只是一个整数。在正常的OpenGL(ES)中,您将使用OpenGL函数为纹理分配实际存储,并用内容填充它。在这种设置中,SurfaceTexture提供了一个Android级别的API/抽象来填充纹理数据(即使用正确的标志为其分配存储,为其提供大小和内容) - 允许您将SurfaceTexture传递给其他类,这些类可以将其填充数据(直接使用Camera获取SurfaceTexture,或者将其包装在Surface类中以便在其他上下文中使用)。这样可以高效地填充OpenGL纹理内容,而不必将原始数据的缓冲区传递给应用程序进程并使其上传到OpenGL。
  3. (反向回答第3和第4点。)OpenGL(ES)是一个通用的绘图API。在正常/原始设置中,考虑一个游戏,您将拥有多个纹理,用于游戏内容的不同部分(背景、道具、演员等),然后使用OpenGL API将其绘制到屏幕上。纹理可以更或少地直接复制到屏幕上,也可以包裹在由三角形构成的3D对象周围。这个过程叫做“渲染”,将输入纹理和一组三角形进行绘制。在最简单的情况下,您会直接将内容呈现到屏幕上。GPU通常也可以将相同的呈现渲染到任何其他输出缓冲区中。在游戏中,通常会将某些场景渲染成纹理,并将其作为最终渲染的一部分,最终显示在屏幕上。
  4. 创建EGL上下文以将相机输出传递到编码器输入。 EGL上下文基本上是用于执行OpenGL渲染的上下文。渲染的目标是来自编码器的表面。也就是说,使用OpenGL绘制的任何图形都会进入编码器输入缓冲区,而不是显示在屏幕上。现在,使用OpenGL绘制的场景可以是任何一系列OpenGL函数调用,将游戏场景渲染到编码器中。(这就是Android Breakout游戏记录器示例所做的。)在上下文中,创建一个纹理句柄。与正常的游戏图形渲染从磁盘加载图片并填充纹理不同,这被制作成一个SurfaceTexture,以允许Camera用相机图片填充它。当接收到此回调时,激活EGL上下文并将一个帧呈现到EGL上下文输出目标(即编码器
    这些听起来可能有些绕,但确实带来了一些好处:
    • 在应用程序代码中,实际的相机帧数据不需要直接处理(甚至在应用程序的进程和地址空间中也可能没有处理)。对于低分辨率,这并不是什么问题,但当涉及更高分辨率时,setPreviewCallback API成为瓶颈。
    • 您可以通过GPU加速几乎免费地进行颜色调整和其他任何OpenGL操作。

根据您的回答,我在我的原始问题中添加了一些后续问题。 - Mike Sweeney
我对那些额外的问题不能发表太多评论 - 原始问题是关于这些API从公共Android API角度做什么(我知道),而后来的问题则是关于平台如何实现以及仅与平台实现者相关的细节。 - mstorsjo
1
对于最后一个问题,我期望SurfaceTexture实际上是一组缓冲区,不止一个,这样生产者(相机)可以写入一帧,而消费者(GL上下文)可以继续渲染上一帧。在updateTexImage()中,我期望它交换这些缓冲区。但这只是我从用户角度的印象和假设,我不知道实际实现情况。 - mstorsjo

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