原文地址: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架构进行详细剖析,暂时留坑。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐