本文档结合FFmpeg源代码,详细解释RTSP推流时如何将媒体数据组装成RTP包的完整流程。
一、整体流程概览
AVPacket (原始编码数据)
│
▼
rtsp_write_packet() [libavformat/rtspenc.c:182]
│
├─→ 根据stream_index找到对应的RTSPStream
├─→ 获取RTP Muxer Context (rtpctx)
│
▼
ff_write_chained() [libavformat/mux.c:1337]
│
├─→ 时间戳转换 (从源流时间基转换到RTP流时间基)
├─→ 调用RTP Muxer的write_packet
│
▼
rtp_write_packet() [libavformat/rtpenc.c:541]
│
├─→ RTCP发送检查 (每5秒或达到阈值时发送SR)
├─→ 时间戳计算: cur_timestamp = base_timestamp + pkt->pts
├─→ 根据编解码器类型调用特定封装函数
│
▼
编解码器特定封装函数
├─→ H.264/HEVC: ff_rtp_send_h264_hevc()
├─→ AAC: ff_rtp_send_aac()
├─→ 其他编解码器...
│
▼
ff_rtp_send_data() [libavformat/rtpenc.c:356]
│
├─→ 构建RTP固定头 (12字节)
├─→ 写入Payload数据
├─→ 更新序列号和统计信息
│
▼
传输层
├─→ UDP模式: 直接写入UDP socket
└─→ TCP模式: 写入动态缓冲区,然后添加Interleaved头发送
二、核心函数详解
2.1 rtsp_write_packet() - RTSP推流入口
位置 : libavformat/rtspenc.c:182
c
static int rtsp_write_packet(AVFormatContext *s, AVPacket *pkt)
{
RTSPState *rt = s->priv_data;
RTSPStream *rtsp_st;
AVFormatContext *rtpctx;
int ret;
// 1. 检查并处理RTSP控制消息 (poll非阻塞检查)
// ... (省略poll逻辑)
// 2. 根据stream_index找到对应的RTSPStream
if (pkt->stream_index < 0 || pkt->stream_index >= rt->nb_rtsp_streams)
return AVERROR_INVALIDDATA;
rtsp_st = rt->rtsp_streams[pkt->stream_index];
rtpctx = rtsp_st->transport_priv; // 获取RTP Muxer Context
// 3. 调用链式写入,将数据传递给RTP Muxer
ret = ff_write_chained(rtpctx, 0, pkt, s, 0);
// 4. TCP模式需要额外处理Interleaved封装
if (!ret && rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP)
ret = ff_rtsp_tcp_write_packet(s, rtsp_st);
return ret;
}
关键点:
- 每个AVStream对应一个RTSPStream,每个RTSPStream有自己的RTP Muxer Context
ff_write_chained()负责将数据传递给RTP Muxer进行封装- TCP模式下需要额外的Interleaved封装处理
2.2 ff_write_chained() - 链式写入
位置 : libavformat/mux.c:1337
c
int ff_write_chained(AVFormatContext *dst, int dst_stream, AVPacket *pkt,
AVFormatContext *src, int interleave)
{
int64_t pts = pkt->pts, dts = pkt->dts, duration = pkt->duration;
int stream_index = pkt->stream_index;
AVRational time_base = pkt->time_base;
int ret;
// 1. 修改packet的stream_index为目标流的索引
pkt->stream_index = dst_stream;
// 2. 时间戳转换:从源流时间基转换到目标流时间基
av_packet_rescale_ts(pkt,
src->streams[stream_index]->time_base,
dst->streams[dst_stream]->time_base);
// 3. 调用目标muxer的write_packet
if (!interleave) {
ret = av_write_frame(dst, pkt);
} else
ret = av_interleaved_write_frame(dst, pkt);
// 4. 恢复packet的原始字段
pkt->pts = pts;
pkt->dts = dts;
pkt->duration = duration;
pkt->stream_index = stream_index;
pkt->time_base = time_base;
return ret;
}
关键点:
- 时间戳转换:将源流的时间基转换为RTP流的时间基
- RTP流的时间基通常是:音频为采样率,视频为90000Hz
2.3 rtp_write_packet() - RTP封装主函数
位置 : libavformat/rtpenc.c:541
c
static int rtp_write_packet(AVFormatContext *s1, AVPacket *pkt)
{
RTPMuxContext *s = s1->priv_data;
AVStream *st = s1->streams[0];
int rtcp_bytes;
int size = pkt->size;
// 1. RTCP发送检查
// 计算自上次SR以来发送的字节数
rtcp_bytes = ((s->octet_count - s->last_octet_count) * RTCP_TX_RATIO_NUM) /
RTCP_TX_RATIO_DEN;
// 满足以下条件之一时发送RTCP SR:
// - 第一个包
// - 发送的字节数 >= RTCP_SR_SIZE (28字节)
// - 距离上次SR超过5秒
if ((s->first_packet ||
((rtcp_bytes >= RTCP_SR_SIZE) &&
(ff_ntp_time() - s->last_rtcp_ntp_time > 5000000))) &&
!(s->flags & FF_RTP_FLAG_SKIP_RTCP)) {
rtcp_send_sr(s1, ff_ntp_time(), 0);
s->last_octet_count = s->octet_count;
s->first_packet = 0;
}
// 2. 计算RTP时间戳
// base_timestamp是随机初始值,用于避免时间戳冲突
s->cur_timestamp = s->base_timestamp + pkt->pts;
// 3. 根据编解码器类型调用特定的封装函数
switch(st->codecpar->codec_id) {
case AV_CODEC_ID_H264:
ff_rtp_send_h264_hevc(s1, pkt->data, size);
break;
case AV_CODEC_ID_AAC:
if (s->flags & FF_RTP_FLAG_MP4A_LATM)
ff_rtp_send_latm(s1, pkt->data, size);
else
ff_rtp_send_aac(s1, pkt->data, size);
break;
// ... 其他编解码器
default:
rtp_send_raw(s1, pkt->data, size);
break;
}
return 0;
}
关键点:
- RTCP SR (Sender Report) 定期发送,用于同步和统计
- RTP时间戳 = base_timestamp + packet的PTS
- 不同编解码器有不同的RTP封装方式
2.4 ff_rtp_send_data() - RTP固定头构建
位置 : libavformat/rtpenc.c:356
c
void ff_rtp_send_data(AVFormatContext *s1, const uint8_t *buf1, int len, int m)
{
RTPMuxContext *s = s1->priv_data;
/* 构建RTP固定头 (12字节) */
// 字节0: V(2 bits) + P(1 bit) + X(1 bit) + CC(4 bits)
// V=2 (RTP版本), P=0, X=0, CC=0
avio_w8(s1->pb, RTP_VERSION << 6); // 0x80 = 10000000
// 字节1: M(1 bit) + PT(7 bits)
// M=marker bit (m参数), PT=payload type
avio_w8(s1->pb, (s->payload_type & 0x7f) | ((m & 0x01) << 7));
// 字节2-3: Sequence Number (16 bits)
avio_wb16(s1->pb, s->seq);
// 字节4-7: Timestamp (32 bits)
avio_wb32(s1->pb, s->timestamp);
// 字节8-11: SSRC (32 bits)
avio_wb32(s1->pb, s->ssrc);
// 写入Payload数据
avio_write(s1->pb, buf1, len);
avio_flush(s1->pb);
// 更新统计信息
s->seq = (s->seq + 1) & 0xffff; // 序列号递增,16位循环
s->octet_count += len; // 累计发送字节数
s->packet_count++; // 累计发送包数
}
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 (2 bits): RTP版本,固定为2
- P (1 bit): Padding标志,通常为0
- X (1 bit): Extension标志,通常为0
- CC (4 bits): CSRC计数,通常为0
- M (1 bit): Marker位,用于标记关键帧或帧边界
- PT (7 bits): Payload Type,标识编码格式 (如H.264=96, AAC=97)
- Sequence Number (16 bits): 序列号,每包递增,用于检测丢包
- Timestamp (32 bits): 时间戳,基于采样时钟
- SSRC (32 bits): 同步源标识符,随机生成,用于区分不同流
三、编解码器特定封装详解
3.1 H.264/HEVC封装 - FU-A分片模式
位置 : libavformat/rtpenc_h264_hevc.c
3.1.1 小NAL单元处理 (Single NAL Unit Mode)
当NAL单元大小 <= max_payload_size时,可以直接发送:
c
void ff_rtp_send_h264_hevc(AVFormatContext *s1, const uint8_t *buf1, int size)
{
// 1. 查找NAL单元起始码 (0x00000001 或 0x000001)
r = ff_nal_find_startcode(buf1, end);
while (r < end) {
// 2. 找到下一个NAL单元起始码
r1 = ff_nal_find_startcode(r + 1, end);
// 3. 提取NAL单元
nal_size = r1 - r - 1;
nal_buf = r + 1; // 跳过起始码
// 4. 调用nal_send处理
nal_send(s1, nal_buf, nal_size, last);
}
}
3.1.2 NAL单元封装逻辑
c
static void nal_send(AVFormatContext *s1, const uint8_t *buf, int size, int last)
{
RTPMuxContext *s = s1->priv_data;
if (size <= s->max_payload_size) {
// 情况1: 可以聚合多个小NAL单元 (STAP-A模式)
if (buffered_size + 2 + size <= s->max_payload_size) {
if (buffered_size == 0) {
*s->buf_ptr++ = 24; // STAP-A类型
}
AV_WB16(s->buf_ptr, size); // NAL单元长度 (2字节)
s->buf_ptr += 2;
memcpy(s->buf_ptr, buf, size); // NAL单元数据
s->buf_ptr += size;
s->buffered_nals++;
} else {
// 情况2: 单个NAL单元直接发送
flush_buffered(s1, 0);
ff_rtp_send_data(s1, buf, size, last);
}
} else {
// 情况3: 大NAL单元需要分片 (FU-A模式)
flush_buffered(s1, 0);
// 构建FU-A头
uint8_t type = buf[0] & 0x1F; // NAL类型
uint8_t nri = buf[0] & 0x60; // NRI (重要性)
s->buf[0] = 28; // FU Indicator: Type=28 (FU-A)
s->buf[0] |= nri; // 保留NRI
s->buf[1] = type; // FU Header: NAL类型
s->buf[1] |= 1 << 7; // S=1 (Start fragment)
buf += 1; // 跳过原始NAL头
size -= 1;
// 分片发送
while (size + 2 > s->max_payload_size) {
memcpy(&s->buf[2], buf, s->max_payload_size - 2);
ff_rtp_send_data(s1, s->buf, s->max_payload_size, 0);
buf += s->max_payload_size - 2;
size -= s->max_payload_size - 2;
s->buf[1] &= ~(1 << 7); // 清除S位
}
// 最后一个分片
s->buf[1] |= 1 << 6; // E=1 (End fragment)
memcpy(&s->buf[2], buf, size);
ff_rtp_send_data(s1, s->buf, size + 2, last);
}
}
3.1.3 H.264 RTP Payload格式
Single NAL Unit Mode:
RTP Header (12 bytes)
│
▼
NAL Unit (直接包含NAL单元,去掉起始码)
STAP-A (Single-Time Aggregation Packet):
RTP Header (12 bytes)
│
▼
STAP-A Header (1 byte): 0x18 (24)
│
▼
NAL Unit 1 Length (2 bytes)
│
▼
NAL Unit 1 Data
│
▼
NAL Unit 2 Length (2 bytes)
│
▼
NAL Unit 2 Data
...
FU-A (Fragmentation Unit):
RTP Header (12 bytes)
│
▼
FU Indicator (1 byte)
├─→ F (1 bit) = 0
├─→ NRI (2 bits) = 从原NAL头复制
└─→ Type (5 bits) = 28 (FU-A)
│
▼
FU Header (1 byte)
├─→ S (1 bit) = 1 (Start) / 0 (Continue)
├─→ E (1 bit) = 0 (Continue) / 1 (End)
├─→ R (1 bit) = 0 (Reserved)
└─→ Type (5 bits) = 原NAL类型
│
▼
FU Payload (NAL单元数据,去掉NAL头)
3.2 AAC封装 - MPEG4-GENERIC格式
位置 : libavformat/rtpenc_aac.c
c
void ff_rtp_send_aac(AVFormatContext *s1, const uint8_t *buff, int size)
{
RTPMuxContext *s = s1->priv_data;
AVStream *st = s1->streams[0];
const int max_au_headers_size = 2 + 2 * s->max_frames_per_packet;
int len, max_packet_size = s->max_payload_size - max_au_headers_size;
uint8_t *p;
// 1. 跳过ADTS头 (如果存在)
if ((s1->streams[0]->codecpar->extradata_size) == 0) {
size -= 7; // ADTS头7字节
buff += 7;
}
// 2. 检查是否需要发送当前缓冲的包
len = (s->buf_ptr - s->buf);
if (s->num_frames &&
(s->num_frames == s->max_frames_per_packet ||
(len + size) > s->max_payload_size ||
时间戳差异超过max_delay)) {
// 发送缓冲的包
int au_size = s->num_frames * 2;
p = s->buf + max_au_headers_size - au_size - 2;
if (p != s->buf) {
memmove(p + 2, s->buf + 2, au_size);
}
AV_WB16(p, au_size * 8); // AU Header Size (单位: bits)
ff_rtp_send_data(s1, p, s->buf_ptr - p, 1);
s->num_frames = 0;
}
// 3. 初始化缓冲区 (如果是新包)
if (s->num_frames == 0) {
s->buf_ptr = s->buf + max_au_headers_size;
s->timestamp = s->cur_timestamp;
}
// 4. 添加AU到缓冲区
if (size <= max_packet_size) {
p = s->buf + s->num_frames++ * 2 + 2;
AV_WB16(p, size * 8); // AU Size (单位: bits)
memcpy(s->buf_ptr, buff, size);
s->buf_ptr += size;
} else {
// 5. 大AU需要分片发送
int au_size = size;
max_packet_size = s->max_payload_size - 4;
p = s->buf;
AV_WB16(p, 2 * 8); // AU Header Size = 2 bytes
while (size > 0) {
len = FFMIN(size, max_packet_size);
AV_WB16(&p[2], au_size * 8); // AU Size
memcpy(p + 4, buff, len);
ff_rtp_send_data(s1, p, len + 4, len == size);
size -= len;
buff += len;
}
}
}
3.2.1 AAC RTP Payload格式 (MPEG4-GENERIC)
RTP Header (12 bytes)
│
▼
AU Header Section
├─→ AU Header Length (2 bits) = 0 (表示16位AU头)
└─→ AU Header (16 bits per AU)
├─→ AU Size (13 bits): ADTS帧大小 (单位: bytes)
└─→ AU Index (3 bits): 索引 (通常为0)
│
▼
AU Payload Section
└─→ ADTS Frame 1 | ADTS Frame 2 | ...
多AU聚合示例:
RTP Header
│
▼
AU Header Section (2 + N*2 bytes)
├─→ AU Header Length = 16 (2 bits, 单位: bits)
├─→ AU Header 1 (16 bits)
│ ├─→ Size 1 (13 bits)
│ └─→ Index 1 (3 bits)
├─→ AU Header 2 (16 bits)
│ ├─→ Size 2 (13 bits)
│ └─→ Index 2 (3 bits)
...
│
▼
AU Payload Section
├─→ ADTS Frame 1 (Size 1 bytes)
├─→ ADTS Frame 2 (Size 2 bytes)
...
四、传输层处理
4.1 UDP模式
UDP模式下,RTP包直接写入UDP socket:
c
// 在rtp_write_header中设置
rtpctx->pb = ffio_fdopen(&rtpctx->pb, udp_handle);
// udp_handle是UDP URLContext
// ff_rtp_send_data直接写入
avio_write(s1->pb, buf1, len); // 写入UDP socket
4.2 TCP Interleaved模式
TCP模式下,需要添加Interleaved头:
位置 : libavformat/rtspenc.c:143
c
int ff_rtsp_tcp_write_packet(AVFormatContext *s, RTSPStream *rtsp_st)
{
RTSPState *rt = s->priv_data;
AVFormatContext *rtpctx = rtsp_st->transport_priv;
uint8_t *buf, *ptr;
int size;
uint8_t *interleave_header, *interleaved_packet;
// 1. 从动态缓冲区获取RTP包数据
size = avio_close_dyn_buf(rtpctx->pb, &buf);
rtpctx->pb = NULL;
ptr = buf;
// 2. 解析并添加Interleaved头
while (size > 4) {
uint32_t packet_len = AV_RB32(ptr); // 读取包长度
int id;
// 判断是RTP还是RTCP
if (RTP_PT_IS_RTCP(ptr[5])) // 检查Payload Type
id = rtsp_st->interleaved_max; // RTCP通道
else
id = rtsp_st->interleaved_min; // RTP通道
// 构建Interleaved头 (覆盖原来的长度字段)
interleaved_packet = interleave_header = ptr;
ptr += 4;
size -= 4;
if (packet_len > size || packet_len < 2)
break;
// Interleaved头格式: '$' + Channel ID + Packet Length
interleave_header[0] = '$'; // 标记字节
interleave_header[1] = id; // 通道ID
AV_WB16(interleave_header + 2, packet_len); // 包长度
// 3. 发送Interleaved包
ffurl_write(rt->rtsp_hd_out, interleaved_packet, 4 + packet_len);
ptr += packet_len;
size -= packet_len;
}
av_free(buf);
// 4. 重新打开动态缓冲区
return ffio_open_dyn_packet_buf(&rtpctx->pb, rt->pkt_size);
}
4.2.1 TCP Interleaved格式
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| '$' | Channel ID | Packet Length (16 bits) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP/RTCP Packet Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
说明:
- '$' (0x24): Interleaved包标记
- Channel ID :
- RTP通道:
interleaved_min(通常为0) - RTCP通道:
interleaved_max(通常为1)
- RTP通道:
- Packet Length: RTP/RTCP包的长度 (不包括Interleaved头)
- Packet Data: 完整的RTP或RTCP包
五、关键数据结构
5.1 RTPMuxContext
位置 : libavformat/rtpenc.h:27
c
struct RTPMuxContext {
const AVClass *av_class;
AVFormatContext *ic;
AVStream *st;
// RTP头字段
int payload_type; // Payload Type (0-127)
uint32_t ssrc; // SSRC标识符
int seq; // 序列号
uint32_t timestamp; // 当前时间戳
uint32_t base_timestamp; // 基础时间戳 (随机初始值)
uint32_t cur_timestamp; // 当前包的时间戳
// 包大小限制
int max_payload_size; // 最大Payload大小 (MTU - IP头 - UDP头 - RTP头)
// 缓冲区
uint8_t *buf; // 输出缓冲区
uint8_t *buf_ptr; // 缓冲区当前指针
// 统计信息 (用于RTCP)
unsigned int packet_count; // 发送包数
unsigned int octet_count; // 发送字节数
unsigned int last_octet_count; // 上次SR时的字节数
int64_t last_rtcp_ntp_time; // 上次发送SR的NTP时间
int64_t first_rtcp_ntp_time; // 首次SR的NTP时间
int first_packet; // 是否第一个包
// 编解码器特定字段
int max_frames_per_packet; // 每包最大帧数 (AAC等)
int num_frames; // 当前包中的帧数
int nal_length_size; // NAL长度字段大小 (H.264/HEVC)
int buffered_nals; // 缓冲的NAL数 (H.264 STAP-A)
int flags; // RTP标志位
};
5.2 RTSPStream
位置 : libavformat/rtsp.h (部分字段)
c
typedef struct RTSPStream {
int stream_index; // 对应的AVStream索引
char control_url[256]; // RTSP控制URL
AVFormatContext *transport_priv; // RTP Muxer Context
int interleaved_min; // RTP通道ID
int interleaved_max; // RTCP通道ID
// ... 其他字段
} RTSPStream;
六、完整示例流程
6.1 H.264视频帧封装示例
假设有一个H.264 IDR帧,包含以下NAL单元:
- SPS (7字节)
- PPS (4字节)
- IDR Slice (5000字节)
步骤1 : rtsp_write_packet() 接收AVPacket (包含完整帧数据)
步骤2 : ff_write_chained() 转换时间戳并调用RTP Muxer
步骤3 : rtp_write_packet() 调用 ff_rtp_send_h264_hevc()
步骤4: 解析NAL单元:
- SPS: 7字节 → 单个RTP包 (Single NAL Unit)
- PPS: 4字节 → 单个RTP包 (Single NAL Unit)
- IDR Slice: 5000字节 → 分片为多个FU-A包
步骤5 : 每个RTP包调用 ff_rtp_send_data():
RTP包1 (SPS):
RTP Header: V=2, PT=96, Seq=100, TS=12345, SSRC=0x12345678
Payload: SPS NAL单元 (7字节)
RTP包2 (PPS):
RTP Header: V=2, PT=96, Seq=101, TS=12345, SSRC=0x12345678
Payload: PPS NAL单元 (4字节)
RTP包3 (IDR Fragment 1):
RTP Header: V=2, PT=96, Seq=102, TS=12345, SSRC=0x12345678, M=0
Payload: FU Indicator (0x1C) + FU Header (0x65|0x80) + Fragment 1
RTP包4 (IDR Fragment 2):
RTP Header: V=2, PT=96, Seq=103, TS=12345, SSRC=0x12345678, M=0
Payload: FU Indicator (0x1C) + FU Header (0x65) + Fragment 2
RTP包5 (IDR Fragment 3):
RTP Header: V=2, PT=96, Seq=104, TS=12345, SSRC=0x12345678, M=1
Payload: FU Indicator (0x1C) + FU Header (0x65|0x40) + Fragment 3
6.2 AAC音频帧封装示例
假设有两个AAC ADTS帧:
- Frame 1: 200字节
- Frame 2: 180字节
步骤1-3: 同H.264示例
步骤4 : ff_rtp_send_aac() 处理:
- 检查缓冲区空间
- 添加AU Header: Size1=200, Size2=180
- 聚合两个帧到一个RTP包
步骤5 : ff_rtp_send_data() 发送:
RTP包:
RTP Header: V=2, PT=97, Seq=200, TS=48000, SSRC=0x87654321, M=1
Payload:
AU Header Section:
AU Header Length = 16 bits
AU Header 1: Size=200 (13 bits), Index=0 (3 bits)
AU Header 2: Size=180 (13 bits), Index=0 (3 bits)
AU Payload Section:
ADTS Frame 1 (200 bytes)
ADTS Frame 2 (180 bytes)
七、关键参数和配置
7.1 包大小限制
c
// 在rtp_write_header中计算
s->max_payload_size = s1->packet_size - 12; // 减去RTP固定头12字节
// packet_size通常设置为MTU大小
// 以太网MTU = 1500字节
// IP头 = 20字节
// UDP头 = 8字节
// RTP头 = 12字节
// 实际Payload最大 = 1500 - 20 - 8 - 12 = 1460字节
7.2 时间戳计算
c
// 音频: 基于采样率
avpriv_set_pts_info(st, 32, 1, st->codecpar->sample_rate);
// 例如: 48000Hz采样率,每采样时间戳增量为1
// 视频: 固定90000Hz
avpriv_set_pts_info(st, 32, 1, 90000);
// 每帧时间戳增量 = 90000 / 帧率
// 例如: 30fps, 每帧时间戳增量 = 90000 / 30 = 3000
7.3 Payload Type分配
c
// 动态Payload Type范围: 96-127
// 静态Payload Type (RFC 3551):
// - PCMU: 0
// - PCMA: 8
// - MPEG Audio: 14
// - H.261: 31
// - H.263: 34
// - MPEG Video: 32
// - H.264: 通常使用96 (动态)
// - AAC: 通常使用97 (动态)
八、总结
RTSP推流中RTP包组装的完整流程:
- 入口 :
rtsp_write_packet()接收AVPacket - 链式传递 :
ff_write_chained()转换时间戳并传递给RTP Muxer - RTP封装 :
rtp_write_packet()根据编解码器类型调用特定封装函数 - 编解码器处理 :
- H.264/HEVC: FU-A分片或STAP-A聚合
- AAC: AU Header + 多帧聚合
- RTP头构建 :
ff_rtp_send_data()构建12字节RTP固定头 - 传输 :
- UDP: 直接发送
- TCP: 添加Interleaved头后发送
整个过程确保了:
- 时间同步: 通过RTP时间戳和RTCP SR
- 包完整性: 通过序列号检测丢包
- MTU适配: 大包自动分片
- 效率优化: 小包聚合减少开销