RTSP推流流程深度解析:从协议原理到FFmpeg实现

前言

RTSP(Real Time Streaming Protocol)作为流媒体传输的核心协议之一,在视频监控、直播推流、视频会议等领域有着广泛应用。本文将从协议原理出发,深入分析RTSP推流的完整流程,并结合FFmpeg源码实现,帮助读者全面理解RTSP推流的技术细节。


目录

  1. RTSP协议基础
  2. RTSP推流架构概览
  3. 完整推流流程详解
  4. 关键技术实现
  5. FFmpeg源码分析
  6. 常见问题与优化
  7. 总结

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;
}

解决方案:

  1. 使用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);
  1. 增大发送缓冲区:
c 复制代码
av_dict_set_int(&opts, "buffer_size", 1048576, 0);  // 1MB
  1. 处理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 多流同步问题

问题: 多个流(音视频)时间戳不同步

解决方案:

  1. 统一时间基准:
c 复制代码
// 设置统一的起始时间
s->start_time_realtime = av_gettime();

// 所有流使用相同的时间基准
for (i = 0; i < s->nb_streams; i++) {
    s->streams[i]->time_base = (AVRational){1, 90000};
}
  1. 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抓包:

  1. 过滤RTSP: rtsp
  2. 过滤RTP: rtp
  3. 分析SDP: 查看ANNOUNCE请求的Content
  4. 分析RTP: 查看序列号、时间戳、负载类型

关键字段检查:

  • RTP序列号: 是否连续(检测丢包)
  • RTP时间戳: 是否递增(检测时间同步)
  • RTCP报告: 查看丢包率、延迟

9.3 常见问题排查

问题1: SETUP失败

复制代码
可能原因:
- 端口被占用
- 防火墙阻止
- 服务器不支持请求的传输方式

解决方法:
- 检查端口范围
- 尝试TCP传输
- 查看服务器日志

问题2: 推流中断

复制代码
可能原因:
- 网络不稳定
- 缓冲区满
- 服务器超时

解决方法:
- 增加缓冲区大小
- 使用TCP传输
- 检查网络连接

问题3: 音视频不同步

复制代码
可能原因:
- 时间戳计算错误
- 编码延迟不一致
- 网络抖动

解决方法:
- 统一时间基准
- 使用RTCP同步
- 增加缓冲区

10. 总结

10.1 核心要点

  1. RTSP推流流程: OPTIONS → ANNOUNCE → SETUP → RECORD → 数据传输 → TEARDOWN
  2. SDP作用: 描述流信息,包含编解码器、端口等参数
  3. RTP封装: 将音视频数据封装成RTP包,添加时间戳、序列号等
  4. 传输方式: UDP(低延迟)和TCP(可靠传输)各有优劣
  5. 时间戳同步: 关键保证音视频同步

10.2 最佳实践

  1. 传输选择: 根据网络环境选择UDP或TCP
  2. 缓冲区配置: 合理设置fifo_size和buffer_size
  3. 错误处理: 实现完善的错误处理和重试机制
  4. 性能监控: 监控丢包率、延迟等指标
  5. 日志记录: 启用详细日志便于问题排查

10.3 技术趋势

  • WebRTC: 基于RTP/RTCP,支持更好的NAT穿透
  • SRT: 可靠UDP传输,适合弱网环境
  • QUIC: 基于UDP的可靠传输,减少延迟

参考文献

  1. RFC 2326 - Real Time Streaming Protocol (RTSP)
  2. RFC 3550 - RTP: A Transport Protocol for Real-Time Applications
  3. RFC 3640 - RTP Payload Format for Transport of MPEG-4 Elementary Streams
  4. RFC 6184 - RTP Payload Format for H.264 Video
  5. FFmpeg官方文档: https://ffmpeg.org/documentation.html

作者简介

本文基于FFmpeg 8.0源码分析,深入解析RTSP推流的完整流程。如有问题或建议,欢迎在评论区讨论。


版权声明: 本文为原创文章,转载请注明出处。

相关推荐
道亦无名8 小时前
音频数据特征值提取 方法和步骤
android·音视频
西***634710 小时前
声画合一 智控全场 —— 高清数字会议系统重构现代会议新生态
音视频·会议系统
REDcker12 小时前
RTSP 直播技术详解
linux·服务器·网络·音视频·实时音视频·直播·rtsp
微尘hjx12 小时前
【Gstreamer 应用程序开发手册 01】关于GSTREAMER
linux·音视频·媒体
石去皿12 小时前
轻量级 Web 应用 —— 把一堆图片按指定频率直接拼成视频,零特效、零依赖、零命令行
前端·音视频
runner365.git13 小时前
做一个基于ffmpeg的AI Agent智能体
人工智能·ffmpeg·大模型
进击的小头14 小时前
FIR滤波器实战:音频信号降噪
c语言·python·算法·音视频
Black蜡笔小新14 小时前
终结“监控盲区”:EasyGBS视频质量诊断技术多场景应用设计
人工智能·音视频·视频质量诊断
彷徨而立16 小时前
【FFmpeg】理解 av_packet_from_data 和 av_packet_unref 接口
ffmpeg
liliangcsdn17 小时前
视频嵌入表示生成方案的探索
数据库·人工智能·音视频