【无标题】

深入理解 RTP 协议与 H.264 视频传输:从核心原理到实战解析

一、为什么需要 RTP?------ 实时音视频传输的基石

在互联网上传输音视频数据,如果直接使用 TCP 协议,虽然可靠,但存在重传机制导致的延迟,不适合实时通话。如果直接使用 UDP 协议,虽然延迟低,但数据包乱序、丢包问题无法解决。

RTP(Real-time Transport Protocol,实时传输协议) 应运而生,它通常运行在 UDP 之上,提供了序列号(Sequence Number) 时间戳(Timestamp)等关键信息,帮助接收端进行数据重组、抖动缓冲和丢包检测,从而保证音视频流的实时性和流畅性。


二、 RTP 协议头部结构:12 字节的"信封"

RTP 包本身不关心承载的内容是什么,它只提供一个标准的"信封"。这个信封就是 RTP 固定头部,长度为 12 字节。

复制代码
 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|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

各字段含义解析:

字段 位数 含义
V (Version) 2 bits 协议版本号,当前标准为 2。
P (Padding) 1 bit 填充标志。若 P=1,则包尾包含填充字节,最后一字节指示填充长度。
X (Extension) 1 bit 扩展标志。若 X=1,则在固定头部之后有一个扩展头部。
CC (CSRC Count) 4 bits 贡献源标识符的计数。表示固定头部后跟的 CSRC 列表数量(0-15)。
M (Marker) 1 bit 标记位。由应用层定义,常用于标记视频帧的结束或音频会话的开始。
PT (Payload Type) 7 bits 载荷类型。标识 RTP 载荷的编码格式(如 G.711、H.264、AAC 等)。
Sequence Number 16 bits 序列号。每发送一个 RTP 包,序列号加 1。用于接收端检测丢包和重组数据。
Timestamp 32 bits 时间戳。反映 RTP 载荷中第一个字节的采样时刻,用于同步和抖动缓冲。
SSRC 32 bits 同步源标识符。在一个 RTP 会话中唯一标识一个媒体流。

RTP 头部 12 字节位分布图:
#mermaid-svg-HKLjTU4sJEO6k42U{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HKLjTU4sJEO6k42U .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HKLjTU4sJEO6k42U .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HKLjTU4sJEO6k42U .error-icon{fill:#552222;}#mermaid-svg-HKLjTU4sJEO6k42U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HKLjTU4sJEO6k42U .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HKLjTU4sJEO6k42U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HKLjTU4sJEO6k42U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HKLjTU4sJEO6k42U .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HKLjTU4sJEO6k42U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HKLjTU4sJEO6k42U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HKLjTU4sJEO6k42U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HKLjTU4sJEO6k42U .marker.cross{stroke:#333333;}#mermaid-svg-HKLjTU4sJEO6k42U svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HKLjTU4sJEO6k42U p{margin:0;}#mermaid-svg-HKLjTU4sJEO6k42U .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HKLjTU4sJEO6k42U .cluster-label text{fill:#333;}#mermaid-svg-HKLjTU4sJEO6k42U .cluster-label span,#mermaid-svg-HKLjTU4sJEO6k42U p{color:#333;}#mermaid-svg-HKLjTU4sJEO6k42U .label text,#mermaid-svg-HKLjTU4sJEO6k42U span,#mermaid-svg-HKLjTU4sJEO6k42U p{fill:#333;color:#333;}#mermaid-svg-HKLjTU4sJEO6k42U .node rect,#mermaid-svg-HKLjTU4sJEO6k42U .node circle,#mermaid-svg-HKLjTU4sJEO6k42U .node ellipse,#mermaid-svg-HKLjTU4sJEO6k42U .node polygon,#mermaid-svg-HKLjTU4sJEO6k42U .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HKLjTU4sJEO6k42U .flowchart-label text{text-anchor:middle;}#mermaid-svg-HKLjTU4sJEO6k42U .node .label{text-align:center;}#mermaid-svg-HKLjTU4sJEO6k42U .node.clickable{cursor:pointer;}#mermaid-svg-HKLjTU4sJEO6k42U .arrowheadPath{fill:#333333;}#mermaid-svg-HKLjTU4sJEO6k42U .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HKLjTU4sJEO6k42U .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HKLjTU4sJEO6k42U .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HKLjTU4sJEO6k42U .edgeLabel p{margin:0;padding:0;display:inline;}#mermaid-svg-HKLjTU4sJEO6k42U .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HKLjTU4sJEO6k42U .labelBkg{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HKLjTU4sJEO6k42U .node .cluster{fill:rgba(255, 255, 222, 0.5);stroke:rgba(170, 170, 51, 0.2);box-shadow:rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;stroke-width:1px;}#mermaid-svg-HKLjTU4sJEO6k42U .cluster text{fill:#333;}#mermaid-svg-HKLjTU4sJEO6k42U .cluster span,#mermaid-svg-HKLjTU4sJEO6k42U p{color:#333;}#mermaid-svg-HKLjTU4sJEO6k42U div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HKLjTU4sJEO6k42U .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HKLjTU4sJEO6k42U .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HKLjTU4sJEO6k42U .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HKLjTU4sJEO6k42U :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} V=2
P
X
CC (4 bits)
M
PT (7 bits)
Sequence Number (16 bits)
Timestamp (32 bits)
SSRC (32 bits)

三、 RTP 载荷:编码数据的"内容"

RTP 头部之后就是载荷(Payload),它承载着编码后的音视频数据。载荷的格式完全由 PT 字段指定的编码类型决定。

3.1 核心原则:第一个字节由载荷类型决定

RTP 协议本身不规定载荷第一个字节的格式,它由具体的编码格式(H.264、AAC、G.711 等)定义。

载荷类型(PT) 编码格式 第一个字节是什么? 格式
0 (PCMU) G.711 μ-law 直接是音频采样数据 无头,纯数据
8 (PCMA) G.711 A-law 直接是音频采样数据 无头,纯数据
26 (JPEG) JPEG JPEG 量化表头 RFC 2435
96 (动态) H.264 NAL 单元头 RFC 6184
97 (动态) AAC AU 头 RFC 3640

关键点:RTP 包头里的 PT 字段告诉接收端"载荷是什么编码",然后接收端才知道第一个字节怎么解析。就像快递单上的"物品类型"告诉你箱子里是书还是衣服,你才知道怎么拆箱。

3.2 最简单的音频载荷:G.711

G.711 是 VoIP 电话中最常见的音频编码。它的 RTP 载荷格式非常简单,直接将 PCM 音频数据放入 RTP 包中,没有任何额外的头部

复制代码
RTP 包头 (12 字节) | G.711 音频数据 (例如 160 字节)

示例:一个 20ms 的音频帧,采样率为 8000 Hz,载荷大小 = 8000 × 0.02 × 1 字节 = 160 字节。

3.3 其他常见编码的第一个字节

编码格式 PT 值 第一个字节格式 示例
G.711 μ-law 0 直接是 8 位 PCM 音频数据 FF(静音)
G.711 A-law 8 直接是 8 位 PCM 音频数据 D5
AAC 动态(96-127) AU 头(2 字节):[AU-size:13bits][AU-index:3bits] 00 20(32 字节)
JPEG 26 JPEG 头:[类型特定:8bits][分片偏移:24bits][类型:8bits][Q:8bits][宽:8bits][高:8bits] 00 00 00 00 01 5E 02 80 01 E0

载荷类型判断流程:
#mermaid-svg-7tWFFGxE3UQGOCZR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7tWFFGxE3UQGOCZR .error-icon{fill:#552222;}#mermaid-svg-7tWFFGxE3UQGOCZR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7tWFFGxE3UQGOCZR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7tWFFGxE3UQGOCZR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7tWFFGxE3UQGOCZR .marker.cross{stroke:#333333;}#mermaid-svg-7tWFFGxE3UQGOCZR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7tWFFGxE3UQGOCZR p{margin:0;}#mermaid-svg-7tWFFGxE3UQGOCZR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7tWFFGxE3UQGOCZR .cluster-label text{fill:#333;}#mermaid-svg-7tWFFGxE3UQGOCZR .cluster-label span{color:#333;}#mermaid-svg-7tWFFGxE3UQGOCZR .cluster-label span p{background-color:transparent;}#mermaid-svg-7tWFFGxE3UQGOCZR .label text,#mermaid-svg-7tWFFGxE3UQGOCZR span{fill:#333;color:#333;}#mermaid-svg-7tWFFGxE3UQGOCZR .node rect,#mermaid-svg-7tWFFGxE3UQGOCZR .node circle,#mermaid-svg-7tWFFGxE3UQGOCZR .node ellipse,#mermaid-svg-7tWFFGxE3UQGOCZR .node polygon,#mermaid-svg-7tWFFGxE3UQGOCZR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7tWFFGxE3UQGOCZR .rough-node .label text,#mermaid-svg-7tWFFGxE3UQGOCZR .node .label text,#mermaid-svg-7tWFFGxE3UQGOCZR .image-shape .label,#mermaid-svg-7tWFFGxE3UQGOCZR .icon-shape .label{text-anchor:middle;}#mermaid-svg-7tWFFGxE3UQGOCZR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7tWFFGxE3UQGOCZR .rough-node .label,#mermaid-svg-7tWFFGxE3UQGOCZR .node .label,#mermaid-svg-7tWFFGxE3UQGOCZR .image-shape .label,#mermaid-svg-7tWFFGxE3UQGOCZR .icon-shape .label{text-align:center;}#mermaid-svg-7tWFFGxE3UQGOCZR .node.clickable{cursor:pointer;}#mermaid-svg-7tWFFGxE3UQGOCZR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7tWFFGxE3UQGOCZR .arrowheadPath{fill:#333333;}#mermaid-svg-7tWFFGxE3UQGOCZR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7tWFFGxE3UQGOCZR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7tWFFGxE3UQGOCZR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7tWFFGxE3UQGOCZR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7tWFFGxE3UQGOCZR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7tWFFGxE3UQGOCZR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7tWFFGxE3UQGOCZR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7tWFFGxE3UQGOCZR .cluster text{fill:#333;}#mermaid-svg-7tWFFGxE3UQGOCZR .cluster span{color:#333;}#mermaid-svg-7tWFFGxE3UQGOCZR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7tWFFGxE3UQGOCZR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7tWFFGxE3UQGOCZR rect.text{fill:none;stroke-width:0;}#mermaid-svg-7tWFFGxE3UQGOCZR .icon-shape,#mermaid-svg-7tWFFGxE3UQGOCZR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7tWFFGxE3UQGOCZR .icon-shape p,#mermaid-svg-7tWFFGxE3UQGOCZR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7tWFFGxE3UQGOCZR .icon-shape .label rect,#mermaid-svg-7tWFFGxE3UQGOCZR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7tWFFGxE3UQGOCZR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7tWFFGxE3UQGOCZR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7tWFFGxE3UQGOCZR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 0 (PCMU)
8 (PCMA)
26 (JPEG)
96 (动态)
97 (动态)
其他
接收 RTP 包
解析 RTP 头部
读取 PT 字段
PT 值?
G.711 μ-law 解码
G.711 A-law 解码
按 RFC 2435 解析 JPEG
按 RFC 6184 解析 H.264
按 RFC 3640 解析 AAC
查询 SDP 中的 rtpmap 映射
输出 PCM 音频
输出 JPEG 图像
输出 H.264 NALU
输出 AAC 音频帧

四、H.264 基础概念

在深入 H.264 载荷格式之前,需要先了解一些基本概念。

4.1 H.264 编码原理

H.264 是一种视频压缩标准,它的核心思想是去除视频中的冗余信息

  • 空间冗余 :一帧图像中相邻像素通常很相似 → 使用帧内预测
  • 时间冗余 :连续帧之间通常只有少量变化 → 使用帧间预测
  • 视觉冗余 :人眼对某些细节不敏感 → 使用变换和量化
  • 编码冗余 :某些数据出现频率高 → 使用熵编码

4.2 帧类型(I帧、P帧、B帧)

帧类型 全称 说明 大小
I 帧 Intra-coded frame(帧内编码帧) 关键帧,完整编码,不依赖其他帧 最大
P 帧 Predicted frame(预测编码帧) 参考前面的 I 帧或 P 帧,只编码差异 中等
B 帧 Bidirectional predicted frame(双向预测编码帧) 参考前后帧,压缩率最高 最小

典型 GOP(Group of Pictures)结构I B B P B B P B B I ...

4.3 NAL 单元(Network Abstraction Layer)

H.264 编码器输出的数据被组织成 NAL 单元(Network Abstraction Layer Unit,简称 NALU),这是 H.264 数据在网络中传输的基本单位。

NALU 结构

复制代码
【NALU 格式】
+----------+------+---------+
| NAL 头   | 载荷  | 尾字节  |
| (1 字节) | (变长) | (可选)  |
+----------+------+---------+

NAL 头格式

复制代码
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

逐位拆解:

字段 含义
0 F (forbidden_zero_bit) 必须为 0,1 表示错误
1-2 NRI (nal_ref_idc) 优先级:00=不重要,11=最重要
3-7 Type (nal_unit_type) NAL 类型

常见 NAL 类型值:

Type 值 含义
1 非 IDR 帧(P 帧/B 帧)
5 IDR 帧(关键帧/I 帧)
7 SPS(序列参数集)
8 PPS(图像参数集)

4.4 起始码(Start Code)

在 H.264 裸流中,每个 NALU 前面通常有起始码:

  • 00 00 00 01(4 字节,最常用)
  • 00 00 01(3 字节,部分场景)

示例

复制代码
00 00 00 01 65 88 84 00 15 FF F8 7E 08 ...

其中 65 就是 NAL 头。

4.5 NAL 头解析示例

65(十六进制)解析:

复制代码
65 = 0x65 = 二进制 0110 0101
     │ │  └───── 00101 = Type = 5 → IDR 关键帧
     │ └──────── 11 = NRI = 3 → 最高优先级
     └────────── 0 = F = 0 → 正常

H.264 编码原理流程图:
#mermaid-svg-X4A4XaMa31tdSXZb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-X4A4XaMa31tdSXZb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-X4A4XaMa31tdSXZb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-X4A4XaMa31tdSXZb .error-icon{fill:#552222;}#mermaid-svg-X4A4XaMa31tdSXZb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-X4A4XaMa31tdSXZb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-X4A4XaMa31tdSXZb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-X4A4XaMa31tdSXZb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-X4A4XaMa31tdSXZb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-X4A4XaMa31tdSXZb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-X4A4XaMa31tdSXZb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-X4A4XaMa31tdSXZb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-X4A4XaMa31tdSXZb .marker.cross{stroke:#333333;}#mermaid-svg-X4A4XaMa31tdSXZb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-X4A4XaMa31tdSXZb p{margin:0;}#mermaid-svg-X4A4XaMa31tdSXZb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-X4A4XaMa31tdSXZb .cluster-label text{fill:#333;}#mermaid-svg-X4A4XaMa31tdSXZb .cluster-label span{color:#333;}#mermaid-svg-X4A4XaMa31tdSXZb .cluster-label span p{background-color:transparent;}#mermaid-svg-X4A4XaMa31tdSXZb .label text,#mermaid-svg-X4A4XaMa31tdSXZb span{fill:#333;color:#333;}#mermaid-svg-X4A4XaMa31tdSXZb .node rect,#mermaid-svg-X4A4XaMa31tdSXZb .node circle,#mermaid-svg-X4A4XaMa31tdSXZb .node ellipse,#mermaid-svg-X4A4XaMa31tdSXZb .node polygon,#mermaid-svg-X4A4XaMa31tdSXZb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-X4A4XaMa31tdSXZb .rough-node .label text,#mermaid-svg-X4A4XaMa31tdSXZb .node .label text,#mermaid-svg-X4A4XaMa31tdSXZb .image-shape .label,#mermaid-svg-X4A4XaMa31tdSXZb .icon-shape .label{text-anchor:middle;}#mermaid-svg-X4A4XaMa31tdSXZb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-X4A4XaMa31tdSXZb .rough-node .label,#mermaid-svg-X4A4XaMa31tdSXZb .node .label,#mermaid-svg-X4A4XaMa31tdSXZb .image-shape .label,#mermaid-svg-X4A4XaMa31tdSXZb .icon-shape .label{text-align:center;}#mermaid-svg-X4A4XaMa31tdSXZb .node.clickable{cursor:pointer;}#mermaid-svg-X4A4XaMa31tdSXZb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-X4A4XaMa31tdSXZb .arrowheadPath{fill:#333333;}#mermaid-svg-X4A4XaMa31tdSXZb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-X4A4XaMa31tdSXZb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-X4A4XaMa31tdSXZb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X4A4XaMa31tdSXZb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-X4A4XaMa31tdSXZb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X4A4XaMa31tdSXZb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-X4A4XaMa31tdSXZb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-X4A4XaMa31tdSXZb .cluster text{fill:#333;}#mermaid-svg-X4A4XaMa31tdSXZb .cluster span{color:#333;}#mermaid-svg-X4A4XaMa31tdSXZb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-X4A4XaMa31tdSXZb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-X4A4XaMa31tdSXZb rect.text{fill:none;stroke-width:0;}#mermaid-svg-X4A4XaMa31tdSXZb .icon-shape,#mermaid-svg-X4A4XaMa31tdSXZb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X4A4XaMa31tdSXZb .icon-shape p,#mermaid-svg-X4A4XaMa31tdSXZb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-X4A4XaMa31tdSXZb .icon-shape .label rect,#mermaid-svg-X4A4XaMa31tdSXZb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X4A4XaMa31tdSXZb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-X4A4XaMa31tdSXZb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-X4A4XaMa31tdSXZb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始视频帧 (YUV)
帧内预测
帧间预测
变换
量化
熵编码
NAL 单元输出
解码参考帧

NAL 单元结构层次图:
#mermaid-svg-h3uHtgFqYRGpsEgo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h3uHtgFqYRGpsEgo .error-icon{fill:#552222;}#mermaid-svg-h3uHtgFqYRGpsEgo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h3uHtgFqYRGpsEgo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h3uHtgFqYRGpsEgo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h3uHtgFqYRGpsEgo .marker.cross{stroke:#333333;}#mermaid-svg-h3uHtgFqYRGpsEgo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h3uHtgFqYRGpsEgo p{margin:0;}#mermaid-svg-h3uHtgFqYRGpsEgo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-h3uHtgFqYRGpsEgo .cluster-label text{fill:#333;}#mermaid-svg-h3uHtgFqYRGpsEgo .cluster-label span{color:#333;}#mermaid-svg-h3uHtgFqYRGpsEgo .cluster-label span p{background-color:transparent;}#mermaid-svg-h3uHtgFqYRGpsEgo .label text,#mermaid-svg-h3uHtgFqYRGpsEgo span{fill:#333;color:#333;}#mermaid-svg-h3uHtgFqYRGpsEgo .node rect,#mermaid-svg-h3uHtgFqYRGpsEgo .node circle,#mermaid-svg-h3uHtgFqYRGpsEgo .node ellipse,#mermaid-svg-h3uHtgFqYRGpsEgo .node polygon,#mermaid-svg-h3uHtgFqYRGpsEgo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h3uHtgFqYRGpsEgo .rough-node .label text,#mermaid-svg-h3uHtgFqYRGpsEgo .node .label text,#mermaid-svg-h3uHtgFqYRGpsEgo .image-shape .label,#mermaid-svg-h3uHtgFqYRGpsEgo .icon-shape .label{text-anchor:middle;}#mermaid-svg-h3uHtgFqYRGpsEgo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-h3uHtgFqYRGpsEgo .rough-node .label,#mermaid-svg-h3uHtgFqYRGpsEgo .node .label,#mermaid-svg-h3uHtgFqYRGpsEgo .image-shape .label,#mermaid-svg-h3uHtgFqYRGpsEgo .icon-shape .label{text-align:center;}#mermaid-svg-h3uHtgFqYRGpsEgo .node.clickable{cursor:pointer;}#mermaid-svg-h3uHtgFqYRGpsEgo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-h3uHtgFqYRGpsEgo .arrowheadPath{fill:#333333;}#mermaid-svg-h3uHtgFqYRGpsEgo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-h3uHtgFqYRGpsEgo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-h3uHtgFqYRGpsEgo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h3uHtgFqYRGpsEgo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-h3uHtgFqYRGpsEgo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h3uHtgFqYRGpsEgo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-h3uHtgFqYRGpsEgo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-h3uHtgFqYRGpsEgo .cluster text{fill:#333;}#mermaid-svg-h3uHtgFqYRGpsEgo .cluster span{color:#333;}#mermaid-svg-h3uHtgFqYRGpsEgo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-h3uHtgFqYRGpsEgo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-h3uHtgFqYRGpsEgo rect.text{fill:none;stroke-width:0;}#mermaid-svg-h3uHtgFqYRGpsEgo .icon-shape,#mermaid-svg-h3uHtgFqYRGpsEgo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h3uHtgFqYRGpsEgo .icon-shape p,#mermaid-svg-h3uHtgFqYRGpsEgo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-h3uHtgFqYRGpsEgo .icon-shape .label rect,#mermaid-svg-h3uHtgFqYRGpsEgo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h3uHtgFqYRGpsEgo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-h3uHtgFqYRGpsEgo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-h3uHtgFqYRGpsEgo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} NAL 头结构
F (1 bit)
NRI (2 bits)
Type (5 bits)
NALU (Network Abstraction Layer Unit)
NAL 头 (1 字节)
NAL 载荷 (变长)
尾字节 (可选)

五、H.264 RTP 载荷格式深度剖析

H.264 是目前应用最广泛的视频编码标准。由于视频帧大小差异巨大,H.264 的 RTP 载荷格式最为复杂,也是本文的重点。

5.1 RTP 载荷第一个字节 = NALU 头

H.264 的 RTP 载荷第一个字节格式与 NALU 头完全一致,即 F、NRI、Type 三个字段。

5.2 三种基本载荷结构

接收者可以通过 RTP 载荷的第一个字节后 5 位(Type)识别载荷结构:

Type 值 结构类型 说明
1-23 单个 NAL 单元包 荷载中只包含一个完整的 NAL 单元
24-27 聚合包(STAP-A/B, MTAP16/24) 聚合多个 NAL 单元到单个 RTP 荷载中
28-29 分片包(FU-A/B) 用于分片单个 NAL 单元到多个 RTP 包

三种载荷结构决策树:
#mermaid-svg-ySCTwR2NE4RkcLB6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ySCTwR2NE4RkcLB6 .error-icon{fill:#552222;}#mermaid-svg-ySCTwR2NE4RkcLB6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ySCTwR2NE4RkcLB6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .marker.cross{stroke:#333333;}#mermaid-svg-ySCTwR2NE4RkcLB6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ySCTwR2NE4RkcLB6 p{margin:0;}#mermaid-svg-ySCTwR2NE4RkcLB6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .cluster-label text{fill:#333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .cluster-label span{color:#333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .cluster-label span p{background-color:transparent;}#mermaid-svg-ySCTwR2NE4RkcLB6 .label text,#mermaid-svg-ySCTwR2NE4RkcLB6 span{fill:#333;color:#333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .node rect,#mermaid-svg-ySCTwR2NE4RkcLB6 .node circle,#mermaid-svg-ySCTwR2NE4RkcLB6 .node ellipse,#mermaid-svg-ySCTwR2NE4RkcLB6 .node polygon,#mermaid-svg-ySCTwR2NE4RkcLB6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ySCTwR2NE4RkcLB6 .rough-node .label text,#mermaid-svg-ySCTwR2NE4RkcLB6 .node .label text,#mermaid-svg-ySCTwR2NE4RkcLB6 .image-shape .label,#mermaid-svg-ySCTwR2NE4RkcLB6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-ySCTwR2NE4RkcLB6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ySCTwR2NE4RkcLB6 .rough-node .label,#mermaid-svg-ySCTwR2NE4RkcLB6 .node .label,#mermaid-svg-ySCTwR2NE4RkcLB6 .image-shape .label,#mermaid-svg-ySCTwR2NE4RkcLB6 .icon-shape .label{text-align:center;}#mermaid-svg-ySCTwR2NE4RkcLB6 .node.clickable{cursor:pointer;}#mermaid-svg-ySCTwR2NE4RkcLB6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .arrowheadPath{fill:#333333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ySCTwR2NE4RkcLB6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySCTwR2NE4RkcLB6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ySCTwR2NE4RkcLB6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySCTwR2NE4RkcLB6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ySCTwR2NE4RkcLB6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ySCTwR2NE4RkcLB6 .cluster text{fill:#333;}#mermaid-svg-ySCTwR2NE4RkcLB6 .cluster span{color:#333;}#mermaid-svg-ySCTwR2NE4RkcLB6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ySCTwR2NE4RkcLB6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ySCTwR2NE4RkcLB6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-ySCTwR2NE4RkcLB6 .icon-shape,#mermaid-svg-ySCTwR2NE4RkcLB6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySCTwR2NE4RkcLB6 .icon-shape p,#mermaid-svg-ySCTwR2NE4RkcLB6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ySCTwR2NE4RkcLB6 .icon-shape .label rect,#mermaid-svg-ySCTwR2NE4RkcLB6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySCTwR2NE4RkcLB6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ySCTwR2NE4RkcLB6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ySCTwR2NE4RkcLB6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1-23
24-27
28-29
接收 H.264 RTP 包
读取载荷第一个字节的 Type 字段 (后5位)
Type 值?
单个 NAL 单元包
聚合包 STAP-A/B, MTAP16/24
分片包 FU-A/B
直接提取完整 NALU
解析多个 NALU 长度前缀
拆分出多个 NALU
收集分片 + 按 S/E 位组装
还原 NAL 头
组装完整 NALU
交给解码器

5.3 单个 NAL 单元包(Type 1-23)

当 NAL 单元大小小于 MTU 时,直接使用单个 NAL 单元包。载荷第一个字节就是 NAL 头。

使用场景:SPS、PPS、SEI 等小 NAL 单元,或者低分辨率视频帧。

5.4 聚合包 STAP-A(Type 24)

当有多个小 NAL 单元(如 SPS + PPS)需要合并到一个 RTP 包时,使用聚合包。

复制代码
【第一个字节:STAP-A 头】
 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F| NRI |  24   |  ← Type 固定为 24(STAP-A)
+-+-+-+-+-+-+-+-+

注意:实际应用中,聚合包使用较少,因为 SPS、PPS 等通常单独发送。

5.5 分片包 FU-A(Type 28)------ 重点

当 NALU 的长度超过 MTU(通常为 1460 字节)时,就必须对 NALU 单元进行分片封包,称为 Fragmentation Units (FUs)。

5.5.1 为什么需要分片?

MTU(最大传输单元) = 1500 字节(以太网)

  • IP 包头 = 20 字节
  • UDP 包头 = 8 字节
  • RTP 包头 = 12 字节
  • 剩余给 RTP 载荷的空间 = 1500 - 20 - 8 - 12 = 1460 字节

如果 NALU 大于 1460 字节,一个 RTP 包装不下,必须拆成多个 FU-A 分片。

5.5.2 FU-A 格式

FU-A 由 1 字节的 FU indicator、1 字节的 FU header 和分片单元荷载组成。

复制代码
+---------------+ +---------------+ +------------------+
| FU indicator  | |  FU header    | |  FU payload      |
|  (1 字节)      | |  (1 字节)     | |  (分片数据)      |
+---------------+ +---------------+ +------------------+
5.5.3 FU indicator 格式
复制代码
 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F| NRI |  28   |  ← Type 固定为 28(FU-A)
+-+-+-+-+-+-+-+-+
5.5.4 FU header 格式
复制代码
 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|S|E|R|  Type  |  ← Type 是原始 NAL 类型
+-+-+-+-+-+-+-+-+

FU header 字段说明:

字段 含义
S 开始位 1 表示分片 NAL 单元的开始
E 结束位 1 表示分片 NAL 单元的结束
R 保留位 必须为 0,接收者必须忽略
Type NAL 类型 原始 NAL Header 中的 Type
5.5.5 打包与解包规则

打包时,原始的 NAL 头的前三位为 FU indicator 的前三位,原始的 NAL 头的后五位(Type)为 FU header 的后五位(Type)。

解包时,取 FU indicator 的前三位和 FU Header 的后五位,还原出原始 NAL 头。

示例分析:

复制代码
原始 NAL 头 = 0110 0101 (0x65)
              │ │  └────── Type = 5 (IDR 帧)
              │ └───────── NRI = 3
              └─────────── F = 0

打包后:
FU indicator = 0111 1100 (0x7C)
               │ │  └────── Type = 28 (FU-A)
               │ └───────── NRI = 3
               └─────────── F = 0

FU header = 1000 0101 (0x85)
            │ │ │ └────── Type = 5 (原始 NAL 类型)
            │ │ └──────── R = 0
            │ └────────── E = 0
            └──────────── S = 1 (第一个分片)
5.5.6 关键规则
  • 相同 NALU 的分片必须使用递增的 RTP 序号连续顺序发送,中间不能插入其他 RTP 包。
  • 分片不可以嵌套,即一个 FU 不可以包含另一个 FU。
  • STAPs、MTAPs 不可以被分片。
  • NAL 单元必须按照 RTP 顺序号的顺序装配。

完整流程图:NALU 分片 → 打包 → 传输 → 重组
#mermaid-svg-OjTt6LdMTSrotxLr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OjTt6LdMTSrotxLr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OjTt6LdMTSrotxLr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OjTt6LdMTSrotxLr .error-icon{fill:#552222;}#mermaid-svg-OjTt6LdMTSrotxLr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OjTt6LdMTSrotxLr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OjTt6LdMTSrotxLr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OjTt6LdMTSrotxLr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OjTt6LdMTSrotxLr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OjTt6LdMTSrotxLr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OjTt6LdMTSrotxLr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OjTt6LdMTSrotxLr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OjTt6LdMTSrotxLr .marker.cross{stroke:#333333;}#mermaid-svg-OjTt6LdMTSrotxLr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OjTt6LdMTSrotxLr p{margin:0;}#mermaid-svg-OjTt6LdMTSrotxLr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OjTt6LdMTSrotxLr .cluster-label text{fill:#333;}#mermaid-svg-OjTt6LdMTSrotxLr .cluster-label span{color:#333;}#mermaid-svg-OjTt6LdMTSrotxLr .cluster-label span p{background-color:transparent;}#mermaid-svg-OjTt6LdMTSrotxLr .label text,#mermaid-svg-OjTt6LdMTSrotxLr span{fill:#333;color:#333;}#mermaid-svg-OjTt6LdMTSrotxLr .node rect,#mermaid-svg-OjTt6LdMTSrotxLr .node circle,#mermaid-svg-OjTt6LdMTSrotxLr .node ellipse,#mermaid-svg-OjTt6LdMTSrotxLr .node polygon,#mermaid-svg-OjTt6LdMTSrotxLr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OjTt6LdMTSrotxLr .rough-node .label text,#mermaid-svg-OjTt6LdMTSrotxLr .node .label text,#mermaid-svg-OjTt6LdMTSrotxLr .image-shape .label,#mermaid-svg-OjTt6LdMTSrotxLr .icon-shape .label{text-anchor:middle;}#mermaid-svg-OjTt6LdMTSrotxLr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OjTt6LdMTSrotxLr .rough-node .label,#mermaid-svg-OjTt6LdMTSrotxLr .node .label,#mermaid-svg-OjTt6LdMTSrotxLr .image-shape .label,#mermaid-svg-OjTt6LdMTSrotxLr .icon-shape .label{text-align:center;}#mermaid-svg-OjTt6LdMTSrotxLr .node.clickable{cursor:pointer;}#mermaid-svg-OjTt6LdMTSrotxLr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OjTt6LdMTSrotxLr .arrowheadPath{fill:#333333;}#mermaid-svg-OjTt6LdMTSrotxLr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OjTt6LdMTSrotxLr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OjTt6LdMTSrotxLr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OjTt6LdMTSrotxLr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OjTt6LdMTSrotxLr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OjTt6LdMTSrotxLr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OjTt6LdMTSrotxLr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OjTt6LdMTSrotxLr .cluster text{fill:#333;}#mermaid-svg-OjTt6LdMTSrotxLr .cluster span{color:#333;}#mermaid-svg-OjTt6LdMTSrotxLr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OjTt6LdMTSrotxLr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OjTt6LdMTSrotxLr rect.text{fill:none;stroke-width:0;}#mermaid-svg-OjTt6LdMTSrotxLr .icon-shape,#mermaid-svg-OjTt6LdMTSrotxLr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OjTt6LdMTSrotxLr .icon-shape p,#mermaid-svg-OjTt6LdMTSrotxLr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OjTt6LdMTSrotxLr .icon-shape .label rect,#mermaid-svg-OjTt6LdMTSrotxLr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OjTt6LdMTSrotxLr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OjTt6LdMTSrotxLr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OjTt6LdMTSrotxLr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ≤ 1460 字节
> 1460 字节
Type 1-23
Type 28
H.264 编码器输出 NALU
NALU 大小 > MTU (1460 字节)?
单个 NAL 单元包 (Type 1-23)
FU-A 分片 (Type 28)
FU-A Start (S=1, E=0)
FU-A Middle (S=0, E=0)
FU-A End (S=0, E=1)
封装 RTP 包 (RTP头 + FU indicator + FU header + payload)
UDP/IP/以太网封装后网络传输
接收端解析 RTP 包
载荷 Type 字段?
直接提取 NALU
收集同一 NALU 全部分片
按序列号排序 + 根据 S/E 位组装
还原 NAL 头 (FU indicator前3位 + FU header后5位)
组装完整 NALU
交给 H.264 解码器

六、RTCP 协议

6.1 什么是 RTCP?

RTCP(RTP Control Protocol,RTP 控制协议) 是 RTP 的姐妹协议,与 RTP 配合使用,负责传输控制信息。它同样运行在 UDP 之上,使用 RTP 端口号 + 1 的端口。

6.2 RTCP 的主要功能

功能 说明
QoS 反馈 报告丢包率、抖动、延迟等网络质量信息
同步 通过 RTCP 的 NTP 时间戳和 RTP 时间戳关联,实现音视频同步
标识 通过 CNAME(规范名)关联同一参与者的多个媒体流
会话控制 参与者加入/离开通知,会话规模估算

6.3 RTCP 包类型

类型 缩写 说明
200 SR(Sender Report) 发送者报告,包含发送统计和 NTP 时间戳
201 RR(Receiver Report) 接收者报告,包含接收质量反馈
202 SDES(Source Description) 源描述,包含 CNAME 等
203 BYE 离开会话通知
204 APP 应用自定义

6.4 RTCP 发送间隔

RTCP 不是每帧都发送,而是周期性发送(通常 5 秒左右),且带宽占用控制在会话带宽的 5% 以内,避免占用过多带宽。


RTCP 与 RTP 交互时序图:
接收端 发送端 接收端 发送端 #mermaid-svg-TIQiR7aX6xzI2mYj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TIQiR7aX6xzI2mYj .error-icon{fill:#552222;}#mermaid-svg-TIQiR7aX6xzI2mYj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TIQiR7aX6xzI2mYj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TIQiR7aX6xzI2mYj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TIQiR7aX6xzI2mYj .marker.cross{stroke:#333333;}#mermaid-svg-TIQiR7aX6xzI2mYj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TIQiR7aX6xzI2mYj p{margin:0;}#mermaid-svg-TIQiR7aX6xzI2mYj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TIQiR7aX6xzI2mYj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-TIQiR7aX6xzI2mYj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TIQiR7aX6xzI2mYj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-TIQiR7aX6xzI2mYj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-TIQiR7aX6xzI2mYj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-TIQiR7aX6xzI2mYj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-TIQiR7aX6xzI2mYj .sequenceNumber{fill:white;}#mermaid-svg-TIQiR7aX6xzI2mYj #sequencenumber{fill:#333;}#mermaid-svg-TIQiR7aX6xzI2mYj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-TIQiR7aX6xzI2mYj .messageText{fill:#333;stroke:none;}#mermaid-svg-TIQiR7aX6xzI2mYj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TIQiR7aX6xzI2mYj .labelText,#mermaid-svg-TIQiR7aX6xzI2mYj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-TIQiR7aX6xzI2mYj .loopText,#mermaid-svg-TIQiR7aX6xzI2mYj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-TIQiR7aX6xzI2mYj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TIQiR7aX6xzI2mYj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TIQiR7aX6xzI2mYj .noteText,#mermaid-svg-TIQiR7aX6xzI2mYj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-TIQiR7aX6xzI2mYj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TIQiR7aX6xzI2mYj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TIQiR7aX6xzI2mYj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TIQiR7aX6xzI2mYj .actorPopupMenu{position:absolute;}#mermaid-svg-TIQiR7aX6xzI2mYj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-TIQiR7aX6xzI2mYj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TIQiR7aX6xzI2mYj .actor-man circle,#mermaid-svg-TIQiR7aX6xzI2mYj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-TIQiR7aX6xzI2mYj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RTP 数据流传输 周期性 RTCP 报告 (约 5s) 基于 RTCP 反馈调整 会话结束 RTP 包 (Seq=1, 音频)RTP 包 (Seq=2, 视频)RTP 包 (Seq=3, 音频)RTP 包 (Seq=4, 视频)RTCP SR (发送者报告: 包计数+NTP时间戳)RTCP RR (接收者报告: 丢包率+抖动)RTCP SDES (CNAME 标识)根据丢包率调整码率根据抖动调整缓冲区RTCP BYE

七、RTSP 信令交互流程

7.1 什么是 RTSP?

RTSP(Real Time Streaming Protocol,实时流协议) 是一个应用层协议,用于控制流媒体服务器。它负责建立和控制媒体会话,但不传输媒体数据本身(媒体数据通过 RTP 传输)。

7.2 RTSP 与 RTP 的关系

协议 作用 类比
RTSP 控制流媒体会话(播放、暂停、停止) 遥控器
RTP 传输媒体数据 电视信号
RTCP 传输控制信息(质量反馈) 信号质量报告

7.3 RTSP 标准交互流程

复制代码
客户端(播放器)                    服务器(摄像头)
    |                                    |
    |--- OPTIONS ----------------------->|  1. 查询服务器能力
    |<-- 200 OK ------------------------|     (支持的方法)
    |                                    |
    |--- DESCRIBE ---------------------->|  2. 请求媒体描述
    |<-- 200 OK (SDP) ------------------|     (返回 SDP 信息)
    |                                    |
    |--- SETUP ------------------------->|  3. 建立传输通道
    |<-- 200 OK ------------------------|     (协商端口、协议)
    |                                    |
    |--- PLAY -------------------------->|  4. 开始播放
    |<-- 200 OK ------------------------|     (开始传输 RTP 数据)
    |                                    |
    |   <<<< RTP 数据流开始传输 >>>>     |
    |   <<<< RTCP 控制信息交换 >>>>     |
    |                                    |
    |--- TEARDOWN ---------------------->|  5. 结束会话
    |<-- 200 OK ------------------------|     (断开连接)

7.4 SDP 示例

复制代码
v=0
o=- 0 0 IN IP4 192.168.1.1
s=Live Stream
c=IN IP4 0.0.0.0
t=0 0
m=video 8081 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42001F

关键字段解析:

字段 含义
m=video 8081 RTP/AVP 96 视频流,端口 8081,RTP/AVP 协议,载荷类型 96
a=rtpmap:96 H264/90000 PT=96 对应 H.264,时钟频率 90000 Hz
a=fmtp:96 packetization-mode=1 分片模式为 1(支持 FU-A)

九、实战:完整网络传输帧格式

设定场景:摄像头输出 1080p H.264 视频,通过 RTSP 单播推送到 Web 预览。一个 IDR 关键帧大小为 50KB,需要拆成 35 个 FU-A 分片包。

8.1 第一个分片(FU-A Start)

复制代码
【RTP 包 1】
80 60 00 01 00 00 00 00 12 34 56 78 7C 85 88 84 00 15 FF F8 ... (总共 1472 字节)

逐字节解析:

字节 十六进制 字段 含义
0 80 V=2, P=0, X=0, CC=0 版本 2,无填充,无扩展
1 60 M=0, PT=96 非标记,动态载荷类型 96
2-3 00 01 序列号 = 1 第一个包
4-7 00 00 00 00 时间戳 = 0 起始时刻
8-11 12 34 56 78 SSRC = 0x12345678 流唯一标识
12 7C FU indicator Type=28 (FU-A), NRI=3
13 85 FU header S=1, E=0, R=0, Type=5 (IDR)
14-1471 88 84 00 15 FF F8 ... FU payload 分片数据(1458 字节)

8.2 中间分片(FU-A Middle)

复制代码
【RTP 包 2】
80 60 00 02 00 00 00 00 12 34 56 78 7C 05 ... (1472 字节)

关键字段:

字节 十六进制 字段 含义
2-3 00 02 序列号 = 2 递增
12 7C FU indicator Type=28, NRI=3
13 05 FU header S=0, E=0, R=0, Type=5

8.3 最后一个分片(FU-A End)

复制代码
【RTP 包 35】
80 E0 00 23 00 00 00 00 12 34 56 78 7C 45 ... (约 512 字节)

关键字段:

字节 十六进制 字段 含义
1 E0 M=1, PT=96 M=1 标记帧结束
2-3 00 23 序列号 = 35 最后一个包
12 7C FU indicator Type=28, NRI=3
13 45 FU header S=0, E=1, R=0, Type=5

8.4 UDP 封装

复制代码
【UDP 数据报】
+-------------------+
|  UDP 包头 (8 字节)  |
+-------------------+
|  RTP 包 (1472 字节) |
+-------------------+

UDP 包头格式:

字节 十六进制 字段 含义
0-1 1F 90 源端口 = 8080 推流端端口
2-3 1F 91 目的端口 = 8081 预览端端口
4-5 05 C0 长度 = 1472 UDP 头 + 数据
6-7 00 00 校验和 = 0 简化(实际应计算)

8.5 IP 封装

复制代码
【IP 数据包】
+-------------------+
|  IP 包头 (20 字节)  |
+-------------------+
|  UDP 数据报 (1480 字节) |
+-------------------+

IP 包头格式:

字节 十六进制 字段 含义
0 45 版本=4, 头长=5 IPv4, 20 字节头
1 00 服务类型 默认
2-3 05 DC 总长度 = 1500 IP 头 + 数据
4-5 00 01 标识 = 1 分片标识
6-7 40 00 标志 + 片偏移 不分片
8 40 TTL = 64 生存时间
9 11 协议 = 17 UDP
10-11 00 00 头部校验和 简化
12-15 C0 A8 01 01 源 IP = 192.168.1.1 推流端
16-19 C0 A8 01 02 目的 IP = 192.168.1.2 预览端

8.6 以太网帧封装

复制代码
【以太网帧】
+-------------------+
|  以太网头 (14 字节) |
+-------------------+
|  IP 包 (1500 字节)  |
+-------------------+
|  FCS (4 字节)      |
+-------------------+

以太网帧头格式:

字节 十六进制 字段 含义
0-5 00 11 22 33 44 55 目的 MAC 预览端 MAC
6-11 AA BB CC DD EE FF 源 MAC 推流端 MAC
12-13 08 00 类型 = 0x0800 IPv4

8.7 完整数据帧结构总结

复制代码
【从摄像头到 Web 预览的完整数据帧】

以太网帧 (1518 字节)
├── 以太网头 (14 字节)
│   ├── 目的 MAC (6 字节)
│   ├── 源 MAC (6 字节)
│   └── 类型 (2 字节) = 0x0800
├── IP 包 (1500 字节)
│   ├── IP 头 (20 字节)
│   │   ├── 版本/头长/服务类型 (2 字节)
│   │   ├── 总长度 (2 字节) = 1500
│   │   ├── 标识/标志/片偏移 (4 字节)
│   │   ├── TTL/协议 (2 字节) = 64/17
│   │   ├── 校验和 (2 字节)
│   │   ├── 源 IP (4 字节) = 192.168.1.1
│   │   └── 目的 IP (4 字节) = 192.168.1.2
│   └── UDP 数据报 (1480 字节)
│       ├── UDP 头 (8 字节)
│       │   ├── 源端口 (2 字节) = 8080
│       │   ├── 目的端口 (2 字节) = 8081
│       │   ├── 长度 (2 字节) = 1480
│       │   └── 校验和 (2 字节)
│       └── RTP 包 (1472 字节)
│           ├── RTP 头 (12 字节)
│           │   ├── V/P/X/CC (1 字节) = 0x80
│           │   ├── M/PT (1 字节) = 0x60
│           │   ├── 序列号 (2 字节) = 0x0001
│           │   ├── 时间戳 (4 字节) = 0x00000000
│           │   └── SSRC (4 字节) = 0x12345678
│           └── RTP 载荷 (1460 字节)
│               ├── FU indicator (1 字节) = 0x7C
│               ├── FU header (1 字节) = 0x85
│               └── FU payload (1458 字节) = 分片数据
└── FCS (4 字节) = 帧校验序列

十、核心规则总结查

在 RTP/H.264 实际传输中,经常会遇到花屏、音画不同步、丢包等问题。下面基于已讲解的原理,梳理常见问题的排查思路与解决方案。

问题现象 原因分析 排查方法 解决方案
花屏/绿屏/马赛克 1. 关键帧丢失或损坏,导致解码器无法正确参考 2. FU-A 分片组装错误(S/E 位处理不当、序列号乱序) 3. NAL 头还原错误(F/NRI/Type 拼接错误) 1. 抓包检查 RTP 序列号连续性,确认是否丢包 2. 检查 FU indicator/header 构造是否正确 3. 检查接收端是否丢弃了不完整的分片组 1. 确保 I 帧(IDR)完整传输,必要时可降低分辨率/码率减少 I 帧大小 2. 严格按 FU indicator 前 3 位 + FU header 后 5 位还原 NAL 头 3. 对未完整接收的分片组直接丢弃,等待下一个 I 帧 4. 启用 RTCP 反馈,根据丢包率调整 FEC/重传策略
音画不同步 1. RTP 时间戳与 NTP 时间戳映射错误 2. 音频/视频使用不同的 SSRC 但未通过 RTCP 关联 3. 网络抖动导致音频缓冲与视频缓冲不一致 1. 检查音频和视频的 RTP 时间戳是否基于同一时钟源 2. 检查 RTCP SR 中的 NTP 时间戳与 RTP 时间戳的对应关系 3. 检查接收端抖动缓冲(Jitter Buffer)的大小设置 1. 在 RTCP SR 中正确填写 NTP 时间戳,确保接收端可关联音视频流 2. 通过 SDES 的 CNAME 将同一设备的音频/视频 SSRC 关联 3. 调整接收端缓冲策略,视频可适当增加缓冲深度
持续丢包/画面卡顿 1. 网络带宽不足,UDP 无拥塞控制 2. MTU 设置不当导致 IP 分片 3. 发送端码率过高,超过接收端处理能力 1. 通过 RTCP RR 查看丢包率、抖动 2. 检查 RTP 序列号缺口,统计丢包间隔 3. 检查路由器/交换机是否丢包,排除链路问题 1. 降低编码码率或分辨率,匹配实际带宽 2. 确保 RTP 载荷不超过 1460 字节,避免 IP 层再次分片 3. 启用 RTCP 反馈,发送端根据丢包率动态调整码率(REMB/TMMBR)
FU-A 分片无法完整组装 1. 分片包未按序列号连续发送,中间插入其他 NALU 的包 2. 接收端未正确识别 S=1 的开始包和 E=1 的结束包 3. 某个分片丢失导致整组丢弃 1. 抓包确认相同 NALU 的分片序列号是否连续 2. 检查 FU header 的 S/E 位是否正确设置 3. 检查接收端是否按序列号递增顺序收集分片 1. 发送端必须保证同一 NALU 的分片序列号连续,中间不能插入其他包 2. 接收端维护一个缓冲区,按 S 位置开始收集,E 位置结束并组装 3. 超时未收到结束包则丢弃整个分片组,等待下一个 I 帧

九、核心规则总结

9.1 打包规则

NAL 单元大小 ≤ MTU(1460 字节)→ 单个 NAL 单元包(Type=1-23)

NAL 单元大小 > MTU → 分片包 FU-A(Type=28)

这是 H.264 RTP 打包的核心逻辑,其他聚合包几乎不用。

9.2 分片规则

  • 相同 NALU 的分片必须使用递增的 RTP 序号连续顺序发送,中间不能插入其他 RTP 包。
  • 分片不可以嵌套,即一个 FU 不可以包含另一个 FU。
  • STAPs、MTAPs 不可以被分片。
  • NAL 单元必须按照 RTP 顺序号的顺序装配。

9.3 解包规则

接收端必须先读 RTP 头部的 PT 字段,确定载荷类型,然后才能解析第一个字节的格式。对于 FU-A 分片,需要根据 FU indicator 和 FU header 还原原始 NAL 头,并根据 S/E 位组装完整 NALU。

9.4 协议栈总结

协议 作用
应用层 RTSP 会话控制(播放、暂停、停止)
传输层 RTP 传输媒体数据
传输层 RTCP 传输控制信息(QoS 反馈)
传输层 UDP 无连接传输
网络层 IP 路由寻址
数据链路层 以太网 局域网帧传输

十一、完整流程回顾

10.1 整体架构

复制代码
摄像头 → H.264 编码器 → NAL 单元 → RTP 打包 → UDP → IP → 以太网帧 → 网络 → Web 浏览器

10.2 发送端流程

  1. 摄像头采集原始视频帧(YUV 格式)
  2. H.264 编码器压缩编码,输出 NAL 单元
  3. 根据 MTU(1460 字节)判断是否分片
  4. 如果 NALU ≤ 1460 字节 → 单个 NAL 单元包
  5. 如果 NALU > 1460 字节 → FU-A 分片包
  6. 打包成 RTP 包(12 字节 RTP 头 + 载荷)
  7. 封装 UDP 数据报(8 字节 UDP 头 + RTP 包)
  8. 封装 IP 数据包(20 字节 IP 头 + UDP 数据报)
  9. 封装以太网帧(14 字节以太网头 + IP 包 + 4 字节 FCS)
  10. 发送到网络

10.3 接收端流程

  1. 接收以太网帧 → 剥离以太网头
  2. 接收 IP 包 → 剥离 IP 头
  3. 接收 UDP 数据报 → 剥离 UDP 头
  4. 接收 RTP 包 → 解析 RTP 头
  5. 读取 PT 字段,确定载荷类型为 H.264
  6. 读取第一个字节后 5 位(Type):
    • Type=1-23 → 单个 NAL 单元,直接取出
    • Type=28 → FU-A 分片,根据 S/E 位组装
  7. 还原 NAL 头(FU indicator 前 3 位 + FU header 后 5 位)
  8. 组装完整 NALU
  9. 交给 H.264 解码器
  10. 解码得到 YUV 帧
  11. 渲染到 Canvas/Video 元素

10.4 RTSP 信令流程

  1. OPTIONS:查询服务器能力
  2. DESCRIBE:请求媒体描述(SDP)
  3. SETUP:建立传输通道(协商端口、协议)
  4. PLAY:开始播放(开始传输 RTP 数据)
  5. RTP 数据流传输
  6. RTCP 控制信息交换
  7. TEARDOWN:结束会话
相关推荐
芯岭技术郦2 小时前
OM6625A芯片 Timer 定时器计数模式使用说明
stm32·单片机·嵌入式硬件
Dr.kangder3 小时前
嵌入式面试总结(八)——大小端
嵌入式硬件·面试·职场和发展·架构·嵌入式
zlinear数据采集卡5 小时前
D223的SRAM硬件记录仪:外部触发与瞬态波形捕获方案
arm开发·嵌入式硬件·fpga开发·开源
zcmodeltech5 小时前
双碳沙盘模型多系统协同控制系统设计:基于STM32与Modbus RTU的碳捕集-零碳园区-综合能源全场景联动方案
stm32·嵌入式硬件·能源·嵌入式实时数据库·多分类
LCG米6 小时前
STM32零基础入门教程:从环境搭建到实战项目(点亮LED + 串口打印 + 呼吸灯)
stm32·单片机·嵌入式硬件
云泽8086 小时前
深入浅出 STM32(十一):从寄存器映射到 HAL 库位操作源码详解
stm32·单片机·嵌入式硬件
哥不想学算法6 小时前
STM32零基础教程(六):ADC、I2C、SPI与DMA
stm32·单片机·嵌入式硬件
szxinmai主板定制专家6 小时前
基于 RK3588 + Xilinx Kintex-7 FPGA 异构工业主控板设计方案
arm开发·人工智能·嵌入式硬件·fpga开发·zynq
我是一棵无人问荆的小草6 小时前
LM393DT比较器输出
单片机·嵌入式硬件