背景
最近在全志科技的h3开发板上预研spice VDI终端,首先是移植在x86板子上跑的VDI终端代码到h3上,主要移植模块有spice-protocol, spice-gtk-0.25, usbredir-0.6, virt-viewer-0.6.0, linux-thinclient, pythinclient(老版本), reset-usb-helper-0.1。具体移植过程不具体描述,因为不具备太大的参考价值,大概的做法就是源码拿过来重新生成Makefile,然后编译,安装,编译过程中遇到的具体问题再具体解决。涉及到修改源码,去除3D模块,重新编译一些底层依赖库等工作,大概移植了一周左右,VDI才顺利跑起来。由于原来的x86终端并不支持h264数据流,所以需要增加spice协议对h264数据流的支持(这一步由于服务端已支持h264数据流的编码,并且arm版本的VDI终端已支持,所以移植过来就好了)。接下来就是利用vdpau来硬解h264数据,绘制鼠标到图像上,性能调优等。
预研过程
由于网上找不到现成的利用ffmpeg封装vdpau解码一帧h264数据的demo,在stackoverflow上有人提出可以参考ffmpeg_vdpau.c 和libavg库里面对vdpau的封装。这两个的代码我都研读过,先是选择了libavg中的封装方式,写了一个demo,结果解码出来的数据会有马赛克一样的花屏现象,怎么调解码参数都无效。然后看ffmpeg_vdpau.c的方式,它比libavg封装的更复杂,而且好像还涉及到另一套专门针对硬解加速的框架,关键是他面向输入流进行封装(InputStream),而非单帧数据(AVPacket),所以要改这个实现还得去深入研究下ffmpeg,由于时间关系并且有一定风险(不知道能否使用或者是否有libavg一样的花屏问题),所以没有针对这个方式进一步研究。
网上搜索得知mpv播放器(MPV源码)和VLC播放器就支持利用vdpau来硬解h264数据流,在h3上测试过mpv可以硬解h264并且解码质量和性能都非常好,所以开始看mpv源码,研究它是如何使用ffmpeg来封装vdpau进行解码以及实现OSD功能的,接着参考mpv的封装方式,剔除了很多不必要的东西,写了一个最简单的可以解码一帧h264数据的demo,其中已经包括了鼠标的绘制,性能的优化,图像翻转等等功能,最后移植到spice-gtk。具体可以参考mpv源码和我写的demo以及代码中的注释说明。
vdpau解码的原理
vdpau 的解码原理很简单,参考API文档和下面的数据流,再看看代码,很好理解。 vdpau API描述和数据流原理
代码
使用mpv和vlc的封装
- 鼠标绘制使用两个线程来实现,首是先解码图像的线程,需要获取当前鼠标的位置,然后把鼠标位图画到解码后的图像的特定位置上,再进行渲染。第二是处理鼠标事件的线程,获取当前最新的图像帧,将鼠标绘制上去。注意,最新的图像帧必须是videoSurface中的数据而不是outputSurface中的数据,否则拖动鼠标会有残影的现象,原因是outputSurface已在第一个线程中绘制过鼠标。所以第二个线程要再把videoSurface重新render到outputSurface。另外,两个线程同时投递视频帧,需要对display queue加锁。
- 单线程模式,在第二个线程只保留鼠标的最新位置。这种方式鼠标的绘制是取决于视频的帧率的,会有明显的滞后感。
- 关于性能优化: videoSurface 使用缓冲区,而不是一直分配、释放。
- 关于质量优化: 有一个经验是,在只有一个线程投递视频帧的情况下,同一个outputSurface只需要添加到display queue一次,后续当outputSurface中的数据有变化时,画面就会自动刷新。另外,两个线程绘制鼠标的时候,可以用同一个outputSurface,但是每次绘制完后都要重新添加到queue(具体原因未知)。
- 关于帧闪烁: 不管是一个线程还是两个线程投递视频帧,只要是将同一个outputSurface投递到queue,就不会出视频闪烁的情况。投递不同的outputsurface, 或者每次都重新分配释放,会出现闪烁。
mpv和vlc的封装方式本质上是一样的,完整的demo代码放github
mpv_vdpau_demo。
使用libavg库的封装
libavg的封装方式应该是过时的,解码出来的图像会有马赛克现象,这里也把代码放到github,以供参考。
libavg_vdpau_demo
移植到spice-gtk
移植到spice-gtk 主要是考虑如何将spice-gtk 中X window的display 用到vdpau中去,这里需要有GObject 类型系统和 gtk中信号驱动编程的基础,可以参考本博客中转载的GObject系列文章。然后再注意下全志科技封装的libvdpau-sunxi-master中的vdp_presentation_queue_display
接口的实现会创建一个新的window,这与spice-gtk中的window有冲突,大概是因为同一个display不能用于两个window所致,所以此处的解决方案是利用vdp_presentation_queue_display
接口的最后一个播放时间参数(该参数没有用)来传递spice-gtk中的window到libvdpau库中去。