目录
[1. RTSP Server 背景](#1. RTSP Server 背景)
[2. 核心流程(RTSP 交互)](#2. 核心流程(RTSP 交互))
[2.1 连接建立阶段](#2.1 连接建立阶段)
[2.2 DESCRIBE 请求](#2.2 DESCRIBE 请求)
[2.3 SETUP 请求](#2.3 SETUP 请求)
[2.4 PLAY 请求](#2.4 PLAY 请求)
[2.5 保活机制](#2.5 保活机制)
[3. 关键数据结构定义](#3. 关键数据结构定义)
[4. 核心功能实现](#4. 核心功能实现)
[4.1 RTSP 命令解析](#4.1 RTSP 命令解析)
[4.2 SDP 生成(含 SPS/PPS)](#4.2 SDP 生成(含 SPS/PPS))
[4.3 RTP 封装(FU-A 分片)](#4.3 RTP 封装(FU-A 分片))
[4.4 RTP 头构建](#4.4 RTP 头构建)
[5. 主事件循环与线程模型](#5. 主事件循环与线程模型)
[5.1 主线程:处理 RTSP 控制命令](#5.1 主线程:处理 RTSP 控制命令)
[5.2 发送线程:循环读帧并发送 RTP](#5.2 发送线程:循环读帧并发送 RTP)
[6. IPC 集成要点](#6. IPC 集成要点)
[6.1 获取 H.264 帧](#6.1 获取 H.264 帧)
[6.2 SPS/PPS 提取](#6.2 SPS/PPS 提取)
[6.3 内存优化](#6.3 内存优化)
[7. 编译与测试](#7. 编译与测试)
[7.1 编译命令](#7.1 编译命令)
[7.2 测试步骤](#7.2 测试步骤)
1. RTSP Server 背景
RTSP(Real Time Streaming Protocol)是一种应用层协议,专门为实时流媒体传输而设计。它由IETF在1998年标准化(RFC 2326),主要用于控制多媒体服务器上的流媒体会话。RTSP服务器通常运行在554端口(默认端口),支持TCP和UDP两种传输方式。
典型应用场景包括:
- 网络摄像头视频监控系统
- 视频点播服务(VOD)
- 在线直播平台
- IPTV系统
RTSP协议特点:
- 支持多种媒体格式(H.264/H.265, AAC, MP3等)
- 提供播放、暂停、定位等控制功能
- 与RTP/RTCP协议配合完成实际数据传输
- 支持单播和组播传输
2. 核心流程(RTSP 交互)
2.1 连接建立阶段
- 客户端发起连接请求:
rtsp://ip:554/stream- IP为服务器地址(如192.168.1.100)
- 554为默认RTSP端口(可配置其他端口)
- /stream为媒体资源路径(可自定义)
2.2 DESCRIBE 请求
http
DESCRIBE rtsp://192.168.1.100/stream RTSP/1.0
CSeq: 1
Accept: application/sdp
服务器响应返回SDP(Session Description Protocol)描述:
- 包含媒体格式信息(如H.264)
- 包含关键参数SPS/PPS(H.264的序列参数集和图像参数集)
- 示例响应:
http
RTSP/1.0 200 OK
CSeq: 1
Content-Type: application/sdp
Content-Length: 380
v=0
o=- 123456 1 IN IP4 192.168.1.100
s=RTSP Stream
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z0LAH5WgFAeQgAAB9AAAPUAAAPuWFhuQ=,aM4G4g==
2.3 SETUP 请求
客户端请求建立传输通道:
http
SETUP rtsp://192.168.1.100/stream/track1 RTSP/1.0
CSeq: 2
Transport: RTP/AVP;unicast;client_port=8000-8001
服务器响应分配端口:
http
RTSP/1.0 200 OK
CSeq: 2
Transport: RTP/AVP;unicast;client_port=8000-8001;server_port=9000-9001
Session: 12345678
- 服务器通常分配RTP端口(如9000)和RTCP端口(如9001)
- 返回会话ID用于后续请求关联
2.4 PLAY 请求
客户端请求开始传输:
http
PLAY rtsp://192.168.1.100/stream RTSP/1.0
CSeq: 3
Session: 12345678
Range: npt=0.000-
服务器启动发送线程:
- 打开媒体源(如摄像头或视频文件)
- 创建RTP封装线程
- 循环读取帧数据→封装RTP包→通过指定端口发送
- 同时通过RTCP端口发送控制信息(如丢包统计)
2.5 保活机制
客户端定期发送OPTIONS请求保持连接:
http
OPTIONS rtsp://192.168.1.100/stream RTSP/1.0
CSeq: 4
Session: 12345678
- 默认超时时间为60秒(可配置)
- 若超时未收到任何请求,服务器将主动关闭会话
- 保活间隔建议设置为30-45秒(小于超时时间)
3. 关键数据结构定义
cpp
// rtsp_server.h
#include <stdint.h>
#include <netinet/in.h>
#define MAX_CLIENTS 4
#define RTP_PAYLOAD_LEN 1400
#define CSEQ_LEN 32
typedef struct {
int rtp_sock;
int rtcp_sock;
struct sockaddr_in client_addr;
uint16_t rtp_port;
uint16_t rtcp_port;
uint32_t ssrc;
uint16_t seq_num;
uint32_t timestamp;
char session_id[32];
int playing;
int cseq;
} rtsp_client_t;
typedef struct {
int listen_sock;
rtsp_client_t clients[MAX_CLIENTS];
pthread_t stream_thread;
volatile int stop_streaming;
// H.264 SPS/PPS (from camera SDK)
uint8_t sps[64], pps[32];
int sps_len, pps_len;
} rtsp_server_t;
4. 核心功能实现
4.1 RTSP 命令解析
实现完整的RTSP协议命令解析功能,包括:
-
OPTIONS:获取服务器支持的方法
-
DESCRIBE:获取媒体描述信息(SDP)
-
SETUP:建立传输会话
-
PLAY:开始媒体流传输
-
TEARDOWN:终止会话 解析时需要处理RTSP头部的关键字段如CSeq、Session、Transport等,支持TCP和UDP两种传输方式。
-
示例代码
cpp// 解析请求行和关键头 int parse_rtsp_request(char* request, char* method, char* uri, int* cseq, char* session) { char* line = strtok(request, "\r\n"); sscanf(line, "%s %s RTSP/1.0", method, uri); while ((line = strtok(NULL, "\r\n")) != NULL) { if (strncmp(line, "CSeq:", 5) == 0) *cseq = atoi(line + 6); else if (strncmp(line, "Session:", 8) == 0) strcpy(session, line + 9); else if (strncmp(line, "Transport:", 10) == 0) { // 提取 client_port=xxxx-yyyy char* port_str = strstr(line, "client_port="); if (port_str) sscanf(port_str, "client_port=%hu-%hu", &rtp_port, &rtcp_port); } } return 0; }
4.2 SDP 生成(含 SPS/PPS)
生成符合RFC 4566标准的SDP描述文件,包含:
-
会话信息(版本、会话ID、创建时间等)
-
媒体描述(媒体类型、传输协议、端口等)
-
编解码参数(H.264时需包含profile-level-id、packetization-mode等)
-
SPS/PPS 需从摄像头 SDK 获取(如海思
HI_MPI_VENC_GetStream) -
SPS/PPS参数集(以base64编码形式嵌入) 示例格式:
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z0IAH5WoFAFuhA==,aM48gA==
-
示例代码
cppvoid generate_sdp(char* sdp, rtsp_server_t* server) { // Base64 encode SPS/PPS char sps_b64[128], pps_b64[64]; base64_encode(server->sps, server->sps_len, sps_b64); base64_encode(server->pps, server->pps_len, pps_b64); sprintf(sdp, "v=0\r\n" "o=- 0 0 IN IP4 0.0.0.0\r\n" "s=IP Camera\r\n" "c=IN IP4 0.0.0.0\r\n" "t=0 0\r\n" "m=video 0 RTP/AVP 96\r\n" "a=rtpmap:96 H264/90000\r\n" "a=fmtp:96 packetization-mode=1;profile-level-id=42001F;" "sprop-parameter-sets=%s,%s\r\n", sps_b64, pps_b64); }
4.3 RTP 封装(FU-A 分片)
实现H.264视频数据的RTP分片封装:
-
判断NAL单元大小:
- ≤ MTU:直接封装
- > MTU:采用FU-A分片
-
FU-A分片步骤:
- 设置分片头(FU indicator + FU header)
- 标记首/中间/尾分片(S/E/R位)
- 每个分片携带12字节RTP头
-
时间戳处理:同一帧的所有分片保持相同时间戳
-
示例代码
cppvoid send_h264_rtp(rtsp_client_t* client, uint8_t* frame, int len) { const int MTU = 1400; const int PAYLOAD_MAX = MTU - 12; // RTP header = 12 bytes if (len <= PAYLOAD_MAX) { // 单 NALU 封装 uint8_t rtp_pkt[MTU]; build_rtp_header(rtp_pkt, client, 0, 0, frame[0]); memcpy(rtp_pkt + 12, frame, len); sendto(client->rtp_sock, rtp_pkt, 12 + len, 0, (struct sockaddr*)&client->client_addr, sizeof(client->client_addr)); } else { // FU-A 分片 int fu_header_size = 2; int payload_size = PAYLOAD_MAX - fu_header_size; int num_packets = (len - 1) / payload_size + 1; // 第一字节单独处理 for (int i = 0; i < num_packets; i++) { uint8_t rtp_pkt[MTU]; uint8_t* payload = rtp_pkt + 12 + fu_header_size; int offset = 1 + i * payload_size; // 跳过 NALU header int curr_payload_size = (i == num_packets - 1) ? (len - offset) : payload_size; // FU indicator rtp_pkt[12] = (frame[0] & 0xE0) | 28; // Type=28 (FU-A) // FU header rtp_pkt[13] = (i == 0) ? (0x80 | (frame[0] & 0x1F)) : // S=1 (i == num_packets - 1) ? 0x40 | (frame[0] & 0x1F) : // E=1 (frame[0] & 0x1F); // S=0, E=0 memcpy(payload, frame + offset, curr_payload_size); int pkt_len = 12 + fu_header_size + curr_payload_size; build_rtp_header(rtp_pkt, client, (i == num_packets - 1), 0, 28); sendto(client->rtp_sock, rtp_pkt, pkt_len, 0, (struct sockaddr*)&client->client_addr, sizeof(client->client_addr)); } } // 更新时间戳(90kHz) client->timestamp += 3600; // ~40ms per frame (25fps) client->seq_num++; }
4.4 RTP 头构建
构建符合RFC 3550标准的RTP头部:
-
版本号(V):固定为2
-
填充位(P)和扩展位(X):根据实际需求设置
-
标记位(M):视频帧的最后一包设为1
-
负载类型(PT):动态分配(96-127)
-
序列号:每个包递增1
-
时间戳:基于90kHz时钟
-
同步源(SSRC):随机生成唯一标识 关键字段处理需考虑网络字节序(Big-endian)
-
示例代码
cppvoid build_rtp_header(uint8_t* pkt, rtsp_client_t* client, int marker, int payload_type, int nal_type) { pkt[0] = 0x80; // V=2, P=0, X=0, CC=0 pkt[1] = (marker << 7) | (payload_type & 0x7F); pkt[2] = (client->seq_num >> 8) & 0xFF; pkt[3] = client->seq_num & 0xFF; pkt[4] = (client->timestamp >> 24) & 0xFF; pkt[5] = (client->timestamp >> 16) & 0xFF; pkt[6] = (client->timestamp >> 8) & 0xFF; pkt[7] = client->timestamp & 0xFF; pkt[8] = (client->ssrc >> 24) & 0xFF; pkt[9] = (client->ssrc >> 16) & 0xFF; pkt[10] = (client->ssrc >> 8) & 0xFF; pkt[11] = client->ssrc & 0xFF; }
5. 主事件循环与线程模型
5.1 主线程:处理 RTSP 控制命令
-
负责监听 RTSP 控制端口(默认554)
-
解析和处理 RTSP 协议命令(OPTIONS、DESCRIBE、SETUP、PLAY、TEARDOWN等)
-
维护会话状态机,跟踪每个客户端的会话状态
-
处理超时和异常情况,发送适当的响应消息
-
示例流程:
- 接收客户端连接请求
- 解析命令头部和SDP信息
- 根据命令类型调用对应的处理函数
- 生成并返回RTSP响应(如200 OK)
- 更新会话状态
-
示例代码
cppvoid* rtsp_control_loop(void* arg) { rtsp_server_t* server = (rtsp_server_t*)arg; fd_set read_fds; struct timeval timeout = {1, 0}; while (!server->stop_streaming) { FD_ZERO(&read_fds); FD_SET(server->listen_sock, &read_fds); int max_fd = server->listen_sock; // 添加所有客户端 socket for (int i = 0; i < MAX_CLIENTS; i++) { if (server->clients[i].rtp_sock > 0) { FD_SET(server->clients[i].rtp_sock, &read_fds); if (server->clients[i].rtp_sock > max_fd) max_fd = server->clients[i].rtp_sock; } } if (select(max_fd + 1, &read_fds, NULL, NULL, &timeout) > 0) { if (FD_ISSET(server->listen_sock, &read_fds)) { handle_new_client(server); } for (int i = 0; i < MAX_CLIENTS; i++) { if (FD_ISSET(server->clients[i].rtp_sock, &read_fds)) { handle_rtsp_command(server, i); } } } } return NULL; }
5.2 发送线程:循环读帧并发送 RTP
-
从视频采集设备或文件持续读取视频帧
-
对视频帧进行RTP封装(添加RTP头部信息)
-
通过UDP协议发送RTP数据包到指定客户端
-
实现功能:
- 帧率控制(按指定fps发送)
- 时间戳同步(基于RTP时间戳)
- 分包处理(对大帧进行分片传输)
- 丢包重传机制(可选)
-
典型工作流程:
- 从缓冲区获取编码后的视频帧
- 计算当前RTP时间戳
- 分割帧数据为适合网络传输的RTP包
- 通过套接字发送数据包
- 更新序列号和时间戳
- 等待下一帧间隔时间
-
示例代码
cppvoid* rtp_stream_thread(void* arg) { rtsp_server_t* server = (rtsp_server_t*)arg; uint8_t frame_buffer[512*1024]; // 512KB buffer while (!server->stop_streaming) { // 从摄像头 SDK 获取一帧(阻塞或轮询) int frame_len = get_h264_frame_from_sdk(frame_buffer, sizeof(frame_buffer)); if (frame_len <= 0) continue; // 广播给所有 PLAYING 客户端 for (int i = 0; i < MAX_CLIENTS; i++) { if (server->clients[i].playing) { send_h264_rtp(&server->clients[i], frame_buffer, frame_len); } } usleep(10000); // ~100fps limit } return NULL; }
6. IPC 集成要点
6.1 获取 H.264 帧
- 海思平台 :调用
HI_MPI_VENC_GetStream获取VENC_STREAM_S; - 瑞芯微 :通过
rk_mpi_venc_get_stream; - 通用方案 :从
/dev/videoX读取(需 V4L2 编码支持)。
6.2 SPS/PPS 提取
- 在首帧或 IDR 帧前,编码器会输出 SPS/PPS NALU;
- 缓存这两个 NALU 用于 SDP 生成。
6.3 内存优化
- 使用环形缓冲区避免频繁 malloc/free;
- RTP 包复用固定大小栈内存(1400 字节);
- 禁用 TCP Nagle 算法:
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, ...)。
7. 编译与测试
7.1 编译命令
bash
gcc -o rtsp_server rtsp_server.c -lpthread
7.2 测试步骤
-
在 IPC 上运行
./rtsp_server -
在 PC 使用 VLC 打开:
bashrtsp://<ipc_ip>:554/stream -
查看日志确认:
- 收到 DESCRIBE/SETUP/PLAY;
- RTP 包持续发送。