8.1声卡和PCM设备的建立过程
前面分析了codec,platform,machine驱动的组成部分及其注册过程,这三者都是物理设备相关的。
pcm逻辑设备,我们习惯称之为PCM中间层或pcm native,起着承上启下的作用:往上是与用户态接口的交互,实现音频数据在用户和内核态之间的拷贝;往下是触发codec,platform,machine的操作函数,实现音频数据在dma_buffer<->cpu_dai<->codec之间的传输。
声卡驱动中,一般挂载着多个逻辑设备,看看我们计算机的声卡驱动有几个逻辑设备:
$ cat /proc/asound/devices
2: [ 0] : control
3: [ 0- 0]: digital audio playback
4: [ 0- 0]: digital audio capture
5: [ 0- 3]: digital audio playback
6: [ 0- 7]: digital audio playback
7: [ 0- 8]: digital audio playback
8: [ 0- 0]: hardware dependent
9: [ 0- 3]: hardware dependent
33: : timer
Device | Description |
---|---|
digital audio playback | 用于回放的 PCM 设备 |
digital audio capture | 用于录制的 PCM 设备 |
control | 用于声卡控制的 CTL 设备,如通路控制、音量调整等 |
timer | 定时器设备 |
sequencer | 音序器设备 |
嵌入式系统中,我们更关心PCM和CTL这两种设备。
$ ll /dev/snd/
total 0
drwxr-xr-x 2 root root 60 7月 14 22:19 by-path
crw-rw----+ 1 root audio 116, 2 7月 14 22:19 controlC0
crw-rw----+ 1 root audio 116, 8 7月 14 22:19 hwC0D0
crw-rw----+ 1 root audio 116, 9 7月 14 22:19 hwC0D3
crw-rw----+ 1 root audio 116, 4 7月 15 13:43 pcmC0D0c
crw-rw----+ 1 root audio 116, 3 7月 15 15:35 pcmC0D0p
crw-rw----+ 1 root audio 116, 5 7月 14 22:20 pcmC0D3p
crw-rw----+ 1 root audio 116, 6 7月 14 22:20 pcmC0D7p
crw-rw----+ 1 root audio 116, 7 7月 14 22:20 pcmC0D8p
crw-rw----+ 1 root audio 116, 1 7月 14 22:19 seq
crw-rw----+ 1 root audio 116, 33 7月 14 22:19 timer
可以看到这些设备节点的Major=116,Minor则与/proc/asound/devices所列的对应起来,都是字符设备。上层可以通过open/close/read/write/ioctl等系统调用来操作声卡设备,这和其他字符设备类似,但一般情况下我们会使用已封装好的用户接口库如alsa-lib。
8.2声卡结构概述
回顾下ASoC是如何注册声卡的,这里仅简单陈述下:
下面详细分析声卡和PCM逻辑设备的注册过程。
上面提到声卡驱动上挂着多个逻辑子设备,有pcm音频数据流,control混音器,midi,timer定时器,sequencer音序器等。
+-----------+
| snd_card |
+-----------+
| | |
+-----------+ | +------------+
| | |
+-----------+ +-----------+ +-----------+
| snd_pcm | |snd_control| | snd_timer | ...
+-----------+ +-----------+ +-----------+
这些与声音相关的逻辑设备都在结构体snd_card管理之下,可以说snd_card是alsa中最顶层的结构。我们在看看alsa声卡驱动的大致结构图。
snd_cards:记录着所注册的声卡实例,每个声卡实例有着各自的逻辑设备,如PCM设备,CTL设备,MIDI设备等,并一一记录到snd_card的device链表上
snd_minors:记录着所有逻辑设备的上下文信息,它是声卡逻辑设备与系统调用api之间的桥梁;每个snd_minor在逻辑设备注册时被填充,在逻辑设备使用时就可以从该结构中的到相应的信息(主要是系统调用函数集file_operations)
8.3声卡的创建
声卡实例通过函数snd_card_new()来创建,其函数原型:
/**
* 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)
主要参数说明:
如下是我ubuntu16.04的声卡信息:
$ cat /proc/asound/cards
0 [PCH ]: HDA-Intel - HDA Intel PCH
HDA Intel PCH at 0xf2530000 irq 31
shortname,longname常用于打印信息,上面的声卡信息是通过如下函数打印出来的:
static void snd_card_info_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
int idx, count;
struct snd_card *card;
for (idx = count = 0; idx < SNDRV_CARDS; idx++) {
mutex_lock(&snd_card_mutex);
if ((card = snd_cards[idx]) != NULL) {
count++;
snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",
idx,
card->id,
card->driver,
card->shortname);
snd_iprintf(buffer, " %s\n",
card->longname);
}
mutex_unlock(&snd_card_mutex);
}
if (!count)
snd_iprintf(buffer, "--- no soundcards ---\n");
}
8.4逻辑设备的创建
当声卡实例建立后,接着可以创建声卡下面的各个逻辑设备看。每个逻辑设备创建式,都会调用snd_device_new()生成一个snd_device实例,并把该实例挂到声卡snd_card的devices链表上。alsa驱动为各种逻辑设备提供了创建接口,如下:
Device | Interface |
---|---|
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是逻辑设备的类型
snd_device_new(card, SNDRV_DEV_XXX, xxx, &ops);
}
其中snd_device_ops是声卡逻辑设备的注册函数集,dev_register()回调尤其重要,它在声卡注册时被调用,用于建立系统的设备节点,/dev/snd/目录的设备节点都是在这里创建的,通过这些设备节点可系统调用open/release/read/write/ioctl..访问操作该逻辑设备。
snd_ctl_dev_register():
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;
return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, &card->ctl_dev);
}
/**
* snd_register_device - Register the ALSA device file for the card
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
* @card: the card instance
* @dev: the device index
* @f_ops: the file operations
* @private_data: user pointer for f_ops->open()
* @device: the device to register
*
* Registers an ALSA device file for the given card.
* The operators have to be set in reg parameter.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_register_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data, struct device *device)
{
int minor;
int err = 0;
struct snd_minor *preg;
if (snd_BUG_ON(!device))
return -EINVAL;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
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);
minor = snd_find_free_minor(type, card, dev);
if (minor < 0) {
err = minor;
goto error;
}
preg->dev = device;
device->devt = MKDEV(major, minor);
err = device_add(device);
if (err < 0)
goto error;
snd_minors[minor] = preg;
error:
mutex_unlock(&sound_mutex);
if (err < 0)
kfree(preg);
return err;
}
从snd_ctl_dev_register函数中可以看到:
1016 /**
1017 * device_add - add device to device hierarchy.
1018 * @dev: device.
1019 *
1020 * This is part 2 of device_register(), though may be called
1021 * separately _iff_ device_initialize() has been called separately.
1022 *
1023 * This adds @dev to the kobject hierarchy via kobject_add(), adds it
1024 * to the global and sibling lists for the device, then
1025 * adds it to the other relevant subsystems of the driver model.
1026 *
1027 * Do not call this routine or device_register() more than once for
1028 * any device structure. The driver model core is not designed to work
1029 * with devices that get unregistered and then spring back to life.
1030 * (Among other things, it's very hard to guarantee that all references
1031 * to the previous incarnation of @dev have been dropped.) Allocate
1032 * and register a fresh new struct device instead.
1033 *
1034 * NOTE: _Never_ directly free @dev after calling this function, even
1035 * if it returned an error! Always use put_device() to give up your
1036 * reference instead.
1037 */
1038 int device_add(struct device *dev)
1039 {
1040 struct device *parent = NULL;
1041 struct kobject *kobj;
1042 struct class_interface *class_intf;
1043 int error = -EINVAL;
1044 struct kobject *glue_dir = NULL;
1045
1046 dev = get_device(dev);
1047 if (!dev)
1048 goto done;
1050 if (!dev->p) {
1051 error = device_private_init(dev);
1052 if (error)
1053 goto done;
1054 }
1055
1056 /*
1057 * for statically allocated devices, which should all be converted
1058 * some day, we need to initialize the name. We prevent reading back
1059 * the name, and force the use of dev_name()
1060 */
1061 if (dev->init_name) {
1062 dev_set_name(dev, "%s", dev->init_name);
1063 dev->init_name = NULL;
1064 }
1065
1066 /* subsystems can specify simple device enumeration */
1067 if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
1068 dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
1069
1070 if (!dev_name(dev)) {
1071 error = -EINVAL;
1072 goto name_error;
1073 }
1074
1075 pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
1076
1077 parent = get_device(dev->parent);
1078 kobj = get_device_parent(dev, parent);
1079 if (kobj)
1080 dev->kobj.parent = kobj;
1081
1082 /* use parent numa_node */
1083 if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
1084 set_dev_node(dev, dev_to_node(parent));
1085
1086 /* first, register with generic layer. */
1087 /* we require the name to be set before, and pass NULL */
1088 error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
1089 if (error) {
1090 glue_dir = get_glue_dir(dev);
1091 goto Error;
1092 }
1093
1094 /* notify platform of device entry */
1095 if (platform_notify)
1096 platform_notify(dev);
1097
1098 error = device_create_file(dev, &dev_attr_uevent);
1099 if (error)
1100 goto attrError;
1101
1102 error = device_add_class_symlinks(dev);
1103 if (error)
1104 goto SymlinkError;
1105 error = device_add_attrs(dev);
1106 if (error)
1107 goto AttrsError;
1108 error = bus_add_device(dev);
1109 if (error)
1110 goto BusError;
1111 error = dpm_sysfs_add(dev);
1112 if (error)
goto DPMError;
device_pm_add(dev);
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto DevAttrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;
devtmpfs_create_node(dev);
}
/* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error;
SysEntryError:
if (MAJOR(dev->devt))
device_remove_file(dev, &dev_attr_dev);
DevAttrError:
device_pm_remove(dev);
dpm_sysfs_remove(dev);
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
device_remove_file(dev, &dev_attr_uevent);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
glue_dir = get_glue_dir(dev);
kobject_del(&dev->kobj);
Error:
cleanup_glue_dir(dev, glue_dir);
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
上面过程是声卡注册时才被回调的。
8.5声卡的注册
当声卡下的所有逻辑设备都已经准备就绪后,就可以调用snd_card_register()注册声卡了:
724 /**
725 * snd_card_register - register the soundcard
726 * @card: soundcard structure
727 *
728 * This function registers all the devices assigned to the soundcard.
729 * Until calling this, the ALSA control interface is blocked from the
730 * external accesses. Thus, you should call this function at the end
731 * of the initialization of the card.
732 *
733 * Return: Zero otherwise a negative error code if the registration failed.
734 */
735 int snd_card_register(struct snd_card *card)
736 {
737 int err;
738
739 if (snd_BUG_ON(!card))
740 return -EINVAL;
741 //创建sysfs设备,声卡的class将会出现在/sys/class/sound下面
742 if (!card->registered) {
743 err = device_add(&card->card_dev);
744 if (err < 0)
745 return err;
746 card->registered = true;
747 }
748 //遍历挂在该声卡的所有逻辑设备,回调各snd_device的ops->dev_register()完成各逻辑设备的注册
749 if ((err = snd_device_register_all(card)) < 0)
750 return err;
751 mutex_lock(&snd_card_mutex);
752 if (snd_cards[card->number]) {
753 /* already registered */
754 mutex_unlock(&snd_card_mutex);
755 return snd_info_card_register(card); /* register pending info */
756 }
757 if (*card->id) {
758 /* make a unique id name from the given string */
759 char tmpid[sizeof(card->id)];
760 memcpy(tmpid, card->id, sizeof(card->id));
761 snd_card_set_id_no_lock(card, tmpid, tmpid);
762 } else {
763 /* create an id from either shortname or longname */
764 const char *src;
765 src = *card->shortname ? card->shortname : card->longname;
766 snd_card_set_id_no_lock(card, src,
767 retrieve_id_from_card_name(src));
768 }
769 snd_cards[card->number] = card;//把该声卡实例保存到snd_cards数组中
770 mutex_unlock(&snd_card_mutex);
//声卡相关信息,见:/proc/asound/card0
771 init_info_for_card(card);
772 #if IS_ENABLED(CONFIG_SND_MIXER_OSS)
773 if (snd_mixer_oss_notify_callback)
774 snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
775 #endif
776 return 0;
777 }
至此完成了声卡及声卡下的所有逻辑设备的注册,用户态可以通过系统调用来访问这些设备。
8.6PCM设备的创建
snd_pcm_set_ops:设置PCM设备的操作接口,设置完成后,在PCM设备层即可访问操作底层音频物理设备。
snd_pcm_new:
/**
800 * snd_pcm_new - create a new PCM instance
801 * @card: the card instance
802 * @id: the id string
803 * @device: the device index (zero based)
804 * @playback_count: the number of substreams for playback
805 * @capture_count: the number of substreams for capture
806 * @rpcm: the pointer to store the new pcm instance
807 *
808 * Creates a new PCM instance.
809 *
810 * The pcm operators have to be set afterwards to the new instance
811 * via snd_pcm_set_ops().
812 *
813 * Return: Zero if successful, or a negative error code on failure.
814 */
815 int snd_pcm_new(struct snd_card *card, const char *id, int device,
816 int playback_count, int capture_count, struct snd_pcm **rpcm)
817 {
818 return _snd_pcm_new(card, id, device, playback_count, capture_count,
819 false, rpcm);
820 }
755 static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
756 int playback_count, int capture_count, bool internal,
757 struct snd_pcm **rpcm)
758 {
759 struct snd_pcm *pcm;
760 int err;
761 static struct snd_device_ops ops = {
762 .dev_free = snd_pcm_dev_free,
763 .dev_register = snd_pcm_dev_register,
764 .dev_disconnect = snd_pcm_dev_disconnect,
765 };
766
767 if (snd_BUG_ON(!card))
768 return -ENXIO;
769 if (rpcm)
770 *rpcm = NULL;
771 pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
772 if (!pcm)
773 return -ENOMEM;
774 pcm->card = card;
775 pcm->device = device;
776 pcm->internal = internal;
777 mutex_init(&pcm->open_mutex);
778 init_waitqueue_head(&pcm->open_wait);
779 INIT_LIST_HEAD(&pcm->list);
780 if (id)
781 strlcpy(pcm->id, id, sizeof(pcm->id));
782 if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
783 snd_pcm_free(pcm);
784 return err;
785 }
786 if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
787 snd_pcm_free(pcm);
788 return err;
789 }
790 if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
791 snd_pcm_free(pcm);
792 return err;
793 }
794 if (rpcm)
795 *rpcm = pcm;
796 return 0;
797 }
在看看pcm设备的系统调用:
const struct file_operations snd_pcm_f_ops[2] = {
3681 {
3682 .owner = THIS_MODULE,
3683 .write = snd_pcm_write,
3684 .write_iter = snd_pcm_writev,
3685 .open = snd_pcm_playback_open,
3686 .release = snd_pcm_release,
3687 .llseek = no_llseek,
3688 .poll = snd_pcm_playback_poll,
3689 .unlocked_ioctl = snd_pcm_playback_ioctl,
3690 .compat_ioctl = snd_pcm_ioctl_compat,
3691 .mmap = snd_pcm_mmap,
3692 .fasync = snd_pcm_fasync,
3693 .get_unmapped_area = snd_pcm_get_unmapped_area,
3694 },
3695 {
3696 .owner = THIS_MODULE,
3697 .read = snd_pcm_read,
3698 .read_iter = snd_pcm_readv,
3699 .open = snd_pcm_capture_open,
3700 .release = snd_pcm_release,
3701 .llseek = no_llseek,
3702 .poll = snd_pcm_capture_poll,
3703 .unlocked_ioctl = snd_pcm_capture_ioctl,
3704 .compat_ioctl = snd_pcm_ioctl_compat,
3705 .mmap = snd_pcm_mmap,
3706 .fasync = snd_pcm_fasync,
3707 .get_unmapped_area = snd_pcm_get_unmapped_area,
3708 }
3709 };
snd_pcm_f_ops作为snd_register_device_for_dev()的参数传入,并被记录在snd_minors[minor]中的字段f_ops中。snd_pcm_f_ops[0]是回放使用的系统调用接口,snd_pcm_f_ops[1]是录制使用的系统调用接口。
9.Frame && Period
音频数据中的几个重要概念:
下面是一章直观的表示buffer/period/frame/sample之间的关系:
这个buffer中有4个period,每当DMA搬运完一个period的数据就会出生一次中断,因此搬运这个buffer中的数据将产生4次中断。
ALSA为什么这样做?因为数据缓冲区可能很大,一次传输可能会导致不可接收的延迟;为了解决这个问题,alsa把缓冲区拆分成多个周期,以周期为单元传输数据。
alsa官网对periods的解释:https://www.alsa-project.org/main/index.php/FramesPeriods
由于i2s总线采样率是稳定的,我们可以计算i2s传输一个周期的数据所需的时间:transfer_time = 1 * period_size /sample_rate,in second.
例如period_size = 1024, sample_rate = 48khz,那么一个周期数据的传输时间是:1*1024/48000 = 21.3(ms)