概述
学习的demo来自于B站“北小菜”博主。主要基于该demo理解RTSP服务器的框架。该demo主要实现了从本地H264视频文件中读取视频帧,然后通过RTP协议实时传输给RTSP客户端
主要逻辑
- 初始化,创建RTSP服务器监听套接字,绑定地址和端口号,然后开始监听
- 主循环
- 不断的接收客户端连接然后进行处理
- 打印服务器信息,并不对其进行处理
RTSP客户端
static void doClient(int clientSockfd, const char* clientIP, int clientPort) {
char method[40];
char url[100];
char version[40];
int CSeq;
int serverRtpSockfd = -1, serverRtcpSockfd = -1;
int clientRtpPort, clientRtcpPort;
char* rBuf = (char*)malloc(BUF_MAX_SIZE);
char* sBuf = (char*)malloc(BUF_MAX_SIZE);
while (true) {
int recvLen;
recvLen = recv(clientSockfd, rBuf, BUF_MAX_SIZE, 0);
if (recvLen <= 0) {
break;
}
rBuf[recvLen] = '\0';
printf("%s rBuf = %s \n",__FUNCTION__,rBuf);
const char* sep = "\n";
char* line = strtok(rBuf, sep);
while (line) {
if (strstr(line, "OPTIONS") ||
strstr(line, "DESCRIBE") ||
strstr(line, "SETUP") ||
strstr(line, "PLAY")) {
if (sscanf(line, "%s %s %s\r\n", method, url, version) != 3) {
// error
}
}
else if (strstr(line, "CSeq")) {
if (sscanf(line, "CSeq: %d\r\n", &CSeq) != 1) {
// error
}
}
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) {
// error
printf("parse Transport error \n");
}
}
line = strtok(NULL, sep);
}
if (!strcmp(method, "OPTIONS")) {
if (handleCmd_OPTIONS(sBuf, CSeq))
{
printf("failed to handle options\n");
break;
}
}
else if (!strcmp(method, "DESCRIBE")) {
if (handleCmd_DESCRIBE(sBuf, CSeq, url))
{
printf("failed to handle describe\n");
break;
}
}
else if (!strcmp(method, "SETUP")) {
if (handleCmd_SETUP(sBuf, CSeq, clientRtpPort))
{
printf("failed to handle setup\n");
break;
}
serverRtpSockfd = createUdpSocket();
serverRtcpSockfd = createUdpSocket();
if (serverRtpSockfd < 0 || serverRtcpSockfd < 0)
{
printf("failed to create udp socket\n");
break;
}
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");
break;
}
}
else if (!strcmp(method, "PLAY")) {
if (handleCmd_PLAY(sBuf, CSeq))
{
printf("failed to handle play\n");
break;
}
}
else {
printf("未定义的method = %s \n", method);
break;
}
printf("sBuf = %s \n", sBuf);
printf("%s sBuf = %s \n", __FUNCTION__, sBuf);
send(clientSockfd, sBuf, strlen(sBuf), 0);
//开始播放,发送RTP包
if (!strcmp(method, "PLAY")) {
int frameSize, startCode;
char* frame = (char*)malloc(500000);
struct RtpPacket* rtpPacket = (struct RtpPacket*)malloc(500000);
FILE* fp = fopen(H264_FILE_NAME, "rb");
if (!fp) {
printf("读取 %s 失败\n", H264_FILE_NAME);
break;
}
rtpHeaderInit(rtpPacket, 0, 0, 0, RTP_VESION, RTP_PAYLOAD_TYPE_H264, 0,
0, 0, 0x88923423);
printf("start play\n");
printf("client ip:%s\n", clientIP);
printf("client port:%d\n", clientRtpPort);
while (true) {
frameSize = getFrameFromH264File(fp, frame, 500000);
if (frameSize < 0)
{
printf("读取%s结束,frameSize=%d \n", H264_FILE_NAME, frameSize);
break;
}
if (startCode3(frame))
startCode = 3;
else
startCode = 4;
frameSize -= startCode;
rtpSendH264Frame(serverRtpSockfd, clientIP, clientRtpPort,
rtpPacket, frame + startCode, frameSize);
Sleep(40);
//usleep(40000);//1000/25 * 1000
}
free(frame);
free(rtpPacket);
break;
}
memset(method,0,sizeof(method)/sizeof(char));
memset(url,0,sizeof(url)/sizeof(char));
CSeq = 0;
}
closesocket(clientSockfd);
if (serverRtpSockfd) {
closesocket(serverRtpSockfd);
}
if (serverRtcpSockfd > 0) {
closesocket(serverRtcpSockfd);
}
free(rBuf);
free(sBuf);
}
逻辑分析
首先接收并解析RTSP请求;获取RTSP请求,分析其命令;然后通过分支语句对不同的RTSP命令进行处理并最终生成响应
RTP流传输(代码事例中具体通过PLAY命令处理)
- 首先是打开H264文件
- 然后初始化RTP包头,循环读取H264帧并发送RTP包
- 代码中使用的是模拟帧率的方法,真实代码实现中需要根据实际情况设置
RTSP协议交互
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;
}
主要流程
- OPTIONS: 客户端查询服务器支持的命令
- DESCRIBE: 客户端请求服务器描述媒体会话,服务器返回SDP (Session Descr

最低0.47元/天 解锁文章
2880

被折叠的 条评论
为什么被折叠?



