【RTSP从零实践】1、根据RTSP协议实现一个RTSP服务

news2025/6/9 18:23:46

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍怎么实现一个RTSP服务器🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰: 2025-06-07 20:57:14

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、实现步骤、实现细节
    • ✨2.1、使用 TCP 实现RTSP服务器
    • ✨2.2、解析 RTSP 客户端的请求报文
    • ✨2.3、处理 OPTION 请求
    • ✨2.4、处理 DESCRIBE 方法
    • ✨2.5、处理 SETUP 方法
    • ✨2.6、处理 PLAY 方法
    • ✨2.7、处理 TEARDOWN 方法
  • 🎄三、RTSP协议的简单实现源码
  • 🎄四、总结


在这里插入图片描述

在这里插入图片描述

🎄一、概述

前面的文章(RTSP协议详解 及 抓包例子解析)详细地介绍了RTSP协议,学完之后,如果不动手去做一下,可能会觉得有点抽象,知识掌握不牢固。这篇文章就是从服务器的角度来看看RTSP协议是怎么实现的,需要注意哪些细节,带着读者动手做一篇。

注意:本文只实现RTSP服务,并没有实现音视频流传输的RTP,这个协议的内容在后面文章介绍。

在实现之前,对RTSP协议做一些必要的回顾:

  • RTSP,全称时 Real Time Streaming Protocol,实时流媒体协议。其传输层协议使用的是TCP。这点要特别注意,我们后面还会学习到RTP_OVER_UDPRTP_OVER_TCP,容易混淆;
  • RTSP协议的方法有很多,但一个最简单的RTSP服务器可以只提供这5个方法:OPTIONS、DESCRIBE、SETUP、PLAY、TEARDOWN
  • RTSP协议是通过 请求报文响应报文 来传输数据的,这些报文都是可读的文本,需要按协议要求解析和填写。

在这里插入图片描述

🎄二、实现步骤、实现细节

这个小写介绍一些实现步骤和细节,下个小节会提供源码,可以结合着源码看帮助理解消化。

✨2.1、使用 TCP 实现RTSP服务器

RTSP协议是使用TCP作为传输层协议的,所以我们实现RTSP服务器的第一个步骤可以是使用创建一个TCP服务器,绑定的监听端口一般是5548554。下面是创建一个TCP服务端的基本流程:

  • 1、创建TCP套接字;
  • 2、绑定指定端口;
  • 3、开始监听;

在这里插入图片描述


✨2.2、解析 RTSP 客户端的请求报文

作为RTSP服务端,开始监听之后,如果有RTSP客户端连接上来,就需要跟客户端进行会话(session)沟通了。服务端首先要做的就是读取客户端发过来的数据(请求报文),分析报文并获取一些必要的数据如:RTSP方法、请求的url、请求的序列号CSeq、客户端的端口等,本文用到的具体的分析如下图:

在这里插入图片描述

解析完请求报文之后,接下来就是处理各个方法的请求报文,本文例子只实现了5个RTSP方法的处理:OPTIONS、DESCRIBE、SETUP、PLAY、TEARDOWN。下面再分别介绍这些方法的处理。


✨2.3、处理 OPTION 请求

客户端发送OPTION请求,要求服务端返回所支持的RTSP方法。作为服务器,我们只需要将自己的方法按格式拼接成字符串回复给客户端即可。
在这里插入图片描述
关于请求报文格式、响应报文格式不了解的,可以看上一篇文章的第三节:RTSP协议详解 及 抓包例子解析,这里不再赘述。


✨2.4、处理 DESCRIBE 方法

处理 DESCRIBE 方法,就是要告诉客户端可以向其提供怎样的服务。一般会使用sdp协议来描述这些服务,例如:“提供一个什么样的视频流”、“提供一个什么样的音频流”、"提供一个视频流、一个音频流"等等。下面是处理DESCRIBE方法的回复:
"Content-Type: application/sdp\r\n" 指明响应体的协议是sdp
"Content-Length: %zu\r\n\r\n%s" 指明响应体的长度。

在这里插入图片描述
关于sdp协议的内容,可以参考这篇文章:SDP(会话描述协议)详解 及 抓包例子分析。下面是本例子的sdp内容:
在这里插入图片描述


✨2.5、处理 SETUP 方法

SETUP方法主要作用是,客户端告诉服务端按照什么传输协议(TCP/UDP)来发送 RTP 包(音视频数据)。如果是UDP协议传输,通过什么端口来发送数据。下面代码是本文例子的实现:
Session: 10086001 指明当前的会话ID;
Transport: RTP/AVP; 指明传输协议的UDP;
client_port=58714-58715 指明客户端通过58714来接收RTP包,通过58715来接收RTCP包。
在这里插入图片描述


✨2.6、处理 PLAY 方法

PLAY是 客户端 告诉 RTSP服务端 可以开始发送音视频数据(RTP)包了。服务器这边回复成功后,就开始向响应端口发送数据。
在这里插入图片描述


✨2.7、处理 TEARDOWN 方法

TEARDOWN方法的作用是,客户端告诉服务器停止发送数据流,结束会话。服务器需要回复后做一些停止发送码流的动作。
在这里插入图片描述


在这里插入图片描述

🎄三、RTSP协议的简单实现源码

基本上按照上个小节的步骤就可以实现一个简单的RTSP服务端了,这里给出源码 rtspSever.c,已经编译通过了,可供读者测试使用。

/**
 * @file rtspServer.c
 * @author wkd_007, csdn主页(https://blog.csdn.net/wkd_007)
 * @brief
 * @version 0.1
 * @date 2025-06-05
 *
 * @copyright Copyright (c) 2025
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define RTSP_PORT 8554
#define MAX_CLIENTS 5
#define SESSION_ID 10086001
#define SESSION_TIMEOUT 60

// 解析RTSP请求
static void rtsp_request_parse(char *buffer, char *method, char *url, int *cseq, int *pRtpPort)
{
    char *line = strtok(buffer, "\r\n");
    sscanf(line, "%s %s RTSP/1.0", method, url);

    while ((line = strtok(NULL, "\r\n")) != NULL)
    {
        if (strncmp(line, "CSeq:", 5) == 0)
        {
            sscanf(line, "CSeq: %d", cseq);
        }

        char *pCliPort = strstr(line, "client_port=");
        if (pCliPort != NULL)
        {
            int rtcpPort = 0;
            sscanf(pCliPort, "client_port=%d-%d", pRtpPort, &rtcpPort);
            // printf("rtpPort: %d-%d\n",*pRtpPort, rtcpPort);
        }
    }
}

// 生成SDP描述
static const char *generate_sdp()
{
    return "v=0\r\n"
           "o=- 0 0 IN IP4 0.0.0.0\r\n"
           "s=Example Stream\r\n"
           "t=0 0\r\n"
           "m=video 0 RTP/AVP 96\r\n"
           "a=rtpmap:96 H264/90000\r\n"
           "a=control:streamid=0\r\n";
}

static void rtsp_handle_OPTION(char *response, int cseq)
{
    sprintf(response,
            "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN\r\n\r\n",
            cseq);
}

static void rtsp_handle_DESCRIBE(char *response, int cseq)
{
    sprintf(response,
            "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Content-Type: application/sdp\r\n"
            "Content-Length: %zu\r\n\r\n%s",
            cseq, strlen(generate_sdp()), generate_sdp());
}

static void rtsp_handle_SETUP(char *response, int cseq, int rtpPort)
{
    sprintf(response,
            "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Session: %u; timeout=%d\r\n"
            "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n\r\n",
            cseq, SESSION_ID, SESSION_TIMEOUT, rtpPort, rtpPort + 1);
}

static void rtsp_handle_PLAY(char *response, int cseq)
{
    sprintf(response,
            "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Session: %u; timeout=%d\r\n"
            "Range: npt=0.000-\r\n\r\n",
            cseq, SESSION_ID, SESSION_TIMEOUT);
}

static void rtsp_handle_TEARDOWN(char *response, int cseq)
{
    sprintf(response,
            "RTSP/1.0 200 OK\r\n"
            "CSeq: %d\r\n"
            "Session: %d; timeout=%d\r\n\r\n",
            cseq, SESSION_ID, SESSION_TIMEOUT);
}

// 处理客户端连接
void *handle_client(void *arg)
{
    int client_sock = *(int *)arg;
    char buffer[1024] = {0};
    int cseq = 0;
    int rtpPort = 0;

    while (1)
    {
        memset(buffer, 0, sizeof(buffer));
        int len = read(client_sock, buffer, sizeof(buffer) - 1);
        if (len <= 0)
            break;

        char method[16] = {0};
        char url[128] = {0};
        rtsp_request_parse(buffer, method, url, &cseq, &rtpPort);

        printf("[%s]\n", buffer);

        char response[1024] = {0}; // 构造响应
        if (strcmp(method, "OPTIONS") == 0)
        {
            rtsp_handle_OPTION(response, cseq);
        }
        else if (strcmp(method, "DESCRIBE") == 0)
        {
            rtsp_handle_DESCRIBE(response, cseq);
        }
        else if (strcmp(method, "SETUP") == 0)
        {
            rtsp_handle_SETUP(response, cseq, rtpPort);
        }
        else if (strcmp(method, "PLAY") == 0)
        {
            rtsp_handle_PLAY(response, cseq);
        }
        else if (strcmp(method, "TEARDOWN") == 0)
        {
            rtsp_handle_TEARDOWN(response, cseq);
        }
        else
        {
            snprintf(response, sizeof(response),
                     "RTSP/1.0 501 Not Implemented\r\nCSeq: %d\r\n\r\n", cseq);
        }

        write(client_sock, response, strlen(response));
    }

    close(client_sock);
    return NULL;
}

int main()
{
    int server_fd, client_fd;
    struct sockaddr_in address;
    int opt = 1;
    socklen_t addrlen = sizeof(address);

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
    {
        perror("socket failed");
        return -1;
    }

    // 设置套接字选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
    {
        perror("setsockopt");
        return -1;
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(RTSP_PORT);

    // 绑定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
    {
        perror("bind failed");
        return -1;
    }

    // 开始监听
    if (listen(server_fd, MAX_CLIENTS) < 0)
    {
        perror("listen");
        return -1;
    }

    printf("RTSP Server listening on port %d\n", RTSP_PORT);

    // 主循环接受连接
    while (1)
    {
        if ((client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0)
        {
            perror("accept");
            return -1;
        }
        printf("RTSP Server listening on port %d 1\n", RTSP_PORT);

        handle_client((void *)&client_fd);
    }

    return 0;
}

👉编译:

gcc rtspServer.c 

👉运行结果:

  • 1、执行./a.out 运行服务端;
  • 2、打开 Wireshark 软件抓取RTSP数据包(这一步根据需要决定做不做);
  • 3、在vlc操作,媒体->打开网络串流,输入rtsp://192.168.2.183:8554,把这里的ip地址换成你自己运行了a.out的电脑的ip,然后点击播放。
    在这里插入图片描述
  • 4、如果你使用了Wireshark抓包,就会得到整个rtsp交互的过程,如下图:
    在这里插入图片描述

在这里插入图片描述

🎄四、总结

本文介绍了实现一个最简单的 RTSP服务端 的一些步骤和细节,也提供了实现源码和运行结果,可以帮助读者快速了解RTSP服务端的实现。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2405689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

行为设计模式之Iterator(迭代器)

行为设计模式之Iterator&#xff08;迭代器&#xff09; 摘要&#xff1a; 迭代器模式(Iterator)是一种行为设计模式&#xff0c;它提供顺序访问聚合对象元素的方法&#xff0c;同时不暴露内部结构。该模式由迭代器接口(Iterator)、具体迭代器(ConcreteIterator)、聚合接口(Ag…

FPGA点亮ILI9488驱动的SPI+RGB接口LCD显示屏(一)

FPGA点亮ILI9488驱动的SPIRGB接口LCD显示屏 ILI9488 RGB接口初始化 目录 前言 一、ILI9488简介 二、3线SPI接口简介 三、配置寄存器介绍 四、手册和初始化verilog FPGA代码 总结 前言 ILI9488是一款广泛应用于嵌入式系统和电子设备的彩色TFT LCD显示控制器芯片。本文将介…

如何实现本地mqtt服务器和云端服务器同步?

有时候&#xff0c;一个物联网项目&#xff0c;A客户想要本地使用&#xff0c;B客户想要线上使用&#xff0c;C客户想要本地部署&#xff0c;当有网环境时能线上使用。这个时候就需要本地MQTT服务和线上MQTT服务能相互自动转发。 后来经我一翻研究&#xff0c;其实Activemq支持…

windows10下搭建nfs服务器

windows10下搭建nfs服务器 有参考这篇博客 Windows10搭建NFS服务 - fuzidage - 博客园 下载 NFS Server这个app 通过网盘分享的文件&#xff1a;nfs1268 (1).exe 链接: https://pan.baidu.com/s/1rE4h710Uh-13kWGXvjkZzw 提取码: mwa4 --来自百度网盘超级会员v5的分享 下载后…

华为云Flexus+DeepSeek征文|Dify - LLM 云服务单机部署大语言模型攻略指南

前言&#xff1a;在当今人工智能快速发展的时代&#xff0c;华为云推出的 Dify - LLM 对话式 AI 开发平台为企业和开发者提供了便捷的大语言模型应用开发解决方案。 通过在华为云 Flexus 云服务器上单机部署 Dify&#xff0c;并成功集成 DeepSeek 模型&#xff0c;我们能够快速…

JAVA反序列化应用 : URLDNS案例

反序列化的基本原理 基础普及 &#xff1a; 对象初始化数据方法 &#xff1a;1、使用构造方法 2、使用封装中的 set,get方法 这边我们就使用 1 注意 我们之后还需要进行 接入 序列化的接口 &#xff1a; 先进行序列化 &#xff1a; 反序列化&#xff1a; 反序列化导致的安…

Vue-Leaflet地图组件开发(三)地图控件与高级样式设计

第三篇&#xff1a;Vue-Leaflet地图控件与高级样式设计 1. 专业级比例尺组件实现 1.1 比例尺控件集成 import { LControl } from "vue-leaflet/vue-leaflet";// 在模板中添加比例尺控件 <l-control-scaleposition"bottomleft":imperial"false&qu…

174页PPT家居制造业集团战略规划和运营管控规划方案

甲方集团需要制定一个清晰的集团价值定位&#xff0c;从“指引多元”、“塑造 能力”以及“强化协同”等方面引领甲方做大做强 集团需要通过管控模式、组织架构及职能、授权界面、关键流程、战略 实施和组织演进路径&#xff0c;平衡风险控制和迅速发展&#xff0c;保证战略落地…

wsl开启即闪退

[ 问题 ]&#xff1a; 在一次电脑卡住&#xff0c;强制关机重启后&#xff0c;遇到打开WSL就闪退的问题在CMD中打开WSL&#xff0c;出现如上图的描述&#xff1a; C:\Users\admin>wsl wsl: 检测到 localhost 代理配置&#xff0c;但未镜像到 WSL。NAT 模式下的 WSL 不支持…

【P2P】直播网络拓扑及编码模式

以下从 P2P 直播的常见拓扑模式出发,分析各种方案的特点与适用场景,并给出推荐。 一、P2P 直播的核心挑战 实时性要求高 直播场景下,延迟必须控制在可接受范围(通常 <2 秒),同时要保证画面连贯、不卡顿。带宽分布不均 每个节点(观众)上传带宽与下载带宽差异较大,且…

Python数据可视化科技图表绘制系列教程(二)

目录 表格风格图 使用Seaborn函数绘图 设置图表风格 设置颜色主题 图表分面 绘图过程 使用绘图函数绘图 定义主题 分面1 分面2 【声明】&#xff1a;未经版权人书面许可&#xff0c;任何单位或个人不得以任何形式复制、发行、出租、改编、汇编、传播、展示或利用本博…

低空城市场景下的多无人机任务规划与动态协调!CoordField:无人机任务分配的智能协调场

作者&#xff1a;Tengchao Zhang 1 ^{1} 1 , Yonglin Tian 2 ^{2} 2 , Fei Lin 1 ^{1} 1, Jun Huang 1 ^{1} 1, Patrik P. Sli 3 ^{3} 3, Rui Qin 2 , 4 ^{2,4} 2,4, and Fei-Yue Wang 5 , 1 ^{5,1} 5,1单位&#xff1a; 1 ^{1} 1澳门科技大学创新工程学院工程科学系&#xff0…

算法-构造题

#include<iostream> #include<bits/stdc.h> using namespace std; typedef long long ll; const ll N 5e5 10; int main() {ll n, k;cin >> n >> k; ll a[N] {0}; // 初始化一个大小为N的数组a&#xff0c;用于存储排列// 构造满足条件的排列for (l…

【Linux】进程的基本概念

目录 概念描述进程-PCB如何查看进程通过系统目录进行查看通过ps指令进行查看 通过系统调用获取进程的PID和PPID(进程标⽰符)通过系统调用创建子进程通过一段代码来介绍fork为什么要有子进程&#xff1f;fork为什么给子进程返回0&#xff0c;给父进程返回子进程的PIDfork函数到底…

设备驱动与文件系统:05 文件使用磁盘的实现

从文件使用磁盘的实现逻辑分享 我们现在讲第30讲&#xff0c;内容是文件使用磁盘的具体实现&#xff0c;也就是相关代码是如何编写的。上一节我们探讨了如何从字符流位置算出盘块号&#xff0c;这是文件操作磁盘的核心。而这节课&#xff0c;我们将深入研究实现这一核心功能的…

AI数据分析在体育中的应用:技术与实践

在现代体育竞技领域&#xff0c;"数据驱动"已不再是一个遥远的概念。尤其随着人工智能&#xff08;AI&#xff09;和大数据分析的不断成熟&#xff0c;从职业俱乐部到赛事直播平台&#xff0c;从运动员训练到球迷观赛体验&#xff0c;AI正以前所未有的方式渗透并改变…

zabbix 6 监控 docker 容器

zabbix 6 监控 docker 容器 1.安装zabbix_agent2 curl -s http://10.26.211.56:8080/centos7-agent2-install.sh | bash2.在zabbix server 端测试 zabbix_get -s 10.26.219.180 -k docker.infoZBX_NOTSUPPORTED: Cannot fetch data: Get "http://1.28/info": dial…

正则持续学习呀

源匹配为 (.*): (.*)$ 替换匹配为 "$1": "$2", 可将headers改为字典 参考 【爬虫军火库】如何优雅地复制请求头 - 知乎

Go基本语法——go语言中的四种变量定义方法

前言 在go语言中&#xff0c;定义一个变量有四种方式&#xff0c;本文单从语法的层面来介绍这几种方式 单变量定义方法 1.var 变量名 类型&#xff0c;不进行初始化 例如&#xff0c;定义一个变量a后为其赋值&#xff0c;并且打印其值&#xff0c;运行结果如下 //1.不进行…

27.【新型数据架构】-数据共享架构

27.【新型数据架构】-数据共享架构:降低数据获取成本,实时数据访问,保持数据新鲜度,促进数据经济发展,打破数据孤岛,标准化数据交换,增强数据安全性,完整审计追踪,合规性保障 一、数据共享架构的本质:打破壁垒的“数字立交桥” 传统企业或组织间的数据往往呈现“烟囱…