之前为项目中使用ffmpeg与SDL封装了一个视频播放控件,用起来挺简单的,在此分享下大概的实现方法和效果。
基本需求
原本项目中被C#调用了一个第三方的播放RTMP的控件,但是呢问题特别多,而且不能再定制功能,因为我接手给实现了一个,大致的需求如下
提供DLL供其他语言调用,主要是C#
支持多窗口,即分屏
支持双击全屏,再双击还原
经过于接口人员了解,大概就是提供C接口DLL并且分屏由业务根据实际需要创建多个窗口(控件),将其原始的窗口句柄传给DLL即可,这么一聊基本就是使用SDL作为绘图层,由FFMPEG负责媒体的解码等,配合Win32 API实现周边功能。
基本绘图
基本绘图思想是外部调用接口创建一个内部的句柄,内部呢是直接起了一个对应线程,而且线程内主要负责使用FFMPEG进行媒体解码,然后调用SDL层进行图像的输出,这个逻辑代码网上可以找一堆,我实现的也是类似的,没什么好展示的。当然了如果读者对Win32开发比较清楚的话,就知道Windows的桌面控件都是可以获取句柄的,哪怕是一个按钮也好,只需要传递过来就可以安装窗口的方式去绘图,当然了SDL提供了直接从窗口创建SDL_WIndow的接口,具体的可参考这个API详情 SDL_CreateWindowFrom(hwnd)
这里面需要注意第一个是窗口大小的变化,在SDL的事件监听里面要监听这个事件,并且针对这个事件进行处理,当然了,在外部通知内部后,里面的绘图层需要调整FFMPEG输出图像的大小。这里我实现的是FFMPEG根据新的大小调整输出,然后SDL也根据新的大小重新调整输出层,也就是Surface,最终实现动态调整大小。注意多线程操作的安全性,大致逻辑如下
while(!IsThreadExit() && !reader_->eof()){ |
另外需要注意的是播放的稳定性问题,也就是输出要根据帧率进行换算,要适当的Sleep,逻辑大致如下
double timestamp = reader_->video_timebase() * f->pts; |
全屏问题
当上面的功能实现以后,全屏如何实现呢,总该不会把窗口又大小缩放到全屏大小吧,估计应该是可以实现的,但是里面确实有很多的坑,而且创建的多了容易相互影响,大小管理等等,肯定也是非常麻烦,这里我想到的是单独创建一个单独的全屏窗口,正常情况下隐藏,需要的时候,按照上面的大小缩放逻辑替换掉窗口句柄为这个全屏窗口句柄,再双击恢复回去,也就是原来的窗口一直没有变化,由外部控制。
由于封装的缘故,其实在SDL的事件监听中监听双击,然后控制对应的实例进行窗口替换并将窗口显示出来,但是对于那个实例,仅仅和普通的大小切换逻辑相同。对于全屏窗口只需要监听双击,然后通知实例进行窗口替换(使用原来的句柄)然后把本窗口隐藏即可。基本没有窗口的创建释放操作,非常的流畅,代码很简单,就不展示了。
结束通知
正常情况下的文件播放在播放完成之后,应该需要一个回调通知,但是流媒体理论上讲直播情况下应该没有停止,即停止由业务控制,做为播放控件,提供接口是必要的,但是可以对流媒体类型进行过滤处理,比如RTMP和RTSP等
最终效果
最终对外提供了几个API,C#调用也非常的简单,经测试单路的时候,CPU占用接近30%,很流畅,当开了几十个窗口之后依然30%多的CPU占用率,且流畅度非常好,对比的原来的播放控件,开5路已经基本界面操作很卡顿了,精巧完美的实现了需求。