问题 渲染视频帧的最佳方法是什么?


从捆绑到我的应用程序(FFmpeg等)的解码器中获取视频帧的最佳选择是什么?

我自然倾向于选择OpenGL,如上所述 Android视频播放器使用NDK,OpenGL ES和FFmpeg

但在 Android中的OpenGL用于视频显示,评论指出OpenGL不是渲染视频的最佳方法。

然后怎样呢? jnigraphics本地图书馆?还有非GL SurfaceView?

请注意,我想使用本机API来渲染帧,例如OpenGL或jnigraphics。但是用于设置SurfaceView等的Java代码是可以的。

PS:MediaPlayer在这里无关紧要,我正在谈论自己解码和显示帧。我不能依赖默认的Android编解码器。


8045
2018-04-14 16:35


起源

关于这个的任何消息?我需要在OpenGL四元组中播放视频 - 即逐帧地将视频转换为OpenGL纹理。我必须采用FFmpeg方式还是有更简单的解决方案? - j00hi
@ j00hi:这是偏离主题的,问题不在于“如何用OpenGL播放视频”。请搜索/提出其他问题。 - olivierg


答案:


我将根据自己的经验尝试详细阐述和巩固答案。

为何选择openGL

当人们想到用openGL渲染视频时,  正试图利用GPU进行色彩空间转换和alpha混合。

例如,将YV12视频帧转换为RGB。像YV12 - > RGB这样的颜色空间转换要求您单独计算每个像素的值。设想一个1280 x 720像素的帧,最终会有多少操作。

我刚刚描述的是SIMD的用途 - 对多个数据并行执行相同的操作。 GPU非常适合色彩空间转换。

为什么!openGL

缺点是将纹理数据导入GPU的过程。考虑到你需要的每一帧 加载 纹理数据进入内存(CPU操作),然后你必须 复制 这种纹理数据进入GPU(CPU操作)。就是这个加载/复制 能够 使用openGL比使用替代品更慢。

如果您正在播放低分辨率视频,那么我认为您可能无法看到速度差异,因为您的CPU不会出现瓶颈。但是,如果你尝试使用HD,你很可能会遇到这个瓶颈,并注意到性能的显着提升。

传统上解决这个瓶颈的方法是使用 像素缓冲对象 (分配GPU内存来存储纹理载荷)。不幸的是,GLES2没有Pixel Buffer Objects。

其他选择

由于上述原因,许多人选择使用软件解码与可用的CPU扩展相结合,如NEON进行色彩空间转换。存在用于NEON的YUV 2 RGB的实现 这里。绘制帧的方法,SDL与openGL对于RGB无关紧要,因为在两种情况下都要复制相同数量的像素。

您可以通过运行确定目标设备是否支持NEON增强功能 cat /proc/cpuinfo 来自adb shell并在功能输出中查找NEON。


12
2017-10-07 22:37





我之前已经走过FFmpeg / OpenGLES路径了,这并不是很有趣。

您可以尝试从FFmpeg项目移植ffplay.c,这是在使用SDL的Android端口之前完成的。这样你就不会从头开始构建你的解码器,你也不必处理它的特性 AudioTrack,这是Android独有的音频API。

在任何情况下,最好尽可能少地进行NDK开发并依赖移植,因为在我看来,ndk-gdb调试体验现在非常糟糕。

话虽如此,我认为OpenGLES的性能是您最不担心的。我发现性能很好,虽然我承认我只在少数设备上测试过。解码本身相当密集,我在播放视频时无法进行非常积极的缓冲(来自SD卡)。


3
2018-04-14 16:45



感谢您的回答。我对AudioTrack和NDK感到很满意。我有一个流行的应用程序,广泛使用这些,具有大量的本机代码和音频优化。构建和使用FFmpeg库也没关系,我并不害怕。我的问题是关于视频帧显示。考虑到所有纹理的东西,OpenGL是实现最佳FPS的方法吗? RGB565默认的OpenGL格式怎么样?这种像素格式的视频看起来不错吗? - olivierg
谢谢你,先生! AudioTrack让我与NDK结合使用。我使用RGB565,我没有注意到视频质量的差异。和以前一样,对我而言,解码本身往往会降低帧速率,而帧速率从未低于~24 fps。 - Matthew Willis
好的,是的,我实际上已经在OpenGL RGB565中将非常大的JPEG图像渲染为纹理,看起来还不错,但我觉得使用RGB888会更好(见 我的另一个问题),因此考虑jnigraphics的原因。我还不确定,但我对OpenGL很熟悉所以我想我会走那条路,除非有人提出更好的想法。 - olivierg
哦,顺便说一下,我重新提出了问题,专注于帧渲染方面。解码器方面是边缘的。抱歉混淆。 - olivierg


实际上我已经部署了一个自定义视频播放器系统,几乎所有的工作都是在NDK方面完成的。我们正在获得全帧视频720P及以上,包括我们的定制DRM系统。 OpenGL不是你的答案,因为在Android上不支持Pixbuffers,因此你每个帧都会对你的纹理进行基本爆破,这会搞砸OpenGLESs缓存系统。坦率地说,你需要通过Froyo及以上版本的Native支持的位图来推送视频帧。之前Froyo你的软管。我还写了很多NEON内在函数用于颜色转换,重新缩放等,以提高吞吐量。我可以在高清视频上通过这个模型推出50-60帧。


1
2018-06-27 17:38



谢谢Greg的这些信息。通过“Native supported Bitmap”,我假设您正在讨论由NDK公开的jnigraphics库。在更改本机代码中的像素后,是否需要刷新/重绘Java中的位图/视图?你在很多设备上测试过吗?帧速率是否与设备一致? - olivierg
格雷格,对于想要通过手机直播视频的人,你能推荐一本书,开源项目或教程吗?很抱歉在评论中提出新问题,但我想引起你的注意。谢谢。 - nickfox
@Greg:请你补充一些细节吗?我已经为这个问题开了一个赏金,因为只有两个答案,而且它们与OpenGL相矛盾。你能更具体一点吗?例如,您是否尝试过使用glTexSubImage2D()而不是glTexImage2D()来防止您提到的OpenGL缓存问题? - olivierg
@Greg:你说你可以用这种技术播放720p视频吗?你使用的是软件视频解码器吗?我已经为iPhone做了这种方法,并且sw解决方案(ffmpeg)在320p视频分辨率下可靠地停止工作(在iPhone 2上可能是480p)。在iPhone上,我切换到了AV Foundation。我希望为Android做同样的事情,但他们似乎没有一个很好的视频编辑框架(尚) - KPK