现代智能电视中都有多种音频输出方式,除了喇叭之外,还有Spdif out、HDMI ARC输出。一般来说,设计时都是排他型输出,比如选择spdif out输出时喇叭会自动静音或者直接不送音频数据了。当然也有例外,有的设计是会同时输出,这样由于链路的差距,两种输出通路间会有一个延迟,延迟超过一定时间(有数据说是20ms),人耳就能明显感受到差距。为了解决这个问题,有的产品在设计时会有UI供用户自己调整音频输出delay的时间。
完成这个音频delay的需求,是实现了一个circle buffer做缓存,音频delay多少ms就将实际buffer数据延后输出。
由于这个delay是会在播放中实时调节,调节过程中,buffer延后,前面一段都是0数据,这样听起来会有明显的噪音,正常情况下音频数据是连续的,比如delay了10ms,circle buffer里的数据将会是10ms的0数据拼接上缓存的有效数据,这样一起送到底层输出时就会听到滋滋的噪音,用户UI不断调节delay,这个滋滋声会很频繁,严重影响听感。
为了优化体验,尝试一个思路就是不让缓存buffer中有0的空数据,那么这多出来的空间数据从哪里来呢?开始的时候有试过从缓冲区的有效数据中copy部分过来,等于delay时重复播放一小段数据,但是这个方法实际效果并不好,因为重复播放一段音频数据,数据衔接处是不连续的,从频谱上很容易看出来有一条竖线的尖刺,人耳听起来仍会有明显的噪音。所以既要保证数据不为空又要保证数据是过渡连续平滑的,就想到了重采样。
基本思路就是将有效数据拉长或缩短至调整delay后的buffer空间。比如缓存区有50ms数据,现在delay了10ms,那么就把50ms数据插值重采样成60ms,回调例如从delay 50ms调整到delay 40ms,就把数据抽取压缩。
直接上代码,基本思路就是怎么样从src源数据填充到dst目标数据:
if (out->speaker_last_delay_size != delay_buffer_size) {
int i;
short *pbuf;
unsigned index = 0;
float mPhaseFraction = 0;
float mPhaseFraction1 = 0;
float PhaseFraction = 0;
int in_frame = 0;
int left_byte_size = out->speaker_delay_buf.size -(out->speaker_delay_buf.rd - out->speaker_delay_buf.start_add );
short *sample = (short *)out->speaker_delay_buf.rd;
pbuf = (short*)data_temp_src;
memset(data_temp_src, 0 ,MAX_DELAY_SIZE+FRAME_SIZE);
memset(data_temp_dst, 0 ,MAX_DELAY_SIZE+FRAME_SIZE);
in_frame = delay_buffer_size >> 2;
PhaseFraction =(float) out->speaker_last_delay_size /delay_buffer_size ;
if (out->speaker_delay_buf.wr != out->speaker_delay_buf.rd && out->speaker_last_delay_size != 0) {
read_from_buffer(out->speaker_delay_buf.rd, pbuf, out->speaker_last_delay_size,
out->speaker_delay_buf.start_add, out->speaker_delay_buf.size);
if (PhaseFraction > 0.0 && in_frame > 0) {
for (i = 0; i < in_frame; i++) {
data_temp_dst[2 * i] = ( pbuf[index * 2] + (short)((pbuf[(index + 1) * 2] - pbuf[index * 2]) * mPhaseFraction1));
data_temp_dst[2 * i + 1] = (pbuf[index * 2 + 1] + (short)((pbuf[(index + 1) * 2 + 1] - pbuf[index * 2 + 1]) * mPhaseFraction1));
mPhaseFraction += PhaseFraction;
index = mPhaseFraction;
mPhaseFraction1 = mPhaseFraction - index;
}
write_to_buffer(out->speaker_delay_buf.rd, data_temp_dst, delay_buffer_size,
out->speaker_delay_buf.start_add, out->speaker_delay_buf.size);
}
}
out->speaker_last_delay_size = delay_buffer_size;
}
重采样的算法也是从android源码里找到移过来的,比较简单的插值算法