分类目录归档:文档

【原创】客户端接入转发服务器 API说明

基本概念

房间:服务器上一个虚拟的概念,进入同一个房间的客户端才允许数据交互,不同房间之间的客户端并无交集。一个房间内客户端的最大数目理论不限,具体根据服务器端设置。服务器允许最大的房间数目同样依据服务端设置而定。最简单的模型是一个房间内一个发布者,一个接收者。

客户端UID:用于标识每个客户端的唯一ID,在系统中不允许有相同UID的客户端同时在线,否则新登录的客户端会将之前的客户端“顶下去”。

音视频发布者:具备上行音视频到服务器(房间)能力的客户端类型,同时也能接收来自服务器(房间)的音视频数据。

音视频接收者:只从服务器(房间)接收音视频的客户端类型,不具备上行能力,比如直播业务中的普通观众。

音视频位置:一个房间内允许同时多个音视频发布者,它们发布音视频到不同的“位置”上,接收者可选择接收某个或某些位置的音视频。目前单个房间允许32个“位置”。发布者登录后可以选择由服务器自动分配空闲的“位置”供其发布,也可以指定“位置”发布,若当前该“位置”上已有其他发布者,后者将顶替前者以保证同一位置上同时只有一个发布者。

音视频接收掩码:客户端可以根据业务需要选择接收房间内某个或某几个位置的音频或视频数据,可以通过设置自己期望接收的掩码到服务器。音频和视频使用各自独立的掩码,每个掩码32bit对应房间内的32个位置。

传输参数:本文传输参数是指视频通道的FEC上行冗余度、上行FEC Group组大小、接收Qos丢包等待时间,音频通道在内部已经根据经验数据配置好了合适的值且不对外开放。对于音视频接收者同样也可以设置FEC上行冗余度、上行FEC Group组大小,只是没有实际意义。

主动API接口:由外层应用主动发起的调用,比如登录、下线、发送音视频等。

被动API接口:称为回调接口更为贴切,比如接收到来自服务器的音视频数据、底层TCP连接状态发生错误的通知、底层要求获取服务器的IP地址。

 

主动接口

1、登录服务器

int IFace_OnlineUser(UINT unUid, UINT unRoomId, BYTE byUserType, UINT unDomainId = 1, BYTE byNetType = OPERATOR_OTHER);

参数:

@unUid,表示当前客户端使用的唯一用户ID,需要由用户自行保障唯一性。要求非0

@unRoomId,表示当前客户端进入的房间号,要求非0

@byUserType,表示客户端的类型,音视频发布者固定为USER_TYPE_AV_PROVIDE, 音视频接收者为USER_TYPE_AV_PLAYER

@unDomainId,域ID,默认为1即可,具体含义请参考SRTP-Server服务器设计相关文档。

@byNetType,客户端主动告知服务器自己的网络类型,便于域服务器快速分配优选的媒体服务器供其使用。若不提供或未知自己的网络类型,域服务器将根据客户端的出口IP查询IP地址库得到其运营商类型。

返回值:

返回0表示登录成功,返回负数则为失败,负数值为其错误码。

 

2、下线服务器

void IFace_OfflineUser();

参数:

返回值:

 

3、请求上传音视频到指定位置

int IFace_OnPosition(BYTE &byIndex);

参数:

@byIndex,大于等于0,小于服务器支持的最大位置数(具体依据服务器配置而定,比如[0,32))。特殊值:当设置为255时表示由服务器分配当前空闲的位置,分配结果将写回参数。请求成功后,将自动停止接收自己位置的音视频流(不看自己)。注意位置是从0开始编号。

返回值:

返回0表示请求发布成功,返回负数则为失败,负数值为其错误码。

 

4、请求从位置上下来

void IFace_OffPosition();

参数:

返回值:

对于当前在位置上的音视频发布者,OffPosition将从位置上下来并停止上行音视频数据,但它仍然保持在线状态并能接收其他位置的音视频流。当IFace_OfflineUser调用时将自动从位置上下来,而无需调用IFace_OffPosition(当然Offline前调用OffPosition也无妨)。请求成功后,将自动恢复接收自己之前位置的音视频流(可能有其他客户端加入该位置)。

 

5、上传视频数据

void IFace_SendVideoStreamData(BYTE* buf, UINT unLen, UINT unDts = 0);

向请求的位置发送视频码流,一次传入带H264起始码的一帧码流。

参数:

@buf,码流存放区。

@unLen,码流长度。

@unDts,可以使用外层时间戳,也可以(建议)由模块内部自行管理时间戳。内部将使用1KHZ时基(毫秒)生成时间戳。

返回值:

 

6、上传音频数据

void IFace_SendAudioStreamData(BYTE* buf, UINT unLen, UINT unDts = 0);

向请求的位置发送音频码流,一次传一帧ADTS码流。

参数:

@buf,码流存放区。

@unLen,码流长度。

@unDts,可以使用外层时间戳,也可以(建议)由模块内部自行管理时间戳。内部将使用1KHZ时基(毫秒)生成时间戳。

返回值:

 

7、设置音视频下行掩码

void IFace_SetAvTransStatus (UINT unAudioMask, UINT unVideoMask);

通过设置音视频下行掩码,可以选择从服务器接收哪几个位置的音视频数据。每一个bit对应一个位置,最低位对应0号位置,最高位对应31号位置。比如希望接收某个index位置的音视频时,可以设置为:

UINT unMask = 0x1 << (index);

unAudioDownChannelsMask |= unMask;

unVideoDownChannelsMask |= unMask;

当系统停止接收某个index位置的音视频时,可以设置为:

UINT unMask = 0x1 << (index);

unMask = ~unMask;

unAudioDownChannelsMask &= unMask;

unVideoDownChannelsMask &= unMask;

参数:

@unAudioMask,控制音频接收的掩码。

@unVideoMask,控制视频接收的掩码。

返回值:

 

8、设置音视频传输参数

void IFace_SetTransParams(UINT unQosDropDelay, FEC_REDUN_METHOD_TYPE eRedunMethod, UINT unRedunRatio, UINT unFecGroupSize);

参数:

@unQosDropDelay,本客户端接收码流时的QOS丢包等待时间,可设置为80。

@eRedunMethod,为上行FEC冗余度方法,建议设置为FIX_REDUN。

@unRedunRatio,固定冗余度时对应的上行冗余比率,建议设置为30。

@unFecGroupSize,为上行FEC分组大小,720P分辨率,建议设置为22,1080P分辨率可设置为28,低分辨率低码率场合建议设置得比较小,比如12

返回值:

注意:本函数需在IFace_OnlineUser之前调用,本API的使用若有疑问,请联系技术支持获得帮助。

 

被动接口

被动接口均使用CSDTermCmdIFace类虚函数方式提供,用户需要使用一个类继承CSDTermCmdIFace类并实现该虚函数。

1、请求获取服务器IP地址

virtual void GetDomianSerIp(char *strIp)

virtual void GetDomianSerIpSecond(char *strIp);

参数:

@strIp,在本函数内向该字符串填入服务器IP地址。

返回值:

服务器可能为双IP的情况,所以我们预留了两个接口,若服务器为单IP,则无需实现GetDomianSerIpSecond函数。

 

2、收到服务器下发的视频数据

virtual void OnRemoteVideo(BYTE byIndex, BYTE* data, UINT unLen, UINT unPTS, VideoFrameInfo tFrameInfo);

参数:

@byIndex,当前码流属于哪一个“位置”。

@data,指向接收的码流帧存放区域

@unLen,接收码流帧的长度

@unPTS,当前码流帧的时间戳

@tFrameInfo,当前码流的详细描述,其定义如下:

typedef struct VideoFrameInfo

{

UINT unWidth;

UINT unHeight;

BOOL bPacketLost;

BOOL bKeyFrame;

}VideoFrameInfo;

模块内部会解析SPS获得当前码流的宽和高告知外层(可能外层根本不需要这个)。另外两个BOOL变量是用于外层实现丢帧冻结机制时的参考。bPacketLost表示当前帧是否接收完整,若网络丢包且FEC未能恢复时该标志将置位。bKeyFrame表示当前帧是否为IDR关键帧。如果使用这两个标志实现丢帧冻结可以联系技术支持获得帮助。需要说明的是,当没有丢包发生时,本函数的输出应与IFace_SendVideoStreamData函数的输入完全一致。

返回值:

 

3、收到服务器下发的音频数据

virtual void OnRemoteAudio(BYTE byIndex, BYTE* data, UINT unLen, UINT unPTS, AudioFrameInfo tFrameInfo);

参数:

@byIndex,当前码流属于哪一个“位置”。

@data,指向接收的码流帧存放区域

@unLen,接收码流帧的长度

@unPTS,当前码流帧的时间戳

@tFrameInfo,当前码流的详细描述,其定义如下:

typedef struct AudioFrameInfo

{

UINT unCodecType;

UINT unSampleRate;

UINT unChannelNum;

}AudioFrameInfo;

音频帧为ADTS格式,其每个包头部均附带了采样率、通道数、编码格式等信息。我们暂时未做解析,tFrameInfo内容不可参考。

返回值:

 

4、底层与服务器之间的TCP连接断开

virtual void OnSystemExit(UINT unUid, UINT unExitCause);

参数:

@unUid,当前登录使用的UID。

@unExitCause,当前与服务器的TCP连接已经断开,告知应用层的错误码

客户端登录服务器成功后将建立与服务器之间的TCP长连接,当因为网络等因素导致TCP连接断开时,内部将自动尝试重连。当相同账号在其他位置登录导致本客户端被“顶”下去时,将不会尝试重连,并通过本接口告知上层。

返回值:

 

 

一种简单场景下的使用示意

下面演示一种纯发送端(不接收)的API调用情况,其他场景的API使用可以以此为参考或咨询技术支持。

1、new一个CSDTerminal类对象

2、使用一个现有类继承自CSDTermCmdIFace类,并实现该类的几个虚函数。一个典型的实现为:

// 获取主域服务器的IP地址1

void CTerminalTestDlg::GetDomianSerIp(char *strIp)

{

sprintf(strIp, “13.5.14.9”);  //服务器的IP地址

}

 

// 获取主域服务器的IP地址2

void CTerminalTestDlg::GetDomianSerIpSecond(char *strIp)

{

//服务器为单IP类型时,直接为空

}

 

//来自底层的退出反馈(比如与服务器之间TCP连接断开时,将通过本接口反馈应用层)

void CTerminalTestDlg::OnSystemExit(UINT unUid, UINT unExitCause)

{

//通知上层

}

 

// 收到服务器发来的视频

void CTerminalTestDlg::OnRemoteVideo(BYTE byIndex, BYTE* data, UINT unLen, UINT unPTS, VideoFrameInfo tFrameInfo)

{

//纯发端无需处理本函数,直接为空

}

 

// 收到服务器发来的音频

void CTerminalTestDlg::OnRemoteAudio(BYTE byIndex, BYTE* data, UINT unLen, UINT unPTS, AudioFrameInfo tFrameInfo)

{

//纯发端无需处理本函数,直接为空

}

 

3、调用一次 IFace_SetTransParams 设置发端上行传输FEC参数,推荐调用为:

IFace_SetTransParams(0, FIX_REDUN, 30, 28);

参数说明:接收时QOS丢包延时为0(发端没有接收,直接设置0),使用固定冗余度方式FIX_REDUN,使用30%的上行冗余,使用Group大小为28

4、调用一次 IFace_OnlineUser 登录服务器,举例调用为:

IFace_OnlineUser(unUserId, unRoomId, USER_TYPE_AV_PROVIDE);

5、调用一次 IFace_OnPosition 设置上传音视频到房间的哪个位置,举例调用为:

IFace_OnPosition(0);

6、调用一次 IFace_SetAvTransStatus 设置发端从服务器接收音视频的情况,推荐调用为:

IFace_SetAvTransStatus(0, 0);

参数说明:第一个参数表示从服务器接收的音频掩码,第二个参数表示接收的视频掩码;二者均为32位整数,每个bit对应一个音视频位置,比如最低bit位对应0号位置,若希望接收该位置的视频或音频则设置该bit为1。因为发端只发不收,所以我们直接设置音频视频接收掩码都为0,表示什么也不接收。

7、N次调用IFace_SendVideoStreamData、IFace_SendAudioStreamData向服务器发送音视频数据

8、 结束前调用一次 IFace_OfflineUser (内部将自动OffPosition)

9、 delete回收CSDTerminal类对象

【原创】FEC/QOS中的NACK方案

 

一、FEC-QOS配合NACK的优势

ACK是大家比较熟悉的传输层保障措施,在类TCP的UDP传输方案中(UDT、KCP等)中通过优化ACK发送时机、更短的超时重传时间等措施来获得更高的数据吞吐率,但ACK并不适合对实时要求极高的直播互动领域。

NACK与ACK不同,它是在没有收到数据包时向远端请求重传,因而更加适合实时通讯。通过与FEC的结合,只对FEC无法恢复的数据包请求NACK重传,能够尽量的降低重传发起概率,降低重传带来的副作用。

二、NACK在系统中的位置

NACK可以放置在原来的FEC-QOS传输层之外,作为上层应用层,这种实现方式NACK将FEC-QOS看做普通的UDP传输,二者并无紧密结合,其优势是可以与成熟的NACK方案无缝衔接。我们知道任何NACK方案都必将引入延时抖动,因为接收端在发起重传请求后,需要等待发送端重新发出的数据,在“重传等待时间”内不对外输出数据。而QOS阶段里为解决UDP乱序包的问题也引入了一个“丢包等待时间”,当遇到包序号不连续时,将等待这一时间,若仍未收到所需的包则认定丢包,不再等待。如果将这两个时间合二为一,可以尽量的降低系统时延和抖动,毕竟我们需要的是一个高实时性的NACK传输方案。我们将NACK的发起和等待放置在QOS之中,入下图所示:

xx1

图1 在QOS中实现NACK发起

当QOS检测到序号不连续时,可能是发生丢包或者是乱序,此时QOS将通过FEC解码模块分析当前疑似丢包是否将导致FEC无法恢复。此时将产生三种分析结果:

A、当前丢包即使丢了也不影响FEC恢复,比如当前丢失的包为一个或者多个冗余包,且该冗余包所在的group内的媒体包均已接收,或者借助已接收的冗余包足够恢复。

B、当前丢包不能确定是否影响FEC恢复,需要接收更多的包才能确定。比如丢包发生在group的中段且丢的数量小于冗余包总数。

C、当前丢包将导致FEC确定无法恢复,比如同一个group内丢失的包数大于冗余包总数。

对于情况A,QOS将直接不予等待,将后续接收的包直接交与FEC。对于情况B,QOS将进入“丢包等待时间”,以期收到乱序的包。对于情况C,QOS将发起NACK重传并进入等待,这个等待时间即是“丢包等待时间”又是“重传等待时间”,在等待期内不管是该乱序包到达或者重传包到达,都能满足FEC的恢复条件。

在介绍了NACK的发起条件后,我们来关注“重传等待时间”的取值问题。若设置固定的重传等待时间将很难满足各类网络情况。时间过小将导致重传包尚未到达,QOS已结束等待并输出后续包,后续即便再收到重传包也将直接丢弃。重传包也可能因网络原因丢包,若“重传等待时间”过大,将导致更大的延时和抖动。为了提高自适应能力,系统通过实时获取当前网络的UDP通讯RTT时间来作为“重传等待时间”的参考,计算出合理的值。

三、信令通道与媒体通道分离

我们使用独立的一个UDP信令通道用来传输NACK请求,而不是复用媒体通道。这样做的主要考虑是:

A、媒体通道上使用的FEC\QOS将必然引入部分延时和抖动(具体参见FEC\QOS原理说明),NACK请求对于时间特别敏感,希望是越早越快通知对方越好。在信令通道上我们将只进行裸UDP收发,不会加入FEC和QOS。

B、避免出现NACK请求包丢失后也发起NACK重传请求的情况。

C、更好的兼容性,对于不支持NACK的节点,只需要忽略信令通道的内容即可与NACK节点互通。

D、程序实现上更加简洁,无需在媒体通道上新增包类型来区分哪个包是NACK请求包。媒体包上增加字节也都会直接转化为带宽的增长。

四、实现流程

方案的实现流程如下图所示:

xx2

图2 NACK的整体流程

对于传输层模块,它是全双工的,为演示方便我们只列出单向的情况,另外一个方向也是完全一致的。在FEC编码之后,所有的发出的UDP(RTP)包均会被存入一个环形缓存区中,当收到远端NACK请求时,将在下一个媒体包传输时触发重传动作,后者将在环形缓存区中检索需要重传的包并在媒体通道上发出。我们没有新增内部线程去执行重传动作,而是借助原本的媒体包发送行为来触发,这样可以简化设计提高稳定性。

检索过程中我们进行了数据包的合法性校验和时间戳有效性校验,当发现当前时间距离数据包初次发送时间的间隔已经较大时,将放弃重传,因为此时远端极有可能已经退出等待,没有必要再浪费带宽。检索使用的是RTP头中的序号字段,需要考虑序号达到最大值时的跳变动作。

【原创】FEC/QOS 音视频实测DEMO

FEC/QOS 音视频实测DEMO将以实际音视频作为测试对象,真实反映本方案给多媒体系统抗丢包能力带来的提升。DEMO界面如下图1所示:

图1   FEC/QOS 音视频实测DEMO界面

本DEMO支持如下特性:

  • 使用Direct进行摄像头、麦克风的采集和输出
  • 使用ffmpeg进行高效图像缩放等前置filter
  • 视频H264 HighProfile编码、解码
  • 音频AAC-LC、AAC-LD、AAC-ELD编码、解码(三种标准可选,44.1KHZ 16bit 2通道立体声)
  • 音视频RTP传输(带FEC\QOS功能)
  • 人为丢包测试功能
  • 实时统计输出线路丢包延时情况

DEMO的内部框架如下图所示:

图2   DEMO的内部框架

在视频采集缩放线程与视频编码线程之间,我们采用高效的双队列机制(队列元素为指针,进出队列无数据拷贝),如果视频编码性能非常充足的情况下,我们也可以将二者合并为一个线程。编码线程与网络发送线程分离,避免网络拥塞影响编码线程(这个对于UDP来说不成立,但对于RTMP这类TCP系统来说,网络收发线程与音视频编解码线程的分离是必须的,因为网络的抖动将影响音视频处理环节。站在系统设计的角度,我们为UDP和TCP统一使用上述架构)。

在音视频发送模块内,我们都配有定时握手包发送线程,这个线程与音视频使用相同的发送通道(端口),仅在包头上予以区别。它的作用非常重要,主要包括两个方面:为NAT穿越提供保障,当我们的客户端(内网IP)向服务器(公网IP)发送数据时,链路路由器会为该通讯链路映射“端口”,这样服务器(公网IP)向该客户端发送数据时,只需将收到的数据包的IP地址和端口翻转作为发送目标IP和端口便能向其发送数据。路由器在收到服务器的数据包时,检查本地存在对应的映射“端口”,予以放行,否则将丢弃这一数据包(这是基于安全的考虑,外网向内网发送数据不得不防)。值得注意的是,路由器上“端口”是有时效性的,超过一定时间即会失效,为了保证服务器能持续有效的向客户端发送数据,客户端必须以心跳包的方式向服务器发送数据以保持“端口”的有效性(客户端根据业务情况不一定向服务器持续发送数据包,可能只作为接收者)。以上是对C/S模式下NAT的简要描述,P2P模式等其他情况请参考专门的资料。定时握手包的另一个作用是传输自定义的信道统计数据,这个类似于RTCP协议,接收方统计出下行丢包率后可以通过本握手包告知发送方,以便通知对方调整发送甚至编码策略。

音频的处理流程与视频类似,因为音频编码耗时非常低,我们一般将音频采集与编码放到一个线程内进行。音频的输出与视频不同,因为它需要按固定的输出频率工作,驱动将发起定时输出线程,我们只需要在该线程内向指定内存存入指定数量的PCM数据即可。(输出频率、存放数量由音频输出通道数、采样率、采样点字节数的配置而定)

若本地IP与远端IP设置一样,则进入本地回环模式,此时将不存在任何网络丢包,我们可以通过设置手动丢包来模拟测试。若本地IP与远端IP不同,且不属于同一网段,我们可以使用开源的WANEM来进行模拟丢包、延时、抖动、重复包等情况,这一方式后面我们将专门予以介绍。

图3    随机4%丢包,不使用FEC时的情况

当使用4%随机丢包时,若关闭发端FEC功能,接收端视频将出现经常性花屏,声音出现丢失断续。若使用20%冗余度,视频花屏概率将大幅降低(不会完全消除,因为丢包是随机的,短时间内可能出现连续大量丢包的情况,超过20%冗余度的无失真抗丢包率是16.67%即会出现花屏)

若使用按间隔丢包,每6个包丢一个(丢包率将恒定为16.67%),此时选择20%冗余度可以实现无失真恢复,视频流畅无花屏,音质良好无断续。

图4    每6个包丢1个包(16.67%丢包),20%冗余度无失真恢复

 

【原创】FEC/QOS 数据测试型DEMO——–QOS功能验证

本DEMO的另一项测试功能是针对QOS来的,通过分析收发包延时结果,我们可以进一步了解丢包对系统延时的影响。在发送的媒体包时,我们在其负载中加入了系统当前时间,通过记录回环后的媒体包的接收时间(以应用层接收为准,即经过了QOS、FEC解码处理后)来计算该媒体包的来回总时长。

首先,我们选择不丢包,其他参数配置情况:冗余度20%(Group中10个媒体包配2个冗余包),两个媒体包之间的发送间隔为60ms,得到的媒体包“收发延时”情况如下图1所示。

图1 不丢包时,媒体包收发延时情况

因为媒体包为本地回环且无丢包发生,QOS、FEC均未做任何处理,延时非常低。可见与缓存一段时间数据再排序的QOS算法相比,本方案的QOS算法在没有丢包时将不会引入任何延时。

当我们选择每10个包丢弃一个时,时延情况如下图2所示:

图2 每10个包丢弃1个时,媒体包收发时延情况

下面我们来解释这一现象。每10个包丢弃1包时的情况如下图3所示:

图3  每10个包丢弃1个的情况示意

在QOS阶段,0号媒体包丢失,接下来收到的1号媒体包不能直接输出给后续FEC环节,需要等待最多200ms(QOS丢包延时设置为200ms),若仍然未收到0号媒体包则直接输出1号媒体包以及收到的后续媒体包。同样,当收到1号冗余包时也需要等待200ms方能输出。

在FEC阶段,当收到1~9号媒体包时因发现存在丢包而不直接输出给上层应用,直到收到1号冗余包时具备了FEC恢复的条件(收到媒体包数+收到冗余包数>=GROUP媒体包数),恢复0号媒体包并输出0~9号媒体包给上层应用。这样计算来0号媒体包被恢复输出总计需要800ms以上(60ms收到1号媒体包+8*60ms收到2~9号媒体包+2*60ms收到1号冗余包+1号冗余包在QOS环节等待200ms)。

当18号媒体包的丢失,19号媒体包会在QOS环节等待200ms,此时间段内会陆续收到冗余包0和冗余包1,200ms后仍然未等到18号媒体包的到来则直接输出到FEC恢复环节实现恢复。这样计算来18号媒体包被恢复输出总计需要约200ms。

由上例可见,当GROUP比较大时,若丢包发生在GROUP的前部,QOS环节的处理将影响后续包的时延指标。同时,前部丢失的媒体包不得不等待较长时间,直至尾部的冗余包到来方能恢复,造成延时进一步增大。下面我们比较GROUP SIZE为5时的情况。

图4  GroupSize为5时,每10个包丢1个的情况

可见采用较小的FEC GROUP可以改善丢包延时情况。

【原创】FEC/QOS 数据测试型DEMO——–FEC功能验证

本方案为C++开发,提供PC、Android(JNI)、IOS跨平台的支持,为了方便测试,在PC下开发了一个简易测试DEMO如下图1所示,该DEMO主要用于验证功能,能直观的展示所有收发包数据。

 

图1  FEC/QOS 数据测试型DEMO界面

测试工具为点对点工作模式,可在两台PC上各自运行(同时也支持单机模式,只需将收发IP地址均设置为本地IP即可),以实现双方之间RTP(FEC+QOS)通讯。软件收发自定义的测试包数据,提供了模拟丢包功能,支持按固定间隔丢包或者按随机比率丢包;支持设置FEC冗余度或者选择冗余度自适应,支持设置QOS丢包等待时延等参数。

测试工具内部默认使用10个媒体包外加冗余度(数量由选择的冗余度决定)作为一个GROUP,当选择冗余度20%时,一个GROUP由10个媒体包附加2个冗余包组成。下图是Wireshark的观察情况,10个媒体包后面紧接着2个冗余包。

图2  Wireshark截包观察GROUP组成

需要说明的是:程序主动丢包是在UDP发送层进行,所以即可能丢媒体包也可能丢

冗余包。下面我们以20%冗余度为例说明系统对各类丢包率的抵抗能力。

当选择每10个包丢1个包时(丢包率10%),一个GROUP中最多只会丢弃1个包,20%的冗余度足够抵抗这一丢包率,测试结果也验证了这一结论,接收到的所有媒体包序号均保持连续,丢包率从10%降为0%,实验情况如上图1所示。

当选择每5个包丢弃1个包时(丢包率20%),丢包情况如下所示:

图3  每5个包丢弃1包时的情况

对于第一个GROUP,一共丢弃了三个包,包括0号媒体包、5号媒体包、0号冗余包。因为接收的媒体包数为8个加接收的冗余包数1个,总数小于总媒体包数(10个),因此接收端FEC无法恢复。对于第二个GROUP,只丢失了两个媒体包,可以正常恢复。下图4的实验结果也说明了推断的正确性,0号媒体包、5号媒体包丢失,13号、18号媒体包被成功恢复,系统丢包率从20%降低到10%左右。

图4  经过FEC恢复后的数据包情况

当选择每4个包丢弃1个包时(丢包率25%),一个GROUP中将丢弃3个数据包,必然不满足FEC恢复条件,无法恢复,此时将原样输出收到的数据包,入下图5所示:

图5  丢包率过高无法恢复的情况

需要说明的是:丢包是针对传输层数据包(媒体包和冗余包)进行的,DEMO软件中只显示媒体包部分,没有显示冗余包。“下行丢包率”参数是统计的传输层数据包的丢失率,而“下行丢包率(经过丢包恢复)”是统计的应用层媒体包的丢失率,所以会出现后者比前者还高的情况。

当系统丢包率达到FEC特定冗余度的理论极限时,丢包将无法恢复,此时唯有加大冗余度才能实现恢复功能。实际应用时,应该对线路的网络丢包情况做一定统计分析,以确定最佳的冗余度方案,这个往往是比较困难和繁琐的,为此我们推出的自动冗余度方案,从一定程度上解决了这个问题。

若定义“无失真抗丢包能力”为可百分百无失真恢复丢失的媒体包的能力。下表是各冗余度下,传输层“无失真抗丢包能力”的估算情况:

是不是10个冗余包配2个冗余包时,信道丢包率只要低于16.67%就肯定可以无失真恢复呢?这个不一定,因为丢包的分布情况是随机的,比如100个包里丢10个包,10个包是连续丢还是随机均匀丢对FEC恢复来说是完全不同的。FEC并不能解决所有丢包问题,但可以显著提升系统的抗丢包能力。

【原创】RTP抗丢包方案概述

           对于UDP丢包,我们采用改进型的vandermonde矩阵FEC (Forward Error /Erasure Correction)前向纠错技术来进行丢包恢复,由发送方进行FEC编码引入冗余包,接收方进行FEC解码并恢复丢失的数据包。对于包乱序和包重复,我们采用QOS乱序恢复处理,该QOS方案特点是在没有丢包的情况下,不引入任何系统延时,并且可以通过可控的丢包等待时延来适应不同的信道乱序程度。QOS需要在接收端进行FEC解码前进行,确保送FEC解码模块的数据包序号是正确的(不存在乱序,仅存在丢包)。众多产品案例表明:采用FEC+QOS+RTP的组合,能显著提升UDP传输的丢包、乱序抵抗力,为上层音视频服务提供有力保障。下图1是各模块在系统中的位置说明。

需要说明如下几点:

(1)从差错控制角度看,传输信道可以分为随机信道、突发信道和混合信道。在随机信道中,丢包出现是随机的,且相互统计独立,满足正态分布。在突发信道中,丢包是集中出现的,在一些短促的时间区间会出现大量的丢包,而在这些时间区间之外又存在较长的无丢包区间。混合信道则是上述二者的合体。本方案侧重于对具备随机信道特性的传输链路进行改进优化。互联网丢包、乱序现象的出现很难预知,人们对 Internet信道的丢包特性研究发现,大多数情况下其满足随机信道的特点,丢失的都是单个包。连续两个或以上包同时丢失的概率虽然比纯随机过程要高,但发生的概率还是要比单包丢失低,发生连续丢失10个以上包的概率就更低了。由于单包丢失出现的最频繁,我们的抗丢包方案主要侧重于对单包丢失的修复,同时也应该兼顾连续丢失的少量包的修复。对大量连续丢失的包的修复相对来说就显得不那么重要了(出现概率低,修复的代价大)。

(2)当然,任何差错控制方案都是有其最大纠错能力限制,当丢包率超出当前系统的纠错能力时,丢包无法恢复,对于视频应用来说意味着视频将出现花屏。为了改善系统在高丢包率下的用户体验,避免长时间花屏无法刷新的现象,我们建议使用者采用ARQ(自动请求重发)+FEC机制,这里的ARQ请求并不是请求远端重发丢失的数据包,因为那样相当于走了TCP这类内嵌ARQ功能协议的老路,必然引入不可控的延时。这里的ARQ只是请求远端即刻编码视频关键帧,避免长时间花屏无法刷新的现象,ARQ请求一般通过额外的TCP信道发出(在绝大多数的系统中,通讯双方一般会有TCP的信令通道,用于双方业务层信令的交互)。ARQ的发起是根据FEC解码输出视频码流是否丢包作为判断依据,发送端和接收端都需要对ARQ的频率做一定的保护措施,避免频繁的发起和响应,造成过多的I帧(过多I帧的副作用前面已有列举)。