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这样的艺术水准,还有非常长的路要走。
附:ortp版本为0.13.1,下载地址为:http://download.savannah.gnu.org/releases/linphone/ortp/sources/