zoukankan      html  css  js  c++  java
  • Linux ALSA声卡驱动之三:PCM设备的创建

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

    1. PCM是什么 模数转换

    模拟信号经过pcm(脉冲编码调制)后为pcm数据;


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

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

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

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

    总之在驱动中所处理的信号只有两个,①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 plain copy
     
    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 plain copy
     
    1. static struct snd_minor *snd_minors[256];  

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

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

     
  • 相关阅读:
    Hard Rock
    Codeforces Round #416 (Div. 2) B. Vladik and Complicated Book
    codeforces 793B. Igor and his way to work
    codeforces 1B Spreadsheets
    HDU 1069 Monkey and Banana
    codeforces 2B The least round way
    【机器学习】 通俗说拟合
    python-八皇后问题
    python-核心知识思维导图
    python-@property 属性
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6287370.html
Copyright © 2011-2022 走看看