Audio System 四 之声卡和PCM设备建立过程


前面几章分析了 Codec、Platform、Machine 驱动的组成部分及其注册过程,这三者都是物理设备相关的,大家应该对音频物理链路有了一定的认知。
接着分析音频驱动的中间层,由于这些并不是真正的物理设备,故我们称之为逻辑设备。

PCM 逻辑设备,我们又习惯称之为 PCM 中间层或 pcm native,起着承上启下的作用:

  • 往上是与用户态接口的交互,实现音频数据在用户态和内核态之间的拷贝;

  • 往下是触发 codec、platform、machine 的操作函数,实现音频数据在 dma_buffer <-> cpu_dai <-> codec 之间的传输。

后面章节将会详细分析这个过程,这里还是先从声卡的注册谈起。

九、声卡和PCM设备建立过程

9.1 声卡设备

adb shell cat /proc/asound/devices 
  2: [ 0]   : control				  ---> 用于声卡控制的 CTL 设备,如通路控制、音量调整等
  3: [ 0- 0]: digital audio playback  ---> 用于回放的 PCM 设备
  4: [ 0- 0]: digital audio capture   ---> 用于录制的 PCM 设备
  5: [ 0- 1]: digital audio playback
  6: [ 0- 1]: digital audio capture
 ......
 27: [ 0-16]: digital audio playback
 28: [ 0-16]: digital audio capture
 29: [ 0-17]: digital audio playback
 30: [ 0-17]: digital audio capture
 33:        : timer					 ---> 定时器设备

设备节点如下:

adb shell ls -l /dev/snd
crw-rw---- 1 system audio 116,  51 1970-06-19 02:07 comprC0D24
crw-rw---- 1 system audio 116,  52 1970-06-19 02:07 comprC0D27
crw-rw---- 1 system audio 116,  53 1970-06-19 02:07 comprC0D28
......
crw-rw---- 1 system audio 116,   2 1970-06-19 02:07 controlC0	---> 用于声卡控制的 CTL 设备,如通路控制、音量调整等
crw-rw---- 1 system audio 116,  59 1970-06-19 02:07 hwC0D1000
crw-rw---- 1 system audio 116,  66 1970-06-19 02:07 hwC0D11
crw-rw---- 1 system audio 116,  67 1970-06-19 02:07 hwC0D12
crw-rw---- 1 system audio 116,  76 1970-06-19 02:07 hwC0D13
......
crw-rw---- 1 system audio 116,  13 1970-06-19 02:07 pcmC0D6c
crw-rw---- 1 system audio 116,  14 1970-06-19 02:07 pcmC0D7p	---> 用于回放的 PCM 设备
crw-rw---- 1 system audio 116,  15 1970-06-19 02:07 pcmC0D8c	---> 用于录制的 PCM 设备
crw-rw---- 1 system audio 116,  33 1970-06-19 02:07 timer		---> 定时器设备

可以看到这些设备节点的 Major=116,Minor 则与 /proc/asound/devices 所列的对应起来,都是字符设备。
上层可以通过 open / close / read / write / ioctl 等系统调用来操作声卡设备,这和其他字符设备类似,
但一般情况下我们会使用已封装好的用户接口库如 tinyalsa、alsa-lib。

9.1.1 声卡结构概述

回顾下 ASoC 是如何注册声卡的,详细请参考章节ASoC machine driver,这里仅简单陈述下:

  • Machine 驱动初始化时,.name = “soc-audio” 的 platform_device 与 platform_driver 匹配成功,触发 soc_probe() 调用;

  • 继而调用 snd_soc_register_card():

    • (1) 为每个音频物理链路找到对应的 codec、codec_dai、cpu_dai、platform 设备实例,完成 dai_link 的绑定;
    • (2) 调用 snd_card_create() 创建声卡;
    • (3) 依次回调 cpu_dai、codec、platform 的 probe() 函数,完成物理设备的初始化;
    • (4) 随后调用 soc_new_pcm():
    • (5) 设置 pcm native 中要使用的 pcm 操作函数,这些函数用于驱动音频物理设备,包括 machine、codec_dai、cpu_dai、platform;
    • (6) 调用 snd_pcm_new() 创建 pcm 逻辑设备,回放子流和录制子流都在这里创建;
    • (7) 回调 platform 驱动的 pcm_new(),完成音频 dma 设备初始化和 dma buffer 内存分配;
    • (8) 最后调用 snd_card_register() 注册声卡。

关于音频物理设备部分(Codec/Platform/Machine)不再累述,下面详细分析声卡和 PCM 逻辑设备的注册过程。

上面提到声卡驱动上挂着多个逻辑子设备,有 pcm 音频数据流、control 混音器、midi 迷笛、timer 定时器等。

                  +-----------+
                  | snd_card  |
                  +-----------+
                    |   |   |
        +-----------+   |   +------------+
        |               |                |
+-----------+    +-----------+    +-----------+
 |  snd_pcm  |    |snd_control|    | snd_timer |    ...
 +-----------+    +-----------+    +-----------+

这些与声音相关的逻辑设备都在结构体 snd_card 管理之下,可以说 snd_card 是 alsa 中最顶层的结构。
我们再看看 alsa 声卡驱动的大致结构图
(不是严格的 UML 类图,有结构体定义、模块关系、函数调用,方便标示结构模块的层次及关系):

在这里插入图片描述

  1. snd_cards
    记录着所注册的声卡实例,每个声卡实例有着各自的逻辑设备,如 PCM 设备、CTL 设备、MIDI 设备等,
    并一 一记录到 snd_card 的 devices 链表上

  2. snd_minors
    记录着所有逻辑设备的上下文信息,它是声卡逻辑设备与系统调用 API 之间的桥梁;
    每个 snd_minor 在逻辑设备注册时被填充,在逻辑设备使用时就可以从该结构体中得到相应的信息(主要是系统调用函数集 file_operations)


9.1.2 声卡的创建snd_card_create()

[->sound/core/init.c]
/**
 *  snd_card_new - create and initialize a soundcard structure
 *  @parent: the parent device object
 *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
 *  @xid: card identification (ASCII string)
 *  @module: top level module for locking
 *  @extra_size: allocate this extra size after the main soundcard structure
 *  @card_ret: the pointer to store the created card instance
 *
 *  Creates and initializes a soundcard structure.
 *
 *  The function allocates snd_card instance via kzalloc with the given
 *  space for the driver to use freely.  The allocated struct is stored
 *  in the given card_ret pointer.
 *
 *  Return: Zero if successful or a negative error code.
 */
int snd_card_new(struct device *parent, int idx, const char *xid,
		    struct module *module, int extra_size,
		    struct snd_card **card_ret)

注释非常详细,简单说下:

  • idx:声卡的编号,如为 -1,则由系统自动分配
  • xid:声卡标识符,如为 NULL,则以 snd_card 的 shortname 或 longname 代替
  • card_ret:返回所创建的声卡实例的指针

如下是Google Pixel手机的声卡信息:

adb shell
sailfish:/ $ cat /proc/asound/cards
 0 [msm8996tashamar]: msm8996-tasha-m - msm8996-tasha-marlin-snd-card
                      msm8996-tasha-marlin-snd-card

9.1.3 逻辑设备的创建

当声卡实例建立后,接着可以创建声卡下面的各个逻辑设备了。
每个逻辑设备创建时,都会调用 snd_device_new() 生成一个 snd_device 实例,
并把该实例挂到声卡 snd_card 的 devices 链表上。

alsa 驱动为各种逻辑设备提供了创建接口,

如下:

PCM snd_pcm_new()
CONTROL snd_ctl_create()
MIDI snd_rawmidi_new()
TIMER snd_timer_new()
SEQUENCER snd_seq_device_new()
JACK snd_jack_new()

这些接口的一般过程如下:

int snd_xxx_new()
{
    // 这些接口供逻辑设备注册时回调
    static struct snd_device_ops ops = {
        .dev_free = snd_xxx_dev_free,
        .dev_register = snd_xxx_dev_register,
        .dev_disconnect = snd_xxx_dev_disconnect,
    };
    // 逻辑设备实例初始化

    // 新建一个设备实例 snd_device,挂到 snd_card 的 devices 链表上,
    // 把该逻辑设备纳入声卡的管理当中,SNDRV_DEV_xxx 是逻辑设备的类型
    return snd_device_new(card, SNDRV_DEV_xxx, card, &ops);
}

其中 snd_device_ops 是声卡逻辑设备的注册函数集,dev_register() 回调尤其重要,
它在声卡注册时被调用,用于建立系统的设备节点,
/dev/snd/ 目录的设备节点都是在这里创建的,
通过这些设备节点可系统调用 open/release/read/write/ioctl… 访问操作该逻辑设备。

例如 snd_ctl_dev_register():

[->/sound/core/control.c]
static const struct file_operations snd_ctl_f_ops =
{
	.owner =	THIS_MODULE,
	.read =		snd_ctl_read,
	.open =		snd_ctl_open,
	.release =	snd_ctl_release,
	.llseek =	no_llseek,
	.poll =		snd_ctl_poll,
	.unlocked_ioctl =	snd_ctl_ioctl,
	.compat_ioctl =	snd_ctl_ioctl_compat,
	.fasync =	snd_ctl_fasync,
};
/*
 * registration of the control device
 */
static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;
	int err, cardnum;
	char name[16];
	
	cardnum = card->number;
	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
		return -ENXIO;
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, &snd_ctl_f_ops, card, name)) < 0)
		return err;
	return 0;
}

事实是调用 snd_register_device_for_dev ():

[->/sound/core/sound.c]
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
				const struct file_operations *f_ops,
				void *private_data, const char *name, struct device *device)
{
	int minor;
	struct snd_minor *preg;
	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;
	preg->private_data = private_data;
	preg->card_ptr = card;
	mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
	minor = snd_find_free_minor(type);
#else
	minor = snd_kernel_minor(type, card, dev);
#endif
	......
	snd_minors[minor] = preg;
	preg->dev = device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name);
	......

	mutex_unlock(&sound_mutex);
	return 0;
}
  • 分配并初始化一个 snd_minor 实例;
  • 保存该 snd_minor 实例到 snd_minors 数组中;
  • 调用 device_create() 生成设备文件节点。

上面过程是声卡注册时才被回调的。


9.1.4 声卡的注册

当声卡下的所有逻辑设备都已经准备就绪后,就可以调用 snd_card_register() 注册声卡了:
在这里插入图片描述

  • 创建声卡的 sysfs 设备;
  • 调用 snd_device_register_all() 注册所有挂在该声卡下的逻辑设备;
  • 建立 proc 信息文件和 sysfs 属性文件。
发布了329 篇原创文章 · 获赞 66 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Ciellee/article/details/101752604