Kernel - 3.4.5
SoC - Samsung exynos
CODEC - WM8994
Machine - goni_wm8994
Userspace - tinyalsa
ALSA/ASoC驱动有如下三部分构成:
· Platform:通常指某款SoC平台,如exynos、omap、qcom等等。Platform又可细分两部分:
1. cpu dai:在嵌入式系统里面通常指CPU的I2S、PCM总线控制器,负责将音频数据从I2S tx FIFO搬运到CODEC(回放的情形,录制则方向相反)。cpu_dai通过snd_soc_register_dai()来注册。注:DAI是Digital Audio Interface的缩写,分为cpu_dai和codec_dai,这两者通过I2S/PCM总线连接;AIF是AudioInterface的缩写,一般分为I2S和PCM接口。
2. pcm dma:负责将dmabuffer中的音频数据搬运到I2S tx FIFO,这部分的逻辑比较复杂,以下几篇会对它详细阐述。音频dma驱动通过snd_soc_register_platform()来注册。值得留意的是:某些情形下是不需要dma操作的,比如Modem和CODEC直连,因为Modem本身已经把数据送到PCM FIFO了,这时只需启动codec_dai接收数据即可;该情形下,Machine驱动dai_link中需要指定.platform_name = "snd-soc-dummy", 这是虚拟出来的platform驱动,实现见sound/soc/soc-utils.c。
· Codec:对于Playback来说,userspace送过来的PCM数据是经过抽样量化出来的数字信号,在codec经过DAC转换成模拟信号送到外放耳机输出,这样我们就可以听到声音了。Codec字面意思是编解码器,但芯片里面的功能部件很多,常见的有AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的codec芯片还有EQ、DSP、SRC、DRC、AGC、Echo canceller、Noise suppression等部件。
· Machine:指某一款机器,它把cpu_dai、codec_dai、modem_dai各个音频接口通过定义dai_link链结起来,然后注册snd_soc_card。和上面两个不一样,Platform和CODEC驱动一般是可以重用的,而Machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:DAIs之间的链结;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External OSC作为I2S、CODEC模块的基准时钟源等等。
从上面的描述来看,对于回放的情形,PCM数据流向大致是:
copy_from_user DMA I2S AIF->DAC->PGA/Mixer
^ ^ ^ ^
| | | |
userspace------>dma buffer----->I2S tx FIFO----->CODEC------>SPK/HP/Earp
这系列初步定为如下几个部分:
· 分析ALSA/ASoC的注册过程,soc-core如何找出并绑定dai_link上配置的cpu_dai、codec_dai、platform并逐一完成各自的初始化(probe)。
· 当pcm_open时,如何建立hw constraints;如何检查硬件参数(hw params)是否在hw constraints约束范围之内;如何回调platform/codec/machine的hw_params()完成各音频接口的时钟/格式配置。
· 当pcm_write时,1)如何把音频数据从用户态缓冲区拷贝到dma_alloc_writecombine()分配出来的dma buffer中;2)如何启动DMA把数据从dmabuffer搬运到I2S tx FIFO;3)如何启动I2S总线控制器把数据从I2S tx FIFO搬运到codec。但在这之前,会先分析dma ops函数集的作用及dma buffer分配过程,dma搬运的源地址和目的地址如何设定,这部分平台相关。
· xrun出现的根源是什么?有些资料这样提及:“xrun指的是,声卡period一到,引发一个中断,告诉alsa驱动,要填入数据,或读走数据,但是问题在于alsa的读取和写入操作必须用户调用writei和readi才会发生的,它不会去缓存数据;如果上层没有用户调用writei和readi,那么就会产生overrun(录制时,数据都满了,还没被读走)和underrun(需要数据来播放,却不写入数据),统称为xrun”。这里源码上分析它是如何产生的,这样我们知道需要正确配置好哪个参数才能更好避免xrun或取得更好的表现性能。
· 最后探讨Android音频系统的deep buffer和low latency。
dai_link:machine驱动中定义的音频数据链路,它指定用到的cpu_dai、codec_dai、platform分别是什么。比如对于goni_wm8994平台的media链路:codec="wm8994-codec"、codec_dai="wm8994-aif1"、cpu_dai="samsung-i2s",pcm_dma="samsung-audio",这四者就构成了一条音频数据链路。一个系统可能有多个音频数据链路,比如media和voice,因此可以定义多个dai_link。如WM8994的典型设计,有三个dai_link,分别是AP-AIF1的"HIFI",BP-AIF2的"Voice",BT-AIF3(AIF3并不是真正的DAI,它需要桥接到AIF1或AIF2上才能工作)。
代码如下:
static struct snd_soc_dai_link goni_dai[] = {
{
.name = "WM8994",
.stream_name = "WM8994 HiFi",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-audio",
.codec_name = "wm8994-codec.0-001a",
.init = goni_wm8994_init,
.ops = &goni_hifi_ops,
}, {
.name = "WM8994 Voice",
.stream_name = "Voice",
.cpu_dai_name = "goni-voice-dai",
.codec_dai_name = "wm8994-aif2",
.codec_name = "wm8994-codec.0-001a",
.ops = &goni_voice_ops,
},
};
hw constraints:指平台本身的硬件特性限制,如所能支持的通道数/采样率/数据格式、DMA支持的周期大小(period size)范围、周期次数(period count)范围等,通过snd_pcm_hardware结构直观描述,如下:
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128*1024,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE*2,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
hw params:用户层设置的硬件参数,如channels、sample rate、pcm format、period size、period count;这些参数受hw constraints约束。
sw params:用户层设置的软件参数,如start threshold、stop threshold、silence threshold。