从RTSP播放遇到RTP无 Marker探讨RTP规范化打包与稳健切帧

下面以"大牛直播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 位,播放器也应能"稳健切帧"而不积压卡顿:

  1. 按 (SSRC, RTP 时间戳) 聚帧

    • 同一 AU 的所有包时间戳相同;时间戳变化 即意味着上一个 AU 结束。适用于 H.264/H.265/AAC。
  2. 结合负载内信号

    • FU 分片的 E 标志 只标识"NALU 结束 ",并不等同于"AU 结束";

    • 若存在 AUD NALU(H.264 type 9 / HEVC type 通常 35),可将其作为 AU 边界提示(可选,非强制)。

  3. 超时回退(防止卡死)

    • 在弱网/丢包下,若长时间未等到 M=1 且时间戳未变,用"动态超时阈值"触发吐帧(例如基于最近帧间隔×1.5)。
  4. 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 位"的常见设备。

相关推荐
你很易烊千玺10 小时前
即时通讯小程序
小程序·uni-app·直播
音视频牛哥2 天前
《“人工智能+”行动意见》深度解析:从智能红利到产业落地,直播模块的技术价值与应用路径
人工智能·计算机视觉·音视频开发
一支鱼4 天前
基于 Node.js 的短视频制作神器 ——FFCreator
前端·node.js·音视频开发
AJi4 天前
编解码原理(一):H264
ffmpeg·音视频开发·视频编码
AKAMAI6 天前
部署在用户身边,将直播延迟压缩至毫秒级
人工智能·云计算·直播
重启的码农7 天前
云游戏技术之高速截屏和GPU硬编码 (5) 色彩空间转换器 (RGBToNV12)
c++·云计算·音视频开发
音视频牛哥8 天前
RTSP流端口占用详解:TCP模式与UDP模式的对比
音视频开发·视频编码·直播
重启的码农8 天前
云游戏技术之高速截屏和GPU硬编码 (4) NVENC 硬件编码 (NvEncoderD3D11)
c++·云计算·音视频开发
重启的码农8 天前
云游戏技术之高速截屏和GPU硬编码 (3) 桌面复制接口 (Desktop Duplication API)
c++·云计算·音视频开发