pcm设备
概述
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
- 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
- };
.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
- 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;
- };
.pcm: 所属的pcm。
.substream_count: 该stream下substream的个数。
.substream_opened: 该stream下open的substream个数。
.substream: 该stream下的substream.
[cpp] view plain copy
- 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;
- };
.pstr: 所属的stream。
.id: 代表的该stream下第几个substream,也就是序号。
.stream: 该substream的方向流,是palyback or capture。
.name: 该substrem的名字。
.runtime: 运行时的pcm的一些信息。
.next: 用于链接下一个sub stream。
下图是对这几个结构体之间的简单表述。
pcm设备的创建
创建一个pcm设备的实例,使用snd_pcm_new函数。
[cpp] view plain copy
- /**
- * 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);
- }
[cpp] view plain copy
- 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;
- }
2. 根据传递进来的参数设置card, device, internal, id。
3. 分别创建palyback & capture stream。
4. 调用snd_device_new接口创建pcm设备。
调用snd_pcm_new_stream创建一个stream
[cpp] view plain copy
- 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;
- }
2. 在proc下创建pcm相关目录信息。会调用snd_pcm_stream_proc_init函数,根据stream的类型创建pcm0p/pcm0c文件夹,然后会在此文件夹下创建info文件。info文件的类型会通过snd_pcm_stream_proc_info_read函数获得。代表就不贴出来了。:(
[cpp] view plain copy
- 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
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
- 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;
- }
整个流程梳理
PCM设备节点创建
当调用snd_card_register的时候,就会依次调用card列表下每个设备的dev_register回调函数,对pcm设备来说就是在_snd_pcm_new函数中的
[cpp] view plain copy
- static struct snd_device_ops ops = {
- .dev_free = snd_pcm_dev_free,
- .dev_register = snd_pcm_dev_register,
- .dev_disconnect = snd_pcm_dev_disconnect,
- };
[cpp] view plain copy
- 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(®ister_mutex);
- err = snd_pcm_add(pcm);
- if (err) {
- mutex_unlock(®ister_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(®ister_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(®ister_mutex);
- return 0;
- }
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
- 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 */
- };
.card_number: 所属的card。
.device: 当前设备类型下的设备编号。
.f_ops: 具体设备的文件操作集合。
.private_data: open函数的私有数据。
.card_ptr: 所属的card。
此结构体是用来保存当前设备的上下文信息,该card下所有逻辑设备都存在此结构。
[cpp] view plain copy
- 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;
- }
2. 根据传递进来的参数,各种参数。对于pcm设备来说,当前的private_data就是pcm。此处需要重点介绍file_operations结构。此函数最终会在应用程序调用open的时候走到此处
[cpp] view plain copy
- 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结构体数组中。
[cpp] view plain copy
- static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
6. 为什么创建出的设备节点全在/dev/snd下呢? 此问题源自sound_class创建的时候,设置的devnode参数。
[cpp] view plain copy
- 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;
- }
[cpp] view plain copy
- /* the class may provide a specific name */
- if (dev->class && dev->class->devnode)
- *tmp = dev->class->devnode(dev, mode);
应用到驱动的过程
当应用程序在通过open系统调用打开/dev/pcmC0D0c的过程
1. 先会调用到在alsa_sound_init中注册的字符设备"alsa"的file_operations中的open函数中。
[cpp] view plain copy
- static const struct file_operations snd_fops =
- {
- .owner = THIS_MODULE,
- .open = snd_open,
- .llseek = noop_llseek,
- };
2. 此处会根据次设备号在snd_minors中获得注册的pcm的snd_minor结构,然后调用open回调
[cpp] view plain copy
- if (file->f_op->open)
- err = file->f_op->open(inode, file);
4. 当应用程序执行ioctl的时候,就直接调用file文件中的file_operaions中的ioctl即可,因为在此处已经将snd_minor中的file_operation替换到file中。
[cpp] view plain copy
- #define replace_fops(f, fops) \
- do { \
- struct file *__file = (f); \
- fops_put(__file->f_op); \
- BUG_ON(!(__file->f_op = (fops))); \
- } while(0)
[cpp] view plain copy
- if ((err = substream->ops->open(substream)) < 0)
至此,整个pcm设备创建,调用,以及应用到驱动整个流程分析完毕。:)
相关阅读
TCP 四次握手 产生SIGPIPE的原因 SIGPIPE信号产生的原因: 简单来说,就是客户端程序向服务器端程序发送了消息,然后关闭客户端,服
改造Android手机为,便携式linux服务器,跑tomcat
环境设备1) 闲置安卓手机一部,我的是 oppoR7. 要获取root权限。如果总root失败可以刷下机。2) 可以开热点的 windows电脑一部,我的
windows下使用FFmpeg生成PCM音频文件并播放(通过命令的
一、PCM文件的定义 PCM文件:模拟音频信号经模数转换(A/D变换)直接形成的二进制序列,该文件没有附加的文件头和文件结束标志。Windo
一. 概述 epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 poll,是 Linux 下多路复用 IO接口 select/poll 的增强版本