ITPub博客

首页 > 应用开发 > Python > GB28181播放自愈:NACK、降级TCP、双缓冲切换

GB28181播放自愈:NACK、降级TCP、双缓冲切换

原创 Python 作者:suoten 时间:2026-09-20 20:23:32 0 删除 编辑

网络抖动是视频播放最大的敌人 做安防视频项目的都知道,实验室里跑得好好的,到了客户现场就出问题。

为什么?网络环境不一样。

实验室是千兆交换机,客户现场可能是4G、WiFi、工业环网,丢包、抖动、带宽波动,什么都有。

GB28181默认走UDP。UDP的特点是无连接、不保证顺序、不保证到达。网络一抖动,画面就花屏、卡顿、甚至断流。

PyGBSentry做了三层播放自愈机制:RTCP NACK、UDP降级TCP、双缓冲无缝切换。今天聊聊实现细节。

第一层:RTCP NACK丢包重传 RTCP NACK在RFC 4585里定义,全称是Negative Acknowledgement。接收端发现丢包,发NACK告诉发送端重传。

怎么检测丢包?

RTP包头部有序列号(Sequence Number),16位,0-65535循环。接收端维护一个期望序列号,收到包后对比:

序列号等于期望值:正常,期望值+1

序列号大于期望值:中间丢了包,记录丢失范围,期望值更新为序列号+1

序列号小于期望值:可能是乱序或重传,忽略或处理

class RTPReorderBuffer: def init(self): self.expected seq = None self.lostpackets = []

def on_rtp(self, seq, payload):
    if self.expected_seq is None:
        self.expected_seq = seq + 1
        return payload
    
    if seq == self.expected_seq:
        self.expected_seq += 1
        return payload
    elif seq > self.expected_seq:
        # 丢了包
        for lost in range(self.expected_seq, seq):
            self.lost_packets.append(lost)
        self.expected_seq = seq + 1
        self.schedule_nack()
        return payload
    else:
        # 乱序或重传
        return payload

NACK怎么发?

NACK是RTCP包,格式是:

0                   1                   2                   3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|V=2|P| FMT=1   |   PT=205      |          length               | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                  SSRC of packet sender                        | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                  SSRC of media source                         | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|            PID                |             BLP               | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ PID是丢失的第一个包序列号,BLP是位图,表示后续16个包哪些也丢了。

发送端怎么重传?

发送端维护一个发送缓存,保存最近发过的RTP包。收到NACK后,从缓存里找到对应包,重新发一遍。

重传的包,序列号不变,但时间戳要更新。接收端收到重传包,按序列号插入正确位置。

效果: 偶发丢包(丢1-2个包)能快速恢复,画面不花屏。丢包率超过5%时,NACK恢复不过来,触发第二层。

第二层:UDP降级TCP UDP丢包严重时,NACK也救不了。这时候需要切换到TCP。

怎么判断需要降级?

看RTCP RR里的丢包率。接收端定期发RR,报告丢包率。丢包率超过阈值(比如5%),或者连续N个RR丢包率都高,触发降级。

降级流程:

信令层发BYE,结束当前UDP会话

信令层重新发INVITE,SDP里m=video的传输协议改成TCP

设备回200 OK,开始走TCP发流

流媒体层切换RTP接收器,从UDP socket切到TCP连接

代码上,SDP的m=行从: m=video 6000 RTP/AVP 96 改成:

m=video 6000 TCP/RTP/AVP 96 a=setup:passive a=connection:new a=setup:passive表示平台被动等待设备连接,a=connection:new表示新建连接。

TCP和UDP的差异:

TCP是可靠传输,不丢包,但延迟可能更高。网络抖动时,TCP会重传,保证数据完整,但可能增加延迟。对于实时视频,延迟增加比花屏好。

降级时机: 不是一丢包就降级。先试NACK,NACK恢复不了再降级。降级后,如果网络恢复,可以再切回UDP。

第三层:双缓冲无缝切换 主码流和子码流切换,传统做法是停一路、起一路,中间黑屏或花屏。

PyGBSentry用双缓冲实现无缝切换。

架构:

流媒体层同时接收主码流和子码流,分别缓存。播放器从两个缓冲区拉流,根据网络状况选择播放哪一路。

主码流RTP → 缓冲区A → 播放器 子码流RTP → 缓冲区B → 播放器 切换逻辑:

网络好时,播放主码流(高清)。网络差时,切到子码流(流畅)。切换时,用时间戳对齐两个缓冲区,避免花屏。

class DualBuffer: def init(self): self.main buffer = deque(maxlen=100) self.subbuffer = deque(maxlen=100) self.current = 'main'

def switch_to_sub(self):
    # 找到子缓冲区中时间戳最接近的帧
    main_ts = self.main_buffer[-1].timestamp
    closest = min(self.sub_buffer, key=lambda f: abs(f.timestamp - main_ts))
    # 从closest开始播放
    self.current = 'sub'
    return closest
def switch_to_main(self):
    sub_ts = self.sub_buffer[-1].timestamp
    closest = min(self.main_buffer, key=lambda f: abs(f.timestamp - sub_ts))
    self.current = 'main'
    return closest

效果: 切换时画面短暂停顿(<100ms),但不花屏、不黑屏。

三层机制的协同 三层机制不是独立的,是协同工作的:

正常播放:走UDP,偶尔丢包,NACK重传

丢包率升高:NACK恢复不过来,触发UDP降级TCP

网络带宽不足:主码流卡顿,切换到子码流

网络恢复:切回主码流,或切回UDP

整个过程自动完成,不需要人工干预。

实测数据 场景 机制 效果 偶发丢包(<1%) NACK 画面无感知 中度丢包(1-5%) NACK + 双缓冲 轻微卡顿,不花屏 严重丢包(>5%) UDP降级TCP 切换时间<1s,画面恢复 带宽不足 主切子码流 切换时间<100ms 网络恢复 自动切回 无需干预 项目现状 PyGBSentry已经开源,AGPL v3.0协议,GitHub和Gitee同步维护。支持GB/T 28181-2022,向下兼容2016版。设备注册、实时预览、录像回放、云台PTZ、语音对讲、平台级联,功能完整。

部署三条命令:

git clone https://gitee.com/suoten/PyGBSentry.git cd PyGBSentry/editions/open-source python tools/generateenv.py --docker && docker compose up -d Gitee: suoten/PyGBSentry GitHub: suoten/PyGBSentry

如果你也在做视频播放优化,欢迎来交流。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050062/viewspace-3183208/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2026-05-07

  • 博文量
    69
  • 访问量
    55000