alsa音频架构1

第一部分 alsa子系统关键结构体对象等

1.声卡设备类型定义

[cpp]  view plain copy
  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)  

一个声卡可以有多个声卡设备,alsa中用snd_card描述声卡对象,用snd_device描述声卡设备对象

2.声卡结构体

[cpp]  view plain copy
  1. struct snd_card {  
  2.     int number;             //声卡索引号  
  3.     char id[16];            //id识别字串  
  4.     char driver[16];        //驱动名  
  5.     char shortname[32];     //短名  
  6.     char longname[80];      //长名  
  7.     char mixername[80];     /* mixer name */  
  8.     char components[128];   /* card components delimited with space */  
  9.     struct module *module;  //模块所有者  
  10.     void *private_data;     /* private data for soundcard */  
  11.     void (*private_free) (struct snd_card *card); /* callback for freeing of private data */  
  12.     struct list_head devices;   //设备链表  
  13.     unsigned int last_numid;    /* last used numeric ID */  
  14.     struct rw_semaphore controls_rwsem; /* controls list lock */  
  15.     rwlock_t ctl_files_rwlock;  /* ctl_files list lock */  
  16.     int controls_count;     /* count of all controls */  
  17.     int user_ctl_count;     /* count of all user controls */  
  18.     struct list_head controls;  //控制链表  
  19.     struct list_head ctl_files; /* active control files */  
  20.     struct snd_info_entry *proc_root;   /* root for soundcard specific files */  
  21.     struct snd_info_entry *proc_id; /* the card id */  
  22.     struct proc_dir_entry *proc_root_link;  /* number link to real id */  
  23.     struct list_head files_list;    /* all files associated to this card */  
  24.     struct snd_shutdown_f_ops *s_f_ops; /* file operations in the shutdown state */  
  25.     spinlock_t files_lock;      /* lock the files for this card */  
  26.     int shutdown;           /* this card is going down */  
  27.     int free_on_last_close;     /* free in context of file_release */  
  28.     wait_queue_head_t shutdown_sleep;  
  29.     struct device *dev;     //设备文件  
  30.     struct device *card_dev;    //声卡设备文件  
  31. #ifdef CONFIG_PM  
  32.     unsigned int power_state;   /* power state */  
  33.     struct mutex power_lock;    /* power lock */  
  34.     wait_queue_head_t power_sleep;  
  35. #endif  
  36. #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)  
  37.     struct snd_mixer_oss *mixer_oss;  
  38.     int mixer_oss_change_count;  
  39. #endif  
  40. };  


2.1.全局变量snd_cards

在"/sound/core/init.c"

[cpp]  view plain copy
  1. struct snd_card *snd_cards[SNDRV_CARDS];  

SNDRV_CARDS为8也就是声卡最多8个

3.声卡设备结构体

[cpp]  view plain copy
  1. struct snd_device {  
  2.     struct list_head list;      //链表  
  3.     struct snd_card *card;      //所属的声卡  
  4.     snd_device_state_t state;   //设备状态  
  5.     snd_device_type_t type;     //设备类型  
  6.     void *device_data;      /* device structure */  
  7.     struct snd_device_ops *ops; //声卡设备操作函数集  
  8. };  

3.1 设备状态的值

[cpp]  view plain copy
  1. #define SNDRV_DEV_BUILD     ((__force snd_device_state_t) 0)//创建  
  2. #define SNDRV_DEV_REGISTERED    ((__force snd_device_state_t) 1)//注册  
  3. #define SNDRV_DEV_DISCONNECTED  ((__force snd_device_state_t) 2)//断开连接  

3.2 设备类型
也就是上面 1.声卡设备类型定义 所指定的类型

4.声卡操作函数集

[cpp]  view plain copy
  1. struct snd_device_ops {  
  2.     int (*dev_free)(struct snd_device *dev);        //释放  
  3.     int (*dev_register)(struct snd_device *dev);    //注册  
  4.     int (*dev_disconnect)(struct snd_device *dev);  //断开连接  
  5. };  


第二部分 声卡

1.声卡创建

传递进来的idx为负值,则系统会分配一个idx作为全局snd_cards数组的索引项值,xid字串用来区分描述声卡id,module一般为THIS_MODULE...

[cpp]  view plain copy
  1. int snd_card_create(int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)  
  2. {  
  3.     struct snd_card *card;  
  4.     int err, idx2;  
  5.     if (snd_BUG_ON(!card_ret))  
  6.         return -EINVAL;  
  7.     *card_ret = NULL;  
  8.     if (extra_size < 0)  
  9.         extra_size = 0;  
  10.     card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); //分配声卡对象和额外空间的内存  
  11.     if (!card)  
  12.         return -ENOMEM;  
  13.     if (xid)    //若需要填充声卡id识别字串  
  14.         strlcpy(card->id, xid, sizeof(card->id)); //card->id=xid 声卡id识别字串  
  15.     err = 0;  
  16.       
  17.     mutex_lock(&snd_card_mutex);    //idx为负值则交由系统选择一个值______________________<  
  18.     if (idx < 0) {     
  19.         for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)  
  20.             /* idx == -1 == 0xffff means: take any free slot */  
  21.             if (~snd_cards_lock & idx & 1<<idx2) {  
  22.                 if (module_slot_match(module, idx2)) {  
  23.                     idx = idx2;  
  24.                     break;  
  25.                 }  
  26.             }  
  27.     }  
  28.     if (idx < 0) {  
  29.         for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)  
  30.             /* idx == -1 == 0xffff means: take any free slot */  
  31.             if (~snd_cards_lock & idx & 1<<idx2) {  
  32.                 if (!slots[idx2] || !*slots[idx2]) {  
  33.                     idx = idx2;  
  34.                     break;  
  35.                 }  
  36.             }  
  37.     }  
  38.     if (idx < 0)  
  39.         err = -ENODEV;  
  40.     else if (idx < snd_ecards_limit) {  
  41.         if (snd_cards_lock & (1 << idx))  
  42.             err = -EBUSY;   /* invalid */  
  43.     } else if (idx >= SNDRV_CARDS)  
  44.         err = -ENODEV;  
  45.     if (err < 0) {  
  46.         mutex_unlock(&snd_card_mutex);  
  47.         snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i), error: %d\n",idx, snd_ecards_limit - 1, err);  
  48.         goto __error;  
  49.     }  
  50.     snd_cards_lock |= 1 << idx;       /* lock it */  
  51.     if (idx >= snd_ecards_limit)  
  52.         snd_ecards_limit = idx + 1; /* increase the limit */  
  53.     mutex_unlock(&snd_card_mutex);      //______________________>  
  54.       
  55.     card->number = idx;      //声卡对象索引号 全局数组snd_cards的数组下标  
  56.     card->module = module;   //声卡对象模块所有者THIS_MODULE  
  57.     INIT_LIST_HEAD(&card->devices);  //初始化声卡设备链表  
  58.     init_rwsem(&card->controls_rwsem);   //初始化读写信号量  
  59.     rwlock_init(&card->ctl_files_rwlock);    //初始化读写锁  
  60.     INIT_LIST_HEAD(&card->controls); //初始化声卡控制链表  
  61.     INIT_LIST_HEAD(&card->ctl_files);    //初始化声卡控制文件链表  
  62.     spin_lock_init(&card->files_lock);   //初始化自旋锁  
  63.     INIT_LIST_HEAD(&card->files_list);   //初始化声卡文件链表  
  64.     init_waitqueue_head(&card->shutdown_sleep);  //初始化关机队列头  
  65. #ifdef CONFIG_PM  
  66.     mutex_init(&card->power_lock);   //初始化电源互斥锁  
  67.     init_waitqueue_head(&card->power_sleep); //初始化睡眠等待队列头  
  68. #endif  
  69.     err = snd_ctl_create(card); //创建用于控制的声卡设备对象  
  70.     if (err < 0) {  
  71.         snd_printk(KERN_ERR "unable to register control minors\n");  
  72.         goto __error;  
  73.     }  
  74.     err = snd_info_card_create(card);   //proc下面的接口  
  75.     if (err < 0) {  
  76.         snd_printk(KERN_ERR "unable to create card info\n");  
  77.         goto __error_ctl;  
  78.     }  
  79.     if (extra_size > 0)  //有额外数据  
  80.         card->private_data = (char *)card + sizeof(struct snd_card);  
  81.     *card_ret = card;  
  82.     return 0;  
  83.   
  84.       __error_ctl:  
  85.     snd_device_free_all(card, SNDRV_DEV_CMD_PRE);  
  86.       __error:  
  87.     kfree(card);  
  88.     return err;  
  89. }  
  90. EXPORT_SYMBOL(snd_card_create);  

这里主要是初始化了声卡的devices声卡设备链表,以后创建的声卡设备将挂在该链表上

并调用了snd_ctl_create创建了用于控制的声卡设备对象,这我们可以得出一个结论每个声卡都有一个声卡控制设备对象

第三部分 声卡设备

1.创建声卡设备

在这里 声卡设备与声卡捆绑,指定声卡设备类型,设置声卡设备状态,捆绑对应的snd_device_ops方法,添加声卡设备到声卡的devices链表

[cpp]  view plain copy
  1. int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops)  
  2. {  
  3.     struct snd_device *dev;  
  4.   
  5.     if (snd_BUG_ON(!card || !device_data || !ops))  
  6.         return -ENXIO;  
  7.     dev = kzalloc(sizeof(*dev), GFP_KERNEL);    //分配声卡设备内存  
  8.     if (dev == NULL) {  
  9.         snd_printk(KERN_ERR "Cannot allocate device\n");  
  10.         return -ENOMEM;  
  11.     }  
  12.     dev->card = card;    //设备捆绑对象  
  13.     dev->type = type;    //指定设备类型  
  14.     dev->state = SNDRV_DEV_BUILD;    //设备状态 已创建  
  15.     dev->device_data = device_data;  //设备数据  
  16.     dev->ops = ops;  //设备操作函数集  
  17.     list_add(&dev->list, &card->devices); //添加到声卡对象的设备devices链表中  
  18.     return 0;  
  19. }  
  20. EXPORT_SYMBOL(snd_device_new);  

这里声卡设备的snd_device_ops是传递进来的结构体指针,实际操作中一般调用以下API来创建不同类型的声卡

1.1 创建声卡设备常见API

[cpp]  view plain copy
  1. snd_ctl_create      -->snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);  
  2. snd_card_proc_new   -->snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)  
  3. snd_timer_new       -->snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops)  
  4. snd_jack_new        -->snd_device_new(card, SNDRV_DEV_JACK, jack, &ops)  
  5. snd_pcm_new         -->snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)  
  6. snd_rawmidi_new     -->snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops)  
  7. snd_seq_device_new  -->snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)  
  8. snd_hwdep_new       -->snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)  
  9. snd_i2c_bus_create  -->snd_device_new(card, SNDRV_DEV_BUS, bus, &ops)  
  10. snd_hda_bus_new     -->snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)  
  11. snd_ac97_bus        -->snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)  
  12. ...//不完整  

这些API都静态设置了对应类型的snd_device_ops结构体ops,然后调用snd_device_new并将ops传递进来
后面我们再针对不同的声卡设备类型展开分析

创建声卡设备后系统大致入下图描述

创建后的声卡设备的state值为SNDRV_DEV_BUILD

 

第四部分 注册声卡

1.注册声卡

[cpp]  view plain copy
  1. int snd_card_register(struct snd_card *card)  
  2. {  
  3.     int err;  
  4.     if (snd_BUG_ON(!card))  
  5.         return -EINVAL;  
  6.     if (!card->card_dev) {  
  7.         //创建设备文件"/sys/class/sound/cardX"  
  8.         card->card_dev = device_create(sound_class, card->dev,MKDEV(0, 0), card,"card%i", card->number);  
  9.         if (IS_ERR(card->card_dev))  
  10.             card->card_dev = NULL;  
  11.     }  
  12.   
  13.     if ((err = snd_device_register_all(card)) < 0)   //-->1.1.注册所有的声卡设备  
  14.         return err;  
  15.     mutex_lock(&snd_card_mutex);  
  16.     if (snd_cards[card->number]) {   //判断对应数组项是否已给占用"/sound/core/init.c" struct snd_card *snd_cards[SNDRV_CARDS];  
  17.         mutex_unlock(&snd_card_mutex);  
  18.         return 0;  
  19.     }  
  20.     snd_card_set_id_no_lock(card, card->id[0] == '\0' ? NULL : card->id);  
  21.     snd_cards[card->number] = card;  //填充全局snd_cards数组  
  22.     mutex_unlock(&snd_card_mutex);  
  23.     init_info_for_card(card);   //proc接口  
  24. #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)  
  25.     if (snd_mixer_oss_notify_callback)  
  26.         snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);  
  27. #endif  
  28.     if (card->card_dev) {    //创建属性文件  
  29.         err = device_create_file(card->card_dev, &card_id_attrs);  
  30.         if (err < 0)  
  31.             return err;  
  32.         err = device_create_file(card->card_dev, &card_number_attrs);  
  33.         if (err < 0)  
  34.             return err;  
  35.     }  
  36.   
  37.     return 0;  
  38. }  
  39. EXPORT_SYMBOL(snd_card_register);  

主要是调用snd_device_register_all函数注册所有声卡设备,其次是填充了全局snd_cards数组对应的数组项
1.1注册挂在该声卡下面的所有声卡设备

[cpp]  view plain copy
  1. int snd_device_register_all(struct snd_card *card)  
  2. {  
  3.     struct snd_device *dev;  
  4.     int err;  
  5.       
  6.     if (snd_BUG_ON(!card))  
  7.         return -ENXIO;  
  8.     list_for_each_entry(dev, &card->devices, list) { //遍历声卡的设备devices链表  
  9.         if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {//状态为已建立且存在dev_register方法  
  10.             if ((err = dev->ops->dev_register(dev)) < 0)   //调用声卡设备的dev_register方法  
  11.                 return err;  
  12.             dev->state = SNDRV_DEV_REGISTERED;   //修改状态为已注册  
  13.         }  
  14.     }  
  15.     return 0;  
  16. }  

这里会遍历声卡对象的devices设备链表,然后调用声卡设备所捆绑的声卡设备操作函数集合的dev_register方法,注册初始化对应的声卡设备

注册完声卡后,声卡设备的state值修改为SNDRV_DEV_REGISTERED

 

第五部分 注册声卡设备

不同类型的声卡设备的操作函数集的dev_register不同,但是也有其共性,下面主要是针对共性来分析

1.snd_minor声卡字符设备结构体

[cpp]  view plain copy
  1. struct snd_minor {  
  2.     int type;           //声卡设备类型 SNDRV_DEVICE_TYPE_XXX  
  3.     int card;           //声卡索引号  
  4.     int device;         /* device number */  
  5.     const struct file_operations *f_ops;    //文件操作函数集  
  6.     void *private_data;     //私有数据  
  7.     struct device *dev;     //设备文件  
  8. };  
1.1 snd_minor的type类型
[cpp]  view plain copy
  1. enum {  
  2.     SNDRV_DEVICE_TYPE_CONTROL,          //控制  
  3.     SNDRV_DEVICE_TYPE_SEQUENCER,        //音序器  
  4.     SNDRV_DEVICE_TYPE_TIMER,            //定时器  
  5.     SNDRV_DEVICE_TYPE_HWDEP,            //硬件依赖层  
  6.     SNDRV_DEVICE_TYPE_RAWMIDI,          //raw midi  
  7.     SNDRV_DEVICE_TYPE_PCM_PLAYBACK,     //PCM回放  
  8.     SNDRV_DEVICE_TYPE_PCM_CAPTURE,      //PCM捕捉  
  9. };  
1.2 全局snd_minors全局数组,数组项最大值SNDRV_OS_MINORS为256
[cpp]  view plain copy
  1. static struct snd_minor *snd_minors[SNDRV_OS_MINORS];  


2.注册设备文件snd_register_device
封装了(2.1)snd_register_device_for_dev函数
[cpp]  view plain copy
  1. static inline int snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data,const char *name)  
  2. {  
  3.     return snd_register_device_for_dev(type, card, dev, f_ops,private_data, name,snd_card_get_device_link(card));  
  4. }  
2.1注册设备文件

[cpp]  view plain copy
  1. int snd_register_device_for_dev(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,  
  2.                 void *private_data,const char *name, struct device *device)  
  3. {  
  4.     int minor;  //次设备号  
  5.     struct snd_minor *preg; //声明一个snd_minor结构体  
  6.   
  7.     if (snd_BUG_ON(!name))  
  8.         return -EINVAL;  
  9.     preg = kmalloc(sizeof *preg, GFP_KERNEL);   //分配snd_minor结构体内存  
  10.     if (preg == NULL)  
  11.         return -ENOMEM;  
  12.     preg->type = type;   //设置snd_minor类型  
  13.     preg->card = card ? card->number : -1;    //声卡索引号  
  14.     preg->device = dev;  //设备文件  
  15.     preg->f_ops = f_ops; //文件操作函数集合  
  16.     preg->private_data = private_data;   //私有数据  
  17.     mutex_lock(&sound_mutex);  
  18. #ifdef CONFIG_SND_DYNAMIC_MINORS  
  19.     minor = snd_find_free_minor();  
  20. #else  
  21.     minor = snd_kernel_minor(type, card, dev);  //获取次设备号  
  22.     if (minor >= 0 && snd_minors[minor])  
  23.         minor = -EBUSY;  
  24. #endif  
  25.     if (minor < 0) {  
  26.         mutex_unlock(&sound_mutex);  
  27.         kfree(preg);  
  28.         return minor;  
  29.     }  
  30.     snd_minors[minor] = preg;   //填充全局snd_minors数组项  
  31.     preg->dev = device_create(sound_class, device, MKDEV(major, minor),private_data, "%s", name);    //创建"/dev/snd/XXX"  
  32.     if (IS_ERR(preg->dev)) {  
  33.         snd_minors[minor] = NULL;  
  34.         mutex_unlock(&sound_mutex);  
  35.         minor = PTR_ERR(preg->dev);  
  36.         kfree(preg);  
  37.         return minor;  
  38.     }  
  39.     mutex_unlock(&sound_mutex);  
  40.     return 0;  
  41. }  
  42. EXPORT_SYMBOL(snd_register_device_for_dev);  
主要是获取次设备号,并创建设备文件,捆绑文件操作函数集合

第六部分 声卡驱动的编写框架

综合上面五个部分得出下图


编写过程为先调用snd_card_create创建声卡,接着调用创建声卡设备的API创建不同类型的声卡设备组件,接着调用snd_card_register注册声卡就行.

 大致走完上面的流程后系统的框图


 

第七部分 声卡核心子系统的初始化工作

1.声明子系统

[cpp]  view plain copy
  1. subsys_initcall(init_soundcore);  

2.子系统初始化

[cpp]  view plain copy
  1. static int __init init_soundcore(void)  
  2. {  
  3.     int rc;  
  4.     rc = init_oss_soundcore();//初始化oss子系统部分  
  5.     if (rc)  
  6.         return rc;  
  7.     sound_class = class_create(THIS_MODULE, "sound");   //创建设备类"/sys/class/sound/"  
  8.     if (IS_ERR(sound_class)) {  
  9.         cleanup_oss_soundcore();  
  10.         return PTR_ERR(sound_class);  
  11.     }  
  12.     sound_class->devnode = sound_devnode;    //创建设备节点的方法  
  13.     return 0;  
  14. }  

主要初始化oss子系统部分
2.1 oss子系统初始化

[cpp]  view plain copy
  1. static int __init init_oss_soundcore(void)  
  2. {  
  3.     if (preclaim_oss && register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops) == -1) {//创建字符设备  
  4.         printk(KERN_ERR "soundcore: sound device already in use.\n");  
  5.         return -EBUSY;  
  6.     }  
  7.     return 0;  
  8. }  

2.2 指定sound_class类的创建设备节点方法sound_devnode

[cpp]  view plain copy
  1. static char *sound_devnode(struct device *dev, mode_t *mode)  
  2. {  
  3.     if (MAJOR(dev->devt) == SOUND_MAJOR) //主设备号14 oss子系统  
  4.         return NULL;  
  5.     return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));  //alsa子系统 "/dev/sndX/"  
  6. }  

所以alsa子系统的音频设备会出现在/dev/snd/目录下
这里我们可以得知alsa架构的设备节点在/dev/snd/目录下,oss架构的设备节点在/dev下

alsa的主设备号为116,oss架构的主设备号为14

alsa的主设备号在/sound/core/sound.c中定义

[cpp]  view plain copy
  1. static int major = CONFIG_SND_MAJOR;  
[cpp]  view plain copy
  1. #define CONFIG_SND_MAJOR    116  


第八部分 声卡控制设备浅析 

前面讲到每个声卡都有一个声卡控制设备对象,所以研究下声卡控制设备

在声卡创建函数snd_card_create中调用了snd_ctl_create函数创建声卡控制设备,并将声卡对象作为参数传递进来

1.创建声卡控制设备

[cpp]  view plain copy
  1. int snd_ctl_create(struct snd_card *card)  
  2. {  
  3.     static struct snd_device_ops ops = {//静态初始化snd_device_ops声卡设备操作函数集结构体  
  4.         .dev_free = snd_ctl_dev_free,//释放方法  
  5.         .dev_register = snd_ctl_dev_register,//注册方法  
  6.         .dev_disconnect = snd_ctl_dev_disconnect,//断开连接方法  
  7.     };  
  8.   
  9.     if (snd_BUG_ON(!card))  
  10.         return -ENXIO;  
  11.     return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);//创建声卡控制设备  
  12. }  

这里还需注意一下snd_device_new函数的参数,发现声卡控制设备的device_data是指向声卡对象的

在注册声卡过程中会调用snd_device_register_all函数,该函数则调用声卡控制设备dev_register方法,既snd_ctl_dev_register函数

2.注册声卡控制设备

[cpp]  view plain copy
  1. static int snd_ctl_dev_register(struct snd_device *device)  
  2. {  
  3.     struct snd_card *card = device->device_data; //获取声卡对象  
  4.     int err, cardnum;  
  5.     char name[16];  
  6.   
  7.     if (snd_BUG_ON(!card))  
  8.         return -ENXIO;  
  9.     cardnum = card->number;  //获取声卡索引号  
  10.     if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))  
  11.         return -ENXIO;  
  12.     sprintf(name, "controlC%i", cardnum);   //设置名字-->"/dev/snd/controlCx"  
  13.     if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, name)) < 0) //注册声卡控制设备  
  14.         return err;  
  15.     return 0;  
  16. }  

通过snd_register_device创建了/dev/snd/controlC0设备文件,假设是0号声卡吧!并捆绑了snd_ctl_f_ops设备文件操作函数集

3.声卡控制设备对应的设备文件操作函数集

[cpp]  view plain copy
  1. static const struct file_operations snd_ctl_f_ops =  
  2. {  
  3.     .owner =    THIS_MODULE,  
  4.     .read =     snd_ctl_read,   //读方法  
  5.     .open =     snd_ctl_open,   //打开方法  
  6.     .release =  snd_ctl_release,    //释放方法  
  7.     .llseek =   no_llseek,  
  8.     .poll =     snd_ctl_poll,   //轮询方法  
  9.     .unlocked_ioctl =   snd_ctl_ioctl,  //命令控制  
  10.     .compat_ioctl = snd_ctl_ioctl_compat,   //32位兼容的命令控制  
  11.     .fasync =   snd_ctl_fasync, //同步方法  
  12. };  

这样就提供了应用层的接口方法了,比较重要的是命令控制方法,主要有以下控制命令

[cpp]  view plain copy
  1. #define SNDRV_CTL_IOCTL_PVERSION    _IOR('U', 0x00, int)//打印alsa版本  
  2. #define SNDRV_CTL_IOCTL_CARD_INFO   _IOR('U', 0x01, struct snd_ctl_card_info)//获取声卡信息  
  3. #define SNDRV_CTL_IOCTL_ELEM_LIST   _IOWR('U', 0x10, struct snd_ctl_elem_list)  
  4. #define SNDRV_CTL_IOCTL_ELEM_INFO   _IOWR('U', 0x11, struct snd_ctl_elem_info)  
  5. #define SNDRV_CTL_IOCTL_ELEM_READ   _IOWR('U', 0x12, struct snd_ctl_elem_value)  
  6. #define SNDRV_CTL_IOCTL_ELEM_WRITE  _IOWR('U', 0x13, struct snd_ctl_elem_value)  
  7. #define SNDRV_CTL_IOCTL_ELEM_LOCK   _IOW('U', 0x14, struct snd_ctl_elem_id)  
  8. #define SNDRV_CTL_IOCTL_ELEM_UNLOCK _IOW('U', 0x15, struct snd_ctl_elem_id)  
  9. #define SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS _IOWR('U', 0x16, int)  
  10. #define SNDRV_CTL_IOCTL_ELEM_ADD    _IOWR('U', 0x17, struct snd_ctl_elem_info)  
  11. #define SNDRV_CTL_IOCTL_ELEM_REPLACE    _IOWR('U', 0x18, struct snd_ctl_elem_info)  
  12. #define SNDRV_CTL_IOCTL_ELEM_REMOVE _IOWR('U', 0x19, struct snd_ctl_elem_id)  
  13. #define SNDRV_CTL_IOCTL_TLV_READ    _IOWR('U', 0x1a, struct snd_ctl_tlv)  
  14. #define SNDRV_CTL_IOCTL_TLV_WRITE   _IOWR('U', 0x1b, struct snd_ctl_tlv)  
  15. #define SNDRV_CTL_IOCTL_TLV_COMMAND _IOWR('U', 0x1c, struct snd_ctl_tlv)  
  16. #define SNDRV_CTL_IOCTL_POWER   _IOWR('U', 0xd0, int)//还没支持  
  17. #define SNDRV_CTL_IOCTL_POWER_STATE _IOR('U', 0xd1, int)//电源状态  

如何与应用层交互参考alsa-lib的说明http://www.alsa-project.org/main/index.php/Main_Page
alsa提供了很多工具可以使用
alsa-utils工具集

[cpp]  view plain copy
  1. aconnect        is a utility for connecting and disconnecting two existing ports in the ALSA sequencer system.    
  2. alsaconf        is a configuration tool which tries to detect the sound cards on your system and write a suitable configuration file for ALSA. This program is incompatible with Udev.   
  3. alsactl         is used to control advanced settings for the ALSA sound card drivers.    
  4. alsaloop        allows creation of a PCM loopback between a PCM capture device and a PCM playback device.   
  5. alsamixer       is an Ncurses based mixer program for use with the ALSA sound card drivers.   
  6. amidi           is used to read from and write to ALSA RawMIDI ports.   
  7. amixer          allows command-line control of the mixers for the ALSA sound card drivers.   
  8. aplay           is a command-line soundfile player for the ALSA sound card drivers.   
  9. aplaymidi       is a command-line utility that plays the specified MIDI file(s) to one or more ALSA sequencer ports.   
  10. arecord         is a command-line soundfile recorder for the ALSA sound card drivers.   
  11. arecordmidi     is a command-line utility that records a standard MIDI file from one or more ALSA sequencer ports.   
  12. aseqdump        is a command-line utility that prints the sequencer events it receives as text.    
  13. aseqnet         is an ALSA sequencer client which sends and receives event packets over a network.   
  14. iecset          is a small utility to set or dump the IEC958 (or so-called “S/PDIF”) status bits of the specified sound card via the ALSA control API.   
  15. speaker-test    is a command-line speaker test tone generator for ALSA.  

还有一个带图形界面的alsamixer工具,相当强大

 alsa-utils工具的使用

alsact

l用来对alsa声卡驱动进行一些高级的设置.系统中装有多个声卡,它也可以支持.
有时在音量控制面板无法调整的选项,可以使用alsactl来实现.
alsactl可以将指定声卡的驱动程序设置信息保存到配置文件.或从配置文件中恢复指定
声卡的驱动程序的设置信息.

alsactl格式:

       alsactl [options] [store|restore] <card # or id>

选项:

       -h, --help
		打印帮助信息

        -f, --file
		指定使用的配置文件,默认为/etc/asound.state.

       -F, --force
		与恢复命令一起使用.表示最大限度的恢复设置值.

       -d, --debug
		调试模式,输出更多细节信息.

       -v, --version
		打印alsactl版本号.

文件:
	/etc/asound.state(或使用-f指定的文件)保存有声卡所有混合器的设置信息.

示例:

# rm /etc/asound.state -f
# alsactl store

 

aconnect

是ALSA音序器的连接管理器.用来连接或断开ALSA音序器上的端口.端口是
可以随意定义的.
如,使用aconnect可以连接到任何由aseqview建立的设备端口.

命令格式:

       aconnect [-d] [-options] sender receiver
       aconnect -i|-o [-options]
       aconnect -x

选项:
连接管理
       -d, --disconnect
		断开连接.

       -e, --exclusive
		使用独占模式连接端口.发送和接收端口将不能再与其他端口相连.

       -r, --real queue
		将时间包的时间戳,转换为真实时间队列的当前值.

显示端口
       -i, --input
		显示存在的输入端口.

       -o, --output
		显示存在的输出端口.

       -l, --list
		显示当前的连接状态.

删除连接
       -x, --removeall
		删除所有连接.

示例:

连接端口64:0到65:0:
           % aconnect 64:0 65:0
这个连接是单向的,所有到发送端口64:0的数据,将被重定向到接收65:0端口.如果有另一个端口65:1,也使用64:0作为发送端口,则数据会同时发送到2个接收端口.
端口连接时,使用:
           % aconnect -d 64:0 65:0

地址也可以使用客户端的名字来代替:
           % aconnect External:0 Emu8000:1

使用-i打印出输入端口信息.-o打印出输出端口信息.
           % aconnect -i
           client 0: ’System’ [type=kernel]
               0 ’Timer           ’
               1 ’Announce        ’
           client 64: ’External MIDI-0’ [type=kernel]
               0 ’MIDI 0-0        ’

可以使用-x选项来清除所有的连接.
           % aconnect -x

 

alsamixer

是一个终端界面的声卡音量调节器.

命令格式:
       alsamixer [options]

选项:
       -h, -help
		显示帮助信息.

       -c <card number or idenfication>
		指定需要设置的声卡.默认为0.

       -D <device identification>
		选择需要控制的调节器.

       -g
		设置界面颜色.

       -s
		最小化界面窗口.

快捷键:
	进入alsamixer界面后,可以使用下面快捷键进行控制:

   常规控制:
	左右箭头或n,p	用来选择通道.

	上下箭头或+,-	同时调整选定通道的左右声道的音量.

	B,=	设置左右声道音量相同.

	M	静音当前通道.<,>分别对左,右声道静音.

	空格	选择录音源.在选定的通道上按"空格",可以标记此通道为录音源.此操作仅限输入设备.插入键或";",删除键或"'"分别选定左右通道.

	L	刷新屏幕.

  快捷设置
          PageUp	增大5格音量.

         PageDown	减小5格音量.
	
         End	设置音量为0.

	分别调整左,右或整个通道的音量.
	Q,W,E	增大 左,右,通道 的音量.

	Z,X,C	减小 左,右,通道 的音量.

	alt-q,ESC	退出.
 

amidi

的作用是对ALSA的RawMIDI端口进行读写.
amidi是一个命令行工具,允许你以独占模式向MIDI设备读/写数据.

命令格式:
       amidi options

选项:
	-h,-V,-l,-L	用于显示信息.
	-s,-r,-S,-d	用于发送/接收数据.

       -h, --help
		打印帮助信息.

       -V, --version
		打印版本号.

       -l, --list-devices
		打印所有硬件MIDI端口的列表.

       -L, --list-rawmidis
		打印所有RawMIDI定义.

       -p, --port=name
		设置要使用的ALSA RawMIDI端口.若不指定,则使用声卡0的端口0.

       -s, --send=filename
		发送指定文件的内容到MIDI端口.文件中必须包含raw MIDI命令(.syx,.mid文件).

       -r, --receive=filename
		将MIDI端口接收的数据写入指定文件.

       -S, --send-hex="..."
		发送十六进制字节到MIDI端口.

       -d, --dump
		从MIDI端口接收数据,然后以十六进制形式打印出来.

       -t, --timeout=秒
		指定超时,当端口无数据输出达到超时时长时,将停止接收数据.

示例:

       amidi -p hw:0 -s my_settings.syx
		发送my_settings.syx终端MIDI命令到端口 hw:0.

       amidi -S ’
		发送XG复位到默认端口.

       amidi -p virtual -d
		建立一个虚拟RawMIDI端口,然后发送所有数据到这个端口.

 

amixer

是命令行的ALSA声卡驱动调节器工具.
amixer用来在命令行控制ALSA的调节器,并且支持多声卡.
amixer不加参数时,将打印默认声卡的设置信息.

命令格式:

       amixer [-c card] [cmd]

命令:
       help   显示语法帮助.

       info   显示调节器设备的信息.

       scontrols	显示调节器器的完整列表 .

       scontents	显示包含详细信息的调节器的完整列表.

       set or sset <SCONTROL> <PARAMETER> ...设置调节器信息.

       get or sget <SCONTROL> 显示调节器的信息.

       controls	显示声卡控制器的信息.
		
       contents	显示完整的声卡控制器信息.

       cset <CONTROL> <PARAMETER> ... 设置声卡控制器信息.

       cget <CONTROL>	显示声卡控制器的信息.

选项:
       [-c card]
		选择指定的声卡.

       [-D device]
		选择需要控制的设备名.默认是 default.

       -h     Help
		显示帮助信息.

       -q
		安静模式.不输出设置结果.

示例:

      # amixer -c 1 sset Line,0 80%,40% unmute cap
	设置第2块声卡的"line"的左声道音量为80%,右声道为40%,取消静音,并设置它为声音源.

      # amixer -c 2 cset numid=34 40%
	设置第34个声卡元素为40%.

 

arecord,aplay

是命令行的ALSA声卡驱动的录音和播放工具.
arecord是命令行ALSA声卡驱动的录音程序.支持多种文件格式和多个声卡.
aplay是命令行播放工具,支持多种文件格式.

命令格式:

       arecord [flags] [filename]
       aplay [flags] [filename [filename]] ...

选项:
       -h, --help
             帮助.

       --version
              打印版本信息.

       -l, --list-devices
              列出全部声卡和数字音频设备.

       -L, --list-pcms
              列出全部PCM定义.

       -D, --device=NAME
	     指定PCM设备名称.

       -q --quiet
		安静模式.

       -t, --file-type TYPE
		文件类型(voc,wav,raw或au).

       -c, --channels=#
		设置通道号.

       -f --format=FORMAT
	     设置格式.格式包括:S8  U8  S16_LE  S16_BE  U16_LE U16_BE  S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE
              FLOAT_LE  FLOAT_BE  FLOAT64_LE  FLOAT64_BE   IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAW A_LAW IMA_ADPCM MPEG GSM

       -r, --rate=#<Hz>
		设置频率.

       -d, --duration=#
		设置持续时间,单位为秒.

       -s, --sleep-min=#
		设置最小休眠时间.

       -M, --mmap
		mmap流.

       -N, --nonblock
		设置为非块模式.

       -B, --buffer-time=#
		缓冲持续时长.单位为微妙.

       -v, --verbose
		显示PCM结构和设置.

       -I, --separate-channels
		设置为每个通道一个单独文件.

示例:

       aplay -c 1 -t raw -r 22050 -f mu_law foobar
	播放raw文件foobar.以22050Hz,单声道,8位,mu_law格式.

       arecord -d 10 -f cd -t wav -D copy foobar.wav
	以CD质量录制foobar.wav文件10秒钟.使用PCM的"copy".

 

aplaymidi

用来播放标准的MIDI文件.
aplaymidi是一个命令行工具,可以在一个或多个ALSA端口上播放MIDI
文件.

命令格式:

       aplaymidi -p client:port[,...] [-d delay] midifile ...

选项:
       -h, --help
              输出帮助信息.

       -V, --version
              输出版本信息.

       -l, --list
              输出可以使用的输出端口列表.

       -p, --port=client:port,...
	    设置端口.

       -d, --delay=seconds
	    设置MIDI文件结束后,等待时长.

 

arecordmidi

用于录制标准的MIDI文件.
arecordmidi可以从一个或多个ALSA端口上,录制一个标准MIDI文件.

命令格式:

       arecordmidi -p client:port[,...] [options] midifile

选项:
       -h,--help
              打印帮助信息.

       -V,--version
              打印版本号.

       -l,--list
              打印可以使用的输入端口.

       -p,--port=client:port,...
	     设置端口.

       -b,--bpm=beats
	     设置MIDI文件的速率,默认为120 BPM.

       -f,--fps=frames
	      设置帧率.

       -s,--split-channels
	      设置每个通道将录制成一个单独的MIDI文件.

       -d,--dump
	      在标准输出上,以文本形式显示接受到的事件信息

 

aseqnet

是ALSA调节器的网络连接工具.
aseqnet是ALSA调节器的客户端程序,可以从网络上发送和接收事件数据包.
网络上有主机A,主机B.A为服务器端,B为客户端.ALSA调节器系统必须同事运行
在两个服务器上.然后建立服务器端口:

hostA% aseqnet
 sequencer opened: 128:0

在HostB上执行:

           hostB% aseqnet hostA
           sequencer opened: 132:0

现在所有发送到HostA:128:0的数据将被传送到HostB:132:0上,反之亦然.

命令格式:

       aseqnet [remotehost]

选项:
       -p port
		指定TCP端口号或服务名.

       -s addr
		设置指定地址用于读操作.

       -d addr
		设置指定地址用于写操作.
       -v
		详细输出模式.

 

设置或输出IEC958状态位.
iecset

是个小工具,通过ALSA的API,设置或输出IEC958(或称S/PDIF)状态位信息.
直接运行iecset将输出当前IEC958的状态信息. 命令格式:

       iecset [options] [cmd arg...]

选项:
       -D device
		设置需要打开的设备名.

       -c card
		设置需要打开的网卡名.

       -x
		输出AESx字节格式的状态信息.

       -i
		从标准输入读取命令信息,每行一个命令.

命令:
       professional <bool>
		专业模式(true)或用户模式(false).

       audio <bool>
		音频模式(true).

       rate <int>
		采样频率,单位Hz.

       emphasis <int>
		设置加强值.0 = none, 1 = 50/15us, 2 = CCITT.

       lock <bool>
		速率锁.

       sbits <int>
		采样位:2 = 20bit, 4 = 24bit, 6 = undefined.

       wordlength <int>
		设置字长:0  =  No,  2 = 22-18 bit, 4 = 23-19 bit, 5 = 24-20
              bit, 6 = 20-16 bit. 

       category <int>
		分类:值从0到0x7f.

       copyright <bool>
		设置是否包含版权.

       original <boo>
		原始标记:

示例:

输出当前IEC958信息.
$ iecset
	Mode: consumer
	Data: audio
	Rate: 44100 Hz
	Copyright: permitted
	Emphasis: none
	Category: general
	Original: 1st generation
	Clock: 1000 ppm

显示当前第1块声卡的IEC958状态位.
$ iecset -Dhw:0
	Mode: consumer
	Data: non-audio
	Rate: 44100 Hz
	Copyright: permitted
	Emphasis: none
	Category: general
	Original: 1st generation
	Clock: 1000 ppm

设置当前为用户模式,并打开"非音频"位.
$ iecset pro off audio off
	Mode: consumer
	Data: non-audio
	Rate: 44100 Hz
	Copyright: permitted
	Emphasis: none
	Category: general
	Original: 1st generation
	Clock: 1000 ppm

 

speaker-test

是一个针对 ALSA驱动的声音测试工具.
speaker-test可以分别对左右声道进行单独的测试.

命令格式:

       speaker-test [-options]

选项:
       -c | --channels NUM
		设置通道数目.

       -D | --device NAME
		设置使用的PCM设备名.

       -f | --frequency FREQ
		设置声音频率.

       --help
		输出帮助信息.

       -b | --buffer TIME
		设置缓冲区时长.0为使用最大的缓冲区大小.

       -p | --period TIME
		设置节拍为多少微秒.

       -r | --rate RATE
		设置音频率.

       -t | --test pink|sine|wav
              -t pink	表示测试时使用噪声.
              -t sine	表示测试时使用音频信号声.
              -t wav	表示测试时使用WAV文件.

       -l | --nloops COUNT
		设置测试循环的次数.

       -w | --wavfile
		设置测试时播放的wav文件.

       -W | --wavdir
		设置一个包含wav文件的目录.默认为/usr/share/sounds/alsa.

示例:

在一个音频接口上进行立体声测试
#  speaker-test -Dplug:front -c2

在两个音频接口上进行4声道测试.
#  speaker-test -Dplug:surround40 -c4

在立体声接口上进行5.1声道测试.
# speaker-test -Dplug:surround51 -c6

测试低音扬声器.
# speaker-test -Dplug:surround51 -c6 -s1 -f75
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值