FFmpeg RTSP拉流流程深度解析
本文基于FFmpeg 8.0源码,深入剖析RTSP协议拉流的完整实现机制,包含详细的流程图、状态机分析和核心代码解读。
📚 目录
- RTSP协议基础
- [FFmpeg RTSP架构总览](#FFmpeg RTSP架构总览)
- RTSP拉流完整流程
- 核心数据结构详解
- 状态机设计与转换
- 传输模式深度对比
- 关键函数源码解析
- RTSP服务端监听模式
- 错误处理与容错机制
- 性能优化与实践建议
1. RTSP协议基础
1.1 什么是RTSP
RTSP(Real Time Streaming Protocol,实时流传输协议)是一种应用层协议,用于控制流媒体服务器的实时传输。它并不直接传输音视频数据,而是作为"遥控器"来控制媒体流的播放、暂停、定位等操作。
1.2 RTSP核心特性
| 特性 | 说明 |
|---|---|
| 协议类型 | 应用层协议,基于文本 |
| 默认端口 | 554(RTSP)、322(RTSPS加密) |
| 传输协议 | 默认使用TCP作为控制信道 |
| 数据传输 | 通常使用RTP/RTCP协议 |
| 会话管理 | 基于Session ID的会话机制 |
| 状态管理 | 有状态协议,维护客户端-服务器状态 |
1.3 RTSP与RTP的关系
┌─────────────┐ 控制命令 ┌─────────────┐
│ RTSP客户端 │ ───────────────────> │ RTSP服务器 │
│ │ OPTIONS/DESCRIBE │ │
│ │ SETUP/PLAY │ │
└──────┬──────┘ └──────┬──────┘
│ │
│ Session管理 │
│ <─────────────────────────────────│
│ │
│ │ 媒体流
│ ▼
│ ┌─────────────┐
│ │ RTP/RTCP │
│ └──────┬──────┘
│ │
│ │ UDP或TCP
│ ▼
│ ┌─────────────┐
│ │ 媒体数据接收 │
│ └─────────────┘
│
└────────────────────────────────────┘
核心区别:
- RTSP: 控制层,发送命令(类似HTTP)
- RTP: 传输层,传输音视频数据包
- RTCP: 控制协议,提供QoS反馈
1.4 RTSP核心命令
| 命令 | 功能 | 说明 |
|---|---|---|
| OPTIONS | 查询功能 | 询问服务器支持哪些方法 |
| DESCRIBE | 获取描述 | 获取媒体流的SDP描述信息 |
| SETUP | 建立会话 | 为单个流建立传输机制(UDP/TCP) |
| PLAY | 开始播放 | 启动数据传输 |
| PAUSE | 暂停播放 | 暂停数据传输(保持会话) |
| TEARDOWN | 终止会话 | 释放所有资源,结束会话 |
| ANNOUNCE | 公告(服务端模式) | 客户端向服务器发送媒体描述 |
| RECORD | 录制(服务端模式) | 开始接收数据 |
2. FFmpeg RTSP架构总览
2.1 核心文件组成
FFmpeg的RTSP实现主要由以下文件组成:
libavformat/
├── rtsp.h # RTSP核心数据结构和函数声明
├── rtsp.c # RTSP协议处理核心逻辑(2700+行)
├── rtspdec.c # RTSP解复用器(拉流)(1011行)
├── rtspenc.c # RTSP复用器(推流)(260行)
├── rtpproto.c # RTP协议实现
├── rtpdec.c # RTP解复用
└── sdp.c # SDP解析
2.2 模块交互关系
┌─────────────────────────────────────────┐
│ 应用层 │
│ ┌───────────────────────────────────┐ │
│ │ FFmpeg应用 (ffplay/ffmpeg) │ │
│ └──────────────┬────────────────────┘ │
└─────────────────┼──────────────────────┘
│
┌─────────────────▼──────────────────────┐
│ AVFormat层 │
│ ┌──────────────────┐ ┌─────────────┐ │
│ │ RTSP Demuxer │ │ RTSP Core │ │
│ │ (rtspdec.c) │ │ (rtsp.c) │ │
│ └────────┬─────────┘ └──────┬──────┘ │
└───────────┼────────────────────┼───────┘
│ │
│ │
┌───────────▼────────────────────▼───────┐
│ 传输层 │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ RTP Protocol │ │ TCP Connection │ │
│ │ (rtpproto.c) │ │ UDP Socket │ │
│ └──────┬───────┘ └────────────────┘ │
└─────────┼──────────────────────────────┘
│
┌─────────▼──────────────────────────────┐
│ 解析层 │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ SDP Parser │ │ RTP Decoder │ │
│ │ (sdp.c) │ │ (rtpdec.c) │ │
│ └──────────────┘ └────────────────┘ │
└─────────────────────────────────────────┘
2.3 架构分层
| 层次 | 职责 | 核心组件 |
|---|---|---|
| 应用层 | 用户接口调用 | ffplay, ffmpeg, API调用 |
| Demuxer层 | 流解复用控制 | rtsp_read_header(), rtsp_read_packet() |
| 协议层 | RTSP协议处理 | ff_rtsp_send_cmd(), ff_rtsp_connect() |
| 传输层 | 数据收发 | RTP over UDP/TCP |
| 解析层 | 数据包解析 | SDP解析, RTP解包 |
3. RTSP拉流完整流程
3.1 整体流程图
RTSP拉流完整时序流程:
┌─────────────┐ ┌─────────────┐
│ RTSP客户端 │ │ RTSP服务器 │
└──────┬──────┘ └──────┬──────┘
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段1: 初始化连接 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──TCP连接(端口554)───────────────────────────────>│
│<───────────────────────────────连接建立──────────│
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段2: 能力协商 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──OPTIONS rtsp://server/stream───────────────────>│
│<──200 OK │
│ Public: DESCRIBE,SETUP,PLAY... │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段3: 获取媒体描述 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──DESCRIBE rtsp://server/stream───────────────────>│
│ Accept: application/sdp │
│<──200 OK │
│ Content-Type: application/sdp │
│ [SDP内容] │
│ │
│ [解析SDP,创建媒体流] │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段4: 建立传输通道(每个流) │ │
│ └────────────────────────────────────────────┘ │
│ │
│──SETUP rtsp://server/stream/track1──────────────>│
│ Transport: RTP/AVP;unicast;client_port=5000-5001│
│<──200 OK │
│ Session: 12345678 │
│ Transport: RTP/AVP;server_port=6000-6001 │
│ │
│──SETUP rtsp://server/stream/track2──────────────>│
│ Transport: RTP/AVP;unicast;client_port=5002-5003│
│<──200 OK │
│ Session: 12345678 │
│ Transport: RTP/AVP;server_port=6002-6003 │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段5: 开始播放 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──PLAY rtsp://server/stream───────────────────────>│
│ Session: 12345678 │
│ Range: npt=0.000- │
│<──200 OK │
│ RTP-Info: url=track1;seq=xxx;rtptime=yyy │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段6: 数据传输(持续) │ │
│ └────────────────────────────────────────────┘ │
│ │
│<──RTP数据包(音视频)────────────────────────────│
│──RTCP反馈(周期性)─────────────────────────────>│
│<──RTP数据包(音视频)────────────────────────────│
│──RTCP反馈(周期性)─────────────────────────────>│
│ ... (持续传输) │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段7: 保活机制(周期性) │ │
│ └────────────────────────────────────────────┘ │
│ │
│──OPTIONS/GET_PARAMETER──────────────────────────>│
│ Session: 12345678 │
│<──200 OK │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段8: 结束会话 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──TEARDOWN rtsp://server/stream──────────────────>│
│ Session: 12345678 │
│<──200 OK │
│ │
│ [关闭所有连接] │
│ │
时序表格式:
| 阶段 | 客户端操作 | 服务器响应 | 说明 |
|---|---|---|---|
| 1. 初始化 | TCP连接(端口554) | 连接建立 | 建立控制通道 |
| 2. 能力协商 | OPTIONS请求 | 200 OK Public: DESCRIBE,SETUP,PLAY... | 查询支持的方法 |
| 3. 获取描述 | DESCRIBE请求 Accept: application/sdp | 200 OK Content-Type: application/sdp [SDP内容] | 获取媒体流描述 |
| 4. 建立传输 | SETUP track1 Transport: RTP/AVP;unicast;client_port=5000-5001 | 200 OK Session: 12345678 Transport: ...;server_port=6000-6001 | 为每个流建立传输通道 |
| SETUP track2 Transport: RTP/AVP;unicast;client_port=5002-5003 | 200 OK Session: 12345678 Transport: ...;server_port=6002-6003 | ||
| 5. 开始播放 | PLAY请求 Session: 12345678 Range: npt=0.000- | 200 OK RTP-Info: url=track1;seq=xxx;rtptime=yyy | 启动数据传输 |
| 6. 数据传输 | 接收RTP包 发送RTCP反馈 | 发送RTP包 接收RTCP反馈 | 持续传输音视频数据 |
| 7. 保活 | OPTIONS/GET_PARAMETER Session: 12345678 | 200 OK | 防止连接超时 |
| 8. 结束 | TEARDOWN请求 Session: 12345678 | 200 OK | 释放所有资源 |
3.2 详细阶段分解
阶段1: 初始化(rtsp_read_header)
c
// 文件: rtspdec.c
static int rtsp_read_header(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
int ret;
// 判断是客户端模式还是服务端监听模式
if (rt->rtsp_flags & RTSP_FLAG_LISTEN) {
ret = rtsp_listen(s); // 服务端模式
} else {
ret = ff_rtsp_connect(s); // 客户端模式
// 如果不是初始暂停,立即开始播放
if (!rt->initial_pause) {
ret = rtsp_read_play(s);
}
}
return ret;
}
流程说明:
- 检查网络环境初始化
- 解析URL提取主机、端口、路径
- 建立TCP控制连接(默认端口554)
- 初始化状态机为IDLE状态
阶段2: DESCRIBE获取SDP
c
// 文件: rtsp.c (简化逻辑)
int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply)
{
RTSPState *rt = s->priv_data;
char cmd[MAX_URL_SIZE];
unsigned char *content = NULL;
// 构造DESCRIBE请求
snprintf(cmd, sizeof(cmd), "Accept: application/sdp\r\n");
// 发送DESCRIBE命令
ff_rtsp_send_cmd(s, "DESCRIBE", rt->control_uri, cmd, reply, &content);
if (reply->status_code != RTSP_STATUS_OK) {
return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA);
}
// 解析SDP描述
ret = ff_sdp_parse(s, (const char *)content);
av_freep(&content);
return ret;
}
SDP示例:
sdp
v=0
o=- 1234567890 1234567890 IN IP4 192.168.1.100
s=RTSP Stream
c=IN IP4 0.0.0.0
t=0 0
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=control:track1
m=audio 0 RTP/AVP 97
a=rtpmap:97 MPEG4-GENERIC/44100/2
a=control:track2
阶段3: SETUP建立传输
c
// 简化的SETUP逻辑
int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port,
int lower_transport, const char *real_challenge)
{
RTSPState *rt = s->priv_data;
RTSPMessageHeader reply;
for (int i = 0; i < rt->nb_rtsp_streams; i++) {
RTSPStream *rtsp_st = rt->rtsp_streams[i];
char transport[2048];
// 构造Transport头
if (lower_transport == RTSP_LOWER_TRANSPORT_TCP) {
snprintf(transport, sizeof(transport),
"Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d",
i * 2, i * 2 + 1);
} else {
snprintf(transport, sizeof(transport),
"Transport: RTP/AVP;unicast;client_port=%d-%d",
rt->rtp_port_min + i * 2, rt->rtp_port_min + i * 2 + 1);
}
// 发送SETUP命令
ff_rtsp_send_cmd(s, "SETUP", rtsp_st->control_url,
transport, &reply, NULL);
// 保存Session ID(第一次)
if (i == 0)
av_strlcpy(rt->session_id, reply.session_id, sizeof(rt->session_id));
}
return 0;
}
阶段4: PLAY开始播放
c
// 文件: rtspdec.c
static int rtsp_read_play(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
RTSPMessageHeader reply1, *reply = &reply1;
char cmd[MAX_URL_SIZE];
// 发送NAT穿透包(UDP模式)
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP) {
for (int i = 0; i < rt->nb_rtsp_streams; i++) {
ff_rtp_send_punch_packets(rt->rtsp_streams[i]->rtp_handle);
}
}
// 构造Range参数(用于seek)
if (rt->state != RTSP_STATE_PAUSED) {
snprintf(cmd, sizeof(cmd),
"Range: npt=%"PRId64".%03"PRId64"-\r\n",
rt->seek_timestamp / AV_TIME_BASE,
rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000);
} else {
cmd[0] = 0;
}
// 发送PLAY命令
ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL);
if (reply->status_code != RTSP_STATUS_OK) {
return ff_rtsp_averror(reply->status_code, -1);
}
// 更新状态为STREAMING
rt->state = RTSP_STATE_STREAMING;
return 0;
}
阶段5: 数据接收循环
c
// 文件: rtspdec.c
static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt)
{
RTSPState *rt = s->priv_data;
int ret;
retry:
// 获取RTP数据包
ret = ff_rtsp_fetch_packet(s, pkt);
if (ret < 0) {
// UDP超时,尝试切换到TCP
if (ret == AVERROR(ETIMEDOUT) && !rt->packets) {
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP &&
rt->lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_TCP)) {
av_log(s, AV_LOG_WARNING, "UDP timeout, retrying with TCP\n");
rtsp_read_pause(s);
rt->session_id[0] = '\0';
if (resetup_tcp(s) == 0) {
rt->state = RTSP_STATE_IDLE;
rtsp_read_play(s);
goto retry;
}
}
}
return ret;
}
rt->packets++;
// 发送保活命令(超时时间的一半)
if ((av_gettime_relative() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2) {
if (rt->server_type == RTSP_SERVER_WMS || rt->get_parameter_supported) {
ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL);
} else {
ff_rtsp_send_cmd_async(s, "OPTIONS", rt->control_uri, NULL);
}
}
return 0;
}
3.3 命令交互时序表
| 序号 | 命令 | 方向 | 触发时机 | 关键参数 |
|---|---|---|---|---|
| 1 | OPTIONS | C→S | 连接建立后 | - |
| 2 | DESCRIBE | C→S | OPTIONS之后 | Accept: application/sdp |
| 3 | SETUP | C→S | 每个媒体流 | Transport, Session |
| 4 | PLAY | C→S | 所有流SETUP完成 | Range, Session |
| 5 | GET_PARAMETER/OPTIONS | C→S | 周期性保活 | Session |
| 6 | PAUSE | C→S | 用户暂停 | Session |
| 7 | TEARDOWN | C→S | 结束播放 | Session |
4. 核心数据结构详解
4.1 RTSPState - 全局状态管理
c
typedef struct RTSPState {
const AVClass *class;
// === 连接管理 ===
URLContext *rtsp_hd; // RTSP TCP控制连接句柄
URLContext *rtsp_hd_out; // 输出连接(HTTP隧道)
// === 流管理 ===
int nb_rtsp_streams; // 流的数量
struct RTSPStream **rtsp_streams; // 流数组
// === 状态管理 ===
enum RTSPClientState state; // 当前状态(IDLE/PAUSED/STREAMING)
int seq; // RTSP命令序列号
char session_id[512]; // 会话ID
int64_t seek_timestamp; // Seek目标时间戳
// === 传输配置 ===
enum RTSPTransport transport; // RTP/RDT
enum RTSPLowerTransport lower_transport; // UDP/TCP
enum RTSPServerType server_type; // 服务器类型
// === 保活机制 ===
int timeout; // 服务器超时时间(秒)
int64_t last_cmd_time; // 上次命令时间
// === 端口配置 ===
int rtp_port_min; // RTP最小端口(默认5000)
int rtp_port_max; // RTP最大端口(默认65000)
// === 认证 ===
char auth[128]; // 认证信息
HTTPAuthState auth_state; // 认证状态
// === 缓冲管理 ===
uint8_t *recvbuf; // 接收缓冲区
int buffer_size; // 缓冲区大小
int reordering_queue_size; // 重排序队列大小
// === 统计信息 ===
uint64_t packets; // 收到的包数量
int nb_byes; // 收到的BYE包数量
} RTSPState;
关键字段说明:
| 字段 | 类型 | 作用 |
|---|---|---|
state |
enum | 状态机核心,控制命令流程 |
session_id |
char[512] | 会话唯一标识,每个命令必带 |
seq |
int | CSeq序列号,确保命令顺序 |
lower_transport |
enum | UDP/TCP选择,影响数据传输方式 |
rtsp_streams |
指针数组 | 管理所有媒体流 |
4.2 RTSPStream - 单个媒体流
c
typedef struct RTSPStream {
// === 连接 ===
URLContext *rtp_handle; // RTP连接(UDP模式)
void *transport_priv; // RTP解析上下文
// === 流信息 ===
int stream_index; // 对应的AVStream索引
char control_url[MAX_URL_SIZE]; // 流的控制URL
// === TCP交错模式 ===
int interleaved_min; // 交错通道最小ID
int interleaved_max; // 交错通道最大ID
// === SDP信息 ===
int sdp_port; // SDP中的端口
struct sockaddr_storage sdp_ip; // SDP中的IP地址
int sdp_payload_type; // 负载类型
// === 动态协议 ===
const RTPDynamicProtocolHandler *dynamic_handler;
PayloadContext *dynamic_protocol_context;
// === RTCP ===
int feedback; // 是否启用RTCP反馈
uint32_t ssrc; // 同步源标识符
} RTSPStream;
4.3 RTSPMessageHeader - 消息头
c
typedef struct RTSPMessageHeader {
// === 基础信息 ===
int content_length; // 内容长度
enum RTSPStatusCode status_code; // 状态码(200, 404等)
int seq; // CSeq序列号
// === 会话管理 ===
char session_id[512]; // 会话ID
int timeout; // 超时时间
// === 传输配置 ===
int nb_transports; // Transport数量
RTSPTransportField transports[RTSP_MAX_TRANSPORTS];
// === 播放控制 ===
int64_t range_start; // Range开始时间
int64_t range_end; // Range结束时间
// === 服务器信息 ===
char server[64]; // Server字段
char content_type[64]; // Content-Type
char reason[256]; // 状态原因
// === Real特殊字段 ===
char real_challenge[64]; // RealChallenge
} RTSPMessageHeader;
4.4 RTSPTransportField - 传输字段
c
typedef struct RTSPTransportField {
// === 端口配置 ===
int client_port_min; // 客户端RTP端口
int client_port_max; // 客户端RTCP端口
int server_port_min; // 服务器RTP端口
int server_port_max; // 服务器RTCP端口
int port_min, port_max; // 多播端口
// === TCP交错 ===
int interleaved_min; // 交错最小通道
int interleaved_max; // 交错最大通道
// === 传输协议 ===
enum RTSPTransport transport; // RTP/RDT
enum RTSPLowerTransport lower_transport; // UDP/TCP
// === 网络配置 ===
struct sockaddr_storage destination; // 目标地址
char source[INET6_ADDRSTRLEN + 1]; // 源地址
int ttl; // 多播TTL
int mode_record; // 录制模式标志
} RTSPTransportField;
5. 状态机设计与转换
5.1 状态定义
c
enum RTSPClientState {
RTSP_STATE_IDLE, // 空闲:未初始化或已TEARDOWN
RTSP_STATE_STREAMING, // 流媒体中:正在接收数据
RTSP_STATE_PAUSED, // 暂停:已SETUP但未PLAY
RTSP_STATE_SEEKING, // Seeking:请求跳转中
};
5.2 状态转换图
RTSP状态机转换流程:
┌─────────┐
│ 初始化 │
└────┬────┘
│
▼
┌─────────┐
│ IDLE │◄──────────┐
│ 空闲状态 │ │
└────┬────┘ │
│ │
OPTIONS/DESCRIBE│ │
│ │
│ SETUP成功 │
▼ │
┌─────────┐ │
│ PAUSED │ │
│ 暂停状态 │ │
└────┬────┘ │
│ │
SETUP更多流 │ │
│ │
│ PLAY成功 │
▼ │
┌─────────┐ │
│STREAMING│ │
│流媒体状态│ │
└────┬────┘ │
│ │
av_seek_frame() │ │
│ │
▼ │
┌─────────┐ │
│ SEEKING │ │
│ 跳转状态 │ │
└────┬────┘ │
│ │
│ PLAY成功 │
│ │
└────────────────┘
PAUSE ──────────┘
TEARDOWN ───────┘
TEARDOWN ───────┘
TEARDOWN ───────┘
状态说明表:
| 状态 | 可执行命令 | 说明 |
|---|---|---|
| IDLE | OPTIONS DESCRIBE TEARDOWN | 空闲状态,未建立会话 |
| PAUSED | SETUP PLAY OPTIONS TEARDOWN | 已建立传输通道,但未开始播放 |
| STREAMING | PAUSE OPTIONS GET_PARAMETER TEARDOWN | 正在接收数据流 |
| SEEKING | - | 临时状态,执行跳转操作 |
5.3 状态转换条件表
| 当前状态 | 触发事件 | 目标状态 | 执行的RTSP命令 | 代码位置 |
|---|---|---|---|---|
| IDLE | 连接成功 + SETUP | PAUSED | SETUP | ff_rtsp_make_setup_request() |
| PAUSED | 开始播放 | STREAMING | PLAY | rtsp_read_play() |
| STREAMING | 用户暂停 | PAUSED | PAUSE | rtsp_read_pause() |
| STREAMING | 用户Seek | SEEKING | PAUSE + PLAY | rtsp_read_seek() |
| SEEKING | Seek完成 | STREAMING | - | rtsp_read_play() |
| 任意 | 关闭连接 | IDLE | TEARDOWN | rtsp_read_close() |
5.4 状态校验机制
在服务端监听模式下,FFmpeg会严格校验命令是否符合当前状态:
c
// 文件: rtspdec.c - parse_command_line()
if (rt->state == RTSP_STATE_IDLE) {
// IDLE状态只接受ANNOUNCE和OPTIONS
if ((*methodcode != ANNOUNCE) && (*methodcode != OPTIONS)) {
av_log(s, AV_LOG_ERROR, "Unexpected command in Idle State %s\n", line);
return AVERROR_PROTOCOL_NOT_FOUND;
}
} else if (rt->state == RTSP_STATE_PAUSED) {
// PAUSED状态接受OPTIONS、RECORD、SETUP
if ((*methodcode != OPTIONS) && (*methodcode != RECORD) && (*methodcode != SETUP)) {
av_log(s, AV_LOG_ERROR, "Unexpected command in Paused State %s\n", line);
return AVERROR_PROTOCOL_NOT_FOUND;
}
} else if (rt->state == RTSP_STATE_STREAMING) {
// STREAMING状态接受PAUSE、OPTIONS、TEARDOWN
if ((*methodcode != PAUSE) && (*methodcode != OPTIONS) && (*methodcode != TEARDOWN)) {
av_log(s, AV_LOG_ERROR, "Unexpected command in Streaming State %s\n", line);
return AVERROR_PROTOCOL_NOT_FOUND;
}
}
6. 传输模式深度对比
6.1 传输模式概览
FFmpeg RTSP支持三种底层传输模式:
c
enum RTSPLowerTransport {
RTSP_LOWER_TRANSPORT_UDP = 0, // UDP单播
RTSP_LOWER_TRANSPORT_TCP = 1, // TCP交错
RTSP_LOWER_TRANSPORT_UDP_MULTICAST = 2, // UDP组播
};
6.2 详细对比表
| 特性 | UDP单播 | TCP交错 | UDP组播 |
|---|---|---|---|
| 传输协议 | UDP | TCP | UDP |
| 端口使用 | 每个流2个端口 (RTP+RTCP) | 复用RTSP连接 (通道ID区分) | 组播地址+端口 |
| NAT穿透 | 需要打洞 | 天然支持 | 不支持 |
| 带宽效率 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 可靠性 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 延迟 | 低 | 中 | 低 |
| 适用场景 | 局域网,对丢包不敏感 | 公网,需要可靠传输 | 多客户端同播 |
| 防火墙友好 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ |
6.3 UDP单播模式
工作原理
UDP单播模式架构:
┌─────────────────────────────────────────────────────┐
│ 客户端 │
│ ┌────────────────┐ ┌──────────────────────────┐ │
│ │ RTSP控制 │ │ 视频流 │ │
│ │ TCP 554 │ │ RTP接收 UDP 5000 │ │
│ │ │ │ RTCP接收 UDP 5001 │ │
│ └───────┬───────┘ └──────────────────────────┘ │
│ │ │
│ │ ┌──────────────────────────┐ │
│ │ │ 音频流 │ │
│ │ │ RTP接收 UDP 5002 │ │
│ │ │ RTCP接收 UDP 5003 │ │
│ │ └──────────────────────────┘ │
└──────────┼──────────────────────────────────────────┘
│
│ 控制命令 (双向)
│
┌──────────▼──────────────────────────────────────────┐
│ 服务器 │
│ ┌────────────────┐ ┌──────────────────────────┐ │
│ │ RTSP控制 │ │ 视频流 │ │
│ │ TCP 554 │ │ RTP发送 UDP 6000 │ │
│ │ │ │ RTCP发送 UDP 6001 │ │
│ └───────────────┘ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ 音频流 │ │
│ │ RTP发送 UDP 6002 │ │
│ │ RTCP发送 UDP 6003 │ │
│ └──────────────────────────┘ │
└────────────────────────────────────────────────────┘
数据流向:
RTP: 服务器 → 客户端 (单向)
RTCP: 客户端 ↔ 服务器 (双向反馈)
SETUP请求示例
http
SETUP rtsp://server/stream/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=5000-5001
User-Agent: FFmpeg
服务器响应
http
RTSP/1.0 200 OK
CSeq: 3
Session: 12345678;timeout=60
Transport: RTP/AVP;unicast;client_port=5000-5001;server_port=6000-6001
核心代码
c
// UDP模式的SETUP处理
if (request.transports[0].lower_transport == RTSP_LOWER_TRANSPORT_UDP) {
int localport = rt->rtp_port_min;
do {
AVDictionary *opts = NULL;
av_dict_set_int(&opts, "buffer_size", rt->buffer_size, 0);
// 打开UDP端口
ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL);
ret = ffurl_open_whitelist(&rtsp_st->rtp_handle, url,
AVIO_FLAG_READ_WRITE,
&s->interrupt_callback, &opts,
s->protocol_whitelist,
s->protocol_blacklist, NULL);
av_dict_free(&opts);
if (ret)
localport += 2; // RTP端口必须是偶数
} while (ret || localport > rt->rtp_port_max);
// 获取实际绑定的本地端口
localport = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
// 构造响应
snprintf(responseheaders, sizeof(responseheaders),
"Transport: RTP/AVP/UDP;unicast;mode=record;"
"client_port=%d-%d;server_port=%d-%d\r\n",
request.transports[0].client_port_min,
request.transports[0].client_port_max,
localport, localport + 1);
}
6.4 TCP交错模式
工作原理
TCP交错模式架构:
┌─────────────────────────────────────────────────────┐
│ 客户端 │
│ ┌──────────────────────────────────────────────┐ │
│ │ RTSP/RTP 统一TCP连接 (端口554) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ TCP连接内部通道复用 │ │ │
│ │ │ │ │ │
│ │ │ 通道0: Video RTP │ │ │
│ │ │ 通道1: Video RTCP │ │ │
│ │ │ 通道2: Audio RTP │ │ │
│ │ │ 通道3: Audio RTCP │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
│ 单条TCP连接
│ 通过通道ID区分
│
┌───────────────────────▼─────────────────────────────┐
│ 服务器 │
│ ┌──────────────────────────────────────────────┐ │
│ │ RTSP/RTP 统一TCP连接 (端口554) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ TCP连接内部通道复用 │ │ │
│ │ │ │ │ │
│ │ │ 通道0: Video RTP │ │ │
│ │ │ 通道1: Video RTCP │ │ │
│ │ │ 通道2: Audio RTP │ │ │
│ │ │ 通道3: Audio RTCP │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
数据包格式:
+--------+--------+--------+--------+--------+---...---+
| '$' |通道ID | 长度(2字节) | RTP数据 |
+--------+--------+--------+--------+--------+---...---+
0 1 2 3 4 ...
数据包格式
在TCP交错模式下,RTP/RTCP数据包以特殊格式嵌入RTSP连接:
+--------+--------+--------+--------+--------+---...---+
| '$' |通道ID | 长度(2字节) | RTP数据 |
+--------+--------+--------+--------+--------+---...---+
0 1 2 3 4 ...
SETUP请求示例
http
SETUP rtsp://server/stream/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
User-Agent: FFmpeg
服务器响应
http
RTSP/1.0 200 OK
CSeq: 3
Session: 12345678;timeout=60
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
核心代码
c
// TCP模式的数据接收
int ff_rtsp_tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st,
uint8_t *buf, int buf_size)
{
RTSPState *rt = s->priv_data;
int id, len, i, ret;
RTSPStream *rtsp_st;
redo:
for (;;) {
RTSPMessageHeader reply;
// 尝试读取RTSP回复或等待'$'标记
ret = ff_rtsp_read_reply(s, &reply, NULL, 1, NULL);
if (ret < 0)
return ret;
if (ret == 1) // 收到'$',表示后面是RTP数据
break;
}
// 读取3字节头:'$' + 通道ID + 长度
ret = ffurl_read_complete(rt->rtsp_hd, buf, 3);
if (ret != 3)
return AVERROR(EIO);
id = buf[0]; // 通道ID
len = AV_RB16(buf + 1); // 数据长度(大端序)
if (len > buf_size || len < 8)
goto redo;
// 读取实际RTP/RTCP数据
ret = ffurl_read_complete(rt->rtsp_hd, buf, len);
if (ret != len)
return AVERROR(EIO);
// 根据通道ID找到对应的流
for (i = 0; i < rt->nb_rtsp_streams; i++) {
rtsp_st = rt->rtsp_streams[i];
if (id >= rtsp_st->interleaved_min &&
id <= rtsp_st->interleaved_max)
goto found;
}
goto redo;
found:
*prtsp_st = rtsp_st;
return len;
}
6.5 传输模式选择策略
FFmpeg自动选择传输模式的策略:
c
// 优先级顺序(可通过rtsp_transport选项修改)
int lower_transport_mask = rt->lower_transport_mask;
// 如果设置了prefer_tcp标志
if (rt->rtsp_flags & RTSP_FLAG_PREFER_TCP) {
// 优先尝试TCP
ret = ff_rtsp_make_setup_request(s, host, port,
RTSP_LOWER_TRANSPORT_TCP,
rt->real_challenge);
if (ret == 0)
goto setup_done;
}
// 尝试UDP
if (lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_UDP)) {
ret = ff_rtsp_make_setup_request(s, host, port,
RTSP_LOWER_TRANSPORT_UDP,
rt->real_challenge);
if (ret == 0)
goto setup_done;
}
// UDP失败后回退到TCP
if (lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_TCP)) {
ret = ff_rtsp_make_setup_request(s, host, port,
RTSP_LOWER_TRANSPORT_TCP,
rt->real_challenge);
}
6.6 UDP超时自动切换TCP
FFmpeg实现了智能的容错机制:
c
// 文件: rtspdec.c - rtsp_read_packet()
ret = ff_rtsp_fetch_packet(s, pkt);
if (ret < 0) {
// UDP超时且还没收到任何包
if (ret == AVERROR(ETIMEDOUT) && !rt->packets) {
// 如果支持TCP且当前是UDP
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP &&
rt->lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_TCP)) {
av_log(s, AV_LOG_WARNING, "UDP timeout, retrying with TCP\n");
// 1. 暂停
if (rtsp_read_pause(s) != 0)
return -1;
// 2. TEARDOWN(Real服务器需要)
if (rt->server_type == RTSP_SERVER_REAL)
ff_rtsp_send_cmd(s, "TEARDOWN", rt->control_uri, NULL, &reply, NULL);
// 3. 清空Session ID,重新SETUP
rt->session_id[0] = '\0';
// 4. 使用TCP重新SETUP
if (resetup_tcp(s) == 0) {
rt->state = RTSP_STATE_IDLE;
rt->need_subscription = 1;
// 5. 重新PLAY
if (rtsp_read_play(s) != 0)
return -1;
goto retry;
}
}
}
return ret;
}
7. 关键函数源码解析
7.1 ff_rtsp_send_cmd - 发送RTSP命令
c
// 文件: rtsp.c
int ff_rtsp_send_cmd(AVFormatContext *s, const char *method,
const char *url, const char *headers,
RTSPMessageHeader *reply, unsigned char **content_ptr)
{
return ff_rtsp_send_cmd_with_content(s, method, url, headers,
reply, content_ptr, NULL, 0);
}
int ff_rtsp_send_cmd_with_content(AVFormatContext *s,
const char *method, const char *url,
const char *headers,
RTSPMessageHeader *reply,
unsigned char **content_ptr,
const unsigned char *send_content,
int send_content_length)
{
RTSPState *rt = s->priv_data;
char buf[MAX_URL_SIZE];
int len;
// 1. 构造请求行
snprintf(buf, sizeof(buf), "%s %s RTSP/1.0\r\n", method, url);
// 2. 添加CSeq
rt->seq++;
av_strlcatf(buf, sizeof(buf), "CSeq: %d\r\n", rt->seq);
// 3. 添加User-Agent
av_strlcatf(buf, sizeof(buf), "User-Agent: %s\r\n",
rt->user_agent ? rt->user_agent : LIBAVFORMAT_IDENT);
// 4. 添加Session(如果已建立)
if (rt->session_id[0] != '\0')
av_strlcatf(buf, sizeof(buf), "Session: %s\r\n", rt->session_id);
// 5. 添加自定义头
if (headers)
av_strlcat(buf, headers, sizeof(buf));
// 6. 添加Content-Length(如果有body)
if (send_content) {
av_strlcatf(buf, sizeof(buf), "Content-Length: %d\r\n",
send_content_length);
}
// 7. 结束头部
av_strlcat(buf, "\r\n", sizeof(buf));
// 8. 发送请求
av_log(s, AV_LOG_DEBUG, "Sending:\n%s", buf);
ffurl_write(rt->rtsp_hd_out, buf, strlen(buf));
// 9. 发送body(如果有)
if (send_content)
ffurl_write(rt->rtsp_hd_out, send_content, send_content_length);
// 10. 更新最后命令时间
rt->last_cmd_time = av_gettime_relative();
// 11. 读取响应
return ff_rtsp_read_reply(s, reply, content_ptr, 0, method);
}
完整请求示例:
http
PLAY rtsp://192.168.1.100:554/stream RTSP/1.0
CSeq: 5
User-Agent: FFmpeg/8.0
Session: 12345678
Range: npt=0.000-
7.2 ff_rtsp_read_reply - 读取RTSP响应
c
// 文件: rtsp.c (简化版)
int ff_rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply,
unsigned char **content_ptr,
int return_on_interleaved_data,
const char *method)
{
RTSPState *rt = s->priv_data;
char buf[MAX_URL_SIZE];
int ret, content_length;
memset(reply, 0, sizeof(*reply));
// 1. 读取状态行
ret = ff_rtsp_read_line(s, buf, sizeof(buf));
if (ret < 0)
return ret;
// 检查是否是RTP数据(TCP交错模式)
if (buf[0] == '$') {
if (return_on_interleaved_data)
return 1; // 告诉调用者:这是RTP数据
else
ff_rtsp_skip_packet(s); // 跳过RTP包
}
// 2. 解析状态行:"RTSP/1.0 200 OK"
sscanf(buf, "RTSP/%*d.%*d %d", &reply->status_code);
// 3. 读取所有头部行
do {
ret = ff_rtsp_read_line(s, buf, sizeof(buf));
if (ret < 0)
return ret;
if (buf[0] == '\0') // 空行表示头部结束
break;
// 4. 解析各种头部字段
ff_rtsp_parse_line(s, reply, buf, rt, method);
} while (1);
// 5. 读取消息体(如果有Content-Length)
if (reply->content_length > 0 && content_ptr) {
*content_ptr = av_malloc(reply->content_length + 1);
ret = ffurl_read_complete(rt->rtsp_hd, *content_ptr,
reply->content_length);
if (ret < reply->content_length) {
av_freep(content_ptr);
return AVERROR(EIO);
}
(*content_ptr)[reply->content_length] = '\0';
}
return 0;
}
7.3 ff_rtsp_parse_line - 解析响应头
c
// 文件: rtsp.c (核心字段解析)
void ff_rtsp_parse_line(AVFormatContext *s, RTSPMessageHeader *reply,
const char *buf, RTSPState *rt, const char *method)
{
const char *p;
// CSeq解析
if (av_stristart(buf, "CSeq:", &p)) {
reply->seq = strtol(p, NULL, 10);
}
// Content-Length解析
else if (av_stristart(buf, "Content-Length:", &p)) {
reply->content_length = strtol(p, NULL, 10);
}
// Content-Type解析
else if (av_stristart(buf, "Content-Type:", &p)) {
p += strspn(p, SPACE_CHARS);
av_strlcpy(reply->content_type, p, sizeof(reply->content_type));
}
// Session解析:"Session: 12345678;timeout=60"
else if (av_stristart(buf, "Session:", &p)) {
char *q;
p += strspn(p, SPACE_CHARS);
q = reply->session_id;
while (*p && *p != ';' && *p != '\r' && *p != '\n')
*q++ = *p++;
*q = '\0';
// 解析timeout参数
if (av_stristr(p, "timeout=")) {
sscanf(av_stristr(p, "timeout="), "timeout=%d", &reply->timeout);
}
}
// Transport解析(最复杂)
else if (av_stristart(buf, "Transport:", &p)) {
ff_rtsp_parse_transport(reply, p);
}
// RTP-Info解析
else if (av_stristart(buf, "RTP-Info:", &p)) {
ff_rtsp_parse_rtp_info(rt, p);
}
// Range解析:"Range: npt=0.000-123.456"
else if (av_stristart(buf, "Range:", &p)) {
ff_rtsp_parse_range_npt(p, &reply->range_start, &reply->range_end);
}
// Server解析
else if (av_stristart(buf, "Server:", &p)) {
p += strspn(p, SPACE_CHARS);
av_strlcpy(reply->server, p, sizeof(reply->server));
// 检测服务器类型
if (av_stristr(p, "WMServer"))
rt->server_type = RTSP_SERVER_WMS;
else if (av_stristr(p, "RealServer") || av_stristr(p, "Helix"))
rt->server_type = RTSP_SERVER_REAL;
}
// WWW-Authenticate解析(认证)
else if (av_stristart(buf, "WWW-Authenticate:", &p) ||
av_stristart(buf, "Proxy-Authenticate:", &p)) {
ff_http_auth_handle_header(&rt->auth_state, "WWW-Authenticate", p);
}
}
7.4 ff_rtsp_fetch_packet - 获取数据包
c
// 文件: rtsp.c (简化逻辑)
int ff_rtsp_fetch_packet(AVFormatContext *s, AVPacket *pkt)
{
RTSPState *rt = s->priv_data;
int ret;
RTSPStream *rtsp_st;
// TCP交错模式
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP) {
uint8_t buf[RTP_MAX_PACKET_LENGTH];
// 读取一个RTP包
ret = ff_rtsp_tcp_read_packet(s, &rtsp_st, buf, sizeof(buf));
if (ret < 0)
return ret;
// 解析RTP包
if (rt->transport == RTSP_TRANSPORT_RTP) {
ret = ff_rtp_parse_packet(rtsp_st->transport_priv, pkt, buf, ret);
} else if (rt->transport == RTSP_TRANSPORT_RDT) {
ret = ff_rdt_parse_packet(rtsp_st->transport_priv, pkt, buf, ret);
}
return ret;
}
// UDP模式
else {
// 轮询所有UDP socket
int max_p = rt->max_p;
struct pollfd *p = rt->p;
int64_t wait_end = av_gettime_relative() + rt->stimeout;
for (;;) {
// 计算剩余超时时间
int64_t timeout = wait_end - av_gettime_relative();
if (timeout < 0)
return AVERROR(ETIMEDOUT);
// poll等待数据
ret = poll(p, max_p, timeout / 1000);
if (ret < 0)
return AVERROR(errno);
if (ret == 0)
return AVERROR(ETIMEDOUT);
// 检查哪个socket有数据
for (int i = 0; i < max_p; i++) {
if (p[i].revents & POLLIN) {
rtsp_st = rt->rtsp_streams[i];
// 从UDP socket读取
ret = ffurl_read(rtsp_st->rtp_handle,
rt->recvbuf, RECVBUF_SIZE);
if (ret < 0)
continue;
// 解析RTP
ret = ff_rtp_parse_packet(rtsp_st->transport_priv,
pkt, rt->recvbuf, ret);
if (ret < 0)
continue;
return ret;
}
}
}
}
}
7.5 rtsp_read_seek - Seek实现
c
// 文件: rtspdec.c
static int rtsp_read_seek(AVFormatContext *s, int stream_index,
int64_t timestamp, int flags)
{
RTSPState *rt = s->priv_data;
int ret;
// 1. 转换时间戳为AV_TIME_BASE单位
rt->seek_timestamp = av_rescale_q(timestamp,
s->streams[stream_index]->time_base,
AV_TIME_BASE_Q);
// 2. 根据当前状态执行不同操作
switch(rt->state) {
case RTSP_STATE_IDLE:
// 空闲状态不需要操作
break;
case RTSP_STATE_STREAMING:
// 流媒体中:先PAUSE,再PLAY(带Range参数)
if ((ret = rtsp_read_pause(s)) != 0)
return ret;
rt->state = RTSP_STATE_SEEKING;
if ((ret = rtsp_read_play(s)) != 0)
return ret;
break;
case RTSP_STATE_PAUSED:
// 暂停状态:直接设置为IDLE,下次PLAY会带Range
rt->state = RTSP_STATE_IDLE;
break;
}
return 0;
}
Seek时的PLAY命令:
http
PLAY rtsp://192.168.1.100:554/stream RTSP/1.0
CSeq: 8
User-Agent: FFmpeg/8.0
Session: 12345678
Range: npt=30.500-
8. RTSP服务端监听模式
8.1 监听模式概述
FFmpeg不仅支持RTSP客户端拉流,还支持作为RTSP服务器接收推流。这种模式主要用于:
- 接收实时编码器推送的流
- 作为流媒体网关
- 测试和开发
8.2 监听模式流程
RTSP服务端监听模式时序流程:
┌─────────────┐ ┌─────────────┐
│ 推流端 │ │ FFmpeg │
│ (编码器) │ │ RTSP服务器 │
└──────┬──────┘ └──────┬──────┘
│ │
│ [启动监听模式: ffmpeg -rtsp_flags listen] │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段1: 服务器启动监听 │ │
│ └────────────────────────────────────────────┘ │
│ │
│ 绑定TCP端口554 │
│ 进入等待连接状态 │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段2: 推流端发起连接 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──TCP连接────────────────────────────────────────>│
│ │
│──OPTIONS rtsp://server/live─────────────────────>│
│<──200 OK │
│ Public: ANNOUNCE,PAUSE,SETUP,TEARDOWN,RECORD │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段3: 公告媒体描述 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──ANNOUNCE rtsp://server/live────────────────────>│
│ Content-Type: application/sdp │
│ [SDP描述] │
│ [解析SDP,创建媒体流]│
│<──200 OK │
│ State: IDLE → PAUSED│
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段4: 建立传输通道 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──SETUP rtsp://server/live/track1───────────────>│
│ Transport: RTP/AVP;unicast;client_port=5000-5001│
│ [打开本地UDP端口] │
│<──200 OK │
│ Session: 12345678 │
│ Transport: ...;server_port=6000-6001 │
│ │
│──SETUP rtsp://server/live/track2───────────────>│
│ Transport: RTP/AVP;unicast;client_port=5002-5003│
│<──200 OK │
│ Session: 12345678 │
│ Transport: ...;server_port=6002-6003 │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段5: 开始录制 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──RECORD rtsp://server/live─────────────────────>│
│ Session: 12345678 │
│<──200 OK │
│ State: PAUSED → STREAMING│
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段6: 数据传输(持续) │ │
│ └────────────────────────────────────────────┘ │
│ │
│──RTP数据包──────────────────────────────────────>│
│──RTP数据包──────────────────────────────────────>│
│──RTP数据包──────────────────────────────────────>│
│ ... (持续传输) │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 阶段7: 结束会话 │ │
│ └────────────────────────────────────────────┘ │
│ │
│──TEARDOWN rtsp://server/live───────────────────>│
│ Session: 12345678 │
│<──200 OK │
│ State: STREAMING → IDLE│
│ │
监听模式时序表:
| 阶段 | 推流端操作 | FFmpeg服务器响应 | 状态变化 |
|---|---|---|---|
| 1. 启动 | - | 绑定TCP端口554 进入等待状态 | - |
| 2. 连接 | TCP连接 | 接受连接 | - |
| 3. 能力查询 | OPTIONS请求 | 200 OK Public: ANNOUNCE,PAUSE,SETUP,TEARDOWN,RECORD | - |
| 4. 公告 | ANNOUNCE请求 Content-Type: application/sdp [SDP描述] | 200 OK 解析SDP,创建媒体流 | IDLE → PAUSED |
| 5. 建立传输 | SETUP track1 Transport: RTP/AVP;unicast;client_port=5000-5001 | 200 OK Session: 12345678 打开本地UDP端口 Transport: ...;server_port=6000-6001 | - |
| SETUP track2 Transport: RTP/AVP;unicast;client_port=5002-5003 | 200 OK Session: 12345678 Transport: ...;server_port=6002-6003 | - | |
| 6. 开始录制 | RECORD请求 Session: 12345678 | 200 OK | PAUSED → STREAMING |
| 7. 数据传输 | 持续发送RTP数据包 | 接收并处理数据包 | - |
| 8. 结束 | TEARDOWN请求 Session: 12345678 | 200 OK | STREAMING → IDLE |
8.3 rtsp_listen核心实现
c
// 文件: rtspdec.c
static int rtsp_listen(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
char proto[128], host[128], path[512], auth[128];
char uri[500];
int port;
int default_port = RTSP_DEFAULT_PORT;
char tcpname[500];
const char *lower_proto = "tcp";
// 1. 解析URL
av_url_split(proto, sizeof(proto), auth, sizeof(auth),
host, sizeof(host), &port, path, sizeof(path), s->url);
// 2. 构造控制URI
ff_url_join(rt->control_uri, sizeof(rt->control_uri),
proto, NULL, host, port, "%s", path);
// 3. 支持RTSPS(RTSP over TLS)
if (!strcmp(proto, "rtsps")) {
lower_proto = "tls";
default_port = RTSPS_DEFAULT_PORT;
}
if (port < 0)
port = default_port;
// 4. 创建监听TCP连接
ff_url_join(tcpname, sizeof(tcpname), lower_proto, NULL, host, port,
"?listen&listen_timeout=%d", rt->initial_timeout * 1000);
ret = ffurl_open_whitelist(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
&s->interrupt_callback, NULL,
s->protocol_whitelist,
s->protocol_blacklist, NULL);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Unable to open RTSP for listening\n");
goto fail;
}
// 5. 初始化状态
rt->state = RTSP_STATE_IDLE;
rt->rtsp_hd_out = rt->rtsp_hd;
// 6. 主循环:等待并处理RTSP命令
for (;;) {
unsigned char rbuf[MAX_URL_SIZE];
unsigned char method[10];
int rbuflen = 0;
enum RTSPMethod methodcode;
// 读取请求行
ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
if (ret < 0)
goto fail;
// 解析命令
ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri),
method, sizeof(method), &methodcode);
if (ret) {
av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n");
goto fail;
}
// 根据方法分发处理
if (methodcode == ANNOUNCE) {
ret = rtsp_read_announce(s);
rt->state = RTSP_STATE_PAUSED;
}
else if (methodcode == OPTIONS) {
ret = rtsp_read_options(s);
}
else if (methodcode == RECORD) {
ret = rtsp_read_record(s);
if (!ret)
return 0; // 准备好接收数据,退出监听循环
}
else if (methodcode == SETUP) {
ret = rtsp_read_setup(s, host, uri);
}
if (ret) {
ret = AVERROR_INVALIDDATA;
goto fail;
}
}
fail:
ff_rtsp_close_streams(s);
ff_rtsp_close_connections(s);
ff_network_close();
return ret;
}
8.4 ANNOUNCE处理
c
// 文件: rtspdec.c
static int rtsp_read_announce(AVFormatContext *s)
{
RTSPState *rt = s->priv_data;
RTSPMessageHeader request = { 0 };
char *sdp;
int ret;
// 1. 读取请求头
ret = rtsp_read_request(s, &request, "ANNOUNCE");
if (ret)
return ret;
rt->seq++;
// 2. 检查Content-Type
if (strcmp(request.content_type, "application/sdp")) {
av_log(s, AV_LOG_ERROR, "Unexpected content type %s\n",
request.content_type);
rtsp_send_reply(s, RTSP_STATUS_SERVICE, NULL, request.seq);
return AVERROR_OPTION_NOT_FOUND;
}
// 3. 读取SDP内容
if (request.content_length) {
sdp = av_malloc(request.content_length + 1);
if (!sdp)
return AVERROR(ENOMEM);
if (ffurl_read_complete(rt->rtsp_hd, sdp, request.content_length)
< request.content_length) {
av_log(s, AV_LOG_ERROR,
"Unable to get complete SDP Description in ANNOUNCE\n");
rtsp_send_reply(s, RTSP_STATUS_INTERNAL, NULL, request.seq);
av_free(sdp);
return AVERROR(EIO);
}
sdp[request.content_length] = '\0';
av_log(s, AV_LOG_VERBOSE, "SDP: %s\n", sdp);
// 4. 解析SDP
ret = ff_sdp_parse(s, sdp);
av_free(sdp);
if (ret)
return ret;
// 5. 发送200 OK
rtsp_send_reply(s, RTSP_STATUS_OK, NULL, request.seq);
return 0;
}
av_log(s, AV_LOG_ERROR, "Content-Length header missing\n");
rtsp_send_reply(s, RTSP_STATUS_INTERNAL,
"Content-Length exceeds buffer size", request.seq);
return AVERROR(EIO);
}
8.5 SETUP处理(服务端)
c
// 文件: rtspdec.c
static int rtsp_read_setup(AVFormatContext *s, char* host, char *controlurl)
{
RTSPState *rt = s->priv_data;
RTSPMessageHeader request = { 0 };
RTSPStream *rtsp_st;
char responseheaders[MAX_URL_SIZE];
int localport = -1;
int streamid = 0;
// 1. 读取SETUP请求
ret = rtsp_read_request(s, &request, "SETUP");
if (ret)
return ret;
rt->seq++;
// 2. 检查Transport
if (!request.nb_transports) {
av_log(s, AV_LOG_ERROR, "No transport defined in SETUP\n");
return AVERROR_INVALIDDATA;
}
// 3. 验证transport参数
for (int i = 0; i < request.nb_transports; i++) {
if (!request.transports[i].mode_record ||
(request.transports[i].lower_transport != RTSP_LOWER_TRANSPORT_UDP &&
request.transports[i].lower_transport != RTSP_LOWER_TRANSPORT_TCP)) {
av_log(s, AV_LOG_ERROR, "mode=record not set or transport"
" protocol not supported\n");
return AVERROR_INVALIDDATA;
}
}
// 4. 查找对应的流
for (streamid = 0; streamid < rt->nb_rtsp_streams; streamid++) {
if (!strcmp(rt->rtsp_streams[streamid]->control_url, controlurl))
break;
}
if (streamid == rt->nb_rtsp_streams) {
av_log(s, AV_LOG_ERROR, "Unable to find requested track\n");
return AVERROR_STREAM_NOT_FOUND;
}
rtsp_st = rt->rtsp_streams[streamid];
localport = rt->rtp_port_min;
// 5. TCP模式
if (request.transports[0].lower_transport == RTSP_LOWER_TRANSPORT_TCP) {
rt->lower_transport = RTSP_LOWER_TRANSPORT_TCP;
if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) {
rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
return ret;
}
rtsp_st->interleaved_min = request.transports[0].interleaved_min;
rtsp_st->interleaved_max = request.transports[0].interleaved_max;
snprintf(responseheaders, sizeof(responseheaders),
"Transport: RTP/AVP/TCP;unicast;mode=record;interleaved=%d-%d\r\n",
request.transports[0].interleaved_min,
request.transports[0].interleaved_max);
}
// 6. UDP模式
else {
do {
AVDictionary *opts = NULL;
av_dict_set_int(&opts, "buffer_size", rt->buffer_size, 0);
char url[MAX_URL_SIZE];
ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL);
ret = ffurl_open_whitelist(&rtsp_st->rtp_handle, url,
AVIO_FLAG_READ_WRITE,
&s->interrupt_callback, &opts,
s->protocol_whitelist,
s->protocol_blacklist, NULL);
av_dict_free(&opts);
if (ret)
localport += 2;
} while (ret || localport > rt->rtp_port_max);
if (localport > rt->rtp_port_max) {
rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
return ret;
}
if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) {
rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
return ret;
}
localport = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
snprintf(responseheaders, sizeof(responseheaders),
"Transport: RTP/AVP/UDP;unicast;mode=record;source=%s;"
"client_port=%d-%d;server_port=%d-%d\r\n",
host,
request.transports[0].client_port_min,
request.transports[0].client_port_max,
localport, localport + 1);
}
// 7. 生成Session ID(RFC 2326要求至少8位)
while (strlen(rt->session_id) < 8)
av_strlcatf(rt->session_id, 512, "%u", av_get_random_seed());
av_strlcatf(responseheaders, sizeof(responseheaders),
"Session: %s\r\n", rt->session_id);
// 8. 发送响应
rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq);
rt->state = RTSP_STATE_PAUSED;
return 0;
}
8.6 状态码定义
c
// RTSP状态码
enum RTSPStatusCode {
RTSP_STATUS_OK = 200, // 成功
RTSP_STATUS_METHOD = 405, // 方法不允许
RTSP_STATUS_BANDWIDTH = 453, // 带宽不足
RTSP_STATUS_SESSION = 454, // 会话未找到
RTSP_STATUS_STATE = 455, // 方法在此状态无效
RTSP_STATUS_AGGREGATE = 459, // 不允许聚合操作
RTSP_STATUS_ONLY_AGGREGATE = 460, // 仅允许聚合操作
RTSP_STATUS_TRANSPORT = 461, // 不支持的传输
RTSP_STATUS_INTERNAL = 500, // 内部服务器错误
RTSP_STATUS_SERVICE = 503, // 服务不可用
RTSP_STATUS_VERSION = 505, // RTSP版本不支持
};
// 状态消息映射
static const struct RTSPStatusMessage {
enum RTSPStatusCode code;
const char *message;
} status_messages[] = {
{ RTSP_STATUS_OK, "OK" },
{ RTSP_STATUS_METHOD, "Method Not Allowed" },
{ RTSP_STATUS_BANDWIDTH, "Not Enough Bandwidth" },
{ RTSP_STATUS_SESSION, "Session Not Found" },
{ RTSP_STATUS_STATE, "Method Not Valid in This State" },
{ RTSP_STATUS_AGGREGATE, "Aggregate operation not allowed" },
{ RTSP_STATUS_ONLY_AGGREGATE, "Only aggregate operation allowed" },
{ RTSP_STATUS_TRANSPORT, "Unsupported transport" },
{ RTSP_STATUS_INTERNAL, "Internal Server Error" },
{ RTSP_STATUS_SERVICE, "Service Unavailable" },
{ RTSP_STATUS_VERSION, "RTSP Version not supported" },
{ 0, "NULL" }
};
9. 错误处理与容错机制
9.1 错误处理策略
| 错误类型 | 处理策略 | 实现位置 |
|---|---|---|
| 网络超时 | UDP→TCP自动切换 | rtsp_read_packet() |
| 连接断开 | 返回EOF | ff_rtsp_read_reply() |
| 认证失败 | 重试3次 | ff_rtsp_send_cmd() |
| SDP解析失败 | 返回错误 | ff_sdp_parse() |
| 端口占用 | 尝试下一个端口 | rtsp_read_setup() |
| 服务器错误(5xx) | 映射到AVERROR | ff_rtsp_averror() |
9.2 UDP超时处理详解
c
// UDP超时后的完整恢复流程
if (ret == AVERROR(ETIMEDOUT) && !rt->packets) {
if (rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP &&
rt->lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_TCP)) {
av_log(s, AV_LOG_WARNING, "UDP timeout, retrying with TCP\n");
// 步骤1: PAUSE当前播放
if (rtsp_read_pause(s) != 0)
return -1;
// 步骤2: TEARDOWN(Real Server特殊要求)
if (rt->server_type == RTSP_SERVER_REAL) {
RTSPMessageHeader reply;
ff_rtsp_send_cmd(s, "TEARDOWN", rt->control_uri, NULL, &reply, NULL);
}
// 步骤3: 清空Session ID
rt->session_id[0] = '\0';
// 步骤4: 使用TCP重新SETUP所有流
if (resetup_tcp(s) == 0) {
rt->state = RTSP_STATE_IDLE;
rt->need_subscription = 1;
// 步骤5: 重新PLAY
if (rtsp_read_play(s) != 0)
return -1;
goto retry;
}
}
}
9.3 保活机制
c
// 防止服务器超时断开连接
if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN)) {
int64_t elapsed = (av_gettime_relative() - rt->last_cmd_time) / 1000000;
// 在超时时间的一半发送保活命令
if (elapsed >= rt->timeout / 2 || rt->auth_state.stale) {
if (rt->server_type == RTSP_SERVER_WMS ||
(rt->server_type != RTSP_SERVER_REAL &&
rt->get_parameter_supported)) {
// WMS或支持GET_PARAMETER的服务器
ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL);
} else {
// 其他服务器使用OPTIONS
ff_rtsp_send_cmd_async(s, "OPTIONS", rt->control_uri, NULL);
}
rt->auth_state.stale = 0;
}
}
9.4 认证处理
FFmpeg支持Basic和Digest两种认证:
c
// 发送带认证的请求
int ff_rtsp_send_cmd_with_auth(AVFormatContext *s, const char *method,
const char *url, const char *headers,
RTSPMessageHeader *reply,
unsigned char **content_ptr)
{
RTSPState *rt = s->priv_data;
char auth[1024];
int ret;
// 第一次尝试(可能没有认证)
ret = ff_rtsp_send_cmd(s, method, url, headers, reply, content_ptr);
// 收到401 Unauthorized
if (reply->status_code == 401) {
// 构造认证头
if (ff_http_auth_create_response(&rt->auth_state, "Authorization",
url, method, auth, sizeof(auth))) {
char new_headers[2048];
// 添加认证头
snprintf(new_headers, sizeof(new_headers),
"%sAuthorization: %s\r\n",
headers ? headers : "", auth);
// 重试
ret = ff_rtsp_send_cmd(s, method, url, new_headers,
reply, content_ptr);
}
}
return ret;
}
10. 性能优化与实践建议
10.1 参数优化建议
| 参数 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|
| buffer_size | -1(系统默认) | 2097152 (2MB) | UDP接收缓冲区,防止丢包 |
| reorder_queue_size | -1 | 500 | RTP重排序队列大小 |
| timeout | 0 | 5000000 (5秒) | Socket I/O超时(微秒) |
| stimeout | 0 | 5000000 (5秒) | 包接收超时(微秒) |
| max_delay | 0.5秒 | 0.1秒 | 最大缓冲延迟 |
10.2 命令行使用示例
基本拉流
bash
# UDP模式(默认)
ffplay rtsp://192.168.1.100:554/stream
# 强制TCP模式
ffplay -rtsp_transport tcp rtsp://192.168.1.100:554/stream
# 指定缓冲区大小
ffplay -buffer_size 4194304 rtsp://192.168.1.100:554/stream
高级配置
bash
# 低延迟配置
ffplay -rtsp_transport tcp \
-fflags nobuffer \
-flags low_delay \
-max_delay 100000 \
rtsp://192.168.1.100:554/stream
# 高可靠性配置(大缓冲)
ffplay -rtsp_transport tcp \
-buffer_size 8388608 \
-reorder_queue_size 1000 \
rtsp://192.168.1.100:554/stream
# 监听模式(接收推流)
ffmpeg -rtsp_flags listen \
-i rtsp://0.0.0.0:8554/live \
-c copy output.mp4
10.3 代码集成示例
c
#include <libavformat/avformat.h>
int main() {
AVFormatContext *fmt_ctx = NULL;
AVDictionary *opts = NULL;
int ret;
// 设置RTSP选项
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
av_dict_set(&opts, "buffer_size", "4194304", 0);
av_dict_set_int(&opts, "timeout", 5000000, 0);
// 打开RTSP流
ret = avformat_open_input(&fmt_ctx, "rtsp://192.168.1.100/stream",
NULL, &opts);
if (ret < 0) {
fprintf(stderr, "Could not open input: %s\n", av_err2str(ret));
return -1;
}
// 获取流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Could not find stream info\n");
return -1;
}
// 打印流信息
av_dump_format(fmt_ctx, 0, "rtsp://192.168.1.100/stream", 0);
// 读取数据包
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
// 处理数据包
av_packet_unref(&pkt);
}
// 清理
avformat_close_input(&fmt_ctx);
av_dict_free(&opts);
return 0;
}
10.4 性能监控指标
建议在生产环境监控以下指标:
- 丢包率: 通过RTCP报告获取
- 延迟 :
rtpctx->last_rtcp_reception_time - rtpctx->last_rtcp_ntp_time - 带宽: 累计接收字节数 / 时间
- 重连次数: UDP→TCP切换次数
- 缓冲队列长度 :
rtpctx->queue.nb_packets
10.5 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| UDP无法接收数据 | NAT/防火墙阻挡 | 使用TCP模式或配置端口转发 |
| 频繁断线重连 | 网络不稳定 | 增大buffer_size和timeout |
| 延迟过高 | 缓冲队列过大 | 设置-fflags nobuffer -flags low_delay |
| 画面卡顿 | 丢包严重 | 检查网络质量,使用TCP |
| 认证失败 | 用户名密码错误 | URL格式:rtsp://user:pass@host/stream |
10.6 调试技巧
bash
# 启用详细日志
export FFREPORT=file=rtsp_debug.log:level=48
ffplay -v trace rtsp://192.168.1.100/stream
# 抓包分析
tcpdump -i eth0 -w rtsp.pcap port 554 or portrange 5000-5100
# 使用Wireshark过滤
# RTSP: rtsp
# RTP: rtp
# RTCP: rtcp
总结
本文从协议基础、架构设计、流程分析、代码实现等多个维度深入剖析了FFmpeg RTSP拉流的完整实现。核心要点包括:
- RTSP是控制协议,实际数据通过RTP传输
- 状态机设计是核心,严格管理IDLE/PAUSED/STREAMING状态转换
- 传输模式选择需根据场景:UDP低延迟,TCP高可靠
- 容错机制完善,支持UDP超时自动切换TCP
- 监听模式支持作为RTSP服务器接收推流
FFmpeg的RTSP实现历经多年打磨,已经非常成熟稳定,是学习流媒体协议栈的优秀范例。
参考资料
💡 如果觉得本文有帮助,欢迎点赞、收藏、分享!
💬 有任何问题欢迎在评论区讨论交流!