ALSA使用总结

基于linux4.x内核的ALSA框架概述(个人工作总结一)

环境:
linux4.X内核
构建:
基于yocto构建镜像
平台:
芯驰X9系列
硬件框架声音示意图:
硬件框架播放player
如图可知,声音传输由SOC通过总线I2S传输音频PCM数据到ADC芯片CS4344,然后将处理后的数据通过功放电路(此处省略)最后传送给到喇叭。

设备树的匹配

在当前使用的内核版本中,多数采用了设备树来描述硬件,即平台、设备、驱动分离模式,在dts中描述一个声卡注册的硬件设备也可以很简单。

DTS:

`sound_cs4344: sound@cs4344 {
		compatible = "semidrive,x9-ref-cs4344";
		status = "okay";
	};`

在驱动的匹配过程中会根据dts中的compatible属性来进行匹配,status标志着该节点是否可用,如果是okay则证明被使用,如果是disabled则在匹配过程中忽略该节点

驱动文件(XXX.C)

static const struct of_device_id x9_ref_cs4344_dt_match[] = {
    {
	.compatible = "semidrive,x9-ref-cs4344",
    },
};
MODULE_DEVICE_TABLE(of, x9_ref_cs4344_dt_match);

static struct platform_driver x9_ref_cs4344_mach_driver = {
    .driver =
	{
	    .name = SND_X9_MACH_DRIVER,
	    .of_match_table = x9_ref_cs4344_dt_match,
	    .probe_type = PROBE_PREFER_ASYNCHRONOUS,
	},
    .probe = x9_ref_cs4344_probe,
    .remove = x9_ref_cs4344_remove,
};

module_platform_driver(x9_ref_cs4344_mach_driver);``

初始化一个struct platform_driver变量,其中 x9_ref_cs4344_dt_match为dts中的匹配表,其成员属性compatible与dts中的相等,即证明匹配成功调用x9_ref_cs4344_probe函数。

声卡设备注册

static struct snd_soc_dai_link **snd_x9_ref_soc_dai_links[]** = {
    {
		.name = "x9_hifi_hs",
		.stream_name = "x9 hifi hs",
		.cpu_name = "30600000.i2s",				//SOC端的i2s物理地址
		.cpu_dai_name = "snd-afe-sc-i2s-dai0",	
		.platform_name = "30600000.i2s",        //指定SOC驱动,通常DMA驱动
		.codec_name = "snd-soc-dummy",			//DAC端的匹配驱动名
        .codec_dai_name = "snd-soc-dummy-dai0",	
		.init = x9_ref_cs4344_init,
		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS,
		.ops = &x9_ref_cs4344_ops,				//音频相关的操作函数集
    },
};
#define SND_X9_MACH_DRIVER "x9-ref-ms4344"
#define SND_CARD_NAME SND_X9_MACH_DRIVER
static struct snd_soc_card **x9_ref_cs4344_card** = {   //代表ASoc设备
    .name = SND_X9_MACH_DRIVER,
    .dai_link = snd_x9_ref_soc_dai_links,
    .num_links = ARRAY_SIZE(snd_x9_ref_soc_dai_links),
};

static int x9_ref_cs4344_probe(struct platform_device *pdev)
{
    struct snd_soc_card *card = &x9_ref_cs4344_card;   //初始化一个实例化声卡
    struct device *dev = &pdev->dev;
    int ret;
    struct snd_x9_chip_hs *chip;
    card->dev = dev;
    /* Allocate chip data */
    chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
    chip->card = card; 
    platform_set_drvdata(pdev, chip); //将chip保存至私有平台数据中
    ret = devm_snd_soc_register_card(dev, card);//注册声卡设备
    return ret;
}

首先初始化一个实例化声卡结构体,成员中的dai_link保存了snd_soc_dai_link结构体地址,然后将声卡card嵌入到更大一层结构chip中,调用devm_snd_soc_register_card注册声卡设备。
devm_snd_soc_register_card函数会调用真正的注册函数snd_soc_register_card

int snd_soc_register_card(struct snd_soc_card *card)
{
	int i, ret;
	struct snd_soc_pcm_runtime *rtd;

	for (i = 0; i < card->num_links; i++) {
		struct snd_soc_dai_link *link = &card->dai_link[i];//遍历上述声卡中的dai_link数,本文环境为1
		
		//做link检查,分配内存给card->dai_link->codecs初始化name和dai_name
		//对link中platform_name、cpu_name、cpu_dai_name、codecs->dai_name等命名做检查
		soc_init_dai_link(card, link);
	}
	/*做基础的初始化、设置链表头、已经连接的links数量等等*/
	dev_set_drvdata(card->dev, card);
	snd_soc_initialize_card_lists(card);
	/*........省略部分初始化.....*/
	
	snd_soc_instantiate_card(card);//主调用函数完成声卡注册
	
	/* deactivate pins to sleep state */
	list_for_each_entry(rtd, &card->rtd_list, list)  {
		struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
		int j;

		for (j = 0; j < rtd->num_codecs; j++) {
			struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
			if (!codec_dai->active)
				pinctrl_pm_select_sleep_state(codec_dai->dev);
		}
		if (!cpu_dai->active)
			pinctrl_pm_select_sleep_state(cpu_dai->dev);
	}
	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);

snd_soc_card代表着Machine驱动,snd_soc_platform则代表着Platform驱动,snd_soc_codec则代表了Codec驱动,而snd_soc_dai_link则负责连接Platform和Codec。在简要分析snd_soc_instantiate_card函数之前,先了解一下ASoC模型。

ASoC框架简述

ASOC是建立在标准的ALSA驱动上,不能单独存在,ASOC将soc_snd_card逻辑上分为codec/platform/machine三个组件,ASoC对于Alsa来说,就是分别注册PCM/CONTROL类型的snd_device设备,并实现相应的操 作方法集。

硬件示意图如下:
ASOC硬件框架
Machine:
抽象为一个主体设备,为SOC、CODEC、输入输出设备提供一个载体,Machine驱动的开发主要工作是向内核注册一个snd_soc_card声卡实体。即上述所创建的结构体card。

Platform :
一般是指某一个SoC平台,比如芯驰X9系列等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,它只与SoC相关,Machine无关。单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。Platform驱动向ASoC注册snd_soc_platform和snd_soc_dai设备。

Codec :
Codec里面包含了I2S接口、D/A等功能,通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。目前主流的codec都是可自动解码,不需要I2C去对寄存器进行配置,所以在芯驰平台中,该设备为一个虚拟设备。

snd_soc_instantiate_card函析

由于该函数中构造复杂,节选部分内容分析,不一一对函数展开。
soc_new_pcm_runtime
分配内存给struct snd_soc_pcm_runtime *rtd,初始化rtd->card = card; rtd->dai_link = dai_link;,//后续数据流都使用rtd中保存的变量

snd_soc_find_dai
该函数将搜索所有已注册的组件及其DAI,以找到同名的DAI。包括soc端与codec端,主要通过name匹配,成功找到对端则返回snd_soc_dai结构体指针给rtd(相当于复制在rtd的对应成员中),snd_soc_dai是snd_soc_platform和snd_soc_codec的数字音频接口。

soc_add_pcm_runtime
将初始化完成的rtd加入card->list链表中(list_add_tail(&rtd->list, &card->rtd_list);),card->num_rtd++;

snd_soc_add_dai_link
list_add_tail(&dai_link->list, &card->dai_link_list); card->num_dai_links++; 将card中的dai_link动态加入card,不影响静态的snd_soc_dai_link数组初始化

snd_card_new
创建一个实例化的具体声卡struct snd_card *card;,对各个成员链表头、子设备初始化,创建根文件下各个系统目录,其中
device_initialize(&card->card_dev);
card->card_dev.parent = parent; //指向snd_soc_card的dev
card->card_dev.class = sound_class; // 如下所示
card->card_dev.release = release_card_device;
card->card_dev.groups = card->dev_groups;
card->dev_groups[0] = &card_dev_attr_group;
err = kobject_set_name(&card->card_dev.kobj, “card%d”, idx);//指明card的数量值,初始为0

 static char *sound_devnode(struct device *dev, umode_t *mode)
{
	.......
	return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev)); //用于在/dev/snd下的目录
}
static int __init init_soundcore(void)
{
	........
	sound_class = class_create(THIS_MODULE, "sound");
	sound_class->devnode = sound_devnode;
	......
}subsys_initcall(init_soundcore);//该函数用于创建声卡的class

snd_card_new
创建一个声卡实例化,其中注册控制子设备(control),/proc目录下的子目录操作

int snd_ctl_create(struct snd_card *card) //创建控制子设备
{
	static struct snd_device_ops ops = {
		.dev_free = snd_ctl_dev_free,
		.dev_register =	snd_ctl_dev_register,
		.dev_disconnect = snd_ctl_dev_disconnect,
	};
    ........
	snd_device_initialize(&card->ctl_dev, card);	//初始化声卡control的dev
	dev_set_name(&card->ctl_dev, "controlC%d", card->number);  //创建该命名
	snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);//创建card的组件,子设备类型为CONTROL,将子设备挂接到card的devices
	......

}

snd_info_card_create
创建声卡的/proc文件

soc_probe_link_components
//调用 platform->probe codec->probe 即 platform 和 codec的prob函数

static int soc_probe_link_dais(struct snd_soc_card *card,
		struct snd_soc_pcm_runtime *rtd, int order)
{
	struct snd_soc_dai_link *dai_link = rtd->dai_link;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int i, ret;		
	.......
	/* set default power off timeout */
	rtd->pmdown_time = pmdown_time;		//默认设置5000ms
	soc_probe_dai(cpu_dai, order);		//调用SOC侧的probe
	for (i = 0; i < rtd->num_codecs; i++) 
		soc_probe_dai(rtd->codec_dais[i], order); //调用codec侧的probe

	if (order != SND_SOC_COMP_ORDER_LAST) //完成5次调用才往下执行
		return 0;

	if (dai_link->init)//调用dai_link中的init
		ret = dai_link->init(rtd); 
	if (dai_link->dai_fmt)//调用dai_link中的dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS,
		snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);
	soc_post_component_init(rtd, dai_link->name);
	
	soc_new_pcm(rtd, rtd->num);//创建声卡逻辑设备pcm实例,rtd->pcm = pcm;PCM是由录音和播放二个子流组成,每个子流有若干个substream
	//调用drivers中的pcm_new
	soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
	soc_link_dai_pcm_new(rtd->codec_dais,rtd->num_codecs, rtd);
			
	......
	return 0;
}

snd_card_register(card->snd_card);:
最后将挂接在声卡上的子设备进行注册,登记在/dev/snd下。

PCM简析

PCM是目前ALSA中主流用来描述数字的音频流,pcm两大核心任务是:playback(播放) 和 capture(录音).一个pcm设备包含 播放/录制 两个数据流,每个数据流有若干个 substream每一个substream只能被一个进程占用,sub_pcm_substream 才是真正实现声卡设备 播放/录制 功能的结构体。可以在 /proc/asound/cardX/pcmXp/info 可查看pcm信息。

//sub_pcm_substream的操作方法集
struct snd_pcm_ops {
	int (*open)(struct snd_pcm_substream *substream); 						/* 必须实现 */
	int (*close)(struct snd_pcm_substream *substream);
	int (*ioctl)(struct snd_pcm_substream * substream,
		     unsigned int cmd, void *arg); 		/* 用于实现几个特定的IOCTL1_{RESET,INFO,CHANNEL_INFO,GSTATE,FIFO_SIZE} */
	int (*hw_params)(struct snd_pcm_substream *substream,
			 struct snd_pcm_hw_params *params); 							/* 用于设定pcm参数,如采样率/位深... */
	int (*hw_free)(struct snd_pcm_substream *substream);
	int (*prepare)(struct snd_pcm_substream *substream); 					/* 读写数据前的准备 */
	int (*trigger)(struct snd_pcm_substream *substream, int cmd); 			/* 触发硬件对数据的启动/停止 */
	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); 		/* 查询当前的硬件指针 */
	int (*wall_clock)(struct snd_pcm_substream *substream,
			  struct timespec *audio_ts); 									/* 通过hw获得audio_tstamp */
	int (*copy)(struct snd_pcm_substream *substream, int channel,
		    snd_pcm_uframes_t pos,void __user *buf, snd_pcm_uframes_t count); /* 除dma外的hw自身实现的数据传输方法 */
	int (*silence)(struct snd_pcm_substream *substream, int channel,
		       snd_pcm_uframes_t pos, snd_pcm_uframes_t count); 			/* hw静音数据的填充方法 */
	struct page *(*page)(struct snd_pcm_substream *substream,
			     unsigned long offset); 									/* 硬件分配缓冲区的方法 */
	int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma); /* */
	int (*ack)(struct snd_pcm_substream *substream); 						/* 通知硬件写了一次数据 */
};

//表示substream运行时状态及实时信息。 "/proc/asound/*/subX/"
struct snd_pcm_runtime {
	/* -- mmap -- */
	struct snd_pcm_mmap_status *status; /* 当前硬件指针位置及其状态 */
	struct snd_pcm_mmap_control *control; /* 当前的应用指针及其状态 */
	/* -- interrupt callbacks -- */ /* HW一次中断传输完毕时的回调,似乎没有哪个模块用到它? */
	void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
	void (*transfer_ack_end)(struct snd_pcm_substream *substream);
	...
	struct snd_dma_buffer *dma_buffer_p;	/* allocated buffer */
}
static int _snd_pcm_new()
{
	struct snd_pcm *pcm;
	int err;
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,//pcm注册函数
		.dev_disconnect = snd_pcm_dev_disconnect,
	};

//创建 播放(playback) 录音(capture)任务队列,完成PCM中的streams[2]的初始化,实例化struct snd_pcm_substream结构(主要通信结构),保存在pcm->streams[X].substream指针中
	snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count);
	snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);
//将声卡功能部件PCM 挂接到 声卡上card->devices (与上述中snd_ctl_create函数初始化control字设备一样)
	snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
}

snd_pcm_set_ops:
实现substream->ops(struct snd_pcm_ops );//该子集会在sound/core/pcm_native.c中的snd_pcm_common_ioctl函数中被上层调用,snd_pcm_common_ioctl是snd_pcm_f_ops.unlocked_ioctl的实现,是pcm子设备的file_operations(文件操作集)

参考连接:
https://blog.csdn.net/LinuxArmbiggod/article/details/80600633

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值