RTP/RTCP的定义及用途,还是请大家自己google。对于wifi手机来说呢,RTP协议用来传送编码后的语音,RTCP协议用来传送控制信息,公司的RTCP附带了一些语音统计信息和jitter buffer的统计信息用来防止语音抖动。由于是公司的东西,我就不细说了。下面是这两个协议的具体实现代码:
RTP和RTCP的头部信息如下,一会给出详细的字节图和编码过程。
RTP的头部信息:
typedef struct _RTP_HEAD
{
unsigned char Version : 2;
unsigned char Padding : 1;
unsigned char Extension : 1;
unsigned char Ccount : 4;
unsigned char Marker : 1;
unsigned char Ptype : 7;
WORD Snumber; //16bits
DWORD Timestamp; //32
DWORD Ssrc; //32
DWORD Csrc; //32
}RTP_HEAD,*pRTP_HEAD;
{
unsigned char Version : 2;
unsigned char Padding : 1;
unsigned char Extension : 1;
unsigned char Ccount : 4;
unsigned char Marker : 1;
unsigned char Ptype : 7;
WORD Snumber; //16bits
DWORD Timestamp; //32
DWORD Ssrc; //32
DWORD Csrc; //32
}RTP_HEAD,*pRTP_HEAD;
RTCP的头部信息:
typedef struct _RTCP_HEAD
{
unsigned char Version : 2;
unsigned char Padding : 1;
unsigned char PCount : 5;
unsigned char Ptype; //8bits
WORD Length; //16bits
}RTCP_HEAD,*pRTCP_HEAD;
里面各个bit位表示的意思老规矩,不懂google之。
{
unsigned char Version : 2;
unsigned char Padding : 1;
unsigned char PCount : 5;
unsigned char Ptype; //8bits
WORD Length; //16bits
}RTCP_HEAD,*pRTCP_HEAD;
里面各个bit位表示的意思老规矩,不懂google之。
具体实现呢,不能给出公司的代码,只能给出尽量通用一些的代码,程序员对比bit图和程序源代码应该很容易就能理解:
RTP的bit图和代码实现:
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 0 |V=2|P|X| CC |M| PT | sequence number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 4 | timestamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 8 | synchronization source (SSRC) identifier |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 12 | payload header |
// | .... |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// | payload |
// | .... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 0 |V=2|P|X| CC |M| PT | sequence number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 4 | timestamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 8 | synchronization source (SSRC) identifier |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 12 | payload header |
// | .... |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// | payload |
// | .... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void RTPCompose( string& buffer,
int payloadType, int sequenceNum, int timestamp, int ssrc,
const char* payloadHeader, int payloadHeaderLength,
const char* payload, int payloadLength )
{
ASSERT( payload && payloadLength >= 0 );
int payloadType, int sequenceNum, int timestamp, int ssrc,
const char* payloadHeader, int payloadHeaderLength,
const char* payload, int payloadLength )
{
ASSERT( payload && payloadLength >= 0 );
buffer.resize( 12 + payloadHeaderLength + payloadLength );
buffer[0] = (char)(2<<6); // v=2, p=x=cc=0
buffer[1] = (char)(payloadType & 0x127); // 7-bits for payload type
*((unsigned short*)&buffer[2]) = (unsigned short)sequenceNum;
*((unsigned int*)&buffer[4]) = (unsigned int)timestamp;
*((unsigned int*)&buffer[8]) = (unsigned int)ssrc;
memcpy( &buffer[12], payloadHeader, payloadHeaderLength );
memcpy( &buffer[12+payloadHeaderLength], payload, payloadLength );
buffer[0] = (char)(2<<6); // v=2, p=x=cc=0
buffer[1] = (char)(payloadType & 0x127); // 7-bits for payload type
*((unsigned short*)&buffer[2]) = (unsigned short)sequenceNum;
*((unsigned int*)&buffer[4]) = (unsigned int)timestamp;
*((unsigned int*)&buffer[8]) = (unsigned int)ssrc;
memcpy( &buffer[12], payloadHeader, payloadHeaderLength );
memcpy( &buffer[12+payloadHeaderLength], payload, payloadLength );
}
解码是编码的逆过程俺就不罗嗦了。
RTCP的bit图和代码实现:
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//byte=0 |V=2|P| RC | PT=SR=200 | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 4 | SSRC of sender |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 8 | NTP timestamp, most significant word |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 12 | NTP timestamp, least significant word |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 16 | RTP timestamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 20 | sender's packet count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 24 | sender's octet count |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 28 |V=2|P| SC | PT=SDES=202 | length |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 32 | SSRC/CSRC_1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 36 | CNAME=1 | length | user and domain name ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//byte=0 |V=2|P| RC | PT=SR=200 | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 4 | SSRC of sender |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 8 | NTP timestamp, most significant word |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 12 | NTP timestamp, least significant word |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 16 | RTP timestamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 20 | sender's packet count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 24 | sender's octet count |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 28 |V=2|P| SC | PT=SDES=202 | length |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// 32 | SSRC/CSRC_1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 36 | CNAME=1 | length | user and domain name ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void RTCPSRCompose( string& buffer,
int receptionCount, int ssrc, int64 ntpTimestamp, int rtpTimestamp,
int sentPackets, int sentBytes,
const wstring& cname )
{
int receptionCount, int ssrc, int64 ntpTimestamp, int rtpTimestamp,
int sentPackets, int sentBytes,
const wstring& cname )
{
const string& cnameUTF8 = ConvertToUTF8( cname );
int length = 8 + 20 + 4 + 6 + cnameUTF8.size( ); // 注意此处可能存在bug。
buffer.resize( length );
buffer[0] = (char)(2<<6); // V=2, P=RC=0
buffer[1] = (char)200; // PT=SR=200
*(unsigned short*)&buffer[2] = 6; // length (7 32-bit words, minus one)
*(unsigned int*)&buffer[4] = (unsigned int)ssrc;
*(unsigned int*)&buffer[8] = (unsigned int)(ntpTimestamp >> 32); // High 32-bits
*(unsigned int*)&buffer[12] = (unsigned int)(ntpTimestamp & 0xFFFFFFFF); // Low 32-bits
*(unsigned int*)&buffer[16] = (unsigned int)rtpTimestamp;
*(unsigned int*)&buffer[20] = (unsigned int)sentPackets;
*(unsigned int*)&buffer[24] = (unsigned int)sentBytes;
buffer[28] = (char)(2<<6 & 1); // V=2, P=0, SC=1
*(unsigned short*)&buffer[30] = 0xFFFF; // 注意这里应该填写长度,由于我们并没有数据因此无法填写。请程序员按自己的
int length = 8 + 20 + 4 + 6 + cnameUTF8.size( ); // 注意此处可能存在bug。
buffer.resize( length );
buffer[0] = (char)(2<<6); // V=2, P=RC=0
buffer[1] = (char)200; // PT=SR=200
*(unsigned short*)&buffer[2] = 6; // length (7 32-bit words, minus one)
*(unsigned int*)&buffer[4] = (unsigned int)ssrc;
*(unsigned int*)&buffer[8] = (unsigned int)(ntpTimestamp >> 32); // High 32-bits
*(unsigned int*)&buffer[12] = (unsigned int)(ntpTimestamp & 0xFFFFFFFF); // Low 32-bits
*(unsigned int*)&buffer[16] = (unsigned int)rtpTimestamp;
*(unsigned int*)&buffer[20] = (unsigned int)sentPackets;
*(unsigned int*)&buffer[24] = (unsigned int)sentBytes;
buffer[28] = (char)(2<<6 & 1); // V=2, P=0, SC=1
*(unsigned short*)&buffer[30] = 0xFFFF; // 注意这里应该填写长度,由于我们并没有数据因此无法填写。请程序员按自己的
包长自己填写。
*(unsigned int*)&buffer[32] = (unsigned int)ssrc;
buffer[36] = 1; // CNAME=1
buffer[37] = (char)cnameUTF8.size( );
memcpy( &buffer[38], cnameUTF8.c_str( ), cnameUTF8.size( ) ); // 同理此处请程序员自己填写
*(unsigned int*)&buffer[32] = (unsigned int)ssrc;
buffer[36] = 1; // CNAME=1
buffer[37] = (char)cnameUTF8.size( );
memcpy( &buffer[38], cnameUTF8.c_str( ), cnameUTF8.size( ) ); // 同理此处请程序员自己填写
}
以上是较为简单的实现。实际应用中必然需要填写自己附加的信息。实际上这个项目的代码均为在linux下用纯c写的代码,而我上传得代码为windows下的c++代码。由于c++中Cstring类的存在,使RTP/RTCP的代码简化了不少。如果是在linux下编程的朋友稍加修改后,即可使用。此处仅仅只是给出RTP/RTCP的一个最简单和相对比较通用的实现而已。
最后当然要唠叨一下TBCP突发通话消息如何通过RTCP包传送,很简单,将上面RTCP包中的RC项由subtype代替。其它按上一篇讲解的携带TBCP消息的RTCP包的标准编写就可以了。注意,这个标准是别人定的,如果觉得不好,自己定一个也是可以的。这样就要编写自己的服务器端了。下次再讨论一下关于PTT的在线服务,创建群组列表等对PTT来说至关重要的问题的实现。