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