WPF开发RTSP播放器,卡顿花屏内存泄漏,用FFmpeg实现工业级低延迟。
最近在做一个WPF的项目,需要播放RTSP视频流,就是那种监控摄像头传过来的画面。一开始图省事,直接用了VLC的控件,结果发现播放起来卡顿得厉害,画面还时不时花掉,内存也是一直往上涨,项目根本没法用。

后来我决定自己用FFmpeg来搞,虽然过程挺折腾的,但总算搞出了一个能用的播放器,延迟很低,也不怎么卡了。下面就是我这段时间折腾下来的经验,没啥华丽的辞藻,都是实际踩过的坑。
先说为什么不用VLC。VLC控件确实方便,拖进去就能用,但它是个黑盒子,你没法控制它怎么解码、怎么缓存。在工控机或者小电脑上,CPU占用动不动就飙到三四十,而且一跑就是几个小时,内存泄漏得关掉重开才行。网上说的DirectShow我也试过,延迟更不可控,遇到海康大华那种非标RTSP流,直接黑屏。
我选的方案是FFmpeg加WPF的WriteableBitmap。FFmpeg负责拉流和解码,WPF负责显示。核心思路是把解码和显示分开,用两个线程跑,中间加个缓冲队列,这样不会互相卡住。
解码部分我用的是FFmpeg的C库,在C里通过P/Invoke调用。首先得打开RTSP流,设置TCP传输,超时时间设成5秒,这样断网了能重连。然后创建解码器,我优先用硬件解码,比如DXVA2,这样CPU占用能降到5%以下。如果硬件解码不支持,就自动回退到软件解码。
解码出来的帧是YUV格式的,得转成RGB才能给WPF显示。这个转换用的FFmpeg的sws_scale函数,比我手动写循环快多了。转换完的RGB数据不能直接扔给UI线程,得先放到一个缓冲队列里。队列我设成最大3帧,满了就丢最旧的帧,这样能保证实时性,不会越积越多。
渲染部分用的是WPF的Dispatcher或者CompositionTarget.Rendering事件,在UI线程里更新画面。具体做法是创建一个WriteableBitmap,然后在Lock之后直接往它的内存指针里拷贝数据,再Unlock。拷贝的时候用C的unsafe代码和Buffer.MemoryCopy,速度很快。注意不要每次Lock整个画面,只标记更新区域,能省不少性能。
优化方面我做了几个关键点。第一是网络层,设置了buffer_size和max_delay参数,让FFmpeg不要缓冲太多数据,实现秒开。第二是解码线程和渲染线程分开,解码线程优先级设高一点,渲染线程用UI线程自带的优先级。第三是内存管理,解码完的帧用引用计数,用完就释放,队列里的帧也及时清理,这样内存跑了几天也不会涨。
遇到的坑也不少。花屏问题主要是YUV转RGB的参数不对,尤其是颜色范围,海康的流有些是TV级别,有些是PC级别,得用av_frame_get_color_range判断一下。内存泄漏经常是因为队列里的帧忘记释放,或者av_frame_ref和av_frame_free没成对出现。延迟高的话,把FFmpeg的nobuffer和low_delay选项打开,再把队列大小改成2帧,延迟就能降到200毫秒以内。
断流重连也是个麻烦事。我是在解码循环里检测到EOF或者错误码,就重新创建AVFormatContext再打开一次,同时把旧的资源全部清理掉,防止内存泄漏。
现在这个播放器已经跑在项目上了,同时播放四路1080P的RTSP流,CPU占用不到15%,延迟在200毫秒左右,基本满足需求。虽然比不上那些商业控件,但至少自己可控,哪里出问题都能修。
如果你也在做类似的WPF项目,建议别偷懒用VLC,FFmpeg虽然上手慢一点,但长期来看值。未来我还打算接入AI分析,比如人脸检测,把解码后的帧直接传给算法,就不需要再走一遍编码了。