leaffei

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 }
View Code

 最后如果您觉得本篇对您有帮助,可以打赏下,谢谢!!!

posted on 2019-03-23 11:13  leaffei  阅读(2608)  评论(0编辑  收藏  举报

导航