大家好,欢迎来到IT知识分享网。
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 类图,有结构体定义、模块关系、函数调用,方便标示结构模块的层次及关系):
-
snd_cards
记录着所注册的声卡实例,每个声卡实例有着各自的逻辑设备,如 PCM 设备、CTL 设备、MIDI 设备等,
并一 一记录到 snd_card 的 devices 链表上 -
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 属性文件。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/11863.html