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
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 发送数据的示例程序
- //
- /// COPYRIGHT NOTICE
- // Copyright (c) 2009, 华中科技大学ticktick Group
- /// All rights reserved.
- ///
- /// @file ortpSend.c
- /// @brief ortpSend的测试
- ///
- /// 本文件示例使用ortp库进行rtp数据包的发送
- ///
- /// @version 1.0
- /// @author tickTick
- /// @date 2010/07/07
- /// @E-mail lujun.hust@gmail.com
- ///
- /// 修订说明:创建文件
- //
- #include <ortp/ortp.h>
- #include <signal.h>
- #include <stdlib.h>
- #ifndef _WIN32
- #include <sys/types.h>
- #include <sys/time.h>
- #include <stdio.h>
- #endif
- // 时间戳增量
- #define TIME_STAMP_INC 160
- #define BYTES_PER_COUNT 65535
- // 时间戳
- uint32_t g_user_ts;
- /** 初始化
- *
- * 主要用于对ortp以及其它参数进行初始化
- * @param: char * ipStr 目的端IP地址描述串
- * @param: iint port 目的端RTP监听端口
- * @return: RtpSession * 返回指向RtpSession对象的指针,如果为NULL,则初始化失败
- * @note:
- */
- RtpSession * rtpInit(char * ipStr,int port)
- {
- // Rtp会话对象
- RtpSession *session;
- char *ssrc;
- // 时间戳初始化
- g_user_ts = 0;
- // ortp的一些基本初始化操作
- ortp_init();
- ortp_scheduler_init();
- // 创建新的rtp会话对象
- session=rtp_session_new(RTP_SESSION_SENDONLY);
- rtp_session_set_scheduling_mode(session,1);
- rtp_session_set_blocking_mode(session,1);
- // 设置远程RTP客户端的的IP和监听端口(即本rtp数据包的发送目的地址)
- rtp_session_set_remote_addr(session,ipStr,port);
- // 设置负载类型
- rtp_session_set_payload_type(session,0);
- // 获取同步源标识
- ssrc=getenv("SSRC");
- if (ssrc!=NULL)
- {
- printf("using SSRC=%i.\n",atoi(ssrc));
- rtp_session_set_ssrc(session,atoi(ssrc));
- }
- return session;
- }
- /** 发送rtp数据包
- *
- * 主要用于发送rtp数据包
- * @param: RtpSession *session RTP会话对象的指针
- * @param: const char *buffer 要发送的数据的缓冲区地址
- * @param: int len 要发送的数据长度
- * @return: int 实际发送的数据包数目
- * @note: 如果要发送的数据包长度大于BYTES_PER_COUNT,本函数内部会进行分包处理
- */
- int rtpSend(RtpSession *session,const char *buffer, int len)
- {
- int curOffset = 0;
- int sendBytes = 0;
- int clockslide=500;
- // 发送包的个数
- int sendCount = 0;
- ortp_message("send data len %i\n ",len);
- // 是否全部发送完毕
- while(curOffset < len )
- {
- // 如果需要发送的数据长度小于等于阙值,则直接发送
- if( len <= BYTES_PER_COUNT )
- {
- sendBytes = len;
- }
- else
- {
- // 如果当前偏移 + 阈值 小于等于 总长度,则发送阈值大小的数据
- if( curOffset + BYTES_PER_COUNT <= len )
- {
- sendBytes = BYTES_PER_COUNT;
- }
- // 否则就发送剩余长度的数据
- else
- {
- sendBytes = len - curOffset;
- }
- }
- ortp_message("send data bytes %i\n ",sendBytes);
- rtp_session_send_with_ts(session,(char *)(buffer+curOffset),sendBytes,g_user_ts);
- // 累加
- sendCount ++;
- curOffset += sendBytes;
- g_user_ts += TIME_STAMP_INC;
- // 发送一定数据包后休眠一会
- if (sendCount%10==0)
- {
- usleep(20000);
- }
- }
- return 0;
- }
- /** 结束ortp的发送,释放资源
- *
- * @param: RtpSession *session RTP会话对象的指针
- * @return: 0表示成功
- * @note:
- */
- int rtpExit(RtpSession *session)
- {
- g_user_ts = 0;
- rtp_session_destroy(session);
- ortp_exit();
- ortp_global_stats_display();
- return 0;
- }
- // 主函数,进行测试
- int main()
- {
- // 待发送的数据缓冲区
- char * pBuffer = "123445356234134234532523654323413453425236244123425234";
- RtpSession * pRtpSession = NULL;
- // 向(192.201.0.51,8000)目的地址发送rtp包
- pRtpSession = rtpInit("192.201.0.51",8000);
- if(pRtpSession==NULL)
- {
- printf("error rtpInit");
- return 0;
- }
- // 循环发送
- while(1)
- {
- if( rtpSend(pRtpSession,pBuffer,20) != 0)
- {
- printf("error rtpInit");
- break;
- }
- usleep(10000);
- printf("sleep");
- }
- // 退出
- rtpExit(pRtpSession);
- return 0;
- }
//=========================================================================================================//
自己实现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, ×tamp, 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