为什么GB28181音频RTP包总是320字节?G.711打包与封装详解
抓过GB28181信令和媒体流的人,大概率都在抓包窗口里盯着RTP载荷发过呆:视频包长度忽大忽小,音频包却像被尺子量过一样,一水儿的320字节,序列号一路递增,载荷长度纹丝不动。有人以为是某个厂商的实现巧合,换了几家设备抓包,还是320。这个数字看着随意,实际上背后是一条从采样率、量化位深、打包时长一路推下来的硬账,再叠加RTP、PS、PES三层封装的工程妥协。把这笔账算清楚,你再去对接第三方平台、排查音频卡顿、判断对端是不是偷工减料,心里就有底了。下面我就按"为什么是320"这条主线,把G.711定长编码的换算、40毫秒打包时长的成本权衡、PS/PES封装的实际字节分布、SDP里的约定,以及现场最常见的几类对不上的情况,一层层拆开讲。
1. 320字节不是拍脑袋定的,先看G.711的定长特性
1.1 一次采样一个字节:8000×8bit的换算链
GB28181体系里,音频编码长期以G.711为主,具体是A律的PCMA和μ律的PCMU两种。这两种编码有个共同点:采样率8000Hz,每个采样点用8个比特量化,也就是一个采样点严格对应一个字节。这个"一采样一字节"的关系非常关键,它意味着音频数据的字节数和采样点数是1:1的,不存在任何压缩波动。
于是换算就变成一道小学题:8000个采样点/秒 × 1字节/采样点 = 8000字节/秒。也就是说,一路G.711音频每秒产生的原始码流永远是8000字节,不多不少。这个8000字节/秒是个恒定值,无论你录的是安静的房间还是嘈杂的马路,无论说话人声音大小,字节数都不变——这就是定长编码和AAC、Opus这类变长编码最本质的区别。
再往下推一步:如果你打算把40毫秒的音频打成一个包,那么40毫秒对应多少个采样点?8000 × 0.04 = 320个采样点,也就是320个字节。这就是320这个数字最直接的来源。它不是协议规定的魔法常量,而是"8kHz采样率 + 8bit量化 + 40ms打包时长"三个条件相乘的结果。
反过来验证一下20毫秒:8000 × 0.02 = 160字节。10毫秒是80字节,60毫秒是480字节。任何一个8的倍数(因为8000/1000=8,每毫秒8个采样点)乘以毫秒数,都是合理的包长。所以当你在抓包里看到160字节的音频包,也不用惊讶,那只是对端把打包时长设成了20毫秒而已。
1.2 打包时长才是唯一可调的变量
把上面的公式抽象一下:音频包字节数 = 采样率 × 量化字节数 × 打包时长。对G.711来说,采样率和量化字节数都是编码标准锁死的,唯一能动的就是打包时长。所以"为什么是320"这个问题,本质上是"为什么打包时长选40毫秒"。
这里有个容易被忽略的细节:打包时长不是"一帧编码的长度"。G.711是样本级编码,严格说它没有"帧"的概念,你给1个采样点它编1个字节,给320个采样点它编320个字节。所谓"帧"完全是打包层人为切出来的。这一点和AAC、AMR这类有固有帧长的编码完全不同——AAC-LC一帧固定1024个采样点,你没法随便切成320个采样点。
所以在G.711场景下,打包时长是一个纯粹的工程参数,可以按需调整。国内安防行业在长期实践中形成了40毫秒的默契,原因在下一节展开。但需要先明确:这个默契不是国标强制绑定的,国标附录里对媒体流打包给出的是原则性要求,具体毫秒数留给实现者权衡。
1.3 为什么只有定长编码才会出现"包都一样大"
对比一下就能明白"都是320"这个现象为什么只发生在G.711身上。AAC编码一帧的字节数取决于这一帧的频谱复杂度,安静段可能只有几十字节,复杂音乐段可能几百字节,一帧一个样。Opus更灵活,从2.5ms到60ms的帧长都能配,码率还能动态调。这些编码打出来的RTP包,长度必然参差不齐。
G.711没有任何心理声学模型,没有滤波器组,没有熵编码,就是最朴素的查表量化。它不关心内容,只关心采样点数量。所以只要打包时长固定,包长就绝对恒定,连一个字节的浮动都没有。这也是很多运维人员判断"音频流是不是正常"的土办法:看RTP长度是不是稳定在320,如果忽大忽小,多半是编解码协商跑偏了,或者混进了别的编码类型。
顺带说一句,G.711也不支持静音抑制(VAD)。有些编码在检测到静音时会发很短的静音帧甚至不发包,G.711做不到这一点,它老老实实每秒发8000字节,一直发。这既是它的缺点(浪费带宽),也是它的优点(包长绝对可预测,抖动缓冲好设计)。
2. 为什么偏偏是40毫秒,把封装开销摊薄的算术
2.1 一个音频RTP包里真正背了多少"过路费"
要理解40毫秒的选择,得先看清一个音频包从应用层到网线之间被加了多少层头。很多人只算音频数据320字节,觉得64kbps的编码怎么也不该占多少带宽,实际抓包一看傻眼。逐层拆开:
- PS pack header:14字节(起始码4字节 + SCR和复用率等10字节)
- PES头:6字节固定部分(起始码前缀3 + stream_id 1 + 包长度2),加上带PTS的可选头8字节(标志2 + 头长度1 + PTS 5)
- 音频ES数据:320字节
- RTP头:12字节
- UDP头:8字节
- IP头:20字节
合计388字节,而真正有用的音频数据只有320字节。封装开销68字节,占比约17.5%。换算成带宽:388字节 × 8bit ÷ 0.04秒 = 77600bps,也就是约77.6kbps。而G.711编码本身的码率是64kbps,多出来的13.6kbps全是封装成本。
这还没算以太网帧头(14字节)、帧间隙和前导码。如果按二层算,每包还要再加二十多字节,实际线路占用会逼近85kbps。这就是为什么做带宽规划时不能直接拿64kbps乘以路数,必须按封装后的实际值来算。
2.2 不同打包时长的成本对照
把20毫秒、40毫秒、10毫秒三种方案摆在一起算,差距一目了然。计算前提统一为:G.711编码、每包一个PS包、带PTS、不含二层开销。
| 打包时长 | 音频数据 | PS+PES头 | RTP/UDP/IP头 | 单包IP总长 | 每秒包数 | 音频有效带宽 | 实际占用带宽 | 封装效率 |
|---|---|---|---|---|---|---|---|---|
| 10ms | 80字节 | 28字节 | 40字节 | 148字节 | 100 | 64kbps | 118.4kbps | 54.1% |
| 20ms | 160字节 | 28字节 | 40字节 | 228字节 | 50 | 64kbps | 91.2kbps | 70.2% |
| 40ms | 320字节 | 28字节 | 40字节 | 388字节 | 25 | 64kbps | 77.6kbps | 82.5% |
| 60ms | 480字节 | 28字节 | 40字节 | 548字节 | 16.7 | 64kbps | 73.1kbps | 87.6% |
从20毫秒切到40毫秒,带宽从91.2kbps降到77.6kbps,省了将近15%。对于一个上千路音频的联网平台,这个差距就是十几兆的出口带宽,是实打实的成本。而60毫秒虽然更省,但延迟代价开始变得难以接受,所以40毫秒成了那个"甜点"。
还有一个隐形成本:每秒包数。20毫秒方案每秒50个包,40毫秒方案每秒25个包。包数直接决定了交换机的包转发压力、防火墙的会话新建速率、以及接收端的软中断次数。在嵌入式设备上,这个差别非常明显——同样一颗芯片,25pps和50pps的处理余量完全不是一个量级。很多低端IPC在20毫秒打包时会出现音频断续,改成40毫秒就稳了,根因就在这里。
2.3 延迟、抖动缓冲与对讲体验的取舍
省钱的反面是延迟。40毫秒一包,意味着接收端至少要收到一个完整包才能开始解码播放,这就是40毫秒的基础延迟。但真实系统里远不止这些。
抖动缓冲(jitter buffer)通常要预留2到4个包的时间来吸收网络抖动。按40毫秒算,缓冲深度就是80到160毫秒。再加上编解码处理、网络传输、平台转发、终端播放,端到端延迟很容易累积到300毫秒以上。这个量级在监控预览场景完全无感,但在双向对讲场景就很要命了——说话的人会听到自己的回声,两个人同时说话时会抢话。
如果换成20毫秒打包,抖动缓冲的粒度更细,深度可以压到60到80毫秒,端到端延迟能降100毫秒左右,对讲体验明显改善。所以行业里一个不成文的做法是:单向预览和对讲走不同的打包策略。纯监听的场景用40毫秒省带宽,对讲场景切到20毫秒保延迟。有些平台甚至会在对讲建立时通过SDP重新协商ptime。
不过要注意,降低打包时长不是免费的。上表算得很清楚,20毫秒方案要多花17.6%的带宽,包数翻倍。在一个已经跑满的千兆口上贸然切换,可能引发丢包,反而让音频更差。我见过一个现场,为了改善对讲延迟把全线设备改成20毫秒,结果核心交换机某个端口的广播风暴抑制被触发,音频大面积断续,最后只能回退。这类调整一定要先算容量,再做灰度。
3. 拆开PS封装,看清320字节在RTP里的真实位置
3.1 pack header、PES头与stream_id的0xC0标识
一个RTP包的载荷(payload)并不是直接从320字节音频开始的,前面压着PS和PES两层头。这个结构必须拎清楚,否则用抓包工具时会找错偏移量。
最前面是PS的pack header,起始码固定为00 00 01 BA,后面跟着系统时钟参考(SCR)、程序复用率和填充长度,总共14字节。这个头的存在感很低,很多人第一次拆包时会把它当成垃圾数据跳过,但它是判断"这个RTP包是不是PS流"的第一道标志——如果载荷开头不是00 00 01 BA,那基本可以断定对端没按PS封装走,后面的解析全都白搭。
pack header之后是PES包。PES包的起始码是00 00 01加一个字节的stream_id。stream_id是区分音视频的关键:0xE0表示视频流,0xC0表示音频流。看到00 00 01 C0就知道这是音频PES,紧接着两个字节是PES包长度,再往后是可选头,里面带标志位和PTS时间戳。
把这些偏移量记牢:RTP载荷的第0到13字节是pack header,第14到19字节是PES固定头,第20到27字节是PES可选头,第28字节开始才是真正的G.711音频数据。所以如果你在Wireshark里看到RTP载荷总长348字节,减去前面28字节头,正好剩320字节音频,对得上。
实际抓包时,一个典型的音频PS包长这样:
00 00 01 BA 44 ... (14字节 pack header) 00 00 01 C0 01 48 80 80 05 21 ... (6字节PES固定头 + 8字节可选头) XX XX XX ... (320字节 G.711 音频数据)其中PES包长度字段的值是0x0148,也就是328,等于8字节可选头加320字节数据。这个数字可以当作一个校验点:如果看到的不是328,说明音频长度不是320,或者对端在后面追加了私有扩展。
3.2 一个PS包塞不下时怎么分片
有人会问:如果音频包变大,比如60毫秒480字节,PS包长度字段装得下吗?装得下。PES_packet_length是16位无符号整数,最大65535,480完全没问题。真正的限制在别处。
按国标的媒体流打包要求,一个RTP包的载荷应当是一个完整的PS包。这意味着如果音频数据大到超过MTU,就必须拆成多个PS包、多个RTP包发送。以标准以太网1500字节MTU为例,减去IP头20、UDP头8、RTP头12,RTP载荷上限是1460字节。减去PS/PES的28字节头,音频数据最多1432字节,对应约179毫秒。所以理论上60毫秒、100毫秒都在MTU范围内,不会触发IP分片。
但这里有个关键约束:一旦触发IP分片,丢一个分片整包就废了,音频会出现明显的咔哒声。所以工程上没人会把音频包做到接近MTU,320字节留足了余量。这个余量对视频包同样重要——视频I帧动辄几十KB,必须靠PS层的分片机制拆成多个RTP包,音频包如果也挤进来添乱,接收端的重组逻辑会复杂很多。
补充一点PS层的一个细节:当一个RTP包只承载音频时,PS包里通常只有一个pack header加一个PES包,结构非常干净。但有些实现会把音频和视频复用到同一个PS包里(虽然GB28181的实践里少见),这时pack header后面会跟多个PES包,解析时不能假设"第一个PES就是音频",必须遍历。
3.3 90kHz时间戳,40毫秒增量是3600而不是320
这是最容易踩的一个认知坑。很多做过纯音频RTP的人,习惯了时间戳增量等于采样点数,看到G.711就以为40毫秒包的时间戳应该递增320。错。
GB28181的RTP时间戳用的是90kHz时钟,不是8kHz采样时钟。原因是PS流本身是为复用设计的,音视频共享一套时间基准,MPEG系统层规定的就是90kHz。所以40毫秒对应的时间戳增量是90000 × 0.04 = 3600。
验证方法很直接:连续抓三个音频包,看RTP头的timestamp字段。如果依次是比如0x0012C000、0x0012CE10、0x0012DC20,两两相差0x0E10,也就是3600,那就对了。如果差值算出来是320,恭喜你,抓到的可能不是标准PS封装流,或者对端真的在用8kHz时钟——这种情况虽然有,但在GB28181对接里属于异常。
同样的90kHz基准也体现在PES头的PTS上。PES可选头里的PTS是33位字段,编码方式比较绕(分成三段,中间插标志位),换算成实际时间要除以90000。音频PES的PTS增量和RTP时间戳增量应当一致,都是3600。如果两者对不上,说明封装实现有bug,播放器可能表现出音画不同步。
我遇到过一起案例:某设备音频RTP时间戳按8000递增,PES的PTS按90000递增,两个都"看起来正常",但平台侧做音画同步时直接错乱,音频比视频快了十几秒。排查了两天才定位到这个问题,因为单独看任何一层的数值都没毛病,只有交叉验证才暴露。
3.4 用抓包把这条链路完整验一遍
给一套可复现的验证步骤,用Wireshark就能做,不需要额外工具。
第一步,先用过滤器rtp圈出媒体流,再叠加udp.port == 你的音频端口缩小范围。GB28181的SDP会给音频单独分配端口(通常是视频端口+2),这个在INVITE的应答里能看到。
第二步,看RTP头的payload type。视频一般是96(动态类型,PS流),音频常见8(PCMA)或0(PCMU)。如果音频的PT也是96,说明对端把音频也塞进PS流里发,这时候就得按PS结构解析,不能直接读载荷。
第三步,切到"packet bytes"面板,找到RTP载荷起始位置,确认前4字节是不是00 00 01 BA。是的话继续,不是的话直接判定封装不合规。
第四步,从偏移14开始找00 00 01 C0,找到后读后面两字节的PES长度,再往后跳到偏移28,选中后面的320字节,看数据是否符合A律编码特征(连续静音时A律编码值多为0xD5,μ律多为0xFF)。
第五步,对比相邻包的timestamp字段和sequence number,确认增量分别是3600和1。
这五步走完,一个音频包从外到内就全透明了。我在现场排查音频问题时基本都按这个顺序走,比翻日志快得多。
提示:Wireshark默认不会把PS流解析成树状结构,需要在"Decode As"里手动指定RTP载荷为MPEG-PS,或者直接看十六进制面板。别指望它自动展开,浪费时间。
4. SDP里的ptime和rtpmap,320字节的另一半约定
4.1 rtpmap这一行透露的编码底牌
GB28181的媒体协商走SDP,音频部分的描述通常长这样:
m=audio 9002 RTP/AVP 8 a=rtpmap:8 PCMA/8000 a=ptime:40 a=sendrecv第一行的数字8是静态payload type,在RFC 3551里被永久分配给G.711 A律。第二行PCMA/8000明确了两件事:编码是A律,采样率8000Hz。"8"和"8000"这两个数字一出来,每秒8000字节的码率就锁死了,没什么可争的。
如果这里写的是a=rtpmap:0 PCMU/8000,那就是μ律,字节数完全一样,只是量化表不同。国内设备多数用A律,因为A律在小信号段的信噪比更好,更适合语音。两者在包长上没有区别,都是每秒8000字节。
真正决定包长的是a=ptime:40这一行。ptime定义的是打包间隔,单位毫秒。40就对应320字节,20就对应160字节。这是发送端对接收端的一个"预告",告诉对方"我会按这个节奏发包,你的抖动缓冲按这个粒度设计"。如果SDP里没写ptime,接收端会按默认值处理(通常20毫秒),这时候如果发送端实际按40毫秒发,两边就对不上了。
4.2 ptime与实际包长的对应关系
把常见配置列成表,方便现场快速比对:
| ptime值 | 音频数据字节 | PS+PES头 | RTP载荷总长 | 每秒包数 | 典型场景 |
|---|---|---|---|---|---|
| 10 | 80 | 28 | 108 | 100 | 极低延迟对讲 |
| 20 | 160 | 28 | 188 | 50 | 对讲、低延迟预览 |
| 40 | 320 | 28 | 348 | 25 | 主流监控预览 |
| 60 | 480 | 28 | 508 | 16.7 | 带宽极度受限 |
注意表里的"RTP载荷总长"包含PS和PES头,这个数值才是抓包时载荷面板显示的长度。很多人只记320,看到载荷面板显示348就以为出了问题,其实只是没算封装头。
还要注意一个陷阱:ptime是建议值,不是强制值。发送端完全可以在SDP里写ptime:40,实际却按20毫秒发160字节的包。这种情况在第三方设备接入时并不罕见,尤其是那些从别的协议栈移植过来的固件。所以不能只看SDP就下结论,必须抓包实测。
4.3 协商不一致时的典型症状
ptime协商不上,症状往往不是"完全没声音",而是更隐蔽的故障:
第一种是音频断续但视频正常。接收端的抖动缓冲按20毫秒设计,结果发送端40毫秒才来一个包,缓冲频繁被掏空,播放时就会周期性卡顿。这种卡顿很有规律,大约每40毫秒一次,听起来像轻微的"嗡嗡"底噪。
第二种是音画不同步逐渐累积。两端对时间戳基数和增量的理解不一致,短时间看不出来,跑上十几分钟后音频就甩开视频一大截。
第三种是对讲回音。ptime过大导致本地采集到播放的环回延迟超过200毫秒,AEC算法压不住,说话人会听到明显的回声。
第四种比较少见但也遇到过:平台侧直接拒收。有些平台对音频包长做了硬校验,发现不是320就直接丢弃,表现为"视频有画面,音频完全静音",日志里还看不到明显报错,非常难查。
5. 换成AAC或G.722,320这个规律立刻失效
5.1 变长编码为什么做不到定长
如果GB28181对接双方协商用AAC,包长立刻变得毫无规律。AAC-LC的帧长固定为1024个采样点,在8kHz采样率下就是128毫秒一帧——这个时长明显太长,所以实际应用会做短帧或者把一帧拆成多个RTP包。但即便拆包,每个包里的数据量也不固定,因为AAC是变换编码加熵编码,压缩后的字节数完全取决于信号复杂度。
举个例子,一段纯静音的AAC帧可能只有十几个字节,一段有明显人声的帧可能一两百字节。接收端没法靠"包长是不是320"来判断流是否正常,只能靠RTP头里的marker位和时间戳。这就是为什么AAC虽然在同等音质下码率只有G.711的一半甚至三分之一,却在GB28181的音频场景里用得不多——可预测性太差,对接和排错的成本高。
G.722稍微特殊一点,它是16kHz采样、14bit量化(打包后每个采样点2字节),码率64kbps。20毫秒对应320个采样点,也就是640字节数据。所以如果你看到640字节的音频包,很可能对端用的是G.722。G.722音质比G.711明显好,但国内安防的普及度不高,主要是历史存量设备的兼容性问题。
5.2 国标体系对音频编码的实际约束
从协议文本看,GB28181对音频编码的要求是"应当支持G.711",其他编码列为可选。这个"应当"和"可选"的措辞很讲究:G.711是保底能力,任何合规设备都必须能收发;AAC、G.722这些属于加分项,支持了更好,不支持也不算违规。
这个规定直接塑造了行业的现状:绝大多数设备默认就用G.711 A律,320字节。因为这是唯一能保证100%互通的选择。你如果用AAC,碰到一个不支持的上级平台就得回退,来回折腾的成本远大于省下的那点带宽。
从工程角度看,这个选择其实挺务实。监控场景的音频主要是环境音和语音对讲,对音质的要求远没有音乐欣赏那么高。G.711虽然原始,但64kbps的码率在现在的网络条件下完全不是问题,反而是它的定长特性和几乎为零的编码延迟更值钱。
5.3 平台侧只认320的兼容性现实
更现实的一层原因是:很多存量平台和NVR的音频处理模块,就是按320字节硬编码的。它们的抖动缓冲用固定长度的环形队列,解码器每次读320字节,改一个字节都可能出问题。
我在一个项目里遇到过:上游设备升级固件后,音频打包时长从40毫秒改成了20毫秒,160字节的包发出去,下级平台直接不认,录像文件里音频轨全是静音。日志里没有任何错误,因为平台根本没报错,它只是"读不到足够的数据就丢弃"。最后靠抓包对比才定位到问题,回退固件才解决。
这件事给我的教训是:在存量系统里,320字节已经从一个计算结果变成了事实标准。你可以说这是技术债,但对接时不得不尊重它。新上设备如果需要改打包时长,一定要先确认上级平台的兼容性,别想当然。
6. 现场对接最常踩的几类"320对不上"的坑
6.1 对端发160字节时的判断与处理
抓到160字节的音频包,先别急着判定故障。按前面的公式,160字节对应20毫秒打包,是完全合法的配置。判断它是不是问题,要看上下文:
看SDP里写的ptime是多少。如果SDP写40,实际发20,那是发送端没遵守协商,属于实现缺陷,可以考虑要求厂商修正。如果SDP写20,实际也发20,那就是正常的,只是双方约定的粒度不同。
看接收端表现。如果音频清晰流畅,那就没必要动。如果出现断续,再考虑是否要调整。调整方向通常是让发送端改成40毫秒,因为这样能同时降低带宽和接收端的包处理压力。
看是否有混合发送。有些设备在启动阶段发20毫秒,稳定后切到40毫秒,抓包时会看到包长变化。这种情况一般无害,但如果接收端的缓冲逻辑不够健壮,切换瞬间可能出现一次卡顿。
还有一种特殊情况:对端用了非标准的分片策略,把320字节拆成两个160字节的RTP包发。这时候序列号会连续递增,但每个包的时间戳相同(因为属于同一个40毫秒的时间片)。这种实现虽然不常见,但确实存在,判断依据是"两个包的timestamp相同、sequence连续"。
6.2 音频包长错位与时间戳跳变的混淆
有时候问题不是包长本身,而是包长和时间戳的组合不对。比如包长稳定在320,但时间戳增量不是3600而是别的值,这会导致播放速度异常。
常见的错误增量有以下几种:
- 增量320:说明发送端用了8kHz时钟而不是90kHz,音频会以11.25倍速播放,听起来像快进。
- 增量1800:对应20毫秒,但包长是320字节(40毫秒的数据),结果是音频被压缩一半时长,播放速度翻倍。
- 增量7200:对应80毫秒,但包长320,结果是音频被拉伸,播放变慢。
- 增量为0:所有包时间戳一样,播放器无法排序,通常表现为完全静音或刺耳噪音。
排查这类问题,最有效的办法是导出连续20个包的timestamp和length,做成表格算差值和比值。差值稳定说明时钟基准一致,比值等于"包长÷8÷增量×90000"应当等于1,偏离1就是有问题。
6.3 音频PES与视频PES交错造成的误判
GB28181实践里,音视频通常走不同的RTP会话(不同端口),但也有实现把两者复用到同一个PS流、同一个RTP会话里。这种情况下,一个RTP载荷里可能出现多个PES包:先是视频PES(stream_id 0xE0),后面跟音频PES(0xC0)。
这时候如果你只看"载荷总长",会发现长度五花八门,完全找不到320的规律,容易误判成"音频编码不对"。正确做法是遍历PS包里的所有PES,按stream_id分开统计。
遍历逻辑不复杂:从偏移0开始找00 00 01,读第4字节判断类型,如果是0xBA就跳过14字节继续找,如果是0xE0或0xC0就按PES长度字段跳到下一个包的起点。把所有0xC0的PES的数据长度单独拉出来统计,如果都是320,说明音频本身没问题,只是被视频包"带偏"了观感。
还有一种更隐蔽的情况:音频PES被视频PES分割,一个320字节的音频数据被拆到两个RTP包里。这时候单个包里的音频PES长度不足320,但拼起来是320。判断依据是PES包长度字段的值是否等于实际数据长度——如果长度字段说328,实际只有200字节,那就是被截断了,需要在后续包里续接。
6.4 排查顺序的实战建议
综合上面这些坑,我给一套从外到内的排查顺序,按这个走基本不会漏:
- 先确认SDP协商结果,看rtpmap和ptime写的什么,建立预期。
- 抓包看RTP载荷起始4字节是否为
00 00 01 BA,确认是PS封装。 - 遍历PS内的PES,按stream_id分拣,统计音频PES的数据长度分布。
- 检查RTP时间戳增量和PES的PTS增量,确认都是3600。
- 对比SDP预期的ptime与实际包长,判断是否一致。
- 如果都正常但音频仍有问题,转去查抖动缓冲配置和网络丢包。
这个顺序的价值在于"从预期到实测逐层收敛",而不是一上来就猜编码类型。我见过太多人一看到包长不对就去改编码参数,折腾半天发现是对端根本没按SDP走。
最后分享一个我个人在实际操作中的习惯:做GB28181对接时,我会在项目初期就写一个小的解析脚本,把抓到的pcap里所有音频包的载荷长度、时间戳、序列号导成CSV,用表格拉一下分布和差值。这比在Wireshark里一个个点开快得多,而且能一眼看出异常值。脚本不复杂,几十行Python用dpkt或scapy就能搞定,一次投入长期受益——尤其是当你要同时对接七八家不同厂商的设备时,这个工具能省掉大量重复的排查时间。
另外提醒一句,改打包时长这类参数,永远先在单台设备、单个通道上验证,确认上级平台能正常收流、录像能正常回放,再批量下发。320字节这个约定在存量系统里根深蒂固,动它之前先想想下游能不能接得住。