【原创】QosFecNack传输方案C风格API说明

简介

本文档主要站在使用者角度对音视频传输库QosFuncNack(以下简称AVCom)进行说明,库内部的算法等实现细节可以参考网站上的详细文档说明。AVCom是一个封装了FEC前向纠错、QOS收端质量保证以及NACK自适应重传、帧率自适应、IDR主动请求、发送端Smooth平滑等处理的UDP传输模块。它能够在传统UDP基础上增强数据传输对于丢包、乱序、重复包等情况的抵抗力,特别适用于WIFI、3G、4G、5G等无线场景下的实时音视频传输。

当需要发送音视频时,用户只需要调用发送接口,传入音视频数据即可;当接收到来自远端的音视频数据时,模块将调用相应的回调接口送出音视频数据,接口简洁。用户无需关心FEC、QOS、NACK等实现细节。

注意:这里的音视频数据是一个广义的概念,既可以是H264码流也可以是JPEG码流,还可以是文本数据,它们对传输层来说都是一视同仁,传输层不会尝试解析,传输层所做的就是以相对可靠的质量将发送端Send的数据在接收端原样输出。

注意:本方案与KCP等ACK方案不同,它们是优化版本的“TCP”,在保障数据不丢失的前提下,提高数据的吞吐率。而本方案不做100%数据传输保障,以实时性为宗旨,转为音视频领域而生。

一种常见的使用场景:

用户已经有自己的媒体传输系统,可能是点对点模式也可能是客户端-服务器模式,可能是单工也可能是双工,这都不影响用户使用本模块。

用户只需要将本模块当做一个普通的处理“函数”。只需要在发端Create一个AVCom对象,然后调用AVCom对象的发送数据接口,该接口将对用户传入的数据进行FEC编码保护,生成的数据将通过用户Create时传入的回调接口送出,这个回调接口一般就是用户之前的网络发送函数。同样,在接收端创建一个AVCom对象,将用户外层接收到的数据原封不动的put给AVCom模块,模块进行QOS、FEC解码处理后通过用户Create时传入的回调接口送出,这个回调接口一般就是用户之前送解码的函数。NACK的处理封装到了模块的内部,对于用户外层而言不可见。

x1

注意:传入传出AVCom模块的数据一定要是“对称”的,比如用户在发送回调函数中采用RTP发送,那么接收端应该是RTP接收后的数据送AVCom,而不是将UDP接收的数据送AVCom。

注意:假设外层用户传入1MB数据,模块内部将自动拆分成适合网络传输的小块并进行FEC编码处理后发出,接收端在收到数据后自行拼接后输出,对于用户来说并未察觉数据的实际收发过程。

QosFuncNack库使用标准C++语言(非C++11)开发,为了方便C、C#等语言的调用,我们提供了一个C风格的封装接口。接口的定义位于RtpAvComSdk.h之中,下面对常用接口进行介绍。

 

日志接口

  • 日志对于问题定位非常重要,SDK提供了一组全局日志开关接口,用户只需要在应用中完成如下步骤即可激活SDK日志。(1)包含全局头文件

    #include “RtpAvComSdk.h”

    (2)系统启动时调用一次初始化

    void  RtpAvCom_Enviroment_Init(const char *  outputPath,         LOG_OUTPUT_LEVEL  outputLevel)

    参数:

    @param: outputPath:日志文件输出的目录,若目录不存在,SDK将自动创建,支持相对路径或绝对路径。注:只需要提供目录,日志文件内部自行命名。

    @param: outputLevel:日志输出的级别,只有等于或者高于该级别的日志才会输出到日志文件,日志级别定义见枚举LOG_OUTPUT_LEVEL:LOG_OUTPUT_LEVEL_DEBUG、LOG_OUTPUT_LEVEL_INFO、LOG_OUTPUT_LEVEL_WARNING、LOG_OUTPUT_LEVEL_ERROR、LOG_OUTPUT_LEVEL_ALARM、LOG_OUTPUT_LEVEL_FATAL、LOG_OUTPUT_LEVEL_NONE。当指定为LOG_OUTPUT_LEVEL_NONE时,将不会生成日志文件。

    (3)系统退出时调用一次反初始化

    void RtpAvCom_Enviroment_Free()

功能API

(1)RtpAvCom_Create接口

Create接口用于创建QOS-FEC-NACK模块,相关的控制参数即在此传入,该接口在所有接口中最为复杂,接下来将分别予以说明。

void* RtpAvCom_Create(

FEC_REDUN_METHOD  eRedunMethod,  unsigned int  unRedunRatio,  unsigned int  unFecGroupSize,

SendExternal  pfVideoSendCallBack,  SendExternal  pfAudioSendCallBack,  void *pObjectSend,

ReciveDataCallBackFunc  pfVideoRecCallBack, ReciveDataCallBackFunc pfAudioRecCallBack , void * pObjectRecv );

绿色部分为传输控制参数:

* @param eRedunMethod:为FEC冗余度方法,包括固定冗余度FIX_REDUN和自适应冗余度AUTO_REDUN两种取值。自适应冗余度根据当前网络的丢包情况动态调整本端发出的冗余度,其丢包抵抗力更强,码率的波动性也更大,其最高冗余度可以达到50%,最低为30%。固定冗余度则由用户设定冗余比例。【本参数影响本端FEC编码发送,与接收无关】

* @param unRedunRatio:为FEC固定冗余度时的冗余比率,建议根据实际网络情况、成本综合考虑而定。最大值100,最小值10。自动冗余度时忽略本参数【本参数影响本端FEC编码发送,与接收无关】

* @param unFecGroupSize: 为FEC分组大小的下限,分组上限为内部自动选取。720P、1080P分辨率建议本值设置为28,对于标清、码率分辨率越低,group设置得越小,具体可技术咨询。【本参数影响本端FEC编码发送,与接收无关】

红色部分为媒体发送函数指针:

应用层媒体数据传入本模块后,经FEC编码处理,模块将调用本“媒体发送函数指针”实现媒体数据的真实发送。pObjectSend 是发送函数的形参,内部不会访问,只会在调用回调函数时透传给外层。bSendByInternal在Android JNI接口中使用,其他平台无需理会。

typedef void (*SendExternal)(int len,  unsigned char*data,  void *pObjectSend , BOOL bSendByInternal);

紫色部分为接收媒体输出函数指针:

在此传入“媒体输出函数指针”,模块内部接收数据并经过FEC解码处理后,数据经本接口送外层。

typedef void (*ReciveDataCallBackFunc)(int len,  BYTE *data,  BOOL bComplete,  BOOL bPrevTotalFrameLost,  void *pObjectRecv);

pObjectRecv 内部不会访问,只会在调用回调函数时透传给外层。

若没有丢包,本接口的输出数据以及输出频率与发送端调用Send(SendVideoData\ SendAudioData)发送的数据以及频率完全一致。当出现FEC\NACK机制恢复失败的情况时,本接口中的bComplete、bPrevTotalFrameLost标志即可通知外层。它们分别表示:

bComplete用于标识当前输出data数据是否完整,若为True表示该帧数据完整,否则可能是头部、中间或者尾部有数据丢失。

bPrevTotalFrameLost用于标识当前输出data数据与上一次输出之间是否有整帧数据丢失的情况。它可以弥补bComplete标志无法判断中间帧丢失的不足,比如发送端发送第1、2、3、4帧视频码流,接收端收到1、2、4,虽然它们单帧角度看接收完整,但因为第3帧的丢失将导致解码花屏。

外层可以借助这两个标志以及帧类型判定实现丢帧冻结机制,具体可咨询技术支持。

 

(2)RtpAvCom_Delete接口

void RtpAvCom_Delete(void* pRtp_avcom); 释放Create创建的资源

* @param pRtp_avcom:模块指针

注意:外层需要做好本接口与其他接口的互斥(send接口和put接口),就如同malloc了一块内存,在其他线程正在使用的过程中,主线程不应该无互斥保护的free掉该内存一样。为了让接收和发送并行、音频和视频并行,一般创建4把锁,分别用于本接口与音频send接口、视频send接口、音频put接口、视频put接口的互斥(其他几个低频率的统计数据获取接口可以复用视频send锁)。若仅创建一把锁,则上述各接口变成串行,影响了效率。

 

(3)音视频发送接口

BOOL RtpAvCom_SendAudioData(void* pRtp_avcom,  unsigned char *byBuf,  int nLen);

BOOL RtpAvCom_SendVideoData(void* pRtp_avcom,  unsigned char *byBuf,   int nLen);

应用层数据通过本函数传入,内部进行FEC编码处理,然后使用外部传入的“媒体发送函数指针”发往远端。

* @param pRtp_avcom 模块指针

* @param byBuf 输入数据

* @param nLen 输入数据长度

RtpAvCom_SendVideoData接口一次传入一帧不限大小的码流,内部视情况自动拆分成合适大小发送给远端,远端内部自动拼接后送外层。

* @return: 返回TRUE成功,为FALSE则失败

注意:音频数据外层需要保证单次输入的小于1000字节,一般来说音频编码后单帧码流远小于本值。

 

(4)收到音视频数据后送模块处理接口

BOOL RtpAvCom_PutAudioData(void* pRtp_avcom,  unsigned char *byBuf,  int nLen);

BOOL RtpAvCom_PutVideoData(void* pRtp_avcom,  unsigned char *byBuf,  int nLen);

外层在接收到数据后需要通过本接口送模块进行QOS-FEC解码处理,处理后的应用层数据将通过上文描述的虚函数或者ReciveDataCallBackFunc回调接口输出。

* @param pRtp_avcom 模块指针

* @param byBuf 输入数据

* @param nLen 输入数据长度

* @return: 返回TRUE成功,为FALSE则失败

 

(5)Nack重传功能使能接口

void RtpAvCom_EnableQosNackForVideo(void* pRtp_avcom,  BOOL bEnable);

模块默认关闭NACK功能,若需要启用NACK,则需要显式调用本API开启。

* @param pRtp_avcom 模块指针

* @param bEnable是否开启视频通道的NACK功能

注意:NACK是一个双向的过程,只有本端和远端均开启NACK时才会真实生效。

 

以下接口为高级接口:

(6)获取网络丢包率接口

BOOL RtpAvCom_GetVideoAudioUpDownLostRatio(void* pRtp_avcom,           float *pfVideoUpLostRatio,    float *pfVideoDownLostRatio,                             float *pfAudioUpLostRatio,   float *pfAudioDownLostRatio);

获得当前时刻的音视频上下行丢包率数据,范围[0.0, 100.0]

* @param pRtp_avcom 模块指针

* @param others音视频上下行丢包率存放变量地址

 

(7)获取网络RTT接口

BOOL RtpAvCom_GetRttMs(void* pRtp_avcom, unsigned int*punRttMs);

获得当前网络的RTT,单位毫秒。

* @param pRtp_avcom 模块指针

* @param punRttMs  RTT存放变量地址