L_RTP.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /******************************************************************************************
  2. Copyright(C), 2019-2021, 个人。
  3. 文件名:main.c
  4. 作者:Wind 版本:V1.0 日期:2020.4.1
  5. 文件描述:
  6. 对纯视频H264码流的RTP传输的实现。
  7. 其它说明:
  8. 当前文件仅用于个人学习。
  9. 历史修改记录:
  10. 1. 2020-4-1:Wind
  11. 创建。
  12. 2. 2021-6-19:Wind
  13. 修改格式以向实验室开放。
  14. ******************************************************************************************/
  15. //+------------------------------------------------------------------------------------------+
  16. //| RTP测试说明
  17. //+------------------------------------------------------------------------------------------+
  18. //| 测试使用VLC播放器进行,首先建立文件输入以下内容并保存为.sdp文件:
  19. //| m=video 1118 RTP/AVP 96
  20. //| a=rtpmap:96 H264
  21. //| a=framerate:30
  22. //| c=IN IP4 169.254.134.37
  23. //| 其中端口和RTP发送端地址需要根据实际设置修改,使用VLC打开此.sdp文件即可进行播放。
  24. //| 注:如果VLC播放器提示SDP文件格式不正确,可更换VLC版本再试。
  25. //| 注:播放前可能需首先关闭防火墙。
  26. //+------------------------------------------------------------------------------------------+
  27. //| 头文件包含
  28. //+------------------------------------------------------------------------------------------+
  29. /*|*/#include <stdio.h>
  30. /*|*/#include <string.h>
  31. /*|*/#include <stdlib.h>
  32. /*|*/#include <unistd.h>
  33. /*|*/#include <sys/ioctl.h>
  34. /*|*/
  35. /*|*/#include "L_RTP.h"
  36. //+------------------------------------------------------------------------------------------+
  37. //| 函数名称:L_RTP_CreateSession
  38. //| 功能描述:创建一个RTP会话,主要是打开对客户端的Socket端口。
  39. //| 参数说明:L_STRUCT_RTPSESSION结构体指针
  40. //| 返回值说明:成功返回0,失败返回-1。
  41. //| 备注:初始化中,执行如下操作:
  42. //| 1. 检测结构体是否已经初始化过
  43. //| 2. 打开一个数据报套接字端口
  44. //| 3. 按需设置socket属性
  45. //| 4. 获取本机IP
  46. //| 5. 置结构体内已初始化标志位
  47. //| 以上任意一步失败则回滚操作并退出。
  48. //+------------------------------------------------------------------------------------------+
  49. int L_RTP_CreateSession(L_STRUCT_RTPSESSION* RTPSession)
  50. {
  51. //****************************************
  52. //这里的判断寄期望于编译器初始化结构体的时候
  53. //将此变量赋值为0,或者调用函数在设置参数时将
  54. //此标志位置0。
  55. //****************************************
  56. if(RTPSession->flag_inited)
  57. {
  58. printf("The Struct has been inited!\n");
  59. goto QUIT;
  60. }
  61. //对于没有初始化的结构体,某些变量需要初始化
  62. RTPSession->s32Sock = -1; //描述符无效
  63. RTPSession->pData = NULL; //数据指针为空
  64. RTPSession->DataLength = 0; //数据长度为0
  65. RTPSession->DataType = RTP_NONE;//荷载类型无效
  66. RTPSession->SequenceNum = 0; //RTP包序列号
  67. //****************************************
  68. //关于Unix中套接字使用的协议族,创建套接字时
  69. //采用PF_x,设置套接字时采用AF_x,这两种形式
  70. //的差别并不大,甚至可以混用。
  71. //****************************************
  72. if((RTPSession->s32Sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
  73. {
  74. printf("Open socket failed!\n");
  75. goto QUIT;
  76. }
  77. //如果地址为255.x.x.x则设置套接字允许广播
  78. if(0xFF000000 == (RTPSession->u32DestIP&0xFF000000))
  79. {
  80. int s32Broadcast = 1;
  81. if(-1 == setsockopt(RTPSession->s32Sock, SOL_SOCKET, SO_BROADCAST, (char *)&s32Broadcast, sizeof(s32Broadcast)))
  82. {
  83. printf("Set socket failed!\n");
  84. goto QUIT;
  85. }
  86. }
  87. //填充目的端地址
  88. RTPSession->stDestAddr.sin_family = AF_INET;
  89. RTPSession->stDestAddr.sin_port = htons(RTPSession->DestPort);
  90. RTPSession->stDestAddr.sin_addr.s_addr = RTPSession->u32DestIP;
  91. bzero(&(RTPSession->stDestAddr.sin_zero), 8);
  92. //****************************************
  93. //获取本机网络设备名
  94. //此处只尝试获取了eth0(有线网络)和wlan0
  95. //(无线网络),但是并非所有标号都是这两种,
  96. //在多网卡情况下或者使用特殊的网卡,默认名称
  97. //会随之更换,程序中也应作出修改。
  98. //****************************************
  99. strcpy(RTPSession->stIfreq.ifr_name, "eth0");
  100. if(ioctl(RTPSession->s32Sock, SIOCGIFADDR, &(RTPSession->stIfreq)) < 0)
  101. {
  102. printf("Get eth0 IP failed!\n");
  103. strcpy(RTPSession->stIfreq.ifr_name, "wlan0");
  104. if(ioctl(RTPSession->s32Sock, SIOCGIFADDR, &(RTPSession->stIfreq)) < 0)
  105. {
  106. printf("Get wlan0 IP failed!\n");
  107. goto QUIT;
  108. }
  109. }
  110. //****************************************
  111. //计算本机IP的网络字节序并保存
  112. //RTP是一个单向的发送协议,因此对于发送端而言
  113. //不需要指定源端口号,如果需要设置可以在此处
  114. //初始化的时候使用bind函数对Socket进行定位。
  115. //在客户端接收数据的时候也不需要发送端的端口号,
  116. //客户端监听的是目的端口号。
  117. //****************************************
  118. RTPSession->u32SrcIP = htonl(((struct sockaddr_in *)(&RTPSession->stIfreq.ifr_addr))->sin_addr.s_addr);
  119. RTPSession->flag_inited = 1;//置标志位,表示该结构指代的RTP会话已经初始化成功
  120. return 0;
  121. QUIT:
  122. //Socket描述符不为-1说明已经打开,故异常退出需要关闭
  123. if(RTPSession->s32Sock >= 0)
  124. {
  125. close(RTPSession->s32Sock);
  126. }
  127. return -1;
  128. }
  129. //+------------------------------------------------------------------------------------------+
  130. //| 函数名称:L_RTP_DestorySession
  131. //| 功能描述:销毁一个RTP会话,主要是销毁对客户端的Socket端口。
  132. //| 参数说明:L_STRUCT_RTPSESSION结构体指针
  133. //| 返回值说明:成功返回0,失败返回-1。
  134. //| 备注:
  135. //+------------------------------------------------------------------------------------------+
  136. int L_RTP_DestorySession(L_STRUCT_RTPSESSION* RTPSession)
  137. {
  138. if(!RTPSession->flag_inited)
  139. {
  140. printf("The Struct has NOT been inited!\n");
  141. goto QUIT;
  142. }
  143. close(RTPSession->s32Sock);
  144. RTPSession->flag_inited = 0;
  145. RTPSession->pData = NULL;
  146. RTPSession->DataLength = 0;
  147. RTPSession->DataType = RTP_NONE;
  148. return 0;
  149. QUIT:
  150. return -1;
  151. }
  152. //+------------------------------------------------------------------------------------------+
  153. //| 函数名称:L_RTP_Send_H264NALU
  154. //| 功能描述:发送H264数据流。
  155. //| 参数说明:L_STRUCT_RTPSESSION结构体指针
  156. //| 返回值说明:成功返回0,失败返回-1。
  157. //| 备注:用此函数发送的数据流不应包含起始码。
  158. //+------------------------------------------------------------------------------------------+
  159. static int L_RTP_Send_H264NALU(L_STRUCT_RTPSESSION* RTPSession)
  160. {
  161. int ret = 0;
  162. unsigned char NALUByte;
  163. L_STRUCT_RTPHEADER *pRTPHeader;
  164. unsigned char *pRTPSendBuf;
  165. //****************************************
  166. //RTP首部是12个字节,通过对结构体的强制类型转换
  167. //将RTP首部的存放位置与申请的发送缓冲区共享。
  168. //****************************************
  169. pRTPSendBuf = (unsigned char *)calloc(RTP_H264_PACKAGE_LENGTH+60, sizeof(unsigned char));
  170. if(pRTPSendBuf == NULL)
  171. {
  172. printf("Calloc failed!\n");
  173. ret = -1;
  174. goto QUIT;
  175. }
  176. pRTPHeader = (L_STRUCT_RTPHEADER *)pRTPSendBuf;
  177. //初始化RTP首部公共部分
  178. pRTPHeader->u7Payload = RTP_HEAD_PAYLOAD_H264;
  179. pRTPHeader->u2Version = 2;
  180. pRTPHeader->u1Marker = 0;
  181. pRTPHeader->u32SSrc = RTPSession->u32SrcIP;
  182. //****************************************
  183. //时间戳计算
  184. //这里计算使用的是编码器生成H264码流的时候
  185. //码流包中携带的时间戳信息,为无符号64位整型值,
  186. //单位是us。
  187. //RTP码流中视频时间戳是基于90KHz的,故其单位为
  188. //1/90000,RTP首部中的时间戳要换算成该单位下
  189. //的变量,方法是(以下计算忽略了变量范围):
  190. //RTPTimeStamp=H264TimeStamp*90000/1000000。
  191. //****************************************
  192. pRTPHeader->u32TimeStamp = htonl((unsigned long)(RTPSession->TimeStamp*9/100));
  193. //****************************************
  194. //由于RTP的定义时依照大端模式进行的,所以
  195. //所有有关RTP协议部分的参数都需要转换成
  196. //对应格式,这是NALU头需要单独提取的原因。
  197. //****************************************
  198. //提取NALU头
  199. NALUByte = *(RTPSession->pData);
  200. RTPSession->pData++;
  201. RTPSession->DataLength--;
  202. //****************************************
  203. //分片处理,对分片的说明见分片长度宏定义注释
  204. //分片和不分片,对NALU头的处理方式是不同的。
  205. //在不分片的情况下,一个RTP包格式是这样的:
  206. //RTP首部(12Bytes)+NALU(1Byte)+数据
  207. //在分片的时候,分片前端是这样的:
  208. //RTP首部(12Bytes)+FUA指示(1Byte)
  209. //+FUA头(1Byte)+数据+...
  210. //这时,NALU头会被分为两个部分分别存放在
  211. //FUA指示字节和FUA头中。
  212. //****************************************
  213. if(RTPSession->DataLength <= RTP_H264_PACKAGE_LENGTH)
  214. {
  215. L_STRUCT_RTP_NALUHEADER *pRTPNALUHeader;
  216. pRTPHeader->u1Marker = 1;
  217. pRTPHeader->u16SeqNum = htons(RTPSession->SequenceNum++);
  218. //将NALU头写到RTP首部之后(占用1Byte)
  219. pRTPNALUHeader = (L_STRUCT_RTP_NALUHEADER *)(pRTPSendBuf+12);
  220. pRTPNALUHeader->u1F = (NALUByte & 0x80) >> 7;
  221. pRTPNALUHeader->u2Nri = (NALUByte & 0x60) >> 5;
  222. pRTPNALUHeader->u5Type = NALUByte & 0x1f;
  223. //拷贝数据流到缓冲区
  224. memcpy(pRTPSendBuf+13, RTPSession->pData, RTPSession->DataLength);
  225. //发送数据到目的地址
  226. if(sendto(RTPSession->s32Sock, pRTPSendBuf, RTPSession->DataLength+13, 0, (struct sockaddr *)&RTPSession->stDestAddr, sizeof(RTPSession->stDestAddr)) < 0)
  227. {
  228. printf("Socket send failed!\n");
  229. ret = -1;
  230. goto QUIT;
  231. }
  232. }
  233. else
  234. {
  235. L_STRUCT_RTP_FUA_INDICATOR *pRTPFUAIndicator;
  236. L_STRUCT_RTP_FUA_HEADER *pRTPFUAHeader;
  237. int tmp_is_first = 1;//用于指示分批发送的数据是否为第一批
  238. int tmp_sendlength = 0;//用于记录每次发送的数据长度
  239. //填充FUA指示字节
  240. pRTPFUAIndicator = (L_STRUCT_RTP_FUA_INDICATOR *)(pRTPSendBuf+12);
  241. pRTPFUAIndicator->u1F = (NALUByte & 0x80) >> 7;
  242. pRTPFUAIndicator->u2Nri = (NALUByte & 0x60) >> 5;
  243. pRTPFUAIndicator->u5Type = 28;
  244. //填充FUA头固定部分
  245. pRTPFUAHeader = (L_STRUCT_RTP_FUA_HEADER *)(pRTPSendBuf+13);
  246. pRTPFUAHeader->u1R = 0;
  247. pRTPFUAHeader->u5Type = NALUByte & 0x1f;
  248. //分批发送数据
  249. while(RTPSession->DataLength>0)
  250. {
  251. //配置每包RTP首部
  252. pRTPHeader->u16SeqNum = htons(RTPSession->SequenceNum++);
  253. pRTPHeader->u1Marker = (RTPSession->DataLength <= RTP_H264_PACKAGE_LENGTH)?1:0;
  254. //配置FU头
  255. pRTPFUAHeader->u1E = (RTPSession->DataLength<=RTP_H264_PACKAGE_LENGTH)?1:0;
  256. if(tmp_is_first == 1)
  257. {
  258. pRTPFUAHeader->u1S = 1;
  259. tmp_is_first = 0;
  260. }
  261. else
  262. {
  263. pRTPFUAHeader->u1S = 0;
  264. }
  265. //计算每次数据提取长度并存入缓冲
  266. tmp_sendlength = (RTPSession->DataLength <= RTP_H264_PACKAGE_LENGTH)?RTPSession->DataLength:RTP_H264_PACKAGE_LENGTH;
  267. memcpy(pRTPSendBuf+14, RTPSession->pData, tmp_sendlength);
  268. //计算发送数据长度并发送数据
  269. tmp_sendlength += 14;
  270. if(sendto(RTPSession->s32Sock, pRTPSendBuf, tmp_sendlength, 0, (struct sockaddr *)&RTPSession->stDestAddr, sizeof(RTPSession->stDestAddr)) < 0)
  271. {
  272. printf("Socket send failed!\n");
  273. ret = -1;
  274. goto QUIT;
  275. }
  276. RTPSession->pData += RTP_H264_PACKAGE_LENGTH;
  277. RTPSession->DataLength -= RTP_H264_PACKAGE_LENGTH;
  278. }
  279. //发送完成后复位数据指针和长度变量
  280. RTPSession->pData = NULL;
  281. RTPSession->DataLength = 0;
  282. }
  283. QUIT:
  284. if(pRTPSendBuf != NULL)
  285. {
  286. free((void *)pRTPSendBuf);
  287. }
  288. return ret;
  289. }
  290. //+------------------------------------------------------------------------------------------+
  291. //| 函数名称:L_RTP_Send
  292. //| 功能描述:通过RTP发送数据。
  293. //| 参数说明:L_STRUCT_RTPSESSION结构体指针
  294. //| 返回值说明:成功返回0,失败返回-1。
  295. //| 备注:对于测试使用的海思平台编码的H264流,发送需要采取多包模式,每包一发,这是本程序的去
  296. //| 起始码部分的编写方式决定的。
  297. //+------------------------------------------------------------------------------------------+
  298. int L_RTP_Send(L_STRUCT_RTPSESSION* RTPSession)
  299. {
  300. if(RTPSession->DataType == RTP_NONE || RTPSession->pData == NULL || RTPSession->DataLength == 0)
  301. {
  302. printf("Structure is not effectively populated!\n");
  303. goto QUIT;
  304. }
  305. switch(RTPSession->DataType)
  306. {
  307. case RTP_H264:
  308. //****************************************
  309. //首先去掉数据流的起始码(00 00 00 01)
  310. //H264起始码占有4个字节,所以对于有起始码的数据
  311. //只需要使其指针指向的位置后移4位。
  312. //对应的数据长度减少4即可。
  313. //
  314. //然而需要注意的是,有可能不是所有的编码器都是
  315. //4个字节的起始码。
  316. //****************************************
  317. RTPSession->pData = RTPSession->pData + 4;
  318. RTPSession->DataLength = RTPSession->DataLength - 4;
  319. case RTP_H264NALU:
  320. L_RTP_Send_H264NALU(RTPSession);
  321. break;
  322. case RTP_NONE:
  323. default:
  324. printf("There is no corresponding operation function for this type!\n");
  325. goto QUIT;
  326. }
  327. return 0;
  328. QUIT:
  329. return -1;
  330. }