RTP协议用于传输语音数据,RTCP协议用于传输语音数据的控制信息 
RTP流使用偶数端口,RTCP流使用相临的奇数端口。 //客户端向服务端发送Invite请求 
//SDP中设置客户端接收音频的参数 
m=audio 12392 RTP/AVP 97 111 112 6 0 8 4 5 3 101 //落地网关向服务端回复200 OK 
//SDP中设置落地网关接收音频的参数 
m=audio 13648 RTP/AVP 8 101 PT:净荷类型 
"0":G.711u  64kb/s 
"8":G.711A  64kb/s 
"4":G.723.1  5.3/6.3kb/s 
"18":G.729  8kb/s "3":GSM编码格式 
"5":DVI4 
"6":DVI4 
"8":PCMA 
"15":G728编码格式收到客户端的语音包,是否可以直接转发给落地网关?
感觉语音通讯之前,先用一队奇数端口打开语音通道,
然后用偶数端口来收发语音包,
具体不知道语音包中发的是什么东西。。

解决方案 »

  1.   

    请做过VOIP的朋友介绍下,谢谢
      

  2.   

    RTP/RTCP的实现 
    Posted on 2009-08-03 12:57 方恨少 阅读(190) 评论(0)  编辑 收藏  
      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;
    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之。
    具体实现呢,不能给出公司的代码,只能给出尽量通用一些的代码,程序员对比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                             |
     //    |                             ....                              |
     //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
    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 );
            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 );
    }
    解码是编码的逆过程俺就不罗嗦了。
    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        ...
     //       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    void RTCPSRCompose( string& buffer, 
                         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;                                    // 注意这里应该填写长度,由于我们并没有数据因此无法填写。请程序员按自己的
                                                                                                     包长自己填写。
       *(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来说至关重要的问题的实现。
      

  3.   

    感觉落地网关有压缩和解压缩RPT包的功能,
    服务器收到客户端的语音包,转发给落地网关之前,
    是否需要对RTP语音包进行处理呢?
      

  4.   

    JRTPLIB库是一个RTP协议的开源库,使用这套库文件,我们可以创建端到端的RTP连接,实现数据的实时传输。
    RTP是实时传输协议的简称。
    压缩包可以从这里获得:http://www.bairuitech.com/upimg/soft/jrtplib-3.7.1.rar
    下载jrtplib-3.7.1.rar后,首先将其解压到一个临时文件夹中,然后开始后续工作。
    首先需要强调的是,jrtplib是一个库而不是应用程序,编译后我们获得的是.lib文件。这个文件是用来实现RTP协议的,意义和我们在写WIN32程序时用到的kernel.lib一样。
    解压后的文件夹中包含两个目录,jrtplib-3.7.1和jthread-1.2.1,打开这两个目录后我们可以看到下面又有两个同名的目录,为了后面能顺利编译,我们把同名目录下的文件全部考到上一级目录中,就是说把c:\jrtplib-3.7.1\jrtplib-3.7.1\*.* 复制到c:\jrtplib-3.7.1\。同理,把c:\jthread-1.2.1\jthread-1.2.1\*.* 复制到c:\jthread-1.2.1\
    完成上述步骤后我们就可以开始编译库文件了。
    Windows平台下建议使用Visual C++6.0,我第一次用visual studio 2008 编译的时候产生的lib文件大的惊人,而且编译中总感觉出了问题。
    首先编译多线程库jthread,在vc6中直接打开工作区文件jthread.dsw,改变工程设置,一个个选中source file下的文件,确保code generation下Use run-time library 为debug mulitithreaded DLL或debug mulitithreaded。然后选build就可以了,和上面一样的方法完成jrtpthread的编译。这个底下的文件比jthread多一些。
    默认产生的文件是jthread.lib和jrtplib.Lib,这两个文件分别位于两个文件夹下的debug文件夹下,将它们复制到VC6的lib文件夹下。
    完成上述工作后我们就可以开始尝试编译jrtplib附带的examples。
    创建一个新的Win32 Console 应用程序项目,添加example文件到source files文件夹中,然后添加jrtplib工程下的所有.h头文件,这里我们可以用VC6提供的一个功能偷懒:)将jrtplib项目添加到本工作区,然后将Header Files下的所有文件复制到我们创建的工程的Header Files文件夹里面。
    (迎奥运,练英语,翻译一下上面这段,请达人看看翻的对不对,先谢过)
    (Create a new Win32 Console Application, then add the example file into the source files dictionary, add all the .h file into workspace from project jrtplib. Here we can insert the jrtplib project into workspace, copy all .h file from jrtplib/Header Files to our project/Header Files.)
    修改example.cpp文件,在文件开始添加
    #pragma comment(lib, "jrtplib.lib")
    #pragma comment(lib, "jthread.lib")
    #pragma comment(lib, "WS2_32.lib")
    检查code generationdebug mulitithreaded DLL或debug mulitithreaded,方法同上文中检查库文件的方法。
    最后就可以编译、连接、生成可执行文件了。
    Example1运行方法:
    Enter local portbase : 8888
    Enter the destination IP address:127.0.0.1
    Enter the destination port:8888
    Number of packets you wish to be send :60
    回车后可以看到程序运行:
    Sending packet 1/60
    Sending packet 2/60
    Got packet !
    运行成功。
    jrtplib的简单应用:
    1.  编译JTHREAD和JRTPLIB,把JTHREAD和JRTPLIB生成的.lib文件路径添加到tools->option->directories->Library files.
    2.  Project->Settings->Link中Object/library modules:添加jthread.lib jrtplib.lib
    3.  Link中添加ws2_32.lib
    4.  C/C++中添加定义DEBUG
    5.  在RTP接收类中头文件添加以下:
    #include <rtpsession.h>
    #include <rtppacket.h>
    #include <rtpudpv4transmitter.h>
    #include <rtpipv4address.h>
    #include <rtpsessionparams.h>
    #include <rtperrors.h>
    #include <rtpsourcedata.h>
    #include <rtpmemorymanager.h>
    ......
    类中添加
    RTPSession session;
    初始化:
     WSADATA dat;
     WSAStartup(MAKEWORD(2,2),&dat);
     RTPUDPv4TransmissionParams transparams;
     RTPSessionParams sessparams;
     m_nPort = port;
     streamMode = mode;
     sessparams.SetOwnTimestampUnit(TIMESTAMPUNIT);
     sessparams.SetAcceptOwnPackets(true);
     transparams.SetPortbase(m_nPort);
     int status=session.Create(sessparams,&transparams);
     std::string str=RTPGetErrorString(status);
     ASSERT(status>=0);
    取RTP包:
     RTPPacket *pack;
     int status;
     while()     //设置循环条件
     {
      session.BeginDataAccess();
      if(session.GotoFirstSourceWithData())
      {
       do 
       {
        while((pack=session.GetNextPacket())!=NULL)
        {
         //这里取得RTP payload: pack->GetPayloadData()
         //payload长度:  pack->GetPayloadLength()
         session.DeletePacket(pack);
        }
       } while(session.GotoNextSourceWithData());
      }
      session.EndDataAccess();
    #ifndef RTP_SUPPORT_THREAD
      status = session.Poll();
      ASSERT(status);
    #endif // RTP_SUPPORT_THREAD
      RTPTime::Wait(RTPTime(1,0));
     }
     session.BYEDestroy(RTPTime(10,0),0,0);
      

  5.   

    把从客户端收到的语音包,直接转发给网关就可以么?
    感觉客户端和网关有对RTP语音包进行打包和解压缩包......还有就是,直接转发给网关的话,
    网关是否可以自动解析出来RTP包,RTCP包,SIP指令包......