http://blog.csdn.net/flyhawk007j2me/article/details/6830354

RTP(Real-time transport Protocol)用于在网路上多媒体数据流的传输协议

做流媒体传输方面的应用一般都会用基于RTP的JRTPLIB库等;

ORTP是一个支持RTP以及RFC3550协议的库,使用C语言编写,可用于Windows、Linux以及类Unix平台

且支持单线程的多个RTP回话,支持自适应抖动处理。

//======================================================================================

ORTP可以在www.linphone.org开源项目中下载,

帮助文档在线查看http://download.savannah.gnu.org/releases/linphone/ortp/docs/

示例代码 rtprecv.c和rtpsend.c   展示了如何接受和发送rtp数据流;

mrtprecv.c和mrtpsend.c展示了如何同时接受和发送多个RTP数据流。

//========================================================================================

ortp_init 
【原型】:void ortp_init( void)
【功能】:初始化ORTP库,在使用ORTP API前需要首先调用本函数。

ortp_exit  
【原型】:void ortp_exit ( void)
【功能】:结束ORTP的使用。

rtp_session_new
【原型】:RtpSession* rtp_session_new (int mode)
【功能】:RtpSession为RTP会话的结构体对象,本函数创建一个新的RTP会话对象。
【参数】:mode为RTP_SESSION_SENDONLY(只发送)或者 RTP_SESSION_RECVONLY (只接收)、RTP_SESSION_SENDRECV(既发送也接收)等
【返回值】:指向新创建的RTP会话对象的指针。

rtp_session_init

函数原型:void rtp_session_init (RtpSession * session, int mode)
函数功能:执行rtp会话的一些必要的初始化工作
参数含义:
session: rtp会话结构体,含有一些rtp会话的基本信息
mode:传输模式,有以下几种,决定本会话的一些特性。
RTP_SESSION_RECVONLY:只进行rtp数据的接收
RTP_SESSION_SENDONLY:只进行rtp数据的发送
RTP_SESSION_SENDRECV:可以进行rtp数据的接收和发送
执行的操作:
1.       设置rtp包缓冲队列的最大长度
2.       根据传输模式设置标志变量的值
3.       随机产生SSRC和同步源描述信息
4.       传入全局的av_profile,即使用默认的profile配置
5.       初始化rtp包缓冲区队列
6.       发送负载类型默认设置为0(pcmu音频),接收负载类型默认设置为-1(未定义)
7.       将session的其他成员的值均设置一个默认值。
rtp_session_set_scheduling_mode
函数原型:void rtp_session_set_scheduling_mode (RtpSession * session, int yesno)
函数功能: RtpScheduler管理多个session的调度和收发的控制,本函数设置是否使用该session调度管理功能。
参数含义:
session: rtp会话结构体
yesno:   是否使用rtp session的系统调度功能
说明:
如果yesno为1,则表明使用系统的session调度管理功能,意味着可以使用以下功能:
1. 可以使用session_set_select在多个rtp会话之间进行选择,根据时间戳判定某个会话是否到达了收发的时间。
2. 可以使用rtp_session_set_blocking_mode()设置是否使用阻塞模式来进行rtp包的发送和接收。
如果yesno为0,则表明该会话不受系统管理和调度。
关于rtp session的管理和调度,由全局的变量RtpScheduler *__ortp_scheduler来负责,该变量必须通过ortp_scheduler_init() 来进行初始化操作。
rtp_session_set_blocking_mode
函数原型:void rtp_session_set_blocking_mode (RtpSession * session, int yesno)
函数功能:设置是否使用阻塞模式,
参数含义:
session: rtp会话结构体
yesno:  是否使用阻塞模式
说明:
阻塞模式只有在scheduling mode被开启的情况下才能使用,本函数决定了rtp_session_recv_with_ts() 和 rtp_session_send_with_ts()两个函数的行为,如果启用了阻塞模式,则rtp_session_recv_with_ts()会一直阻塞直到接收RTP包的时间点到达(这个时间点由该函数参数中所定义的时间戳来决定),当接收完RTP数据包后,该函数才会返回。同样,rtp_session_send_with_ts()也会一直阻塞直到需要被发送的RTP包的时间点到达,发送结束后,函数才返回。
rtp_session_signal_connect
函数原型:int rtp_session_signal_connect (RtpSession * session, const char *signal,   RtpCallback cb, unsigned long user_data)
函数功能:本函数提供一种方式,用于通知应用程序各种可能发生的RTP事件(信号)。可能通过注册回调函数的形式来实现本功能。
参数含义:
session: rtp会话结构体
signal: 信号的名称
cb:     回调函数
user_data:传递给回调函数的数据
返回值:0表示成功,-EOPNOTSUPP表示信号名称不存在,-1表示回调函数绑定错误
说明:
信号的名称必须是以下字符串中的一种:
"ssrc_changed" : 数据流的同步源标识改变
"payload_type_changed" : 数据流的负载类型改变
"telephone-event_packet" : telephone-event RTP包(RFC2833)被接收
"telephone-event" : telephone event 发生
"network_error" : 网络错误产生,传递给回调函数的是描述错误的字符串(const char *型)或者错误码(int型)
"timestamp_jump" : 接收到的数据包发生了时间戳的跳跃。
要取消事件(信号)的监听,可以使用下面这个函数
int rtp_session_signal_disconnect_by_callback ( RtpSession * session, const char * signal_name, RtpCallback cb )
rtp_session_set_local_addr
函数原型:int rtp_session_set_local_addr( RtpSession * session, const char * addr,int port)
函数功能:设置本地rtp数据监听地址
参数含义:
session: rtp会话结构体
addr: 本地IP地址,例如127.0.0.1,如果为NULL,则系统分配0.0.0.0
port:   监听端口,如果设置为-1,则系统为其自动分配端口
返回值: 0表示成功
说明:
如果是RTP_SESSION_SENDONLY(只发送)型会话,则不需要进行本设置,而必须设置rtp_session_set_remote_addr() 来设置远程目的地址。
如果采用了系统自动分配监听端口,则可以通过int rtp_session_get_local_port(const RtpSession *session) 来获取系统分配的监听端口号。
rtp_session_set_remote_addr
函数原型:int rtp_session_set_remote_addr (RtpSession * session, const char * addr, int port)
函数功能:设置RTP发送的目的地址
参数含义:
session: rtp会话结构体
addr: 目的IP地址
port:   目的地址的监听端口号
返回值: 0表示成功
rtp_session_set_send_payload_type
函数原型:int rtp_session_set_send_payload_type (RtpSession * session, int paytype)
函数功能:设置RTP发送数据的负载类型
参数含义:
session: rtp会话结构体
paytype:负载类型
返回值: 0表示成功,-1表示负载未定义
说明:
负载类型在payloadtype.h文件中有详细的定义,RTP接收端有着类似的负载类型设置函数,int rtp_session_set_recv_payload_type ( RtpSession * session, int paytype ) ,注意,发送的负载类型必须与接收的负载类型一致才能正常完成收发。
rtp_session_send_with_ts
函数原型:int rtp_session_send_with_ts (RtpSession * session, const char * buffer, int len,uint32_t userts)
函数功能:发送RTP数据包
参数含义:
session: rtp会话结构体
buffer: 需要发送的RTP数据的缓冲区
len:    需要发送的RTP数据的长度
userts: 本RTP数据包的时间戳
返回值: 成功发送到网络中的字节数
说明:
发送RTP数据需要自己管理时间戳的递增,每调用一次本函数,请根据实际情况对userts进行递增,具体递增的规则见RTP协议中的说明。
例如:如果发送的是采样率为90000Hz的视频数据包,每秒25帧,则时间戳的增量为:90000/25 = 3600
时间戳的起始值为随机值,建议设置为0 。
rtp_session_recv_with_ts
函数原型:int rtp_session_recv_with_ts (RtpSession * session, char * buffer,int len, uint32_t time, int * have_more)
函数功能:接收RTP数据包
参数含义:
session: rtp会话结构体
buffer: 存放接收的RTP数据的缓冲区
len:    期望接收的RTP数据的长度
time:期望接收的RTP数据的时间戳
have_more:标识接收缓冲区是否还有数据没有传递完。当用户给出的缓冲区不够大时,为了标识缓冲区数据未取完,则have_more指向的数据为1,期望用户以同样的时间戳再次调用本函数;否则为0,标识取完。

rtp_session_destroy
【原型】: void rtp_session_destroy(RtpSession *session)
【功能】:摧毁rtp会话对象,释放资源
【参数】:session已经创建的RTP会话对象

rtp_session_get_recv_payload_type
【原型】:int rtp_session_get_recv_payload_type(const RtpSession *session)  
【功能】:获取当前的rtp数据流的媒体类型
【返回值】: 媒体类型(整数)

ortp 发送数据的示例程序

  1. //
  2. /// COPYRIGHT NOTICE
  3. // Copyright (c) 2009, 华中科技大学ticktick Group
  4. /// All rights reserved.
  5. ///
  6. /// @file    ortpSend.c
  7. /// @brief   ortpSend的测试
  8. ///
  9. /// 本文件示例使用ortp库进行rtp数据包的发送
  10. ///
  11. /// @version 1.0
  12. /// @author  tickTick
  13. /// @date    2010/07/07
  14. /// @E-mail  lujun.hust@gmail.com
  15. ///
  16. /// 修订说明:创建文件
  17. //
  18. #include <ortp/ortp.h>
  19. #include <signal.h>
  20. #include <stdlib.h>
  21. #ifndef _WIN32
  22. #include <sys/types.h>
  23. #include <sys/time.h>
  24. #include <stdio.h>
  25. #endif
  26. // 时间戳增量
  27. #define TIME_STAMP_INC  160
  28. #define BYTES_PER_COUNT 65535
  1. // 时间戳
  2. uint32_t g_user_ts;
  3. /**  初始化
  4. *
  5. *   主要用于对ortp以及其它参数进行初始化
  6. *   @param:  char * ipStr 目的端IP地址描述串
  7. *   @param:  iint port 目的端RTP监听端口
  8. *   @return:  RtpSession * 返回指向RtpSession对象的指针,如果为NULL,则初始化失败
  9. *   @note:
  10. */
  11. RtpSession * rtpInit(char * ipStr,int port)
  12. {
  13. // Rtp会话对象
  14. RtpSession *session;
  15. char *ssrc;
  16. // 时间戳初始化
  17. g_user_ts = 0;
  18. // ortp的一些基本初始化操作
  19. ortp_init();
  20. ortp_scheduler_init();
  21. // 创建新的rtp会话对象
  22. session=rtp_session_new(RTP_SESSION_SENDONLY);
  23. rtp_session_set_scheduling_mode(session,1);
  24. rtp_session_set_blocking_mode(session,1);
  25. // 设置远程RTP客户端的的IP和监听端口(即本rtp数据包的发送目的地址)
  26. rtp_session_set_remote_addr(session,ipStr,port);
  27. // 设置负载类型
  28. rtp_session_set_payload_type(session,0);
  29. // 获取同步源标识
  30. ssrc=getenv("SSRC");
  31. if (ssrc!=NULL)
  32. {
  33. printf("using SSRC=%i.\n",atoi(ssrc));
  34. rtp_session_set_ssrc(session,atoi(ssrc));
  35. }
  36. return session;
  37. }
  38. /**  发送rtp数据包
  39. *
  40. *   主要用于发送rtp数据包
  41. *   @param:  RtpSession *session RTP会话对象的指针
  42. *   @param:  const char *buffer 要发送的数据的缓冲区地址
  43. *   @param: int len 要发送的数据长度
  44. *   @return:  int 实际发送的数据包数目
  45. *   @note:     如果要发送的数据包长度大于BYTES_PER_COUNT,本函数内部会进行分包处理
  46. */
  47. int rtpSend(RtpSession *session,const char *buffer, int len)
  48. {
  49. int curOffset = 0;
  50. int sendBytes = 0;
  51. int clockslide=500;
  52. // 发送包的个数
  53. int sendCount = 0;
  54. ortp_message("send data len %i\n ",len);
  55. // 是否全部发送完毕
  56. while(curOffset < len )
  57. {
  58. // 如果需要发送的数据长度小于等于阙值,则直接发送
  59. if( len <= BYTES_PER_COUNT )
  60. {
  61. sendBytes = len;
  62. }
  63. else
  64. {
  65. // 如果当前偏移 + 阈值 小于等于 总长度,则发送阈值大小的数据
  66. if( curOffset + BYTES_PER_COUNT <= len )
  67. {
  68. sendBytes = BYTES_PER_COUNT;
  69. }
  70. // 否则就发送剩余长度的数据
  71. else
  72. {
  73. sendBytes = len - curOffset;
  74. }
  75. }
  76. ortp_message("send data bytes %i\n ",sendBytes);
  77. rtp_session_send_with_ts(session,(char *)(buffer+curOffset),sendBytes,g_user_ts);
  78. // 累加
  79. sendCount ++;
  80. curOffset += sendBytes;
  81. g_user_ts += TIME_STAMP_INC;
  82. // 发送一定数据包后休眠一会
  83. if (sendCount%10==0)
  84. {
  85. usleep(20000);
  86. }
  87. }
  88. return 0;
  89. }
  90. /**  结束ortp的发送,释放资源
  91. *
  92. *   @param:  RtpSession *session RTP会话对象的指针
  93. *   @return:  0表示成功
  94. *   @note:
  95. */
  96. int rtpExit(RtpSession *session)
  97. {
  98. g_user_ts = 0;
  99. rtp_session_destroy(session);
  100. ortp_exit();
  101. ortp_global_stats_display();
  102. return 0;
  103. }
  104. // 主函数,进行测试
  105. int main()
  106. {
  107. // 待发送的数据缓冲区
  108. char * pBuffer = "123445356234134234532523654323413453425236244123425234";
  109. RtpSession * pRtpSession = NULL;
  110. // 向(192.201.0.51,8000)目的地址发送rtp包
  111. pRtpSession = rtpInit("192.201.0.51",8000);
  112. if(pRtpSession==NULL)
  113. {
  114. printf("error rtpInit");
  115. return 0;
  116. }
  117. // 循环发送
  118. while(1)
  119. {
  120. if( rtpSend(pRtpSession,pBuffer,20) != 0)
  121. {
  122. printf("error rtpInit");
  123. break;
  124. }
  125. usleep(10000);
  126. printf("sleep");
  127. }
  128. // 退出
  129. rtpExit(pRtpSession);
  130. return 0;
  131. }

//=========================================================================================================//

自己实现rtp还是比较简单的。分为接收和发送,接收的时候直接去掉12个字节的报头,然后向下送。而发送的时候稍微麻烦点,我的实现手法如下:

初始化报头:
      char rtppacket[172] = {0x80, 0x08};//PCMA 0x08 PCMU 0x00
      srand((int)time(0));
      unsigned short seq = 0, tmp = 0;
      unsigned int timestamp = htonl(0);
      unsigned int ssrc = htonl(rand());
 
      memcpy(rtppacket+ 4, &timestamp, 4);
      memcpy(rtppacket+ 8, &ssrc, 4);

发送报文(在一个循环中):
     memcpy(rtppacket+ 2, &tmp, 2);
     seq++;
     tmp = htons(seq);
     memcpy(rtppacket+ 12, &voicedata, 160);
     直接UDP send报文。

在这里面,timestamp是我全部赋值为0的,并且这个实现是只能承载G711A。但是在ZTE的网关上运行正常。

//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------//

效果是基本实现了,但是又来了一个疑问,既然这么简单就可以实现rtp的功能,那么怎么会需要100多页的RFC3550,又何必需要ortp那么多的代码呢?

首先是多种RTP格式支持
 ortp有一个RtpProfile的全局变量,该变量在ortp初始化的时候赋值,该变量中有一个PayloadType的数组,每个PayloadType对应一种媒体流,

PayloadType中包含了媒体流的特性,包括名称,采样率,声音位数,静音格式,占用带宽等。这些定义都在avprofile.c中。
 其中的几个参数中,采样率用的比较多,主要用来计算实时性,还有就是计算jitter。

  • 发送接收的实时调度

ortp中发送和接收主要是两个函数rtp_session_send_with_ts和rtp_session_recv_with_ts。

以rtp_session_recv_with_ts为例:内部接收数据使用的是rtp_session_recvm_with_ts,

首先,会接收所有scoket上的数据,然后将rtp包存放在一个队列之中,一系列处理之后,有一个pthread_mutex_lock的线程锁,将线程锁住。

此时,由rtp_scheduler_schedule线程进行调度(该线程在协议栈初始化)时创建。

rtp_scheduler_schedule会遍历所有的media session(媒体流),然后判断其中的timestamp(时间戳),如果计算的时间到达,则让rtp_session_recvm_with_ts继续处理。

时间戳的算法是以第一个打到的rtp数据包为准,然后根据其中的时间,进行推算。假如第一个包是10点整来的,然后ptime又是20ms,那么下一个包的时间就是10点又20毫秒。

media session是一个RtpSession对象,包含多种属性和方法。RtpScheduler中包含一个RtpSession的队列,用来支持多媒体流。
     值得一提的是,rtp_scheduler_schedule中有一个独特的"sleep",该sleep可以停顿10ms。

并且这个时间是绝对的,如果中间因为处理或者其他原因延迟了2ms,那么这个sleep停顿的就是8ms。

具体函数可以看一下posixtimer.c中的posix_timer_do实现。

精确的计时使用select,精确时间的取得很多使用gettimeofday这个函数,该函数在精确计时的时候非常有用。

单线程支持多路媒体流
      对外是有一个sessionset,完全模拟了select的做法,对外提供的接口,也和标准select几乎一样。主要的处理实现,还是在rtp_scheduler里面完成。模拟select的唤醒,使用了pthread_cond_wait。

 自适应的缓冲区算法
      主要的实现都在jitterctl.c里面,也不算很复杂,没有太仔细看。从注释中看到算法如下:
      The algorithm computes two values:
      slide: an average of difference between the expected and the socket-received timestamp
      jitter: an average of the absolute value of the difference between socket-received timestamp and slide.
      slide is used to make clock-slide detection and correction.

RTCP的实现

rtcp是不算很复杂的东西。ortp中,在RtpSession里面,即包含了rtcp的一些相关属性。需要的时候,直接取出然后组包发送就可以了。不过还要涉及到SDES,东西倒也是不少。

后记
      大概读了一下ortp,一共大约14K的代码量,确实算是一款优秀的作品,作者对于线程,实时性的一些处理,都相当的深入。对于数据结构的组织,也非常不错,大部分数据,均适用了自己的封装。采用了链表的结构,声明在str_utils.h里面,这些数据结构的具体使用,就没有太仔细看了。

读完以后,再回头看看自己的项目,确实没有必要用ortp这样的“庞然大物”了:多种rtp格式?我只需要G711A;实时性?我有底层硬件保证了;多路媒体流?不需要,我只要VOIP语音;自适应的jitter?也不用,底层硬件有保证;至于RTCP,不是必须项,也可以不要。在单用途的终端上,协议栈的实现可以简化非常多。

最后感叹一下,老外的计算机水平确实比国内高出很多。我看的这份ortp的实现,设计,实现几近炉火纯青,方方面面考虑周详仔细,并且实现了Linux平台和Windows平台的通用。显示在程序结构,数据组织,操作系统,网络协议方面不凡的功力。国内程序员,写程序很多是为了混口饭,不会去精益求精,能实现功能,就已经很不错,是老板眼中的红人。国内写代码要达到ortp这样的艺术水准,还有非常长的路要走。

参考;http://blog.sina.com.cn/s/blog_6a9032c10100lyi1.html

 

转载于:https://www.cnblogs.com/pengyingh/articles/2474139.html

ortp流媒体协议 [1]相关推荐

  1. 基于HLS流媒体协议的视频加密方案

    本文只讨论应用于浏览器环境的流媒体协议的加密. 背景 付费观看视频的模式是很多平台的核心业务,如果视频被录制并非法传播,付费业务将受到严重威胁.因此对视频服务进行加密的技术变得尤为重要. 本文所指的视 ...

  2. 三种主流流媒体协议比较

    一.介绍 在流媒体协议中,常用的流媒体协议主要有 HTTP协议,RTSP协议和RTMP协议. 在国内视频网站多采用HTTP+MP4或者HTTP+FLV协议传输视频,而国外使用RTMP,RTSP等专门的 ...

  3. 智能会议系统(9)---流媒体协议学习

    流媒体协议学习 一.协议篇 1. rtsp协议详解(http://www.mikewootc.com/wiki/net/protocol/rtsp.html) 2. rtp与rtcp协议详解(http ...

  4. 【流媒体|从入门到出家】:流媒体协议—HTTP

    第一篇章  流媒体原理 1.1    流媒体概念 1.2    流式传输特点 1.3    流媒体系统构成 1.4    流媒体涉及技术 1.5    流媒体应用 1.6    国内外大型流媒体系统 ...

  5. 【音视频零基础入门 1】视频播放器原理、流媒体协议、封装格式、视频编码、音频编码

    [音视频零基础入门 1]视频播放器原理.流媒体协议.封装格式.视频编码 一.视频播放器原理 1.1 解协议 1.2 解封装 1.3 解码 1.4 视音频同步 二.流媒体协议 三.封装格式 四.封装格式 ...

  6. RTMPLive多流媒体协议转无插件直播协议(RTMP)解决方案

    随着互联网以及科技水平的发展,对于互联网服务,PC不再是唯一选择,智能手机.平板电脑.特定的移动终端等都是可选择的用户终端硬件方式,因此,我们需要一款能将安防协议,电视广播协议以及其他各种格式的流媒体 ...

  7. SkeyeRTMPLive多流媒体协议转无插件直播协议(RTMP)解决方案

    随着互联网以及科技水平的发展,对于互联网服务,PC不再是唯一选择,智能手机.平板电脑.特定的移动终端等都是可选择的用户终端硬件方式,因此,我们需要一款能将安防协议,电视广播协议以及其他各种格式的流媒体 ...

  8. 流媒体协议之RTMP详解

    流媒体协议之RTMP详解 文章目录 流媒体协议之RTMP详解 1 RTMP概述 2 RTMP交互过程 2.1 握手协议 2.2 RTMP分块(chunk) 2.3 协议控制消息(Protocol Co ...

  9. 秒懂流媒体协议 RTMP 与 RTSP

    你好,这里是网络技术联盟站. RTMP 与 RTSP 是比较常见的两种流媒体协议,那么什么是RTMP?什么是RTSP?它们两之间有什么区别?使用的时候应该如何选择? 今天瑞哥就用人话好好和大家聊聊,如 ...

最新文章

  1. Spring Boot 多版本更新,紧急修复 RFD 安全漏洞
  2. python 面向对象(三大特性)
  3. 爬虫数据采集技术趋势-智能化解析
  4. Delphi 对象的创建(create)与释放(free/destory)
  5. squid代理服务器(捎带的SNAT)
  6. 精通android学习笔记(一)---广播
  7. css3--弹性布局
  8. 【转】Qt调用dll中的功能函数
  9. 我可以在输入字段上使用:before或:after伪元素吗?
  10. 简单的print函数的实现
  11. 如何在MAC上使用VOSviewer和Pajek
  12. kettle-spoon安装
  13. 基金投资入门1:基金基础知识介绍
  14. FPS游戏:D3D实现游戏人物上色
  15. jadx动态调试安卓apk
  16. 关于uni-app中SegmentedControl 分段器不显示的问题
  17. Dynamics CRM 向视图列添加自定义图标和提示信息
  18. 关闭苹果无线服务器,苹果iOS11 WiFi、蓝牙无法关闭怎么回事?附彻底关闭方法...
  19. 海湾gst5000主机消防广播_海湾GST5000消防主机常见问题及解决!
  20. ios平台触动精灵lua调用C语言

热门文章

  1. Codeforces Round #313 (Div. 1) A. Gerald's Hexagon 数学题
  2. centos安装python gcc sqlite
  3. 诗与远方:无题(八十九)
  4. tar.gz还原mysql_阿里云Mysql备份数据还原到本地流程
  5. Nodejs学习笔记(一)——基础之全局对象、包和npm
  6. java long常量池_Java提高篇之常量池
  7. 史上最全ClassLoader总结
  8. JVM结构与OOM问题分析
  9. 用得最多的altium版本_83版《神雕》有多猛?收视率破90%!金庸最满意的版本...
  10. github get 请求指定页面的代码