下面以"大牛直播SDK 的 RTSP 播放器遇到 RTP 不带 Marker 位(M bit) "为切入点,结合 RTP/RTCP 基础 与 H.264/H.265/AAC 的负载规范 ,说明发送端如何规范打包 ,以及接收端如何稳健容错(即使对端未按规范设置 Marker)。

windows平台rtsp播放器延迟测试
一、先厘清 Marker 位在各规范里的语义
-
RTP 基础(RFC 3550) :M 位的语义由具体负载格式定义,常用于标记"重要边界事件"(如视频帧边界或音频语音突发边界)。
-
H.264(RFC 6184) :M 位设在一个接入单元(Access Unit, AU)的最后一个 RTP 包 (即该 AU 的"最后一包");聚合包(STAP/MTAP)的 M 位等于 其中"最后一个 NALU"若单独承载时应有的取值。接收端可以把 M 位当作"早期提示",但 不得仅依赖它判断 AU 结束。
-
H.265/HEVC(RFC 7798) :与 H.264 一致,M=1 标示当前 RTP 流中该 AU 的最后一包。
-
AAC(RFC 3640,MPEG4-GENERIC) :M=1 表示"本包包含了一个 AU 的最后一片,或包含一个/多个完整 AU"。
小结:规范的发送端应正确设置 M 位 ;但健壮的接收端不能只依赖 M 位 来切帧,尤其是面对一些"简化实现"的摄像头/设备。
二、发送端(打包器)如何"规范打包":H.264/H.265
编辑
1) 通用约束
-
时间戳 :同一帧(AU)内的所有 RTP 包 使用相同的 RTP 时间戳 ;视频时钟为 90 kHz。参数集(SPS/PPS/VPS)与 SEI 的时间戳与其所在 AU 的"主编码图像"一致。
-
序号:RTP 序号每包 +1,随机起始。
-
Marker :仅在"该 AU 的最后一包"置 1;否则为 0。
2) 单 NALU 包(Single NAL Unit)
-
NALU 尺寸 ≤
MTU - IP/UDP/RTP - 负载头
时,整 NALU 进一包; -
若该 NALU 是 AU 内的最后一个 NALU ,则 M=1 ;否则 M=0。
3) 分片(FU-A for H.264 / FU for HEVC)
-
当 NALU 过大需分片为 FU :首片
S=1,E=0
,中片S=0,E=0
,尾片S=0,E=1
; -
只有"该 AU 的最后一个 NALU 的最后一片(E=1)"那一包才设 M=1,其他分片包 M=0。
4) 聚合(STAP/MTAP / HEVC AP)
- 将多个小 NALU 聚合发送时,聚合包的 M 位="其中最后一个 NALU"若单独发送时应有的 M 值 ;也即:若该聚合包包含了 AU 的最后一个 NALU ,则整个包的 M=1。
5) MTU 与分片尺寸建议
-
典型 UDP MTU 1500,预留:IP(20/40) + UDP(8) + RTP(12) + 负载头(H.264 FU 2B / HEVC FU 3B 左右),保守 负载≈1200--1300B;
-
TCP/RTSP 内嵌(interleaved)虽无 UDP 头,但仍建议与 UDP 一致的分片尺寸,利于复用与中间件处理。
三、发送端(打包器)如何"规范打包":AAC(MPEG4-GENERIC)
-
AU 头 :按 SDP 中的
sizeLength/indexLength/indexDeltaLength
生成 AU-headers-section;最简单做法是 "一包一帧(1 AU/包)"; -
帧内容 :发送裸 AAC 数据(不带 ADTS 头);
-
Marker:
-
分片时 :最后一片所在包 M=1;
-
未分片(完整 AU) :该包 M=1;
-
-
时间戳 :以采样步进累加(如 AAC-LC 48kHz,1024/48000 s ≈ 21.33 ms/帧)。
四、接收端(播放器)如何"稳健容错":Marker 缺失时的切帧策略
即使对端未设置 M 位,播放器也应能"稳健切帧"而不积压卡顿:
-
按 (SSRC, RTP 时间戳) 聚帧
- 同一 AU 的所有包时间戳相同;时间戳变化 即意味着上一个 AU 结束。适用于 H.264/H.265/AAC。
-
结合负载内信号
-
FU 分片的 E 标志 只标识"NALU 结束 ",并不等同于"AU 结束";
-
若存在 AUD NALU(H.264 type 9 / HEVC type 通常 35),可将其作为 AU 边界提示(可选,非强制)。
-
-
超时回退(防止卡死)
- 在弱网/丢包下,若长时间未等到 M=1 且时间戳未变,用"动态超时阈值"触发吐帧(例如基于最近帧间隔×1.5)。
-
M 位优先但非唯一
-
有 M 位时优先用 M=1 切 AU;无 M 位 时按"时间戳变化 + NALU 完整性 + 超时兜底"组合判定。
-
这与 H.264 规范的建议一致:解码端可以使用 M 位作提示,但不得仅依赖它。
-
五、参考实现
H.264/H.265 发送端(打包器)
ini
for each AccessUnit AU:
ts = au_pts_in_seconds * 90000
last_nalu_index = AU.nalus.last_index
for i, nalu in enumerate(AU.nalus):
if nalu.size <= PAYLOAD_BUDGET:
M = (i == last_nalu_index) ? 1 : 0
send_single_nalu(nalu, ts, M)
else:
fragments = fragment(nalu, PAYLOAD_BUDGET)
for j, frag in enumerate(fragments):
S = (j == 0); E = (j == fragments.last_index)
M = (E && i == last_nalu_index) ? 1 : 0
send_fu(frag, ts, S, E, M)
播放端(聚帧)
scss
onRtp(pkt):
key = (pkt.ssrc, pkt.timestamp)
au = map[key]
au.add(pkt)
if pkt.marker == 1:
flush(au)
else if timestamp_changed_since_last_packet(key):
flush(previous_au_for_that_ssrc)
else if au_waiting_time_exceeds(dynamic_threshold):
flush(au) // 超时兜底,避免卡住
六、常见非标现象与对策
-
设备不设 M 位 / 乱设 :按上节"稳健容错"聚帧,不要仅靠 M;
-
把 ADTS 塞进 AAC RTP :应发送裸 AAC;
-
错误的 FU 边界 (S/E 乱用、片尺寸越界):接收端要做解包校验,异常丢弃并触发"超时吐帧";
-
时间戳不变/乱跳 :对端打包器应修正;接收端以 时间戳变化优先 + 超时兜底,并告警上报。
结论(开发要点)
-
发送端 :严格按规范设置 Marker(H.264/HEVC:AU 最后一包置 1 ;AAC:最后分片或包含完整 AU 置 1 ),并保证 同 AU 同时间戳 、序号连续 、分片头正确。
-
接收端 (大牛直播SDK RTSP 播放器侧):实现 "M 位优先 + 时间戳变化 + FU 完整 + 超时兜底" 的复合切帧算法,这既符合规范精神,又能兼容"不带 M 位"的常见设备。