立足深圳20年,专业定制各种USB工业相机/摄像头模组|官方淘宝店|厂家淘宝店|产品定做
全国统一热线(电话微信+固话)
13603071057
0755-29808800
当前位置:主页 > 新闻中心 > 常见问题 > RTP协议学习大总结从原理到代码

RTP协议学习大总结从原理到代码

文章出处: 人气:发表时间:2019-05-05 11:33
  一、流媒体概念
 
  流媒体包含广义和狭义两种内涵:广义上的流媒体指的是使音频和视频形成稳定和连续的传
 
  输流和回放流的一系列技术、方法和协议的总称,即流媒体技术;狭义上的流媒体是相对
 
  于传统的下载-回放方式而言的,指的是一种从Internet上获取音频和视频等多媒体数据的
 
  新方法,它能够支持多媒体数据流的实时传输和实时播放。通过运用流媒体技术,服务器
 
  能够向客户机发送稳定和连续的多媒体数据流,客户机在接收数据的同时以一个稳定的速率
 
  回放,而不用等数据全部下载完之后再进行回放。
 
  二、流媒体协议
 
  实时传输协议(Real-time Transport Protocol,PRT)是在Internet上处理多媒体数据流的一
 
  种网络协议,利用它能够在一对一(unicast,单播)或者一对多(multicast,多播)的网
 
  络环境中实现传流媒体数据的实时传输。RTP通常使用UDP来进行多媒体数据的传输,但
 
  如果需要的话可以使用TCP或者ATM等其它协议,整个RTP协议由两个密切相关的部分组
 
  成:RTP数据协议和RTP控制协议。实时流协议(Real Time Streaming Protocol,RTSP)最早
 
  由Real Networks和Netscape公司共同提出,它位于RTP和RTCP之上,其目的是希望通过
 
  IP网络有效地传输多媒体数据。
 
  2.1 RTP数据协议
 
  RTP数据协议负责对流媒体数据进行封包并实现媒体流的实时传输,每一个RTP数据
 
  报都由头部(Header)和负载(Payload)两个部分组成,其中头部前12个字节的含
 
  义是固定的,而负载则可以是音频或者视频数据。RTP数据报的头部格式如图1所示:
 
  图1 RTP头部格式
 
  其中比较重要的几个域及其意义如下:
 
  CSRC记数(CC)表示CSRC标识的数目。CSRC标识紧跟在RTP固定头部之后,用来表
 
  示RTP数据报的来源,RTP协议允许在同一个会话中存在多个数据源,它们可以通过RTP
 
  混合器合并为一个数据源。例如,可以产生一个CSRC列表来表示一个电话会议,该会议通
 
  过一个RTP混合器将所有讲话者的语音数据组合为一个RTP数据源。
 
  负载类型(PT)标明RTP负载的格式,包括所采用的编码算法、采样频率、承载通道
 
  等。例如,类型2表明该RTP数据包中承载的是用ITU G.721算法编码的语音数据,采样频
 
  率为8000Hz,并且采用单声道。
 
  序列号用来为接收方提供探测数据丢失的方法,但如何处理丢失的数据则是应用程序自
 
  己的事情,RTP协议本身并不负责数据的重传。
 
  时间戳记录了负载中第一个字节的采样时间,接收方根据时间戳能够确定数据的到达
 
  是否受到了延迟抖动的影响,但具体如何来补偿延迟抖动则是应用程序自己的事情。
 
  从RTP数据报的格式不难看出,它包含了传输媒体的类型、格式、序列号、时间戳以及是
 
  否有附加数据等信息,这些都为实时的流媒体传输提供了相应的基础。RTP协议的目的是
 
  提供实时数据(如交互式的音频和视频)的端到端传输服务,因此在RTP中没有连接的概念,
 
  它可以建立在底层的面向连接或面向非连接的传输协议之上;RTP也不依赖于特别的网络
 
  地址格式,而仅仅只需要底层传输协议支持组帧(Framing)和分段(Segmentation)就足够
 
  了;另外RTP本身还不提供任何可靠性机制,这些都要由传输协议或者应用程序自己来保
 
  证。在典型的应用场合下,RTP一般是在传输协议之上作为应用程序的一部分加以实现的,
 
  如图2所示:
 
  图2 RTP与各种网络协议的关系
 
  2.2 RTCP控制协议
 
  RTCP控制协议需要与RTP数据协议一起配合使用,当应用程序启动一个RTP会话时将同时
 
  占用两个端口,分别供RTP和RTCP使用。RTP本身并不能为按序传输数据包提供可靠的保
 
  证,也不提供流量控制和拥塞控制,这些都由RTCP来负责完成。通常RTCP会采用与RTP
 
  相同的分发机制,向会话中的所有成员周期性地发送控制信息,应用程序通过接收这些数据,
 
  从中获取会话参与者的相关资料,以及网络状况、分组丢失概率等反馈信息,从而能够对服
 
  务质量进行控制或者对网络状况进行诊断。
 
  RTCP协议的功能是通过不同的RTCP数据报来实现的,主要有如下几种类型:
 
  SR发送端报告,所谓发送端是指发出RTP数据报的应用程序或者终端,发送端同时也
 
  可以是接收端。
 
  RR接收端报告,所谓接收端是指仅接收但不发送RTP数据报的应用程序或者终端。
 
  SDES源描述,主要功能是作为会话成员有关标识信息的载体,如用户名、邮件地址、
 
  电话号码等,此外还具有向会话成员传达会话控制信息的功能。
 
  BYE通知离开,主要功能是指示某一个或者几个源不再有效,即通知会话中的其他成员
 
  自己将退出会话。
 
  APP由应用程序自己定义,解决了RTCP的扩展性问题,并且为协议的实现者提供了很
 
  大的灵活性。
 
  RTCP数据报携带有服务质量监控的必要信息,能够对服务质量进行动态的调整,并能够对
 
  网络拥塞进行有效的控制。由于RTCP数据报采用的是多播方式,因此会话中的所有成员都
 
  可以通过RTCP数据报返回的控制信息,来了解其他参与者的当前情况。
 
  在一个典型的应用场合下,发送媒体流的应用程序将周期性地产生发送端报告SR,该RTCP
 
  数据报含有不同媒体流间的同步信息,以及已经发送的数据报和字节的计数,接收端根据
 
  这些信息可以估计出实际的数据传输速率。另一方面,接收端会向所有已知的发送端发送接
 
  收端报告RR,该RTCP数据报含有已接收数据报的最大序列号、丢失的数据报数目、延时抖
 
  动和时间戳等重要信息,发送端应用根据这些信息可以估计出往返时延,并且可以根据数据
 
  报丢失概率和时延抖动情况动态调整发送速率,以改善网络拥塞状况,或者根据网络状况平
 
  滑地调整应用程序的服务质量。
 
  2.3 RTSP实时流协议
 
  作为一个应用层协议,RTSP提供了一个可供扩展的框架,它的意义在于使得实时流媒体数
 
  据的受控和点播变得可能。总的说来,RTSP是一个流媒体表示协议,主要用来控制具有实
 
  时特性的数据发送,但它本身并不传输数据,而是必须依赖于下层传输协议所提供的某些服
 
  务。RTSP可以对流媒体提供诸如播放、暂停、快进等操作,它负责定义具体的控制消息、
 
  操作方法、状态码等,此外还描述了与RTP间的交互操作。
 
  RTSP在制定时较多地参考了HTTP/1.1协议,甚至许多描述与HTTP/1.1完全相同。RTSP之所
 
  以特意使用与HTTP/1.1类似的语法和操作,在很大程度上是为了兼容现有的Web基础结构,
 
  正因如此,HTTP/1.1的扩展机制大都可以直接引入到RTSP中。
 
  由RTSP控制的媒体流集合可以用表示描述(Presentation Description)来定义,所谓表示是
 
  指流媒体服务器提供给客户机的一个或者多个媒体流的集合,而表示描述则包含了一个表示
 
  中各个媒体流的相关信息,如数据编码/解码算法、网络地址、媒体流的内容等。
 
  虽然RTSP服务器同样也使用标识符来区别每一流连接会话(Session),但RTSP连接并没有
 
  被绑定到传输层连接(如TCP等),也就是说在整个RTSP连接期间,RTSP用户可打开或者
 
  关闭多个对RTSP服务器的可靠传输连接以发出RTSP请求。此外,RTSP连接也可以基于面
 
  向无连接的传输协议(如UDP等)。
 
  RTSP协议目前支持以下操作:
 
  检索媒体允许用户通过HTTP或者其它方法向媒体服务器提交一个表示描述。如表示是
 
  组播的,则表示描述就包含用于该媒体流的组播地址和端口号;如果表示是单播的,为了安
 
  全在表示描述中应该只提供目的地址。
 
  邀请加入媒体服务器可以被邀请参加正在进行的会议,或者在表示中回放媒体,或者在
 
  表示中录制全部媒体或其子集,非常适合于分布式教学。
 
  添加媒体通知用户新加入的可利用媒体流,这对现场讲座来讲显得尤其有用。与
 
  HTTP/1.1类似,RTSP请求也可以交由代理、通道或者缓存来进行处理。
 
  参考文档
 
  三、JRTPlib3.7.1在windows下的编译步骤
 
  编译设置过程:
 
  1.解压jrtplib-3.7.1和jthread-1.2.1
 
  2.用VC打开工程文件jthread.dsw
 
  3.编译jrtplib.lib和jthread.lib需要注意VC6要求安装Vs6sp6,在编译jrtplib.lib和jthread.lib
 
  前,在project——settings——C/C++——Code generation:use run-time library中,对于debug,
 
  选择:Debug Multithreaded DLL,对于release,则选择:Multithreaded DLL。
 
  4.首先编译jthread库,然后将jthread-1.2.1\src内的"jmutex.h"和"jthread.h"两个头文件放入
 
  jrtplib-3.7.1\src目录下,然后将jrtplib-3.7.1\src文件夹下所有头文件中的<jmutex.h>和
 
  <jthread.h>语句修改为"jmutex.h"和"jthread.h",需要修改的文件为rtpudpv4transmitter.h、
 
  rtpsession.h和rtppollthread.h。编译时注意编译方式和jthread.lib一致。
 
  5.编译生成的jthread.lib和jrtplib.lib拷贝到系统目录:C:\Program Files\Microsoft Visual
 
  Studio\VC98\Lib下,将jrtplib-3.7.1\src下所有的.h头文件复制到C:\Program Files\Microsoft
 
  Visual Studio\VC98\Include,以便以后使用。
 
  6.现在我们就可以编译jrtplib-3.7.1\examples下的实例程序了。建立VC工程,打开
 
  example1.c,在Project Settings的link页添加jthread.lib jrtplib.lib ws2_32.lib,在
 
  project——settings——C/C++——Code generation:use run-time library中,对于debug,选
 
  择:Debug Multithreaded DLL,对于release,则选择:Multithreaded DLL。
 
  7.编译源程序,运行就OK啦
 
  四、JRTPLIB库的使用方法及程序实现
 
  (1)JRTPLIB函数的使用
 
  a、在使用JRTPLIB进行实时流媒体数据传输之前,首先应该生成RTPSession
 
  类的一个实例来表示此次RTP会话,然后调用Create()方法来对其进行初始
 
  化操作。JRTPLIB-3.7中的Create方法为
 
  Create(sessparams,&transparams)。其中的两个参数需要如下先定义:
 
  RTPUDPv4TransmissionParams transparams;
 
  RTPSessionParams sessparams;
 
  sessparams.SetOwnTimestampUnit(1.0/8000.0);/*设置时间戳,1/8000表示1
 
  秒钟采样8000次,即录音时的8KHz*/
 
  sessparams.SetAcceptOwnPackets(true);
 
  transparams.SetPortbase(portbase);/*本地通讯端口*/
 
  b、设置恰当的时戳单元,是RTP会话初始化过程所要进行的另外一项重要工作,
 
  这是通过调用RTPSession类的SetTimestampUnit()方法来实现的,前面已经
 
  提过。
 
  c、当RTP会话成功建立起来之后,接下去就可以开始进行流媒体数据的实时传
 
  输了。首先需要设置好数据发送的目标地址,RTP协议允许同一会话存在多个目
 
  标地址,这可以通过调用RTPSession类的AddDestination()、
 
  DeleteDestination()和ClearDestinations()方法来完成。例如,下面的语
 
  句表示的是让RTP会话将数据发送到本地主机的6000端口:
 
  unsigned long addr=ntohl(inet_addr("127.0.0.1"));
 
  sess.AddDestination(addr,6000);
 
  d、目标地址全部指定之后,接着就可以调用RTPSession类的SendPacket()方
 
  法,向所有的目标地址发送流媒体数据。SendPacket()是RTPSession类提供
 
  的一个重载函数对于同一个RTP会话来讲,负载类型、标识和时戳增量通常来
 
  讲都是相同的,JRTPLIB允许将它们设置为会话的默认参数,这是通过调用
 
  RTPSession类的SetDefaultPayloadType()、SetDefaultMark()和
 
  SetDefaultTimeStampIncrement()方法来完成的。为RTP会话设置这些默认参
 
  数的好处是可以简化数据的发送,例如,如果为RTP会话设置了默认参数:
 
  sess.SetDefaultPayloadType(0);
 
  sess.SetDefaultMark(false);
 
  sess.SetDefaultTimeStampIncrement(10);
 
  之后在进行数据发送时只需指明要发送的数据及其长度就可以了:
 
  sess.SendPacket(buffer,5);
 
  在真正的语音传输中,上面的buffer就是我们录音时所得到的buffer。使用上
 
  面的函数可以简单的发送,但无法真正的实现RTP传输,我们需要调用另一个接
 
  口:sess.SendPacket((void*)buffer,sizeof(buffer),0,false,8000);详细
 
  的说明可以查看JRTPLIB的说明文档。
 
  e、对于流媒体数据的接收端,首先需要调用RTPSession类的Poll()方法来
 
  接收发送过来的RTP或者RTCP数据报。
 
  由于同一个RTP会话中允许有多个参与者(源),你既可以通过调用RTPSession类的
 
  GotoFirstSource()和GotoNextSource()方法来遍历所有的源,也可以通过调用RTPSession
 
  类的GotoFirstSourceWithData()和GotoNextSourceWithData()方法来遍历那些携带有数据
 
  的源。在从RTP会话中检测出有效的数据源之后,接下去就可以调用RTPSession类的
 
  GetNextPacket()方法从中抽取RTP数据报,当接收到的RTP数据报处理完之后,一定要
 
  记得及时释放。
 
  JRTPLIB为RTP数据报定义了三种接收模式,其中每种接收模式都具体规定了
 
  哪些到达的RTP数据报将会被接受,而哪些到达的RTP数据报将会被拒绝。通
 
  过调用RTPSession类的SetReceiveMode()方法可以设置下列这些接收模式:
 
  RECEIVEMODE_ALL缺省的接收模式,所有到达的RTP数据报都将被接受;
 
  RECEIVEMODE_IGNORESOME除了某些特定的发送者之外,所有到达的RTP数
 
  据报都将被接受,而被拒绝的发送者列表可以通过调用AddToIgnoreList()、
 
  DeleteFromIgnoreList()和ClearIgnoreList()方法来进行设置;
 
  RECEIVEMODE_ACCEPTSOME除了某些特定的发送者之外,所有到达的RTP数
 
  据报都将被拒绝,而被接受的发送者列表可以通过调用AddToAcceptList()、
 
  DeleteFromAcceptList和ClearAcceptList()方法来进行设置。
 
  下面是采用第三种接收模式的程序示例。
 
  if(sess.GotoFirstSourceWithData()){
 
  do{
 
  sess.AddToAcceptList(remoteIP,allports,portbase);
 
  sess.SetReceiveMode(RECEIVEMODE_ACCEPTSOME);
 
  RTPPacket*pack;
 
  pack=sess.GetNextPacket();//处理接收到的数据
 
  delete pack;}
 
  while(sess.GotoNextSourceWithData());
 
  }
 
  完整的代码中,首先需调用Poll()方法接收RTP数据报,然后在
 
  BeginDataAccess()和EndDataAccess()之间进行数据接收的操作。此时,我们
 
  设定程序一直do-while等待并处理数据
 
  (2)程序流程
 
  发送:获得接收端的IP地址和端口号创建RTP会话指定RTP数据接收端设置
 
  RTP会话默认参数发送流媒体数据
 
  接收:获得用户指定的端口号创建RTP会话设置接收模式接受RTP数据检索RTP数据
 
  源获取RTP数据报删除RTP数据报
 
  五、RTPSession类介绍
 
  对于大多数的RTP应用程序,RTPSession类可能是JRTPLIB唯一使用的类。它能完全处
 
  理RTCP部份的数据包,所以用户可以把精力集中在真正的数据收发。
 
  要知道RTPSession类在多线程下并不是安全的,因此,用户要通过某些锁同步机制来保
 
  证不会出现在不同线程当中调用同一个RTPSession实例。
 
  RTPSession类有如下的接口。
 
  •RTPSession(RTPTransmitter::TransmissionProtocol proto=RTPTransmitter::IPv4UDPProto)
 
  使用proto类型传输层创建一个PRTSession实例。如果proto使用用户自定义
 
  (user-defined)传输层,则相应的NewUserDefinedTransmitter()函数必须实现。ps:这里默认
 
  就行了,默认就是IPV4网络。
 
  •int Create(const RTPSessionParams&sessparams,const
 
  RTPTransmissionParams*transparams=0)
 
  使用RTPSession参数sessparams和RTPTransmission参数transparams真正创建一个RTP
 
  会话。如果transparams为NULL,则使用默认的参数。ps:RTPSessionParams我们可能要设
 
  得比较多,RTPTransmissionParams参数就只要设置其中的端口就行了,端口一定要设对,不
 
  然进行组播时,这个进程将不接收数据。设置方式可以看example.cpp。
 
  •void Destroy()
 
  离开一个会话但不向其它组成员发送BYE包。ps:我不推荐用这个函数除非是错误处理,
 
  正常离开我们应该用ByeDestroy()。
 
  •void BYEDestroy(const RTPTime&maxwaittime,const void*reason,size t reasonlength)
 
  发送一个BYE包并且离开会话。在发送BYE包前等待maxwaittime,如果超时,会不发
 
  送BYE包直接离开,BYE包会包含你的离开原因reason。相应的reasonlength表示reason长
 
  度。ps:因为BYE包是一个RTCP包,RTCP不是要发就发的,它的发送时间是为了平衡带宽通
 
  过计算得出来的,那就很有可能到了要发的时候以经超过了maxwaittime时间了,作者可能
 
  认一直保留个这会话这么久没意义。当然,我常常把maxwaittime设得很大。
 
  •bool IsActive()
 
  看看这个RTPSession实例是否以经通过Create建立了真实的会话。
 
  •uint32 t GetLocalSSRC()
 
  返回我们的SSRC。ps:至于什么是SSRC,去看看RFC3550吧。我说过JRTPLIB只是RTP
 
  协议的包装,并没有做任何应用的事情。
 
  •int AddDestination(const RTPAddress&addr)
 
  添加一个发送目标。ps:当然,如果我们使用组播,这里只用调用一次,把我们的组播
 
  地址写进去。这样,这组的全部人都能收到你发的包。但是组播可因特网的上设置很烦。而
 
  且用组播测试也很烦(组播必须BIND一个端口,如果你想在同一台机器上运行两个软件
 
  实例来没试,你就会发现同一个端口BIND两次,当然,后面那次会失败,也就是说测试
 
  不了,要测?找两台机器,或用虚拟机),如果组播不满足,我们就要把组播变在单播,
 
  这时就要返复调用这个函数把其它组成员的IP都加进来了。具体可以看看example3.cpp。
 
  •int DeleteDestination(const RTPAddress&addr)
 
  从发送地址列表中删除一下地址。
 
  •void ClearDestinations()
 
  清除发送地址列表。
 
  •bool SupportsMulticasting()
 
  返回JRTPLIB是否支持组播。ps:这里指JRTPLIB本身,不是你的真实网络。编译JRTPLIB
 
  库时可能指定。
 
  .
 
  •int JoinMulticastGroup(const RTPAddress&addr)
 
  加入一个组播组addr。
 
  •int LeaveMulticastGroup(const RTPAddress&addr)
 
  离开一个组播组addr。
 
  •void LeaveAllMulticastGroups()
 
  离开所有组播组。ps:我们可以同时加入多个组播组。.
 
  •int SendPacket(const void*data,size t len)
 
  •int SendPacket(const void*data,size t len,uint8 t pt,bool mark,uint32 t timestampinc)
 
  •int SendPacketEx(const void*data,size t len,uint16 t hdrextID,const void*hdrextdata,size
 
  t numhdrextwords)
 
  •int SendPacketEx(const void*data,size t len,uint8 t pt,boolmark,uint32 t timestampinc,
 
  uint16 t hdrextID,const void*hdrextdata,size t numhdrextwords)
 
  上面的4个函数都是发送数据包的,我想如果你没有看RTP协议,我说了你也晕。如果
 
  你RTP协议看了,再看看RTPSession.h的注识,你就懂了。
 
  •int SetDefaultPayloadType(uint8 t pt)
 
  设定默认的RTP PayloadType为PT。ps:和上面的第一个和第三个发送函数配套。至于应
 
  该设个什么数,如果你看BAIDU上乱七八糟的文章,当然的乱设就可能了。其实应该按
 
  RFC3551,根据你要传输的媒体类型来设。
 
  •int SetDefaultMark(bool m)
 
  这设RTP数据包的Mark标识。ps:设为什么值好?这个,呵呵,连RFC3550也不能确定
 
  了。要看具体的RTP Payload规范,MPEG的,H263的都不一样。
 
  MPEG2 www.ietf.org/rfc/rfc2250.txt
 
  MPEG4 www.rfc-editor.org/rfc/rfc3016.txt
 
  H263 www.ietf.org/rfc/rfc2190.txt
 
  •int SetDefaultTimestampIncrement(uint32 t timestampinc)
 
  设置默认的时间戳的增量。ps:也是和上的第一和第三个函数配套的。每发一个RTP数
 
  据包timestamp就会自动增加
 
  •int IncrementTimestamp(uint32 t inc)
 
  这个函数用来手工增加Timestamp。有时我这很好用,例如,一个RTP数据包因为只含
 
  有静音数据,我们没有发送,这是我们就应手工增加Timestamp以便发下一个RTP数据包
 
  时它的Timestamp是正确的。
 
  •int IncrementTimestampDefault()
 
  这个函数用于增加由SetDefaultTimestampIncrement设定的值。有时候这很有用,例如,
 
  一个RTP数据包因为只含有静音数据,我们没有发送。这时,这个函数就会被调用用来设
 
  置Timestamp以便下一个RTP包的Timestamp是正确的。
 
  •int SetPreTransmissionDelay(const RTPTime&delay)
 
  This function allows you to inform the library about the delay between
 
  sampling the first sample of a packet and sending the packet.This delay is
 
  taken into account when calculating the relation between RTP timestamp
 
  and wallclock time,used for inter-media synchronization.
 
  •RTPTransmissionInfo*GetTransmissionInfo()
 
  This function returns an instance of a subclass of RTPTransmissionInfo
 
  which will give some additional information about the transmitter(a list
 
  of local IP addresses for example).The user has to delete the returned
 
  instance when it is no longer needed.
 
  •int Poll()
 
  If you’re not using the poll thread,this function must be called regularly
 
  to process incoming data and to send RTCP data when necessary.
 
  61
 
  •int WaitForIncomingData(const RTPTime&delay,bool*dataavailable=0)
 
  Waits at most a time delay until incoming data has been detected.Only
 
  works when you’re not using the poll thread.If dataavailable is not NULL,
 
  it should be set to true if data was actually read and to false otherwise.
 
  •int AbortWait()
 
  If the previous function has been called,this one aborts the waiting.Only
 
  works when you’re not using the poll thread.
 
  •RTPTime GetRTCPDelay()
 
  Returns the time interval after which an RTCP compound packet may have
 
  to be sent.Only works when you’re not using the poll thread.
 
  •int BeginDataAccess()
 
  下面的函数(直到EndDataAccess)要在BeginDataAccess和EndDataAccess之间被调用,
 
  BeginDataAccess确保轮询(poll)线程不会在这期间访问source table。EndDataAccess调用
 
  完成后,轮询(poll)线程会得到锁而继续访问。ps:首先,你里的source table中的每一个source
 
  表示参与会议中的每一个参与者的每一个独立的媒体流。我们会在下面用到他们,但同时,
 
  poll线程也会轮询它们以正确处理和RTCP有关的内容。
 
  •bool GotoFirstSource()
 
  开始递归参与者的第一个流,如果找到了,就返回tree,否则返回false。ps:我们通过
 
  这个函数和下面的GotoNextSource遍历source table中的每一个source。
 
  •bool GotoNextSource()
 
  设置当前的源(source)为source table中的下一个源。如果已经到尾部了就返回false.
 
  •bool GotoPreviousSource()
 
  设置当前的源(source)为source table中上一个源。如果已经到头部了就返回false.
 
  •bool GotoFirstSourceWithData()
 
  开始递归参与者中第一个有RTP数据的流,如果找到了,就返回tree,否则返回false。
 
  PS:在接收数据是我们常用的是这套函数,因为如果没有数据要来都没用。
 
  •bool GotoNextSourceWithData()
 
  设置当前的源(source)为source table中有RTP数据的下一个源。如果已经到尾部了
 
  就返回false.
 
  •bool GotoPreviousSourceWithData()
 
  设置当前的源(source)为source table中有RTP数据的上一个源。如果已经到头部了
 
  就返回false.
 
  •RTPSourceData*GetCurrentSourceInfo()
 
  返回当前参与者的当前源(source)的RTPSourceData实列。ps:返回的这个
 
  RTPSourceData就是本进程从期它参与者的RTCP数据包中收集得到的信息,对我们来说其
 
  实很有用,只是作者的例程没有用上,国内的网络也没有提到。在RFC3550中有关RTCP
 
  的东西都在这了,看过RFC3550的人都知到,里头谈得最多的就是RTCP。这个类我们以后
 
  会专门说。
 
  •RTPSourceData*GetSourceInfo(uint32 t ssrc)
 
  返回由ssrc指定的RTPSourceData,或都NULL(当这个条目不存在)。ps:这个函数也
 
  很有用。因为GetCurrentSourceInfo只有在GotoFirstSource等上下文当中才能用。如果我们
 
  是在RTPSource子类的成员函数中,我们没有这个上下文,就只能用这个函数。
 
  •RTPPacket*GetNextPacket()
 
  得到当前参与者当前媒体流的下一个RTP数据包。
 
  •int EndDataAccess()
 
  请看BeginDataAccess
 
  •int SetReceiveMode(RTPTransmitter::ReceiveMode m)
 
  Sets the receive mode to m,which can be one of the following:
 
  –RTPTransmitter::AcceptAll
 
  All incoming data is accepted,no matter where it originated from.
 
  –RTPTransmitter::AcceptSome
 
  Only data coming from specific sources will be accepted.
 
  –RTPTransmitter::IgnoreSome
 
  All incoming data is accepted,except for data coming from a specificset of sources.
 
  Note that when the receive mode is changed,the list of addressed to be ignored or
 
  accepted will be cleared.
 
  •int AddToIgnoreList(const RTPAddress&addr)
 
  Adds addr to the list of addresses to ignore.
 
  •int DeleteFromIgnoreList(const RTPAddress&addr)
 
  Deletes addr from the list of addresses to ignore.
 
  •void ClearIgnoreList()
 
  Clears the list of addresses to ignore.
 
  •int AddToAcceptList(const RTPAddress&addr)
 
  Adds addr to the list of addresses to accept.
 
  •int DeleteFromAcceptList(const RTPAddress&addr)
 
  Deletes addr from the list of addresses to accept.
 
  •void ClearAcceptList()
 
  Clears the list of addresses to accept.
 
  •int SetMaximumPacketSize(size t s)
 
  Sets the maximum allowed packet size to s.
 
  •int SetSessionBandwidth(double bw)
 
  Sets the session bandwidth to bw,which is specified in bytes per second.
 
  •int SetTimestampUnit(double u)
 
  Sets our own timestamp unit to u.The timestamp unit is defined as a time
 
  interval divided by the number of samples in that interval:for 8000Hz
 
  audio this would be 1.0/8000.0.
 
  •void SetNameInterval(int count)
 
  在处理source table中的sourcese后,RTCP packet builder(我们不用理这个内部的东西)
 
  会检查是否有其它(non-CNAME)SDES项目要发送。如果count为零或负数,则不发送,如
 
  果count为正数,则在sources table处理count次后会把SDES name item加到当前RTCP包
 
  中。ps:其实每次处理sources table都会伴随都SDES RTCP数据包的发送,在这个数据包当
 
  中CNAME是必须的,但其它的项目不是必须的,这就函数确定了NAME项目发送的频度,
 
  如果为1,则表不每个SDES RTCP数据包都带着它,如果为2则每两个SDES数据包就发送
 
  一次NAME项目,下面的SetEMailInterval、SetLocationInterval、SetPhoneInterval、
 
  SetToolInterval、SetNoteInterval都是同一原理。关于这个ITEM的描述,请看RFC3550.老版
 
  本的JRTPLIB没有使用这套函数,而是用EnableSendName()等函数。
 
  •void SetEMailInterval(int count)
 
  After all possible sources in the source table have been processed,the RTCP
 
  packet builder will check if other(non-CNAME)SDES items need to be
 
  sent.If count is zero or negative,nothing will happen.If count is positive,
 
  an SDES e-mail item will be added after the sources in the source table
 
  have been processed count times.
 
  •void SetLocationInterval(int count)
 
  After all possible sources in the source table have been processed,the RTCP
 
  packet builder will check if other(non-CNAME)SDES items need to be
 
  sent.If count is zero or negative,nothing will happen.If count is positive,
 
  an SDES location item will be added after the sources in the source table
 
  have been processed count times.
 
  •void SetPhoneInterval(int count)
 
  After all possible sources in the source table have been processed,the RTCP
 
  packet builder will check if other(non-CNAME)SDES items need to be
 
  sent.If count is zero or negative,nothing will happen.If count is positive,
 
  an SDES phone item will be added after the sources in the source table
 
  have been processed count times.
 
  •void SetToolInterval(int count)
 
  After all possible sources in the source table have been processed,the RTCP
 
  packet builder will check if other(non-CNAME)SDES items need to be
 
  sent.If count is zero or negative,nothing will happen.If count is positive,
 
  an SDES tool item will be added after the sources in the source table have
 
  been processed count times.
 
  •void SetNoteInterval(int count)
 
  After all possible sources in the source table have been processed,the RTCP
 
  packet builder will check if other(non-CNAME)SDES items need to be
 
  sent.If count is zero or negative,nothing will happen.If count is positive,
 
  an SDES note item will be added after the sources in the source table have
 
  been processed count times.
 
  •int SetLocalName(const void*s,size t len)
 
  设置NAME SDES项目,以遍会议的其它人员看到你的名称。下同。
 
  •int SetLocalEMail(const void*s,size t len)
 
  Sets the SDES e-mail item for the local participant to the value s with
 
  length len.
 
  •int SetLocalLocation(const void*s,size t len)
 
  Sets the SDES location item for the local participant to the value s with
 
  length len.
 
  •int SetLocalPhone(const void*s,size t len)
 
  Sets the SDES phone item for the local participant to the value s with
 
  length len.
 
  •int SetLocalTool(const void*s,size t len)
 
  Sets the SDES tool item for the local participant to the value s with length
 
  len.
 
  •int SetLocalNote(const void*s,size t len)
 
  Sets the SDES note item for the local participant to the value s with length
 
  len.
 
  In case you specified in the constructor that you want to use your own transmission
 
  component,you should override the following function:
 
  •RTPTransmitter*NewUserDefinedTransmitter()
 
  The RTPTransmitter instance returned by this function will then be used to send
 
  and receive RTP and RTCP packets.Note that when the session is destroyed,
 
  this RTPTransmitter instance will be destroyed with a delete call.
 
  By inheriting your own class from RTPSession and overriding one or more of the
 
  functions below,certain events can be detected:
 
  •void OnRTPPacket(RTPPacket*pack,const RTPTime&receivetime,const RTPAddress
 
  *senderaddress)
 
  如果有RTPPacket数据包来到,会调用这个函数处理。ps:这个函数在我们继承RTPSession
 
  类时很可能重载,这是获取RTP数据包除了上面所说的方法以外的另外一种方法,这个方法
 
  比较适合异步的情况。默认这个是一个空虚函数。除了这个函数以外,下面的几个函数了会
 
  经常重载。
 
  •void OnRTCPCompoundPacket(RTCPCompoundPacket*pack,const RTPTime&receivetime,
 
  const RTPAddress*senderaddress)
 
  Is called when an incoming RTCP packet is about to be processed.
 
  •void OnSSRCCollision(RTPSourceData*srcdat,const RTPAddress*senderaddress,bool isrtp)
 
  Is called when an SSRC collision was detected.The instance srcdat is the
 
  one present in the table,the address senderaddress is the one that collided
 
  with one of the addresses and isrtp indicates against which address
 
  of srcdat the check failed.
 
  •void OnCNAMECollision(RTPSourceData*srcdat,const RTPAddress*senderaddress,const
 
  uint8 t*cname,size t cnamelength)
 
  Is called when another CNAME was received than the one already present for source srcdat.
 
  •void OnNewSource(RTPSourceData*srcdat)
 
  当有一个新的条目加到source table时,调用这个函数。ps:这也是一个比较重要的函数,
 
  因为这意味着很有可能有一个新的与会者加入。但令我很不高兴的是,这时候的
 
  RTPSourceData里头的CNAME和NAME等字段都还是无效的,这不是RTCP的责任,因为在
 
  这个SDES RTCP数据包中所有的信息都以经有了(通过抓包证实了这一点)。我们的函数被
 
  调用后,需要延时一会才能得到有关这个Source的CNAME和NAME等相关的信息。当然,
 
  如果你不想软件死掉,不能在这个函数体内以阻塞的方式延时。
 
  •void OnRemoveSource(RTPSourceData*srcdat)
 
  当有一个条目从source table中移除时调用这个函数。ps:这通常意味着有一个与会者
 
  离开了,和OnNewSource不一样,这时的CNAME和NAME等都是有效的。用这个函数要注
 
  意,我们的“意味着两个字”因为“加入”的可能不是一个新的与会者,而是一个现有与会者
 
  的一个新的媒体流。“离开”的也可能不是一个与会者,而只是其中一个与会者的其中一个媒
 
  体流,这两个函数只能给我们更新与会者提供一个触发条件而已。当OnNewSource调用时,
 
  我们要看看这个CNAME是不是以经在我们与会者名单中,如果不是,那就是一个新与会
 
  者。同时,如果OnRemoveSource被调用,则我们要看看这个CNAME的与会者还有没有其
 
  它的Source,如果没有了,这个与会者才是真正离开。这么很麻烦??那就对了,那就是
 
  现在的H323和SIP要做的事情--会话管理。
 
  •void OnTimeout(RTPSourceData*srcdat)
 
  Is called when participant srcdat is timed out.
 
  •void OnBYETimeout(RTPSourceData*srcdat)
 
  Is called when participant srcdat is timed after having sent a BYE packet.
 
  •void OnBYEPacket(RTPSourceData*srcdat)
 
  Is called when a BYE packet has been processed for source srcdat.
 
  •void OnAPPPacket(RTCPAPPPacket*apppacket,const RTPTime&receivetime,
 
  const RTPAddress*senderaddress)
 
  In called when an RTCP APP packet apppacket has been received at time
 
  receivetime from address senderaddress.
 
  •void OnUnknownPacketType(RTCPPacket*rtcppack,const RTPTime&receivetime,
 
  const RTPAddress*senderaddress)
 
  Is called when an unknown RTCP packet type was detected.
 
  •void OnUnknownPacketFormat(RTCPPacket*rtcppack,const RTPTime&receivetime,
 
  const RTPAddress*senderaddress)
 
  Is called when an unknown packet format for a known packet type was
 
  detected.
 
  •void OnNoteTimeout(RTPSourceData*srcdat)
 
  Is called when the SDES NOTE item for source srcdat has been timed out.
 
  •void OnSendRTCPCompoundPacket(RTCPCompoundPacket*pack)
 
  Is called when an RTCP compound packet has just been sent.Useful to
 
  inspect outgoing RTCP data.
 
  •void OnPollThreadError(int errcode)
 
  Is called when error errcode was detected in the poll thread.
 
  •void OnPollThreadStep()
 
  Is called each time the poll thread loops.This happens when incoming data
 
  was detected or when its time to send an RTCP compound packet.
 
  六、环境搭建及编译方法
 
  (1)Toolchain的安装
 
  首先找到xscale-arm-toolchain.tgz文件,假设该文件包放在/tmp/下
 
  #cd/
 
  #tar-zxvf/tmp/xscale-arm-toolchain.tgz
 
  再设置环境变量
 
  #export PATH=/usr/local/arm-linux/bin:$PATH
 
  最后检查一下交叉编译工具是否安装成功
 
  #arm-linux-g++--version
 
  看是否显示arm-linux-g++的版本,如有则安装成功。
 
  (2)JRTPLIB库的交叉编译及安装
 
  首先从JRTPLIB的网站(http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.htmll)下载最新的源
 
  码包,此处使用的是jrtplib-2.8.tar,假设下载后的源码包放在/tmp下,执行下面的命令对
 
  其解压缩:
 
  #cd/tmp
 
  #tar-zxvf jrtplib-2.8.tar
 
  然后要对jrtplib进行配置和编译
 
  #cd jrtplib-2.8
 
  #./configure CC=arm-linux-g++cross-compile=yes
 
  修改Makefile文件
 
  将链接命令ld和ar改为arm-linux-ld和arm-linux-ar
 
  #make
 
  最后再执行如下命令就可以完成JRTPLIB的安装:
 
  #make install
 
  (3)程序编译
 
  a、配置编译环境
 
  可以用export来配置,也可以用编写Makefile的方法。这里采用Makefile。
 
  编写Makefile&:
 
  INCL=-I/usr/local/include
 
  CFLAGS=-pipe-O2-fno-strength-reduce
 
  LFLAGS=/usr/local/lib/libjrtp.a-L/usr/X11R6/lib
 
  LIBS=-LX11-LXext/usr/local/lib/libjrtp.a
 
  CC=arm-linux-g++
 
  main:main.o
 
  $(CC)$(LFLAGS)$(INCL)-o main main.o$(LIBS)
 
  main.o:main.cpp
 
  clean:
 
  rm-f main
 
  rm-f*.o
 
  .SUFFIXES:.cpp
 
  .cpp.o:
 
  $(CC)-c$(CFLAGS)$(INCL)-o$ $</*$ 表示目标的完整名字*/
 
  /*$<表示第一个依赖文件的名字*/
 
  b、编译
 
  假设发送和接收程序分别放在/tmp/send和/tmp/receive目录下
 
  #cd/tmp/send
 
  #make
 
  #cd/tmp/receive
 
  #make
 
  七、易出错误及注意问题
 
  1、找不到一些标准的最基本的一些头文件。
 
  主要是因为Toolchain路径没安装对,要严格按照步骤安装。
 
  2、找不到使用的jrtplib库中的一些头文件。
 
  在jrtplib的安装目录下,include路径下不能再有别的目录。
 
  3、recieve函数接收数据包不能正确提出所要数据。
 
  由于每一个RTP数据报都由头部(Header)和负载(Payload)两个部分组成,若使用getrawdata()
 
  是返回整个数据包的数据,包含传输媒体的类型、格式、序列号、时间戳以及是否有附加数
 
  据等信息。getpayload()函数是返回所发送的数据。两者一定要分清。
 
  4、设置RECEIVEMODE_ACCEPTSOME接收模式后,运行程序接收端不能接包。
 
  IP地址格式出了问题。iner_addr()与ntohl()函数要用对,否则参数传不进去,接受列表中无
 
  值,当然接收不了数据包。
 
  5、编译通过,但测试时接收端不能接收到数据。
 
  可能是接收机防火墙未关闭。运行:
 
  #iptables-F
 
  也可能是IP地址没有设置好。运行:
 
  #ifocnfig eth0*.*.*.*netmask*.*.*.*
 
  6、使用jrtolib库时,在程序中include后最好加上库所在的路径。
 
  八、程序
 
  send:
 
  #include<stdio.h>
 
  #include<string.h>
 
  #include"rtpsession.h"
 
  //错误处理函数
 
  void checkerror(int err)
 
  {
 
  if(err<0){
 
  char*errstr=RTPGetErrorString(err);
 
  printf("Error:%s\\n",errstr);
 
  exit(-1);
 
  }
 
  }
 
  int main(int argc,char**argv)
 
  {
 
  RTPSession sess;
 
  unsigned long destip;
 
  int destport;
 
  int portbase=6000;
 
  int status,index;
 
  char buffer[128];
 
  if(argc!=3){
 
  printf("Usage:./sender destip destport\\n");
 
  return-1;
 
  }
 
  //获得接收端的IP地址和端口号
 
  destip=inet_addr(argv[1]);
 
  if(destip==INADDR_NONE){
 
  printf("Bad IP address specified.\\n");
 
  return-1;
 
  }
 
  destip=ntohl(destip);
 
  destport=atoi(argv[2]);
 
  //创建RTP会话
 
  status=sess.Create(portbase);
 
  checkerror(status);
 
  //指定RTP数据接收端
 
  status=sess.AddDestination(destip,destport);
 
  checkerror(status);
 
  //设置RTP会话默认参数
 
  sess.SetDefaultPayloadType(0);
 
  sess.SetDefaultMark(false);
 
  sess.SetDefaultTimeStampIncrement(10);
 
  //发送流媒体数据
 
  index=1;
 
  do{
 
  sprintf(buffer,"%d:RTP packet",index++);
 
  sess.SendPacket(buffer,strlen(buffer));
 
  printf("Send packet!\\n");
 
  }while(1);
 
  return 0;
 
  }
 
  receive:
 
  #include<stdio.h>
 
  #include"rtpsession.h"
 
  #include"rtppacket.h"
 
  //错误处理函数
 
  void checkerror(int err)
 
  {
 
  if(err<0){
 
  char*errstr=RTPGetErrorString(err);
 
  printf("Error:%s\\n",errstr);
 
  exit(-1);
 
  }
 
  }
 
  int main(int argc,char**argv)
 
  {
 
  RTPSession sess;
 
  int localport,portbase;
 
  int status;
 
  unsigned long remoteIP;
 
  if(argc!=4){
 
  printf("Usage:./sender localport\\n");
 
  return-1;
 
  }
 
  //获得用户指定的端口号
 
  remoteIP=inet_addr(argv[1]);
 
  localport=atoi(argv[2]);
 
  portbase=atoi(argv[3]);
 
  //创建RTP会话
 
  status=sess.Create(localport);
 
  checkerror(status);
 
  //RTPHeader*rtphdr;
 
  unsigned long timestamp1;
 
  unsigned char*RawData;
 
  unsigned char temp[30];
 
  int lengh,i;
 
  bool allports=1;
 
  sess.AddToAcceptList(remoteIP,allports,portbase);
 
  do{
 
  //设置接收模式
 
  sess.SetReceiveMode(RECEIVEMODE_ACCEPTSOME);
 
  sess.AddToAcceptList(remoteIP,allports,portbase);
 
  //接受RTP数据
 
  status=sess.PollData();
 
  //检索RTP数据源
 
  if(sess.GotoFirstSourceWithData()){
 
  do{
 
  RTPPacket*packet;
 
  //获取RTP数据报
 
  while((packet=sess.GetNextPacket())!=NULL){
 
  printf("Got packet!\n");
 
  timestamp1=packet->GetTimeStamp();
 
  lengh=packet->GetPayloadLength();
 
  RawData=packet->GetPayload();
 
  for(i=0;i<lengh;i++){
 
  temp<i>=RawData<i>;
 
  printf("%c",temp<i>);
 
  }
 
  temp<i>='\0';
 
  printf("timestamp:%d lengh=%d data:%s\n",timestamp1,lengh,&temp);
 
  //删除RTP数据报
 
  delete packet;
 
  }
 
  }while(sess.GotoNextSourceWithData());
 
  }
 
  }while(1);
 
  return 0;
 
  }
此文关键字:RTP,协议,学习,大,总结,从,原理,到,代码,一,、,