PCM data flow - 6 - 声卡和PCM设备的建立过程

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

PCM逻辑设备,我们又习惯称之为PCM中间层或pcm native,起着承上启下的作用:往上是与用户态接口的交互,实现音频数据在用户态和内核态之间的拷贝;往下是触发codec、platform、machine的操作函数,实现音频数据在dma_buffer<-> cpu_dai <-> codec之间的传输。

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

声卡驱动中,一般挂载着多个逻辑设备,看看我们计算机的声卡驱动有几个逻辑设备:

 

[plain] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. $ cat /proc/asound/devices   
  2.   1:        : sequencer  
  3.   2: [ 0- 7]: digital audio playback  
  4.   3: [ 0- 3]: digital audio playback  
  5.   4: [ 0- 2]: digital audio capture  
  6.   5: [ 0- 0]: digital audio playback  
  7.   6: [ 0- 0]: digital audio capture  
  8.   7: [ 0- 3]: hardware dependent  
  9.   8: [ 0- 0]: hardware dependent  
  10.   9: [ 0]   : control  
  11.  33:        : timer  

 

·          digital audio playback:用于回放的PCM设备

·          digital audio capture:用于录制的PCM设备

·          control:用于声卡控制的CTL设备,如通路控制、音量调整等

·          timer:定时器设备

·          sequencer:音序器设备

嵌入式系统中,通常我们更关心PCM和CTL这两种设备。

设备节点如下:

[plain] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. $ ll /dev/snd  
  2. drwxr-xr-x   3 root root      260 Feb 26 13:59 ./  
  3. drwxr-xr-x  16 root root     4300 Mar  6 17:07 ../  
  4. drwxr-xr-x   2 root root       60 Feb 26 13:59 by-path/  
  5. crw-rw---T+  1 root audio 116,  9 Feb 26 13:59 controlC0  
  6. crw-rw---T+  1 root audio 116,  8 Feb 26 13:59 hwC0D0  
  7. crw-rw---T+  1 root audio 116,  7 Feb 26 13:59 hwC0D3  
  8. crw-rw---T+  1 root audio 116,  6 Feb 26 13:59 pcmC0D0c  
  9. crw-rw---T+  1 root audio 116,  5 Mar  6 19:08 pcmC0D0p  
  10. crw-rw---T+  1 root audio 116,  4 Feb 26 13:59 pcmC0D2c  
  11. crw-rw---T+  1 root audio 116,  3 Feb 26 13:59 pcmC0D3p  
  12. crw-rw---T+  1 root audio 116,  2 Feb 26 13:59 pcmC0D7p  
  13. crw-rw---T+  1 root audio 116,  1 Feb 26 13:59 seq  
  14. crw-rw---T+  1 root audio 116, 33 Feb 26 13:59 timer  

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

 

 

6.1. 声卡结构概述

 

回顾下ASoC是如何注册声卡的,详细请参考章节5.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()函数,完成物理设备的初始化;

·          随后调用soc_new_pcm()创建pcm逻辑设备:

           1.  设置pcm native中要使用的pcm操作函数,这些函数用于操作音频物理设备,包括machine、codec_dai、cpu_dai、platform;

           2.  调用snd_pcm_new()创建pcm设备,回放子流实例和录制子流实例都在这里创建;

           3.  回调platform驱动的pcm_new(),完成音频dma设备初始化和dma buffer内存分配;

·          最后调用snd_card_register()注册声卡。

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

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

[plain] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1.                  +-----------+  
  2.                  | snd_card  |  
  3.                  +-----------+  
  4.                    |   |   |  
  5.        +-----------+   |   +------------+  
  6.        |               |                |  
  7. +-----------+    +-----------+    +-----------+  
  8. |  snd_pcm  |    |snd_control|    | snd_timer |    ...  
  9. +-----------+    +-----------+    +-----------+  

这些与声音相关的逻辑设备都在结构体snd_card管理之下,可以说snd_card是alsa中最顶层的结构。

我们再看看alsa声卡驱动的大致结构图(不是严格的UML类图,有结构体定义、模块关系、函数调用,方便标示结构模块的层次及关系):

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

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

 

 

6.2. 声卡的创建

 

声卡实例通过函数snd_card_create()来创建,其函数原型:

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  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)  

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

·          idx:声卡的编号,如为-1,则由系统自动分配

·          xid:声卡标识符,如为NULL,则以snd_card的shortname或longname代替

·          card_ret:返回所创建的声卡实例的指针

如下是我的计算机的声卡信息:

 

[plain] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. $ cat /proc/asound/cards  
  2.  0 [PCH            ]: HDA-Intel - HDA Intel PCH  
  3.                       HDA Intel PCH at 0xf7c30000 irq 47  

编号number:0

标识符id:PCH

shortname:HDAIntel PCH

longname:HDAIntel PCH at 0xf7c30000 irq 47

shortname、longname常用于打印信息,上面的声卡信息是通过如下函数打印出来的:

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static void snd_card_info_read(struct snd_info_entry *entry,  
  2.                    struct snd_info_buffer *buffer)  
  3. {  
  4.     int idx, count;  
  5.     struct snd_card *card;  
  6.   
  7.     for (idx = count = 0; idx < SNDRV_CARDS; idx++) {  
  8.         mutex_lock(&snd_card_mutex);  
  9.         if ((card = snd_cards[idx]) != NULL) {  
  10.             count++;  
  11.             snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n",  
  12.                     idx,  
  13.                     card->id,  
  14.                     card->driver,  
  15.                     card->shortname);  
  16.             snd_iprintf(buffer, "                      %s\n",  
  17.                     card->longname);  
  18.         }  
  19.         mutex_unlock(&snd_card_mutex);  
  20.     }  
  21.     if (!count)  
  22.         snd_iprintf(buffer, "--- no soundcards ---\n");  
  23. }  

 

6.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()

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

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. int snd_xxx_new()  
  2. {  
  3.     // 这些接口供逻辑设备注册时回调  
  4.     static struct snd_device_ops ops = {  
  5.         .dev_free = snd_xxx_dev_free,  
  6.         .dev_register = snd_xxx_dev_register,  
  7.         .dev_disconnect = snd_xxx_dev_disconnect,  
  8.     };  
  9.       
  10.     // 逻辑设备实例初始化  
  11.       
  12.     // 新建一个设备实例snd_device,挂到snd_card的devices链表上,把该逻辑设备纳入声卡的管理当中  
  13.     return snd_device_new(card, SNDRV_DEV_xxx, card, &ops);  
  14. }  

 

其中snd_device_ops是声卡逻辑设备的注册相关函数集,dev_register()回调尤其重要,它在声卡注册时被调用,用于建立系统的设备文件节点,/dev/snd/目录的设备文件节点都是在这里创建的。

例如snd_ctl_dev_register():

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. // CTL设备的系统调用接口  
  2. static const struct file_operations snd_ctl_f_ops =  
  3. {  
  4.     .owner =    THIS_MODULE,  
  5.     .read =     snd_ctl_read,  
  6.     .open =     snd_ctl_open,  
  7.     .release =  snd_ctl_release,  
  8.     .llseek =   no_llseek,  
  9.     .poll =     snd_ctl_poll,  
  10.     .unlocked_ioctl =   snd_ctl_ioctl,  
  11.     .compat_ioctl = snd_ctl_ioctl_compat,  
  12.     .fasync =   snd_ctl_fasync,  
  13. };  
  14.   
  15. /* 
  16.  * registration of the control device 
  17.  */  
  18. static int snd_ctl_dev_register(struct snd_device *device)  
  19. {  
  20.     struct snd_card *card = device->device_data;  
  21.     int err, cardnum;  
  22.     char name[16];  
  23.   
  24.     if (snd_BUG_ON(!card))  
  25.         return -ENXIO;  
  26.     cardnum = card->number;  
  27.     if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))  
  28.         return -ENXIO;  
  29.     sprintf(name, "controlC%i", cardnum);  
  30.     if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,  
  31.                        &snd_ctl_f_ops, card, name)) < 0)  
  32.         return err;  
  33.     return 0;  
  34. }  

事实是调用snd_register_device_for_dev():

·          分配并初始化一个snd_minor实例;

·          保存该snd_minor实例到snd_minors数组中;

·          调用device_create()生成设备文件节点。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * snd_register_device_for_dev - Register the ALSA device file for the card 
  3.  * @type: the device type, SNDRV_DEVICE_TYPE_XXX 
  4.  * @card: the card instance 
  5.  * @dev: the device index 
  6.  * @f_ops: the file operations 
  7.  * @private_data: user pointer for f_ops->open() 
  8.  * @name: the device file name 
  9.  * @device: the &struct device to link this new device to 
  10.  * 
  11.  * Registers an ALSA device file for the given card. 
  12.  * The operators have to be set in reg parameter. 
  13.  * 
  14.  * Returns zero if successful, or a negative error code on failure. 
  15.  */  
  16. int snd_register_device_for_dev(int type, struct snd_card *card, int dev,  
  17.                 const struct file_operations *f_ops,  
  18.                 void *private_data,  
  19.                 const char *name, struct device *device)  
  20. {  
  21.     int minor;  
  22.     struct snd_minor *preg;  
  23.   
  24.     if (snd_BUG_ON(!name))  
  25.         return -EINVAL;  
  26.     preg = kmalloc(sizeof *preg, GFP_KERNEL);  
  27.     if (preg == NULL)  
  28.         return -ENOMEM;  
  29.     preg->type = type;  
  30.     preg->card = card ? card->number : -1;  
  31.     preg->device = dev;  
  32.     preg->f_ops = f_ops;  
  33.     preg->private_data = private_data;  
  34.     mutex_lock(&sound_mutex);  
  35. #ifdef CONFIG_SND_DYNAMIC_MINORS  
  36.     minor = snd_find_free_minor(type);  
  37. #else  
  38.     minor = snd_kernel_minor(type, card, dev);  
  39.     if (minor >= 0 && snd_minors[minor])  
  40.         minor = -EBUSY;  
  41. #endif  
  42.     if (minor < 0) {  
  43.         mutex_unlock(&sound_mutex);  
  44.         kfree(preg);  
  45.         return minor;  
  46.     }  
  47.     snd_minors[minor] = preg;  
  48.     preg->dev = device_create(sound_class, device, MKDEV(major, minor),  
  49.                   private_data, "%s", name);  
  50.     if (IS_ERR(preg->dev)) {  
  51.         snd_minors[minor] = NULL;  
  52.         mutex_unlock(&sound_mutex);  
  53.         minor = PTR_ERR(preg->dev);  
  54.         kfree(preg);  
  55.         return minor;  
  56.     }  
  57.   
  58.     mutex_unlock(&sound_mutex);  
  59.     return 0;  
  60. }  

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

 

6.4. 声卡的注册

当声卡下的所有逻辑设备都已经准备就绪后,就可以调用snd_card_register()注册声卡了:

·          创建声卡的sysfs设备;

·          调用snd_device_register_all()注册所有挂在该声卡下的逻辑设备;

·          建立proc信息文件和sysfs属性文件。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  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 registration failed. 
  11.  */  
  12. int snd_card_register(struct snd_card *card)  
  13. {  
  14.     int err;  
  15.   
  16.     if (snd_BUG_ON(!card))  
  17.         return -EINVAL;  
  18.   
  19.     // 创建sysfs设备,声卡的class将会出现在/sys/class/sound/下面  
  20.     if (!card->card_dev) {  
  21.         card->card_dev = device_create(sound_class, card->dev,  
  22.                            MKDEV(0, 0), card,  
  23.                            "card%i", card->number);  
  24.         if (IS_ERR(card->card_dev))  
  25.             card->card_dev = NULL;  
  26.     }  
  27.   
  28.     // 遍历挂在该声卡的所有逻辑设备,回调各snd_device的ops->dev_register()完成各逻辑设备的注册  
  29.     if ((err = snd_device_register_all(card)) < 0)  
  30.         return err;  
  31.     mutex_lock(&snd_card_mutex);  
  32.     if (snd_cards[card->number]) {  
  33.         /* already registered */  
  34.         mutex_unlock(&snd_card_mutex);  
  35.         return 0;  
  36.     }  
  37.     if (*card->id) {  
  38.         /* make a unique id name from the given string */  
  39.         char tmpid[sizeof(card->id)];  
  40.         memcpy(tmpid, card->id, sizeof(card->id));  
  41.         snd_card_set_id_no_lock(card, tmpid, tmpid);  
  42.     } else {  
  43.         /* create an id from either shortname or longname */  
  44.         const char *src;  
  45.         src = *card->shortname ? card->shortname : card->longname;  
  46.         snd_card_set_id_no_lock(card, src,  
  47.                     retrieve_id_from_card_name(src));  
  48.     }  
  49.     snd_cards[card->number] = card; // 把该声卡实例保存到snd_cards数组中  
  50.     mutex_unlock(&snd_card_mutex);  
  51.     // 声卡相关信息,见:/proc/asound/card0  
  52.     init_info_for_card(card);  
  53. #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)  
  54.     if (snd_mixer_oss_notify_callback)  
  55.         snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);  
  56. #endif  
  57.     // 声卡的sysfs属性节点  
  58.     if (card->card_dev) {  
  59.         err = device_create_file(card->card_dev, &card_id_attrs);  
  60.         if (err < 0)  
  61.             return err;  
  62.         err = device_create_file(card->card_dev, &card_number_attrs);  
  63.         if (err < 0)  
  64.             return err;  
  65.     }  
  66.   
  67.     return 0;  
  68. }  

至此完成了声卡及声卡下的所有逻辑设备的注册,用户态应用可以通过系统调用来访问这些设备了。

 

6.5. PCM设备的创建

最后我们简单说说PCM设备的建立过程:

snd_pcm_set_ops:设置PCM设备的操作接口,设置完成后,PCM设备就可以控制/操作底层音频物理设备了。

snd_pcm_new

·          创建一个PCM设备实例snd_pcm;

·          创建playback stream和capture stream,旗下的substream也同时建立;

·          调用snd_device_new()把PCM设备挂到声卡的devices链表上。

 

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. static int _snd_pcm_new(struct snd_card *card, const char *id, int device,  
  2.         int playback_count, int capture_count, bool internal,  
  3.         struct snd_pcm **rpcm)  
  4. {  
  5.     struct snd_pcm *pcm;  
  6.     int err;  
  7.     static struct snd_device_ops ops = {  
  8.         .dev_free = snd_pcm_dev_free,  
  9.         .dev_register = snd_pcm_dev_register,  
  10.         .dev_disconnect = snd_pcm_dev_disconnect,  
  11.     };  
  12.   
  13.     if (snd_BUG_ON(!card))  
  14.         return -ENXIO;  
  15.     if (rpcm)  
  16.         *rpcm = NULL;  
  17.     pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);  
  18.     if (pcm == NULL) {  
  19.         snd_printk(KERN_ERR "Cannot allocate PCM\n");  
  20.         return -ENOMEM;  
  21.     }  
  22.     pcm->card = card;  
  23.     pcm->device = device;  
  24.     pcm->internal = internal;  
  25.     if (id)  
  26.         strlcpy(pcm->id, id, sizeof(pcm->id));  
  27.     if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {  
  28.         snd_pcm_free(pcm);  
  29.         return err;  
  30.     }  
  31.     if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {  
  32.         snd_pcm_free(pcm);  
  33.         return err;  
  34.     }  
  35.     mutex_init(&pcm->open_mutex);  
  36.     init_waitqueue_head(&pcm->open_wait);  
  37.     if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {  
  38.         snd_pcm_free(pcm);  
  39.         return err;  
  40.     }  
  41.     if (rpcm)  
  42.         *rpcm = pcm;  
  43.     return 0;  
  44. }  

我们再看看PCM设备的系统调用接口:

[cpp] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  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_pcm_f_ops[0]是回放使用的系统调用接口,snd_pcm_f_ops[1]是录制使用的系统调用接口。

 

6.6. 参考资料

·          Linux ALSA声卡驱动之二:声卡的创建:http://blog.csdn.net/droidphone/article/details/6289712

·          Linux ALSA声卡驱动之三:PCM设备的创建:http://blog.csdn.net/droidphone/article/details/6308006

posted on 2016-08-24 16:24  jamboo  阅读(931)  评论(0编辑  收藏  举报

导航