音頻文件在播放時出現聲音斷斷續續,一卡一卡的或類似“爆破”(Pop-Click)雜音的現象,稱之爲 Xrun(可以是 underrun,也可以是 overrun)。
分析:
alsa driver使用了環形緩衝區對dma buffer進行管理,如下圖。
播放時,應用程序把音頻數據源源不斷地寫入dma buffer中,然後相應platform的dma操作則不停地從該buffer中取出數據,經dai送往codec中,當寫入的數據慢,播放的數據快時聲音會出現斷斷續續,一卡一卡的現象。
錄音時,codec源源不斷地把A/D轉換好的音頻數據經過dai送入dma buffer中,而應用程序則不斷地從該buffer中讀走音頻數據,當寫入的數據快,播放的數據慢時,當數據量較多buffer有可能被沖掉,聲音會出現類似“爆破”(Pop-Click)雜音的現象。
[RK3288][Android5.1] 調試如下:
1、播放的數據快,寫入的數據慢時,觸發DMA中斷,將音頻數據寫入dma buffer中,出現一卡一卡的現象!檢查codec和machine的採樣頻率,設置ASoc框架的Codec驅動和Machine驅動的輸出freq = 12288000。
//設置codec dai的主時鐘,採樣率
static int xvf3500_set_dai_sysclk(struct snd_soc_dai *codec_dai,int clk_id, unsigned int freq, int dir)
{
...
freq = 12288000;
switch (freq) {
...
case 12288000:
case 16934400:
case 24576000:
case 33868800:
xvf3500->sysclk_constraints = &constraints_12288;
xvf3500->sysclk = freq;
return 0;
...
}
return -EINVAL;
}
//在machine設置codec dai採樣率,保持codec驅動採樣率一致
static int rk29_xvf3500_init(struct snd_soc_pcm_runtime *rtd)
{
...
ret = snd_soc_dai_set_sysclk(codec_dai, 0,12288000, SND_SOC_CLOCK_IN);
if (ret < 0) {
printk(KERN_ERR "Failed to set xvf3500 SYSCLK: %d\n", ret);
return ret;
}
...
return 0;
}
2、當寫入的數據快,播放的數據慢時,當數據量較多buffer有可能被沖掉,聲音會出現類似“爆破”或“呲呲”雜音的現象。通過調整硬件抽象層的period_size和period_count,來改變dma的傳輸數據量。
struct pcm_config {
unsigned int channels;
unsigned int rate;
unsigned int period_size;
unsigned int period_count;
enum pcm_format format;
unsigned int start_threshold;
unsigned int stop_threshold;
unsigned int silence_threshold;
int avail_min;
};
合理的pcm_config可以做到更好的低時延和功耗。解釋一下結構中的各個參數,每個參數的單位都是frame(1幀 = 通道*採樣位深):
- period_size. 每次傳輸的數據長度。值越小,時延越小,cpu佔用就越高。
- period_count. 緩之衝區period的個數。緩衝區越大,發生XRUN的機會就越少。
- format. 定義數據格式,如採樣位深,大小端。
- start_threshold. 緩衝區的數據超過該值時,硬件開始啓動數據傳輸。如果太大, 從開始播放到聲音出來時延太長,甚至可導致太短促的聲音根本播不出來;如果太小, 又可能容易導致XRUN.
- stop_threshold. 緩衝區空閒區大於該值時,硬件停止傳輸。默認情況下,這個數 爲整個緩衝區的大小,即整個緩衝區空了,就停止傳輸。但偶爾的原因導致緩衝區空, 如CPU忙,增大該值,繼續播放緩衝區的歷史數據,而不關閉再啓動硬件傳輸(一般此 時有明顯的聲音卡頓),可以達到更好的體驗。
- silence_threshold. 這個值本來是配合stop_threshold使用,往緩衝區填充靜音 數據,這樣就不會重播歷史數據了。但如果沒有設定silence_size,
- avail_min. 緩衝區空閒區大於該值時,pcm_mmap_write()才往緩衝寫數據。這個 值越大,往緩衝區寫入數據的次數就越少,面臨XRUN的機會就越大。Android samsung tuna 設備在screen_off時增大該值以減小功耗,在screen_on時減小該 值以減小XRUN的機會。
在不同的場景下,合理的參數就是在性能、時延、功耗等之間達到較好的平衡。
//修改HAL層的period_size 和 period_count路徑...hardware/rockchip/audio/tinyalsa_hal/audio_hw.h
//播放
struct pcm_config pcm_config = {
.channels = 2,
.rate = 48000,
.period_size = 2048,
.period_count = 4,
.format = PCM_FORMAT_S16_LE,
};
//錄音
struct pcm_config pcm_config_in = {
.channels = 2,
.rate = 48000,
.period_size = 128,//1024
.period_count = 32,//4
.format = PCM_FORMAT_S16_LE,
};
調試技巧:實時錄音會有呲呲噪音問題,修改pcm_config_in的period size和period_count大小,使其period_size * period_count的乘積爲4096不變。
總結:
這種方法通過增加或減少音頻數據的 period_count或period_size來進行補償。但這樣也會使音頻播放/錄音的數據準備時間變長,增加音頻操作的延遲。