Linux音频驱动-PCM设备

来源:互联网 发布:鞍钢职工居家工资算法 编辑:程序博客网 时间:2024/06/05 18:54

概述

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);#endifpstr->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;elseprev->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;elseprev->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: 0device: 0subdevice: 0stream: CAPTUREid: ALC662 rev1 Analogname: ALC662 rev1 Analogsubname: subdevice #0class: 0subclass: 0subdevices_count: 1subdevices_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_MINORSminor = snd_find_free_minor(type);#elseminor = snd_kernel_minor(type, card, dev);if (minor >= 0 && snd_minors[minor])minor = -EBUSY;#endifif (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设备创建,调用,以及应用到驱动整个流程分析完毕。:)


  

0 0
原创粉丝点击