ALSA学习笔记 (3)PCM_snd_pcm_recover snd_pcm_resume snd_pcm_prepare-程序员宅基地

技术标签: ALSA驱动  alsa  pcm  

1. 概述

每个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的一些重要的软件和硬件运行环境和参数。

2. 示例代码

2.1 创建pcm实例

struct snd_pcm *pcm;
snd_pcm_new(chip->card, “My Chip”, 0 , 1, 1,&pcm)

2.2 设置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);

2.3 定义PCM的操作函数

/* 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;
}

2.4 定义硬件参数

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 = {
    ...};

3. 创建pcm实例代码分析

我们可以通过snd_pcm_new创建一个PCM实例,其中传入的参数包括
card: 所属的声卡
id: PCM实例的ID(名字)
device: PCM实例的编号
playback_count: PCM播放流中子流的个数
capture_count: PCM录音流中子流的个数
rpcm: 返回的PCM实例

具体的创建的流程

  1. 逻辑设备的操作回调函数结构体
  2. 为snd_pcm结构体分配空间,根据传入参数赋值
  3. 根据传入的playback和capture的个数创建PCM流 snd_pcm_str
    3.1 根据传入的参数,为PCM流(snd_pcm_str)赋值:方向,所属的PCM,PCM子流的个数
    3.2 根据传入子流的个数,为PCM流创建子流
    为子流分配空间,赋值(包括pcm,pcm流,ID, 方向…),添加子流到子流的链表,内部结构体初始化
  4. 创建一个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)

5 PCM逻辑设备的注册

当声卡被注册时,会注册所有的逻辑设备。主要的工作是创建PCM设备节点
具体的流程:

  1. 添加pcm结构体到全局链表snd_pcm_devices
  2. 确定PCM设备节点名字
  3. 创建一个snd_minor,并添加到全局结构体 snd_minors
  4. 注册一个设备节点
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);

6 PCM信息运行时指针

打开一个 PCM 子流的时候,PCM 运行时实例就会分配给这个子流。这个指针可以通过 substream->runtime 获得。指针拥有多种信息:hw_params 和 sw_params 的配置的拷贝,缓冲区指针,mmap 记录,自旋锁。大部分的驱动程序操作集的函数来说是只读,我们可以在定义操作函数的时候访问这些成员。我们下面具体分析各个成员:

6.1 硬件描述

包含了基本硬件配置的定义,需要在 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 定义了以字节为单位的最大的缓冲区大小

6.2 运行状态

可以通过 runtime->status 来获得运行状态

6.3 私有数据

runtime->private_data是在 PCM open 的时候指向一个动态数据

7 结构图

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xuechwtx/article/details/106645678

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读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

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签