zoukankan      html  css  js  c++  java
  • 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]
    
    "
    113                     "Options:
    "
    114                     "-l | --stream_loop     Read and send strame for loop
    "
    115                     "-i | --filepath        File need to send
    "
    116                     "-f | --fileformat      Container of file(support ts, ps, h264, mpeg2, flv)
    "
    117                     "-s | --rtpurl          Rtp url include ip and port
    "
    118                     "-h | --help            Print this message
    ");
    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)] = '';
    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)] = '';
    183                 
    184                 break;
    185 
    186             case 's':            
    187                 memcpy(args->rtpUrl, optarg, strlen(optarg));
    188                 
    189                 args->rtpUrl[strlen(optarg)] = '';
    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
    ", 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
    ", 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

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

  • 相关阅读:
    bzoj2143 飞飞侠
    Codeforces 543.B Destroying Roads
    Codeforces 666.B World Tour
    bzoj2441 [中山市选2011]小W的问题(debug中)
    bzoj2329 [HNOI2011]括号修复
    一些新发现的好东西
    < meta http-equiv = "X-UA-Compatible" content = "IE=edge,chrome=1" />的作用
    js经典代码技巧学习之一:使用三元运算符处理javascript兼容
    《javascript高级程序设计》笔记4.1.4:检测类型
    页面关闭和刷新事件
  • 原文地址:https://www.cnblogs.com/leaffei/p/10583045.html
Copyright © 2011-2022 走看看