编写一个简单的RTSP协议-主流程
参考资料:
- 简单的RTSP服务器:https://github.com/ImSjt/RtspServer
- 从零开始写一个RTSP服务器:https://blog.csdn.net/weixin_42462202/article/details/99068041
- RootEncoder:pedroSG94/RootEncoder: RootEncoder for Android (rtmp-rtsp-stream-client-java) is a stream encoder to push video/audio to media servers using protocols RTMP, RTSP, SRT and UDP with all code written in Java/Kotlin (github.com)
- SmolRTSP:OpenIPC/smolrtsp: A lightweight real-time streaming library for IP cameras (github.com)
在开始我们先了解实现RTSP协议的流程:
- 创建RTSP服务器套接字
- 创建套接字
- 绑定套接字到指定地址和端口
- 监听端口
- 创建RTP和RTCP套接字,并监听对应的端口。
- 接受客户端连接,并获取IP地址和端口号。
- 处理客户端请求和解析。
程序编译与运行:
gcc h264_rtsp_server.c rtp.c -o demo
安装Linux版本的VLC:
sudo apt-get install vlc -y
将test.h264码流文件放在程序的同级目录下并运行程序:
./demo
rtsp://127.0.0.1:8554
打开vlc程序,并将运行后打开的url地址即可播放码流。
1.创建TCP服务器
RTSP 服务器需要一个 TCP 套接字来与客户端进行通信。通过创建 TCP 套接字,服务器能够监听来自客户端的连接请求。
serverSockfd = createTcpSocket();
if(serverSockfd < 0)
{
printf("failed to create tcp socket\n");
return -1;
}
服务器需要绑定到一个特定的 IP 地址和端口,以便客户端知道如何连接到服务器。绑定到 "0.0.0.0"
表示服务器将监听所有可用的网络接口上的指定端口(SERVER_PORT
)。
if(bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT) < 0)
{
printf("failed to bind addr\n");
return -1;
}
在创建和绑定套接字之后,服务器可以调用 listen
函数开始监听连接请求,并使用 accept
函数接受客户端的连接。
if(listen(serverSockfd, 10) < 0)
{
printf("failed to listen\n");
return -1;
}
2.创建RTP和RTCP套接字
创建 RTP 和 RTCP 的 UDP 套接字,准备传输通道:
- RTP 套接字:用于传输实时音视频数据。RTSP 服务器需要一个 RTP 套接字来发送音视频流给客户端。
- RTCP 套接字:用于传输控制信息,如质量反馈和同步信息。RTSP 服务器需要一个 RTCP 套接字来接收和发送这些控制信息。
serverRtpSockfd = createUdpSocket();
serverRtcpSockfd = createUdpSocket();
if(serverRtpSockfd < 0 || serverRtcpSockfd < 0)
{
printf("failed to create udp socket\n");
return -1;
}
绑定套接字到指定地址和端口:
if(bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT) < 0 ||
bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT) < 0)
{
printf("failed to bind addr\n");
return -1;
}
3.接受客户端连接
RTSP 服务器需要接受客户端的连接请求,以便与客户端进行通信。在接受连接的同时,服务器获取了客户端的 IP 地址和端口号。一旦连接建立,服务器可以开始处理客户端的 RTSP 请求,如播放、暂停、停止等操作。
int clientSockfd;
char clientIp[40];
int clientPort;
clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
if(clientSockfd < 0)
{
printf("failed to accept client\n");
return -1;
}
4.处理客户端请求
处理客户端的RTSP请求,并根据不同的请求类型执行相应的操作
doClient(clientSockfd, clientIp, clientPort, serverRtpSockfd, serverRtcpSockfd);
处理客户端请求:接收并解析客户端发送的 RTSP 请求,根据请求类型执行相应的操作。
发送响应:生成并发送 RTSP 响应数据给客户端,确保客户端能够正确接收到服务器的响应。
传输媒体数据:在接收到 PLAY
请求后,通过 RTP 协议将媒体数据(如视频帧)发送给客户端,实现实时流媒体传输。
主函数的处理流程为:
- 接收和解析客户端请求:
- 通过
recv
函数接收客户端发送的 RTSP 请求数据,并将其存储在缓冲区rBuf
中。 - 使用
getLineFromBuf
函数逐行解析请求数据,提取请求方法、URL、版本和序列号(CSeq)。
- 通过
- 处理不同的 RTSP 方法:
- 根据解析出的请求方法(如
OPTIONS
、DESCRIBE
、SETUP
、PLAY
),调用相应的处理函数(如handleCmd_OPTIONS
、handleCmd_DESCRIBE
、handleCmd_SETUP
、handleCmd_PLAY
)。 - 每个处理函数会生成相应的响应数据,并将其存储在发送缓冲区
sBuf
中。
- 根据解析出的请求方法(如
- 发送响应给客户端:
- 将生成的响应数据通过
send
函数发送给客户端。
- 将生成的响应数据通过
- 处理
PLAY
请求并发送 RTP 数据:- 如果请求方法是
PLAY
,则开始读取 H.264 文件中的视频帧,并通过 RTP 协议将视频帧发送给客户端。 - 使用
rtpSendH264Frame
函数发送 RTP 包,并更新 RTP 包头中的时间戳。 - 使用
usleep
函数控制发送帧的间隔时间,以模拟实时播放。
- 如果请求方法是
- 清理资源:
- 在处理完成或发生错误时,关闭客户端套接字并释放分配的内存。