1.嵌入式音频系统硬件连接
下图所示的嵌入式设备使用IIS将音频数据发送给编解码器。对编解码器的I/O寄存器的编程通过IIC总线进行。
2.音频体系结构-ALSA
ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构
在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。
展开剩余86%3.ALSA设备文件
ALSA驱动核心会创建和管理一些设备节点,比如:
/dev/snd/controlC0: 一个控制结点,(应用程序用它来控制声卡,例如通道选择,音量的控制等)
/dev/snd/pcmC0D0p:用于播放的pcm设备
/dev/snd/pcmC0D0c:用于录音的pcm设备
C0D0代表的是声卡0中的设备0,最后一个c代表capture,最后一个p代表playback。
4.声卡的建立流程
第一步,创建snd_card的一个实例
snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下.
第二步,创建声卡的功能部件(逻辑设备),例如PCM, Mixer等,并将逻辑设备与步骤一创建的声卡绑定
通常, alsa-driver的已经提供了一些常用的部件的创建函数,PCM ---- snd_pcm_new()、CONTROL -- snd_ctl_create()
第三步,将声卡注册进ALSA框架
经过以上的创建步骤之后,声卡的逻辑结构如下图所示:
5.PCM设备的创建
对于一个pcm设备,可以生成两个设备文件,一个用于playback,一个用于capture,代码中也确定了他们的命名规则:
playback -- pcmCxDxp,通常系统中只有一个声卡和一个pcm,它就是pcmC0D0p
capture -- pcmCxDxc,通常系统中只有一个声卡和一个pcm,它就是pcmC0D0c
新建一个pcm设备的过程:
snd_card_create ,pcm是声卡下的一个设备(部件),所以第一步是要创建一个声卡
snd_pcm_new, 调用该api创建一个pcm,在该api中会做以下事情:
建立playback stream,相应的substream也同时建立
建立capture stream,相应的substream也同时建立
调用snd_device_new()把该pcm挂到声卡中,参数ops中的dev_register字段指向了函数snd_pcm_dev_register,这个回调函数会在声卡的注册阶段被调用
snd_pcm_set_ops, 设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数
snd_card_register 注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序( alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
6.Control设备的创建
Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或设置control的状态,从而达到控制音频Codec进行各种Mixer等控制操作。
要自定义一个Control,我们首先要定义3各回调函数:info,get和put。然后,定义一个snd_kcontrol_new结构:
static struct snd_kcontrol_new my_control __devinitdata = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Switch",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = 0xffff,
.info = my_control_info,
.get = my_control_get,
.put = my_control_put
};
7.音频数据的dma操作
soc-platform驱动的最主要功能就是要完成音频数据的传送,大多数情况下,音频数据都是通过dma来完成的
申请dma buffer
在声卡的建立阶段,pcm_new回调函数会被调用,函数进一步为playback和capture分别调用preallocate_dma_buffer函数分配dma内存,然后完成substream->dma_buffer的初始化赋值工作
在声卡的hw_params阶段, snd_soc_platform_driver结构的ops->hw_params会被调用,在该回调用,通常会使用api: snd_pcm_set_runtime_buffer()把substream->dma_buffer的数值拷贝到substream->runtime的相关字段中( .dma_area, .dma_addr, .dma_bytes),这样以后就可以通过substream->runtime获得这些地址和大小信息了。
dma buffer获得后,即是获得了dma操作的源地址,那么目的地址在哪里?其实目的地址当然是在dai中,也就是前面介绍的snd_soc_dai结构的playback_dma_data和capture_dma_data字段中,而这两个字段的值也是在hw_params阶段,由snd_soc_dai_driver结构的ops->hw_params回调,利用api: snd_soc_dai_set_dma_data进行设置的。紧随其后, snd_soc_platform_driver结构的ops->hw_params回调利用api: snd_soc_dai_get_dma_data获得这些dai的dma信息,其中就包括了dma的目的地址信息。这些dma信息通常还会被保存在substream->runtime->private_data中,以便在substream的整个生命周期中可以随时获得这些信息,从而完成对dma的配置和操作
dma buffer管理
播放时,应用程序把音频数据源源不断地写入dma buffer中,然后相应platform的dma操作则不停地从该buffer中取出数据,经dai送往codec中。录音时则正好相反, codec源源不断地把A/D转换好的音频数据经过dai送入dmabuffer中,而应用程序则不断地从该buffer中读走音频数据。
发布于:湖南省