#MINI2440實現語音識別# (二)驅動聲卡UDA1341遇到的問題和解決辦法

原文地址: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架構進行詳細剖析,暫時留坑。

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