Linux ALSA 声卡驱动之三:PCM设备的创建

来源:互联网 发布:公知怎么喷中国航母 编辑:程序博客网 时间:2024/05/16 10:31

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!


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中定义。

[c-sharp] view plaincopy
  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指针的全局数组:

[c-sharp] view plaincopy
  1. static struct snd_minor *snd_minors[256];  

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

[c-sharp] view plaincopy
  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():

[c-sharp] view plaincopy
  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()中:

[c-sharp] view plaincopy
  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中:

[c-sharp] view plaincopy
  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中创建设备节点:

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

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


4.3.1 字符设备注册

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

[c-sharp] view plaincopy
  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的定义:

[c-sharp] view plaincopy
  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结构中定义的回调。

[c-sharp] view plaincopy
  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设备

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 天猫客服打字慢怎么办 京东买的kindle坏了怎么办 欧巴怎么办韩语怎么写 聚划算淘宝口令打不开怎么办 道聚城白银礼包下架怎么办 聚星输了很多钱怎么办 弹力运动裤被烟烧了个洞怎么办 生完宝宝胯宽怎么办 黑色纯棉裤子洗的发白怎么办 金盾保险柜密码忘了怎么办 装修好的房子漏水怎么办 刚装修的房子墙面开裂怎么办 刚装修的房子有味道怎么办 代销产品规格填写不完整怎么办 我的信息被泄露怎么办 进入不良网站手机发信息怎么办 发不良信息被停机了怎么办 手机qq登录显示被冻结怎么办 qq账户被冻结了怎么办 qq钱包账户被永久冻结怎么办 怎么办转让费还没给我 收钱吗不能抵用红包怎么办 红包抵扣被关了怎么办 天猫客户给差评怎么办 淘宝闪电退款有纠纷怎么办呀 手机屏幕右下角出现彩色点怎么办 在超市买到假货怎么办 天猫超市多发货怎么办 天猫中不小心取消退款了怎么办 天猫超市写错了怎么办 二维码收付款不到红包怎么办 天猫优惠劵过期了怎么办 天猫购物津贴用不完怎么办 天猫上买的大件东西实物不符怎么办 天猫上面料成分与实物不符怎么办 闲鱼发货与实物不符怎么办 天猫超市买贵了怎么办 天猫超市里购买的东西退货怎么办 淘宝店上传的图片不清楚怎么办 微信图片打印出来不清楚怎么办 微信图片打印不清楚怎么办