Linux ALSA驱动框架(一)--ALSA架构简介--声卡的创建

mac2025-09-20  49

Linux ALSA驱动框架(一)--ALSA架构简介--声卡的创建

2018-05-29 06:48:35 技术芯 阅读数 10528更多

分类专栏: linux alsa音频驱动框架

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/sinat_37817094/article/details/80490830

(1)ALSA简介

(1) Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制

ALSA Library API:alsa 用户库接口,常见有 tinyalsa、alsa-lib

ALSA CORE:alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC)

ASoC CORE:asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系

Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec

 

 

 

(2) alsa驱动框架核心层给我们干的活:创建声卡设备的控制接口和PCM设备

snd_soc_init()--->

platform_driver_register(&soc_driver)--->

soc_probe()--->

snd_soc_register_card(card)--->注册自己的声卡设备

snd_soc_instantiate_card(card)--->

 

static int snd_soc_instantiate_card(struct snd_soc_card *card) 

{

     ret = snd_card_create();创建声卡设备-->snd_ctl_create()-->snd_ctl_dev_register创建声卡设备的控制接口函数-->snd_register_device()-->snd_register_device_for_dev()

     ret = soc_probe_link_dais(card, i, order);  --->soc_new_pcm()创建一个新的PCM设备-->snd_pcm_new()--->_snd_pcm_new()--->snd_pcm_new_stream()

     ret = snd_card_register(card->snd_card);-->snd_pcm_dev_register()创建pcm设备文件-->snd_register_device_for_dev()创建pcm设备节点

}

 

 

(3) 在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。

ls sound

core               该目录包含了ALSA驱动的中间层,它是整个ALSA驱动的核心部分

core/oss        包含模拟旧的OSS架构的PCM和Mixer模块

core/seq        有关音序器相关的代码

include          ALSA驱动的公共头文件目录,该目录的头文件需要导出给用户空间的应用程序使用,通常,驱动模块私有的头文件不应放置在这里

drivers           放置一些与CPU、BUS架构无关的公用代码

i2c                 ALSA自己的I2C控制代码

pci                 pci声卡的顶层目录,子目录包含各种pci声卡的代码

isa                 isa声卡的顶层目录,子目录包含各种isa声卡的代码

soc                针对system-on-chip体系的中间层代码

soc/codecs    针对soc体系的各种codec的代码,与平台无关

 

 

(4)

alsa驱动的设备文件结构: 字符设备

# ls /dev/snd/ -lh

total 0

crw-rw----    1 root     root      116,   0 Aug 21 16:01 controlC0

crw-rw----    1 root     root      116,  24 Aug 21 16:01 pcmC0D0c

crw-rw----    1 root     root      116,  16 Aug 21 16:01 pcmC0D0p

crw-rw----    1 root     root      116,  25 Aug 21 16:01 pcmC0D1c

crw-rw----    1 root     root      116,  17 Aug 21 16:01 pcmC0D1p

crw-rw----    1 root     root      116,  26 Aug 21 16:01 pcmC0D2c

crw-rw----    1 root     root      116,  33 Aug 21 16:01 timer

controlC0               用于声卡的控制,例如通道选择,混音,麦克风的控制等

pcmC0D0c             用于录音的pcm设备

pcmC0D0p             用于播放的pcm设备

timer                       定时器

C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。

(5)

sound/core/sound.c

static int __init alsa_sound_init(void)

{

 if (register_chrdev(major, "alsa", &snd_fops))  //注册alsa字符设备

}

static const struct file_operations snd_fops =                                                                                          

{                                                                                                                                       

    .owner =    THIS_MODULE,                                                                                                            

    .open =     snd_open,                                                                                                               

    .llseek =   noop_llseek,                                                                                                            

};                                                                                                                                      

  

static struct snd_minor *snd_minors[SNDRV_OS_MINORS];

谁来设置snd_minors[]结构体数组,snd_register_device_for_dev函数里面有数组项snd_minors外,为了mdev或udev能自动创建设备节点,我们有class_create()函数创建类,下面创建设备device_create(),类在Sound_ core.c入口函数里面被创建,sound.c的入口函数里面注册字符设备函数,snd_register_device_for_dev()函数里面创建声卡逻辑设备时有device_create()

(6)

谁调用snd_register_device_for_dev()(两路调用)

一路:声卡设备的控制接口

另一路:声卡设备的数据接口

声卡设备的控制接口

sound/core/control.c

snd_card_create--->  创建一个snd_card结构体

snd_ctl_create--->

static int snd_ctl_dev_register(struct snd_device *device)-->  //创建声卡设备的控制接口函数

snd_register_device--->

snd_register_device_for_dev()

int snd_ctl_create(struct snd_card *card)

{

    static struct snd_device_ops ops = {

        .dev_free = snd_ctl_dev_free,

        .dev_register = snd_ctl_dev_register,

        .dev_disconnect = snd_ctl_dev_disconnect,

    };   

    if (snd_BUG_ON(!card))

        return -ENXIO;

    return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);

}

创建声卡设备的数据接口(声卡驱动)

snd_pcm_new---->  创建一个新的PCM设备

 _snd_pcm_new---> 创建播放流和录音流

static int snd_pcm_dev_register(struct snd_device *device)  //创建声卡设备的数据接口

总结:

(1)构造snd_card结构体,snd_card_create()构造snd_card结构体并自动创建控制接口。调用函数snd_ctrl_create

(2)初始化;如snd_pcm_new(),创建逻辑设备(播放设备或录音设备)

(3)注册 snd_card_register

 

(2)声卡的创建

 

(1) struct snd_card是什么

snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card结构体。

struct snd_card {

struct list_head devices     记录该声卡下所有逻辑设备的链表

struct list_head controls    记录该声卡下所有的控制单元的链表

void *private_data            声卡的私有数据,可以在创建声卡时通过参数指定数据的大小

}

 

snd_soc_init

{

 return platform_driver_register(&soc_driver);

}

static int soc_probe(struct platform_device *pdev) 

{

   return snd_soc_register_card(card); 

}int snd_soc_register_card(struct snd_soc_card *card)

{

  ret = snd_soc_instantiate_card(card); 

}

static int snd_soc_instantiate_card(struct snd_soc_card *card) 

{

     ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card); -->snd_ctl_create()

     ret = soc_probe_link_dais(card, i, order);  --->soc_new_pcm()-->snd_pcm_new()

     ret = snd_card_register(card->snd_card);

}

snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);创建snd_card的一个实例

index:一个整数值,该声卡的编号 id:字符串,声卡的标识符 第四个参数:该参数决定在创建snd_card实例时,需要同时额外分配的私有数据的大小,该数据的指针最终会赋值给snd_card的private_data数据成员

card:返回所创建的snd_card实例的指针

设置Driver的ID和名字

static int __init alsa_sound_init(void)    subsys_initcall(alsa_sound_init);

snd_info_init()-->

snd_card_info_init()-->

snd_card_info_read()-->

{

snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",idx,card->id,card->driver,card->shortname);

snd_iprintf(buffer, "%s\n",card->longname);

}

创建声卡的功能部件(逻辑设备),例如PCM,Mixer,MIDI等

这时候可以创建声卡的各种功能部件了,还记得开头的snd_card结构体的devices字段吗?每一种部件的创建最终会调用snd_device_new()来生成一个snd_device实例,并把该实例链接到snd_card的devices链表中。

通常,alsa-driver的已经提供了一些常用的部件的创建函数,而不必直接调用snd_device_new(),比如:

    PCM  ----           snd_pcm_new()

    RAWMIDI --       snd_rawmidi_new()

    CONTROL --      snd_ctl_create()

    TIMER   --          snd_timer_new()

    INFO    --           snd_card_proc_new()

    JACK    --           snd_jack_new()

 

注册声卡 static int snd_soc_instantiate_card(struct snd_soc_card *card) 

{

     ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);

     ret = soc_probe_link_dais(card, i, order);  --->soc_new_pcm()-->snd_pcm_new()

     ret = snd_card_register(card->snd_card);

 

}

比如我们的例子:

sound/soc/ingenic/asoc-board/phoenix_icdc.c 

static int snd_phoenix_probe(struct platform_device *pdev)

{

ret = snd_soc_register_card(&phoenix);

}

 

 

 

(2) int snd_card_create(int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)

{

     if (extra_size < 0)

        extra_size = 0;

    card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);

//根据extra_size参数的大小分配内存,该内存区可以作为芯片的专有数据使用

 

 if (xid)

        strlcpy(card->id, xid, sizeof(card->id));

 

 

    if (idx < 0) {

        for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)

            /* idx == -1 == 0xffff means: take any free slot */

            if (~snd_cards_lock & idx & 1<<idx2) {

                if (module_slot_match(module, idx2)) {

                    idx = idx2;

                    break;

                }

            }

    }

    if (idx < 0) {

        for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)

            /* idx == -1 == 0xffff means: take any free slot */

            if (~snd_cards_lock & idx & 1<<idx2) {

                if (!slots[idx2] || !*slots[idx2]) {

                    idx = idx2;

                    break;

                }

            }

    }

//拷贝声卡的ID字符串

 

    card->number = idx;

    card->module = module;

    INIT_LIST_HEAD(&card->devices);

    init_rwsem(&card->controls_rwsem);

    rwlock_init(&card->ctl_files_rwlock);

    INIT_LIST_HEAD(&card->controls);

    INIT_LIST_HEAD(&card->ctl_files);

    spin_lock_init(&card->files_lock);

    INIT_LIST_HEAD(&card->files_list);

    init_waitqueue_head(&card->shutdown_sleep);

    atomic_set(&card->refcount, 0);

#ifdef CONFIG_PM

    mutex_init(&card->power_lock);

    init_waitqueue_head(&card->power_sleep);

#endif

//初始化snd_card结构中必要的字段

 

 err = snd_ctl_create(card);

    if (err < 0) {

        snd_printk(KERN_ERR "unable to register control minors\n");

        goto __error;

    }

//建立逻辑设备:Control

 

 err = snd_info_card_create(card);

    if (err < 0) {

        snd_printk(KERN_ERR "unable to create card info\n");

        goto __error_ctl;

    }

//建立proc文件中的info节点:通常就是/proc/asound/card0

 

  if (extra_size > 0)

        card->private_data = (char *)card + sizeof(struct snd_card);

    *card_ret = card;

    return 0;

//分配的内存指针放入private_data字段中

 

}

 

int snd_card_register(struct snd_card *card)

{

        if (!card->card_dev) {

        card->card_dev = device_create(sound_class, card->dev,

                           MKDEV(0, 0), card,

                           "card%i", card->number);

        if (IS_ERR(card->card_dev))

            card->card_dev = NULL;

    }

//创建sysfs下的设备

 

if ((err = snd_device_register_all(card)) < 0) 

        return err;

//通过snd_device_register_all()注册所有挂在该声卡下的逻辑设备,snd_device_register_all()实际上是通过snd_card的devices链表,遍历所有的snd_device,并且调用snd_device的ops->dev_register()来实现各自设备的注册的。

 

最后代码就是建立一些相应的proc和sysfs下的文件或属性节点

 

}

 

static int __init init_soundcore(void)  subsys_initcall(init_soundcore)

{

    int rc;

 

    rc = init_oss_soundcore();

    if (rc)

        return rc;

 

    sound_class = class_create(THIS_MODULE, "sound");

    if (IS_ERR(sound_class)) {

        cleanup_oss_soundcore();

        return PTR_ERR(sound_class);

    }

 

    sound_class->devnode = sound_devnode;

 

    return 0;

}

 

static char *sound_devnode(struct device *dev, umode_t *mode)

{

    if (MAJOR(dev->devt) == SOUND_MAJOR)

        return NULL;

    return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));

}

//声卡的class将会出现在文件系统的/sys/class/sound/下面,并且,sound_devnode()也决定了相应的设备节点也将会出现在/dev/snd/下面。

 

# ls /dev/snd/ -lh

total 0

crw-rw----    1 root     root      116,   0 Aug 21 12:00 controlC0

crw-rw----    1 root     root      116,  24 Aug 21 12:00 pcmC0D0c

crw-rw----    1 root     root      116,  16 Aug 21 12:00 pcmC0D0p

crw-rw----    1 root     root      116,  25 Aug 21 12:00 pcmC0D1c

crw-rw----    1 root     root      116,  17 Aug 21 12:00 pcmC0D1p

crw-rw----    1 root     root      116,  26 Aug 21 12:00 pcmC0D2c

crw-rw----    1 root     root      116,  33 Aug 21 12:00 timer

 

 

 ls /sys/class/sound/ -lh

total 0

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 card0 -> ../../devices/platform/ingenic-alsa.0/sound/card0

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 controlC0 -> ../../devices/platform/ingenic-alsa.0/sound/card0/controlC0

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 pcmC0D0c -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D0c

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 pcmC0D0p -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D0p

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 pcmC0D1c -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D1c

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 pcmC0D1p -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D1p

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 pcmC0D2c -> ../../devices/platform/ingenic-alsa.0/sound/card0/pcmC0D2c

lrwxrwxrwx    1 root     root           0 Aug 21 12:00 timer -> ../../devices/virtual/sound/timer

最新回复(0)