FFMPEG和SDL实现流媒体播放控件

之前为项目中使用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()){
//FFMPEG读取网络包

AVFrame* f = reader_->DecodeVideo(p);
if(nullptr == f){ continue; }
{
AutoSpinLock lock(lock_);
AVFrame* yuv = sws_->scale(f);

//SDL绘图

if(window_resized) {
window_resized = false;
//SDL更新
}
}
}

另外需要注意的是播放的稳定性问题,也就是输出要根据帧率进行换算,要适当的Sleep,逻辑大致如下

double timestamp = reader_->video_timebase() * f->pts;
if(lastest_timestamp_ == 0.0) {
lastest_timestamp_ = timestamp;
SDL_Delay(200);
lastest_timer_.reset();
} else if(cost_timer_.elapsed() > 0.1){
lastest_timestamp_ = timestamp;
SDL_Delay(200);
lastest_timer_.reset();
} else {
double cost = lastest_timer_.elapsed();
double ts = (timestamp - lastest_timestamp_) - lastest_timer_.elapsed();
if(ts > 0.1) ts = 0.04;
if(ts > 0) SDL_Delay(ts * 1000);
}

cost_timer_.reset();

全屏问题

当上面的功能实现以后,全屏如何实现呢,总该不会把窗口又大小缩放到全屏大小吧,估计应该是可以实现的,但是里面确实有很多的坑,而且创建的多了容易相互影响,大小管理等等,肯定也是非常麻烦,这里我想到的是单独创建一个单独的全屏窗口,正常情况下隐藏,需要的时候,按照上面的大小缩放逻辑替换掉窗口句柄为这个全屏窗口句柄,再双击恢复回去,也就是原来的窗口一直没有变化,由外部控制。

由于封装的缘故,其实在SDL的事件监听中监听双击,然后控制对应的实例进行窗口替换并将窗口显示出来,但是对于那个实例,仅仅和普通的大小切换逻辑相同。对于全屏窗口只需要监听双击,然后通知实例进行窗口替换(使用原来的句柄)然后把本窗口隐藏即可。基本没有窗口的创建释放操作,非常的流畅,代码很简单,就不展示了。

结束通知

正常情况下的文件播放在播放完成之后,应该需要一个回调通知,但是流媒体理论上讲直播情况下应该没有停止,即停止由业务控制,做为播放控件,提供接口是必要的,但是可以对流媒体类型进行过滤处理,比如RTMP和RTSP等

最终效果

最终对外提供了几个API,C#调用也非常的简单,经测试单路的时候,CPU占用接近30%,很流畅,当开了几十个窗口之后依然30%多的CPU占用率,且流畅度非常好,对比的原来的播放控件,开5路已经基本界面操作很卡顿了,精巧完美的实现了需求。

最近的文章

Modbus协议应用的不足讨论

Modbus是工业设备的标准通信协议了,一般通过串口进行通信,为了适应现在互联网也支持通过网络传输的TCP版本,我司现在一般要求设备方提供TCP版本,毕竟部署方便,不过在现行的主从模式下,也并非能支持所有场景 基本能力的不足Modbus/TCP 协议本身并不复杂,这里就不详解解释了,另开一篇讨论。这 …

技术 继续阅读
更早的文章

Poco插入大量数据到数据库的优化

最近在调试 Poco 操作数据库的性能问题,确实发现一些有意思的地方,不仅有事务方面的批量,还有容器实现的内部批量,当然先以最基础的事务模式作为开头,来进插入性能的对比测试 事务操作一般插入数据没什么好说的,但是在数据量比较多的时候,总体的插入就会很慢,这个时候最基本的操作就是批量操作,也就是靠事务 …

技术 继续阅读