rtp传输音视频(纯c代码)
参考链接: 1. PES,TS,PS,RTP等流的打包格式解析之RTP流 https://blog.csdn.net/appledurian/article/details/73135343
2. RTP协议全解析(H264码流和PS流)https://blog.csdn.net/chen495810242/article/details/39207305
(重要)以下代码并未实测,除ts的发送外,其余都是伪代码(并且未搜集资料查询思路是否正确), 这边只为自己记录,参考请谨慎, 自己记录下而已。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 #include <getopt.h> 6 #include <errno.h> 7 #include <unistd.h> 8 #include <netinet/in.h> 9 #include <arpa/inet.h> 10 11 static union { char c[4]; unsigned long mylong; } endian_test = {{ 'l', '?', '?', 'b' } }; 12 13 #define ENDIANNESS ((char)endian_test.mylong) 14 15 #define PRINTF_DEBUG 16 #define TAB44 " " 17 18 #define MAX_ARGS_FILEFORMAT 10 19 #define MAX_ARGS_FILEPATH 128 20 #define MAX_RTPURL_IP 128 21 #define MAX_ARGS_RTPURL 256 22 #define MTU 1400 23 24 #define DEFAULT_FILE_PATH "./videos/mux/ts_test.ts" 25 #define DEFAULT_FILE_FORMAT "ts" 26 #define DEFAULT_RTP_URL "rtp://127.0.0.1:8888" 27 28 #define DEFAULT_ARGS {0, DEFAULT_FILE_PATH, DEFAULT_FILE_FORMAT, DEFAULT_RTP_URL} 29 30 /* define4ts */ 31 #define MAX_TS_PACKET_COUNT 7 32 #define TS_PACKET_LEN 188 33 34 /* define4ps */ 35 #define SCODE_PS_END 0x000001B9 36 #define SCODE_PS_HEADER 0x000001BA 37 #define SCODE_PS_SYSTEM_HEADER 0x000001BB 38 #define SCODE_PS_SYSTEM_MAP_HEADER 0x000001BC 39 40 /* define4mpeg2 */ 41 typedef enum e_mpeg2_sc_type 42 { 43 E_SC_MPEG2_SEQ_HEADER = 0x000001B3, 44 E_SC_MPEG2_SEQ_PIC_EXTEN_HEADER = 0x000001B5, 45 E_SC_MPEG2_SEQ_END = 0x000001B7, 46 E_SC_MPEG2_GROUP_HEADER = 0x000001B8, 47 E_SC_MPEG2_PICTURE_HEADER = 0x00000100 48 } E_MPEG2_SC_TYPE; 49 50 typedef enum e_rtp_playload_type 51 { 52 E_RTP_PLAYLOAD_TS = 33, 53 E_RTP_PLAYLOAD_PS = 96, 54 E_RTP_PLAYLOAD_MPEG4 = 97, 55 E_RTP_PLAYLOAD_H264 = 98, 56 } E_RTP_PLAYLOAD_TYPE; 57 58 typedef struct t_args 59 { 60 unsigned short isLoop; 61 62 unsigned char filePath[MAX_ARGS_FILEPATH+1]; 63 unsigned char fileFormat[MAX_ARGS_FILEFORMAT+1]; 64 unsigned char rtpUrl[MAX_ARGS_RTPURL+1]; 65 } T_ARGS; 66 67 /****************************************************** 68 个人理解 69 1. 位域内单字节的内存排布是定义的先后, 先定义的在内存的低地址; 70 2. 位域内单字节, 字节由高到低, 先定义的为高字节; 71 3. 因此对于小端(低地址放低字节). 72 ******************************************************/ 73 typedef struct t_rtp_header 74 { 75 #if 1 /* 小端, BIG_ENDIAN系统宏, 暂不知道怎么用 */ 76 /* bytes 0 */ 77 unsigned char csrc_len:4; 78 unsigned char extension:1; 79 unsigned char padding:1; 80 unsigned char version:2; 81 /* bytes 1*/ 82 unsigned char playload:7; 83 unsigned char marker:1; 84 #else 85 /* bytes 0 */ 86 unsigned char version:2; 87 unsigned char padding:1; 88 unsigned char extension:1; 89 unsigned char csrc_len:4; 90 /* bytes 1*/ 91 unsigned char marker:1; 92 unsigned char playload:7; 93 #endif 94 95 /* byte 2, 3 */ 96 unsigned short seq_no; 97 /* bytess 4-7 */ 98 unsigned int timestamp; 99 /* bytes 8-11 */ 100 unsigned int ssrc; 101 } T_RTP_HEADER; 102 103 /* gloabl data */ 104 FILE *fp = NULL; 105 106 struct sockaddr_in servAddr; 107 108 T_ARGS defaultArgs = DEFAULT_ARGS; 109 110 static void Usage(void) 111 { 112 fprintf(stderr, "usage: rtpserver [options]\n\n" 113 "Options:\n" 114 "-l | --stream_loop Read and send strame for loop\n" 115 "-i | --filepath File need to send\n" 116 "-f | --fileformat Container of file(support ts, ps, h264, mpeg2, flv)\n" 117 "-s | --rtpurl Rtp url include ip and port\n" 118 "-h | --help Print this message\n"); 119 } 120 121 /****************************************************************************** 122 1. const char shortOpt[] = "li:f:s:h"; 123 单个字符表示选项; 124 单个字符后接一个冒号, 表示后面必须跟一个参数. 参数紧跟选项后或者加一个空格; 125 单个字符后接两个冒号, 表示可有也可没有, 参数紧跟选项后, 不能加空格. 126 2. 参数的值赋给了optarg; 127 3. c = getopt_long(argc, argv, shortOpt, longOpt, NULL); 128 返回值为参数字符, 若全部解析完成则返回-1. 129 ******************************************************************************/ 130 static void ParseArgs(int argc, char *argv[], T_ARGS *args) 131 { 132 int c = 0; 133 134 const char shortOpt[] = "li:f:s:h"; 135 const struct option longOpt[] = { 136 {"stream_loop", no_argument, NULL, 'l'}, 137 {"filepath", required_argument, NULL, 'i'}, 138 {"fileformat", required_argument, NULL, 'f'}, 139 {"rtpurl", required_argument, NULL, 's'}, 140 {"help", no_argument, NULL, 'h'}, 141 {0, 0, 0, 0} 142 }; 143 144 for (;;) 145 { 146 c = getopt_long(argc, argv, shortOpt, longOpt, NULL); 147 148 if (-1 == c) 149 { 150 break; 151 } 152 153 switch (c) 154 { 155 case 'l': 156 args->isLoop = 1; 157 158 break; 159 160 case 'i': 161 memcpy(args->filePath, optarg, strlen(optarg)); 162 163 args->filePath[strlen(optarg)] = '\0'; 164 165 break; 166 167 case 'f': 168 if ((0 != strcmp(optarg, "ts")) 169 && (0 != strcmp(optarg, "ps") 170 && (0 != strcmp(optarg, "h264") 171 && (0 != strcmp(optarg, "mpeg2") 172 && (0 != strcmp(optarg, "flv")) 173 { 174 Usage(); 175 176 exit(0); 177 } 178 179 memcpy(args->fileFormat, optarg, strlen(optarg)); 180 181 182 args->fileFormat[strlen(optarg)] = '\0'; 183 184 break; 185 186 case 's': 187 memcpy(args->rtpUrl, optarg, strlen(optarg)); 188 189 args->rtpUrl[strlen(optarg)] = '\0'; 190 191 break; 192 193 default: 194 Usage(); 195 196 exit(0); 197 } 198 } 199 } 200 201 static void Parse_RtpUrl(unsigned char* const rtpUrl, unsigned char *urlIp, unsigned short *urlPort) 202 { 203 unsigned short port = 0; 204 205 unsigned char *url = NULL; 206 unsigned char *portStart = NULL; 207 208 url = rtpUrl; 209 210 url += strlen("rtp://"); 211 212 portStart = strstr(url, ":"); 213 214 port = atoi(portStart+1); 215 216 *urlPort = port; 217 218 memcpy(urlIp, url, portStart-url); 219 } 220 221 static unsigned long GetTickCount() 222 { 223 struct timespec ts; 224 225 clock_gettime(CLOCK_MONOTONIC, &ts); 226 227 return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000); 228 } 229 230 static void Rtp_Header_Costruct(T_RTP_HEADER *rtpHeader, E_RTP_PLAYLOAD_TYPE playloadType) 231 { 232 static unsigned short seqNo = 111; 233 234 rtpHeader->version = 2; 235 rtpHeader->padding = 0; 236 rtpHeader->extension = 0; 237 rtpHeader->csrc_len = 0; 238 rtpHeader->marker = 0; 239 rtpHeader->playload = playloadType; 240 rtpHeader->seq_no = seqNo++; 241 rtpHeader->timestamp = htonl(GetTickCount()*1000/90000); 242 rtpHeader->ssrc = htonl(111); 243 } 244 245 static void Rtp_DealTs(int socketFd) 246 { 247 int readLen = 0; 248 int bufCount = 0; 249 int packetCount = 0; 250 251 unsigned char rtpBuf[MTU] = {0}; 252 253 memset(rtpBuf, 0x0, MTU); 254 255 Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); 256 257 bufCount = sizeof(T_RTP_HEADER); 258 259 while (1) 260 { 261 if (feof(fp)) 262 { 263 if (defaultArgs.isLoop) 264 { 265 rewind(fp); 266 267 packetCount = 0; 268 269 memset(rtpBuf, 0x0, MTU); 270 271 Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); 272 273 bufCount = sizeof(T_RTP_HEADER); 274 } 275 else 276 { 277 break; 278 } 279 } 280 281 readLen = fread(rtpBuf+bufCount, 1, TS_PACKET_LEN, fp); 282 283 packetCount++; 284 bufCount += readLen; 285 286 if (packetCount>=MAX_TS_PACKET_COUNT) 287 { 288 sendto(socketFd, rtpBuf, bufCount, 0, (const struct sockaddr*)&servAddr, sizeof(servAddr)); 289 290 packetCount = 0; 291 292 memset(rtpBuf, 0x0, MTU); 293 294 Rtp_Header_Costruct((T_RTP_HEADER*)rtpBuf, E_RTP_PLAYLOAD_TS); 295 296 bufCount = sizeof(T_RTP_HEADER); 297 298 usleep(100); // 应根据帧率发送或者可实现rtcp来动态控制发送速度 299 } 300 } 301 } 302 303 static void Rtp_DealPs(int socketFd) 304 { 305 int readLen = 0; 306 307 unsigned int startCode = 0; 308 309 while (1) 310 { 311 if (4 != fread(&startCode, 4, 1, fp)) 312 { 313 break; 314 } 315 316 switch (startCode) 317 { 318 case SCODE_PS_END: 319 break; 320 321 case SCODE_PS_HEADER: 322 /* get and send, like psparse.c */ 323 324 break; 325 326 case SCODE_PS_SYSTEM_HEADER: 327 /* get and send, like psparse.c */ 328 329 break; 330 331 case SCODE_PS_SYSTEM_MAP_HEADER: 332 /* get and send, like psparse.c */ 333 334 break; 335 336 default: 337 /* 338 1. get and send, like psparse.c; 339 2. here data mybe>MTU, 分包, 每次发MTU, 直到全部完成; 340 3. rtp头上的marker标识了一帧的开始/结束, 分包的时候刚开始写0, 最后一包填1; 341 4. 未证实: 分包时rtp头的timestamp应该是不变的. 342 */ 343 344 break; 345 } 346 } 347 } 348 349 /************************************************************************************************************************** 350 1. 组合封包模式 351 在NALU单元很小的时候, 可以将多个NALU封装到一个RTP包里面进行传输, 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24; 352 那么这里的类型值分别是24, 25, 26以及27; 353 我们主要介绍STAP-A, 其封包格式如下所示: 354 [RTP Header] [Nalu头, type: 24(一个字节78)] [Nalu1 len(2 bytes)] [Nalu1 data] [Nalu2 len(2 bytes)] [Nalu2 data] ... 355 2. 分片封包模式 356 当一个NALU长度超过了MTU, 就需要采用分片的方式进行RTP封包, 将一个NALU分到多个RTP包中进行传输; 357 存在两种分片类型FU-A和FU-B, 类型值分别是28和29. 358 RTP+FU-A分片封包的组合方式如下: 359 [RTP Header][FU indicator][FU header][payload]: 其中RTP Header占12字节, FU indicator和FU header各占1个字节; 360 361 [FU indicator]有以下格式: 362 +---------------+ 363 |0|1|2|3|4|5|6|7| 364 +-+-+-+-+-+-+-+-+ 365 |F|NRI| Type | 366 +---------------+ 367 type=28表示FU-A分包 368 369 [FU header]的格式如下: 370 +---------------+ 371 |0|1|2|3|4|5|6|7| 372 +-+-+-+-+-+-+-+-+ 373 |S|E|R| Type | 374 +---------------+ 375 S: 设置成1表示此FU-A分片包为NAL单元的起始包, 其他情况设置为0; 376 E:设置成1表示此FU-A分片为NAL单元的结束包, 其他情况设置为0; 377 R:保留位,必须为0; 378 Type: 为被分包的Nalu的type. 379 380 简单说就是加了一个字节描述分包的开始和结束. 381 *******************************************************************************************************************************/ 382 static void Rtp_DealH264(int socketFd) 383 { 384 while (!feof(fp)) 385 { 386 /* 387 1. get nalu data; 388 2. sps, pps等较小的, 可采用组合封包; 389 3. 帧数据大于MTU, 需分包. 如FU-A, 将帧拆分, 加上FU-A的格式, 再加上FTP的头发送出去; 390 4. 以上都可参照h264parse.c 391 */ 392 } 393 } 394 395 static void Rtp_DealMpeg2(int socketFd) 396 { 397 while (!feof(fp)) 398 { 399 /* 400 1. get data by startcode(seq, gop, pic...); 401 2. data_len<MTU, send; 402 3. data_len>MTU, 分包, 每次最大MTU; 403 4. 以上都可参照mpeg2parse.c 404 */ 405 } 406 } 407 408 static void Rtp_DealFlv(int socketFd) 409 { 410 while (!feof(fp)) 411 { 412 /* 413 1. get data by tag(script, video, audio...); 414 2. data_len<MTU, send; 415 3. data_len>MTU, 分包, 每次最大MTU; 416 4. 以上都可参照flvparse.c 417 */ 418 } 419 } 420 421 /* 422 1. rtp client, send data to servAddr; 423 2. server can play used rtp://ip:port 424 */ 425 int main(int argc, char *argv[]) 426 { 427 int socketFd = 0; 428 429 unsigned short serverPort = 0; 430 431 unsigned char serverIp[MAX_RTPURL_IP] = {0}; 432 433 ParseArgs(argc, argv, &defaultArgs); 434 435 memset(serverIp, 0x0, MAX_RTPURL_IP); 436 437 Parse_RtpUrl(defaultArgs.rtpUrl, serverIp, &serverPort); 438 439 socketFd = socket(AF_INET, SOCK_DGRAM, 0); 440 if (socketFd < 0) 441 { 442 printf("%s\n", strerror(errno)); 443 444 exit(0); 445 } 446 447 memset(&servAddr, 0, sizeof(servAddr)); 448 449 servAddr.sin_family = AF_INET; 450 servAddr.sin_port = htons(serverPort); 451 servAddr.sin_addr.s_addr = inet_addr(serverIp); 452 453 fp = fopen(defaultArgs.filePath, "r+"); 454 if (!fp) 455 { 456 printf("%s\n", strerror(errno)); 457 458 exit(0); 459 } 460 461 if (0 == strcmp(defaultArgs.fileFormat, "ts")) 462 { 463 464 Rtp_DealTs(socketFd); 465 466 fclose(fp); 467 } 468 else if (0 == strcmp(defaultArgs.fileFormat "ps")) 469 { 470 Rtp_DealPs(socketFd); 471 472 fclose(fp); 473 } 474 else if (0 == strcmp(defaultArgs.fileFormat "h264")) 475 { 476 Rtp_DealH264(socketFd); 477 478 fclose(fp); 479 } 480 else if (0 == strcmp(defaultArgs.fileFormat "mpeg2")) 481 { 482 Rtp_DealMpeg2(socketFd); 483 484 fclose(fp); 485 } 486 else if (0 == strcmp(defaultArgs.fileFormat "flv")) 487 { 488 Rtp_DealFlv(socketFd); 489 490 fclose(fp); 491 } 492 493 return 0; 494 }
最后如果您觉得本篇对您有帮助,可以打赏下,谢谢!!!