我正在为我的VOIP应用程序寻找Java中的自适应抖动缓冲区实现。我为我的应用程序编写了一个固定的抖动缓冲区,但由于网络质量差,我遇到了缓冲区欠载或缓冲区溢出问题。
是否有任何基于Java的自适应抖动缓冲区实现可直接用于我的应用程序或用作参考。
非常感谢任何帮助。
由于
答案 0 :(得分:8)
我已经解决了这个问题(在C语言中)了一段时间,而且当我认为我已经得到它时,互联网变得忙碌或以其他方式改变某个地方并且繁荣!一些不稳定的音频再次。好。我很确定我现在已经舔了它。
使用下面的算法,我的音质确实非常好。我将它与我在相同网络条件下运行的其他软电话进行了比较,并且表现明显更好。
我要做的第一件事是尝试确定我们正在注册的PBX或其他SIP代理是否在具有UA(软电话)的本地网络上。
如果是,我将我的jitterbuffer定义为100ms,如果没有,我使用200ms。这样我就可以限制我的延迟;甚至200ms也不会产生明显的会话故障或过度谈话。
因此。然后我使用你可用的任何类型的系统计数器,例如Windows = GetTickCount64(),用我的第一个数据包进入播放的毫秒精度时间填充变量。让我们称之为变量" x"。
然后当((GetTickCount64() - x)> jitterbuffer)为真时,我开始在该缓冲区上播放。
直接固定长度抖动缓冲器实现。这是一个棘手的问题。
虽然我正在解码RTP帧(如从muLaw到PCM)以缓冲它以进行播放,但我计算音频帧的AVERAGE ABSOLUTE幅度,并将其与帧一起保存以供播放。
我通过这样的结构来做到这一点:
typedef struct tagCCONNECTIONS {
char binuse;
struct sockaddr_in client;
SOCKET socket;
unsigned short media_index;
UINT32 media_ts;
long ssrc;
unsigned long long lasttimestamp;
int frames_buffered;
int buffer_building;
int starttime;
int ssctr;
struct {
short pcm[160];
} jb[AUDIO_BUFFER]; /* Buffered Audio frame array */
char jbstatus[AUDIO_BUFFER]; /* An array containing the status of the data in the CCONNETIONS::jb array */
char jbsilence[AUDIO_BUFFER];
int jbr,jbw; /* jbr = read position in CCONNECTIONS::jb array, jbw = write position */
short pcms[160];
char status;
/* These members are only used to buffer playback */
PCMS *outraw;
char *signal;
WAVEHDR *preparedheaders;
/**************************************************/
DIALOGITEM *primary;
int readptr;
int writeptr;
} CCONNECTIONS;
好的,请注意tagCCONNECTIONS :: jbsilence [AUDIO_BUFFER]结构成员。这样,对于tagCCONNECTIONS :: jb [x] .pcm []中的每个解码音频帧,都有关于该帧是否可听见的相应数据。
这意味着对于即将播放的每个音频帧,我们都会知道该帧是否可听见。
也...
#define READY 1
#define EMPTY 0
tagCCONNECTIONS :: jbstatus [AUDIO_BUFFER]字段让我们知道我们正在考虑播放的特定音频帧是READY还是EMPTY。在缓冲器下溢的理论情况下,它可能是空的,在这种情况下我们通常会等待它准备好,然后开始播放......
现在我的日常播放音频...我有两个主要功能。一个叫做pushframe(),一个叫popframe()。
我的线程打开网络连接并接收RTP调用pushframe(),它将muLaw转换为PCM,计算帧的AVERAGE ABSOLUTE幅度,如果它听不见,则将其标记为静音,并标记:: jbstatus [x]为READY
然后在播放音频的话题中,我们首先检查抖动缓冲时间是否已经过期
if ( ( GetTickCount64() - x ) > jitterbuffer ) {...}
然后,我们检查要播放的下一帧是否为READY(意味着它确实已被填充)。
然后我们检查帧框架是否准备就绪,如果它是可听见的还是静止的!
***重要
基本上,我们知道200ms的抖动缓冲区可以容纳10个20ms的音频帧。
如果在最初的200ms抖动缓冲延迟(保存音频)之后的任何时刻我们排队的音频帧数下降到10以下(或者jitterbuffer / 20),我们会进入我所说的" buffer_building&#34 ;模式。如果我们计划播放的下一个音频帧是静音的,我们告诉程序抖动缓冲器还没有充满,距离充满还有20毫秒,但我们继续播放框架我们现在正在进行,因为它是我们看到的NEXT框架"沉默" ...再一次。我们只是不播放静音帧并使用静音期等待入站帧重新填充我们的缓冲区。
tagCCONNECTIONS::lasttimestamp = GetTickCount64() - (jitterbuffer-20);
在假设"假设的情况下,这将有一段完全的沉默。沉默,但允许缓冲区补充自己。然后,当我再次完整的10帧时,我会退出" buffer_building"模式,只播放音频。
我输入" buffer_building"模式,即使我们从一个完整的缓冲区缩短一帧,因为一个啰嗦的人可能正在说话,并且没有多少沉默。即使在" buffer_building期间,这也可能会快速耗尽缓冲区。模式。
现在......"什么是沉默?"我听你问。在我的处理中,我将硬编码静音视为任何具有低于200的AVERAGE ABSOLUTE 16位PCM幅度的帧。我的数字如下:
int total_pcm_val=0;
/* int jbn= whatever frame we're on */
for (i=0;i<160;i++) {
total_pcm_val+=ABS(cc->jb[jbn].pcm[i]);
}
total_pcm_val/=160;
if (total_pcm_val < 200) {
cc->jbsilence[jbn] = 1;
}
现在,我实际上正计划在该连接上保持整体平均幅度,并且如果当前音频帧的振幅刚刚接收到总平均幅度的5%或更小,则可能正在玩耍,然后我们认为框架是静音的,或者可能是2%...我不知道,但是如果有很多风或背景噪音,那么“沉默”的定义就是这样。可以适应。我必须使用它,但我相信这是补充抖动缓冲区的关键。
如果没有重要的信息可以收听,并保持实际信息(他们的声音)清晰。
我希望这会有所帮助。在解释事情时,我有点分散,但我对我的VoIP应用程序的声音非常非常满意。