下载视频大量丢帧:UDP → TCP
我们最开始使用 UDP 进行下载。发现下载视频中间有大量丢帧,影响用户使用。
首先排查网络。tcpdump 分段抓包,统计 RTP 序列号的连续性,确实能观测到批量丢包,严重时丢包率不低。
由此分析出大量丢帧是由于UDP丢包导致,可以换成TCP通信, 解决丢包的问题。
切换成 TCP 之后,UDP 丢包问题解决了。有遇到信息问题:下载经常断开,概率很高。
又回到网络排查的思路上:
参数试错:关闭 ZLMediaKit 的 paced_sender_ms 平滑发送、把 HTTP keepalive 从 30 秒延长到 180 秒,断连概率轻微下降但远未根治;
抓包排除:反复抓包、逐节点排查网络层,确认不是防火墙、交换机、链路抖动的问题。
两步排查走完,开始怀疑不是网络层的问题。
三、问题一:中途断连
分析
接下来在流媒体推流模块加日志后,断开前的 pattern 很清晰:发送缓冲区持续增长 → write() 返回异常 → 应用层主动 close()。
由于上级平台接收速度慢,导致数据不能及时发送出去,TCP 滑动窗口逐渐缩小,下级内核发送缓冲区最终堆满,应用层捕获异常后主动断开连接。
修复
流媒体服务器做如下改动:推流时判断前次数据是否已发送完成。每次推流前检查上次发送是否完成,如果未完成,则等待一段时间,不往 socket 里塞新数据。以本地发送完成状态做自我节流。不需要探测对端,不需要改协议。
修复后,中途断连问题解决。
四、问题二:提前结束
现象
断连修完之后,过一段时间,又暴露出来一个问题:下载完成后,文件的时长偶尔小于期望,极端情况下1小时能小3分钟以上。严重影响用户使用。
分析
排查 GB28181 的下载完成机制后发现:下级平台流媒体推送完成后,立即触发完成回调给国标信令平台,信令平台发送 SIP MESSAGE(121)通知给上级,结束下载。但 SIP 信令和 RTP 媒体走两套独立通道——下级发完最后一帧就发 121,信令先发到上级平台,结束下载,文件截断。
修复
第一步:流媒体服务器在推流完成后,不立即触发完成回调,而是等发送缓冲区真正清空——确认数据已经从 socket 发出去了——再触发回调。
第二步:上级平台侧在收到 SIP 121 通知后,延迟一段时间再结束下载,让在途数据有最后到达的机会。
改造前后的时序差异:
上级平台
网络
下级平台
上级平台
网络
下级平台
改造前:信令跑赢媒体帧,录像截断
滞后RTP帧直接丢弃
改造后:缓冲区排空再发信令,延时兜底
等待socket内核缓冲区完全排空
延时窗口等待在途数据
推送最后一批RTP媒体帧
立即下发SIP 121结束通知
收到通知,关闭文件写入
推送最后一批RTP媒体帧
下发SIP 121结束通知
全部媒体帧接收完成
正常关闭,生成完整录像
修复之外,还做了几项配套调整:ZLMediaKit 的 paced_sender_ms 平滑发送参数优化、HTTP keepalive 超时延长,辅助缓解高倍速下缓冲区压力;倍速拉流时根据上游实际接收吞吐动态限制推送速率,避免下游无脑满速推送;对齐上下游 RTP SSRC 标识,消除流匹配的隐性异常。这些不是主修复,但少了它们,极端场景下仍然可能触发边界问题。
落地效果
UDP 丢包:切换 TCP 后彻底解决
TCP 中途断连:发送端节流机制上线后未再复现
录像截断:双层时序防护上线后,取证录像100% 完整,不再出现时长缺失
性能:整套逻辑改造为应用层纯逻辑,无额外 CPU、内存开销,平台并发承载能力不受影响
五、回头看
整条链路走下来,四个阶段:UDP 丢包 → 切 TCP → 中途断连 → 提前结束。后面两个问题排查成本远高于修复成本,各自只有几行代码的改动量。
几条踩过的坑,后来在其他项目里反复验证过:
不要迷信 TCP 万能。
TCP 只解决网络层的丢包重传和拥塞控制,不负责应用层的收发速率匹配。高速、不对称链路场景下,应用层必须自己做发送节奏管控——确认上一批发出了再推下一批。换了协议只是换了一组问题,真正要修的是应用层对底层状态的感知能力。
这件事的通用形式是:任何跨网数据传输,只要带宽不对称且速率高,应用层必须实现某种形式的背压,不能假设 TCP 会替你搞定一切。
双通道协议里,控制通道与数据通道的时序没有天然保证。
GB28181 的 SIP 信令和 RTP 媒体走两套独立通道,SIP 畅通不代表 RTP 也畅通。下游"发完"和上游"收完"之间在不对称链路上可以差几十到几百毫秒。
结束类信令的发送时机必须绑定数据通道的实际完成状态,而不能绑定"我写完了"这个应用层事件。 这个原则适用于所有控制面与数据面分离的协议——不只是 GB28181。
前置故障会完全掩盖后续故障。
