当前位置: 首页 > news >正文

项目--基于RTSP协议的简易服务器开发(2)

一、项目创立初衷:

由于之前学过计算机网络的相关知识,了解了计算机网络的基本工作原理,对于主流的协议有一定的了解。但对于应用层的协议还知之甚少,因此我去了解了下目前主要的应用层传输协议,发现RTSP(实时传输协议)在实时传输方面有很大的贡献,于是我开始了对于该协议的学习、研究,并创建了该项目。
 

二、总体设计:

 服务器处理流程简述:

        创建tcp连接套接字;绑定地址;监听;

        再建立通信套接字,接收客户端连接-->处理客户端请求-->处理完毕,释放资源、关闭套接字。

三、细部设计

1.rtp数据包的设计及相关函数:

要想发送rtp数据包,首先要定义rtp包结构体(在rtp.h文件中设置):

rtp首部字段:

struct RtpHeader
{//rtp协议头部/* byte 0 */uint8_t csrcLen : 4;//CSRC计数器,占4位,指示CSRC 标识符的个数。uint8_t extension : 1;//占1位,如果X=1,则在RTP报头后跟有一个扩展报头。uint8_t padding : 1;//填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。uint8_t version : 2;//RTP协议的版本号,占2位,当前协议版本号为2。/* byte 1 */uint8_t payloadType : 7;//有效载荷类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等。uint8_t marker : 1;//标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。/* bytes 2,3 */uint16_t seq;//占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。接收者通过序列号来检测报文丢失情况,重新排序报文,恢复数据。/* bytes 4-7 */uint32_t timestamp;//占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。/* bytes 8-11 */uint32_t ssrc;//占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。/*标准的RTP Header 还可能存在 0-15个特约信源(CSRC)标识符每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源*/
};

rtp包结构体:

struct RtpPacket
{   //头部字段struct RtpHeader rtpheader;//信息载体uint8_t payload [0];
};

定义初始化rtp包的函数、通过udp发送rtp数据包的函数(rtp.cpp文件中):

//初始化数据包

void rtpHeaderInit(struct RtpPacket* rtpPacket, uint8_t csrcLen, uint8_t extension,
    uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,
    uint16_t seq, uint32_t timestamp, uint32_t ssrc)

//通过udp发送

int rtpSendPacketOverUdp(int serverRtpSockfd, const char* ip, int16_t port, struct RtpPacket* rtpPacket, uint32_t dataSize)

2.处理客户端流程:

1.根据创建的通信套接字,接收客户端发送的数据(rtsp数据包的负载),存储到接收缓冲区

2.通过接收的数据,判断客户端的请求类型:

由于连接时,双方发送的rtsp信息以 \r\n结尾:

eg:

所以可以用strtok函数,将接收的数据分行存储,并逐次判断:

const char* sep = "\n";//1.分解接受缓冲区中数据,根据不同类型做出相应
char* line = strtok(rBuf, sep);     //以sep分割字符串rBuf,并存储在line中

再以用strstr函数,判断是哪种请求(RTSP的请求:OPTIONS、DESCRIBE、SETUP、PLAY、TERADOWN),若不是请求字段,则根据其数据格式,考察其他字段。

strstr(line,"OPTIONOS");

eg:

根据接收的消息,找到关于客户端的相关信息,为数据包的发送做准备。 

   //考察CSeq字段else if (strstr(line, "CSeq")) {if (sscanf(line, "CSeq: %d\r\n", &CSeq) != 1) {     //将服务器收到的CSeq赋值给C传入的CSeq参数// error}}//考察Transport字段,else if (!strncmp(line, "Transport:", strlen("Transport:"))) {// Transport: RTP/AVP/UDP;unicast;client_port=13358-13359// Transport: RTP/AVP;unicast;client_port=13358-13359if (sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",&clientRtpPort, &clientRtcpPort) != 2) {    //从请求消息中获取客户端 ip、port// errorprintf("parse Transport error \n");}}line = strtok(NULL, sep);   //将line(客户端发送的数据)进行到下一行}

3.根据其请求的类型,做出相应的处理:

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;
}//传入序号、Rtp端口参数,作为SETUP请求的回应
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 int handleTEARDOWN(char *result,int cseq)
{                               sprintf(result,"RTSP/1.0 200 OK\r\n""CSeq: %d \r\n",cseq);return 0;
}

若为setup请求,需要开始建立连接,创建rtp、rtcp udp套接字,绑定地址,最后在传输阶段(PLAY),通过这两个套接字,以及接收到的客户端的端口,发送数据

4.在发送数据前,要先从本地的h264文件读取数据:

由于h264文件是由一个个NALU构成的,且每个NALU之间有固定的间隔符(00 00 00 01或 00 00 01),

static int getFrameFromH264File(FILE* fp, char* frame, int size) {int rSize, frameSize;char* nextStartCode;if (fp < 0)return -1;rSize = fread(frame, 1, size, fp);//每次读取的长度//h264由一个个NALU构成,每个NALU以 0001 0010隔开//根据分隔符0001 0010每次读取一个NALUif (!startCode3(frame) && !startCode4(frame))return -1;//找到第一个开始的编码nextStartCode = findNextStartCode(frame + 3, rSize - 3);    //减去分隔符,数据指针后移if (!nextStartCode){//lseek(fd, 0, SEEK_SET);//frameSize = rSize;return -1;}else{//寻找成功frameSize = (nextStartCode - frame);//退回到fseek(fp, frameSize - rSize, SEEK_CUR);}return frameSize;
}

5.通过UDP协议发送数据

1.通过framesize大小判断该包应该是何种打包模式

 单一NALU单元打包模式:(framesize<rtp的最大限制)

 memcpy(rtpPacket->payload, frame, frameSize);ret = rtpSendPacketOverUdp(serverRtpSockfd, ip, port, rtpPacket, frameSize);if (ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;   if ((naluType & 0x1F) == 7 || (naluType & 0x1F) == 8) // 如果是SPS、PPS(只是编解码需要的数据,并非视频数据)就不需要加时间戳goto out;

分包模式:(framesize<rtp的最大限制)

nt remainPktSize = frameSize % RTP_MAX_PKT_SIZE; // 剩余不完整包的大小int i, pos = 1;// 发送完整的包for (i = 0; i < pktNum; i++){//根据NALU包格式解析rtpPacket->payload[0] = (naluType & 0x60) | 28;rtpPacket->payload[1] = naluType & 0x1F;if (i == 0) //第一包数据rtpPacket->payload[1] |= 0x80; // startelse if (remainPktSize == 0 && i == pktNum - 1) //最后一包数据rtpPacket->payload[1] |= 0x40; // end//从负载的第三个数据包开始拷贝memcpy(rtpPacket->payload + 2, frame + pos, RTP_MAX_PKT_SIZE);ret = rtpSendPacketOverUdp(serverRtpSockfd, ip, port, rtpPacket, RTP_MAX_PKT_SIZE + 2);if (ret < 0)return -1;rtpPacket->rtpHeader.seq++;sendBytes += ret;pos += RTP_MAX_PKT_SIZE;}// 发送剩余的数据if (remainPktSize > 0){rtpPacket->payload[0] = (naluType & 0x60) | 28;rtpPacket->payload[1] = naluType & 0x1F;rtpPacket->payload[1] |= 0x40; //endmemcpy(rtpPacket->payload + 2, frame + pos, remainPktSize + 2);ret = rtpSendPacketOverUdp(serverRtpSockfd, ip, port, rtpPacket, remainPktSize + 2);if (ret < 0)return -1;rtpPacket->rtpHeader.seq++;     //发送完毕之后进行序号的累加sendBytes += ret;}}

四、效果实现

meeting_01

http://www.lryc.cn/news/34455.html

相关文章:

  • ubus编译_环境搭建
  • 移动通信(16)信号检测
  • 数据结构与算法之《顺序表》
  • MySQL索引15连问,抗住!
  • 【服务器管理】手动部署LNMP环境(CentOS 8)(非阿里云版本)
  • 论文笔记:Positive-incentive Noise
  • 340秒语音芯片,轻松实现语音交互,畅享智能生活WTV380语音ic方案
  • 有java基础学习大数据该如何规划
  • 【Java基础】HashMap的底层数据结构是怎样的?
  • MongoDB5副本集高可用集群部署
  • 【Java】最新版本SpringCloudStream整合RocketMQ实现单项目中事件的发布与监听
  • abp.net 5.0 部署IIS10
  • Windows安装Qt与VS2019添加QT插件
  • 自学大数据第5天~hadoop集群搭建(二)
  • MySQL (六)------MySQL的常用函数、 事务(TCL)、DCL用户操作语句、常见环境、编码问题
  • 【3.8】操作系统内存管理、Redis数据结构、哈希表
  • Shell编程:轻松掌握入门级Shell脚本,成为Shell高手
  • FastApi的搭建与测试
  • C++基础——C++面向对象之重载与多态基础总结(函数重载、运算符重载、多态的使用)
  • 调用一个函数时发生了什么?
  • MindAR的网页端WebAR图片识别功能的图片目标编译器中文离线版本功能(含源码)
  • 测试经理:“你做了三年测试,连服务端的接口测试都不会?”
  • 4G AFR到5G应用场景介绍
  • 正电源子 IMX6ULL 自学笔记(驱动开发)
  • AM5728(AM5708)开发实战之移植OpenCV-3.4.11
  • Notepad++ 下载与安装教程
  • 005+limou+HTML——(5)HTML图片和HTML超链接
  • ES6 Generator
  • SCI期刊写作必备(二):代码|手把手绘制目标检测领域YOLO论文常见的性能对比折线图,一键生成YOLOv7等主流论文同款图表,包含多种不同功能风格对比图表
  • linux cpu飙高排查