【问题标题】:Understanding Android camera SurfaceTexture and MediaCodec Surface usage了解Android相机SurfaceTexture和MediaCodec Surface的使用
【发布时间】:2017-08-03 01:08:22
【问题描述】:

我正在尝试了解 Android 中的图形内存使用/流程,特别是关于使用 MediaCodec 从相机编码帧的情况。为了做到这一点,我必须了解一堆我不清楚的图形、OpenGL 和 Android 术语/概念。我已经阅读了 Android 图形架构材料、一堆 SO 问题和一堆来源,但我仍然感到困惑,主要是因为这些术语似乎在不同的上下文中具有不同的含义。

我从fadden 的网站here 上查看了CameraToMpegTest。我的具体问题是MediaCodec::createInputSurface() 如何与Camera::setPreviewTexture() 一起工作。似乎创建了 OpenGL 纹理,然后将其用于创建 Android SurfaceTexture,然后可以将其传递给 setPreviewTexture()。我的具体问题:

  1. 调用setPreviewTexture() 对帧从相机进入的内存缓冲区有何实际作用?
  2. 据我了解,OpenGL 纹理是一块可由 GPU 访问的内存。在 Android 上,这必须使用带有正确使用标志的 gralloc 进行分配。 SurfaceTexture 的 Android 描述提到它允许您“将图像流式传输到给定的 OpenGL 纹理”:https://developer.android.com/reference/android/graphics/SurfaceTexture.html#SurfaceTexture(int)SurfaceTexture 在 OpenGL 纹理之上做了什么?
  3. MediaCodec::createInputSurface() 返回一个 Android Surface。据我了解,Android Surface 代表缓冲区队列的生产者端,因此它可能是多个缓冲区。 API reference 提到“必须使用硬件加速 API 渲染 Surface,例如 OpenGL ES”。相机捕获的帧如何从SurfaceTexture 到输入到编码器的Surface?我看到 CameraToMpegTest 以某种方式使用此 Surface 创建了一个 EGLSurface,但对 EGL 了解不多,我不明白这部分。
  4. 有人可以澄清“渲染”的用法吗?我看到诸如“渲染到表面”、“渲染到屏幕”之类的其他用法似乎可能意味着不同的东西。

编辑:对 mstorsjo 回复的跟进:

  1. 我深入研究了CameraServiceSurfaceTextureCameraClient::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 标志会对缓冲区的分配方式产生什么样的影响?
  2. SurfaceTexture 的 OpenGL (ES) 部分似乎主要作为GLConsumer 的一部分实现,而魔法似乎在updateTexImage() 中。是否为 OpenGL (ES) 纹理分配了额外的缓冲区,或者是否可以使用由相机填充的同一个 gralloc 缓冲区?此处是否需要进行一些内存复制才能将相机像素数据从 gralloc 缓冲区获取到 OpenGL (ES) 纹理中?我想我不明白调用updateTexImage() 的作用。

【问题讨论】:

    标签: android opengl-es android-camera android-mediacodec egl


    【解决方案1】:
    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 渲染的上下文。渲染的目标是来自编码器的 Surface。也就是说,使用 OpenGL 绘制的任何图形最终都会在编码器输入缓冲区中而不是在屏幕上。现在,使用 OpenGL 绘制的场景可以是任何 OpenGL 函数调用序列,将游戏场景渲染到编码器中。 (这就是 Android Breakout 游戏记录器示例所做的。)在上下文中,创建了一个纹理句柄。不像在正常的游戏图形渲染中那样通过从磁盘加载图片来填充纹理,而是将其制成SurfaceTexture,以允许Camera 用相机图片填充它。 SurfaceTexture 类提供回调,当Camera 更新内容时发出信号。当接收到此回调时,将激活 EGL 上下文并将一帧渲染到 EGL 上下文输出目标(即编码器输入)中。渲染本身并没有做任何花哨的事情,而是将输入纹理直接复制到输出中。

    这听起来可能有点迂回曲折,但确实有一些好处:

    • 相机帧的实际原始位永远不需要在应用程序代码中直接处理(并且可能根本不需要在应用程序的进程和地址空间中)。对于低分辨率,这不是什么大问题,但setPreviewCallback API 是高分辨率时的瓶颈。
    • 您可以在 OpenGL 中进行颜色调整和任何其他可以在 GPU 加速中进行的操作,几乎免费。

    【讨论】:

    • 根据您的回答,我在原始问题中添加了一些后续内容。
    • 我不能对这些额外的问题发表太多评论 - 最初的问题是从公共 Android API 的角度来看这些 API 做了什么(我知道),而后面的问题是关于如何平台已实现,详细信息仅与平台实现者相关。
    • 对于最后一个问题,我希望 SurfaceTexture 实际上是一组缓冲区,不止一个,因此生产者(相机)可以在消费者(GL上下文)写入一帧可以继续渲染上一帧。在updateTexImage() 中,我希望它交换这些缓冲区。这只是我从用户角度的印象和假设,我不知道实际的实现。
    猜你喜欢
    • 2015-12-03
    • 2015-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-13
    • 1970-01-01
    • 1970-01-01
    • 2017-09-16
    相关资源
    最近更新 更多