android - 了解Android相机SurfaceTexture和MediaCodec Surface的使用

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

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

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

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

  • 编辑:跟进 mstorsjo 的回复:
  • 我挖掘了 SurfaceTexture 的代码和 CameraClient::setPreviewTarget()CameraService更多尝试了解 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()做。
  • 最佳答案

  • 这意味着相机通过不透明的句柄而不是在应用程序地址空间内的用户提供的缓冲区中提供输出帧(如果使用 setPreviewCallbacksetPreviewCallbackWithBuffer )。这个不透明的句柄,即纹理,可以在 OpenGL 绘图中使用。
  • 几乎。在这种情况下,OpenGL 纹理不是物理内存块,而是 EGL 上下文中可变内存块的句柄。在这种情况下,示例代码本身并不实际分配纹理或调整纹理大小,它只是使用 glGenTextures 为纹理创建一个“名称”/句柄。 - 它基本上只是一个整数。在普通的 OpenGL (ES) 中,您将使用 OpenGL 函数来为纹理分配实际存储空间并用内容填充它。在此设置中,SurfaceTexture提供 Android 级别的 API/抽象来使用数据填充纹理(即,使用正确的标志为其分配存储空间,为其提供大小和内容) - 允许您传递 SurfaceTexture到可以用数据填充它的其他类(Camera 直接采用 SurfaceTexture,或者包装在 Surface 类中以便能够在其他上下文中使用它)。这允许使用内容有效地填充 OpenGL 纹理,而无需将原始数据缓冲区传递到应用程序的进程并让您的应用程序将其上传到 OpenGL。
  • (以相反的顺序回答第 3 点和第 4 点。) OpenGL (ES) 是用于绘图的通用 API。在正常/原始设置中,考虑一个游戏,游戏内容的不同部分(背景、 Prop 、 Actor 等)有许多纹理,然后使用 OpenGL API 将其绘制到屏幕上。纹理可以或多或少地直接复制到屏幕上,或者包裹在由三角形构建的 3D 对象周围。这是称为“渲染”的过程,获取输入纹理和一组三角形并绘制它。在最简单的情况下,您会将内容直接呈现到屏幕上。 GPU 通常也可以对任何其他输出缓冲区进行相同的渲染。在游戏中,通常会将某些场景渲染为纹理,并使用该预渲染纹理作为最终渲染的一部分,最终渲染实际上最终显示在屏幕上。
  • 创建一个 EGL 上下文,用于将相机的输出传递到编码器输入。 EGL 上下文基本上是进行 OpenGL 渲染的上下文。渲染的目标是来自编码器的 Surface。也就是说,使用 OpenGL 绘制的任何图形最终都会在编码器输入缓冲区中而不是在屏幕上。现在,使用 OpenGL 绘制的场景可以是任何 OpenGL 函数调用序列,将游戏场景渲染到编码器中。 (这就是 Android Breakout 游戏记录器示例所做的。)在上下文中,创建了一个纹理句柄。不像在正常的游戏图形渲染中那样通过从磁盘加载图片来填充纹理,而是将其制成SurfaceTexture。 , 以允许 Camera用相机图片填充它。 SurfaceTexture类提供回调,当 Camera 时发出信号已更新内容。当接收到此回调时,将激活 EGL 上下文并将一帧渲染到 EGL 上下文输出目标(即编码器输入)中。渲染本身并没有做任何花哨的事情,而是将输入纹理直接复制到输出中。

  • 这听起来可能很迂回,但它确实带来了一些好处:
  • 相机帧的实际原始位永远不需要直接在应用程序代码中处理(并且可能根本不需要在应用程序的进程和地址空间中)。对于低分辨率,这不是什么大问题,但 setPreviewCallback当涉及到更高分辨率时,API 是一个瓶颈。
  • 您可以在 OpenGL 中进行颜色调整和任何其他可以做的事情,几乎可以免费使用 GPU 加速。
  • 关于android - 了解Android相机SurfaceTexture和MediaCodec Surface的使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42756489/

    相关文章:

    android - 多个异常 : Unable to start activity, Unable to instantiate fragment, Error inflating class

    java - 取消Timer类对象后如何重新创建它?

    java - 在 Android/Java 上安全使用 glMapBufferRange()

    android - intent.putExtra 的所有属性以在 android 中裁剪图像

    android - 如何实现像whatsapp blue tick mark这样的消息阅读状态?

    opengl-es - 如何模拟 GL_TEXTURE_EXTERNAL_OES 纹理?

    android - 如何检测触摸了哪个 3d 对象?

    android - Android错误 Intent 从库中选择图像取决于路径

    android - 无法解析 android.hardware.camera2 的导入

    java - 我应该在 Guava 中将字符串键设置为软键吗?