| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- /******************************************************************************************
- Copyright(C), 2019-2021, 个人。
- 文件名:main.c
- 作者:Wind 版本:V1.0 日期:2020.4.1
- 文件描述:
- 对纯视频H264码流的RTP传输的实现。
- 其它说明:
- 当前文件仅用于个人学习。
- 历史修改记录:
- 1. 2020-4-1:Wind
- 创建。
- 2. 2021-6-19:Wind
- 修改格式以向实验室开放。
- ******************************************************************************************/
- //+------------------------------------------------------------------------------------------+
- //| RTP测试说明
- //+------------------------------------------------------------------------------------------+
- //| 测试使用VLC播放器进行,首先建立文件输入以下内容并保存为.sdp文件:
- //| m=video 1118 RTP/AVP 96
- //| a=rtpmap:96 H264
- //| a=framerate:30
- //| c=IN IP4 169.254.134.37
- //| 其中端口和RTP发送端地址需要根据实际设置修改,使用VLC打开此.sdp文件即可进行播放。
- //| 注:如果VLC播放器提示SDP文件格式不正确,可更换VLC版本再试。
- //| 注:播放前可能需首先关闭防火墙。
- //+------------------------------------------------------------------------------------------+
- //| 头文件包含
- //+------------------------------------------------------------------------------------------+
- /*|*/#include <stdio.h>
- /*|*/#include <string.h>
- /*|*/#include <stdlib.h>
- /*|*/#include <unistd.h>
- /*|*/#include <sys/ioctl.h>
- /*|*/
- /*|*/#include "L_RTP.h"
- //+------------------------------------------------------------------------------------------+
- //| 函数名称:L_RTP_CreateSession
- //| 功能描述:创建一个RTP会话,主要是打开对客户端的Socket端口。
- //| 参数说明:L_STRUCT_RTPSESSION结构体指针
- //| 返回值说明:成功返回0,失败返回-1。
- //| 备注:初始化中,执行如下操作:
- //| 1. 检测结构体是否已经初始化过
- //| 2. 打开一个数据报套接字端口
- //| 3. 按需设置socket属性
- //| 4. 获取本机IP
- //| 5. 置结构体内已初始化标志位
- //| 以上任意一步失败则回滚操作并退出。
- //+------------------------------------------------------------------------------------------+
- int L_RTP_CreateSession(L_STRUCT_RTPSESSION* RTPSession)
- {
- //****************************************
- //这里的判断寄期望于编译器初始化结构体的时候
- //将此变量赋值为0,或者调用函数在设置参数时将
- //此标志位置0。
- //****************************************
- if(RTPSession->flag_inited)
- {
- printf("The Struct has been inited!\n");
- goto QUIT;
- }
- //对于没有初始化的结构体,某些变量需要初始化
- RTPSession->s32Sock = -1; //描述符无效
- RTPSession->pData = NULL; //数据指针为空
- RTPSession->DataLength = 0; //数据长度为0
- RTPSession->DataType = RTP_NONE;//荷载类型无效
- RTPSession->SequenceNum = 0; //RTP包序列号
- //****************************************
- //关于Unix中套接字使用的协议族,创建套接字时
- //采用PF_x,设置套接字时采用AF_x,这两种形式
- //的差别并不大,甚至可以混用。
- //****************************************
- if((RTPSession->s32Sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
- {
- printf("Open socket failed!\n");
- goto QUIT;
- }
- //如果地址为255.x.x.x则设置套接字允许广播
- if(0xFF000000 == (RTPSession->u32DestIP&0xFF000000))
- {
- int s32Broadcast = 1;
- if(-1 == setsockopt(RTPSession->s32Sock, SOL_SOCKET, SO_BROADCAST, (char *)&s32Broadcast, sizeof(s32Broadcast)))
- {
- printf("Set socket failed!\n");
- goto QUIT;
- }
- }
- //填充目的端地址
- RTPSession->stDestAddr.sin_family = AF_INET;
- RTPSession->stDestAddr.sin_port = htons(RTPSession->DestPort);
- RTPSession->stDestAddr.sin_addr.s_addr = RTPSession->u32DestIP;
- bzero(&(RTPSession->stDestAddr.sin_zero), 8);
- //****************************************
- //获取本机网络设备名
- //此处只尝试获取了eth0(有线网络)和wlan0
- //(无线网络),但是并非所有标号都是这两种,
- //在多网卡情况下或者使用特殊的网卡,默认名称
- //会随之更换,程序中也应作出修改。
- //****************************************
- strcpy(RTPSession->stIfreq.ifr_name, "eth0");
- if(ioctl(RTPSession->s32Sock, SIOCGIFADDR, &(RTPSession->stIfreq)) < 0)
- {
- printf("Get eth0 IP failed!\n");
- strcpy(RTPSession->stIfreq.ifr_name, "wlan0");
- if(ioctl(RTPSession->s32Sock, SIOCGIFADDR, &(RTPSession->stIfreq)) < 0)
- {
- printf("Get wlan0 IP failed!\n");
- goto QUIT;
- }
- }
- //****************************************
- //计算本机IP的网络字节序并保存
- //RTP是一个单向的发送协议,因此对于发送端而言
- //不需要指定源端口号,如果需要设置可以在此处
- //初始化的时候使用bind函数对Socket进行定位。
- //在客户端接收数据的时候也不需要发送端的端口号,
- //客户端监听的是目的端口号。
- //****************************************
- RTPSession->u32SrcIP = htonl(((struct sockaddr_in *)(&RTPSession->stIfreq.ifr_addr))->sin_addr.s_addr);
- RTPSession->flag_inited = 1;//置标志位,表示该结构指代的RTP会话已经初始化成功
- return 0;
- QUIT:
- //Socket描述符不为-1说明已经打开,故异常退出需要关闭
- if(RTPSession->s32Sock >= 0)
- {
- close(RTPSession->s32Sock);
- }
- return -1;
- }
- //+------------------------------------------------------------------------------------------+
- //| 函数名称:L_RTP_DestorySession
- //| 功能描述:销毁一个RTP会话,主要是销毁对客户端的Socket端口。
- //| 参数说明:L_STRUCT_RTPSESSION结构体指针
- //| 返回值说明:成功返回0,失败返回-1。
- //| 备注:
- //+------------------------------------------------------------------------------------------+
- int L_RTP_DestorySession(L_STRUCT_RTPSESSION* RTPSession)
- {
- if(!RTPSession->flag_inited)
- {
- printf("The Struct has NOT been inited!\n");
- goto QUIT;
- }
- close(RTPSession->s32Sock);
- RTPSession->flag_inited = 0;
- RTPSession->pData = NULL;
- RTPSession->DataLength = 0;
- RTPSession->DataType = RTP_NONE;
- return 0;
- QUIT:
- return -1;
- }
- //+------------------------------------------------------------------------------------------+
- //| 函数名称:L_RTP_Send_H264NALU
- //| 功能描述:发送H264数据流。
- //| 参数说明:L_STRUCT_RTPSESSION结构体指针
- //| 返回值说明:成功返回0,失败返回-1。
- //| 备注:用此函数发送的数据流不应包含起始码。
- //+------------------------------------------------------------------------------------------+
- static int L_RTP_Send_H264NALU(L_STRUCT_RTPSESSION* RTPSession)
- {
- int ret = 0;
- unsigned char NALUByte;
- L_STRUCT_RTPHEADER *pRTPHeader;
- unsigned char *pRTPSendBuf;
- //****************************************
- //RTP首部是12个字节,通过对结构体的强制类型转换
- //将RTP首部的存放位置与申请的发送缓冲区共享。
- //****************************************
- pRTPSendBuf = (unsigned char *)calloc(RTP_H264_PACKAGE_LENGTH+60, sizeof(unsigned char));
- if(pRTPSendBuf == NULL)
- {
- printf("Calloc failed!\n");
- ret = -1;
- goto QUIT;
- }
- pRTPHeader = (L_STRUCT_RTPHEADER *)pRTPSendBuf;
- //初始化RTP首部公共部分
- pRTPHeader->u7Payload = RTP_HEAD_PAYLOAD_H264;
- pRTPHeader->u2Version = 2;
- pRTPHeader->u1Marker = 0;
- pRTPHeader->u32SSrc = RTPSession->u32SrcIP;
- //****************************************
- //时间戳计算
- //这里计算使用的是编码器生成H264码流的时候
- //码流包中携带的时间戳信息,为无符号64位整型值,
- //单位是us。
- //RTP码流中视频时间戳是基于90KHz的,故其单位为
- //1/90000,RTP首部中的时间戳要换算成该单位下
- //的变量,方法是(以下计算忽略了变量范围):
- //RTPTimeStamp=H264TimeStamp*90000/1000000。
- //****************************************
- pRTPHeader->u32TimeStamp = htonl((unsigned long)(RTPSession->TimeStamp*9/100));
- //****************************************
- //由于RTP的定义时依照大端模式进行的,所以
- //所有有关RTP协议部分的参数都需要转换成
- //对应格式,这是NALU头需要单独提取的原因。
- //****************************************
- //提取NALU头
- NALUByte = *(RTPSession->pData);
- RTPSession->pData++;
- RTPSession->DataLength--;
- //****************************************
- //分片处理,对分片的说明见分片长度宏定义注释
- //分片和不分片,对NALU头的处理方式是不同的。
- //在不分片的情况下,一个RTP包格式是这样的:
- //RTP首部(12Bytes)+NALU(1Byte)+数据
- //在分片的时候,分片前端是这样的:
- //RTP首部(12Bytes)+FUA指示(1Byte)
- //+FUA头(1Byte)+数据+...
- //这时,NALU头会被分为两个部分分别存放在
- //FUA指示字节和FUA头中。
- //****************************************
- if(RTPSession->DataLength <= RTP_H264_PACKAGE_LENGTH)
- {
- L_STRUCT_RTP_NALUHEADER *pRTPNALUHeader;
- pRTPHeader->u1Marker = 1;
- pRTPHeader->u16SeqNum = htons(RTPSession->SequenceNum++);
- //将NALU头写到RTP首部之后(占用1Byte)
- pRTPNALUHeader = (L_STRUCT_RTP_NALUHEADER *)(pRTPSendBuf+12);
- pRTPNALUHeader->u1F = (NALUByte & 0x80) >> 7;
- pRTPNALUHeader->u2Nri = (NALUByte & 0x60) >> 5;
- pRTPNALUHeader->u5Type = NALUByte & 0x1f;
- //拷贝数据流到缓冲区
- memcpy(pRTPSendBuf+13, RTPSession->pData, RTPSession->DataLength);
- //发送数据到目的地址
- if(sendto(RTPSession->s32Sock, pRTPSendBuf, RTPSession->DataLength+13, 0, (struct sockaddr *)&RTPSession->stDestAddr, sizeof(RTPSession->stDestAddr)) < 0)
- {
- printf("Socket send failed!\n");
- ret = -1;
- goto QUIT;
- }
- }
- else
- {
- L_STRUCT_RTP_FUA_INDICATOR *pRTPFUAIndicator;
- L_STRUCT_RTP_FUA_HEADER *pRTPFUAHeader;
- int tmp_is_first = 1;//用于指示分批发送的数据是否为第一批
- int tmp_sendlength = 0;//用于记录每次发送的数据长度
- //填充FUA指示字节
- pRTPFUAIndicator = (L_STRUCT_RTP_FUA_INDICATOR *)(pRTPSendBuf+12);
- pRTPFUAIndicator->u1F = (NALUByte & 0x80) >> 7;
- pRTPFUAIndicator->u2Nri = (NALUByte & 0x60) >> 5;
- pRTPFUAIndicator->u5Type = 28;
- //填充FUA头固定部分
- pRTPFUAHeader = (L_STRUCT_RTP_FUA_HEADER *)(pRTPSendBuf+13);
- pRTPFUAHeader->u1R = 0;
- pRTPFUAHeader->u5Type = NALUByte & 0x1f;
- //分批发送数据
- while(RTPSession->DataLength>0)
- {
- //配置每包RTP首部
- pRTPHeader->u16SeqNum = htons(RTPSession->SequenceNum++);
- pRTPHeader->u1Marker = (RTPSession->DataLength <= RTP_H264_PACKAGE_LENGTH)?1:0;
- //配置FU头
- pRTPFUAHeader->u1E = (RTPSession->DataLength<=RTP_H264_PACKAGE_LENGTH)?1:0;
- if(tmp_is_first == 1)
- {
- pRTPFUAHeader->u1S = 1;
- tmp_is_first = 0;
- }
- else
- {
- pRTPFUAHeader->u1S = 0;
- }
- //计算每次数据提取长度并存入缓冲
- tmp_sendlength = (RTPSession->DataLength <= RTP_H264_PACKAGE_LENGTH)?RTPSession->DataLength:RTP_H264_PACKAGE_LENGTH;
- memcpy(pRTPSendBuf+14, RTPSession->pData, tmp_sendlength);
- //计算发送数据长度并发送数据
- tmp_sendlength += 14;
- if(sendto(RTPSession->s32Sock, pRTPSendBuf, tmp_sendlength, 0, (struct sockaddr *)&RTPSession->stDestAddr, sizeof(RTPSession->stDestAddr)) < 0)
- {
- printf("Socket send failed!\n");
- ret = -1;
- goto QUIT;
- }
- RTPSession->pData += RTP_H264_PACKAGE_LENGTH;
- RTPSession->DataLength -= RTP_H264_PACKAGE_LENGTH;
- }
- //发送完成后复位数据指针和长度变量
- RTPSession->pData = NULL;
- RTPSession->DataLength = 0;
- }
- QUIT:
- if(pRTPSendBuf != NULL)
- {
- free((void *)pRTPSendBuf);
- }
- return ret;
- }
- //+------------------------------------------------------------------------------------------+
- //| 函数名称:L_RTP_Send
- //| 功能描述:通过RTP发送数据。
- //| 参数说明:L_STRUCT_RTPSESSION结构体指针
- //| 返回值说明:成功返回0,失败返回-1。
- //| 备注:对于测试使用的海思平台编码的H264流,发送需要采取多包模式,每包一发,这是本程序的去
- //| 起始码部分的编写方式决定的。
- //+------------------------------------------------------------------------------------------+
- int L_RTP_Send(L_STRUCT_RTPSESSION* RTPSession)
- {
- if(RTPSession->DataType == RTP_NONE || RTPSession->pData == NULL || RTPSession->DataLength == 0)
- {
- printf("Structure is not effectively populated!\n");
- goto QUIT;
- }
- switch(RTPSession->DataType)
- {
- case RTP_H264:
- //****************************************
- //首先去掉数据流的起始码(00 00 00 01)
- //H264起始码占有4个字节,所以对于有起始码的数据
- //只需要使其指针指向的位置后移4位。
- //对应的数据长度减少4即可。
- //
- //然而需要注意的是,有可能不是所有的编码器都是
- //4个字节的起始码。
- //****************************************
- RTPSession->pData = RTPSession->pData + 4;
- RTPSession->DataLength = RTPSession->DataLength - 4;
- case RTP_H264NALU:
- L_RTP_Send_H264NALU(RTPSession);
- break;
- case RTP_NONE:
- default:
- printf("There is no corresponding operation function for this type!\n");
- goto QUIT;
- }
- return 0;
- QUIT:
- return -1;
- }
|