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推流的完整流程。如有问题或建议,欢迎在评论区讨论。


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

相关推荐
锁我喉是吧42 分钟前
Windows mediamtx +ffmpeg电脑推视频流
ffmpeg··rtsp·mediamtx
Industio_触觉智能1 小时前
RK3576轻松搭建RTMP视频推流,基于FFmpeg+Nginx协同
nginx·ffmpeg·实时音视频·rtmp·瑞芯微·视频推流·rk3576
全栈视界师3 小时前
《机器人实践开发③:Foxglove可视化机器人的眼睛-视频》
opencv·机器人·音视频
双木的木3 小时前
Coggle数据科学 | 并行智能体:洞察复杂系统的 14 种并发设计模式
运维·人工智能·python·设计模式·chatgpt·自动化·音视频
有位神秘人4 小时前
Android视频播放方案
android·音视频
AI周红伟4 小时前
开源 | InfiniteTalk:无限长虚拟人视频生成的新范式
音视频
pu_taoc4 小时前
音频重采样注意事项--软件层面
音视频
加油20194 小时前
音视频处理(五):DLNA投屏技术详解
音视频·dlna·upnp·ssdp·投屏技术·mcast
问道飞鱼13 小时前
【工具介绍】Ffmpeg工具介绍与简单使用
ffmpeg·视频工具