每个pcm实例对应一个pcm设备文件。一个pcm实例由一个playback stream和一个capture stream组成,而每个 pcm stream由一个或多个pcm子流组成。当一个子流已经存在,并且已经被打开,当再次被打开的时候,会被阻塞。
相关结构体:
snd_pcm: 是挂在snd_card下面的一个snd_device
snd_pcm_str streams[2]: 该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream, 其中用户空间中每个PCM设备对应一个pcm 流,设备号相同的设备对应成对的两个pcm流。
snd_pcm_substream: snd_pcm_str中的substream字段,指向snd_pcm_substream结构,snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是他的 snd_pcm_ops ops字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。
runtime字段则指向snd_pcm_runtime结构, snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数。
struct snd_pcm *pcm;
snd_pcm_new(chip->card, “My Chip”, 0 , 1, 1,&pcm)
snd_pcm_set_ops(pcm,SNDRV_PCM_STREAM_PLAYBACK, &snd_mychip_playback_ops);
snd_pcm_set_ops(pcm,SNDRV_PCM_STREAM_CAPTURE, &snd_mychip_capture_ops);
/* playback 操作函数*/
static struct snd_pcm_ops snd_mychip_playback_ops = {
.open = snd_mychip_playback_open,
.close = snd_mychip_playback_close,
// .................
};
/* capture 操作函数*/
static struct snd_pcm_ops snd_mychip_capture_ops = {
.open = snd_mychip_capture_open,
.close = snd_mychip_capture_close,
// .................
};
/* open 函数中需要传入一个定义好的硬件参数*/
static int snd_mychip_playback_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = snd_mychip_playback_hw;
}
static struct snd_pcm_hardware snd_mychip_playback_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FORMAT_S16_LE,
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 32768,
.period_bytes_min = 4096,
.period_bytes_max = 32768,
.periods_min = 1,
.periods_max = 1024,
};
static struct snd_pcm_hardware snd_mychip_capture_hw = {
...};
我们可以通过snd_pcm_new创建一个PCM实例,其中传入的参数包括
card: 所属的声卡
id: PCM实例的ID(名字)
device: PCM实例的编号
playback_count: PCM播放流中子流的个数
capture_count: PCM录音流中子流的个数
rpcm: 返回的PCM实例
具体的创建的流程
具体的代码分析如下:
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, struct snd_pcm **rpcm)
// 直接调用函数_snd_pcm_new,参数internal传入false
_snd_pcm_new(card, id, device, playback_count, capture_count, false, rpcm);
struct snd_pcm *pcm;
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)
// 1. 逻辑设备的操作函数结构体, 主要用于注册子设备
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
// 2. 为snd_pcm结构体分配空间,根据传入参数赋值
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
strlcpy(pcm->id, id, sizeof(pcm->id));
// 3. 根据传入的playback和capture的个数创建PCM流 snd_pcm_str
snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)
snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)
snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
// stream = SNDRV_PCM_STREAM_PLAYBACK; substream_count = playback_count;
// 3.1 根据传入的参数,为PCM流(snd_pcm_str)赋值:方向,所属的PCM,PCM子流的个数
struct snd_pcm_str *pstr = &pcm->streams[stream];
struct snd_pcm_substream *substream, *prev;
pstr->stream = stream;
pstr->pcm = pcm;
pstr->substream_count = substream_count;
//proc
snd_pcm_stream_proc_init(pstr);
// 3.2 根据传入子流的个数,为PCM流创建子流
for (idx = 0, prev = NULL; idx < substream_count; idx++)
// 为子流分配空间,赋值(pcm,pcm流,ID, 方向.....)
substream = kzalloc(sizeof(*substream), GFP_KERNEL);
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;
//proc
snd_pcm_substream_proc_init(substream);
//结构体初始化
substream->group = &substream->self_group;
spin_lock_init(&substream->self_group.lock);
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;
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
// 4. 创建一个PCM逻辑设备,创建逻辑设备,并添加到逻辑设备链表
snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
当声卡被注册时,会注册所有的逻辑设备。主要的工作是创建PCM设备节点
具体的流程:
static int snd_pcm_dev_register(struct snd_device *device)
//1. 添加pcm结构体到全局链表snd_pcm_devices
struct snd_pcm *pcm = device->device_data;
snd_pcm_add(pcm);
for (cidx = 0; cidx < 2; cidx++) {
//2. 确定PCM设备节点名字
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;
}
// 注册创建PCM设备节点
snd_register_device_for_dev(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm, str, dev);
//3 创建一个snd_minor,并添加到全局结构体 snd_minors
struct snd_minor *preg = kmalloc(sizeof *preg, GFP_KERNEL);
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;
snd_minors[minor] = preg;
//4 注册一个设备节点
minor = snd_find_free_minor(type);
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
打开一个 PCM 子流的时候,PCM 运行时实例就会分配给这个子流。这个指针可以通过 substream->runtime 获得。指针拥有多种信息:hw_params 和 sw_params 的配置的拷贝,缓冲区指针,mmap 记录,自旋锁。大部分的驱动程序操作集的函数来说是只读,我们可以在定义操作函数的时候访问这些成员。我们下面具体分析各个成员:
包含了基本硬件配置的定义,需要在 open 的时候对它们进行定义。
runtime 实例拥有这个描述符的拷贝而不是已经存在的描述符的指针。
Struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = snd_mychip_playback_hw; /*通用定义*/
典型的硬件描述如下:
static struct snd_pcm_hardware snd_mychip_playback_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FORMAT_S16_LE,
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 32768,
.period_bytes_min = 4096,
.period_bytes_max = 32768,
.periods_min = 1,
.periods_max = 1024,
};
info 字段包含 pcm 的类型和能力
formats 字段包含了支持格式的标志位(SNDRV_PCM_FMTBIT_XXX)
rates 字段包含了支持的采样率(SNDRV_PCM_RATE_XXX)
rate_min 和 rate_max 定义了最小和最大的采样率。应该和采样率相对应。
channel_min 和 channel_max 定义了最大和最小的通道,以前可能你已看到。
buffer_bytes_max 定义了以字节为单位的最大的缓冲区大小
可以通过 runtime->status 来获得运行状态
runtime->private_data是在 PCM open 的时候指向一个动态数据
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数