aac转pcm linux
2019-05-20 13:12:32 zgp2917 阅读数 7

本文转自:https://blog.csdn.net/longwang155069/article/details/53321464

概述
1.  什么是pcm?
pcm(Pulse-code modulation)脉冲编码调制,是将模拟信号转化为数字信号的一种方法。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。

2. pcm的两个重要属性
    a.  采样率:        单位时间内采样的次数,采样频率越高越高,
    b.  采样位数:    一个采样信号的位数,也是对采样精度的变现。

对于人类而言,能接受声音的频率范围是20Hz-20KHz, 所以采样的频率44.1KHz 以及16bit的采样位数就可以有很好的保真能力(CD格式的采样率和采样位数)。


                                                              图1-1  声音的录音和播放过程

数据结构
在ALSA架构下,pcm也被称为设备,所谓的逻辑设备。在linux系统中使用snd_pcm结构表示一个pcm设备。

    struct snd_pcm {
        struct snd_card *card;
        struct list_head list;
        int device; /* device number */
        unsigned int info_flags;
        unsigned short dev_class;
        unsigned short dev_subclass;
        char id[64];
        char name[80];
        struct snd_pcm_str streams[2];
        struct mutex open_mutex;
        wait_queue_head_t open_wait;
        void *private_data;
        void (*private_free) (struct snd_pcm *pcm);
        struct device *dev; /* actual hw device this belongs to */
        bool internal; /* pcm is for internal use only */
        bool nonatomic; /* whole PCM operations are in non-atomic context */
    #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
        struct snd_pcm_oss oss;
    #endif
    };

.card:         此pcm设备所属的card。
.list:           用于将pcm设备链接起来,最终所有的pcm设备会放入snd_pcm_devices链表中。
.device:      该pcm的索引号。
.id:             该pcm的标识。
.streams:   指向pcm的capture和playback stream,通常0代表playback,1代表capture。

通常一个pcm下会有两个stream, 分别为capture stream和playback stream,在每个stream下又会存在多个substream。
linux系统中使用snd_pcm_str定义stream, 使用snd_pcm_substream定义substream。

    struct snd_pcm_str {
        int stream;                /* stream (direction) */
        struct snd_pcm *pcm;
        /* -- substreams -- */
        unsigned int substream_count;
        unsigned int substream_opened;
        struct snd_pcm_substream *substream;
    };

.stream:  当前stream的方向,capture or playback。
.pcm:      所属的pcm。
.substream_count:  该stream下substream的个数。
.substream_opened:  该stream下open的substream个数。
.substream:  该stream下的substream.

    struct snd_pcm_substream {
        struct snd_pcm *pcm;
        struct snd_pcm_str *pstr;
        void *private_data;        /* copied from pcm->private_data */
        int number;
        char name[32];            /* substream name */
        int stream;            /* stream (direction) */
        struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
        size_t buffer_bytes_max;    /* limit ring buffer size */
        struct snd_dma_buffer dma_buffer;
        size_t dma_max;
        /* -- hardware operations -- */
        const struct snd_pcm_ops *ops;
        /* -- runtime information -- */
        struct snd_pcm_runtime *runtime;
            /* -- timer section -- */
        struct snd_timer *timer;        /* timer */
        unsigned timer_running: 1;    /* time is running */
        /* -- next substream -- */
        struct snd_pcm_substream *next;
        /* -- linked substreams -- */
        struct list_head link_list;    /* linked list member */
        struct snd_pcm_group self_group;    /* fake group for non linked substream (with substream lock inside) */
        struct snd_pcm_group *group;        /* pointer to current group */
        /* -- assigned files -- */
        void *file;
        int ref_count;
        atomic_t mmap_count;
        unsigned int f_flags;
        void (*pcm_release)(struct snd_pcm_substream *);
        struct pid *pid;
        /* misc flags */
        unsigned int hw_opened: 1;
    };

.pcm:       所属的pcm。
.pstr:       所属的stream。
.id:           代表的该stream下第几个substream,也就是序号。
.stream:  该substream的方向流,是palyback or capture。
.name:     该substrem的名字。
.ops:        硬件操作函数集合。
.runtime:   运行时的pcm的一些信息。
.next:        用于链接下一个sub stream。

下图是对这几个结构体之间的简单表述。

 

pcm设备的创建
创建一个pcm设备的实例,使用snd_pcm_new函数。

    /**
     * snd_pcm_new - create a new PCM instance
     * @card: the card instance
     * @id: the id string
     * @device: the device index (zero based)
     * @playback_count: the number of substreams for playback
     * @capture_count: the number of substreams for capture
     * @rpcm: the pointer to store the new pcm instance
     *
     * Creates a new PCM instance.
     *
     * The pcm operators have to be set afterwards to the new instance
     * via snd_pcm_set_ops().
     *
     * Return: Zero if successful, or a negative error code on failure.
     */
    int snd_pcm_new(struct snd_card *card, const char *id, int device,
            int playback_count, int capture_count, struct snd_pcm **rpcm)
    {
        return _snd_pcm_new(card, id, device, playback_count, capture_count,
                false, rpcm);
    }

此函数会传入六个参数,其中该函数的注释写的很清楚,不做过多解释。函数最终会返回rpcm参数。

    static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
            int playback_count, int capture_count, bool internal,
            struct snd_pcm **rpcm)
    {
        struct snd_pcm *pcm;
        int err;
        static struct snd_device_ops ops = {
            .dev_free = snd_pcm_dev_free,
            .dev_register =    snd_pcm_dev_register,
            .dev_disconnect = snd_pcm_dev_disconnect,
        };
     
        if (snd_BUG_ON(!card))
            return -ENXIO;
        if (rpcm)
            *rpcm = NULL;
        pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
        if (pcm == NULL) {
            dev_err(card->dev, "Cannot allocate PCM\n");
            return -ENOMEM;
        }
        pcm->card = card;
        pcm->device = device;
        pcm->internal = internal;
        if (id)
            strlcpy(pcm->id, id, sizeof(pcm->id));
        if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
            snd_pcm_free(pcm);
            return err;
        }
        if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
            snd_pcm_free(pcm);
            return err;
        }
        mutex_init(&pcm->open_mutex);
        init_waitqueue_head(&pcm->open_wait);
        if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
            snd_pcm_free(pcm);
            return err;
        }
        if (rpcm)
            *rpcm = pcm;
        return 0;
    }

1.  分配一个snd_pcm结构体。
2.  根据传递进来的参数设置card, device, internal, id。
3.  分别创建palyback & capture stream。
4.  调用snd_device_new接口创建pcm设备。

调用snd_pcm_new_stream创建一个stream

    int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
    {
        int idx, err;
        struct snd_pcm_str *pstr = &pcm->streams[stream];
        struct snd_pcm_substream *substream, *prev;
     
    #if IS_ENABLED(CONFIG_SND_PCM_OSS)
        mutex_init(&pstr->oss.setup_mutex);
    #endif
        pstr->stream = stream;
        pstr->pcm = pcm;
        pstr->substream_count = substream_count;
        if (substream_count > 0 && !pcm->internal) {
            err = snd_pcm_stream_proc_init(pstr);
            if (err < 0) {
                pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
                return err;
            }
        }
        prev = NULL;
        for (idx = 0, prev = NULL; idx < substream_count; idx++) {
            substream = kzalloc(sizeof(*substream), GFP_KERNEL);
            if (substream == NULL) {
                pcm_err(pcm, "Cannot allocate PCM substream\n");
                return -ENOMEM;
            }
            substream->pcm = pcm;
            substream->pstr = pstr;
            substream->number = idx;
            substream->stream = stream;
            sprintf(substream->name, "subdevice #%i", idx);
            substream->buffer_bytes_max = UINT_MAX;
            if (prev == NULL)
                pstr->substream = substream;
            else
                prev->next = substream;
     
            if (!pcm->internal) {
                err = snd_pcm_substream_proc_init(substream);
                if (err < 0) {
                    pcm_err(pcm,
                        "Error in snd_pcm_stream_proc_init\n");
                    if (prev == NULL)
                        pstr->substream = NULL;
                    else
                        prev->next = NULL;
                    kfree(substream);
                    return err;
                }
            }
            substream->group = &substream->self_group;
            spin_lock_init(&substream->self_group.lock);
            mutex_init(&substream->self_group.mutex);
            INIT_LIST_HEAD(&substream->self_group.substreams);
            list_add_tail(&substream->link_list, &substream->self_group.substreams);
            atomic_set(&substream->mmap_count, 0);
            prev = substream;
        }
        return 0;
    }            

1.   根据传递进来的参数,设置pcm的stream, pcm, substream_count的值。
2.   在proc下创建pcm相关目录信息。会调用snd_pcm_stream_proc_init函数,根据stream的类型创建pcm0p/pcm0c文件夹,然后会在此文件夹下创建info文件。info文件的类型会通过snd_pcm_stream_proc_info_read函数获得。代表就不贴出来了。:(

    root@test:/proc/asound/card0/pcm0c$ cat info
    card: 0
    device: 0
    subdevice: 0
    stream: CAPTURE
    id: ALC662 rev1 Analog
    name: ALC662 rev1 Analog
    subname: subdevice #0
    class: 0
    subclass: 0
    subdevices_count: 1
    subdevices_avail: 1

3.   会根据substrem_count的个数,进行for循环操作。
4.   分配一个substream结构,设置必要的参数,如:  pcm,  pstr,  number,  stream,  name等。
5.   调用snd_pcm_substream_proc_init函数,创建sub0目录,然后在此目录下创建info, hw_params, sw_params,status等文件。
6.   将所有的substream会通过linklist链表保存,同时如果有多个substream会通过next指针相连。

至此,pcm设备就全部创建完成,创建完成后会形成如下的逻辑试图。

大体上就是一棵树,根节点是card0, 然后子节点是pcm设备,pcm设备分为capture & playback stream, 然后在stream下又分为substrem。

PCM硬件操作函数集设置
实例化一个pcm设备之后,还需要通过snd_pcm_set_ops函数设置该硬件的操作集合。

    void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
                 const struct snd_pcm_ops *ops)
    {
        struct snd_pcm_str *stream = &pcm->streams[direction];
        struct snd_pcm_substream *substream;
        
        for (substream = stream->substream; substream != NULL; substream = substream->next)
            substream->ops = ops;
    }

该函数会根据当前stream的方向/类型,设置该硬件对应的snd_pcm_ops操作集合。

整个流程梳理

PCM设备节点创建
当调用snd_card_register的时候,就会依次调用card列表下每个设备的dev_register回调函数,对pcm设备来说就是在_snd_pcm_new函数中的

        static struct snd_device_ops ops = {
            .dev_free = snd_pcm_dev_free,
            .dev_register =    snd_pcm_dev_register,
            .dev_disconnect = snd_pcm_dev_disconnect,
        };

此时会调用到snd_pcm_dev_register回调处理函数。

    static int snd_pcm_dev_register(struct snd_device *device)
    {
        int cidx, err;
        struct snd_pcm_substream *substream;
        struct snd_pcm_notify *notify;
        char str[16];
        struct snd_pcm *pcm;
        struct device *dev;
     
        if (snd_BUG_ON(!device || !device->device_data))
            return -ENXIO;
        pcm = device->device_data;
        mutex_lock(&register_mutex);
        err = snd_pcm_add(pcm);
        if (err) {
            mutex_unlock(&register_mutex);
            return err;
        }
        for (cidx = 0; cidx < 2; cidx++) {
            int devtype = -1;
            if (pcm->streams[cidx].substream == NULL || pcm->internal)
                continue;
            switch (cidx) {
            case SNDRV_PCM_STREAM_PLAYBACK:
                sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
                devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
                break;
            case SNDRV_PCM_STREAM_CAPTURE:
                sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
                devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
                break;
            }
            /* device pointer to use, pcm->dev takes precedence if
             * it is assigned, otherwise fall back to card's device
             * if possible */
            dev = pcm->dev;
            if (!dev)
                dev = snd_card_get_device_link(pcm->card);
            /* register pcm */
            err = snd_register_device_for_dev(devtype, pcm->card,
                              pcm->device,
                              &snd_pcm_f_ops[cidx],
                              pcm, str, dev);
            if (err < 0) {
                list_del(&pcm->list);
                mutex_unlock(&register_mutex);
                return err;
            }
     
            dev = snd_get_device(devtype, pcm->card, pcm->device);
            if (dev) {
                err = sysfs_create_groups(&dev->kobj,
                              pcm_dev_attr_groups);
                if (err < 0)
                    dev_warn(dev,
                         "pcm %d:%d: cannot create sysfs groups\n",
                         pcm->card->number, pcm->device);
                put_device(dev);
            }
     
            for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
                snd_pcm_timer_init(substream);
        }
     
        list_for_each_entry(notify, &snd_pcm_notify_list, list)
            notify->n_register(pcm);
     
        mutex_unlock(&register_mutex);
        return 0;
    }

1.   合法性判断,对pcm设备来说,snd_device->device_data存放的是当前的pcm指针。
2.    会调用snd_pcm_add此函数,判断此pcm设备是存在snd_pcm_devices链表中存在,存在就返回错误,不存在就添加。
3.    设置当前pcm设备name, 以及具体的pcm设备类型,PCM_CAPTURE  or PCM_PLAYBACK。
4.    调用snd_register_device_for_dev添加pcm设备到系统中。
5.    调用snd_get_device此函数返回当前注册的pcm设备,然后设置该pcm的属性。
6.    调用snd_pcm_timer_init函数,进行pcm定时器的初始化。

在继续分析snd_register_device_for_dev函数之前需要先介绍一个结构体。struct snd_minor。

    struct snd_minor {
        int type;            /* SNDRV_DEVICE_TYPE_XXX */
        int card;            /* card number */
        int device;            /* device number */
        const struct file_operations *f_ops;    /* file operations */
        void *private_data;        /* private data for f_ops->open */
        struct device *dev;        /* device for sysfs */
        struct snd_card *card_ptr;    /* assigned card instance */
    };

.type:  设备类型,比如是pcm, control, timer等设备。
.card_number:  所属的card。
.device:  当前设备类型下的设备编号。
.f_ops:  具体设备的文件操作集合。
.private_data:  open函数的私有数据。
.card_ptr:  所属的card。

此结构体是用来保存当前设备的上下文信息,该card下所有逻辑设备都存在此结构。

    int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
                    const struct file_operations *f_ops,
                    void *private_data,
                    const char *name, struct device *device)
    {
        int minor;
        struct snd_minor *preg;
     
        if (snd_BUG_ON(!name))
            return -EINVAL;
        preg = kmalloc(sizeof *preg, GFP_KERNEL);
        if (preg == NULL)
            return -ENOMEM;
        preg->type = type;
        preg->card = card ? card->number : -1;
        preg->device = dev;
        preg->f_ops = f_ops;
        preg->private_data = private_data;
        preg->card_ptr = card;
        mutex_lock(&sound_mutex);
    #ifdef CONFIG_SND_DYNAMIC_MINORS
        minor = snd_find_free_minor(type);
    #else
        minor = snd_kernel_minor(type, card, dev);
        if (minor >= 0 && snd_minors[minor])
            minor = -EBUSY;
    #endif
        if (minor < 0) {
            mutex_unlock(&sound_mutex);
            kfree(preg);
            return minor;
        }
        snd_minors[minor] = preg;
        preg->dev = device_create(sound_class, device, MKDEV(major, minor),
                      private_data, "%s", name);
        if (IS_ERR(preg->dev)) {
            snd_minors[minor] = NULL;
            mutex_unlock(&sound_mutex);
            minor = PTR_ERR(preg->dev);
            kfree(preg);
            return minor;
        }
     
        mutex_unlock(&sound_mutex);
        return 0;
    }

1.   首先上来就分配一个snd_minor结构体。
2.   根据传递进来的参数,各种参数。对于pcm设备来说,当前的private_data就是pcm。此处需要重点介绍file_operations结构。此函数最终会在应用程序调用open的时候走到此处

    const struct file_operations snd_pcm_f_ops[2] = {
        {
            .owner =        THIS_MODULE,
            .write =        snd_pcm_write,
            .aio_write =        snd_pcm_aio_write,
            .open =            snd_pcm_playback_open,
            .release =        snd_pcm_release,
            .llseek =        no_llseek,
            .poll =            snd_pcm_playback_poll,
            .unlocked_ioctl =    snd_pcm_playback_ioctl,
            .compat_ioctl =     snd_pcm_ioctl_compat,
            .mmap =            snd_pcm_mmap,
            .fasync =        snd_pcm_fasync,
            .get_unmapped_area =    snd_pcm_get_unmapped_area,
        },
        {
            .owner =        THIS_MODULE,
            .read =            snd_pcm_read,
            .aio_read =        snd_pcm_aio_read,
            .open =            snd_pcm_capture_open,
            .release =        snd_pcm_release,
            .llseek =        no_llseek,
            .poll =            snd_pcm_capture_poll,
            .unlocked_ioctl =    snd_pcm_capture_ioctl,
            .compat_ioctl =     snd_pcm_ioctl_compat,
            .mmap =            snd_pcm_mmap,
            .fasync =        snd_pcm_fasync,
            .get_unmapped_area =    snd_pcm_get_unmapped_area,
        }
    };

3.   调用snd_kernel_minor函数获得设备的此设备号。该此设备号已经存在则返回BUSY,小于返回错误。
4.   用次设备号为下标,将当前申请的snd_minor放入到全局的snd_minors结构体数组中。

static struct snd_minor *snd_minors[SNDRV_OS_MINORS];

5.   调用device_create函数创建该pcm的设备节点。
6.   为什么创建出的设备节点全在/dev/snd下呢?  此问题源自sound_class创建的时候,设置的devnode参数。

    static char *sound_devnode(struct device *dev, umode_t *mode)
    {
        if (MAJOR(dev->devt) == SOUND_MAJOR)
            return NULL;
        return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));
    }
     
    static int __init init_soundcore(void)
    {
        int rc;
     
        rc = init_oss_soundcore();
        if (rc)
            return rc;
     
        sound_class = class_create(THIS_MODULE, "sound");
        if (IS_ERR(sound_class)) {
            cleanup_oss_soundcore();
            return PTR_ERR(sound_class);
        }
     
        sound_class->devnode = sound_devnode;
     
        return 0;
    }

当调用device_create的时候,最终会调用到device_add->devtmpfs_create_node->device_get_devnode中

        /* the class may provide a specific name */
        if (dev->class && dev->class->devnode)
            *tmp = dev->class->devnode(dev, mode);

最终出现的设备节点会出现在/dev/snd下。

应用到驱动的过程
当应用程序在通过open系统调用打开/dev/pcmC0D0c的过程
1.  先会调用到在alsa_sound_init中注册的字符设备"alsa"的file_operations中的open函数中。

    static const struct file_operations snd_fops =
    {
        .owner =    THIS_MODULE,
        .open =        snd_open,
        .llseek =    noop_llseek,
    };

2.  此处会根据次设备号在snd_minors中获得注册的pcm的snd_minor结构,然后调用open回调

        if (file->f_op->open)
            err = file->f_op->open(inode, file);

3.   此处的open回调就是snd_pcm_f_ops中的open。
4.   当应用程序执行ioctl的时候,就直接调用file文件中的file_operaions中的ioctl即可,因为在此处已经将snd_minor中的file_operation替换到file中。

    #define replace_fops(f, fops) \
        do {    \
            struct file *__file = (f); \
            fops_put(__file->f_op); \
            BUG_ON(!(__file->f_op = (fops))); \
        } while(0)

5.  比如当前调用的是playback中的open,会调用snd_pcm_playback_open函数,此函数会设置pcm的runtime信息,最终会调用硬件相关的open函数中。

    if ((err = substream->ops->open(substream)) < 0)


至此,整个pcm设备创建,调用,以及应用到驱动整个流程分析完毕。:)
 

2017-05-15 10:24:56 eydwyz 阅读数 528
  1. /**alsa play test 
  2. *ALSA用户空间编译,ALSA驱动的声卡在用户空间,不宜直接使用 
  3. *文件接口中,而应使用alsa-lib 
  4. *打开---->设置参数--->读写音频数据 ALSA全部使用alsa-lib中的API 
  5. *交叉编译 
  6. *export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH 
  7. *arm-linux-gcc -o alsa_play alsa_play_test.c -L. -lasound 
  8. *需要交叉编译后的libasound.so库的支持 
  9. * 
  10. */  
  11. #include <stdio.h>  
  12. #include <stdlib.h>  
  13. #include "alsa/asoundlib.h"  
  14.   
  15. int main(int argc, char *argv[])  
  16. {  
  17.     int i;  
  18.     int ret;  
  19.     int buf[128];  
  20.     unsigned int val;  
  21.     int dir=0;  
  22.     char *buffer;  
  23.     int size;  
  24.     snd_pcm_uframes_t frames;  
  25.     snd_pcm_uframes_t periodsize;  
  26.     snd_pcm_t *playback_handle;//PCM设备句柄pcm.h  
  27.     snd_pcm_hw_params_t *hw_params;//硬件信息和PCM流配置  
  28.     if (argc != 2) {  
  29.         printf("error: alsa_play_test [music name]\n");  
  30.         exit(1);  
  31.     }  
  32.     printf("play song %s by wolf\n", argv[1]);  
  33.     FILE *fp = fopen(argv[1], "rb");  
  34.     if(fp == NULL)  
  35.     return 0;  
  36.     fseek(fp, 100, SEEK_SET);  
  37.       
  38.     //1. 打开PCM,最后一个参数为0意味着标准配置  
  39.     ret = snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);  
  40.     if (ret < 0) {  
  41.         perror("snd_pcm_open");  
  42.         exit(1);  
  43.     }  
  44.       
  45.     //2. 分配snd_pcm_hw_params_t结构体  
  46.     ret = snd_pcm_hw_params_malloc(&hw_params);  
  47.     if (ret < 0) {  
  48.         perror("snd_pcm_hw_params_malloc");  
  49.         exit(1);  
  50.     }  
  51.     //3. 初始化hw_params  
  52.     ret = snd_pcm_hw_params_any(playback_handle, hw_params);  
  53.     if (ret < 0) {  
  54.         perror("snd_pcm_hw_params_any");  
  55.         exit(1);  
  56.     }  
  57.     //4. 初始化访问权限  
  58.     ret = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);  
  59.     if (ret < 0) {  
  60.         perror("snd_pcm_hw_params_set_access");  
  61.         exit(1);  
  62.     }  
  63.     //5. 初始化采样格式SND_PCM_FORMAT_U8,8位  
  64.     ret = snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_U8);  
  65.     if (ret < 0) {  
  66.         perror("snd_pcm_hw_params_set_format");  
  67.         exit(1);  
  68.     }  
  69.     //6. 设置采样率,如果硬件不支持我们设置的采样率,将使用最接近的  
  70.     //val = 44100,有些录音采样频率固定为8KHz  
  71.       
  72.   
  73.     val = 8000;  
  74.     ret = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &val, &dir);  
  75.     if (ret < 0) {  
  76.         perror("snd_pcm_hw_params_set_rate_near");  
  77.         exit(1);  
  78.     }  
  79.     //7. 设置通道数量  
  80.     ret = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2);  
  81.     if (ret < 0) {  
  82.         perror("snd_pcm_hw_params_set_channels");  
  83.         exit(1);  
  84.     }  
  85.       
  86.     /* Set period size to 32 frames. */  
  87.     frames = 32;  
  88.     periodsize = frames * 2;  
  89.     ret = snd_pcm_hw_params_set_buffer_size_near(playback_handle, hw_params, &periodsize);  
  90.     if (ret < 0)   
  91.     {  
  92.          printf("Unable to set buffer size %li : %s\n", frames * 2, snd_strerror(ret));  
  93.            
  94.     }  
  95.           periodsize /= 2;  
  96.   
  97.     ret = snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params, &periodsize, 0);  
  98.     if (ret < 0)   
  99.     {  
  100.         printf("Unable to set period size %li : %s\n", periodsize,  snd_strerror(ret));  
  101.     }  
  102.                                     
  103.     //8. 设置hw_params  
  104.     ret = snd_pcm_hw_params(playback_handle, hw_params);  
  105.     if (ret < 0) {  
  106.         perror("snd_pcm_hw_params");  
  107.         exit(1);  
  108.     }  
  109.       
  110.      /* Use a buffer large enough to hold one period */  
  111.     snd_pcm_hw_params_get_period_size(hw_params, &frames, &dir);  
  112.                                   
  113.     size = frames * 2; /* 2 bytes/sample, 2 channels */  
  114.     buffer = (char *) malloc(size);  
  115.     fprintf(stderr,  
  116.             "size = %d\n",  
  117.             size);  
  118.       
  119.     while (1)   
  120.     {  
  121.         ret = fread(buffer, 1, size, fp);  
  122.         if(ret == 0)   
  123.         {  
  124.               fprintf(stderr, "end of file on input\n");  
  125.               break;  
  126.         }   
  127.         else if (ret != size)   
  128.         {  
  129.         }  
  130.         //9. 写音频数据到PCM设备  
  131.         while(ret = snd_pcm_writei(playback_handle, buffer, frames)<0)  
  132.         {  
  133.             usleep(2000);  
  134.             if (ret == -EPIPE)  
  135.             {  
  136.                   /* EPIPE means underrun */  
  137.                   fprintf(stderr, "underrun occurred\n");  
  138.                   //完成硬件参数设置,使设备准备好  
  139.                   snd_pcm_prepare(playback_handle);  
  140.             }   
  141.             else if (ret < 0)   
  142.             {  
  143.                   fprintf(stderr,  
  144.                       "error from writei: %s\n",  
  145.                       snd_strerror(ret));  
  146.             }    
  147.         }  
  148.           
  149.     }         
  150.     //10. 关闭PCM设备句柄  
  151.     snd_pcm_close(playback_handle);  
  152.       
  153.     return 0;  
  154. }  
2018-07-12 10:58:56 tekenuo 阅读数 136

上一篇博文中,将MP3文件通过mad库解析成了PCM文件,如果直接用默认的音乐播放器打开,会发现播放时长变为原先的10来倍,并且音频播放也是10倍缓慢。可安装sox组件,用play来播放:

//播放音频out.pcm  MP3的采样率为44100Hz,双声道

play -t raw -r 44100 -e signed -b 16 -c 2 out.pcm

-r = sampling rate

-b = sampling precision (bits)

-c = number of channels

 

2017-06-22 13:38:19 u012635648 阅读数 170

概述

1.  什么是pcm?
pcm(Pulse-code modulation)脉冲编码调制,是将模拟信号转化为数字信号的一种方法。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。

2. pcm的两个重要属性
    a.  采样率:        单位时间内采样的次数,采样频率越高越高,
    b.  采样位数:    一个采样信号的位数,也是对采样精度的变现。

对于人类而言,能接受声音的频率范围是20Hz-20KHz, 所以采样的频率44.1KHz 以及16bit的采样位数就可以有很好的保真能力(CD格式的采样率和采样位数)。


                                                              图1-1  声音的录音和播放过程

数据结构

在ALSA架构下,pcm也被称为设备,所谓的逻辑设备。在linux系统中使用snd_pcm结构表示一个pcm设备。
[cpp] view plain copy
  1. struct snd_pcm {  
  2.     struct snd_card *card;  
  3.     struct list_head list;  
  4.     int device; /* device number */  
  5.     unsigned int info_flags;  
  6.     unsigned short dev_class;  
  7.     unsigned short dev_subclass;  
  8.     char id[64];  
  9.     char name[80];  
  10.     struct snd_pcm_str streams[2];  
  11.     struct mutex open_mutex;  
  12.     wait_queue_head_t open_wait;  
  13.     void *private_data;  
  14.     void (*private_free) (struct snd_pcm *pcm);  
  15.     struct device *dev; /* actual hw device this belongs to */  
  16.     bool internal; /* pcm is for internal use only */  
  17.     bool nonatomic; /* whole PCM operations are in non-atomic context */  
  18. #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)  
  19.     struct snd_pcm_oss oss;  
  20. #endif  
  21. };  
.card:         此pcm设备所属的card。
.list:           用于将pcm设备链接起来,最终所有的pcm设备会放入snd_pcm_devices链表中。
.device:      该pcm的索引号。
.id:             该pcm的标识。
.streams:   指向pcm的capture和playback stream,通常0代表playback,1代表capture。

通常一个pcm下会有两个stream, 分别为capture stream和playback stream,在每个stream下又会存在多个substream。
linux系统中使用snd_pcm_str定义stream, 使用snd_pcm_substream定义substream。
[cpp] view plain copy
  1. struct snd_pcm_str {  
  2.     int stream;             /* stream (direction) */  
  3.     struct snd_pcm *pcm;  
  4.     /* -- substreams -- */  
  5.     unsigned int substream_count;  
  6.     unsigned int substream_opened;  
  7.     struct snd_pcm_substream *substream;  
  8. };  
.stream:  当前stream的方向,capture or playback。
.pcm:      所属的pcm。
.substream_count:  该stream下substream的个数。
.substream_opened:  该stream下open的substream个数。
.substream:  该stream下的substream.

[cpp] view plain copy
  1. struct snd_pcm_substream {  
  2.     struct snd_pcm *pcm;  
  3.     struct snd_pcm_str *pstr;  
  4.     void *private_data;     /* copied from pcm->private_data */  
  5.     int number;  
  6.     char name[32];          /* substream name */  
  7.     int stream;         /* stream (direction) */  
  8.     struct pm_qos_request latency_pm_qos_req; /* pm_qos request */  
  9.     size_t buffer_bytes_max;    /* limit ring buffer size */  
  10.     struct snd_dma_buffer dma_buffer;  
  11.     size_t dma_max;  
  12.     /* -- hardware operations -- */  
  13.     const struct snd_pcm_ops *ops;  
  14.     /* -- runtime information -- */  
  15.     struct snd_pcm_runtime *runtime;  
  16.         /* -- timer section -- */  
  17.     struct snd_timer *timer;        /* timer */  
  18.     unsigned timer_running: 1;  /* time is running */  
  19.     /* -- next substream -- */  
  20.     struct snd_pcm_substream *next;  
  21.     /* -- linked substreams -- */  
  22.     struct list_head link_list; /* linked list member */  
  23.     struct snd_pcm_group self_group;    /* fake group for non linked substream (with substream lock inside) */  
  24.     struct snd_pcm_group *group;        /* pointer to current group */  
  25.     /* -- assigned files -- */  
  26.     void *file;  
  27.     int ref_count;  
  28.     atomic_t mmap_count;  
  29.     unsigned int f_flags;  
  30.     void (*pcm_release)(struct snd_pcm_substream *);  
  31.     struct pid *pid;  
  32.     /* misc flags */  
  33.     unsigned int hw_opened: 1;  
  34. };  
.pcm:       所属的pcm。
.pstr:       所属的stream。
.id:           代表的该stream下第几个substream,也就是序号。
.stream:  该substream的方向流,是palyback or capture。
.name:     该substrem的名字。
.ops:        硬件操作函数集合。
.runtime:   运行时的pcm的一些信息。
.next:        用于链接下一个sub stream。

下图是对这几个结构体之间的简单表述。



pcm设备的创建

创建一个pcm设备的实例,使用snd_pcm_new函数。
[cpp] view plain copy
  1. /** 
  2.  * snd_pcm_new - create a new PCM instance 
  3.  * @card: the card instance 
  4.  * @id: the id string 
  5.  * @device: the device index (zero based) 
  6.  * @playback_count: the number of substreams for playback 
  7.  * @capture_count: the number of substreams for capture 
  8.  * @rpcm: the pointer to store the new pcm instance 
  9.  * 
  10.  * Creates a new PCM instance. 
  11.  * 
  12.  * The pcm operators have to be set afterwards to the new instance 
  13.  * via snd_pcm_set_ops(). 
  14.  * 
  15.  * Return: Zero if successful, or a negative error code on failure. 
  16.  */  
  17. int snd_pcm_new(struct snd_card *card, const char *id, int device,  
  18.         int playback_count, int capture_count, struct snd_pcm **rpcm)  
  19. {  
  20.     return _snd_pcm_new(card, id, device, playback_count, capture_count,  
  21.             false, rpcm);  
  22. }  
此函数会传入六个参数,其中该函数的注释写的很清楚,不做过多解释。函数最终会返回rpcm参数。
[cpp] view plain copy
  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.         dev_err(card->dev, "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. }  
1.  分配一个snd_pcm结构体。
2.  根据传递进来的参数设置card, device, internal, id。
3.  分别创建palyback & capture stream。
4.  调用snd_device_new接口创建pcm设备。

调用snd_pcm_new_stream创建一个stream
[cpp] view plain copy
  1. int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)  
  2. {  
  3.     int idx, err;  
  4.     struct snd_pcm_str *pstr = &pcm->streams[stream];  
  5.     struct snd_pcm_substream *substream, *prev;  
  6.   
  7. #if IS_ENABLED(CONFIG_SND_PCM_OSS)  
  8.     mutex_init(&pstr->oss.setup_mutex);  
  9. #endif  
  10.     pstr->stream = stream;  
  11.     pstr->pcm = pcm;  
  12.     pstr->substream_count = substream_count;  
  13.     if (substream_count > 0 && !pcm->internal) {  
  14.         err = snd_pcm_stream_proc_init(pstr);  
  15.         if (err < 0) {  
  16.             pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");  
  17.             return err;  
  18.         }  
  19.     }  
  20.     prev = NULL;  
  21.     for (idx = 0, prev = NULL; idx < substream_count; idx++) {  
  22.         substream = kzalloc(sizeof(*substream), GFP_KERNEL);  
  23.         if (substream == NULL) {  
  24.             pcm_err(pcm, "Cannot allocate PCM substream\n");  
  25.             return -ENOMEM;  
  26.         }  
  27.         substream->pcm = pcm;  
  28.         substream->pstr = pstr;  
  29.         substream->number = idx;  
  30.         substream->stream = stream;  
  31.         sprintf(substream->name, "subdevice #%i", idx);  
  32.         substream->buffer_bytes_max = UINT_MAX;  
  33.         if (prev == NULL)  
  34.             pstr->substream = substream;  
  35.         else  
  36.             prev->next = substream;  
  37.   
  38.         if (!pcm->internal) {  
  39.             err = snd_pcm_substream_proc_init(substream);  
  40.             if (err < 0) {  
  41.                 pcm_err(pcm,  
  42.                     "Error in snd_pcm_stream_proc_init\n");  
  43.                 if (prev == NULL)  
  44.                     pstr->substream = NULL;  
  45.                 else  
  46.                     prev->next = NULL;  
  47.                 kfree(substream);  
  48.                 return err;  
  49.             }  
  50.         }  
  51.         substream->group = &substream->self_group;  
  52.         spin_lock_init(&substream->self_group.lock);  
  53.         mutex_init(&substream->self_group.mutex);  
  54.         INIT_LIST_HEAD(&substream->self_group.substreams);  
  55.         list_add_tail(&substream->link_list, &substream->self_group.substreams);  
  56.         atomic_set(&substream->mmap_count, 0);  
  57.         prev = substream;  
  58.     }  
  59.     return 0;  
  60. }             
1.   根据传递进来的参数,设置pcm的stream, pcm, substream_count的值。
2.   在proc下创建pcm相关目录信息。会调用snd_pcm_stream_proc_init函数,根据stream的类型创建pcm0p/pcm0c文件夹,然后会在此文件夹下创建info文件。info文件的类型会通过snd_pcm_stream_proc_info_read函数获得。代表就不贴出来了。:(
[cpp] view plain copy
  1. root@test:/proc/asound/card0/pcm0c$ cat info   
  2. card: 0  
  3. device: 0  
  4. subdevice: 0  
  5. stream: CAPTURE  
  6. id: ALC662 rev1 Analog  
  7. name: ALC662 rev1 Analog  
  8. subname: subdevice #0  
  9. class: 0  
  10. subclass: 0  
  11. subdevices_count: 1  
  12. subdevices_avail: 1  
3.   会根据substrem_count的个数,进行for循环操作。
4.   分配一个substream结构,设置必要的参数,如:  pcm,  pstr,  number,  stream,  name等。
5.   调用snd_pcm_substream_proc_init函数,创建sub0目录,然后在此目录下创建info, hw_params, sw_params,status等文件。
6.   将所有的substream会通过linklist链表保存,同时如果有多个substream会通过next指针相连。

至此,pcm设备就全部创建完成,创建完成后会形成如下的逻辑试图。

大体上就是一棵树,根节点是card0, 然后子节点是pcm设备,pcm设备分为capture & playback stream, 然后在stream下又分为substrem。

PCM硬件操作函数集设置

实例化一个pcm设备之后,还需要通过snd_pcm_set_ops函数设置该硬件的操作集合。
[cpp] view plain copy
  1. void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,  
  2.              const struct snd_pcm_ops *ops)  
  3. {  
  4.     struct snd_pcm_str *stream = &pcm->streams[direction];  
  5.     struct snd_pcm_substream *substream;  
  6.       
  7.     for (substream = stream->substream; substream != NULL; substream = substream->next)  
  8.         substream->ops = ops;  
  9. }  
该函数会根据当前stream的方向/类型,设置该硬件对应的snd_pcm_ops操作集合。

整个流程梳理


PCM设备节点创建

当调用snd_card_register的时候,就会依次调用card列表下每个设备的dev_register回调函数,对pcm设备来说就是在_snd_pcm_new函数中的
[cpp] view plain copy
  1. static struct snd_device_ops ops = {  
  2.     .dev_free = snd_pcm_dev_free,  
  3.     .dev_register = snd_pcm_dev_register,  
  4.     .dev_disconnect = snd_pcm_dev_disconnect,  
  5. };  
此时会调用到snd_pcm_dev_register回调处理函数。
[cpp] view plain copy
  1. static int snd_pcm_dev_register(struct snd_device *device)  
  2. {  
  3.     int cidx, err;  
  4.     struct snd_pcm_substream *substream;  
  5.     struct snd_pcm_notify *notify;  
  6.     char str[16];  
  7.     struct snd_pcm *pcm;  
  8.     struct device *dev;  
  9.   
  10.     if (snd_BUG_ON(!device || !device->device_data))  
  11.         return -ENXIO;  
  12.     pcm = device->device_data;  
  13.     mutex_lock(&register_mutex);  
  14.     err = snd_pcm_add(pcm);  
  15.     if (err) {  
  16.         mutex_unlock(&register_mutex);  
  17.         return err;  
  18.     }  
  19.     for (cidx = 0; cidx < 2; cidx++) {  
  20.         int devtype = -1;  
  21.         if (pcm->streams[cidx].substream == NULL || pcm->internal)  
  22.             continue;  
  23.         switch (cidx) {  
  24.         case SNDRV_PCM_STREAM_PLAYBACK:  
  25.             sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);  
  26.             devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;  
  27.             break;  
  28.         case SNDRV_PCM_STREAM_CAPTURE:  
  29.             sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);  
  30.             devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;  
  31.             break;  
  32.         }  
  33.         /* device pointer to use, pcm->dev takes precedence if 
  34.          * it is assigned, otherwise fall back to card's device 
  35.          * if possible */  
  36.         dev = pcm->dev;  
  37.         if (!dev)  
  38.             dev = snd_card_get_device_link(pcm->card);  
  39.         /* register pcm */  
  40.         err = snd_register_device_for_dev(devtype, pcm->card,  
  41.                           pcm->device,  
  42.                           &snd_pcm_f_ops[cidx],  
  43.                           pcm, str, dev);  
  44.         if (err < 0) {  
  45.             list_del(&pcm->list);  
  46.             mutex_unlock(&register_mutex);  
  47.             return err;  
  48.         }  
  49.   
  50.         dev = snd_get_device(devtype, pcm->card, pcm->device);  
  51.         if (dev) {  
  52.             err = sysfs_create_groups(&dev->kobj,  
  53.                           pcm_dev_attr_groups);  
  54.             if (err < 0)  
  55.                 dev_warn(dev,  
  56.                      "pcm %d:%d: cannot create sysfs groups\n",  
  57.                      pcm->card->number, pcm->device);  
  58.             put_device(dev);  
  59.         }  
  60.   
  61.         for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)  
  62.             snd_pcm_timer_init(substream);  
  63.     }  
  64.   
  65.     list_for_each_entry(notify, &snd_pcm_notify_list, list)  
  66.         notify->n_register(pcm);  
  67.   
  68.     mutex_unlock(&register_mutex);  
  69.     return 0;  
  70. }  
1.   合法性判断,对pcm设备来说,snd_device->device_data存放的是当前的pcm指针。
2.    会调用snd_pcm_add此函数,判断此pcm设备是存在snd_pcm_devices链表中存在,存在就返回错误,不存在就添加。
3.    设置当前pcm设备name, 以及具体的pcm设备类型,PCM_CAPTURE  or PCM_PLAYBACK。
4.    调用snd_register_device_for_dev添加pcm设备到系统中。
5.    调用snd_get_device此函数返回当前注册的pcm设备,然后设置该pcm的属性。
6.    调用snd_pcm_timer_init函数,进行pcm定时器的初始化。

在继续分析snd_register_device_for_dev函数之前需要先介绍一个结构体。struct snd_minor。
[cpp] 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.     struct snd_card *card_ptr;  /* assigned card instance */  
  9. };  
.type:  设备类型,比如是pcm, control, timer等设备。
.card_number:  所属的card。
.device:  当前设备类型下的设备编号。
.f_ops:  具体设备的文件操作集合。
.private_data:  open函数的私有数据。
.card_ptr:  所属的card。

此结构体是用来保存当前设备的上下文信息,该card下所有逻辑设备都存在此结构。

[cpp] 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.     preg->card_ptr = card;  
  20.     mutex_lock(&sound_mutex);  
  21. #ifdef CONFIG_SND_DYNAMIC_MINORS  
  22.     minor = snd_find_free_minor(type);  
  23. #else  
  24.     minor = snd_kernel_minor(type, card, dev);  
  25.     if (minor >= 0 && snd_minors[minor])  
  26.         minor = -EBUSY;  
  27. #endif  
  28.     if (minor < 0) {  
  29.         mutex_unlock(&sound_mutex);  
  30.         kfree(preg);  
  31.         return minor;  
  32.     }  
  33.     snd_minors[minor] = preg;  
  34.     preg->dev = device_create(sound_class, device, MKDEV(major, minor),  
  35.                   private_data, "%s", name);  
  36.     if (IS_ERR(preg->dev)) {  
  37.         snd_minors[minor] = NULL;  
  38.         mutex_unlock(&sound_mutex);  
  39.         minor = PTR_ERR(preg->dev);  
  40.         kfree(preg);  
  41.         return minor;  
  42.     }  
  43.   
  44.     mutex_unlock(&sound_mutex);  
  45.     return 0;  
  46. }  
1.   首先上来就分配一个snd_minor结构体。
2.   根据传递进来的参数,各种参数。对于pcm设备来说,当前的private_data就是pcm。此处需要重点介绍file_operations结构。此函数最终会在应用程序调用open的时候走到此处
[cpp] 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. };  
3.   调用snd_kernel_minor函数获得设备的此设备号。该此设备号已经存在则返回BUSY,小于返回错误。
4.   用次设备号为下标,将当前申请的snd_minor放入到全局的snd_minors结构体数组中。
[cpp] view plain copy
  1. static struct snd_minor *snd_minors[SNDRV_OS_MINORS];  
5.   调用device_create函数创建该pcm的设备节点。
6.   为什么创建出的设备节点全在/dev/snd下呢?  此问题源自sound_class创建的时候,设置的devnode参数。
[cpp] view plain copy
  1. static char *sound_devnode(struct device *dev, umode_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.   
  8. static int __init init_soundcore(void)  
  9. {  
  10.     int rc;  
  11.   
  12.     rc = init_oss_soundcore();  
  13.     if (rc)  
  14.         return rc;  
  15.   
  16.     sound_class = class_create(THIS_MODULE, "sound");  
  17.     if (IS_ERR(sound_class)) {  
  18.         cleanup_oss_soundcore();  
  19.         return PTR_ERR(sound_class);  
  20.     }  
  21.   
  22.     sound_class->devnode = sound_devnode;  
  23.   
  24.     return 0;  
  25. }  
当调用device_create的时候,最终会调用到device_add->devtmpfs_create_node->device_get_devnode中
[cpp] view plain copy
  1. /* the class may provide a specific name */  
  2. if (dev->class && dev->class->devnode)  
  3.     *tmp = dev->class->devnode(dev, mode);  
最终出现的设备节点会出现在/dev/snd下。

应用到驱动的过程

当应用程序在通过open系统调用打开/dev/pcmC0D0c的过程
1.  先会调用到在alsa_sound_init中注册的字符设备"alsa"的file_operations中的open函数中。
[cpp] view plain copy
  1. static const struct file_operations snd_fops =  
  2. {  
  3.     .owner =    THIS_MODULE,  
  4.     .open =     snd_open,  
  5.     .llseek =   noop_llseek,  
  6. };  
2.  此处会根据次设备号在snd_minors中获得注册的pcm的snd_minor结构,然后调用open回调
[cpp] view plain copy
  1. if (file->f_op->open)  
  2.     err = file->f_op->open(inode, file);  
3.   此处的open回调就是snd_pcm_f_ops中的open。
4.   当应用程序执行ioctl的时候,就直接调用file文件中的file_operaions中的ioctl即可,因为在此处已经将snd_minor中的file_operation替换到file中。
[cpp] view plain copy
  1. #define replace_fops(f, fops) \  
  2.     do {    \  
  3.         struct file *__file = (f); \  
  4.         fops_put(__file->f_op); \  
  5.         BUG_ON(!(__file->f_op = (fops))); \  
  6.     } while(0)  
5.  比如当前调用的是playback中的open,会调用snd_pcm_playback_open函数,此函数会设置pcm的runtime信息,最终会调用硬件相关的open函数中。
[cpp] view plain copy
  1. if ((err = substream->ops->open(substream)) < 0)  

至此,整个pcm设备创建,调用,以及应用到驱动整个流程分析完毕。:)
2016-11-25 20:11:16 longwang155069 阅读数 6119

概述

1.  什么是pcm?
pcm(Pulse-code modulation)脉冲编码调制,是将模拟信号转化为数字信号的一种方法。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。

2. pcm的两个重要属性
    a.  采样率:        单位时间内采样的次数,采样频率越高越高,
    b.  采样位数:    一个采样信号的位数,也是对采样精度的变现。

对于人类而言,能接受声音的频率范围是20Hz-20KHz, 所以采样的频率44.1KHz 以及16bit的采样位数就可以有很好的保真能力(CD格式的采样率和采样位数)。


                                                              图1-1  声音的录音和播放过程

数据结构

在ALSA架构下,pcm也被称为设备,所谓的逻辑设备。在linux系统中使用snd_pcm结构表示一个pcm设备。
struct snd_pcm {
	struct snd_card *card;
	struct list_head list;
	int device; /* device number */
	unsigned int info_flags;
	unsigned short dev_class;
	unsigned short dev_subclass;
	char id[64];
	char name[80];
	struct snd_pcm_str streams[2];
	struct mutex open_mutex;
	wait_queue_head_t open_wait;
	void *private_data;
	void (*private_free) (struct snd_pcm *pcm);
	struct device *dev; /* actual hw device this belongs to */
	bool internal; /* pcm is for internal use only */
	bool nonatomic; /* whole PCM operations are in non-atomic context */
#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
	struct snd_pcm_oss oss;
#endif
};
.card:         此pcm设备所属的card。
.list:           用于将pcm设备链接起来,最终所有的pcm设备会放入snd_pcm_devices链表中。
.device:      该pcm的索引号。
.id:             该pcm的标识。
.streams:   指向pcm的capture和playback stream,通常0代表playback,1代表capture。

通常一个pcm下会有两个stream, 分别为capture stream和playback stream,在每个stream下又会存在多个substream。
linux系统中使用snd_pcm_str定义stream, 使用snd_pcm_substream定义substream。
struct snd_pcm_str {
	int stream;				/* stream (direction) */
	struct snd_pcm *pcm;
	/* -- substreams -- */
	unsigned int substream_count;
	unsigned int substream_opened;
	struct snd_pcm_substream *substream;
};
.stream:  当前stream的方向,capture or playback。
.pcm:      所属的pcm。
.substream_count:  该stream下substream的个数。
.substream_opened:  该stream下open的substream个数。
.substream:  该stream下的substream.

struct snd_pcm_substream {
	struct snd_pcm *pcm;
	struct snd_pcm_str *pstr;
	void *private_data;		/* copied from pcm->private_data */
	int number;
	char name[32];			/* substream name */
	int stream;			/* stream (direction) */
	struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
	size_t buffer_bytes_max;	/* limit ring buffer size */
	struct snd_dma_buffer dma_buffer;
	size_t dma_max;
	/* -- hardware operations -- */
	const struct snd_pcm_ops *ops;
	/* -- runtime information -- */
	struct snd_pcm_runtime *runtime;
        /* -- timer section -- */
	struct snd_timer *timer;		/* timer */
	unsigned timer_running: 1;	/* time is running */
	/* -- next substream -- */
	struct snd_pcm_substream *next;
	/* -- linked substreams -- */
	struct list_head link_list;	/* linked list member */
	struct snd_pcm_group self_group;	/* fake group for non linked substream (with substream lock inside) */
	struct snd_pcm_group *group;		/* pointer to current group */
	/* -- assigned files -- */
	void *file;
	int ref_count;
	atomic_t mmap_count;
	unsigned int f_flags;
	void (*pcm_release)(struct snd_pcm_substream *);
	struct pid *pid;
	/* misc flags */
	unsigned int hw_opened: 1;
};
.pcm:       所属的pcm。
.pstr:       所属的stream。
.id:           代表的该stream下第几个substream,也就是序号。
.stream:  该substream的方向流,是palyback or capture。
.name:     该substrem的名字。
.ops:        硬件操作函数集合。
.runtime:   运行时的pcm的一些信息。
.next:        用于链接下一个sub stream。

下图是对这几个结构体之间的简单表述。



pcm设备的创建

创建一个pcm设备的实例,使用snd_pcm_new函数。
/**
 * snd_pcm_new - create a new PCM instance
 * @card: the card instance
 * @id: the id string
 * @device: the device index (zero based)
 * @playback_count: the number of substreams for playback
 * @capture_count: the number of substreams for capture
 * @rpcm: the pointer to store the new pcm instance
 *
 * Creates a new PCM instance.
 *
 * The pcm operators have to be set afterwards to the new instance
 * via snd_pcm_set_ops().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
{
	return _snd_pcm_new(card, id, device, playback_count, capture_count,
			false, rpcm);
}
此函数会传入六个参数,其中该函数的注释写的很清楚,不做过多解释。函数最终会返回rpcm参数。
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, bool internal,
		struct snd_pcm **rpcm)
{
	struct snd_pcm *pcm;
	int err;
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};

	if (snd_BUG_ON(!card))
		return -ENXIO;
	if (rpcm)
		*rpcm = NULL;
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	if (pcm == NULL) {
		dev_err(card->dev, "Cannot allocate PCM\n");
		return -ENOMEM;
	}
	pcm->card = card;
	pcm->device = device;
	pcm->internal = internal;
	if (id)
		strlcpy(pcm->id, id, sizeof(pcm->id));
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	mutex_init(&pcm->open_mutex);
	init_waitqueue_head(&pcm->open_wait);
	if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if (rpcm)
		*rpcm = pcm;
	return 0;
}
1.  分配一个snd_pcm结构体。
2.  根据传递进来的参数设置card, device, internal, id。
3.  分别创建palyback & capture stream。
4.  调用snd_device_new接口创建pcm设备。

调用snd_pcm_new_stream创建一个stream
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
	int idx, err;
	struct snd_pcm_str *pstr = &pcm->streams[stream];
	struct snd_pcm_substream *substream, *prev;

#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	mutex_init(&pstr->oss.setup_mutex);
#endif
	pstr->stream = stream;
	pstr->pcm = pcm;
	pstr->substream_count = substream_count;
	if (substream_count > 0 && !pcm->internal) {
		err = snd_pcm_stream_proc_init(pstr);
		if (err < 0) {
			pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
			return err;
		}
	}
	prev = NULL;
	for (idx = 0, prev = NULL; idx < substream_count; idx++) {
		substream = kzalloc(sizeof(*substream), GFP_KERNEL);
		if (substream == NULL) {
			pcm_err(pcm, "Cannot allocate PCM substream\n");
			return -ENOMEM;
		}
		substream->pcm = pcm;
		substream->pstr = pstr;
		substream->number = idx;
		substream->stream = stream;
		sprintf(substream->name, "subdevice #%i", idx);
		substream->buffer_bytes_max = UINT_MAX;
		if (prev == NULL)
			pstr->substream = substream;
		else
			prev->next = substream;

		if (!pcm->internal) {
			err = snd_pcm_substream_proc_init(substream);
			if (err < 0) {
				pcm_err(pcm,
					"Error in snd_pcm_stream_proc_init\n");
				if (prev == NULL)
					pstr->substream = NULL;
				else
					prev->next = NULL;
				kfree(substream);
				return err;
			}
		}
		substream->group = &substream->self_group;
		spin_lock_init(&substream->self_group.lock);
		mutex_init(&substream->self_group.mutex);
		INIT_LIST_HEAD(&substream->self_group.substreams);
		list_add_tail(&substream->link_list, &substream->self_group.substreams);
		atomic_set(&substream->mmap_count, 0);
		prev = substream;
	}
	return 0;
}			
1.   根据传递进来的参数,设置pcm的stream, pcm, substream_count的值。
2.   在proc下创建pcm相关目录信息。会调用snd_pcm_stream_proc_init函数,根据stream的类型创建pcm0p/pcm0c文件夹,然后会在此文件夹下创建info文件。info文件的类型会通过snd_pcm_stream_proc_info_read函数获得。代表就不贴出来了。:(
root@test:/proc/asound/card0/pcm0c$ cat info 
card: 0
device: 0
subdevice: 0
stream: CAPTURE
id: ALC662 rev1 Analog
name: ALC662 rev1 Analog
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1
3.   会根据substrem_count的个数,进行for循环操作。
4.   分配一个substream结构,设置必要的参数,如:  pcm,  pstr,  number,  stream,  name等。
5.   调用snd_pcm_substream_proc_init函数,创建sub0目录,然后在此目录下创建info, hw_params, sw_params,status等文件。
6.   将所有的substream会通过linklist链表保存,同时如果有多个substream会通过next指针相连。

至此,pcm设备就全部创建完成,创建完成后会形成如下的逻辑试图。

大体上就是一棵树,根节点是card0, 然后子节点是pcm设备,pcm设备分为capture & playback stream, 然后在stream下又分为substrem。

PCM硬件操作函数集设置

实例化一个pcm设备之后,还需要通过snd_pcm_set_ops函数设置该硬件的操作集合。
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
		     const struct snd_pcm_ops *ops)
{
	struct snd_pcm_str *stream = &pcm->streams[direction];
	struct snd_pcm_substream *substream;
	
	for (substream = stream->substream; substream != NULL; substream = substream->next)
		substream->ops = ops;
}
该函数会根据当前stream的方向/类型,设置该硬件对应的snd_pcm_ops操作集合。

整个流程梳理


PCM设备节点创建

当调用snd_card_register的时候,就会依次调用card列表下每个设备的dev_register回调函数,对pcm设备来说就是在_snd_pcm_new函数中的
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
此时会调用到snd_pcm_dev_register回调处理函数。
static int snd_pcm_dev_register(struct snd_device *device)
{
	int cidx, err;
	struct snd_pcm_substream *substream;
	struct snd_pcm_notify *notify;
	char str[16];
	struct snd_pcm *pcm;
	struct device *dev;

	if (snd_BUG_ON(!device || !device->device_data))
		return -ENXIO;
	pcm = device->device_data;
	mutex_lock(&register_mutex);
	err = snd_pcm_add(pcm);
	if (err) {
		mutex_unlock(&register_mutex);
		return err;
	}
	for (cidx = 0; cidx < 2; cidx++) {
		int devtype = -1;
		if (pcm->streams[cidx].substream == NULL || pcm->internal)
			continue;
		switch (cidx) {
		case SNDRV_PCM_STREAM_PLAYBACK:
			sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
			break;
		case SNDRV_PCM_STREAM_CAPTURE:
			sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
			break;
		}
		/* device pointer to use, pcm->dev takes precedence if
		 * it is assigned, otherwise fall back to card's device
		 * if possible */
		dev = pcm->dev;
		if (!dev)
			dev = snd_card_get_device_link(pcm->card);
		/* register pcm */
		err = snd_register_device_for_dev(devtype, pcm->card,
						  pcm->device,
						  &snd_pcm_f_ops[cidx],
						  pcm, str, dev);
		if (err < 0) {
			list_del(&pcm->list);
			mutex_unlock(&register_mutex);
			return err;
		}

		dev = snd_get_device(devtype, pcm->card, pcm->device);
		if (dev) {
			err = sysfs_create_groups(&dev->kobj,
						  pcm_dev_attr_groups);
			if (err < 0)
				dev_warn(dev,
					 "pcm %d:%d: cannot create sysfs groups\n",
					 pcm->card->number, pcm->device);
			put_device(dev);
		}

		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
			snd_pcm_timer_init(substream);
	}

	list_for_each_entry(notify, &snd_pcm_notify_list, list)
		notify->n_register(pcm);

	mutex_unlock(&register_mutex);
	return 0;
}
1.   合法性判断,对pcm设备来说,snd_device->device_data存放的是当前的pcm指针。
2.    会调用snd_pcm_add此函数,判断此pcm设备是存在snd_pcm_devices链表中存在,存在就返回错误,不存在就添加。
3.    设置当前pcm设备name, 以及具体的pcm设备类型,PCM_CAPTURE  or PCM_PLAYBACK。
4.    调用snd_register_device_for_dev添加pcm设备到系统中。
5.    调用snd_get_device此函数返回当前注册的pcm设备,然后设置该pcm的属性。
6.    调用snd_pcm_timer_init函数,进行pcm定时器的初始化。

在继续分析snd_register_device_for_dev函数之前需要先介绍一个结构体。struct snd_minor。
struct snd_minor {
	int type;			/* SNDRV_DEVICE_TYPE_XXX */
	int card;			/* card number */
	int device;			/* device number */
	const struct file_operations *f_ops;	/* file operations */
	void *private_data;		/* private data for f_ops->open */
	struct device *dev;		/* device for sysfs */
	struct snd_card *card_ptr;	/* assigned card instance */
};
.type:  设备类型,比如是pcm, control, timer等设备。
.card_number:  所属的card。
.device:  当前设备类型下的设备编号。
.f_ops:  具体设备的文件操作集合。
.private_data:  open函数的私有数据。
.card_ptr:  所属的card。

此结构体是用来保存当前设备的上下文信息,该card下所有逻辑设备都存在此结构。

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
				const struct file_operations *f_ops,
				void *private_data,
				const char *name, struct device *device)
{
	int minor;
	struct snd_minor *preg;

	if (snd_BUG_ON(!name))
		return -EINVAL;
	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	if (preg == NULL)
		return -ENOMEM;
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;
	preg->private_data = private_data;
	preg->card_ptr = card;
	mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
	minor = snd_find_free_minor(type);
#else
	minor = snd_kernel_minor(type, card, dev);
	if (minor >= 0 && snd_minors[minor])
		minor = -EBUSY;
#endif
	if (minor < 0) {
		mutex_unlock(&sound_mutex);
		kfree(preg);
		return minor;
	}
	snd_minors[minor] = preg;
	preg->dev = device_create(sound_class, device, MKDEV(major, minor),
				  private_data, "%s", name);
	if (IS_ERR(preg->dev)) {
		snd_minors[minor] = NULL;
		mutex_unlock(&sound_mutex);
		minor = PTR_ERR(preg->dev);
		kfree(preg);
		return minor;
	}

	mutex_unlock(&sound_mutex);
	return 0;
}
1.   首先上来就分配一个snd_minor结构体。
2.   根据传递进来的参数,各种参数。对于pcm设备来说,当前的private_data就是pcm。此处需要重点介绍file_operations结构。此函数最终会在应用程序调用open的时候走到此处
const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};
3.   调用snd_kernel_minor函数获得设备的此设备号。该此设备号已经存在则返回BUSY,小于返回错误。
4.   用次设备号为下标,将当前申请的snd_minor放入到全局的snd_minors结构体数组中。
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
5.   调用device_create函数创建该pcm的设备节点。
6.   为什么创建出的设备节点全在/dev/snd下呢?  此问题源自sound_class创建的时候,设置的devnode参数。
static char *sound_devnode(struct device *dev, umode_t *mode)
{
	if (MAJOR(dev->devt) == SOUND_MAJOR)
		return NULL;
	return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));
}

static int __init init_soundcore(void)
{
	int rc;

	rc = init_oss_soundcore();
	if (rc)
		return rc;

	sound_class = class_create(THIS_MODULE, "sound");
	if (IS_ERR(sound_class)) {
		cleanup_oss_soundcore();
		return PTR_ERR(sound_class);
	}

	sound_class->devnode = sound_devnode;

	return 0;
}
当调用device_create的时候,最终会调用到device_add->devtmpfs_create_node->device_get_devnode中
	/* the class may provide a specific name */
	if (dev->class && dev->class->devnode)
		*tmp = dev->class->devnode(dev, mode);
最终出现的设备节点会出现在/dev/snd下。

应用到驱动的过程

当应用程序在通过open系统调用打开/dev/pcmC0D0c的过程
1.  先会调用到在alsa_sound_init中注册的字符设备"alsa"的file_operations中的open函数中。
static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};
2.  此处会根据次设备号在snd_minors中获得注册的pcm的snd_minor结构,然后调用open回调
	if (file->f_op->open)
		err = file->f_op->open(inode, file);
3.   此处的open回调就是snd_pcm_f_ops中的open。
4.   当应用程序执行ioctl的时候,就直接调用file文件中的file_operaions中的ioctl即可,因为在此处已经将snd_minor中的file_operation替换到file中。
#define replace_fops(f, fops) \
	do {	\
		struct file *__file = (f); \
		fops_put(__file->f_op); \
		BUG_ON(!(__file->f_op = (fops))); \
	} while(0)
5.  比如当前调用的是playback中的open,会调用snd_pcm_playback_open函数,此函数会设置pcm的runtime信息,最终会调用硬件相关的open函数中。
	if ((err = substream->ops->open(substream)) < 0)

至此,整个pcm设备创建,调用,以及应用到驱动整个流程分析完毕。:)


  

linux alsa pcm 播放声音

阅读数 11215

没有更多推荐了,返回首页