RTP头的解析及大小端处理的细节
在看如何解析RTP头之前,先复习下大端和小端的概念,再分析如何解析RTP头
大端和小端
大小端是不同的CPU架构对内存的使用方式不同,将最高有效字节放在内存的低位,为大端序,在最高有效字节放在内存的高位,为小端序。在之前本以为大端和小端只是针对的多字节,其实在一个字节内的比特序也有大小端的概念,这里列几个结论
- 进制书写中,最高位在最左端,最低为在最右端
0x12345678,最高位位0x12。那么在小端系统中解释为0x78563412,大端解释为0x12345678(可以认为系统中的内存顺序为低位在前,高位在后,与书写顺序相反)。 - 所有的网络协议都是以大端序(多字节序和比特序)来定义。
- 在x86下是小端(多字节序和比特序)。
- 以太网中是以小端来发送比特,发送时现转换为小端,接收时网卡会将比特序转换位本机的比特序,所以比特序的转换是在网卡上处理的,应用层无需关心,见下面参考资料1的描述
参考资料
RTP头的解析
RTP头的定义
可以看到协议的定义是以大端顺序定义的
以位域的方式解析RTP Header
按照RTP Header的定义,定义如下位域结构体
struct MBRTPHeader
{
#ifdef RTP_BIG_ENDIAN
unsigned char version:2;
unsigned char padding:1;
unsigned char extension:1;
unsigned char csrccount:4;
uint8_t marker:1;
uint8_t payloadtype:7;
#else // little endian
unsigned char csrccount:4;
unsigned char extension:1;
unsigned char padding:1;
unsigned char version:2;
unsigned char payloadtype:7;
unsigned char marker:1;
#endif // RTP_BIG_ENDIAN
unsigned short sequencenumber;
unsigned int timestamp;
unsigned int ssrc;
};
位域结构体中定义了一个RTP_BIG_ENDIAN宏,用来标识系统是否是大端,如果系统是大端那么位域(比特序)的定义就是按照协议中的字段顺序来定义。如果是小端,应该是按照协议中相反的顺序来定义。x86 PC一般都是小端,下面按照小端解析RTP。
void ProcessRTPHeader(unsigned char* pData)
{
RTPHeader *pRtpHeader = (RTPHeader*)pData;
//取version
int v = pRtpHeader->version;
//取extension
int x = pRtpHeader->extension;
//取payload type
int pt = pRtpHeader->payloadtype;
//取marker
int mark = pRtpHeader->marker;
//取seq
int seq = ntohs(pRtpHeader->sequencenumber);
//取ssrc
int ssrc = ntohl(pRtpHeader->ssrc);
}
在上面的代码示例中,取sequencenumber和ssrc时需要注意,因为其为多字节,所以需要转换为本地字节序(小端多字节序)后,再取值。
通过移位操作解析Rtp Header
定义如下结构体
struct RTPHeader
{
int version;
int type;
int pad, ext, cc, mark;
int seq, time;
int ssrc;
};
void ProcessRTPHeader(unsigned char* pData)
{
RTPHeader rtp;
/*取version,version在最高位(本机是小端),移到最低位后取值。以下的pad,ext,cc,mark,type字段同理都做了相应的位移操作*/
rtp.version = (packet[0] >> 6) & 3;
rtp.pad = (packet[0] >> 5) & 1;
rtp.ext = (packet[0] >> 4) & 1;
rtp.cc = packet[0] & 7;
rtp.mark = (packet[1] >> 7) & 1;
rtp.type = (packet[1]) & 127;
//rbe16函数作用同ntohs,只是转入的参数不同
rtp.seq = rbe16(packet + 2);
//rbe32函数作用同ntohl,只是传入的参数不同
rtp.time = rbe32(packet + 4);
rtp.ssrc = rbe32(packet + 8);
}
int rbe16(const unsigned char *p)
{
int v = p[0] << 8 | p[1];
return v;
}
int rbe32(const unsigned char *p)
{
int v = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
return v;
}
两种方式的更有优势,通过位域的方式代码更简单。通过位运算的方式,兼容性格强,不需判断系统的字节序。
总结
这里介绍了解析RTP Header时对多字节序及比特序的大小端处理所应注意的细节,可以扩展开去,解析其它协议也应注意大小端的处理的不同。