oRTP分析
一:关于oRTP.................................................................................................................................2
二:源代码的构建框架...................................................................................................................2
三:有关时间戳的说明.................东了
结构体外,一个更重要的结构体是
。该结构体即是一个会话
的抽象,与会话相关的各种信息都定义在该结构体上或者能够通过该结构体找到。要使用
逑行娸体数据的传输,需要先创建一个会话,之后所有数据的传输都在会话上完成或
基于会话完成。
结构体的定义如下:
Rtpsession
ext
mask pos
inc ssrc candidate
inc same ssrc count
Iw recv pL
cha
on pay load tyre changed
Telep
on network error
signa
events
allocator
tp
Sessiorlisu
lticast ttl
multicast loopback
data
Lng
current tev
d
contribut. ing sources
use connect
ssrc set
图
可以看到,这是一个非常大的结构体,从侧面说明了要维护的与会话相关的量还是比较多的
关于该结构体的比较详细的说明会在后面给出。
的初始化通过接口
完成,外部获得一个新的
是通过调用接
完成。关」
的其他有关配置和获取信息的操作都叮以在文件
中找到定义
使用进行数据传输时,可以在一个任务上完成多个会话流的接收和发送。这得益
中调度模块的支持。要使用调度模块,应用需要在进行的初始化时对调度进
行初始化,将需要调度管理的会话注册到调度模块中,这样当进行接收和发送操作时,先向
调度询问当前会话是否可以进行发送和接收,如果不能进行收发操作,则处理下一个会话。
这有点类似接口上的操作。调度模块使用的数据结构主要为
,如下图
所示:
Reschedule
list
all sessions
r sessions
r max
w sessions
Wmaⅹ
unblock select con
thread
thread running
timer
RtpTimer
t⊥me
state
timer inc
timer inl t
LIe I
timer unini t
interval
图
保存了所有要处理的会话,的意义类似于,在这里分别代表接收、发送以及
异常
等文件实现了调度模块。
数据在底层实际的接收和发送是通过接凵完成的,这些实现在
文
件中
为了方便将移植到不同平台上,实现了对操作系统接口的封装,包括常用的
仼务的创建及销毁,条件变量及互斥锁,进程间的管道通信机制等。这些在文件中实
现
狳了操作系统相关的接口外,为了便于内部操作,实现了部分数据结构,一个是双
向链表,在文件中;一个是队列,在文件
文件中。链表的实现比较简单,
队列的实现相对较复杂一点。首先,队列数据结构由三部分组成:队列头、消息块以及数据
块,图示如下:
g stopper
gb
g count
b prev
b next
b cont
datat
datab
b rptr
reserved
db freeIn
reserved
dh ref
Buffer
图
上图中从左到右依次为队列头,消息块和数据块ε队列头指向消息块,消息块之间可以构成
双向链衣,这是队列的基本要素。消息块本身不带,数据是由专门的数据块来保存的,
并被消息块指向。上图是一个初始化后的状态,消息块的读写指针都指向数据块的
的开始位置。数据块的和指针则分别指向
空间的开始地址和结束地址处。向
中写入和读出数据后的状态变化如下图:
b pre
b next
b next
b cont
b con
b datap
b rptr
db base
b rptr
db base
b wptr
db lim
b wpt
db lim
db freen
db freef
reserve
db ref
reserved
db ref
Buffer
Buffe
图
除了向队列添加消息块外,上述数据结构设计还支持向个消息块添加新的消息块,这样可
以支持一个消息块保存较人块的数据,如下图所示
b cont
b rptr
dh base
do lim
reserved
db freen
d2
do ref
ms
1
b
datab/ buffer
s
data/huffer
图
消息块的
指针用于连接新的消息块
在发送上层应用的
数据之前,会构造一个消息块,数据指针会指向
这避免了数据拷贝。较低层的接口处理薮据时依赖于消息玦结构。接收后的数据从消息块中
拷贝到用户
。接收的和包的解析处理函数在文件
和
文件
中实现。另外,
文件实现了数据包的构造处坦
在基于的音视频流传输中,防抖动能力是一个重要的特性,这在一定程度上能够保证
用户有良好的体验。在中,是通过模块完成这部分工作的。相关数据结构如下
图所小:
JitterControl
count.
JItt comp ts
adapt jitt comp ts
slide
prev slide
olddiff
corrective step
corrective slide
adapti
enabled
图
要使用功能,需要使能
变量,如果要支持自适应补偿,则需要使能
变
对」数据传输过程中产生的一些事件(比如发生改变,数据为数据等),在
中是通过
(信号表)来处理的。
关联了事件类型与其上的回调函数
使用
处理如下些事件
(发生改变),
发生改变)
包到达),
事件)
事件),
网络错误事件),
以及
包事件)。用户可针对这些事件注册回调处理函数,当底层接收函数
接收到包后,会对包进行检查,发现是上述某些事件的话,则触发冋调函数的执行。
文件实现了对该衣的操作,包括初始化,添加
朋除
以及执行
中对于事件的处理是基于事件结构体和事件队列的。队列用于存放事件结构体,结
构体用于存放事件的数据。相关的处理在文件
中定义。特别的,对」
事件
的处理放在
文件中,其中包括了如何构造用于传输
包,如何将
事件添加到包中,如何发送数据,以及接收到对应数据包后该如
何处理。关于
的构成如下图所示:
db bas
db lim
eeN
b pi
db ref
b next
telephone
b cont
event
b datap
event
b rptr
volume
Ihrtplvent rype
b wptI
reserved
packet
reserved
duration
ep
Buffer
Telephone event
RtpEndpoint
Pay load type
月ddr
addrlen
图
最左边的结构体是包中存放的有关
的数据,通过
指针可以找到
的详细信息。最终放入事件队列的也是指向的内容
在使用提供的厍之前,需要先对其进行初始化,这部分的实现在
文件
的初始化主要调用两个接口
。其中
完成
的注册,
完成了调度任务的初始化
:有关吋间戳的说明
关于传输中时间戳的说明(这部分来自于网络)
时间戳单位:协议中使用的时间戳,其单位不是秒之类的,而是以采样频率为基础
的。这样做的目的就是为了使时间戳单位更为精准。比如说一个吝频的采样频率为
那么我们可以把时间戳单位设为
时间戳增量:相邻两个包之间的时间差(以时间戳单位为基准)。
采样频率:每秒钟抽取样本的次薮,例如音频的采样率一般为
帧率:
每秒传输或者显示帧数,例如
在协议中并没有规定吋间戳的粒度,这取决于有效载付的类型。因此的时间戳
又称为媒体时间戳,以强调这种时间戳的粒度取决于信号的类型。例如,对于采样的
话音信号,若每隔
构成一个数据块,则一个数据块中包含有个样本(
)。因此每发送一个分组,其时间戳的值就增加。
如果采样频率为
则由上面讨论可知,时间戳单位为
,我们就假设
钟被划分了个时间块,如果每秒发送帧,那么,每·个帧的发送占多少个时间块
呢?当然是
。因此,我们根据定义“时间戳增量是发送第二个包相距
发送第一个包时的时间问隔”,故时间戳堦量应该为
关」中时间戳的计算问题:从年到现在的经过的秒数赋值给时间
戳的高位,这个时间的低位通过当前获取的纳秒时间值计算得到。将秒划分为
的次方来衣示,则份了持续的时间大约位皮秒。如果当前时间为秒毫秒,
则毫秒为
微妙
纳秒,
皮秒,即
多个
皮秒。也就是说在时间戳的低位划分的的次方个皮秒块中占用了
个块,转换为进制表示为
,也就是说当当前时间的低位为毫秒的
话
时间戳的低位就设置为
在系统中,我们常用的一个时间是年月日以米的时间所经过的秒数。在
中,我们可以将当前所获得的上述时间加上
(十六进制)就是年
月日以来所经过的秒数了。换为十进制,则为
计算方法为(
代码中有关时间戳变量的说明
在数的接收和发送过程中,用到了许多记录时间的变量。通过这些时间变量,
完成对数据的流控功能。所有这些交量都定义在
结构体中,如下图所示:(这
里只是截取了时间相关的变量)
Upstream
snd ts offset
snd rand offset
snd last t
rey time offset
ts_offset
rev query ts offset
rcv last app ts
rcy last ret ts
hwrcv extseq
nwrcv seg at last SR
hwrcy since last sr
SR t
last rcy sr time
nd sec
last rtcp report snt
last rtcp report snt
last rto
cket
ent payload byte
nt bytes
send bw start
recv bytes
recv_bw_start
ssrc_changed_thres
下面对这些交量的含义进行集中的说明
应用程序发送其第一个时间戳时的调度器时间
被应用程
序发送的第一个应用程序时间戳
添加到用户上的一个随札数,用来产生流的时问戳
流上最后发送的时间戳
前述三个时间变量是结尾的,分别标记了第一个时间戳,包括调度器的时间偏移
在应用开始发送数据时,应用发送数据的时间偏移,也即是自己的时间戳,还有一个随机数
用来添加到偏移上的,而第四个才是真正标记流里面当前最新发送的数据的时间戳。
应用程序询问其第一个时间戳时的调度时间,这里询问意指获取接收到的数据包
此应该指开始接收数据时的调度器时间
第一个流的时间戳此应该指第
个包到来付其流上带的吋间戳值
被应用程
序询问的第一个时间戳此应该指应用接收数据流时的时间
应用程序得到的流
的最后一个时间戳此应该指应用程序收到的最后一个包的时间戳,是包里的时间戳值,
而非应用自己的时间。
被应用
程序询问的最后一个应用程序时间戳此处应该指应用收最后一个包时的应用时间,是应用
按照
类型及其采样牽增长的吋间戳记录,不是系统时间,也不是包里的吋间
最后一个返回的采样的时间戳,仅仅对于连续的音频而言
接收相对」发送来讲存在一个问题,就是接收数据包时当前系统有个时间,数据包里面
也有时间戳记录的时间,调度器也有记录时间。而对于发送,当前应用的时间就是给包的时
间戳吋间,这两个值对于发送来讲是样的。
上最
后接收的扩展的序列号
每次发送报告包后,该变量更新为
因此是
最近发送报告包时的最高扩展序列号
每收到—个包,该变量加,在报告报构造好后
该变量就清为零,因此说明这个变量计数的是从上一个报告包以来接收的包数目。
根据上面三个变量就可以计算出丢包率。首先,最近次丢失包数(就是自从上次
或者发送以来)通过
计算得到。
但是丢包率为啥要除以
比较奇怪。这个值是自从上一次发送报告包以
来累计接收的包数。这个值不应该就是期望接收的包数。(最高序刎号减去最初序列号)
累计包丢失数通过每次的丢包数累加得到。
最后
个接收到的的时间戳,取的是中间的。这个值也是报告包中上值的来
最后一个被
接收到的时间,这个时间是用系统当前的时间来表示的。这个值记录了接收到最后一个
时的系统吋间,再发送当前报告包时,再次获取系统当前时间,然后二者相减,得到的值乘
得到以
为单位的时间值。
发送序列号。累加变量,保存会话的序列号的
增长。
最后一个报告发送的时间,按照接收时间戳单位。程序中这个值是用
变量的佰来更新的。就是应用最后一次进行接收时其时间戳増长到的值
不管收没收到就是这个值了?
最后一个报告发送的时间,按照发送时间戳单位。稈序中这个值是用
变量的值来更新的,就是应用最后一次进行发送操作时其时间戳增长到的值。不管有没
有发送报告包出去?
按照时间戳单位長示的报告发送的间隔。这个值程序中使用默认时间值秒与
的乘积来表示。是不是计算过于简单了?
在最后
发送的一个包中记录的发送者发送的包总数。这个变量把这个值记录了下来。记
录这个值是为了实现协议中规定的:如果之前的包发送之后到当前再次发送包
这期间如果发送了包,则发送
报告包,否则只需发送
包就可以了。
用于发送者报告的
字节数,数据来源。这个变量保存了从开始发送到发送这个报告包时发送的字
节总数,但不包括头部和填充。
上面这些时间相关变量都是用于包的。
用于带宽评佔
同上
上面两个变量用于计算发送带宽,记录的开始时间,
记录了发送的字节数,
该值没调用接∏发送数据后都会进行累加更新。记录次带宽值后,清为零,之后进行
次带宽估计的计算。
同上
同上
作用和处理逻辑都同上面发送部分。
四:调度的实现
要使用
的调度功能,需要在初始化库时调用接口
对调度
模块进行初始化。在该接∏中创建个
类型的结构体
(参见图
),并调用
初始化它。
在
中,分配定时器
类型结构体,参见图)挂
载到调度结构伓上。(定时器初始间隔设置为
)。接着初始化
的其他部分,包括初始化互斥锁、条件变量等。在调度模坎运行的整个过
程中,相关操作都围绕该结构体,
被定义为全局变量。
初始化完后调用
启动调度任务。调度任务的执行体为
参数为调度结构体自身
调度任务执行后,首先初始化。在这过程中将设置为运行状态,保存系统当
前时间值。接着进入仟务的循环,遍历
上注册的所有会话。如果不为空,
说明应用有会话需要调度管理。此吋会调用
进行处理。所有需要调度管
理的会话按上述逻辑处理完之后,广播信号量
唤醒所有因等待
而
睡眠的任务,意即让这些任务去检查自己的会话是否需要进行处理了,这块后续还会说明。
此时调度器完成了自凵当前的工作廾始准备进入睡眠状态,而其他的任务廾始检查掩码结果
以决定是需要进行数据的收发还是等待下次调度
调度的睡眠是通过调用的
接山米完成的,这里就是
接
在该接口中,计算系统当前的时间,并和初始启动的时间(调度器初始化时保存)做差运算,
结果转换为毫秒单位
记录了下一次调度器超时到达的时间,每次就让
减去系统当前时间与启动吋间的差值,如果大于零,说明调度吋间还没有
到达,就调用
等待(
差值)时间,然后重新获取系统当前时间,计
算新的差值。流程图如下:
1