Linux 音频驱动(五) ALSA音频驱动之PCM逻辑设备
目录
- 1. 前言
- 2. PCM逻辑设备
- 2.1. 创建 PCM逻辑设备:
- 2.2. PCM逻辑设备文件操作函数集:snd_pcm_f_ops[]
- 2.3. Open PCM逻辑设备
- 2.4. Write/Read PCM逻辑设备
- 2.5. Close PCM逻辑设备
- 3. 结束语
1. 前言
前面几章分析了ASoC音频驱动中Codec、Platform、Machine驱动的组成部分及其注册过程,这三者都是物理设备相关的,大家应该对音频物理链路有了一定的认知。接着分析音频驱动的中间层,由于这些并不是真正的物理设备,故我们称之为逻辑设备。
PCM逻辑设备,我们又习惯称之为PCM中间层或pcm native,起着承上启下的作用:
1.往上是与用户态接口的交互,实现音频数据在用户态和内核态之间的拷贝,即user space <-> kernel space;
2.往下是触发codec、platform、machine的操作函数,实现音频数据在dma_buffer<-> cpu_dai <-> codec之间的传输。
2. PCM逻辑设备
Kernel 版本:3.10
内核源码文件:
./kernel-3.10/sound/core/device.c
./kernel-3.10/sound/core/init.c
./kernel-3.10/sound/core/pcm.c
./kernel-3.10/sound/core/pcm_lib.c
./kernel-3.10/sound/core/pcm_native.c
./kernel-3.10/sound/soc/soc-core.c
./kernel-3.10/sound/soc/soc-pcm.c
./kernel-3.10/sound/core/sound.c
Tinyalsa源码文件:
./external/tinyalsa/pcm.c
2.1. 创建 PCM逻辑设备:
在Linux 音频驱动(四) ASoC音频驱动之Machine驱动中我们给出了注册声卡的完整时序图。现在简化该时序图,重点看一下其中创建PCM逻辑设备的过程。
(为了和原时序图保持一致,没有更改本时序图里的时序编号)
snd_register_device_for_dev()创建pcmCxDxp、pcmCxDxc设备节点的过程如下:
- 首先,分配并初始化一个snd_minor结构中的各字段;
type:SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE
card:card的编号
device:pcm实例的编号,大多数情况为0
f_ops:pcmCxDxp、pcmCxDxc设备节点的文件操作函数集,为snd_pcm_f_ops[*]
private_data:指向该snd_pcm的实例对象 - 根据type,card和pcm的编号,确定数组的索引值minor,minor也作为pcm设备的次设备号;
- 把该snd_minor结构的地址放入全局数组snd_minors[minor]中;
- 最后,调用device_create创建设备节点。
// ./kernel-3.10/sound/core/sound.c, line 57
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
// ./kernel-3.10/sound/core/sound.c, line 269
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;......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);......minor = snd_find_free_minor(type);......snd_minors[minor] = preg;preg->dev = device_create(sound_class, device, MKDEV(major, minor),private_data, "%s", name);......
}
注1:C0D0代表的是Card 0 Device 0,即声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。
注2:snd_minors[]非常重要,用于保存声卡下某个逻辑设备的上下文信息,它在逻辑设备建立阶段被填充,在逻辑设备被使用时就可以从该结构体中得到相应的信息。
注3:sound_class 是sysfs文件系统中的声卡类,在init_soundcore()中被创建。
我们看一下实例:
可以看到这些设备节点的Major=116,Minor则与系统逻辑设备信息文件/proc/asound/devices所列的对应起来,都是字符设备。上层可以通过open/close/read/write/ioctl等系统调用来操作声卡设备,和其他字符设备类似。但是,一般情况下我们使用已封装好的用户接口库如tinyalsa、alsa-lib。
下文将基于tinyslsa详细介绍。Tinyalsa提供了一套完整的pcm逻辑设备用户接口,比如pcm_open()、pcm_start()、pcm_prepare()、pcm_read()、pcm_write()。
2.2. PCM逻辑设备文件操作函数集:snd_pcm_f_ops[]
PCM逻辑设备文件操作函数集对于Playback和Capture是分开定义的,该操作函数集如下:
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,....},{.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,......}
};
2.3. Open PCM逻辑设备
User Space:应用程序通过
pcm_open()
直接呼叫系统调用open()
打开pcmCxDxp或pcmCxDxc
// ./external/tinyalsa/pcm.c, line 851
struct pcm *pcm_open(unsigned int card, unsigned int device, unsigned int flags, struct pcm_config *config)
{......snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');pcm->flags = flags;pcm->fd = open(fn, O_RDWR);......
}
Kernel Space:应用层呼叫的系统调用open()到内核空间就是调用PCM逻辑设备的snd_pcm_f_ops.open文件操作函数
// ./kernel-3.10/sound/core/pcm_native.c, line 2131
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{|--> //./kernel-3.10/sound/core/pcm_native.c, line 2159| snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);|--> //./kernel-3.10/sound/core/pcm_native.c, line 2104| snd_pcm_open_file(file, pcm, stream);|--> //./kernel-3.10/sound/core/pcm_native.c, line 2062| snd_pcm_open_substream(pcm, stream, file, &substream);|--> //./kernel-3.10/sound/core/soc-pcm.c, line 132| substream->ops->open(substream); //即soc_pcm_open(), 在soc_new_pcm()函数中配置的|--> cpu_dai->driver->ops->startup()|--> platform->driver->ops->open()|--> codec_dai->driver->ops->startup()|--> rtd->dai_link->ops->startup()
}
2.4. Write/Read PCM逻辑设备
在我的源码包里,tinyalsa write PCM逻辑设备是通过 ioctl() 函数完成的,即应用程序将需要播放的音频数据通过pcm_write() --> ioctl()
传递到内核。
User Space:应用程序write PCM逻辑设备是通过
pcm_write() --> ioctl()
来完成的
// ./external/tinyalsa/pcm.c, line 483
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{......ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)......
}
Kernel Space:应用层呼叫的系统调用ioctl()到内核空间就是调用PCM逻辑设备的snd_pcm_f_ops.ioctl文件操作函数
// ./kernel-3.10/sound/core/pcm_native.c, line 2784
static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{|--> //./kernel-3.10/sound/core/pcm_native.c, line 2624| snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd, (void __user *)arg);|--> //./kernel-3.10/sound/core/pcm_lib.c, line 2101| snd_pcm_lib_write(substream, xferi.buf, xferi.frames);|--> //./kernel-3.10/sound/core/pcm_lib.c, line 1985| snd_pcm_lib_write1(pcm, stream, file, &substream);|--> //./kernel-3.10/sound/core/pcm_lib.c, line 1962| transfer(substream); //即snd_pcm_lib_write_transfer(). snd_pcm_lib_write1()函数的最后一个入参|--> hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);| //内存和DMA Buffer之间的数据传递, 循环搬送直到播放完毕|--> copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)) |--> //./kernel-3.10/sound/core/pcm_native.c, line 904 | snd_pcm_start() //启动DMA传输(只是在开始时,调用一次)|--> //./kernel-3.10/sound/core/pcm_native.c, line 785 | snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);|--> //./kernel-3.10/sound/core/pcm_native.c, line 716 or line 765| snd_pcm_action_group()/snd_pcm_action_single()|--> //ops为snd_pcm_action()函数第一个入参| res = ops->do_action(substream, state); //snd_pcm_action_start.do_action(), snd_pcm_do_start()|--> //./kernel-3.10/sound/core/soc-pcm.c, line 609| substream->ops->trigger(); //即soc_pcm_trigger(), 在soc_new_pcm()函数中配置的|--> codec_dai->driver->ops->trigger()|--> platform->driver->ops->trigger()|--> cpu_dai->driver->ops->trigger()
}
snd_pcm_action()的第一个入参很重要,write PCM逻辑设备时,snd_pcm_action()函数的第一个入参为struct action_ops snd_pcm_action_start。
细心读者可能已经注意到,PCM逻辑设备提供了snd_pcm_f_ops[0].write()文件操作函数,那为什么应用程序不是通过系统调用 write()来写音频数据给内核呢 ?
这个问题,我自己的思考:
比较一下PCM逻辑设备的write()和ioctl(),可以发现snd_pcm_write()函数是snd_pcm_playback_ioctl()函数的简化版。
对于单声道音频数据,应用程序使用系统调用write()就可以满足需求。
对于多声道音频数据,应用程序要使用系统调用ioctl()。在多声道时,snd_pcm_playback_ioctl()会调用snd_pcm_lib_writev()。
关于read PCM逻辑设备,应用程序也是通过系统调用 ioctl() 完成的,到内核空间后调用snd_pcm_capture_ioctl()。code flow 和 write非常相似,这里不再赘述。
2.5. Close PCM逻辑设备
User Space:应用程序通过
pcm_close() --> pcm_stop() --> ioctl()
关闭pcmCxDxp或pcmCxDxc
// ./external/tinyalsa/pcm.c, line 823
int pcm_close(struct pcm *pcm)
{......pcm_stop(pcm);|--> ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP)......
}
Kernel Space:应用层呼叫的系统调用ioctl()到内核空间就是调用PCM逻辑设备的snd_pcm_f_ops.ioctl文件操作函数
// ./kernel-3.10/sound/core/pcm_native.c, line 2784
static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{|--> //./kernel-3.10/sound/core/pcm_native.c, line 2624| snd_pcm_playback_ioctl1(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);|--> //./kernel-3.10/sound/core/pcm_native.c, line 2556| snd_pcm_common_ioctl1(file, substream, cmd, arg);|--> //./kernel-3.10/sound/core/pcm_native.c, line 1565| snd_pcm_drop(substream);|--> //./kernel-3.10/sound/core/pcm_native.c, line 959| snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);|--> //./kernel-3.10/sound/core/pcm_native.c, line 785| snd_pcm_action(&snd_pcm_action_stop, substream, state);|--> //./kernel-3.10/sound/core/pcm_native.c, line 716 or line 765| snd_pcm_action_group()/snd_pcm_action_single()|--> //ops为snd_pcm_action()函数第一个入参| res = ops->do_action(substream, state); //snd_pcm_action_stop.do_action(), snd_pcm_do_stop()|--> //./kernel-3.10/sound/core/soc-pcm.c, line 609| substream->ops->trigger(); //即soc_pcm_trigger(), 在soc_new_pcm()函数中配置的|--> codec_dai->driver->ops->trigger()|--> platform->driver->ops->trigger()|--> cpu_dai->driver->ops->trigger()
}
snd_pcm_action()的第一个入参很重要,close PCM逻辑设备时,snd_pcm_action()函数的第一个入参为struct action_ops snd_pcm_action_stop。
注:请注意区分write/close PCM逻辑设备时snd_pcm_action()的第一个入参。
3. 结束语
本文重点介绍了对PCM逻辑设备open、write、close时的code flow。为了直观展示code flow,忽略了音频数据传递的细节。
下一篇文章Linux 音频驱动(六) ALSA音频驱动之PCM Write数据传递过程将重点介绍音频数据PCM data的传递细节。
Linux 音频驱动(五) ALSA音频驱动之PCM逻辑设备相关推荐
- Linux(RedHat9.0)下Alsa声卡驱动的安装
自从装来RedHat9.0就没有听到过声音,也不知道安装了多少次声卡驱动,每种都试验过,都没有成功过,今天终于安装成功,需要的朋友可以参考一下. 首先查看一下,系统声音是否打开,有些朋友电脑没有声音, ...
- linux音频框架分析,Alsa音频子系统Codec---al5623.c内核代码框架分析
驱动代码位于: sound/soc/codec/alc5623.c 随便找个Linux内核都会有. 1.首先进行i2c总线驱动加载在: static int __init alc5623_modini ...
- Linux ALSA音频子系统二
From 本文以高通平台为例,介绍一下android下的音频结构.android使用的是tinyALSA作为音频系统,使用方法和基本框架与linux中常用的ALSA音频子系统是一致的. ALSA音频框 ...
- Linux ALSA音频驱动之一:框架概述
1.ALSA概述 ALSA表示高级Linux声音体系结构(Advanced Linux Sound Architecture).它由一系列内核驱动,应用程序编译接口(API)以及支持Linux下声音的 ...
- Linux ALSA音频驱动一:音频系统概述
音频系统概述 音频系统通过数据+控制总线与CODEC连接,控制通路用I2C,数据通常为I2S,框图如图1所示. I2C:寄存器读写,用于配置CODEC控制通路. I2S:音频数据传输,通常与platf ...
- Linux ALSA音频框架分析五:HDA Driver分析
Linux ALSA音频框架分析五:HDA Driver分析 一 概述 HDA(High Definition Audio)是intel设计的用来取代AC97的音频标准,硬件架构上由hda dodec ...
- Linux 音频驱动(一) ASoC音频框架简介
目录 1. ALSA简介 2. ASoC音频驱动构成 3. PCM数据流 4. 数据结构简介 5. ASoC音频驱动注册流程 1. ALSA简介 Native ALSA Application:tin ...
- Linux 音频驱动(四) ASoC音频驱动之Machine驱动
目录 1. 基本介绍 2. 源码分析 2.1. Machine数据结构 struct snd_soc_dai_link 3. 声卡 3.1. 数据结构struct snd_soc_card 3.2. ...
- Linux 音频驱动(三) ASoC音频驱动之Codec驱动
目录 1. 简介 2. 源码分析 2.1. Codec 2.1.1. 数据结构struct snd_soc_codec_driver 2.1.2. 注册Codec 2.2. Codec DAI 2.2 ...
最新文章
- flask 学习实战项目实例
- mysql 判断表或字段存不存在
- malloc 初始化_关于内存分配malloc、calloc、realloc的区别
- HDU 1301 Jungle Roads(裸最小生成树)
- java request获取文件_request获取路径方式
- HDU 1222 Wolf and Rabbit
- 小米手机 - Charles无法安装证书 因为无法读取证书
- 顶点 java笔试_网易2014校园招聘杭州Java笔试题--题解--第一天
- ❤️缓存集合(一级缓存、二级缓存、缓存原理以及自定义缓存—源码+图文分析,建议收藏) ❤️
- Automatic Judge
- vue-watch : 深度监控的语法格式--检测数据的tabledata这个数组的变化
- Kafka日志清除策略
- Python + Selenium 自动发布文章(一):开源中国
- 基于Android系统手机通讯录管理软件的设计与开发
- 拼多多怎么查看订单详情|盛天海科技
- 浏览器点击复制内容并打开微信
- matlab中tdma源程序,40MF-TDMA系统中多用户多业务的无线接入控制和时隙分配算法MATLAB源代码...
- 十位数连加 c语言,用C语言编写一个简易计算器可实现加减乘除,连加连减,连乖连除....
- 一、CSS定位布局[相对定位、绝对定位、固定定位]
- 2021年汽车传感器行业研究报告
热门文章
- 手动搭建Hadoop环境
- 通过guest账户无法进入远程计算机,小经验|guest远程关机设置
- 前端框架比比看:bootstrap, bulma, foundation, material-design-lite, materialize-css, semantic-ui
- 10.3国庆作业(UART实验)
- java工程师的工作述职报告,java程序员述职报告
- VMware的 Mount虚拟光驱问题
- 怎么用matlab对图片缩小,使用Matlab实现对图片的缩放
- 如何让网站HTTPS评级为A或者A+
- openvas linux/window漏洞扫描和整改
- python画名字廖华兴_python第二周基本图形绘制