在Android中显示自定义视频的有效路径
Posted
技术标签:
【中文标题】在Android中显示自定义视频的有效路径【英文标题】:Efficient path for displaying customized video in Android 【发布时间】:2015-09-24 00:18:12 【问题描述】:在 android 中,我需要一种有效的方法来修改相机流,然后再将其显示在屏幕上。 This post 讨论了几种方法,我能够实现第一种:
-
从 onPreviewFrame 获取帧缓冲区
将帧转换为 YUV
修改框架
将修改后的帧转换为 jpeg
显示框架到放置在 SurfaceView 上的 ImageView 用于
预览
这很有效,但将我通过常规相机预览获得的 30 fps 降低到了 5 fps 左右。从不同的图像空间来回转换帧也很耗电,这是我不想要的。
是否有示例说明如何直接访问原始帧而不必进行如此多的转换?使用 OpenGL 是正确的方法吗?这一定是一件很常见的事情,但我找不到很好的例子。
注意:为了向后兼容,我宁愿避免使用 Camera2 API。
【问题讨论】:
您尝试进行哪些修改?使用 GLES 操作有些事情非常简单,有些事情很尴尬。一个例子:youtube.com/watch?v=kH9kCP2T5Gg 我需要应用一些必须在 CPU 上而非 GPU 上运行的专有信号处理链(用 C 编写)。 如果你的代码必须在 CPU 上运行,那么你就不能使用 OpenGL ES(根据定义,除非你是 Mesa 的忠实粉丝)。您可以使用 ImageReader (developer.android.com/reference/android/media/ImageReader.html) 从 Surface 直接访问 YUV 数据,避免初始缓冲区复制和 RGB 转换,但这需要 API 19+。 不能使用 OpenGL 写入内存,然后可以从 CPU 访问吗?基本上,我正在寻找 CPU 密集度最低的数据流,允许在显示之前修改原始相机数据。 之前您说您有用 C 编写的代码必须在 CPU 上运行,而您的问题表明它需要在 YUV 中。 ImageReader 路径使您能够以很少的开销 CPU 访问 YUV 数据(它抽象出平面/半平面格式和对齐偏移)。除非你可以在 GPU 上进行处理,否则你不会做得更好。顺便说一句,你为什么将帧转换为 JPEG?你把它存放在某个地方吗?这会花费你不少钱。 【参考方案1】:基于 CPU 的管道的最有效形式如下所示:
-
从表面上的相机接收帧,而不是
byte[]
。使用 Camera2,您可以将帧直接发送到 ImageReader;这将使您可以 CPU 访问来自相机的原始 YUV 数据,而无需复制或转换它。 (我不确定如何使用旧的 Camera API 来安装它,因为它需要 SurfaceTexture 或 SurfaceHolder,而 ImageReader 不提供这些。您可以通过 SurfaceTexture 运行帧并从 glReadPixels()
获取 RGB 值,但我不知道这是否会给你带来任何好处。)
对 YUV 数据进行修改。
将 YUV 数据转换为 RGB。
将 RGB 数据转换为位图或 GLES 纹理。 glTexImage2D 会更高效,但 OpenGL ES 有一个陡峭的学习曲线。如果您决定走这条路,您需要的大部分作品都在 Grafika(例如 texture upload benchmark)中。
渲染图像。根据您在第 4 步中所做的操作,您将通过 custom View 上的 Canvas 渲染位图,或者在 SurfaceView 或 TextureView 上使用 GLES 渲染纹理。
我认为最显着的加速将来自消除 JPEG 压缩和解压缩,因此您可能应该从那里开始。将帧编辑器的输出转换为位图,然后将其绘制在 TextureView 或自定义 View 的 Canvas 上,而不是转换为 JPEG 并使用 ImageView。如果这不能让您获得您想要的加速,请找出是什么让您放慢了速度,然后着手处理这部分管道。
【讨论】:
我对您的部分回答感到困惑。位图不一定是图像的压缩表示(jpeg、png 等)吗?如果是这样,无论如何,第 4 步“将 RGB 数据转换为位图”是否会导致 jpeg 编码(或其他一些压缩)?自从我写了这个问题以来,我尝试走下使用 OpenGL 的道路,并使用 GLES 纹理让我的相机流到显示器,但我无法理解如何在 CPU 上高效地使用 Java 或 C使用 JNI,在预览之前修改 yuv 平面。一些说明这一点的代码会很棒!谢谢 没有。 android.graphics.Bitmap 包装了一个未压缩的 RGBA 像素包。可以使用Bitmap#compress()
写JPEG/PNG/WEBP,使用BitmapFactory将压缩后的图片转成Bitmap,但是Bitmap存储没有压缩。【参考方案2】:
如果您受限于旧相机 API,那么使用 SurfaceTexture 并在 GPU 着色器中进行处理可能是最有效的。
这假设您想要做的任何修改都可以合理地表示为 GL 片段着色器,并且您对 OpenGL 足够熟悉,可以使用纹理设置将单个四边形渲染到帧缓冲区所需的所有样板来自 SurfaceTexture。
然后,您可以使用 glReadPixels 从最终渲染输出中读取结果,并将其保存为 JPEG。
请注意,着色器将为您提供 RGB 数据,而不是 YUV,因此如果您确实需要 YUV,则必须在处理之前转换回 YUV 颜色空间。
如果您可以使用 camera2,正如 fadden 所说,ImageReader 或 Allocation(分别用于基于 Java/JNI 的处理或 Renderscript)也将成为选项。
如果您只使用 JPEG 来获取要放置在 ImageView 上的位图,而不是因为您想保存它,那么正如 fadden 所说,您可以跳过编码/解码步骤并绘制到视图直接地。例如,如果使用 Camera->SurfaceTexture->GL 路径,您可以只使用 GLSurfaceView 作为输出目标,然后直接渲染到 GLSurfaceView 中(如果您需要对数据执行此操作)。
【讨论】:
以上是关于在Android中显示自定义视频的有效路径的主要内容,如果未能解决你的问题,请参考以下文章