現代智能電視中都有多種音頻輸出方式,除了喇叭之外,還有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源碼裏找到移過來的,比較簡單的插值算法