转自:https://blog.csdn.net/chuanmengfang2285/article/details/101059880
RTP在RFC1889中规定,正式名称是“RTP,一个用于实时应用的传输协议”。这个RFC实际上描述了两个协议:实时传输协议(RTP)和实时传输控制协议(RTCP)。这两个协议提供了可以支持实时应用(例如语音和影像)的网络传输服务。
UDP无法做到避免分组丢失和确保分组有序传输,运行在UDP之上的RTP帮助实现了这些功能。例如,RTP分组包括序列号,这样,使用RTP的应用程序至少能够检测到分组丢失的发生并确保收到的数据以正确的次序提交给用户。RTP分组还包含了一个时间戳,这个时间戳对应于分子从源媒体流进行抽样的时间。目的应用程序可以利用这个时间戳来确保信息同步地传递给目的用户并计算出时延和抖动。
注意:RTP本身并不处理这些事情,它只是提供了一些附加的信息给高层的应用,以便高层应用能够合理地决定数据分组或是语音分组如何能被最好地处理。
正如前面提到的,RTP有一个伴随协议,RTCP。这个协议为会话用户之间提供了大量的可供交换的信息和关于会话质量的反馈信息。信息的类型包括这样的一些细节问题,诸如丢失的RTP分组的数目、时延和到达间隔的抖动。RTP中包含真正的语音分组,而RTCP则是用于质量反馈的传输。
当一个RTP会话打开时,一个RTCP会话也被隐性地打开。换句话说,当一个UDP端口号被分配给一个RTP会话用来传递媒体分组时,一个独立的端口号被分配给RTCP会话。一个RTP端口号一般是偶数,相应的RTCP端口号将是相邻的下一个奇数。RTP和RTCP可以使用介于1025到65535之间的任何UDP端口对;但是在端口号没有被显示分配的情况下,端口5004和5005将分配作为缺省端口。
1、RTP净荷格式
RTP携带有数字编码的语音信息。它通过抽取一个或几个数字编码的语音样本并加上一个RTP报头,就得到了RTP分组,RTP分组由一个RTP报头和语音抽样的净荷构成。这些RTP分组传到UDP层,加上UDP报头。然后再传给IP层,加上IP报头。最后得到的IP数据包就被路由到目的地。在目的地,不同的报头用来将分组沿着协议栈向上传递给相应的应用。
考虑到现有的许多不同的语音和图像编码标准,RTP必须包含一个机制,能够使接收方知道使用哪一种编码标准,这样净荷数据才能够被正确地解释。RTP通过在RTP报头中加入一个净荷类型标识符来实现这个功能。
这个机制为不同的编码模式分配了一个净荷类型号并且描述了它们本身的编码技术。表1和表2列出了各种不同的RTP音频和视频净荷类型。
表1 RTP音频和视频净荷类型(一)
Payload Type | Encoding Name | Media Type | Clock Rate(Hz) | Channels |
0 | PCMU | Audio | 8,000 | 1 |
1 | 1016 | Audio | 8,000 | 1 |
2 | G726-32 | Audio | 8,000 | 1 |
3 | GSM | Audio | 8,000 | 1 |
*4 | G723 | Audio | 8,000 | 1 |
5 | DV14 | Audio | 8,000 | 1 |
6 | DV14 | Audio | 16,000 | 1 |
7 | LPC | Audio | 8,000 | 1 |
8 | PCMA | Audio | 8,000 | 1 |
9 | G722 | Audio | 8,000 | 1 |
10 | L16 | Audio | 44,100 | 2 |
11 | L16 | Audio | 44,100 | 1 |
*12 | QCELP | Audio | 8,000 | 1 |
13 | Reserved | |||
14 | MPA | Audio | 90,000 | 1 |
15 | G728 | Audio | 8,000 | 1 |
*16 | DV14 | Audio | 11,025 | 1 |
*17 | DV14 | Audio | 22,050 | 1 |
*18 | G729 | Audio | 8,000 | 1 |
19 | Reserved | |||
20 | Unassigned | 1 | ||
21 | Unassigned | 1 | ||
22 | Unassigned | 1 | ||
23 | Unassigned | 1 | ||
*dyn | GSM-HR | Audio | 8,000 | 1 |
*dyn | GSM-EFR | Audio | 8,000 | 1 |
*dyn | L8 | Audio | Variable | 1 |
*dyn | RED | Audio | Conditional | 1 |
*dyn | VDVI | Audio | Variable | 1 |
表2 RTP音频和视频净荷类型(二)
Payload Type | Encoding Name | Media Type | Clock Rate(Hz) |
24 | Unassigned | Video | |
25 | CelB | Video | 90,000 |
26 | JPEG | Video | 90,000 |
27 | Unassigned | Video | |
28 | nv | Video | 90,000 |
29 | Unassigned | Video | |
30 | Unassigned | Video | |
31 | H261 | Video | 90,000 |
32 | MPV | Video | 90,000 |
33 | MP2T | Audio/Video | 90,000 |
*34 | H263 | Video | 90,000 |
35-71 | Unassigned | ? | |
72-76 | Reserved | N/A | N/A |
77-95 | Unassigned | ? | |
96-127 | Dynamic | ? | 90,000 |
*Dyn | BT656 | Video | 90,000 |
*Dyn | H263-1998 | Video | 90,000 |
*Dyn | MPIS | Video | 90,000 |
*Dyn | MP2P | Video | 90,000 |
*Dyn | BMPEG | Video | 90,000 |
正如表1和表2看到的那样,一些编码模式有具体的净荷类型号。这些模式成为静态净荷类型。其他的净荷类型标记为“Dyn”,代表的是动态,并不为其分配具体的净荷类型号。因为RTP一开始制定的时候,还没有用于实时应用的单独的信号标准。因此,那时发送应用程序是不可能使接收方应用程序提前知道在会话中将使用哪种类型的编码的。这样某些具体的净荷类型号就被分配一些通用的编码模式。当一个RTP分组到达接收方时,净荷类型号将明确地指出发送方选择的编码模式,这样,接收方就知道应该采取什么的方式对数据进行解码。接收方如果支持这种编码模式就没什么问题,但是,发送方无法提前知道接收方能够处理什么样的编码模式。
于是某些单独的信号系统出来了,可以使媒体信息流的协商成为呼叫建立过程的一部分并在实际发送任何语音或视频数据之前完成。例如会话初始化协议(SIP)和会话描述协议(SDP)支持这样的功能。因此,会话的护嗓防现在可以提前就将要使用的编码模式达成一致。作为协议的一部分,它们还可以决定应用在呼叫的编码模式上的净荷类型号。
RTP音频/视频大纲中将96到127的净荷类型号分配用于动态净荷类型。而且大纲规定中提到的一些新的编码模式并没有固定的(静态)净荷类型,它们被认为是动态的。动态净荷类型很有用。它们可以使许多新的编码模式被开发出来,而不会耗尽可用的净荷类型号的空间(最大为127)。
注意:编码的名字也是很重要的。当在RTP中使用一种新的编码模式时,特别是如果编码模式的净荷类型是动态的,那么编码的名字就必须在一个独立的净荷规定文件中规定并在IANA登记。这是因为外部信号系统*(比如说SDP)使用编码名,可以明确地引用一个特定的净荷规定。
在动态净荷类型中,有一个和其他的不太一样。这个类型是冗余净荷类型(RED),在RFC2198中规定。通常一个单独的RTP分组包括一个或更多的已编码语音或视频的抽样,分组中的每个抽样以相同的方式编码。如果分组丢失了,那么接收方应用必须尽可能妥当地处理数据的丢失,例如及时重放前一个分组的内容填补空白。冗余净荷类型的概念是指一个分组,在像往常一样包括一个或多个语音抽样的基础上,再加上一个或更多的先前的已经发往接收方的抽样的副本。换句话说,一个分组中包含一个当前会话的抽样加上一个先前会话抽样的备份副本。如果某个分组丢失了,那么丢失分组的会话抽样的备份副本将会在下一个分组中到达。注意,备份副本可能使用与原来不一样的编码模式。如果备份副本使用一个更窄带宽的编码模式,那么它将提供一个冗余来处理分组的丢失,而不需要额外的带宽。
2、RTP报头
RTP实际上携带编码语音信息。换句话说,一个或更多的数字编码抽样构成了RTP的净荷。RTP报头加到这个净荷上,然后分组被传递给UDP。
RTP报头包括的信息对目标应用程序重组原先的语音抽样来说是很必要的(至少,使得使用的编码模式进行精确的匹配成为可能)。
RTP报头如下所示
- 版本【V】这部分报头是2bit的域,应该设为2,表示RTP的当前版本号。
- 填充【P】这部分报头是1bit的域,表示分组在净荷的结尾是否有一个或者多个填充字节。由于净荷需要和32bit的边界对齐,所以在净荷尾部就需要填充字节和边界对齐。如果存在这样的填充字节,那么设置P比特位,并且净荷的最后一位指出共有多少个填充字节。
- 扩展【X】这部分报头是1bit的域,表示固定长度的报头是否包含一个报头扩展区。如果设置了这个比特,那么报头后将跟有一个报头扩展区。
- CSCR计数【CC】这部分报头是4bit的域,表示报头中有用源标志符的数量,取值范围为0到15。
- 标记【M】 这部分报头是1bit的域,表示的意义看传输的净荷类型而定。对一个应用程序来说,如果它在静止期不发送任何分组,那么在静止期后的第一个分组中设置这个比特(例如,突发对话的第一个分组)。不支持静止抑制的应用程序不应该设置这个比特位。
- 净荷类型【PT】这部分报头是7bit的域,表示RTP净荷的格式。总的来说,一个简单的RTP分组应该包含唯一净荷格式编码的媒体信息。
- 序列号【】这部分报头是16bit的域,由发送方在会话开始时设为一个随机数,然后每发送一个RTP分组就加1.这个标题使接收方可以检测到分组的丢失或是分组到达顺序的错误。
- 时间戳【】这部分报头是32bit的域,记录净荷中的第一个抽样产生的时间。这个抽样时间必须来自按时间单调性增加的时钟,这样远端应用程序就能以同步的方式打开分组,从而进行时延抖动的计算。时钟的分解应该适当,这样才能支持同步拆包。时钟频率取决于净荷数据的类型。典型的语音编码模式的频率是8000Hz。从一个分组到另一个分组的时间戳的值的增加取决于分组中抽样的数量。时间戳和一个分组到下一个分组的抽样时点的数目有关。例如,如果一个分组包括十个语音抽样和一个值为1的时间戳,那么下一个分组的时间戳为11。考虑到抽样以8000Hz的速率发生(每0.125s),那么时间戳中10的差别就代表了时间上1.25ms的差别。如果在静止期,就没有发送任何分组,那么下一个RTP分组可能包含一个明显比前个RTP分组大得多的时间戳。两者之差表示了第一个分组中包含的抽样数量和静止期的长度。时间戳的初始值是由发送应用程序选择的随机数。
- 同步源【SSRC】这部分报头是32bit的域,表示同步源。同步源是一个实体,,负责序列号和时间戳的值,并且通常是RTP分组的发送者。这个标识符由发送方随机选取,这样就不会依赖于网络地址。这个标题在一个RTP会话中是全局唯一的。如果一个RTP流来自混频器,那么SSRC就标志混频器,而不是初识的媒体源。
- 有用源【CSRC】这部分报头是32bit的域,包含一个会话的发起者的SSRC值。当RTP流来自一个混频器时,这个域用来表示混频器后的初识的媒体源。在一个RTP报头中可能存在0到15个CSRC条目。
RTP扩展报头
RTP报头是用来满足大多数的媒体流的一般要求。然后某些净荷的格式可能需要有附加信息。这种信息可以包含在净荷本身内,例如,大纲中规定,净荷中的前n个字节有特定的意义。同样的,你可以给RTP报头中增加扩展部分。
通过将RTP报头中的X位的值置为1,可以表示RTP报头中存在扩展报头。RTP扩展部分存在于CSRC域和真正的数据净荷之间。
3、RTP混频器
混频器是一个使来自不同源的多个媒体流混合成一个RTP流的应用程序。一个典型的例子就是音频会议。例如有四个参与者,每一个都以64kbit/s的速率发送和接收音频数据。如果某个参与者在每个方向上的可用带宽都被限制为64kbit/s,那么该参与者将不会有足够的带宽来向其他的参与者发送或者从他们那里接收RTP信息流。这时,混频器通过将许多单个信息流组合成一个以64kbit/s的速率运行的信息流来解决这个问题。某个参与者并没有听到其他三个参数者单独说话的声音,却接收到所有人的组合的音频信息。另一个例子是其中的一个参与者通过低速连接到该会议上,而其他参与者使用高速连接。在这种情况下,混频器可能还会应用低带宽的编码模式改变低速链路的数据格式。根据RTP报头中的值,某个参与者接收到的RTP分组将包含具体的混频器的SSRC值和对应于其他三个参与者的CRSC值。混频器设置时间戳的值。
翻译器是用来管理不支持同一媒体格式或是比特速率的实体之间的通信。例如,参与者A只能支持64kbit/s的PCMU语音编码(净荷类型0),而参与者B只能支持32kbit/s的G726-32(净荷类型2),他们之间连接一个翻译器。根据RTP报头,参与者A收到的RTP分组会有一个SSRC值标志参与者B,而不是标志翻译器的值。混频器和翻译器的区别在于混频器将几个流集中为一个流,也许还要进行一些净荷格式的翻译,而翻译器只进行媒体格式的翻译,不集中媒体流。
混频器由于翻译器的地方在于如果有多个流的情况下,不需要为每个流分配额外的带宽。不足之处在于RTP流的接收方不能在连续监听来自一个源的流的同时减轻来自另一个源的流的影响。
RTP协议包头的格式:
10~16 Bit为PT域,指的就是负载类型(PayLoad),负载类型定义了RTP负载的格式,协议原文说该域由具体应用决定其解释。
目前,负载类型主要用来告诉接收端(或者播放器)传输的是哪种类型的媒体(例如G.729,H.264,MPEG-4等),这样接收端(或者播放器)才知道了数据流的格式,才会调用适当的编解码器去解码或者播放,这就是负载类型的主要作用。
就ORTP库而言,负载类型定义如下:
每一种负载类型都有着其独特的参数,这里基本上涵盖了当前主流的一些媒体类型,例如pcmu 、g.729、h.263(很奇怪,竟然没有定义h.264)、mpeg-4等等。Jrtplib库应该也有相类似的定义。
在ORTP库和JRTplib库中,都提供了设置RTP负载类型的函数,根据实际的应用进行设置,要注意不要使用ORTP默认的pcmu音频的负载类型传输H.264编码的视频数据。
时间戳基本概念:
时间戳单位:时间戳计算的单位不是秒之类的单位,而是由采样频率所代替的单位,这样做的目的就是为了使时间戳单位更为精准。比如说一个音频的采样频率为8000Hz,那么我们可以把时间戳单位设为1 / 8000。
时间戳增量:相邻两个RTP包之间的时间差(以时间戳单位为基准)。
采样频率: 每秒钟抽取样本的次数,例如音频的采样率一般为8000Hz
帧率: 每秒传输或者显示帧数,例如25f/s
RTP时间戳协议中的定义:
RTP包头的第2个32Bit即为RTP包的时间戳,Time Stamp ,占32位。
时间戳反映了RTP分组中的数据的第一个字节的采样时刻。在一次会话开始时的时间戳初值也是随机选择的。即使是没有信号发送时,时间戳的数值也要随时间不断的增加。接收端使用时间戳可准确知道应当在什么时间还原哪一个数据块,从而消除传输中的抖动。时间戳还可用来使视频应用中声音和图像同步。
在RTP协议中并没有规定时间戳的粒度,这取决于有效载荷的类型。因此RTP的时间戳又称为媒体时间戳,以强调这种时间戳的粒度取决于信号的类型。例如,对于8kHz采样的话音信号,若每隔20ms构成一个数据块,则一个数据块中包含有160个样本(0.02×8000=160)。因此每发送一个RTP分组,其时间戳的值就增加160。
详细说来:首先,时间戳就是一个值,用来反映某个数据块的产生(采集)时间点的,后采集的数据块的时间戳肯定是大于先采集的数据块的。有了这样一个时间戳,就可以标记数据块的先后顺序。
第二,在实时流传输中,数据采集后立刻传递到RTP模块进行发送,那么,其实,数据块的采集时间戳就直接作为RTP包的时间戳。
第三,如果用RTP来传输固定的文件,则这个时间戳就是读文件的时间点,依次递增。
第四,时间戳的单位采用的是采样频率的倒数,例如采样频率为8000Hz时,时间戳的单位为1 / 8000 ,在Jrtplib库中,有设置时间戳单位的函数接口,而ORTP库中根据负载类型直接给定了时间戳的单位(音频负载1/8000,视频负载1/90000)
第五,时间戳增量是指两个RTP包之间的时间间隔,详细点说,就是发送第二个RTP包相距发送第一个RTP包时的时间间隔(单位是时间戳单位)。
如果采样频率为90000Hz,则由上面讨论可知,时间戳单位为1/90000,我们就假设1s钟被划分了90000个时间块,那么,如果每秒发送25帧,那么,每一个帧的发送占多少个时间块呢?当然是 90000/25 = 3600。因此,我们根据定义“时间戳增量是发送第二个RTP包相距发送第一个RTP包时的时间间隔”,故时间戳增量应该为3600,在Jrtplib中好像不需要自己管理时间戳的递增,由库内部管理。