内容提要
本文记录WebRTC视频管线实战优化:持锁调用网络IO导致死锁、AVFrame复用误标I帧、帧率超目标拉高CPU;切换RTSP摄像头后采用低延时防积压策略;全链路打点定位x264编码瓶颈,改用NVENC、降低SHM轮询、绕过sws_scale,CPU大幅下降;并增加时间戳跳变、重连竞态、SHM重建等自愈逻辑。
延伸解读
锁内网络IO:死锁的典型陷阱
文章指出,RTSP模块在互斥锁保护的代码块内直接调用网络发送函数,当Docker网络变化导致Live555线程IO阻塞时,锁无法释放,编码线程等待锁,最终整个管线卡死。这提醒开发者,互斥锁内只应进行内存操作,严禁执行网络IO或外部回调,否则极易引发死锁。
实时流防积压:丢旧帧保最新
切换RTSP摄像头后,FFmpeg内部缓冲容易堆积旧帧,导致延时累积。文章提出三层策略:拉流层用TCP、关闭RTP重排队列;解码层开启低延时、关闭B帧;业务层对比PTS与墙钟,滞后超0.5秒即丢帧。核心思路是允许丢弃旧帧,确保处理最新帧,避免历史帧积压。
全链路打点:量化瓶颈才能优化
文章强调,主观感受卡顿无意义,需在SHM帧结构中透传微秒级时间戳,量化各环节耗时。实测发现x264编码耗时32ms、SHM轮询等待18ms、sws_scale转换12ms是主要瓶颈。针对性改用NVENC、降低轮询间隔、绕过无效转换后,CPU从107%降至48%,延时和帧率显著改善。
自愈逻辑:应对隐蔽故障
长时间运行可能遇到摄像头RTP时间戳跳变、WebRTC重连竞态、SHM重建后旧映射失效等问题。文章建议检测lag超5秒重置时间锚点、异步触发前重置状态标志、检测global_seq不更新时重新映射共享内存,并增加帧看门狗。通过故障注入测试验证自愈链路可自动恢复,无需人工干预。
Q&A
WebRTC 信令握手成功但浏览器没有画面,可能是什么原因?
根据文章,可能的原因包括:持锁调用网络发送造成死锁,导致编码管线卡死;AVFrame 复用 BUG 使所有帧被错误标记为 I 帧;共享内存结构体定义分散在多个文件,修改分辨率后内存布局错位。
如何优化 WebRTC 视频管线以降低 CPU 占用?
文章提到的优化措施有:开启 NVENC 硬件编码(编码耗时从 32ms 降至 7.5ms);将 SHM 读取轮询从 30ms 降低至 5ms;同分辨率直接拷贝 YUV 平面绕过 sws_scale;使用令牌桶 pacing 替代固定间隔丢帧。优化后 CPU 从 107% 下降到 48%。
切换 RTSP 摄像头输入后,如何防止延时累积?
文章提出三层低延时防积压策略:拉流层使用 TCP 传输、关闭 RTP 重排队列、缩短探测时间;解码层开启低延时模式、关闭 B 帧、禁用多线程解码;业务层对比 PTS 和系统墙钟时间,滞后超过 0.5s 丢弃旧帧,并支持指数退避重连。
WebRTC 视频管线中,如何定位性能瓶颈?
文章建议全链路打点,在 SHM 帧结构里透传微秒级时间戳(写入 SHM、读取 SHM、入队列、编码开始、编码耗时),浏览器通过 getStats() 统计 jitter buffer 和解码耗时。通过量化各环节耗时,发现 x264 编码 32ms、SHM 轮询 sleep 30ms、sws_scale 转换 12ms 是主要瓶颈。
长时间运行后 WebRTC 画面卡死,有哪些自愈逻辑?
文章提到三个隐蔽问题及自愈逻辑:摄像头 RTP 时间戳基准跳变,检测 lag 超过 5s 自动重置时间锚点;WebRTC 重连信令竞态,异步触发前完成状态重置;SHM 文件重建后老进程持有旧 mmap,检测 global_seq 长时间不更新则自动 unmap 并重新打开共享内存。浏览器增加帧看门狗,6 秒无帧强制重连。
在 Docker 中使用 NVENC 硬件编码需要注意什么?
文章指出,在 Docker compose 中配置 NVENC 时,capabilities 必须带上 video,否则会报 libnvidia-encode.so 加载失败。