基于udp的音频抖动缓冲的实现

来源:互联网 发布:门窗销售软件 编辑:程序博客网 时间:2024/05/16 18:35

1. 音频包的结构:

关键成员变量如下,其字段意思自明,无需进一步叙述:

public var lastseq:uint;

public var from:uint;

public var sid:uint;

public var seq:uint;

public var time_stamp:uint;

public var fec_id:uint;

public var type:uint;

public var voice_type:uint;

public var payload:ByteArray;

public var wavBytes:ByteArray;

public var isBroadcast:Boolean;

public var playTime:uint;

public var flvBytes:ByteArray;

public var reSend:Boolean;

2. 音频包的存储结构:

使用双向链表来存储,每个节点对应一个音频包,该双向链表有链表大小size属性,有头节点head.每次来一个音频包,就把该音频包插入到双向链表中的适当位置(在考虑丢包,乱序,延迟之后,确定该音频包的位置),链表按音频包的序列号依次排序,head的序列号最小

3. 把音频包添加到双向链表中:

返回值说明:

Res == 0;说明是正常的按顺序从小到大接收到的音频包

Res == 1;说明是重复接收到的音频包

Res == 2;说明是发生丢包

Res == 3;说明是接收到的是晚到的音频包

根据返回值的不同进行相对应的处理:

Res == 0 或者Res == 3,直接flush(),确定要播放的音频包的数目

Res == 1,重复的包,直接丢弃

Res == 2,发生丢包,对所有丢包序列进行重传

4. flush函数:

(1)上一次播放的音频包与当前链表的head节点的序列号是连续的,

即 lastSeq+2 = head.data.seq:

则从当前head节点开始,找到一段连续的音频包,最多为链表的长度个包,

设此时找到的音频包的个数为flushCnt

(2)上一次播放的音频包与当前的head节点的序列号是不连续的,

即 lastSeq+2 < head.data.seq:

如果此时,链表中的所有的音频包的总共的播放时间已经大于最大的播放时间,

即MAX_PLAY_TIME,则调用flushLater(0,bufTime);函数,见下面对该函数的叙述

5.   playAudio函数:

 对4步骤中确定的flushCnt个音频包进行播放

(1)如果上次播放的音频包与当前链表的head节点的序列号是不连续的,则要进行丢包

补偿,如果丢包的个数为1,即lastSeq+4 == head.data.seq,则再播放一次上次播放的音频包:具体来说,如果有序列号依次为:2,4,6,10,12,...

上次播放到序列号6为止,而这次要从序列号10开始播放,丢失了序列号为8的音频包,因此,在播放序列号10之前,还要再播放一次序列号6;即播放序列为:

2,4,6,6,10,12,...

如果丢包的个数大于等于2,即lastSeq+4 < head.data.seq,则再播放一次上次播放的音频包,同时,当前音频包要播放两次,具体来说:如果有序列号依次为:

2,4,6,12,14,...上次播放到序列号6为止,而这次要从序列号12开始播放,丢失了序列号为8,10的两个音频包,因此要先播放序列号6,播放序列号12,再接着播放序列号12,再接着播放后面的序列号,即播放序列为:2,4,6,6,12,12,14,...

如丢包补偿的实现如下:

if (_lastAudioPlayRes) {

    var seqLen:uint = voiceRes.seq - _lastAudioPlayRes.seq;

    if (seqLen == 4) {

        _ns.appendBytes(_lastAudioPlayRes.flvBytes);

    } else if (seqLen > 4) {

        _ns.appendBytes(_lastAudioPlayRes.flvBytes);

        _ns.appendBytes(voiceRes.flvBytes);

       appendBytes(voiceRes.flvBytes);

    }

}

6.   flushLater函数:

 当flush函数的返回值为0时,表示此次flush函数没有找到要播放的一小段音频包,此时,如果链表中的音频包的播放时间累积总和大于MAX_PLAY_TIME,则传递给flushLater()的参数为minPlayTime==0,_audioBufTimeMax,表示此时只要找到一个音频包,就可以播放,避免每次flush都没找到音频包

 否则,若链表中的音频包在netstream中的缓冲时间小于audio_flush_time,且netStream的播放缓冲还没满,则计算最小的播放时间minPlayTime = uint((AUDIO_BUF_TIME_MAX - bufLen)*1000);,然后在链表中找出几帧音频包,使得他们的播放时间总和(包括丢失的包的播放时间)大于minPlayTime即可播放

具体来说:如果有音频包序列为:2,4,6,10,14,16,18...

上次播放到序列号为6为止,然后这一次在调用flush的时候,由于lastSeq+2<head.data.seq,因此这一次没找到要播放的音频包,flushCnt==0,因此会比较此时播放链表中的音频包序列10,14,16,18 ...的播放时间总和与MAX_PLAY_TIME的大小,如果大于MAX_PLAY_TIME,表明NetStream中的播放缓冲已经满了,于是调用flushLater(),只要找到一帧音频包,就进行播放;如果小于MAX_PLAY_TIME,则表明NetStream中的播放缓冲还没满,还有一小段时间bufLen=bufTimeMax-netstream.bufferLength;所以需要在播放链表中找到几帧音频包,满足播放时间大于bufLen时,就进行播放

7. 平滑Rtt的计算,参考公式如下:

var smoothRtt:uint = (n * _smoothRttSum / _rttCnt + curRtt) / m;

var deltaRtt:int = curRtt - smoothRtt;

var smoothDeltaRtt:int = (n * _smoothRttDeltaSum / _rttCnt + deltaRtt) / m;

var rtt:int = smoothRtt + smoothDeltaRtt;

说明:n,m的取值得根据具体情况,取经验值,上述的MAX_PLAY_TIME,AUDIO_BUF_TIME_MAX也要取相应的经验值

 

转载请注明出处:山水间博客,http://blog.csdn.net/linyanwen99/article/details/8985656

 

原创粉丝点击