从零开始写一个RTSP服务器系列
从零开始写一个RTSP服务器(四)一个传输H.264的RTSP服务器
从零开始写一个RTSP服务器(六)一个传输AAC的RTSP服务器
从零开始写一个RTSP服务器(九)一个RTP OVER RTSP/TCP的RTSP服务器
从零开始写一个RTSP服务器(二)RTSP协议的实现
文章目录
写在前面
此系列只追求精简,旨在学习RTSP协议的实现过程,不追求复杂完美,所以这里要实现的RTSP服务器为了简单,实现上同一时间只能有一个客户端,下面开始介绍实现过程
在写一个RTSP服务器之前,我们必须知道一个RTSP服务器最简单的包含两部分,一部分是RTSP的交互,一部分是RTP发送,本文先实现RTSP交互过程
一、创建套接字
想一下我们在vlc输入rtsp://127.0.0.1:8554
后发生了什么事?
在这种情况下,vlc其实是一个rtsp客户端,当输入这个url后,vlc知道目的IP为127.0.0.1
,目的端口号为8854
,这时vlc会发起一个tcp连接取连接服务器,连接成功后就开始发送请求,服务端响应
所以我们要写一个rtsp服务器,第一步肯定是创建tcp服务器
首先创建tcp套接字,绑定端口,监听
-
创建套接字
/* * 作者:_JT_ * 博客:https://blog.youkuaiyun.com/weixin_42462202 */ serverSockfd = socket(AF_INET, SOCK_STREAM, 0); setsockopt(serverSockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
-
绑定地址和端口号
bind(serverSockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)
这个示例绑定的地址是
INADDR_ANY
,端口号为8554
-
开始监听
listen(serverSockfd, 10);
RTSP服务器传输音视频数据和信息使用的是RTP和RTCP,所以我们还要为RTP和RTCP创建UDP套接字,并绑定号端口
-
创建套接字
serverRtpSockfd = createUdpSocket(); serverRtcpSockfd = createUdpSocket();
-
绑定端口号
bindSocketAddr(serverRtpSockfd, "0.0.0.0", SERVER_RTP_PORT); bindSocketAddr(serverRtcpSockfd, "0.0.0.0", SERVER_RTCP_PORT);
当创建好套接字还有绑定号端口后,就可以接收客户端请求了
-
开始accept等待客户端连接
clientfd = accept(serverSockfd, (struct sockaddr *)&addr, &len);
二、解析请求
当rtsp客户端连接成功后就会开始发送请求,服务器这是需要接收客户端请求并开始解析,再采取相应得操作
请求的格式为(详细参考上一篇从零开始写一个RTSP服务器(一)不一样的RTSP协议讲解)
OPTIONS rtsp://127.0.0.1:8554/live RTSP/1.0\r\n
CSeq: 2\r\n
\r\n
DESCRIBE rtsp://127.0.0.1:8554/live RTSP/1.0\r\n
CSeq: 3\r\n
Accept: application/sdp\r\n
\r\n
SETUP rtsp://127.0.0.1:8554/live/track0 RTSP/1.0\r\n
CSeq: 4\r\n
Transport: RTP/AVP;unicast;client_port=54492-54493\r\n
\r\n
PLAY rtsp://127.0.0.1:8554/live RTSP/1.0\r\n
CSeq: 5\r\n
Session: 66334873\r\n
Range: npt=0.000-\r\n
\r\n
这里我们做得最简单,首先解析第一行
得到方法
,对于OPTIONS
、DESCRIBE
、PLAY
、TEARDOWN
我们只解析CSeq
。对于SETUP
,我们讲client_port
解析出来
所以我们要做的第一步就是解析请求中的信息
-
接收客户端数据
recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
这里实现了一个简单得函数
getLineFromBuf
,从buf中读取一行(\r\n) -
解析第一行请求得到方法
sscanf(line, "%s %s %s\r\n", method, url, version);
-
其次解析
CSeq
sscanf(line, "CSeq: %d\r\n", &cseq)
-
如果方法是
SETUP
则再解析client_port
if(!strcmp(method, "SETUP")) { sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", &clientRtpPort, &clientRtcpPort); }
解析完请求命令后,接下来就是更具不同得方法做不同的响应了,如下
/*
* 作者:_JT_
* 博客:https://blog.youkuaiyun.com/weixin_42462202
*/
if(!strcmp(method, "OPTIONS"))
{
handleCmd_OPTIONS();
}
else if(!strcmp(method, "DESCRIBE"))
{
handleCmd_DESCRIBE();
}
else if(!strcmp(method, "SETUP"))
{
handleCmd_SETUP();
}
else if(!strcmp(method, "PLAY"))
{
handleCmd_PLAY();
}
else if(!strcmp(method, "TEARDOWN"))
{
handleCmd_TEARDOWN();
}
三、OPTIONS响应
OPTIONS是客户端向服务端请求可用的方法,我们这里就向客户端回复我们当前可用的方法
sprintf(sBuf, "RTSP/1.0 200 OK\r\n"
"CSeq: %d\r\n"
"Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
"\r\n",
cseq);
send(clientSockfd, sBuf, strlen(sBuf));
四、DESCRIBE响应
DESCRIBE是客户端向服务器请求媒体信息,这是服务器需要回复sdp描述文件,这个例子中的媒体是H.264
-
sdp文件生成
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(sBuf, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Content-B