【IPC】 RTSP Server 如何实现推流

目录

[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==

  • 示例代码

    cpp 复制代码
    void 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头
  • 时间戳处理:同一帧的所有分片保持相同时间戳

  • 示例代码

    cpp 复制代码
    void 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)

  • 示例代码

    cpp 复制代码
    void 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)
    • 更新会话状态
  • 示例代码

    cpp 复制代码
    void* 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包
    • 通过套接字发送数据包
    • 更新序列号和时间戳
    • 等待下一帧间隔时间
  • 示例代码

    cpp 复制代码
    void* 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 打开:

    bash 复制代码
    rtsp://<ipc_ip>:554/stream
  • 查看日志确认:

    • 收到 DESCRIBE/SETUP/PLAY;
    • RTP 包持续发送。
相关推荐
23124_802 小时前
Cookie伪造
运维·服务器
RisunJan2 小时前
Linux命令-killall(根据进程名称来终止一个或多个进程)
linux·运维·服务器
小-黯3 小时前
Linux桌面入口文件.desktop文件内容格式
linux·运维·服务器
数智工坊4 小时前
【操作系统-IO调度】
java·服务器·数据库
475.354 小时前
linux-journal日志清理
linux·运维·服务器
小付爱coding4 小时前
跟着官网学LangChain【第01章:模型(Models)】
服务器·网络·langchain
小王不爱笑1324 小时前
云服务器部署 JavaWeb 项目
运维·服务器
清泉影月4 小时前
Linux:Squid正向代理实现内网访问互联网
linux·运维·服务器
康康的AI博客5 小时前
工业数据中台:PLC、SCADA、MES的实时协同架构
java·服务器·网络