RTSP是一个实时传输流协议,是一个应用层的协议。通常说的RTSP 包括RTSP协议、RTP协议、RTCP协议,对于这些协议的作用简单的理解如下
RTSP协议:负责服务器与客户端之间的请求与响应
RTP协议: 负责服务器与客户端之间传输媒体数据
RTCP协议:负责提供有关RTP传输质量的反馈,就是确保RTP传输的质量
三者的关系: rtsp并不会发送媒体数据,只是完成服务器和客户端之间的信令交互,rtp协议负责媒体数据传输,rtcp负责rtp数据包的监视和反馈。rtp和rtcp并没有规定传输层的类型,可以选择udp和tcp。Rtsp的传输层则要求是基于tcp。
RTSP 交互流程示例
以一个典型的 RTSP 点播流程 为例(如客户端点播 rtsp://example.com/video):
抓包
步骤 1:OPTIONS(查询支持的方法)
客户端请求:
OPTIONS rtsp://example.com/video RTSP/1.0
CSeq: 1
User-Agent: VLC
服务器响应:
RTSP/1.0 200 OK
CSeq: 1
Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN
步骤 2:DESCRIBE(获取媒体描述)
客户端请求
DESCRIBE rtsp://example.com/video RTSP/1.0
CSeq: 2
Accept: application/sdp
服务器响应(返回 SDP 描述)
RTSP/1.0 200 OK
CSeq: 2
Content-Type: application/sdp
Content-Length: 123
v=0
o=- 123456 1 IN IP4 192.168.1.1
s=Example Video
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
步骤 3:SETUP(建立传输通道)
客户端请求(告知服务器自己的 RTP/RTCP 端口):
SETUP rtsp://example.com/video/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001
服务器响应(确认传输参数):
RTSP/1.0 200 OK
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001;server_port=9000-9001
Session: 123456
步骤 4:PLAY(开始播放)
客户端请求:
PLAY rtsp://example.com/video RTSP/1.0
CSeq: 4
Session: 123456
Range: npt=0-
服务器响应:
RTSP/1.0 200 OK
CSeq: 4
Session: 123456
RTP-Info: url=rtsp://example.com/video/track1;seq=123;rtptime=123456
步骤 5:RTP 数据传输
服务器通过 UDP 向客户端的 8000 端口发送 RTP 数据包。
客户端通过 8001 端口发送 RTCP 反馈(如丢包统计)。
步骤 6:TEARDOWN(终止会话)
客户端请求:
TEARDOWN rtsp://example.com/video RTSP/1.0
CSeq: 5
Session: 1234
服务器响应
RTSP/1.0 200 OK
CSeq: 5
Session: 123456
模拟整体代码
//
// Created by bxc on 2022/11/30.
//
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <windows.h>
#include <string>
#pragma comment(lib, "ws2_32.lib")
#include <stdint.h>
#pragma warning( disable : 4996 )
#define SERVER_PORT 8554
#define SERVER_RTP_PORT 55532
#define SERVER_RTCP_PORT 55533
static int createTcpSocket()
{
int sockfd;
int on = 1;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
return -1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
return sockfd;
}
static int bindSocketAddr(int sockfd, const char* ip, int port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
return -1;
return 0;
}
static int acceptClient(int sockfd, char* ip, int* port)
{
int clientfd;
socklen_t len = 0;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
len = sizeof(addr);
clientfd = accept(sockfd, (struct sockaddr*)&addr, &len);
if (clientfd < 0)
return -1;
strcpy(ip, inet_ntoa(addr.sin_addr));
*port = ntohs(addr.sin_port);
return clientfd;
}
static int handleCmd_OPTIONS(char* result, int cseq)
{
sprintf(result, "RTSP/1.0 200 OK\r\n"
"CSeq: %d\r\n"
"Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
"\r\n",
cseq);
return 0;
}
static int handleCmd_DESCRIBE(char* result, int cseq, char* url)
{
char sdp[500];
char localIp[100];
sscanf(url, "rtsp://%[^:]:", localIp);
sprintf(sdp, "v=0\r\n"
"o=- 9%ld 1 IN IP4 %s\r\n"
"t=0 0\r\n"
"a=control:*\r\n"
"m=video 0 RTP/AVP 96\r\n"
"a=rtpmap:96 H264/90000\r\n"
"a=control:track0\r\n",
time(NULL), localIp);
sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
"Content-Base: %s\r\n"
"Content-type: application/sdp\r\n"
"Content-length: %zu\r\n\r\n"
"%s",
cseq,
url,
strlen(sdp),
sdp);
return 0;
}
static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort)
{
sprintf(result, "RTSP/1.0 200 OK\r\n"
"CSeq: %d\r\n"
"Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n"
"Session: 66334873\r\n"
"\r\n",
cseq,
clientRtpPort,
clientRtpPort + 1,
SERVER_RTP_PORT,
SERVER_RTCP_PORT);
return 0;
}
static int handleCmd_PLAY(char* result, int cseq)
{
sprintf(result, "RTSP/1.0 200 OK\r\n"
"CSeq: %d\r\n"
"Range: npt=0.000-\r\n"
"Session: 66334873; timeout=10\r\n\r\n",
cseq);
return 0;
}
static void doClient(int clientSockfd, const char* clientIP, int clientPort) {
// 定义请求相关的变量
char method[40]; // 存储请求方法 (OPTIONS, DESCRIBE, SETUP, PLAY)
char url[100]; // 存储请求的URL
char version[40]; // 存储RTSP协议版本
int CSeq; // 存储CSeq序列号
// RTP 和 RTCP 客户端端口
int clientRtpPort, clientRtcpPort;
// 分配接收缓冲区(用于接收客户端数据)
char* rBuf = (char*)malloc(10000); // rBuf:接收数据缓冲区
char* sBuf = (char*)malloc(10000); // sBuf:发送数据缓冲区
// 开始循环处理客户端请求
while (true) {
int recvLen;
// 从客户端socket读取数据,最多读取2000字节
recvLen = recv(clientSockfd, rBuf, 2000, 0);
// 如果接收失败或连接断开,退出循环
if (recvLen <= 0) {
break;
}
// 添加字符串结束符,防止数据污染
rBuf[recvLen] = '\0';
// 将接收到的数据转成string方便查看
std::string recvStr = rBuf;
printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
printf("%s rBuf = %s \n", __FUNCTION__, rBuf);
// 分割符,用于解析RTSP请求的每一行
const char* sep = "\n";
char* line = strtok(rBuf, sep); // 逐行解析请求数据
while (line) {
// 解析请求方法 (OPTIONS, DESCRIBE, SETUP, PLAY)
if (strstr(line, "OPTIONS") ||
strstr(line, "DESCRIBE") ||
strstr(line, "SETUP") ||
strstr(line, "PLAY")) {
// 解析请求方法、URL 和协议版本,例如: "OPTIONS rtsp://127.0.0.1/live.sdp RTSP/1.0"
if (sscanf(line, "%s %s %s\r\n", method, url, version) != 3) {
// 如果解析失败,跳过
// error
}
}
// 解析 CSeq 字段 (CSeq: 1)
else if (strstr(line, "CSeq")) {
if (sscanf(line, "CSeq: %d\r\n", &CSeq) != 1) {
// error
}
}
// 解析 Transport 字段,获取 RTP 和 RTCP 端口
else if (!strncmp(line, "Transport:", strlen("Transport:"))) {
// 解析示例:
// Transport: RTP/AVP/UDP;unicast;client_port=13358-13359
// Transport: RTP/AVP;unicast;client_port=13358-13359
if (sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
&clientRtpPort, &clientRtcpPort) != 2) {
// 解析失败
printf("parse Transport error \n");
}
}
// 继续解析下一行
line = strtok(NULL, sep);
}
// 根据解析的请求方法,调用相应的处理函数
if (!strcmp(method, "OPTIONS")) {
// 处理 OPTIONS 请求
if (handleCmd_OPTIONS(sBuf, CSeq)) {
printf("failed to handle options\n");
break; // 处理失败,退出
}
}
else if (!strcmp(method, "DESCRIBE")) {
// 处理 DESCRIBE 请求
if (handleCmd_DESCRIBE(sBuf, CSeq, url)) {
printf("failed to handle describe\n");
break;
}
}
else if (!strcmp(method, "SETUP")) {
// 处理 SETUP 请求
if (handleCmd_SETUP(sBuf, CSeq, clientRtpPort)) {
printf("failed to handle setup\n");
break;
}
}
else if (!strcmp(method, "PLAY")) {
// 处理 PLAY 请求
if (handleCmd_PLAY(sBuf, CSeq)) {
printf("failed to handle play\n");
break;
}
}
else {
// 处理未定义的请求方法
printf("未定义的method = %s \n", method);
break;
}
// 打印发送缓冲区的响应内容
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
printf("%s sBuf = %s \n", __FUNCTION__, sBuf);
// 发送处理结果到客户端
send(clientSockfd, sBuf, strlen(sBuf), 0);
// 如果是 PLAY 请求,进入 RTP 数据传输
if (!strcmp(method, "PLAY")) {
// 开始播放
printf("start play\n");
printf("client ip:%s\n", clientIP);
printf("client port:%d\n", clientRtpPort);
// 发送RTP包的无限循环
while (true) {
// 休眠 40 毫秒,模拟每秒 25 帧的视频传输
Sleep(40);
// usleep(40000); // 另一种实现(Linux),单位微秒
}
// 结束 RTP 发送
break;
}
// 清空变量,为下一次解析做准备
memset(method, 0, sizeof(method) / sizeof(char));
memset(url, 0, sizeof(url) / sizeof(char));
CSeq = 0;
}
// 关闭客户端的 socket
closesocket(clientSockfd);
// 释放动态分配的内存
free(rBuf);
free(sBuf);
}
int main(int argc, char* argv[])
{
// 启动windows socket start
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("PC Server Socket Start Up Error \n");
return -1;
}
// 启动windows socket end
int serverSockfd;
serverSockfd = createTcpSocket();
if (serverSockfd < 0)
{
WSACleanup();
printf("failed to create tcp socket\n");
return -1;
}
if (bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT) < 0)
{
printf("failed to bind addr\n");
return -1;
}
if (listen(serverSockfd, 10) < 0)
{
printf("failed to listen\n");
return -1;
}
printf("%s rtsp://127.0.0.1:%d\n", __FILE__, SERVER_PORT);
while (true) {
int clientSockfd;
char clientIp[40];
int clientPort;
clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
if (clientSockfd < 0)
{
printf("failed to accept client\n");
return -1;
}
printf("accept client;client ip:%s,client port:%d\n", clientIp, clientPort);
doClient(clientSockfd, clientIp, clientPort);
}
closesocket(serverSockfd);
return 0;
}
客户端连接
服务器