原文地址:http://blog.csdn.net/sinat_26551021/article/details/79484042
1. 前言
寫這篇的主要目的是爲了對#MINI2440實現語音識別# (一)整體概述和實現流程記錄中,在驅動UDA1341聲卡過程中遇到的問題進行記錄和闡述。這裏不對Linux中ALSA架構的音頻子系統進行詳細闡述,具體參考以下幾篇文章:
MINI2440+UDA1341TS分析之一
MINI2440+UDA1341TS分析之二
MINI2440+UDA1341TS分析之三
2. 基本知識點
1.處理器s3c2440和聲卡UDA1341通過L3、IIS兩種總線相連,其中L3總線用於對UDA1341的寄存器進行讀寫,從而實現對UDA1341的配置,而IIS總線是用來走音頻數據流的(IIS總線由荷蘭飛利浦公司定義)。
2.嵌入式Linux中音頻部分用的是ASOC架構,它是在ALSA架構之後再封裝的一層。
3. 遇到的問題
3.1 問題1
問題描述:板子能成功識別出聲卡,也能使用aplay播放音頻,但是使用arecord錄製音頻時,始終沒有聲音。
解決辦法:從原理圖中可以看到,UDA1341有兩個輸入端VIN1、VIN2,其中麥克風接在VIN2上,所以我們需要選擇VIN2作爲音頻輸入口。
在linux-3.6.5\sound\soc\codecs\uda134x.c中大約186行添加uda134x_write(codec, 2, 2|(5U<<2)); //把錄音通道改爲 VIN2
,如下:
uda134x->slave_substream = substream;
} else
uda134x->master_substream = substream;
uda134x_write(codec, 2, 2|(5U<<2)); //把錄音通道改爲 VIN2
return 0;
}
static void uda134x_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
原因分析: 閱讀UDA1341芯片手冊,要修改輸入通道爲通道2,需將MM1、MM0位分別設成1、0,UDA1341寄存器讀寫方式參照MINI2440+UDA1341TS分析之一
3.2 問題2
問題描述:解決上述問題1之後,通arecord可成功錄製聲音,但是通過aplay播放音頻不正常,聲音會有循環卡頓現象。此外,會出現另外一個現象,就是在ARM上arecord的wav文件,通過aplay可正常播放,但是放到PC端播放就會出現卡頓,反過來(在PC端arecord)一樣的。
解決辦法和原因分析:
要播放音頻,有兩種方式:
1.應用程序取一段音頻數據傳遞給驅動程序,驅動程序再通過IIS總線傳給硬件播放聲音,然後應用程序再取出下一段音頻數據播放。這樣會導致播放出的聲音是一段一段的。
2.爲了避免方式1的問題,可以採用環形(即循環)BUFFER。在驅動程序中開闢一段很大的內存空間,分割成幾大塊,一塊叫一個period。應用程序不斷的往period扔數據,扔完一塊便指向下一塊period,到BUFFER末尾之後又回到BUFFER頭部。而驅動程序則從BUFFER頭部開始依次取出,通過DMA發送給硬件。這樣就可以避免音頻播放斷斷續續的問題。
以上過程反映到驅動程序在\linux-3.6.5\sound\soc\samsung\dma.c中即爲:
/*準備DMA傳輸*/
dma_prepare
/*place a dma buffer onto the queue for the dma system to handle.*/
dma_enqueue(substream);
dma_info.len = prtd->dma_period*limit; //此次DMA傳輸的長度
while (prtd->dma_loaded < limit)
{
//prtd->params->ops->prepare = s3c_dma_prepare
prtd->params->ops->prepare(prtd->params->ch, &dma_info);
s3c2410_dma_set_buffdone_fn //設置回調函數
s3c2410_dma_enqueue
s3c2410_dma_ctrl
s3c2410_dma_start
/*DMA傳輸完成後調用audio_buffdone*/
audio_buffdone
prtd->dma_pos += prtd->dma_period; //更新位置
snd_pcm_period_elapsed(substream); //刷新狀態
...
pos += prtd->dma_period; //更新位置
}
從上面可以看到,DMA傳輸過程存在兩個問題:
1、dma_info.len = prtd->dma_period*limit; 每次DMA傳輸的長度設成了整個BUFFER的長度,而不是一個period的大小,
此處應改爲dma_info.len = prtd->dma_period;如下:
dma_info.fp = audio_buffdone;
dma_info.fp_param = substream;
dma_info.period = prtd->dma_period;
// dma_info.len = prtd->dma_period*limit;
dma_info.len = prtd->dma_period;
while (prtd->dma_loaded < limit) {
2、在DMA傳輸完成後調用audio_buffdone時,更新了一次位置,之後又pos += prtd->dma_period;
更新了一次位置,從而導致播放不正常,這裏把audio_buffdone中更新位置部分刪除即可,如下
static void audio_buffdone(void *data)
{
struct snd_pcm_substream *substream = data;
struct runtime_data *prtd = substream->runtime->private_data;
pr_debug("Entered %s\n", __func__);
// if (prtd->state & ST_RUNNING) {
// prtd->dma_pos += prtd->dma_period;
// if (prtd->dma_pos >= prtd->dma_end)
// prtd->dma_pos = prtd->dma_start;
// if (substream)
// snd_pcm_period_elapsed(substream);
// spin_lock(&prtd->lock);
// if (!samsung_dma_has_circular()) {
// prtd->dma_loaded--;
// dma_enqueue(substream);
// }
// spin_unlock(&prtd->lock);
// }
if (prtd->state & ST_RUNNING) {
spin_lock(&prtd->lock);
if (!samsung_dma_has_circular()) {
prtd->dma_loaded--;
dma_enqueue(substream);
}
spin_unlock(&prtd->lock);
if (substream)
snd_pcm_period_elapsed(substream);
}
}
4. 源碼路徑
留坑,github路徑
5. 後續問題及補充
1、以後有時間對ASOC架構進行詳細剖析,暫時留坑。