解決Android 音頻Xrun問題

音頻文件在播放時出現聲音斷斷續續,一卡一卡的或類似“爆破”(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來進行補償。但這樣也會使音頻播放/錄音的數據準備時間變長,增加音頻操作的延遲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章