Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)上
1、基本概念及逻辑关系
如上图,通过上一节声卡的学习我们已经知道PCM是声卡的一个子设备,或者表示一个PCM实例。
每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件。pcm实例数量的这种限制源于linux设备号所占用的位大小,如果以后使用64位的设备号,我们将可以创建更多的pcm实例。不过大多数情况下,在嵌入式设备中,一个pcm实例已经足够了。
一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。可以用如下图来表示他们直接的逻辑关系:
当一个子流已经存在,并且已经被打开,当再次被打开的时候,会被阻塞。
在实际的应用中,通常不会如上图这么复杂,大多数情况下是一个声卡有一个PCM实例,PCM下面有一个playback和capture,而playback和capture各自有一个substream。
PCM层有几个很重要的结构体,我们通过如下的UML图来梳理他们直接的关系。
图片地址:http://hi.csdn.net/attachment/201104/2/0_1301728746sAUd.gif
1、snd_pcm:挂在snd_card下面的一个snd_device。
2、snd_pcm中的字段:streams[2]:该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream。
3、snd_pcm_str中的substream字段:指向snd_pcm_substream结构。
4、snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是他的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数。
2、PCM创建流程
PCM的整个创建流程请参考如下时序图进行理解:
alsa-driver的中间层已经提供新建PCM的API:
2.1、创建PCM实例
int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm)
card:表示所属的声卡。
ID:PCM实例的ID(名字)。
device:表示目前创建的是该声卡下的第几个PCM,第一个PCM设备从0开始计数。
playback_count:表示该PCM播放流中将会有几个substream。
capture_count :表示该PCM录音流中将会有几个substream。
rpcm:返回的PCM实例。
该函数的主要作用是创建PCM逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的PCM操作函数(数据搬运时,需要调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备工作)。
2.2、设置PCM设备的操作函数
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, const struct snd_pcm_ops *ops)
pcm:上述snd_pcm_new 创建的PCM实例。
direction:是指SNDRV_PCM_STREAM_PLAYBACK或SNDRV_PCM_STREAM_CAPTURE,即设置为播放或者录音功能。
snd_pcm_ops:结构中的函数通常就是我们驱动要实现的函数。
2.3、定义PCM的操作函数
以AC97驱动(linux/sound/arm/pxa2xx-ac97.c)为例,在驱动中对于PCM进行了如下设置:
static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = { .open = pxa2xx_ac97_pcm_open, .close = pxa2xx_ac97_pcm_close, .hw_params = pxa2xx_pcm_hw_params, .prepare = pxa2xx_ac97_pcm_prepare, .trigger = pxa2xx_pcm_trigger, .pointer = pxa2xx_pcm_pointer, }; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pxa2xx_ac97_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pxa2xx_ac97_pcm_ops);
2.4、定义硬件参数
static const struct snd_pcm_hardware pxa2xx_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .period_bytes_min = 32, .period_bytes_max = 8192 - 32, .periods_min = 1, .periods_max = 256, .buffer_bytes_max = 128 * 1024, .fifo_size = 32, }; int pxa2xx_pcm_open(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dmaengine_dai_dma_data *dma_params; int ret; runtime->hw = pxa2xx_pcm_hardware; dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); if (!dma_params) return 0; /* * For mysterious reasons (and despite what the manual says) * playback samples are lost if the DMA count is not a multiple * of the DMA burst size. Let's add a rule to enforce that. */ ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); if (ret) return ret; ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); if (ret) return ret; ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) return ret; return snd_dmaengine_pcm_open( substream, dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev, dma_params->chan_name)); }