精华内容
下载资源
问答
  • Linux ALSA框架之一:ALSA架构简介

    千次阅读 2016-04-05 11:58:38
    ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构,想了解更多的关于ALSA的这一开源项目的信息和知识,请查看以下网址:http://www.alsa-project.org/. 在内核设备驱动层,ALSA...

    .  概述

    ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构,想了解更多的关于ALSA的这一开源项目的信息和知识,请查看以下网址:http://www.alsa-project.org/.

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

          图 1.1   alsa的软件体系结构

    上图可以看出,用户空间的alsa-lib对应用程序提供统一的API接口,这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度,内核空间中,alsa-soc其实是对alsa-driver的进一步封装,他针对嵌入式设备提供了一些列增强的功能,本系列博文仅对嵌入式系统中的alsa-driver和alsa-soc进行讨论.

    2.  ALSA设备文件结构

    我们从alsa在linux中的设备文件结构开始我们的alsa之旅.看看我的电脑中的alsa驱动的设备文件结构:

    复制代码
    $ cd /dev/snd
    $ ls -l
    crw-rw----+ 1 root audio 116, 8 2011-02-23 21:38 controlC0
    crw-rw----+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0
    crw-rw----+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c
    crw-rw----+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p
    crw-rw----+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p
    crw-rw----+ 1 root audio 116, 3 2011-02-23 21:38 seq
    crw-rw----+ 1 root audio 116, 2 2011-02-23 21:38 timer
    $
    复制代码

    我们可以看到以下设备文件:

    • controlC0 -->                 用于声卡的控制,例如通道选择,混音,麦克风的控制等
    • midiC0D0  -->                用于播放midi音频
    • pcmC0D0c -->               用于录音的pcm设备
    • pcmC0D0p -->               用于播放的pcm设备
    • seq  -->                        音序器
    • timer -->                       定时器

    其中,C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则,从上面的列表可以看出,我的声卡下挂了6个设备,根据声卡的实际能力,驱动实际上可以挂上更多种类的设备,在include/sound/core.h中,定义了以下设备类型:

    复制代码
     1 #define    SNDRV_DEV_TOPLEVEL    ((__force snd_device_type_t) 0)
     2 #define    SNDRV_DEV_CONTROL    ((__force snd_device_type_t) 1)
     3 #define    SNDRV_DEV_LOWLEVEL_PRE    ((__force snd_device_type_t) 2)
     4 #define    SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000)
     5 #define    SNDRV_DEV_PCM        ((__force snd_device_type_t) 0x1001)
     6 #define    SNDRV_DEV_RAWMIDI    ((__force snd_device_type_t) 0x1002)
     7 #define    SNDRV_DEV_TIMER        ((__force snd_device_type_t) 0x1003)
     8 #define    SNDRV_DEV_SEQUENCER    ((__force snd_device_type_t) 0x1004)
     9 #define    SNDRV_DEV_HWDEP        ((__force snd_device_type_t) 0x1005)
    10 #define    SNDRV_DEV_INFO        ((__force snd_device_type_t) 0x1006)
    11 #define    SNDRV_DEV_BUS        ((__force snd_device_type_t) 0x1007)
    12 #define    SNDRV_DEV_CODEC        ((__force snd_device_type_t) 0x1008)
    13 #define    SNDRV_DEV_JACK          ((__force snd_device_type_t) 0x1009)
    14 #define    SNDRV_DEV_LOWLEVEL    ((__force snd_device_type_t) 0x2000)
    复制代码

    通常,我们更关心的是pcm和control这两种设备.

    3.  驱动的代码文件结构

    在Linux2.6代码树中,Alsa的代码文件结构如下:

    sound
         /core
                /oss
                /seq
         /ioctl32     
         /include     
         /drivers     
         /i2c         
         /synth       
                /emux
         /pci         
                /(cards)
         /isa         
                /(cards)
         /arm         
         /ppc         
         /sparc       
         /usb         
         /pcmcia    /(cards)
         /oss         
         /soc         
                /codecs

    • 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的代码,与平台无关

    本文转自:http://blog.csdn.net/droidphone/article/details/6271122

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

    1. struct snd_card

    1.1 snd_card是什么

    snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card结构体.正因为如此,本节中,我们也从 struct cnd_card开始吧.

    1.2 snd_card的定义

    snd_card的定义位于改头文件中:include/sound/core.h

    复制代码
     1 /* main structure for soundcard */
     2 
     3 struct snd_card {
     4     int number;            /* number of soundcard (index to
     5                                 snd_cards) */
     6 
     7     char id[16];            /* id string of this card */
     8     char driver[16];        /* driver name */
     9     char shortname[32];        /* short name of this soundcard */
    10     char longname[80];        /* name of this soundcard */
    11     char mixername[80];        /* mixer name */
    12     char components[128];        /* card components delimited with
    13                                 space */
    14     struct module *module;        /* top-level module */
    15 
    16     void *private_data;        /* private data for soundcard */
    17     void (*private_free) (struct snd_card *card); /* callback for freeing of
    18                                 private data */
    19     struct list_head devices;    /* devices */
    20 
    21     unsigned int last_numid;    /* last used numeric ID */
    22     struct rw_semaphore controls_rwsem;    /* controls list lock */
    23     rwlock_t ctl_files_rwlock;    /* ctl_files list lock */
    24     int controls_count;        /* count of all controls */
    25     int user_ctl_count;        /* count of all user controls */
    26     struct list_head controls;    /* all controls for this card */
    27     struct list_head ctl_files;    /* active control files */
    28 
    29     struct snd_info_entry *proc_root;    /* root for soundcard specific files */
    30     struct snd_info_entry *proc_id;    /* the card id */
    31     struct proc_dir_entry *proc_root_link;    /* number link to real id */
    32 
    33     struct list_head files_list;    /* all files associated to this card */
    34     struct snd_shutdown_f_ops *s_f_ops; /* file operations in the shutdown
    35                                 state */
    36     spinlock_t files_lock;        /* lock the files for this card */
    37     int shutdown;            /* this card is going down */
    38     int free_on_last_close;        /* free in context of file_release */
    39     wait_queue_head_t shutdown_sleep;
    40     struct device *dev;        /* device assigned to this card */
    41 #ifndef CONFIG_SYSFS_DEPRECATED
    42     struct device *card_dev;    /* cardX object for sysfs */
    43 #endif
    44 
    45 #ifdef CONFIG_PM
    46     unsigned int power_state;    /* power state */
    47     struct mutex power_lock;    /* power lock */
    48     wait_queue_head_t power_sleep;
    49 #endif
    50 
    51 #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
    52     struct snd_mixer_oss *mixer_oss;
    53     int mixer_oss_change_count;
    54 #endif
    55 };
    复制代码
    • struct list_head devices     记录该声卡下所有逻辑设备的链表
    • struct list_head controls    记录该声卡下所有的控制单元的链表
    • void *private_data            声卡的私有数据,可以在创建声卡时通过参数指定数据的大小

    2. 声卡的建立流程

    2.1 创建snd_card的一个实例

    1 struct snd_card *card;
    2 int err;
    3 ....
    4 err = snd_card_create(index, id, THIS_MODULE, 0, &card);
    • index           一个整数值,该声卡的编号
    • id                字符串,声卡的标识符
    • 第四个参数    该参数决定在创建snd_card实例时,需要同时额外分配的私有数据的大小,该数据的指针最终会赋值给snd_card的private_data数据成员
    • card             返回所创建的snd_card实例的指针

    2.2 创建声卡的芯片专用数据

    声卡的专用数据主要用于存放该声卡的一些资源信息,例如中断资源、io资源、dma资源等.可以有两种创建方法:

    • 通过上一步中snd_card_create()中的第四个参数,让snd_card_create自己创建
    1 // struct mychip 用于保存专用数据
    2 err = snd_card_create(index, id, THIS_MODULE,
    3                 sizeof(struct mychip), &card);
    4 // 从private_data中取出
    5 struct mychip *chip = card->private_data;
    • 自己创建:
    复制代码
     1 struct mychip {
     2     struct snd_card *card;
     3     ....
     4 };
     5 struct snd_card *card;
     6 struct mychip *chip;
     7 err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
     8 // 专用数据记录snd_card实例
     9 chip->card = card;
    10 .....
    11 chip = kzalloc(sizeof(*chip), GFP_KERNEL);
    复制代码

    然后,把芯片的专有数据注册为声卡的一个低阶设备:

    复制代码
     1 static int snd_mychip_dev_free(struct snd_device *device)
     2 {
     3     return snd_mychip_free(device->device_data);
     4 }
     5 
     6 static struct snd_device_ops ops = {
     7     .dev_free = snd_mychip_dev_free,
     8 };
     9 ....
    10 snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
    复制代码

    2.3 设置Driver的ID和名字

    1 strcpy(card->driver, "My Chip");
    2 strcpy(card->shortname, "My Own Chip 123");
    3 sprintf(card->longname, "%s at 0x%lx irq %i",
    4             card->shortname, chip->ioport, chip->irq);

    snd_card的driver字段保存着芯片的ID字符串,user空间的alsa-lib会使用到该字符串,所以必须要保证该ID的唯一性.shortname字段更多地用于打印信息,longname字段则会出现在/proc/asound/cards中.

    2.4 创建声卡的功能部件(逻辑设备),例如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()

    2.5 注册声卡

    1 err = snd_card_register(card);
    2 if (err < 0) {
    3     snd_card_free(card);
    4     return err;
    5 }

    2.6 一个实际的例子

    我把/sound/arm/pxa2xx-ac97.c的部分代码贴上来:

    复制代码
      1 static int __devinit pxa2xx_ac97_probe(struct platform_device *dev)
      2 {
      3     struct snd_card *card;
      4     struct snd_ac97_bus *ac97_bus;
      5     struct snd_ac97_template ac97_template;
      6     int ret;
      7     pxa2xx_audio_ops_t *pdata = dev->dev.platform_data;
      8 
      9     if (dev->id >= 0) {
     10         dev_err(&dev->dev, "PXA2xx has only one AC97 port./n");
     11         ret = -ENXIO;
     12         goto err_dev;
     13     }
     14 ////(1)////
     15     ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
     16                   THIS_MODULE, 0, &card);
     17     if (ret < 0)
     18         goto err;
     19 
     20     card->dev = &dev->dev;
     21 ////(3)////
     22     strncpy(card->driver, dev->dev.driver->name, sizeof(card->driver));
     23 
     24 ////(4)////
     25     ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
     26     if (ret)
     27         goto err;
     28 ////(2)////
     29     ret = pxa2xx_ac97_hw_probe(dev);
     30     if (ret)
     31         goto err;
     32 
     33 ////(4)////
     34     ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);
     35     if (ret)
     36         goto err_remove;
     37     memset(&ac97_template, 0, sizeof(ac97_template));
     38     ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);
     39     if (ret)
     40         goto err_remove;
     41 ////(3)////
     42     snprintf(card->shortname, sizeof(card->shortname),
     43          "%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97));
     44     snprintf(card->longname, sizeof(card->longname),
     45          "%s (%s)", dev->dev.driver->name, card->mixername);
     46 
     47     if (pdata && pdata->codec_pdata[0])
     48         snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]);
     49     snd_card_set_dev(card, &dev->dev);
     50 ////(5)////
     51     ret = snd_card_register(card);
     52     if (ret == 0) {
     53         platform_set_drvdata(dev, card);
     54         return 0;
     55     }
     56 
     57 err_remove:
     58     pxa2xx_ac97_hw_remove(dev);
     59 err:
     60     if (card)
     61         snd_card_free(card);
     62 err_dev:
     63     return ret;
     64 }
     65 
     66 static int __devexit pxa2xx_ac97_remove(struct platform_device *dev)
     67 {
     68     struct snd_card *card = platform_get_drvdata(dev);
     69 
     70     if (card) {
     71         snd_card_free(card);
     72         platform_set_drvdata(dev, NULL);
     73         pxa2xx_ac97_hw_remove(dev);
     74     }
     75 
     76     return 0;
     77 }
     78 
     79 static struct platform_driver pxa2xx_ac97_driver = {
     80     .probe        = pxa2xx_ac97_probe,
     81     .remove        = __devexit_p(pxa2xx_ac97_remove),
     82     .driver        = {
     83         .name    = "pxa2xx-ac97",
     84         .owner    = THIS_MODULE,
     85 #ifdef CONFIG_PM
     86         .pm    = &pxa2xx_ac97_pm_ops,
     87 #endif
     88     },
     89 };
     90 
     91 static int __init pxa2xx_ac97_init(void)
     92 {
     93     return platform_driver_register(&pxa2xx_ac97_driver);
     94 }
     95 
     96 static void __exit pxa2xx_ac97_exit(void)
     97 {
     98     platform_driver_unregister(&pxa2xx_ac97_driver);
     99 }
    100 
    101 module_init(pxa2xx_ac97_init);
    102 module_exit(pxa2xx_ac97_exit);
    103 
    104 MODULE_AUTHOR("Nicolas Pitre");
    105 MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
    复制代码

    驱动程序通常由probe回调函数开始,对一下2.1中的步骤,是否有相似之处?

    经过以上的创建步骤之后,声卡的逻辑结构如下图所示:

          图 2.1  声卡的软件逻辑结构

    下面的章节里我们分别讨论一下snd_card_create()和snd_card_register()这两个函数.

    3. snd_card_create()

     snd_card_create()在/sound/core/init.c中定义.

    复制代码
     1 /**
     2  *  snd_card_create - create and initialize a soundcard structure
     3  *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
     4  *  @xid: card identification (ASCII string)
     5  *  @module: top level module for locking
     6  *  @extra_size: allocate this extra size after the main soundcard structure
     7  *  @card_ret: the pointer to store the created card instance
     8  *
     9  *  Creates and initializes a soundcard structure.
    10  *
    11  *  The function allocates snd_card instance via kzalloc with the given
    12  *  space for the driver to use freely.  The allocated struct is stored
    13  *  in the given card_ret pointer.
    14  *
    15  *  Returns zero if successful or a negative error code.
    16  */
    17 int snd_card_create(int idx, const char *xid,
    18             struct module *module, int extra_size,
    19             struct snd_card **card_ret)
    复制代码

    首先,根据extra_size参数的大小分配内存,该内存区可以作为芯片的专有数据使用(见前面的介绍):

    1     card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
    2     if (!card)
    3         return -ENOMEM;

    拷贝声卡的ID字符串::

    1     if (xid)
    2         strlcpy(card->id, xid, sizeof(card->id));

    如果传入的声卡编号为-1,自动分配一个索引编号:

    复制代码
     1     if (idx < 0) {
     2         for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
     3             /* idx == -1 == 0xffff means: take any free slot */
     4             if (~snd_cards_lock & idx & 1<<idx2) {
     5                 if (module_slot_match(module, idx2)) {
     6                     idx = idx2;
     7                     break;
     8                 }
     9             }
    10     }
    11     if (idx < 0) {
    12         for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
    13             /* idx == -1 == 0xffff means: take any free slot */
    14             if (~snd_cards_lock & idx & 1<<idx2) {
    15                 if (!slots[idx2] || !*slots[idx2]) {
    16                     idx = idx2;
    17                     break;
    18                 }
    19             }
    20     }
    复制代码

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

    复制代码
     1     card->number = idx;
     2     card->module = module;
     3     INIT_LIST_HEAD(&card->devices);
     4     init_rwsem(&card->controls_rwsem);
     5     rwlock_init(&card->ctl_files_rwlock);
     6     INIT_LIST_HEAD(&card->controls);
     7     INIT_LIST_HEAD(&card->ctl_files);
     8     spin_lock_init(&card->files_lock);
     9     INIT_LIST_HEAD(&card->files_list);
    10     init_waitqueue_head(&card->shutdown_sleep);
    11 #ifdef CONFIG_PM
    12     mutex_init(&card->power_lock);
    13     init_waitqueue_head(&card->power_sleep);
    14 #endif
    复制代码

    建立逻辑设备:Control 

    1     /* the control interface cannot be accessed from the user space until */
    2     /* snd_cards_bitmask and snd_cards are set with snd_card_register */
    3     err = snd_ctl_create(card);

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

    1     err = snd_info_card_create(card);

    把第一步分配的内存指针放入private_data字段中:

    1     if (extra_size > 0)
    2         card->private_data = (char *)card + sizeof(struct snd_card);

    4. snd_card_register()

    snd_card_register()在/sound/core/init.c中定义.

    复制代码
     1 /**
     2  *  snd_card_register - register the soundcard
     3  *  @card: soundcard structure
     4  *
     5  *  This function registers all the devices assigned to the soundcard.
     6  *  Until calling this, the ALSA control interface is blocked from the
     7  *  external accesses.  Thus, you should call this function at the end
     8  *  of the initialization of the card.
     9  *
    10  *  Returns zero otherwise a negative error code if the registrain failed.
    11  */
    12 int snd_card_register(struct snd_card *card)
    复制代码

    首先,创建sysfs下的设备:

    复制代码
    1     if (!card->card_dev) {
    2         card->card_dev = device_create(sound_class, card->dev,
    3                            MKDEV(0, 0), card,
    4                            "card%i", card->number);
    5         if (IS_ERR(card->card_dev))
    6             card->card_dev = NULL;
    7     }
    复制代码

    其中,sound_class是在/sound/sound_core.c中创建的:

    复制代码
     1 static char *sound_devnode(struct device *dev, mode_t *mode)
     2 {
     3     if (MAJOR(dev->devt) == SOUND_MAJOR)
     4         return NULL;
     5     return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));
     6 }
     7 static int __init init_soundcore(void)
     8 {
     9     int rc;
    10 
    11     rc = init_oss_soundcore();
    12     if (rc)
    13         return rc;
    14 
    15     sound_class = class_create(THIS_MODULE, "sound");
    16     if (IS_ERR(sound_class)) {
    17         cleanup_oss_soundcore();
    18         return PTR_ERR(sound_class);
    19     }
    20 
    21     sound_class->devnode = sound_devnode;
    22 
    23     return 0;
    24 }
    复制代码

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

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

    1     if ((err = snd_device_register_all(card)) < 0)
    2         return err;

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

    至此,整个声卡完成了建立过程.

    本文转自:http://blog.csdn.net/droidphone/article/details/6289712

    展开全文
  • Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等.对于Mixer(混音)来说,Control接口显得尤为重要,从ALSA 0.9.x版本开始,所有的mixer工作都是通过control接口的API...

    Control接口


    Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等.对于Mixer(混音)来说,Control接口显得尤为重要,从ALSA 0.9.x版本开始,所有的mixer工作都是通过control接口的API来实现的.

    ALSA已经为AC97定义了完整的控制接口模型,如果你的Codec芯片只支持AC97接口,你可以不用关心本节的内容.

    <sound/control.h>定义了所有的Control API.如果你要为你的codec实现自己的controls,请在代码中包含该头文件.

    Controls的定义


    要自定义一个Control,我们首先要定义3各回调函数:info,get和put.然后,定义一个snd_kcontrol_new结构:

    复制代码
     1 static struct snd_kcontrol_new my_control __devinitdata = {
     2     .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
     3     .name = "PCM Playback Switch",
     4     .index = 0,
     5     .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
     6     .private_value = 0xffff,
     7     .info = my_control_info,
     8     .get = my_control_get,
     9     .put = my_control_put
    10 };
    复制代码

    iface字段指出了control的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是MIXER,当然也可以定义属于全局的CARD类型,也可以定义属于某类设备的类型,例如HWDEP,PCMRAWMIDI,TIMER等,这时需要在device和subdevice字段中指出卡的设备逻辑编号.

    name字段是该control的名字,从ALSA 0.9.x开始,control的名字是变得比较重要,因为control的作用是按名字来归类的.ALSA已经预定义了一些control的名字,我们再Control Name一节详细讨论.

    index字段用于保存该control的在该卡中的编号.如果声卡中有不止一个codec,每个codec中有相同名字的control,这时我们可以通过index来区分这些controls.当index为0时,则可以忽略这种区分策略.

    access字段包含了该control的访问类型.每一个bit代表一种访问类型,这些访问类型可以多个“或”运算组合在一起.

    private_value字段包含了一个任意的长整数类型值.该值可以通过info,get,put这几个回调函数访问.你可以自己决定如何使用该字段,例如可以把它拆分成多个位域,又或者是一个指针,指向某一个数据结构.

    tlv字段为该control提供元数据.

    Control的名字


    control的名字需要遵循一些标准,通常可以分成3部分来定义control的名字:源--方向--功能.

    • 源  可以理解为该control的输入端,alsa已经预定义了一些常用的源,例如:Master,PCM,CD,Line等等
    • 方向  代表该control的数据流向,例如:Playback,Capture,Bypass,Bypass Capture等等,也可以不定义方向,这时表示该Control是双向的(playback和capture).
    • 功能  根据control的功能,可以是以下字符串:Switch,Volume,Route等等

     也有一些命名上的特例:

    • 全局的capture和playback    "Capture Source","Capture Volume","Capture Switch",它们用于全局的capture source,switch和volume.同理,"Playback Volume","Playback Switch",它们用于全局的输出switch和volume.
    • Tone-controles    音调控制的开关和音量命名为:Tone Control - XXX,例如,"Tone Control - Switch","Tone Control - Bass","Tone Control - Center".
    • 3D controls    3D控件的命名规则:,"3D Control - Switch","3D Control - Center","3D Control - Space".
    • Mic boost    麦克风音量加强控件命名为:"Mic Boost"或"Mic Boost(6dB)".

    访问标志(ACCESS Flags)


    Access字段是一个bitmask,它保存了改control的访问类型.默认的访问类型是:SNDDRV_CTL_ELEM_ACCESS_READWRITE,表明该control支持读和写操作.如果access字段没有定义(.access==0),此时也认为是READWRITE类型.

    如果是一个只读control,access应该设置为:SNDDRV_CTL_ELEM_ACCESS_READ,这时,我们不必定义put回调函数.类似地,如果是只写control,access应该设置为:SNDDRV_CTL_ELEM_ACCESS_WRITE,这时,我们不必定义get回调函数.

    如果control的值会频繁地改变(例如:电平表),我们可以使用VOLATILE类型,这意味着该control会在没有通知的情况下改变,应用程序应该定时地查询该control的值.

    回调函数

    info回调函数

    info回调函数用于获取control的详细信息.它的主要工作就是填充通过参数传入的snd_ctl_elem_info对象,以下例子是一个具有单个元素的boolean型control的info回调:

    复制代码
    1 static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,
    2     struct snd_ctl_elem_info *uinfo)
    3 {
    4     uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
    5     uinfo->count = 1;
    6     uinfo->value.integer.min = 0;
    7     uinfo->value.integer.max = 1;
    8     return 0;
    9 }
    复制代码

    type字段指出该control的值类型,值类型可以是BOOLEAN, INTEGER, ENUMERATED, BYTES,IEC958和INTEGER64之一.count字段指出了改control中包含有多少个元素单元,比如,立体声的音量control左右两个声道的音量值,它的count字段等于2.value字段是一个联合体(union),value的内容和control的类型有关.其中,boolean和integer类型是相同的.

    ENUMERATED类型有些特殊.它的value需要设定一个字符串和字符串的索引,请看以下例子:

    复制代码
     1 static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
     2 struct snd_ctl_elem_info *uinfo)
     3 {
     4     static char *texts[4] = {
     5         "First", "Second", "Third", "Fourth"
     6     };
     7     uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
     8     uinfo->count = 1;
     9     uinfo->value.enumerated.items = 4;
    10     if (uinfo->value.enumerated.item > 3)
    11         uinfo->value.enumerated.item = 3;
    12     strcpy(uinfo->value.enumerated.name,
    13         texts[uinfo->value.enumerated.item]);
    14     return 0;
    15 }
    复制代码

    alsa已经为我们实现了一些通用的info回调函数,例如:snd_ctl_boolean_mono_info(),snd_ctl_boolean_stereo_info()等等.

    get回调函数

    该回调函数用于读取control的当前值,并返回给用户空间的应用程序.

    复制代码
    1 static int snd_myctl_get(struct snd_kcontrol *kcontrol,
    2     struct snd_ctl_elem_value *ucontrol)
    3 {
    4     struct mychip *chip = snd_kcontrol_chip(kcontrol);
    5     ucontrol->value.integer.value[0] = get_some_value(chip);
    6     return 0;
    7 }
    复制代码

    value字段的赋值依赖于control的类型(如同info回调).很多声卡的驱动利用它存储硬件寄存器的地址、bit-shift和bit-mask,这时,private_value字段可以按以下例子进行设置:

    1 private_value = reg | (shift << 16) | (mask << 24);

    然后,get回调函数可以这样实现:

    复制代码
     1 static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol,
     2     struct snd_ctl_elem_value *ucontrol)
     3 
     4 {
     5     int reg = kcontrol->private_value & 0xff;
     6     int shift = (kcontrol->private_value >> 16) & 0xff;
     7     int mask = (kcontrol->private_value >> 24) & 0xff;
     8     ....
     9 
    10     //根据以上的值读取相应寄存器的值并填入value中
    11 }
    复制代码

    如果control的count字段大于1,表示control有多个元素单元,get回调函数也应该为value填充多个数值 

    put回调函数


    put回调函数用于把应用程序的控制值设置到control中.

    复制代码
     1 static int snd_myctl_put(struct snd_kcontrol *kcontrol,
     2     struct snd_ctl_elem_value *ucontrol)
     3 {
     4     struct mychip *chip = snd_kcontrol_chip(kcontrol);
     5     int changed = 0;
     6     if (chip->current_value !=
     7         ucontrol->value.integer.value[0]) {
     8         change_current_value(chip,
     9         ucontrol->value.integer.value[0]);
    10         changed = 1;
    11     }
    12     return changed;
    13 }
    复制代码

    如上述例子所示,当control的值被改变时,put回调必须要返回1,如果值没有被改变,则返回0.如果发生了错误,则返回一个负数的错误号.

    和get回调一样,当control的count大于1时,put回调也要处理多个control中的元素值.

    创建Controls


    当把以上讨论的内容都准备好了以后,我们就可以创建我们自己的control了.alsa-driver为我们提供了两个用于创建control的API:

    • snd_ctl_new1()
    • snd_ctl_add()

    我们可以用以下最简单的方式创建control:

    1 err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip));
    2 if (err < 0)
    3     return err;

    在这里,my_control是一个之前定义好的snd_kcontrol_new对象,chip对象将会被赋值在kcontrol->private_data字段,该字段可以在回调函数中访问.

    snd_ctl_new1()会分配一个新的snd_kcontrol实例,并把my_control中相应的值复制到该实例中,所以,在定义my_control时,通常我们可以加上__devinitdata前缀.snd_ctl_add则把该control绑定到声卡对象card当中.

    元数据(Metadata)


    很多mixer control需要提供以dB为单位的信息,我们可以使用DECLARE_TLV_xxx宏来定义一些包含这种信息的变量,然后把control的tlv.p字段指向这些变量,最后,在access字段中加上SNDRV_CTL_ELEM_ACCESS_TLV_READ标志,就像这样:

    复制代码
     1 static DECLARE_TLV_DB_SCALE(db_scale_my_control, -4050, 150, 0);
     2 
     3 
     4 static struct snd_kcontrol_new my_control __devinitdata = {
     5     ...
     6     .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
     7             SNDRV_CTL_ELEM_ACCESS_TLV_READ,
     8     ...
     9     .tlv.p = db_scale_my_control,
    10 };
    复制代码

    DECLARE_TLV_DB_SCALE宏定义的mixer control,它所代表的值按一个固定的dB值的步长变化.该宏的第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位.第三个参数是变化的步长,也是以0.01dB为单位.如果该control处于最小值时会做出mute时,需要把第四个参数设为1.

    DECLARE_TLV_DB_LINEAR宏定义的mixer control,它的输出随值的变化而线性变化. 该宏的第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位.第二个参数是最大值,以0.01dB为单位.如果该control处于最小值时会做出mute时,需要把第二个参数设为TLV_DB_GAIN_MUTE.

    这两个宏实际上就是定义一个整形数组,所谓tlv,就是Type-Lenght-Value的意思,数组的第0各元素代表数据的类型,第1个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据.

    Control设备的建立


    Control设备和PCM设备一样,都属于声卡下的逻辑设备.用户空间的应用程序通过alsa-lib访问该Control设备,读取或控制control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作.

    Control设备的创建过程大体上和PCM设备的创建过程相同.详细的创建过程可以参考本博的另一篇文章:Linux音频驱动之三:PCM设备的创建.下面我们只讨论有区别的地方.

    我们需要在我们的驱动程序初始化时主动调用snd_pcm_new()函数创建pcm设备,而control设备则在snd_card_create()内被创建,snd_card_create()通过调用snd_ctl_create()函数创建control设备节点.所以我们无需显式地创建control设备,只要建立声卡,control设备被自动地创建.

    和pcm设备一样,control设备的名字遵循一定的规则:controlCxx,这里的xx代表声卡的编号.我们也可以通过代码正是这一点,下面的是snd_ctl_dev_register()函数的代码

    复制代码
     1 /*
     2  * registration of the control device
     3  */
     4 static int snd_ctl_dev_register(struct snd_device *device)
     5 {
     6     struct snd_card *card = device->device_data;
     7     int err, cardnum;
     8     char name[16];
     9 
    10     if (snd_BUG_ON(!card))
    11         return -ENXIO;
    12     cardnum = card->number;
    13     if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
    14         return -ENXIO;
    15         /* control设备的名字 */
    16     sprintf(name, "controlC%i", cardnum);
    17     if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
    18                        &snd_ctl_f_ops, card, name)) < 0)
    19         return err;
    20     return 0;
    21 }
    复制代码

    snd_ctl_dev_register()函数会在snd_card_register()中,即声卡的注册阶段被调用.注册完成后,control设备的相关信息被保存在snd_minors[]数组中,用control设备的此设备号作索引,即可在snd_minors[]数组中找出相关的信息.注册完成后的数据结构关系可以用下图进行表述:

          control设备的操作函数入口

    用户程序需要打开control设备时,驱动程序通过snd_minors[]全局数组和此设备号,可以获得snd_ctl_f_ops结构中的各个回调函数,然后通过这些回调函数访问control中的信息和数据(最终会调用control的几个回调函数get,put,info).详细的代码我就不贴了,大家可以读一下代码:/sound/core/control.c

    本文转自:http://blog.csdn.net/droidphone/article/details/6409983

    展开全文
  • 每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件.pcm实例数量的这种限制源于linux设备号所占用的位大小,如果以后使用64位的设备号,我们将可以创建更多的pcm实例.不过大多数情况下,在嵌入式设备中,...

    1. PCM是什么


    PCM是英文Pulse-code modulation的缩写,中文译名是脉冲编码调制.我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲,把这些脉冲的幅值按一定的精度进行量化,这些量化后的数值被连续地输出、传输、处理或记录到存储介质中,所有这些组成了数字音频的产生过程.

      图1.1  模拟音频的采样、量化

    PCM信号的两个重要指标是采样频率和量化精度,目前,CD音频的采样频率通常为44100Hz,量化精度是16bit.通常,播放音乐时,应用程序从存储介质中读取音频数据(MP3、WMA、AAC......),经过解码后,最终送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得的PCM数据送回给应用程序,由应用程序完成压缩、存储等任务.所以,音频驱动的两大核心任务就是:

    • playback    如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频
    • capture     把mic拾取到得模拟信号,经过采样、量化,转换为PCM信号送回给用户空间的应用程序

    2. alsa-driver中的PCM中间层


    ALSA已经为我们实现了功能强劲的PCM中间层,自己的驱动中只要实现一些底层的需要访问硬件的函数即可.

    要访问PCM的中间层代码,你首先要包含头文件<sound/pcm.h>,另外,如果需要访问一些与 hw_param相关的函数,可能也要包含<sound/pcm_params.h>.

    每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件.pcm实例数量的这种限制源于linux设备号所占用的位大小,如果以后使用64位的设备号,我们将可以创建更多的pcm实例.不过大多数情况下,在嵌入式设备中,一个pcm实例已经足够了.

    一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成.

          图2.1  声卡中的pcm结构

    在嵌入式系统中,通常不会像图2.1中这么复杂,大多数情况下是一个声卡,一个pcm实例,pcm下面有一个playback和capture stream,playback和capture下面各自有一个substream.

    下面一张图列出了pcm中间层几个重要的结构,他可以让我们从uml的角度看一看这列结构的关系,理清他们之间的关系,对我们理解pcm中间层的实现方式.

           图2.2  pcm中间层的几个重要的结构体的关系图

    • snd_pcm是挂在snd_card下面的一个snd_device
    • snd_pcm中的字段:streams[2],该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream
    • snd_pcm_str中的substream字段,指向snd_pcm_substream结构
    • snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是他的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理.它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数.

    3. 新建一个pcm


    alsa-driver的中间层已经为我们提供了新建pcm的api:

    int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count,
                                        struct snd_pcm ** rpcm);

    • 参数device 表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始.
    • 参数playback_count 表示该pcm将会有几个playback substream.
    • 参数capture_count 表示该pcm将会有几个capture substream.

    另一个用于设置pcm操作函数接口的api:

    void snd_pcm_set_ops(struct snd_pcm *pcm,int direction, struct snd_pcm_ops *ops);

    新建一个pcm可以用下面一张新建pcm的调用的序列图进行描述:

          图3.1 新建pcm的序列图

    • snd_card_create  pcm是声卡下的一个设备(部件),所以第一步是要创建一个声卡
    • snd_pcm_new  调用该api创建一个pcm,才该api中会做以下事情
      • 如果有,建立playback stream,相应的substream也同时建立
      • 如果有,建立capture stream,相应的substream也同时建立
      • 调用snd_device_new()把该pcm挂到声卡中,参数ops中的dev_register字段指向了函数snd_pcm_dev_register,这个回调函数会在声卡的注册阶段被调用.
    • snd_pcm_set_ops  设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数
    • snd_card_register  注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序(alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc

    4. 设备文件节点的建立(dev/snd/pcmCxxDxxp、pcmCxxDxxc)


    4.1 struct snd_minor

    每个snd_minor结构体保存了声卡下某个逻辑设备的上下文信息,他在逻辑设备建立阶段被填充,在逻辑设备被使用时就可以从该结构体中得到相应的信息.pcm设备也不例外,也需要使用该结构体.该结构体在include/sound/core.h中定义.

    复制代码
    1 struct snd_minor {
    2     int type;            /* SNDRV_DEVICE_TYPE_XXX */
    3     int card;            /* card number */
    4     int device;            /* device number */
    5     const struct file_operations *f_ops;    /* file operations */
    6     void *private_data;        /* private data for f_ops->open */
    7     struct device *dev;        /* device for sysfs */
    8 };
    复制代码

    在sound/sound.c中定义了一个snd_minor指针的全局数组:

    1 static struct snd_minor *snd_minors[256];

    前面说过,在声卡的注册阶段(snd_card_register),会调用pcm的回调函数snd_pcm_dev_register(),这个函数里会调用函数snd_register_device_for_dev():

    复制代码
     1 static int snd_pcm_dev_register(struct snd_device *device)
     2 {
     3     ......
     4 
     5     /* register pcm */
     6     err = snd_register_device_for_dev(devtype, pcm->card,
     7                          pcm->device,
     8                     &snd_pcm_f_ops[cidx],
     9                     pcm, str, dev);
    10     ......
    11 }
    复制代码

    我们再进入snd_register_device_for_dev():

    复制代码
     1 int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
     2                 const struct file_operations *f_ops,
     3                 void *private_data,
     4                 const char *name, struct device *device)
     5 {
     6     int minor;
     7     struct snd_minor *preg;
     8 
     9     if (snd_BUG_ON(!name))
    10         return -EINVAL;
    11     preg = kmalloc(sizeof *preg, GFP_KERNEL);
    12     if (preg == NULL)
    13         return -ENOMEM;
    14     preg->type = type;
    15     preg->card = card ? card->number : -1;
    16     preg->device = dev;
    17     preg->f_ops = f_ops;
    18     preg->private_data = private_data;
    19     mutex_lock(&sound_mutex);
    20 #ifdef CONFIG_SND_DYNAMIC_MINORS
    21     minor = snd_find_free_minor();
    22 #else
    23     minor = snd_kernel_minor(type, card, dev);
    24     if (minor >= 0 && snd_minors[minor])
    25         minor = -EBUSY;
    26 #endif
    27     if (minor < 0) {
    28         mutex_unlock(&sound_mutex);
    29         kfree(preg);
    30         return minor;
    31     }
    32     snd_minors[minor] = preg;
    33     preg->dev = device_create(sound_class, device, MKDEV(major, minor),
    34                   private_data, "%s", name);
    35     if (IS_ERR(preg->dev)) {
    36         snd_minors[minor] = NULL;
    37         mutex_unlock(&sound_mutex);
    38         minor = PTR_ERR(preg->dev);
    39         kfree(preg);
    40         return minor;
    41     }
    42 
    43     mutex_unlock(&sound_mutex);
    44     return 0;
    45 }
    复制代码
    • 首先,分配并初始化一个snd_minor结构中的各字段
      • type: SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE
      • card: card的编号
      • device: pcm实例的编号,大多数情况为0
      • f_ops: snd_pcm_f_ops
      • private_data: 指向该pcm的实例
    • 根据type,card和pcm的编号,确定数组的索引值minor,minor也作为pcm设备的此设备号
    • 把该snd_minor结构的地址放入全局数组snd_minors[minor]中
    • 最后,调用device_create创建设备节点

    4.2 设备文件的建立


    在4.1节的最后,设备文件已经建立,不过4.1节的重点在于snd_minors数组的赋值过程,在本节中,我们把重点放在设备文件中.

    回到pcm的回调函数snd_pcm_dev_register()中:

    复制代码
     1 static int snd_pcm_dev_register(struct snd_device *device)
     2 {
     3     int cidx, err;
     4     char str[16];
     5     struct snd_pcm *pcm;
     6     struct device *dev;
     7 
     8     pcm = device->device_data;
     9          ......
    10     for (cidx = 0; cidx < 2; cidx++) {
    11                   ......
    12         switch (cidx) {
    13         case SNDRV_PCM_STREAM_PLAYBACK:
    14             sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
    15             devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
    16             break;
    17         case SNDRV_PCM_STREAM_CAPTURE:
    18             sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
    19             devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
    20             break;
    21         }
    22         /* device pointer to use, pcm->dev takes precedence if
    23          * it is assigned, otherwise fall back to card's device
    24          * if possible */
    25         dev = pcm->dev;
    26         if (!dev)
    27             dev = snd_card_get_device_link(pcm->card);
    28         /* register pcm */
    29         err = snd_register_device_for_dev(devtype, pcm->card,
    30                           pcm->device,
    31                           &snd_pcm_f_ops[cidx],
    32                           pcm, str, dev);
    33                   ......
    34     }
    35          ......
    36 }
    复制代码

    以上代码我们可以看出,对于一个pcm设备,可以生成两个设备文件,一个用于playback,一个用于capture,代码中也确定了他们的命名规则:

    • playback  --  pcmCxDxp,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0p
    • capture  --  pcmCxDxc,通常系统中只有一各声卡和一个pcm,它就是pcmC0D0c

    snd_pcm_f_ops

    snd_pcm_f_ops是一个标准的文件系统file_operations结构数组,它的定义在sound/core/pcm_native.c中:

    复制代码
     1 const struct file_operations snd_pcm_f_ops[2] = {
     2     {
     3         .owner =        THIS_MODULE,
     4         .write =        snd_pcm_write,
     5         .aio_write =        snd_pcm_aio_write,
     6         .open =            snd_pcm_playback_open,
     7         .release =        snd_pcm_release,
     8         .llseek =        no_llseek,
     9         .poll =            snd_pcm_playback_poll,
    10         .unlocked_ioctl =    snd_pcm_playback_ioctl,
    11         .compat_ioctl =     snd_pcm_ioctl_compat,
    12         .mmap =            snd_pcm_mmap,
    13         .fasync =        snd_pcm_fasync,
    14         .get_unmapped_area =    snd_pcm_get_unmapped_area,
    15     },
    16     {
    17         .owner =        THIS_MODULE,
    18         .read =            snd_pcm_read,
    19         .aio_read =        snd_pcm_aio_read,
    20         .open =            snd_pcm_capture_open,
    21         .release =        snd_pcm_release,
    22         .llseek =        no_llseek,
    23         .poll =            snd_pcm_capture_poll,
    24         .unlocked_ioctl =    snd_pcm_capture_ioctl,
    25         .compat_ioctl =     snd_pcm_ioctl_compat,
    26         .mmap =            snd_pcm_mmap,
    27         .fasync =        snd_pcm_fasync,
    28         .get_unmapped_area =    snd_pcm_get_unmapped_area,
    29     }
    30 };
    复制代码

    snd_pcm_f_ops作为snd_register_device_for_dev的参数被传入,并被记录在snd_minors[minor]中的字段f_ops中.最后,在snd_register_device_for_dev中创建设备节点:

    1     snd_minors[minor] = preg;
    2     preg->dev = device_create(sound_class, device, MKDEV(major, minor),
    3                   private_data, "%s", name);

    创建节点之后我们就能在/dev目录下查看到相应的设备文件

    4.3 层层深入,从应用程序到驱动层pcm


    4.3.1 字符设备注册

    在sound/core/sound.c中有alsa_sound_init()函数,定义如下: 

    复制代码
     1 static int __init alsa_sound_init(void)
     2 {
     3     snd_major = major;
     4     snd_ecards_limit = cards_limit;
     5     if (register_chrdev(major, "alsa", &snd_fops)) {
     6         snd_printk(KERN_ERR "unable to register native major device number %d/n", major);
     7         return -EIO;
     8     }
     9     if (snd_info_init() < 0) {
    10         unregister_chrdev(major, "alsa");
    11         return -ENOMEM;
    12     }
    13     snd_info_minor_register();
    14     return 0;
    15 }
    复制代码

    register_chrdev中的参数major与之前创建pcm设备是device_create时的major是同一个,这样的结果是,当应用程序open设备文件/dev/snd/pcmCxDxp时,会进入snd_fops的open回调函数,我们将在下一节中讲述open的过程.

    4.3.2 打开pcm设备

    从上一节中我们得知,open一个pcm设备时,将会调用snd_fops的open回调函数,我们先看看snd_fops的定义:

    1 static const struct file_operations snd_fops =
    2 {
    3     .owner =    THIS_MODULE,
    4     .open =        snd_open
    5 };

    跟入snd_open函数,它首先从inode中取出此设备号,然后以次设备号为索引,从snd_minors全局数组中取出当初注册pcm设备时填充的snd_minor结构(参看4.1节的内容),然后从snd_minor结构中取出pcm设备的f_ops,并且把file->f_op替换为pcm设备的f_ops,紧接着直接调用pcm设备的f_ops->open(),然后返回.因为file->f_op已经被替换,以后,应用程序的所有read/write/ioctl调用都会进入pcm设备自己的回调函数中,也就是4.2节中提到的snd_pcm_f_ops结构中定义的回调.

    复制代码
     1 static int snd_open(struct inode *inode, struct file *file)
     2 {
     3     unsigned int minor = iminor(inode);
     4     struct snd_minor *mptr = NULL;
     5     const struct file_operations *old_fops;
     6     int err = 0;
     7 
     8     if (minor >= ARRAY_SIZE(snd_minors))
     9         return -ENODEV;
    10     mutex_lock(&sound_mutex);
    11     mptr = snd_minors[minor];
    12     if (mptr == NULL) {
    13         mptr = autoload_device(minor);
    14         if (!mptr) {
    15             mutex_unlock(&sound_mutex);
    16             return -ENODEV;
    17         }
    18     }
    19     old_fops = file->f_op;
    20     file->f_op = fops_get(mptr->f_ops);
    21     if (file->f_op == NULL) {
    22         file->f_op = old_fops;
    23         err = -ENODEV;
    24     }
    25     mutex_unlock(&sound_mutex);
    26     if (err < 0)
    27         return err;
    28 
    29     if (file->f_op->open) {
    30         err = file->f_op->open(inode, file);
    31         if (err) {
    32             fops_put(file->f_op);
    33             file->f_op = fops_get(old_fops);
    34         }
    35     }
    36     fops_put(old_fops);
    37     return err;
    38 }
    复制代码

    下面的序列图展示了应用程序如何最终调用到snd_pcm_f_ops结构中的回调函数:

          图4.3.2.1    应用程序操作pcm设备

    本文转自:http://blog.csdn.net/droidphone/article/details/6308006

    展开全文
  • ASoC--ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系.在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有一些局限性: Codec驱动...
  • * This structure covers the clocking, formating and ALSA operations for each 9 * interface. 10 */ 11 struct snd_soc_dai_driver { 12 /* DAI description */ 13 const char *name;...
  • Linux ALSA声卡驱动之七:ASoC架构中的Codec . snd_soc_dai  该结构在snd_soc_register_dai函数中通过动态内存申请获得,简要介绍一下几个重要字段 driver 指向关联的snd_soc_dai_driver结构,由注册时通过...
  • snd_soc_instantiate_card接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例: 1 /* card bind complete so register a sound card */ 2 ret = snd_card_create(SNDRV_DEFAULT_IDX1,...
  • Linux ALSA音频框架分析三:ALSA框架

    千次阅读 2017-10-10 10:25:45
    Linux ALSA音频框架分析三:ALSA框架 一、概述  ALSA 是 Advanced Linux Sound Architecture 的缩写,即高级 Linux声音架构,在 Linux 操作系统上提供了对音频和 MIDI(Musical InstrumentDigital Interface,音乐...
  • Linux ALSA驱动框架

    2019-04-27 00:01:04
    Linux ALSA驱动框架(一)--ALSA架构简介--声卡的创建 ... linux驱动由浅入深系列:ALSA框架详解 音频子系统之二 https://blog.csdn.net/chenpuo/article/details/80815636 Linux audio驱动...
  • 虽然linuxalsa框架给用户空间提供了系统调用,但alsa-lib对系统调用进行进一步封装,并实现了很多插件,例如多音频源进行混音播放时,调用的就是 dmix 插件。 一 软件包下载 进入网站...
  • Linux ALSA音频框架分析六:ALSA-lib移植虽然linuxalsa框架给用户空间提供了系统调用,但alsa-lib对系统调用进行进一步封装,并实现了很多插件,例如多音频源进行混音播放时,调用的就是 dmix 插件。一 软件包下载...
  • Linux ALSA音频框架分析四:ALSA 的硬件抽象  ALSA 用 cards,device 和 subdevices 的分层结构表示 Audio 硬件设备和他们的组件。这个分层结构是 ALSA 看待硬件设备结构和能力的视角,是对实体硬件的抽象化实例。...
  • 目录 一、引言 二、代码框架 三、网络设备框架常用接口介绍 ------> dts对应的通用驱动 ------>...本篇文章来分析一下基于ALSA框架的声卡创建,以及在此基础上利用ASOC的声卡创建 二、ALSA的使用 ...
  • Linux ALSA音频框架分析五:HDA Driver分析 一 概述 HDA(High Definition Audio)是intel设计的用来取代AC97的音频标准,硬件架构上由hda dodec和hda controller组成见图1: 图1 二 Stream 的概念 HAD 引入了...
  • Linux ALSA驱动框架(一)--ALSA架构简介--声卡的创建 2018-05-29 06:48:35技术芯阅读数 10528更多 分类专栏:linux alsa音频驱动框架 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文...
  • Linux ALSA音频框架分析一:数字音频简介  声音在自然界以声波的形式存在,是一系列连续变化的模拟信号,声音的三个要素是音调、音强和音色。声波有三个重要参数:频率 ω0、幅度A n 和相位ψn ,这也就决定了音频...
  • Linux ALSA驱动框架分析之(一):架构介绍 Linux ALSA驱动框架分析之(二):pcm逻辑设备的创建 Linux ALSA驱动框架分析之(三):Control逻辑设备的创建 音频相关概念 声音的采样 我们知道在现实生活中,人耳听到...
  •  Linux音频系统比较复杂,各层间有很多交叉,可能是最无序的子系统,并且它有两套音频驱动框架: OSS (Open Sound System)和ALSA (Advanced Linux Sound Architechture),所以底层驱动有OSS和ALSA两套API。ALSA经过...
  • (1)ALSA简介(1)Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制ALSA Library API:alsa 用户...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,061
精华内容 1,224
关键字:

linuxalsa框架

linux 订阅