前言
RTSP(Real Time Streaming Protocol)作为流媒体传输的核心协议之一,在视频监控、直播推流、视频会议等领域有着广泛应用。本文将从协议原理出发,深入分析RTSP推流的完整流程,并结合FFmpeg源码实现,帮助读者全面理解RTSP推流的技术细节。
目录
1. RTSP协议基础
1.1 RTSP协议简介
RTSP(Real Time Streaming Protocol)是一个应用层协议,用于控制实时流媒体的传输。它工作在TCP之上,主要用于建立和控制媒体会话。
RTSP的核心特点:
| 特性 | 说明 |
|---|---|
| 协议类型 | 应用层控制协议 |
| 传输层 | TCP(控制) + UDP/TCP(数据) |
| 工作模式 | 客户端-服务器模式 |
| 主要功能 | 会话建立、流控制、播放控制 |
| 数据格式 | RTP/RTCP封装音视频数据 |
1.2 RTSP与HTTP的区别
虽然RTSP在语法上与HTTP相似,但两者有本质区别:
┌─────────────────────────────────────────────────────────┐
│ RTSP vs HTTP 对比 │
└─────────────────────────────────────────────────────────┘
HTTP:
├─→ 请求-响应模式
├─→ 无状态协议
├─→ 数据在HTTP消息体内传输
└─→ 主要用于Web内容传输
RTSP:
├─→ 会话控制协议
├─→ 有状态协议(Session ID)
├─→ 数据通过RTP独立传输
└─→ 主要用于流媒体控制
1.3 RTSP推流与拉流的区别
| 方面 | 推流(Publish) | 拉流(Play) |
|---|---|---|
| 命令序列 | ANNOUNCE → SETUP → RECORD | DESCRIBE → SETUP → PLAY |
| SDP处理 | 客户端生成并发送 | 服务器返回并解析 |
| Transport Mode | mode=record |
mode=play |
| 数据方向 | Client → Server | Server → Client |
| 使用场景 | 摄像头推流、直播推流 | 视频播放、监控查看 |
2. RTSP推流架构概览
2.1 整体架构图
┌─────────────────────────────────────────────────────────────┐
│ RTSP推流架构图 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌─────────────────┐
│ RTSP Client │ │ RTSP Server │
│ (推流端) │ │ (接收端) │
└─────────────────┘ └─────────────────┘
│ │
│ ┌──────────────────────────────┐ │
│ │ RTSP Control (TCP:554) │ │
│ │ - OPTIONS │ │
│ │ - ANNOUNCE │ │
│ │ - SETUP │ │
│ │ - RECORD │ │
│ │ - TEARDOWN │ │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ RTP Data (UDP/TCP) │ │
│ │ - Video RTP → Port 5004 │ │
│ │ - Audio RTP → Port 5006 │ │
│ │ - RTCP → Port 5005, 5007 │ │
│ └──────────────────────────────┘ │
│ │
└──────────────────────────────────────┘
2.2 协议栈结构
┌─────────────────────────────────────────────────────────┐
│ RTSP推流协议栈 │
└─────────────────────────────────────────────────────────┘
应用层
│
├─→ RTSP控制层
│ ├─→ OPTIONS, ANNOUNCE, SETUP, RECORD, TEARDOWN
│ └─→ SDP (Session Description Protocol)
│
├─→ RTP数据层
│ ├─→ RTP封装 (音视频数据)
│ ├─→ RTCP反馈 (统计信息)
│ └─→ 编解码器特定封装 (H.264 FU-A, AAC AU)
│
传输层
├─→ TCP (RTSP控制 + RTP over TCP)
└─→ UDP (RTP over UDP)
│
网络层
└─→ IP
2.3 数据流向
┌─────────────────────────────────────────────────────────┐
│ 数据流向图 │
└─────────────────────────────────────────────────────────┘
编码器
│
▼
AVPacket (原始音视频数据)
│
▼
RTP Muxer
├─→ 添加RTP头 (时间戳、序列号、SSRC)
├─→ 编解码器特定封装
│ ├─→ H.264: FU-A分片
│ └─→ AAC: AU Header + ADTS
└─→ 生成RTP包
│
▼
UDP Socket (或TCP Interleaved)
│
▼
网络传输
│
▼
RTSP Server
3. 完整推流流程详解
3.1 推流时序图
RTSP推流完整时序图:
┌─────────────────────────────────────────────────────────────────┐
│ RTSP推流完整时序图 │
└─────────────────────────────────────────────────────────────────┘
RTSP客户端 RTSP服务器
│ │
│──────────── 阶段1: TCP连接建立 ──────────────▶│
│ (端口554) │
│ │
│──────────── 阶段2: 能力协商 ──────────────────▶│
│ OPTIONS * RTSP/1.0 │
│ CSeq: 1 │
│◀─────────── 200 OK │
│ Public: ANNOUNCE, SETUP, RECORD... │
│ │
│──────────── 阶段3: 流描述发布 ────────────────▶│
│ ANNOUNCE rtsp://server/live RTSP/1.0 │
│ Content-Type: application/sdp │
│ Content-Length: xxx │
│ [SDP内容描述流信息] │
│◀─────────── 200 OK │
│ │
│──────────── 阶段4: 传输通道建立 ──────────────▶│
│ SETUP rtsp://server/live/streamid=0 │
│ Transport: RTP/AVP/UDP;mode=record; │
│ client_port=5004-5005 │
│◀─────────── 200 OK │
│ Transport: RTP/AVP/UDP;mode=record; │
│ server_port=18000-18001; │
│ Session: 12345678 │
│ │
│ SETUP rtsp://server/live/streamid=1 │
│ Transport: RTP/AVP/UDP;mode=record; │
│ client_port=5006-5007 │
│ Session: 12345678 │
│◀─────────── 200 OK │
│ Transport: RTP/AVP/UDP;mode=record; │
│ server_port=18002-18003 │
│ │
│──────────── 阶段5: 开始推流 ──────────────────▶│
│ RECORD rtsp://server/live RTSP/1.0 │
│ Session: 12345678 │
│ Range: npt=0.000- │
│◀─────────── 200 OK │
│ │
│═══════════ 阶段6: 数据传输 (UDP) ════════════▶│
│ [视频RTP包] → UDP:5004→18000 │
│ [音频RTP包] → UDP:5006→18002 │
│ [RTCP包] → UDP:5005→18001, 5007→18003 │
│ (持续发送...) │
│ │
│──────────── 阶段7: 结束推流 ──────────────────▶│
│ TEARDOWN rtsp://server/live RTSP/1.0 │
│ Session: 12345678 │
│◀─────────── 200 OK │
│ │
└─────────────────────────────────────────────┘
时序图说明:
| 阶段 | 命令 | 方向 | 说明 |
|---|---|---|---|
| 阶段1 | TCP连接 | Client → Server | 建立RTSP控制连接(端口554) |
| 阶段2 | OPTIONS | Client ↔ Server | 查询服务器支持的方法 |
| 阶段3 | ANNOUNCE | Client → Server | 发送SDP描述流信息 |
| 阶段4 | SETUP | Client ↔ Server | 为每个流建立RTP传输通道 |
| 阶段5 | RECORD | Client ↔ Server | 通知服务器开始接收数据 |
| 阶段6 | RTP数据 | Client → Server | 持续发送RTP/RTCP包 |
| 阶段7 | TEARDOWN | Client ↔ Server | 结束推流会话 |
关键时间点:
时间轴:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
T0: TCP连接建立
│
T1: OPTIONS命令 (能力协商)
│
T2: ANNOUNCE命令 (发布SDP)
│
T3: SETUP命令 (建立传输通道)
│ ├─→ SETUP streamid=0 (视频流)
│ └─→ SETUP streamid=1 (音频流)
│
T4: RECORD命令 (开始推流)
│
T5: 开始发送RTP数据包
│ ├─→ 视频RTP包 (持续)
│ ├─→ 音频RTP包 (持续)
│ └─→ RTCP反馈包 (周期性)
│
T6: TEARDOWN命令 (结束推流)
│
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
流程图(详细版):
┌─────────────────────────────────────────────────────────────────┐
│ RTSP推流流程图 │
└─────────────────────────────────────────────────────────────────┘
开始
│
▼
┌─────────────────┐
│ 初始化RTSP上下文 │
│ - 解析URL │
│ - 设置参数 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 建立TCP连接 │
│ (端口554) │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌──────────────┐
│ 发送OPTIONS命令 │──────▶│ 200 OK │
│ 查询服务器能力 │ │ 获取支持方法 │
└────────┬────────┘ └──────────────┘
│
▼
┌─────────────────┐
│ 生成SDP内容 │
│ - 流信息 │
│ - 编解码器参数 │
│ - 端口信息 │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌──────────────┐
│ 发送ANNOUNCE命令│──────▶│ 200 OK │
│ 发布流描述 │ │ 服务器确认 │
└────────┬────────┘ └──────────────┘
│
▼
┌─────────────────┐
│ 为每个流建立通道│
│ - 视频流 │
│ - 音频流 │
└────────┬────────┘
│
├─────────────────────────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 创建UDP Socket │ │ 创建UDP Socket │
│ (视频流) │ │ (音频流) │
│ RTP: 5004 │ │ RTP: 5006 │
│ RTCP: 5005 │ │ RTCP: 5007 │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 发送SETUP命令 │ │ 发送SETUP命令 │
│ streamid=0 │ │ streamid=1 │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 200 OK │ │ 200 OK │
│ 获取服务器端口 │ │ 获取服务器端口 │
│ 获取Session ID │ │ 获取Session ID │
└────────┬────────┘ └────────┬────────┘
│ │
└─────────────┬───────────────┘
│
▼
┌─────────────────┐ ┌──────────────┐
│ 发送RECORD命令 │──────▶│ 200 OK │
│ 开始推流 │ │ 进入STREAMING│
└────────┬────────┘ └──────────────┘
│
▼
┌─────────────────┐
│ 开始发送数据包 │
│ ┌─────────────┐ │
│ │ 视频RTP包 │ │
│ │ → UDP:5004 │ │
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ 音频RTP包 │ │
│ │ → UDP:5006 │ │
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ RTCP反馈包 │ │
│ │ → UDP:5005 │ │
│ │ → UDP:5007 │ │
│ └─────────────┘ │
└────────┬────────┘
│
│ (持续发送...)
│
▼
┌─────────────────┐ ┌──────────────┐
│ 发送TEARDOWN命令│──────▶│ 200 OK │
│ 结束推流 │ │ 关闭会话 │
└────────┬────────┘ └──────────────┘
│
▼
┌─────────────────┐
│ 清理资源 │
│ - 关闭Socket │
│ - 释放内存 │
└────────┬────────┘
│
▼
结束
3.2 详细流程步骤
步骤1: TCP连接建立
功能: 建立RTSP控制连接
实现代码位置 : libavformat/rtsp.c:ff_rtsp_connect()
c
// 建立TCP连接
ff_url_join(tcpname, sizeof(tcpname), lower_rtsp_proto, NULL,
host, port, "?timeout=%"PRId64, rt->stimeout);
ffurl_open_whitelist(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE, ...);
关键点:
- 默认端口: 554 (RTSP) 或 322 (RTSPS)
- 支持TLS加密 (rtsps://)
- 支持HTTP隧道 (rtsp://...?rtsp_transport=http)
步骤2: OPTIONS命令
功能: 查询服务器支持的方法
请求示例:
OPTIONS * RTSP/1.0
CSeq: 1
User-Agent: Lavf58.76.100
响应示例:
RTSP/1.0 200 OK
CSeq: 1
Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD
作用:
- 确认服务器支持ANNOUNCE和RECORD方法
- 检测服务器类型(通过Server头)
步骤3: ANNOUNCE命令(核心步骤)
功能: 向服务器发布流描述信息(SDP)
SDP生成过程:
c
// 1. 创建SDP内容
sdp = av_mallocz(SDP_MAX_SIZE);
sdp_ctx = *s; // 复制AVFormatContext
ctx_array[0] = &sdp_ctx;
av_sdp_create(ctx_array, 1, sdp, SDP_MAX_SIZE);
// 2. 发送ANNOUNCE命令
ff_rtsp_send_cmd_with_content(s, "ANNOUNCE", rt->control_uri,
"Content-Type: application/sdp\r\n",
reply, NULL, sdp, strlen(sdp));
SDP示例:
v=0
o=- 0 0 IN IP4 192.168.1.100
s=Live Stream
c=IN IP4 192.168.1.100
t=0 0
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42001e
a=control:streamid=0
m=audio 0 RTP/AVP 97
a=rtpmap:97 MPEG4-GENERIC/48000/2
a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1190
a=control:streamid=1
SDP关键字段说明:
| 字段 | 说明 | 示例 |
|---|---|---|
v= |
协议版本 | v=0 |
o= |
会话源 | o=- 0 0 IN IP4 192.168.1.100 |
s= |
会话名称 | s=Live Stream |
c= |
连接信息 | c=IN IP4 192.168.1.100 |
m= |
媒体描述 | m=video 0 RTP/AVP 96 |
a=rtpmap |
RTP映射 | a=rtpmap:96 H264/90000 |
a=fmtp |
格式参数 | a=fmtp:96 packetization-mode=1 |
a=control |
控制URL | a=control:streamid=0 |
请求示例:
ANNOUNCE rtsp://server/live RTSP/1.0
CSeq: 2
User-Agent: Lavf58.76.100
Content-Type: application/sdp
Content-Length: 234
[SDP内容]
响应示例:
RTSP/1.0 200 OK
CSeq: 2
步骤4: SETUP命令(为每个流建立传输通道)
功能: 为每个媒体流建立RTP传输通道
流程:
┌─────────────────────────────────────────────────────────┐
│ SETUP命令处理流程 │
└─────────────────────────────────────────────────────────┘
1. 选择传输方式
├─→ UDP (默认,低延迟)
└─→ TCP (可靠传输)
2. 分配本地端口
├─→ RTP端口 (偶数,如5004)
└─→ RTCP端口 (奇数,如5005)
3. 创建UDP Socket
└─→ 绑定到本地端口
4. 发送SETUP请求
└─→ 包含Transport头
5. 解析服务器响应
├─→ 获取服务器端口
├─→ 获取Session ID
└─→ 设置远程地址
UDP模式SETUP请求:
SETUP rtsp://server/live/streamid=0 RTSP/1.0
CSeq: 3
User-Agent: Lavf58.76.100
Transport: RTP/AVP/UDP;unicast;mode=record;client_port=5004-5005
UDP模式响应:
RTSP/1.0 200 OK
CSeq: 3
Session: 12345678;timeout=60
Transport: RTP/AVP/UDP;unicast;mode=record;client_port=5004-5005;server_port=18000-18001
TCP模式SETUP请求:
SETUP rtsp://server/live/streamid=0 RTSP/1.0
CSeq: 3
User-Agent: Lavf58.76.100
Transport: RTP/AVP/TCP;unicast;mode=record;interleaved=0-1
TCP模式响应:
RTSP/1.0 200 OK
CSeq: 3
Session: 12345678;timeout=60
Transport: RTP/AVP/TCP;unicast;mode=record;interleaved=0-1
关键代码实现:
c
// UDP模式: 创建UDP socket
ff_url_join(buf, sizeof(buf), "rtp", NULL, host, -1,
"?localport=%d", j);
ffurl_open_whitelist(&rtsp_st->rtp_handle, buf,
AVIO_FLAG_READ_WRITE, ...);
// 构建Transport头
av_strlcpy(transport, "RTP/AVP/UDP;unicast;mode=record", ...);
av_strlcatf(transport, sizeof(transport),
"client_port=%d-%d", port, port + 1);
// 发送SETUP命令
ff_rtsp_send_cmd(s, "SETUP", rtsp_st->control_url, cmd, reply, NULL);
Transport字段详解:
| 参数 | 说明 | 示例 |
|---|---|---|
RTP/AVP |
RTP over UDP | RTP/AVP/UDP |
RTP/AVP/TCP |
RTP over TCP | RTP/AVP/TCP |
unicast |
单播模式 | unicast |
multicast |
组播模式 | multicast |
mode=record |
推流模式 | mode=record |
client_port |
客户端端口 | client_port=5004-5005 |
server_port |
服务器端口 | server_port=18000-18001 |
interleaved |
TCP通道ID | interleaved=0-1 |
步骤5: RECORD命令
功能: 通知服务器开始接收数据流
请求示例:
RECORD rtsp://server/live RTSP/1.0
CSeq: 4
User-Agent: Lavf58.76.100
Session: 12345678
Range: npt=0.000-
响应示例:
RTSP/1.0 200 OK
CSeq: 4
Session: 12345678
关键代码:
c
static int rtsp_write_record(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
RTSPMessageHeader reply1, *reply = &reply1;
char cmd[MAX_URL_SIZE];
snprintf(cmd, sizeof(cmd), "Range: npt=0.000-\r\n");
ff_rtsp_send_cmd(s, "RECORD", rt->control_uri, cmd, reply, NULL);
if (reply->status_code != RTSP_STATUS_OK)
return ff_rtsp_averror(reply->status_code, -1);
rt->state = RTSP_STATE_STREAMING; // 进入推流状态
return 0;
}
Range字段说明:
npt=0.000-: 从时间0开始,到结束(直播流)npt=10.000-20.000: 从10秒到20秒(点播流)
步骤6: 数据包发送
数据封装流程:
┌─────────────────────────────────────────────────────────┐
│ 数据包封装流程 │
└─────────────────────────────────────────────────────────┘
AVPacket (原始数据)
│
▼
rtsp_write_packet()
│
├─→ 根据stream_index找到RTSPStream
├─→ 获取RTP Muxer Context
│
▼
ff_write_chained()
│
├─→ RTP Muxer封装
│ ├─→ 添加RTP头
│ │ ├─→ Version (2 bits) = 2
│ │ ├─→ Payload Type (7 bits)
│ │ ├─→ Sequence Number (16 bits)
│ │ ├─→ Timestamp (32 bits)
│ │ └─→ SSRC (32 bits)
│ │
│ ├─→ 编解码器特定封装
│ │ ├─→ H.264: FU-A分片
│ │ └─→ AAC: AU Header
│ │
│ └─→ 生成RTP包
│
▼
UDP发送 (或TCP Interleaved)
│
└─→ 发送到服务器
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Contributing Source (CSRC) identifiers |
| (可选) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段说明:
- V (Version): 版本号,固定为2
- P (Padding): 是否有填充
- X (Extension): 是否有扩展头
- CC (CSRC Count): CSRC数量
- M (Marker): 标记位(帧结束等)
- PT (Payload Type): 负载类型 (96-127为动态)
- Sequence Number: 序列号 (用于检测丢包)
- Timestamp: 时间戳 (用于同步)
- SSRC: 同步源标识符
H.264 RTP封装示例:
┌─────────────────────────────────────────────────────────┐
│ H.264 FU-A分片格式 │
└─────────────────────────────────────────────────────────┘
RTP Header (12 bytes)
│
├─→ Payload Type = 96
├─→ Marker = 1 (最后一个分片)
└─→ Timestamp = 当前时间戳
│
▼
FU Indicator (1 byte)
├─→ F (1 bit): 禁止位
├─→ NRI (2 bits): 重要性指示
└─→ Type (5 bits) = 28 (FU-A)
│
▼
FU Header (1 byte)
├─→ S (1 bit): 起始标记
├─→ E (1 bit): 结束标记
├─→ R (1 bit): 保留
└─→ Type (5 bits): NAL单元类型
│
▼
FU Payload
└─→ NAL单元数据
AAC RTP封装示例:
┌─────────────────────────────────────────────────────────┐
│ AAC AU格式 │
└─────────────────────────────────────────────────────────┘
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帧大小
└─→ AU Index (3 bits): 索引
│
▼
AU Payload Section
└─→ ADTS Frame 1 | ADTS Frame 2 | ...
UDP发送代码:
c
// UDP模式: 直接发送到UDP socket
ret = ff_write_chained(rtpctx, 0, pkt, s, 0);
// → RTP封装 → 写入UDP socket
TCP Interleaved发送代码:
c
// TCP模式: 先写入动态缓冲区
ret = ff_write_chained(rtpctx, 0, pkt, s, 0);
// 然后添加Interleaved头并发送
if (!ret && rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP)
ret = ff_rtsp_tcp_write_packet(s, rtsp_st);
TCP Interleaved格式:
┌─────────────────────────────────────────────────────────┐
│ 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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP/RTCP Packet Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
说明:
- '$': 数据包标记 (0x24)
- Channel ID: 通道ID
- 偶数: RTP数据通道 (0, 2, 4...)
- 奇数: RTCP控制通道 (1, 3, 5...)
- Packet Length: RTP/RTCP包长度 (16位,大端)
步骤7: TEARDOWN命令
功能: 结束推流会话
请求示例:
TEARDOWN rtsp://server/live RTSP/1.0
CSeq: 5
User-Agent: Lavf58.76.100
Session: 12345678
响应示例:
RTSP/1.0 200 OK
CSeq: 5
Session: 12345678
关闭流程:
c
static int rtsp_write_close(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
// 1. 发送RTCP BYE包
ff_rtsp_undo_setup(s, 1);
// → av_write_trailer() 会发送RTCP BYE
// 2. 发送TEARDOWN命令
ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL);
// 3. 关闭所有连接
ff_rtsp_close_streams(s);
ff_rtsp_close_connections(s);
ff_network_close();
return 0;
}
4. 关键技术实现
4.1 SDP生成机制
SDP生成流程:
c
// 1. 分配SDP缓冲区
sdp = av_mallocz(SDP_MAX_SIZE);
// 2. 创建临时AVFormatContext用于SDP生成
sdp_ctx = *s; // 复制原始上下文
sdp_ctx.url = url; // 设置URL
ctx_array[0] = &sdp_ctx;
// 3. 生成SDP内容
av_sdp_create(ctx_array, 1, sdp, SDP_MAX_SIZE);
SDP内容结构:
v=0 # 协议版本
o=- 0 0 IN IP4 192.168.1.100 # 会话源
s=Live Stream # 会话名称
c=IN IP4 192.168.1.100 # 连接信息
t=0 0 # 时间信息 (0 0表示直播)
m=video 0 RTP/AVP 96 # 媒体描述
a=rtpmap:96 H264/90000 # RTP映射
a=fmtp:96 packetization-mode=1 # 格式参数
a=control:streamid=0 # 控制URL
m=audio 0 RTP/AVP 97 # 第二个媒体流
a=rtpmap:97 MPEG4-GENERIC/48000/2
a=control:streamid=1
4.2 RTP封装机制
RTP Muxer初始化:
c
// 为每个流创建RTP Muxer Context
ff_rtp_chain_mux_open((AVFormatContext **)&rtsp_st->transport_priv,
s, st, rtsp_st->rtp_handle,
rt->pkt_size,
rtsp_st->stream_index);
RTP封装过程:
AVPacket
│
▼
RTP Muxer
│
├─→ 计算RTP时间戳
│ └─→ rtp_timestamp = (pts - start_time) * time_base_den / time_base_num
│
├─→ 选择Payload Type
│ ├─→ 标准类型 (0-95): 直接使用
│ └─→ 动态类型 (96-127): 从SDP获取
│
├─→ 编解码器特定封装
│ ├─→ H.264: 按NAL单元分片
│ │ ├─→ 小包: Single NAL Unit Mode
│ │ └─→ 大包: FU-A分片
│ │
│ └─→ AAC: AU Header + ADTS Frame
│
└─→ 生成RTP包
├─→ 添加RTP头
└─→ 设置序列号 (自动递增)
4.3 时间戳同步
时间戳计算:
c
// 设置起始时间
if (s->start_time_realtime == 0 || s->start_time_realtime == AV_NOPTS_VALUE)
s->start_time_realtime = av_gettime();
// RTP时间戳计算
rtp_timestamp = av_rescale_q(pkt->pts - stream->start_time,
stream->time_base,
rtp_time_base);
时间戳同步机制:
| 时间戳类型 | 说明 | 用途 |
|---|---|---|
| PTS (Presentation Time Stamp) | 媒体时间戳 | 音视频同步 |
| RTP Timestamp | RTP时间戳 | RTP包同步 |
| NTP Timestamp | 网络时间戳 | 绝对时间同步 |
4.4 端口分配策略
端口分配流程:
c
// 1. 计算随机偏移
port_off = av_get_random_seed() % ((rt->rtp_port_max - rt->rtp_port_min)/2);
port_off -= port_off & 0x01; // 确保是偶数
// 2. 从偏移位置开始尝试
for (j = rt->rtp_port_min + port_off, i = 0; i < rt->nb_rtsp_streams; ++i) {
// 3. 尝试绑定端口
while (j + 1 <= rt->rtp_port_max) {
ff_url_join(buf, sizeof(buf), "rtp", NULL, host, -1,
"?localport=%d", j);
err = ffurl_open_whitelist(&rtsp_st->rtp_handle, buf, ...);
if (!err)
goto rtp_opened;
j += 2; // 每次增加2 (RTP + RTCP)
}
}
端口分配规则:
| 流类型 | RTP端口 | RTCP端口 | 说明 |
|---|---|---|---|
| 视频流0 | 5004 | 5005 | 偶数端口用于RTP |
| 音频流0 | 5006 | 5007 | 奇数端口用于RTCP |
| 视频流1 | 5008 | 5009 | 每个流占用2个端口 |
4.5 错误处理与重试
错误处理机制:
c
// SETUP失败时的处理
if (reply->status_code == 461 /* Unsupported protocol */ && i == 0) {
err = 1; // 标记需要尝试其他传输方式
goto fail;
} else if (reply->status_code != RTSP_STATUS_OK) {
err = ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA);
goto fail;
}
// 传输方式回退
do {
// 尝试UDP
err = ff_rtsp_make_setup_request(s, host, port,
RTSP_LOWER_TRANSPORT_UDP, ...);
if (err < 0)
goto fail;
lower_transport_mask &= ~(1 << RTSP_LOWER_TRANSPORT_UDP);
// 如果UDP失败,尝试TCP
if (err == 1) {
err = ff_rtsp_make_setup_request(s, host, port,
RTSP_LOWER_TRANSPORT_TCP, ...);
}
} while (err);
5. FFmpeg源码分析
5.1 推流入口函数
rtsp_write_header():
c
static int rtsp_write_header(AVFormatContext *s)
{
int ret;
// 1. 建立RTSP连接并完成ANNOUNCE + SETUP
ret = ff_rtsp_connect(s);
if (ret)
return ret;
// 2. 发送RECORD命令开始推流
if (rtsp_write_record(s) < 0) {
ff_rtsp_close_streams(s);
ff_rtsp_close_connections(s);
return AVERROR_INVALIDDATA;
}
return 0;
}
函数调用链:
avformat_write_header()
│
▼
rtsp_write_header()
│
├─→ ff_rtsp_connect()
│ ├─→ TCP连接建立
│ ├─→ OPTIONS命令
│ ├─→ ff_rtsp_setup_output_streams()
│ │ ├─→ 生成SDP
│ │ ├─→ ANNOUNCE命令
│ │ └─→ 创建RTSPStream
│ └─→ ff_rtsp_make_setup_request()
│ ├─→ 创建UDP socket
│ ├─→ SETUP命令
│ └─→ ff_rtsp_open_transport_ctx()
│ └─→ 创建RTP Muxer
│
└─→ rtsp_write_record()
└─→ RECORD命令
5.2 数据包发送
rtsp_write_packet():
c
static int rtsp_write_packet(AVFormatContext *s, AVPacket *pkt)
{
RTSPState *rt = s->priv_data;
RTSPStream *rtsp_st;
AVFormatContext *rtpctx;
int ret;
// 1. 检查RTSP控制消息
// (处理服务器可能的控制请求)
// 2. 找到对应的RTSPStream
rtsp_st = rt->rtsp_streams[pkt->stream_index];
rtpctx = rtsp_st->transport_priv;
// 3. RTP封装并发送
ret = ff_write_chained(rtpctx, 0, pkt, s, 0);
// 4. TCP模式需要额外处理
if (!ret && rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP)
ret = ff_rtsp_tcp_write_packet(s, rtsp_st);
return ret;
}
数据流转:
av_interleaved_write_frame()
│
▼
rtsp_write_packet()
│
├─→ 根据stream_index找到RTSPStream
├─→ 获取RTP Muxer Context
│
▼
ff_write_chained()
│
├─→ RTP Muxer处理
│ ├─→ 计算RTP时间戳
│ ├─→ 编解码器特定封装
│ └─→ 生成RTP包
│
├─→ UDP模式
│ └─→ 直接写入UDP socket
│
└─→ TCP模式
├─→ 写入动态缓冲区
└─→ ff_rtsp_tcp_write_packet()
├─→ 添加Interleaved头
└─→ 写入RTSP TCP连接
5.3 关键数据结构
RTSPState结构:
c
typedef struct RTSPState {
URLContext *rtsp_hd; // RTSP控制连接
URLContext *rtsp_hd_out; // RTSP输出连接 (隧道模式)
RTSPStream **rtsp_streams; // 流数组
int nb_rtsp_streams; // 流数量
char control_uri[MAX_URL_SIZE]; // 控制URI
char session_id[512]; // 会话ID
enum RTSPClientState state; // 客户端状态
enum RTSPLowerTransport lower_transport; // 传输方式
int rtp_port_min; // RTP端口范围
int rtp_port_max;
// ... 其他字段
} RTSPState;
RTSPStream结构:
c
typedef struct RTSPStream {
URLContext *rtp_handle; // RTP UDP socket
void *transport_priv; // RTP Muxer Context
int stream_index; // 对应的AVStream索引
int interleaved_min; // TCP Interleaved通道ID
int interleaved_max;
char control_url[MAX_URL_SIZE]; // 控制URL
int sdp_payload_type; // SDP中的Payload Type
uint32_t ssrc; // SSRC标识符
} RTSPStream;
6. 常见问题与优化
6.1 UDP推流阻塞问题
问题: UDP发送缓冲区满时会导致阻塞
原因分析:
c
// UDP发送代码
static int udp_write(URLContext *h, const uint8_t *buf, int size)
{
// 如果没有设置AVIO_FLAG_NONBLOCK,会阻塞等待
if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
ret = ff_network_wait_fd(s->udp_fd, 1); // 可能阻塞
if (ret < 0)
return ret;
}
// sendto也可能阻塞
ret = sendto(s->udp_fd, buf, size, 0, ...);
return ret;
}
解决方案:
- 使用fifo_size选项 (推荐):
c
// 在URL中添加fifo_size参数
rtp://server:port?fifo_size=500000
// 或通过AVDictionary设置
AVDictionary *opts = NULL;
av_dict_set_int(&opts, "fifo_size", 500000, 0);
- 增大发送缓冲区:
c
av_dict_set_int(&opts, "buffer_size", 1048576, 0); // 1MB
- 处理FIFO满的情况:
c
ret = rtsp_write_packet(s, pkt);
if (ret == AVERROR(ENOMEM)) {
// FIFO满了,可以选择:
// 1. 等待后重试
av_usleep(1000);
// 2. 丢弃非关键帧
if (!(pkt->flags & AV_PKT_FLAG_KEY))
return 0;
}
6.2 网络延迟优化
优化策略:
| 优化项 | 方法 | 效果 |
|---|---|---|
| 传输方式 | 使用UDP而非TCP | 减少延迟 |
| 缓冲区大小 | 合理设置buffer_size | 平衡延迟和稳定性 |
| RTP包大小 | 优化pkt_size | 减少网络开销 |
| 时间戳精度 | 使用高精度时间戳 | 提高同步精度 |
代码示例:
c
// 设置UDP传输
AVDictionary *opts = NULL;
av_dict_set(&opts, "rtsp_transport", "udp", 0);
// 设置缓冲区大小
av_dict_set_int(&opts, "buffer_size", 65536, 0);
// 设置RTP包大小
av_dict_set_int(&opts, "pkt_size", 1400, 0);
6.3 多流同步问题
问题: 多个流(音视频)时间戳不同步
解决方案:
- 统一时间基准:
c
// 设置统一的起始时间
s->start_time_realtime = av_gettime();
// 所有流使用相同的时间基准
for (i = 0; i < s->nb_streams; i++) {
s->streams[i]->time_base = (AVRational){1, 90000};
}
- RTP时间戳同步:
c
// 计算相对时间戳
rtp_timestamp = av_rescale_q(pkt->pts - stream->start_time,
stream->time_base,
rtp_time_base);
6.4 错误恢复机制
常见错误及处理:
| 错误码 | 含义 | 处理方法 |
|---|---|---|
| 401 | 未授权 | 添加认证信息 |
| 461 | 不支持的协议 | 尝试其他传输方式 |
| 454 | Session Not Found | 重新建立会话 |
| 500 | 服务器错误 | 重试或报告错误 |
错误处理代码:
c
// 认证重试
if (reply->status_code == 401 &&
(cur_auth_type == HTTP_AUTH_NONE || rt->auth_state.stale) &&
rt->auth_state.auth_type != HTTP_AUTH_NONE && attempts < 2)
goto retry;
// 传输方式回退
if (reply->status_code == 461 && i == 0) {
// 尝试TCP
err = ff_rtsp_make_setup_request(s, host, port,
RTSP_LOWER_TRANSPORT_TCP, ...);
}
7. 实际应用示例
7.1 完整推流示例
c
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
int main(int argc, char *argv[])
{
AVFormatContext *fmt_ctx = NULL;
AVStream *video_stream = NULL, *audio_stream = NULL;
AVPacket pkt;
int ret;
AVDictionary *opts = NULL;
// 1. 分配输出上下文
avformat_alloc_output_context2(&fmt_ctx, NULL, "rtsp",
"rtsp://192.168.1.100:554/live");
if (!fmt_ctx) {
fprintf(stderr, "Could not create output context\n");
return 1;
}
// 2. 添加视频流
video_stream = avformat_new_stream(fmt_ctx, NULL);
if (!video_stream) {
fprintf(stderr, "Could not create video stream\n");
return 1;
}
video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
video_stream->codecpar->codec_id = AV_CODEC_ID_H264;
video_stream->codecpar->width = 1920;
video_stream->codecpar->height = 1080;
video_stream->codecpar->format = AV_PIX_FMT_YUV420P;
video_stream->time_base = (AVRational){1, 25}; // 25fps
// 3. 添加音频流
audio_stream = avformat_new_stream(fmt_ctx, NULL);
if (!audio_stream) {
fprintf(stderr, "Could not create audio stream\n");
return 1;
}
audio_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
audio_stream->codecpar->codec_id = AV_CODEC_ID_AAC;
audio_stream->codecpar->sample_rate = 48000;
audio_stream->codecpar->ch_layout.nb_channels = 2;
audio_stream->time_base = (AVRational){1, 48000};
// 4. 设置选项
av_dict_set(&opts, "rtsp_transport", "udp", 0);
av_dict_set_int(&opts, "fifo_size", 500000, 0);
av_dict_set_int(&opts, "buffer_size", 1048576, 0);
// 5. 打开输出 (执行ANNOUNCE + SETUP + RECORD)
ret = avformat_write_header(fmt_ctx, &opts);
if (ret < 0) {
fprintf(stderr, "Error opening output: %s\n", av_err2str(ret));
return 1;
}
// 6. 发送数据包
while (1) {
// 编码得到AVPacket (这里简化,实际需要编码器)
// ...
// 写入RTSP
ret = av_interleaved_write_frame(fmt_ctx, &pkt);
if (ret < 0) {
if (ret == AVERROR(ENOMEM)) {
// FIFO满了,等待后重试
av_usleep(1000);
continue;
}
fprintf(stderr, "Error writing packet: %s\n", av_err2str(ret));
break;
}
av_packet_unref(&pkt);
}
// 7. 关闭 (发送TEARDOWN)
av_write_trailer(fmt_ctx);
avformat_free_context(fmt_ctx);
av_dict_free(&opts);
return 0;
}
7.2 使用FFmpeg命令行推流
bash
# 基本推流命令
ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac \
-f rtsp rtsp://server:554/live
# 指定UDP传输
ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac \
-rtsp_transport udp \
-f rtsp rtsp://server:554/live
# 指定端口范围
ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac \
-rtsp_transport udp \
-min_port 5000 -max_port 6000 \
-f rtsp rtsp://server:554/live
# 使用TCP传输
ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac \
-rtsp_transport tcp \
-f rtsp rtsp://server:554/live
8. 性能优化建议
8.1 传输方式选择
| 场景 | 推荐传输方式 | 原因 |
|---|---|---|
| 局域网 | UDP | 低延迟,低开销 |
| 公网/防火墙 | TCP | 可靠传输,防火墙友好 |
| 高质量网络 | UDP | 最佳性能 |
| 弱网环境 | TCP | 自动重传 |
8.2 缓冲区配置
推荐配置:
c
// UDP模式
av_dict_set_int(&opts, "fifo_size", 500000, 0); // 500KB FIFO
av_dict_set_int(&opts, "buffer_size", 1048576, 0); // 1MB发送缓冲区
av_dict_set_int(&opts, "pkt_size", 1400, 0); // 1400字节RTP包
// TCP模式
av_dict_set_int(&opts, "buffer_size", 65536, 0); // 64KB缓冲区
8.3 编码参数优化
H.264编码优化:
c
// 设置关键帧间隔
av_dict_set(&opts, "g", "50", 0); // GOP大小
// 设置码率
av_dict_set(&opts, "b:v", "2000k", 0);
// 设置profile
av_dict_set(&opts, "profile", "baseline", 0);
AAC编码优化:
c
// 设置码率
av_dict_set(&opts, "b:a", "128k", 0);
// 设置采样率
audio_stream->codecpar->sample_rate = 48000;
9. 调试技巧
9.1 启用详细日志
c
// 设置日志级别
av_log_set_level(AV_LOG_DEBUG);
// 或在命令行
ffmpeg -loglevel debug ...
9.2 抓包分析
使用Wireshark抓包:
- 过滤RTSP:
rtsp - 过滤RTP:
rtp - 分析SDP: 查看ANNOUNCE请求的Content
- 分析RTP: 查看序列号、时间戳、负载类型
关键字段检查:
- RTP序列号: 是否连续(检测丢包)
- RTP时间戳: 是否递增(检测时间同步)
- RTCP报告: 查看丢包率、延迟
9.3 常见问题排查
问题1: SETUP失败
可能原因:
- 端口被占用
- 防火墙阻止
- 服务器不支持请求的传输方式
解决方法:
- 检查端口范围
- 尝试TCP传输
- 查看服务器日志
问题2: 推流中断
可能原因:
- 网络不稳定
- 缓冲区满
- 服务器超时
解决方法:
- 增加缓冲区大小
- 使用TCP传输
- 检查网络连接
问题3: 音视频不同步
可能原因:
- 时间戳计算错误
- 编码延迟不一致
- 网络抖动
解决方法:
- 统一时间基准
- 使用RTCP同步
- 增加缓冲区
10. 总结
10.1 核心要点
- RTSP推流流程: OPTIONS → ANNOUNCE → SETUP → RECORD → 数据传输 → TEARDOWN
- SDP作用: 描述流信息,包含编解码器、端口等参数
- RTP封装: 将音视频数据封装成RTP包,添加时间戳、序列号等
- 传输方式: UDP(低延迟)和TCP(可靠传输)各有优劣
- 时间戳同步: 关键保证音视频同步
10.2 最佳实践
- 传输选择: 根据网络环境选择UDP或TCP
- 缓冲区配置: 合理设置fifo_size和buffer_size
- 错误处理: 实现完善的错误处理和重试机制
- 性能监控: 监控丢包率、延迟等指标
- 日志记录: 启用详细日志便于问题排查
10.3 技术趋势
- WebRTC: 基于RTP/RTCP,支持更好的NAT穿透
- SRT: 可靠UDP传输,适合弱网环境
- QUIC: 基于UDP的可靠传输,减少延迟
参考文献
- RFC 2326 - Real Time Streaming Protocol (RTSP)
- RFC 3550 - RTP: A Transport Protocol for Real-Time Applications
- RFC 3640 - RTP Payload Format for Transport of MPEG-4 Elementary Streams
- RFC 6184 - RTP Payload Format for H.264 Video
- FFmpeg官方文档: https://ffmpeg.org/documentation.html
作者简介
本文基于FFmpeg 8.0源码分析,深入解析RTSP推流的完整流程。如有问题或建议,欢迎在评论区讨论。
版权声明: 本文为原创文章,转载请注明出处。