概述

近期在和同事调试G729的编解码库时碰到一个语音质量的问题,问题产生的原因和RTP包中的时间戳设置有关,特此记录下来。

问题现象,1001和1002账号注册在fs,媒体设置为G729并通过fs中转,1001终端使用eyebean,1002终端使用自己开发的sip终端,从1001-1002的语音正常,从1002-1001的语音卡顿异常。

 

环境

centos:CentOS  release 7.0 (Final)或以上版本

freeswitch:v1.8.7

GCC:4.8.5

 

问题分析

freeswitch在正常的语音转发中没有发现过类似问题。

从语音质量的现象看,只有单边的语音卡顿,即从1002-1001方向的语音有问题。

1002的终端属于自研产品,其中G729模块也是刚刚接入,发生问题的概率较大。

有了基本的分析之后,还是要找到明确的证据支撑,这时候就需要抓包分析。

使用wireshark对SIP终端侧进行抓包,查看抓包的RTP流。如下图

 

timeshift中文介绍_timeshift中文介绍

 

 

其中蓝色的第1、3行,是出现问题的RTP流。

再打开RTP流分析的页面。

从1002-fs的RTP流如下图。可以看到stream0的包分析结果。

 

timeshift中文介绍_字段_02

 

 

从fs-1001的RTP流如下图。可以看到stream1的包分析结果。

这个页面我们发现了一个问题,就是RTP流的发包中,Delta(ms)列的数据有一些异常,每隔5个包,就会出现1个40ms间隔的包,非常的规律。

 

timeshift中文介绍_采样率_03

 

  

对应到RTP流中,根据timestamp字段就会发现,每隔6个包,就会有1个包丢掉了。

 

timeshift中文介绍_采样率_04

 

 

至此,我们可以得到一个初步的分析结果,就是1002发送的RTP包,经过fs的转发后,被部分丢弃了,造成1001收到的语音质量问题。

 

RTP包timestamp字段

由于1002SIP终端的G729库是新接入的,所以对于RTP打包格式是首先要怀疑的。

RTP头格式如下:

 0                   1                   2                   3

 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 |V=2|P|X|  CC   |M|     PT      |       sequence number         |

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 |                           timestamp                           |

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 |           synchronization source (SSRC) identifier            |

 +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

 |            contributing source (CSRC) identifiers             |

 |                             ....                              |

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

对于其他字段的介绍网上很多,直接略过,这里主要看一下timestamp字段。

timestamp,32比特。时间戳,反映了RTP数据包中第一个八位字节的采样时间。时间戳的初始值应当是随机的,类似序号。时钟频率依赖于负载数据格式,因此时间戳增量依赖于当前数据格式和策略。如果RTP数据包周期性产生,那么将使用采样时钟确定的标称采样时刻,而非读取系统时间。举例而言,对于固定采样率的音频,时间戳时钟可能会在每个采样周期增加1;如果音频应用程序从输入设备读取覆盖160个采样周期的块,则对于每个这样的块,时间戳将增加160,无论该块是在分组中传输还是作为静默丢弃。

上面这一段是官方的介绍,说实话我也看的稀里糊涂的,但是其中的关键是“时间戳将增加160”这一句,和我的认知有了冲突。

当我们看到timestamp字段的名字时,首先想到的是时间戳,既然是时间,按照正常的理解,应该是和打包时长ptime相对应,比如打包时长ptime是20ms,那么timestamp的间隔也应该是20。

但是事实上,RTP包中的timestamp字段并不是这样定义的。

在官方的RFC1889文档中,If an audio application reads blocks covering 160 sampling periods   from the input device, the timestamp would be increased by 160 for each such block, regardless of whether the block is transmitted in a packet or dropped as silent.

下面翻译成我自己的理解。

timestamp字段和通常的时间没有关系。

timestamp字段是为了表示媒体流的采样长度和顺序。

timestamp字段的初始值是随机数。

timestamp字段的间隔的计算方法,根据媒体流协商的媒体类型来决定,具体由俩个指标,采样率和打包时长。

计算公式是(timestamp间隔=采样率*打包时长)。

举个栗子,媒体类型的协商结果是G729,其中采样率是8000,打包时长是20ms,那么timestamp的间隔 = 8000 * 0.02 = 160。

再举个栗子,媒体协商结果是iLBC,其中采样率是8000,打包时长是30ms,那么timestamp的间隔 = 8000 * 0.03 = 240。

再举最后一个栗子,媒体协商结果是opus,其中采样率是16000,打包时长是20ms,那么timestamp的间隔 = 16000 * 0.02 = 320。

 

总结

RTP流在VOIP和RTC通信中非常常见。

我们从一个问题出发,在分析解决的过程中,重新认识了RTP包格式,尤其是其中timestamp字段的定义,和平常的时间戳定义有区别。

碰到网络问题,wireshark抓包是非常好用的工具,可以解决90%的问题。

 

空空如常

求真得真