首页 文章

了解Android相机SurfaceTexture和MediaCodec Surface使用情况

提问于
浏览
5

我正在尝试了解Android中的图形内存使用/流量,特别是使用 MediaCodec 来编码来自摄像头的帧 . 为了做到这一点,我已经阅读了Android图形架构材料,一堆SO问题和一堆来源,但我仍然感到困惑,主要是因为它似乎在不同的上下文中有不同的含义 .

我've looked at CameraToMpegTest from fadden'的网站here . 我的具体问题是 MediaCodec::createInputSurface() 如何与 Camera::setPreviewTexture() 一起使用 . 似乎创建了一个OpenGL纹理,然后用它来创建一个Android SurfaceTexture ,然后可以将其传递给 setPreviewTexture() . 我的具体问题:

  • 调用 setPreviewTexture() 实际上在帧从相机到什么内存缓冲区方面做了什么?

  • 据我所知,OpenGL纹理是GPU可以访问的一块内存 . 在Android上,必须使用带有正确用法标志的gralloc进行分配 . SurfaceTexture 的Android描述提到它允许你"stream images to a given OpenGL texture":https://developer.android.com/reference/android/graphics/SurfaceTexture.html#SurfaceTexture(int) . SurfaceTexture 在OpenGL纹理之上做了什么?

  • MediaCodec::createInputSurface() 返回Android Surface . 据我所知,Android Surface 表示缓冲队列的 生产环境 者端,因此它可能是多个缓冲区 . API reference提到"the Surface must be rendered with a hardware-accelerated API, such as OpenGL ES" . 摄像机捕获的帧如何从 SurfaceTexture 到输入到编码器的 Surface ?我看到CameraToMpegTest使用这个 Surface 以某种方式创建一个 EGLSurface ,但对EGL不太了解我没有得到这个部分 .

  • 有人可以澄清"render"的用法吗?我看到诸如"render to a surface","render to the screen"之类的东西,其他用法似乎可能意味着不同的东西 .

编辑:mstorsjo的回复的后续行动:

  • 我在 CameraService 中挖掘了 SurfaceTextureCameraClient::setPreviewTarget() 的代码,以便更好地了解 Camera::setPreviewTexture() 的内部工作原理并提出更多问题 . 至于我理解内存分配的原始问题,似乎 SurfaceTexture 创建了一个 BufferQueueCameraService 将相关的 IGraphicBufferProducer 传递给平台相机HAL实现 . 然后,摄像机HAL可以适当地设置gralloc使用标志(例如 GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_NEVER | GRALLOC_USAGE_HW_TEXTURE ),并将缓冲区从此 BufferQueue 出列 . 因此,摄像机捕获帧的缓冲区是gralloc分配的缓冲区,带有一些特殊用法标志,如 GRALLOC_USAGE_HW_TEXTURE . 我在具有统一内存架构的ARM平台上工作,因此GPU和CPU可以访问相同的内存,因此 GRALLOC_USAGE_HW_TEXTURE 标志会对缓冲区的分配方式产生什么样的影响?

  • SurfaceTexture 的OpenGL(ES)部分似乎主要是作为 GLConsumer 的一部分实现的,魔术似乎在 updateTexImage() 中 . 是否为OpenGL(ES)纹理分配了额外的缓冲区,或者是否可以使用相机填充的相同gralloc缓冲区?是否需要进行一些内存复制才能将相机像素数据从gralloc缓冲区转换为OpenGL(ES)纹理?我想我不明白是什么叫 updateTexImage() .

1 回答

  • 4
    • 这意味着摄像机通过不透明的句柄而不是应用程序地址空间内的用户提供的缓冲区(如果使用 setPreviewCallbacksetPreviewCallbackWithBuffer )提供输出帧 . 这个不透明的手柄,纹理,可以在OpenGL绘图中使用 .

    • 几乎 . 在这种情况下,OpenGL纹理不是内存的物理块,而是EGL上下文中可变内存块的句柄 . 在这种情况下,示例代码本身并不实际分配或调整纹理大小,它只使用 glGenTextures 为纹理创建一个"name" /句柄 - 它使用OpenGL函数为纹理分配实际存储并用内容填充它 . 在此设置中, SurfaceTexture 提供Android级API /抽象以使用数据填充纹理(即使用正确的标志为其分配存储,为其提供大小和内容) - 允许您将 SurfaceTexture 传递给可填充的其他类它与数据( Camera 直接接受 SurfaceTexture ,或包装在 Surface 类中以便能够在其他上下文中使用它) . 这允许有效地填充OpenGL纹理内容,而无需将原始数据缓冲区传递到应用程序的进程,并让您的应用程序将其上传到OpenGL .

    • (以相反的顺序回答第3点和第4点 . )OpenGL(ES)是绘图的通用API . 在普通/原始设置中,考虑一个游戏,你有许多纹理用于游戏内容的不同部分(背景,道具,演员等),然后用OpenGL API将其绘制到屏幕上 . 纹理可以或多或少地只是复制到屏幕上,或者包裹在由三角形构建的3D对象周围 . 这是一个称为“渲染”的过程,它采用输入纹理和三角形集并绘制它 . 在最简单的情况下,您可以直接将内容呈现到屏幕上 . GPU通常也可以对任何其他输出缓冲区执行相同的渲染 . 在游戏中,通常将一些场景渲染到纹理中,并将该预渲染的纹理用作最终渲染的一部分,最终渲染实际上最终显示在屏幕上 .

    • 创建EGL上下文,用于将摄像机的输出传递到编码器输入 . EGL上下文基本上是用于执行OpenGL渲染的上下文 . 渲染的目标是编码器的Surface . 也就是说,使用OpenGL绘制的任何图形都会在编码器输入缓冲区中而不是在屏幕上 . 现在,使用OpenGL绘制的场景可以是任何OpenGL函数调用序列,将游戏场景渲染到编码器中 . (这是Android Breakout游戏 Logger 示例所做的 . )在上下文中,创建纹理句柄 . 而不是通过从磁盘加载图片来填充纹理,如在正常的游戏图形渲染中,这被制成 SurfaceTexture ,以允许 Camera 用相机图片填充它 . SurfaceTexture 类提供回调,在 Camera 更新内容时发出信号 . 当收到此回调时,将激活EGL上下文,并将一帧渲染到EGL上下文输出目标(即编码器输入)中 . 渲染本身并没有做任何花哨的事情,但更多或者将输入纹理直接复制到输出中 .

    这可能听起来很迂回,但它确实带来了一些好处:

    • 相机帧的实际原始位不需要直接在应用程序代码中处理(并且可能永远不会在应用程序中出现问题,但是 setPreviewCallback API在更高分辨率时是一个瓶颈 .

    • 您可以在OpenGL中进行颜色调整和其他任何操作,几乎可以通过GPU加速免费进行 .

相关问题