在 Android 中显示自定义视频的有效路径
Efficient path for displaying customized video in Android
在 Android 中,我需要一种在屏幕上显示之前修改相机流的有效方法。 This post 讨论了几种这样做的方法,我能够实现第一个:
- 从 onPreviewFrame 获取帧缓冲区
- 将帧转换为 YUV
- 修改框架
- 将修改后的帧转换为 jpeg
- 显示框架到放置在SurfaceView上的ImageView用于
预览
这很有效,但将我通过常规相机预览获得的 30 fps 降低到了 5 fps 左右。从不同的图像空间来回转换帧也很耗电,这是我不想要的。
是否有关于如何直接访问原始帧而不必经过这么多转换的示例?使用 OpenGL 是执行此操作的正确方法吗?这一定是很常见的事情,但我找不到好的例子。
注意:为了向后兼容,我宁愿避免使用 Camera2 API。
基于 CPU 的管道的最有效形式如下所示:
- 从 Surface 上的相机接收帧,而不是
byte[]
。使用 Camera2,您可以将帧直接发送到 ImageReader;这将使您 CPU 无需复制或转换就可以访问来自相机的原始 YUV 数据。 (我不确定如何使用旧相机 API 来装配它,因为它需要 SurfaceTexture 或 SurfaceHolder,而 ImageReader 不提供这些。您可以通过 SurfaceTexture 运行 帧并从 glReadPixels()
获取 RGB 值,但我不知道这是否会给你带来任何好处。)
- 对 YUV 数据进行修改。
- 将 YUV 数据转换为 RGB。
- 将 RGB 数据转换为位图或 GLES 纹理。 glTexImage2D will be more efficient, but OpenGL ES comes with a steep learning curve. Most of the pieces you need are in Grafika (e.g. the texture upload benchmark) 如果你决定走那条路。
- 渲染图像。根据您在第 4 步中所做的操作,您将通过 custom View 上的 Canvas 渲染位图,或者在 SurfaceView 或 TextureView 上使用 GLES 渲染纹理。
我认为最显着的加速将来自消除 JPEG 压缩和解压缩,因此您应该从这里开始。将帧编辑器的输出转换为位图,然后将其绘制在 TextureView 或自定义视图的 Canvas 上,而不是转换为 JPEG 并使用 ImageView。如果这不能让您获得想要的加速,找出是什么让您变慢并在管道的那一部分上工作。
如果您仅限于使用旧相机 API,那么使用 SurfaceTexture 并在 GPU 着色器中进行处理可能是最有效的。
假设您想要进行的任何修改都可以合理地表达为 GL 片段着色器,并且您对 OpenGL 足够熟悉,可以设置将单个四边形渲染到帧缓冲区所需的所有样板,使用来自 SurfaceTexture 的纹理。
然后您可以使用 glReadPixels 从最终渲染输出中读回结果,并将其保存为 JPEG。
请注意,着色器将为您提供 RGB 数据,而不是 YUV,因此如果您确实需要 YUV,则必须在处理之前转换回 YUV 色彩空间。
如果您可以使用 camera2,如 fadden 所说,ImageReader 或 Allocation(分别用于 Java/JNI-based 处理或 Renderscript)也成为选项。
如果您只是使用 JPEG 获取要放置在 ImageView 上的位图,而不是因为您想保存它,那么再次如 fadden 所说,您可以跳过 encode/decode 步骤并直接绘制到视图。例如,如果使用 Camera->SurfaceTexture->GL 路径,您可以只使用 GLSurfaceView 作为输出目标并直接渲染到 GLSurfaceView,如果这就是您需要对数据执行的全部操作。
在 Android 中,我需要一种在屏幕上显示之前修改相机流的有效方法。 This post 讨论了几种这样做的方法,我能够实现第一个:
- 从 onPreviewFrame 获取帧缓冲区
- 将帧转换为 YUV
- 修改框架
- 将修改后的帧转换为 jpeg
- 显示框架到放置在SurfaceView上的ImageView用于 预览
这很有效,但将我通过常规相机预览获得的 30 fps 降低到了 5 fps 左右。从不同的图像空间来回转换帧也很耗电,这是我不想要的。
是否有关于如何直接访问原始帧而不必经过这么多转换的示例?使用 OpenGL 是执行此操作的正确方法吗?这一定是很常见的事情,但我找不到好的例子。
注意:为了向后兼容,我宁愿避免使用 Camera2 API。
基于 CPU 的管道的最有效形式如下所示:
- 从 Surface 上的相机接收帧,而不是
byte[]
。使用 Camera2,您可以将帧直接发送到 ImageReader;这将使您 CPU 无需复制或转换就可以访问来自相机的原始 YUV 数据。 (我不确定如何使用旧相机 API 来装配它,因为它需要 SurfaceTexture 或 SurfaceHolder,而 ImageReader 不提供这些。您可以通过 SurfaceTexture 运行 帧并从glReadPixels()
获取 RGB 值,但我不知道这是否会给你带来任何好处。) - 对 YUV 数据进行修改。
- 将 YUV 数据转换为 RGB。
- 将 RGB 数据转换为位图或 GLES 纹理。 glTexImage2D will be more efficient, but OpenGL ES comes with a steep learning curve. Most of the pieces you need are in Grafika (e.g. the texture upload benchmark) 如果你决定走那条路。
- 渲染图像。根据您在第 4 步中所做的操作,您将通过 custom View 上的 Canvas 渲染位图,或者在 SurfaceView 或 TextureView 上使用 GLES 渲染纹理。
我认为最显着的加速将来自消除 JPEG 压缩和解压缩,因此您应该从这里开始。将帧编辑器的输出转换为位图,然后将其绘制在 TextureView 或自定义视图的 Canvas 上,而不是转换为 JPEG 并使用 ImageView。如果这不能让您获得想要的加速,找出是什么让您变慢并在管道的那一部分上工作。
如果您仅限于使用旧相机 API,那么使用 SurfaceTexture 并在 GPU 着色器中进行处理可能是最有效的。
假设您想要进行的任何修改都可以合理地表达为 GL 片段着色器,并且您对 OpenGL 足够熟悉,可以设置将单个四边形渲染到帧缓冲区所需的所有样板,使用来自 SurfaceTexture 的纹理。
然后您可以使用 glReadPixels 从最终渲染输出中读回结果,并将其保存为 JPEG。
请注意,着色器将为您提供 RGB 数据,而不是 YUV,因此如果您确实需要 YUV,则必须在处理之前转换回 YUV 色彩空间。
如果您可以使用 camera2,如 fadden 所说,ImageReader 或 Allocation(分别用于 Java/JNI-based 处理或 Renderscript)也成为选项。
如果您只是使用 JPEG 获取要放置在 ImageView 上的位图,而不是因为您想保存它,那么再次如 fadden 所说,您可以跳过 encode/decode 步骤并直接绘制到视图。例如,如果使用 Camera->SurfaceTexture->GL 路径,您可以只使用 GLSurfaceView 作为输出目标并直接渲染到 GLSurfaceView,如果这就是您需要对数据执行的全部操作。