欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

TCP连接模式下解析RTP包过程记录

程序员文章站 2022-07-01 10:15:51
...

TCP连接模式下解析RTP包过程记录

注:资料都是网上查的,代码是自己实现的,分享给大家,算是一个记录吧

环境: Ubuntu 16.04 64 bit

#if 0
TCP连接模式下解析RTP包过程记录


前提:例程以接收H264 RTP包为基础


一、h264基础概念

1.NAL、Slice与frame意思及相互关系

1 frame(帧)的数据可以分为多个slice(片). 
每个slice中的数据,在帧内预测只用到自己slice的数据, 与其他slice 数据没有依赖关系。 
NAL 是用来将编码的数据进行打包的。 比如,每一个slice 数据可以放在NAL 包中。 
I frame 是自己独立编码,不依赖于其他frame 数据。 
P frame 依赖 I frame 数据。 
B frame 依赖 I frame, P frame 或其他 B frame 数据。


一个frame是可以分割成多个Slice来编码的,而一个Slice编码之后被打包进一个NAL单元,不过NAL单元除了容纳Slice编码的码流外,还可以容纳其他数据,比如序列参数集SPS。

NAL指网络提取层,里面放一些与网络相关的信息 
Slice是片的意思,264中把图像分成一帧(frame)或两场(field),而帧又可以分成一个或几个片(Slilce);片由宏块(MB)组成。宏块是编码处理的基本单元。

2、NAL nal_unit_type中的1(非IDR图像的编码条带)、2(编码条带数据分割块A)、3(编码条带数据分割块B)、4(编码条带数据分割块C)、5(IDR图像的编码条带)种类型 
与 Slice种的三种编码模式:I_slice、P_slice、B_slice 
NAL nal_unit_type 里的五种类型,代表接下来数据是表示啥信息的和具体如何分块。 
I_slice、P_slice、B_slice 表示I类型的片、P类型的片,B类型的片.其中I_slice为帧内预测模式编码;P_slice为单向预测编码或帧内模式;B_slice 中为双向预测或帧内模式。

3、还有frame的3种类型:I frame、P frame、 B frame之间有什么映射关系么? 
I frame、P frame、 B frame关系同 I_slice、P_slice、B_slice,slice和frame区别在问题1中已经讲明白。

4、最后,NAL nal_unit_type中的6(SEI)、7(SPS)、8(PPS)属于什么帧呢? 
NAL nal_unit_type 为序列参数集(SPS)、图像参数集(PPS)、增强信息(SEI)不属于啥帧的概念。表示后面的数据信息为序列参数集(SPS)、图像参数集(PPS)、增强信息(SEI)。

二. h264 rtp 封包详解
1.网络抽象层单元类型 (NALU)
 +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |F|NRI|  Type   |
  +---------------+

F: 1 个比特. 
forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.

NRI: 2 个比特. 
nal_ref_idc. 取 00 ~ 11, 似乎指示这个 NALU 的重要性, 如 00 的 NALU 解码器可以丢弃它而不影响图像的回放. 不过一般情况下不太关心

这个属性.

Type: 5 个比特. 
nal_unit_type. 这个 NALU 单元的类型. 简述如下:

0 没有定义 
1-23 NAL单元 单个 NAL 单元包. 
24 STAP-A 单一时间的组合包 
25 STAP-B 单一时间的组合包 
26 MTAP16 多个时间的组合包 
27 MTAP24 多个时间的组合包 
28 FU-A 分片的单元 
29 FU-B 分片的单元 
30-31 没有定义



1.打包模式

下面是 RFC 3550 中规定的 RTP 头的结构.

0 1 2 3 
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
|V=2|P|X| CC |M| PT | sequence number | 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
| timestamp | 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
| synchronization source (SSRC) identifier | 
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ 
| contributing source (CSRC) identifiers | 
| …. | 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
版本(V):2比特此域定义了RTP的版本.此协议定义的版本是2.

填充位(P):1比特若填充位比特被设置,此包包含一到多个附加在末端的填充比特,不是负载的一部分.填充位的最后一个字节包含可以忽略多少个填充比特.
填充位可能用于某些具有固定长度的加密算法,或者在底层数据单元中传输多个RTP包.

扩展(X):1比特 若设置扩展比特,固定头(仅)后面跟随一个头扩展.

CSRC计数(CC):4比特 CSRC计数包含了跟在固定头后面CSRC识别符的数目.

标志(M):1比特 标志的解释由具体协议规定.它用来允许在比特流中标记重要的事件,如帧范围.规定该标志在静音后的第一个语音包时置位.

负载类型(PT):7比特 此域定义了负载的格式,由具体应用决定其解释.协议可以规定负载类型码和负载格式之间一个默认的匹配.其他的负载类型码可以通过非RTP方法动态定义.
RTP发射机在任意给定时间发出一个单独的RTP负载类型;此域不用来复用不同的媒体流.

***(sequence number):16比特 每发送一个RTP数据包,***加一,接收机可以据此检测包损和重建包序列.***的初始值是随机的(不可预测),以使即便在源本身不加密时
(有时包要通过翻译器,它会这样做),对加密算法泛知的普通文本攻击也会更加困难.

时间标志(timestamp):32比特 时间标志反映了RTP数据包中第一个比特的抽样瞬间.抽样瞬间必须由随时间单调和线形增长的时钟得到,以进行同步和抖动计算.时钟的分辨率必须满足要求的同步准确度,
足以进行包到达抖动测量.时钟频率与作为负载传输的数据格式独立,在协议中或定义此格式的负载类型说明中静态定义,也可以在通过非RTP方法定义的负载格式中动态说明.若RTP包周期性生成,可以使用
由抽样时钟确定的额定抽样瞬间,而不是读系统时钟.例如,对于固定速率语音,时间标志钟可以每个抽样周期加1.若语音设备从输入设备读取覆盖160个抽样周期的数据块,对于每个这样的数据块,时间标志
增加160,无论此块被发送还是被静音压缩. 时间标志的起始值是随机的,如同***.多个连续的RTP包可能由同样的时间标志,若他们在逻辑上同时产生.如属于同一个图象帧.若数据没有按照抽样的 顺序发送,
连续的RTP包可以包含不单调的时间标志,如MPEG交织图象帧.

同步源(SSRC):32比特 SSRC域用以识别同步源.标识符被随机生成,以使在同一个RTP会话期中没有任何两个同步源有相同的SSRC识别符.尽管多个源选择同一个SSRC识别符的概率很低,所有RTP实现工具都必
须准备检测和解决冲突.若一个源改变本身的源传输地址,必须选择新的SSRC识别符,以避免被当作一个环路源.

有贡献源(CSRC)列表:015项,每项32比特 CSRC列表识别在此包中负载的有贡献源.识别符的数目在CC域中给定.若有贡献源多于15个,仅识别15个.CSRC识别符由混合器插入,用有贡献源的SSRC识别符.例如
语音包,混合产生新包的所有源的SSRC标识符都被陈列,以期在接收机处正确指示交谈者.

注意:前12个字节出现在每个RTP包中,仅仅在被混合器插入时,才出现CSRC识别符列表.
负载类型 Payload type (PT): 7 bits 
*** Sequence number (SN): 16 bits 
时间戳 Timestamp: 32 bits

H.264 Payload 格式定义了三种不同的基本的负载(Payload)结构. 接收端可能通过 RTP Payload 
的第一个字节来识别它们. 这一个字节类似 NALU 头的格式, 而这个头结构的 NAL 单元类型字段 
则指出了代表的是哪一种结构,

这个字节的结构如下, 可以看出它和 H.264 的 NALU 头结构是一样的. 
+———————————————+ 
|0|1|2|3|4|5|6|7| 
+-+-+-+-+-+-+-+-+ 
|F|NRI| Type | 
+———————————————+ 

字段 Type: 这个 RTP payload 中 NAL 单元的类型. 这个字段和 H.264 中类型字段的区别是, 当 type 
的值为 24 ~ 31 表示这是一个特别格式的 NAL 单元, 而 H.264 中, 只取 1~23 是有效的值.

24 STAP-A 单一时间的组合包 
25 STAP-B 单一时间的组合包 
26 MTAP16 多个时间的组合包 
27 MTAP24 多个时间的组合包 
28 FU-A 分片的单元 
29 FU-B 分片的单元 
30-31 没有定义

可能的结构类型分别有:

1.单一 NAL 单元模式 
即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的 
NALU 头类型字段是一样的.

2.组合封包模式 
即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24. 
那么这里的类型值分别是 24, 25, 26 以及 27.

3.分片封包模式 
用于把一个 NALU 单元封装成多个 RTP 包. 存在两种类型 FU-A 和 FU-B. 类型值分别是 2829.

2.1 单一 NAL 单元模式

对于 NALU 的长度小于 MTU 大小的包, 一般采用单一 NAL 单元模式. 
对于一个原始的 H.264 NALU 单元常由 [Start Code] [NALU Header] [NALU Payload] 三部分组成, 其中 Start Code 用于标示这是一个

NALU 单元的开始, 必须是 “00 00 00 01” 或 “00 00 01”, NALU 头仅一个字节, 其后都是 NALU 单元内容. 只有当NALU单元和一帧的开始重合时是“00 00 00 01”
打包时去除 “00 00 01” 或 “00 00 00 01” 的开始码, 把其他数据封包的 RTP 包即可.

   0               1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |F|NRI|  type   |                                               |
  +-+-+-+-+-+-+-+-+                                               |
  |                                                               |
  |               Bytes 2..n of a Single NAL unit                 |
  |                                                               |
  |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                               :...OPTIONAL RTP padding        |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


如有一个 H.264 的 NALU 是这样的:

[00 00 00 01 67 42 A0 1E 23 56 0E 2F … ]

这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.

封装成 RTP 包将如下:

[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ]

即只要去掉 4 个字节的开始码就可以了. 

2.2 组合封包模式

其次, 当 NALU 的长度特别小时, 可以把几个 NALU 单元封在一个 RTP 包中.

   0               1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                          RTP Header                           |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |STAP-A NAL HDR |         NALU 1 Size           | NALU 1 HDR    |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                         NALU 1 Data                           |
  :                                                               :
  +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |               | NALU 2 Size                   | NALU 2 HDR    |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                         NALU 2 Data                           |
  :                                                               :
  |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                               :...OPTIONAL RTP padding        |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.3 Fragmentation Units (FUs).

而当 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包. 也称为 Fragmentation Units (FUs).
   0               1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  | FU indicator  |   FU header   |                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
  |                                                               |
  |                         FU payload                            |
  |                                                               |
  |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                               :...OPTIONAL RTP padding        |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  Figure 14.  RTP payload format for FU-A


The FU indicator octet has the following format:

  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |F|NRI|  Type   |
  +---------------+

The FU header has the following format:

  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |S|E|R|  Type   |
  +---------------+

3.SDP 参数
下面描述了如何在 SDP 中表示一个 H.264 流:

. “m=” 行中的媒体名必须是 “video” 
. “a=rtpmap” 行中的编码名称必须是 “H264”. 
. “a=rtpmap” 行中的时钟频率必须是 90000. 
. 其他参数都包括在 “a=fmtp” 行中.

如:

m=video 49170 RTP/AVP 98 
a=rtpmap:98 H264/90000 
a=fmtp:98 profile-level-id=42A01E; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==

下面介绍一些常用的参数.

3.1 packetization-mode: 
表示支持的封包模式. 
当 packetization-mode 的值为 0 时或不存在时, 必须使用单一 NALU 单元模式. 
当 packetization-mode 的值为 1 时必须使用非交错(non-interleaved)封包模式. 
当 packetization-mode 的值为 2 时必须使用交错(interleaved)封包模式. 
这个参数不可以取其他的值.

3.2 sprop-parameter-sets: 
这个参数可以用于传输 H.264 的序列参数集和图像参数 NAL 单元. 这个参数的值采用 Base64 进行编码. 不同的参数集间用”,”号隔开.

3.3 profile-level-id: 
这个参数用于指示 H.264 流的 profile 类型和级别. 由 Base16(十六进制) 表示的 3 个字节. 第一个字节表示 H.264 的 Profile 类型, 第

三个字节表示 H.264 的 Profile 级别:

3.4 max-mbps: 
这个参数的值是一个整型, 指出了每一秒最大的宏块处理速度.



二、组包--与封包相反

根据抓包实测,在TCP模式下收到的TCP包前四个字节如下:
   0               1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |       Magic    |      Channel      |         Length           |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|

  Magic:魔数 :0x24
  Channel:偶数:RTP 奇数其他,一般是RTCP
  Length:本次包的长度
  我们将这些关键字节当作过滤标准,此次历程只是单纯的接收RTP 视频包

  由于TCP存在粘包的问题,所以可根据长度进行拆解

  关于粘包问题可参考:https://www.cnblogs.com/kex1n/p/6502002.html

  大致步骤为:
                                         收到包
                                           |
                                          \|/
           不符合的直接丢掉-----------符合过滤条件
                                          \|/
        去掉12字节的RTPHeader,获得NaluHeader,根据NaluHeader中的Type进行判断当前Nalu类型
                                          \|/                           
                                  --------------------
                                  |                  |
                                  |                  | 
                                 \|/                \|/
                            1~23属于单包       28属于分片的
                                  |                  |
                                  |                  |
                                 \|/                \|/
                            写入H264_Header        判断是不是一帧的开始,可根据FU Header 的S 判断,等于1代表开始,如果是开始,则写入H264_Header以及将FU indicator 和 FU header这两个字节转换成一个字节,其实就是将FU Header去掉,但要保留FU Header的Type替换FU indicator中的Type,原因是FU Header本身就是因为分片的时候加上的,具体方法可以使用:((FU indicator)&0xe0) | ((FU Header)&0x1f)
                                  |                  |
                                  |                  |
                                  |                  |
                                 \|/                \|/
            直接将nalu和数据写入文件              不是开始则代表是数据,直接跳过FU indicator 和 FU header这两个字节将数据写入即可



注意:
     1.分片重组的时候,一定要记得去掉FU header,并将FU indicator 的后5位换成相应的H264的帧类型
     2.在封包的时候会将Nalu的区分标志“00 00 00 01” 或者 “00 00 01”去掉,在解包的时候要手动加上


本次实验代码:
#endif

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<string.h>
#include<errno.h>
#include<netdb.h>
#include<assert.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>


#define HP_MAX_LEN 40960
int fd;

/*RTSP 请求数据包*/
struct rtsp_pack{

    unsigned char buff[HP_MAX_LEN];
    int pos;
};

/*丢包函数*/
void drop_packg(int sfd, int length)
{
    int ret = 0;
    char buff[40960];
    int lengthh = 0;

    ret = recv(sfd, buff, length, 0);
    if (ret == length)
    {
        return;
    }

    lengthh = length - ret;

    while(lengthh > 0)
    {
        printf("adfasdfasdfadf\n");
        ret = recv(sfd, buff, lengthh, 0);
        lengthh = lengthh - ret;
    }
    return ;
}
typedef struct rtp_header
{
#ifdef ORTP_BIGENDIAN
    uint16_t version:2;
    uint16_t padbit:1;
    uint16_t extbit:1;
    uint16_t cc:4;
    uint16_t markbit:1;
    uint16_t paytype:7;
#else
    uint16_t cc:4;
    uint16_t extbit:1;
    uint16_t padbit:1;
    uint16_t version:2;
    uint16_t paytype:7;
    uint16_t markbit:1;
#endif                                  
    uint16_t seq_number;
    uint32_t timestamp;
    uint32_t ssrc;
    uint32_t csrc[16];
} rtp_header_t;

/*TCP 数据包头*/
typedef struct 
{
    unsigned char magic;
    unsigned char channel;
    unsigned short length;
}ST_Magic;


/*RTP Header*/
typedef struct 
{
    /* byte 0 */
    unsigned char csrc_len:4;  /* CC expect 0 */
    unsigned char extension:1; /* X  expect 1, see RTP_OP below */
    unsigned char padding:1;   /* P  expect 0 */
    unsigned char version:2;   /* V  expect 2 */

    /* byte 1 */
    unsigned char payload:7;   /* PT  RTP_PAYLOAD_RTSP */
    unsigned char marker:1;    /* M   expect 1 */

    /* byte 2,3 */
    unsigned short seq_no;     /*sequence number*/

    /* byte 4-7 */
    unsigned  long timestamp;  /*time stamp*/

    /* byte 8-11 */
    unsigned long ssrc;        /* stream number is used here. */
} RTPHeader;/*12 bytes*/

/*NaluHeader*/
typedef struct{

    unsigned char Type:5;
    unsigned char NRI:2;
    unsigned char F:1;
}NaluHeader;

/*FU_indicator + FU_Header*/
typedef struct{
    unsigned char Type:5;
    unsigned char NRI:2;
    unsigned char F:1;
    unsigned char fuType:5;
    unsigned char R:1;
    unsigned char E:1;
    unsigned char S:1;
}FuNaluHeader;



int rtsp_addline( struct rtsp_pack *hp,char *newline ){

    int current_pos = hp->pos;
    int newline_len = strlen(newline);

    if( current_pos + newline_len + 2 >= HP_MAX_LEN )
        return -1;
    if( (current_pos <= 4) && (current_pos != 0) )
        return -1;

    if( hp->pos == 0 ){
        memcpy( hp->buff,newline,newline_len );
    }else{
        current_pos -= 2;
        memcpy( hp->buff + current_pos,newline,newline_len );
    }

    current_pos += newline_len;
    memcpy( hp->buff + current_pos,"\r\n\r\n",4 );
    current_pos += 4;

    hp->pos = current_pos;
    return 0;
}


int next[32];
void match_pre( char *pattern,int *next,int pattern_len ){

    int matched = 0;
    int begin = 1;

    next[0] = 0;
    while( begin + matched < pattern_len ){
        if( pattern[begin + matched ] == pattern[matched] ){
            matched++;
            next[begin + matched - 1] = matched;
        }else{
            if( matched == 0 ){
                begin++;
            }else{
                begin += matched - next[ matched - 1];
                matched = next[ matched - 1];
            }
        }
    }
    return;
}
int match( char *str,char *pattern,int *len ){

    int pattern_len = strlen( pattern );
    int str_len = strlen(str);

    int matched = 0;
    int begin = 0;
    char *p = NULL;

    if( pattern_len > 32 )
        return -1;

    match_pre( pattern,next,pattern_len );

    while( begin + matched < str_len ){
        if( pattern[matched] == str[begin + matched] ){
            matched++;
            if( matched == pattern_len ){
                p = str + begin + matched;
                while( *p != '\r' || *(p + 1) != '\n')
                    p++;
                *len = p - (str + begin);
                return begin;
            }
        }else{
            if( matched == 0 ){
                begin++;
            }else{
                begin += matched - next[ matched - 1];
                matched = next[ matched - 1];
            }
        }
    }
    return -1;

}

char *line_match( char *str,char *pattern ){
    int len;
    int index = match( str,pattern,&len );
    if( index == -1 )
        return NULL;

    char *buff = (char *)malloc( sizeof(char) * len + 1 );
    memcpy( buff,&str[index],len );
    buff[len] = '\0';

    return buff;
}

int main(){
    struct rtsp_pack rtsp;
    struct sockaddr_in sa;
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    int ret = -1;
    unsigned char buff[40960];
    rtsp.pos = 0;

    int size = 0;

    fd = open("./xxx.mpeg", O_WRONLY|O_CREAT);
    if (-1 == fd)
    {
        printf("open file faild\n");
        return -1;
    }

    memset(&rtsp, 0, sizeof(struct rtsp_pack));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(554);
    sa.sin_addr.s_addr = inet_addr("184.72.239.149");

    ret = connect( sfd,(struct sockaddr *)&sa,sizeof(sa) );

    if( ret < 0 ){
        printf("connect error,%d\n",errno);
        return -1;
    }

    printf("connect success!!!\n");

    /**/
    rtsp_addline(&rtsp, (char *)"OPTIONS rtsp://184.72.239.149:554/vod/mp4://BigBuckBunny_175k.mov RTSP/1.0");
    rtsp_addline(&rtsp, (char *)"CSeq: 1");
    rtsp_addline(&rtsp, (char *)"User-Agent: LibVLC/3.0.2 (LIVE555 Streaming Media v2016.11.28)");
    rtsp_addline(&rtsp, (char *)"\r\n");

    memset(buff, 0, sizeof(buff));
    ret = send(sfd, rtsp.buff, rtsp.pos, 0);
    ret = recv(sfd, buff, 8192, 0);
    printf("ret = %d\n", ret);
    printf("%s\n",buff);


    memset(&rtsp, 0, sizeof(struct rtsp_pack));
    memset(buff, 0, sizeof(buff));
    rtsp_addline(&rtsp, (char *)"DESCRIBE rtsp://184.72.239.149:554/vod/mp4://BigBuckBunny_175k.mov RTSP/1.0");
    rtsp_addline(&rtsp, (char *)"CSeq: 2");
    rtsp_addline(&rtsp, (char *)"User-Agent: LibVLC/3.0.2 (LIVE555 Streaming Media v2016.11.28)");
    rtsp_addline(&rtsp, (char *)"Accept: application/sdp");
    rtsp_addline(&rtsp, (char *)"\r\n");

    ret = send(sfd, rtsp.buff, rtsp.pos, 0);
    ret = recv(sfd, buff, 8192, 0);
    printf("ret = %d\n", ret);

    printf("%s\n", buff);

    char *p = line_match(buff, "Session:");
    char Session[128];
    memset(Session, 0, sizeof(Session));

    for( int i = 0; ; i++ ){
        if( p[i] == ';'){
            Session[i] = '\0';
            break;
        }
        Session[i] = p[i];
    }
    printf("session = %s\n", Session);
    memset(&rtsp, 0, sizeof(struct rtsp_pack));
    memset(buff, 0, sizeof(buff));

    rtsp_addline(&rtsp, (char *)"SETUP rtsp://184.72.239.149:554/vod/mp4://BigBuckBunny_175k.mov/trackID=2 RTSP/1.0");
    rtsp_addline(&rtsp, (char *)"CSeq: 3");
    rtsp_addline(&rtsp, (char *)"User-Agent: LibVLC/3.0.2 (LIVE555 Streaming Media v2016.11.28)");
    rtsp_addline(&rtsp, (char *)"Transport: RTP/AVP/TCP;unicast;");

    ret = send(sfd, rtsp.buff, rtsp.pos, 0);
    ret = recv(sfd, buff, 8192, 0);
    printf("ret = %d\n", ret);

    printf("%s\n", buff);
#if 0   
    memset(&rtsp, 0, sizeof(struct rtsp_pack));
    memset(buff, 0, sizeof(buff));

    rtsp_addline(&rtsp, (char *)"SETUP rtsp://184.72.239.149:554/vod/mp4://BigBuckBunny_175k.mov/trackID=2 RTSP/1.0");
    rtsp_addline(&rtsp, (char *)"CSeq: 4");
    rtsp_addline(&rtsp, (char *)"User-Agent: LibVLC/3.0.2 (LIVE555 Streaming Media v2016.11.28)");
    rtsp_addline(&rtsp, (char *)"Transport: RTP/AVP/TCP;unicast;");
    rtsp_addline(&rtsp, Session);
    rtsp_addline(&rtsp, "\n\r");

    ret = send(sfd, rtsp.buff, rtsp.pos, 0);
    ret = recv(sfd, buff, 8192, 0);
    printf("ret = %d\n", ret);

    printf("%s\n", buff);
#endif

    memset(&rtsp, 0, sizeof(struct rtsp_pack));
    memset(buff, 0, sizeof(buff));

    rtsp_addline(&rtsp, (char *)"PLAY rtsp://184.72.239.149:554/vod/mp4://BigBuckBunny_175k.mov/ RTSP/1.0");
    rtsp_addline(&rtsp, (char *)"CSeq: 5");
    rtsp_addline(&rtsp, (char *)"User-Agent: LibVLC/3.0.2 (LIVE555 Streaming Media v2016.11.28)");
    rtsp_addline(&rtsp, Session);
    rtsp_addline(&rtsp, (char *)"Range: npt=0.000-");
    rtsp_addline(&rtsp, "\n\r");

    ret = send(sfd, rtsp.buff, rtsp.pos, 0);
    ret = recv(sfd, buff, 8192, 0);
    printf("ret = %d\n", ret);

    printf("%s\n", buff);

RTPHeader *rtpHeader = NULL;
ST_Magic *stMagic = NULL;
char header[5];
NaluHeader *naluHeader = NULL;
memset(header, 0, sizeof(header));
int lengthh = 0;
char H264_Header[]={0x00, 0x00, 0x00, 0x01};
/*sps及pps 这里直接使用手动解析出来的*/
char sps[]={0x67 ,0x42 ,0xc0 ,0x1e ,0xd9 ,0x03 ,0xc5 ,0x68 ,0x40 ,0x00 ,0x00 ,0x03 ,0x00 ,0x40 ,0x00 ,0x00 ,0x0c ,0x03 ,0xc5 ,0x8b ,0x92};
char pps[]={0x68 ,0xcb ,0x8c ,0xb2};
int Type;
int NRI;
int F;
char I_SLIC[]={0X65};
FuNaluHeader * fuNaluHeader = NULL;

write(fd, H264_Header, sizeof(H264_Header));
write(fd, sps, sizeof(sps));
write(fd, H264_Header, sizeof(H264_Header));
write(fd, pps, sizeof(pps));

while(1)
{

    /*接收4字节,判断这个包的长度,然后再接收相应长度的数据,解决粘包问题*/
    ret = recv(sfd, header, 4, 0);
    stMagic = (ST_Magic *)header;

    /*判断魔数,且是RTP包才接收*/
    if (stMagic->magic == 0x24 && !(stMagic->channel%2))
    {
        printf("magic = %d\n", stMagic->magic);
        printf("channel = %d\n", stMagic->channel);
        printf("length = %d\n", ntohs(stMagic->length));
        lengthh = ntohs(stMagic->length);

        printf("lengthh = %d\n", lengthh);
        /*接收第一包数据*/
        ret = recv(sfd, buff, lengthh, 0);
        rtpHeader = (RTPHeader *)buff;
        printf("RTP packg\n");
        printf("version = %d\n", rtpHeader->version);
        printf("padding = %d\n", rtpHeader->padding);
        printf("extension = %d\n", rtpHeader->extension);
        printf("csrc_len =  %d\n", rtpHeader->csrc_len);
        printf("marker = %d\n", rtpHeader->marker);
        printf("payload = %d\n", rtpHeader->payload);
        printf("seq_no = %d\n", ntohs(rtpHeader->seq_no));
        lengthh  = lengthh - ret;

        naluHeader = (NaluHeader *)(buff+12);
        printf("type = %d\n", naluHeader->Type);
        printf("NRI = %d\n", naluHeader->NRI);
        printf("F = %d\n", naluHeader->F);
        /*判断nalu类型*/
        if ((naluHeader->Type > 0) && (naluHeader->Type < 25))            //1~23代表单包,如果是单包,直接在前面增加H264_Header(0001)然后将数据写入到文件中
        {
            printf("type =================================== naluHeader->Type = %d\n", naluHeader->Type);
            printf("danbao=====================================================\n");
            write(fd, H264_Header, sizeof(H264_Header));
            write(fd, buff+12, ret - 12);
        }else if (naluHeader->Type == 28)                             //28代表分片,如果是分片则判断是不是一帧的开始,如果是,则增加H264_Header然后将数据写入到文件中,如果不是,则直接将数据写入到文件中
        {
            printf("fenbao===============================================================\n");
            fuNaluHeader = (FuNaluHeader *)(buff+12);
            printf("fenbao leixing =============================================================== fuType = %d\n", fuNaluHeader->fuType);
            if (fuNaluHeader->S == 1)   //first slice
            {
                printf("fuType = %d\n", fuNaluHeader->fuType);
                //start
                write(fd, H264_Header, sizeof(H264_Header));
                write(fd, I_SLIC, 1);
            }
            write(fd, buff+14, ret - 14);
        }else{

            printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!type = %d\n", naluHeader->Type);
        }
        while(lengthh> 0)                                //while 循环则是接收一次没有接收完的包数据
        {
            printf("jinru while===============\n");         
            ret = recv(sfd, buff, lengthh, 0);              
            write(fd, buff, ret);
            lengthh -= ret;
        }
        printf("\n");
        memset(buff, 0, sizeof(buff));
    }else{

        printf("length ===================================================================================############## %d\n", ntohs(stMagic->length));
        /*丢掉不是RTP的包*/
        drop_packg(sfd, ntohs(stMagic->length));    
    }
}
close(fd);
close(sfd);
return 0;
}

相关标签: RTSP RTP