ALSA音频架构 -- snd_pcm_open函数分析

Posted ZC_L

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ALSA音频架构 -- snd_pcm_open函数分析相关的知识,希望对你有一定的参考价值。

引言

alsa-lib主要是给抽象出来的一套ALSA应用程序的用户空间库,供具体的应用程序调用。alsa-utils 主要是相关的操作APP,可以充当官方demo,供开发人员参考。前文已经给出ALSA音频架构。本文主要详细分析snd_pcm_open。

snd_pcm_open顺序图

代码详细分析 (以播放为例)

问题引入

alsa_utils aplay.c 中的播放接口采用函数指针实现,具体定义如下

static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size);

赋值如下

		writei_func = snd_pcm_writei;
		readi_func = snd_pcm_readi;
		writen_func = snd_pcm_writen;
		readn_func = snd_pcm_readn;

snd_pcm_writei通过调用_snd_pcm_writei写入PCM数据流,_snd_pcm_writei函数原型如下

static inline snd_pcm_sframes_t _snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)

	/* lock handled in the callback */
	if (!pcm->fast_ops->writei)
		return -ENOSYS;
	return pcm->fast_ops->writei(pcm->fast_op_arg, buffer, size);   // 播放函数指针

_snd_pcm_writei会调用pcm->fast_ops->writei进行实际操作。
查看aplay.c源码始终没有发现PCM设备中的结构体const snd_pcm_fast_ops_t *fast_ops在哪里初始化,极大可能在snd_pcm_open中进行了相应的操作。

snd_pcm_open 具体分析

alsa_utils aplay.c 中调用 snd_pcm_open 如下

   ...
   char *pcm_name = "default";
   ...
	err = snd_pcm_open(&handle, pcm_name, stream, open_mode);
	if (err < 0) 
		error(_("audio open error: %s"), snd_strerror(err));
		return 1;
	

snd_pcm_open 函数原型如下

int snd_pcm_open(snd_pcm_t **pcmp, const char *name, 
		 snd_pcm_stream_t stream, int mode)

	snd_config_t *top;
	int err;

	assert(pcmp && name);
	if (_snd_is_ucm_device(name)) 
		name = uc_mgr_alibcfg_by_device(&top, name);
		if (name == NULL)
			return -ENODEV;
	 else 
		err = snd_config_update_ref(&top);
		if (err < 0)
			return err;
	
	err = snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);
	snd_config_unref(top);
	return err;

pcmp,即打开的PCM设备句柄; name,要打开的PCM设备名称,默认default
stream,对应的PCM流类型,播放PCM流(SND_PCM_STREAM_PLAYBACK)和录音PCM流(SND_PCM_STREAM_CAPTURE)
mode,打开方式,阻塞、非阻塞及异步等

snd_pcm_open通过调用snd_config_update_ref来获取als.conf中的配置信息,参数保存至snd_config_t 。
通过snd_pcm_open_noupdate 解析 snd_config_t 配置,snd_pcm_open_noupdate 函数原型如下

static int snd_pcm_open_noupdate(snd_pcm_t **pcmp, snd_config_t *root,
				 const char *name, snd_pcm_stream_t stream,
				 int mode, int hop)

	int err;
	snd_config_t *pcm_conf;
	const char *str;

	err = snd_config_search_definition(root, "pcm", name, &pcm_conf);
	if (err < 0) 
		SNDERR("Unknown PCM %s", name);
		return err;
	
	if (snd_config_get_string(pcm_conf, &str) >= 0)
	    // 循环递归解析
		err = snd_pcm_open_noupdate(pcmp, root, str, stream, mode,
					    hop + 1);
	else 
		snd_config_set_hop(pcm_conf, hop);
		err = snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode);
	
	snd_config_delete(pcm_conf);
	return err;

snd_pcm_open_conf 提取 snd_config_t 参数

static const char *const build_in_pcms[] = 
	"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
	"linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share",
	"shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
	NULL
;
static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name,
			     snd_config_t *pcm_root, snd_config_t *pcm_conf,
			     snd_pcm_stream_t stream, int mode)

...
sprintf(buf, "_snd_pcm_%s_open", str); //open_name即“_snd_pcm_hw_open”
...
const char *const *build_in = build_in_pcms;
sprintf(buf1, "libasound_module_pcm_%s.so", str);
...
// 通过open_name在lib中获取对应的动态库函数
open_func = snd_dlobj_cache_get(lib, open_name,
			SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION), 1);
if (open_func) 
		err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
...

snd_pcm_open_conf 调用snd_dlobj_cache_get在动态库中libasound_module_pcm_hw.so获取函数指针_snd_pcm_hw_open

_snd_pcm_hw_open通过调用snd_pcm_hw_open来创建hw_pcm设备。snd_pcm_hw_open函数原型如下

int snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name,
		    int card, int device, int subdevice,
		    snd_pcm_stream_t stream, int mode,
		    int mmap_emulation ATTRIBUTE_UNUSED,
		    int sync_ptr_ioctl)

	...
	if ((ret = snd_ctl_hw_open(&ctl, NULL, card, 0)) < 0)
		return ret;
	...
	fd = snd_open_device(filename, fmode);
	...
	return snd_pcm_hw_open_fd(pcmp, name, fd, sync_ptr_ioctl);
       _err:
	if (fd >= 0)
		close(fd);
	snd_ctl_close(ctl);
	return ret;

snd_pcm_hw_open主要完成如下工作:
调用snd_ctl_hw_open创建了一个hw control设备,并设置回调const snd_ctl_ops_t *ops,回调参数为snd_ctl_hw_ops,具体操作接口如下:

static const snd_ctl_ops_t snd_ctl_hw_ops = 
	.close = snd_ctl_hw_close,
	.nonblock = snd_ctl_hw_nonblock,
	.async = snd_ctl_hw_async,
	.subscribe_events = snd_ctl_hw_subscribe_events,
	.card_info = snd_ctl_hw_card_info,
	.element_list = snd_ctl_hw_elem_list,
	.element_info = snd_ctl_hw_elem_info,
	.element_add = snd_ctl_hw_elem_add,
	.element_replace = snd_ctl_hw_elem_replace,
	.element_remove = snd_ctl_hw_elem_remove,
	.element_read = snd_ctl_hw_elem_read,
	.element_write = snd_ctl_hw_elem_write,
	.element_lock = snd_ctl_hw_elem_lock,
	.element_unlock = snd_ctl_hw_elem_unlock,
	.element_tlv = snd_ctl_hw_elem_tlv,
	.hwdep_next_device = snd_ctl_hw_hwdep_next_device,
	.hwdep_info = snd_ctl_hw_hwdep_info,
	.pcm_next_device = snd_ctl_hw_pcm_next_device,
	.pcm_info = snd_ctl_hw_pcm_info,
	.pcm_prefer_subdevice = snd_ctl_hw_pcm_prefer_subdevice,
	.rawmidi_next_device = snd_ctl_hw_rawmidi_next_device,
	.rawmidi_info = snd_ctl_hw_rawmidi_info,
	.rawmidi_prefer_subdevice = snd_ctl_hw_rawmidi_prefer_subdevice,
	.set_power_state = snd_ctl_hw_set_power_state,
	.get_power_state = snd_ctl_hw_get_power_state,
	.read = snd_ctl_hw_read,
;

调用snd_pcm_hw_open_fd创建hw PCM设备并配置对应的回调,snd_pcm_hw_open_fd函数原型如下

int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name, int fd,
		       int sync_ptr_ioctl)
    ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode);
	...
	// 配置回调接口
	pcm->ops = &snd_pcm_hw_ops;   
	pcm->fast_ops = &snd_pcm_hw_fast_ops;
	pcm->private_data = hw;
	pcm->poll_fd = fd;
	pcm->poll_events = info.stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
	pcm->tstamp_type = tstamp_type;
	...


回调接口如下

static const snd_pcm_ops_t snd_pcm_hw_ops = 
	.close = snd_pcm_hw_close,
	.info = snd_pcm_hw_info,
	.hw_refine = snd_pcm_hw_hw_refine,
	.hw_params = snd_pcm_hw_hw_params,
	.hw_free = snd_pcm_hw_hw_free,
	.sw_params = snd_pcm_hw_sw_params,
	.channel_info = snd_pcm_hw_channel_info,
	.dump = snd_pcm_hw_dump,
	.nonblock = snd_pcm_hw_nonblock,
	.async = snd_pcm_hw_async,
	.mmap = snd_pcm_hw_mmap,
	.munmap = snd_pcm_hw_munmap,
	.query_chmaps = snd_pcm_hw_query_chmaps,
	.get_chmap = snd_pcm_hw_get_chmap,
	.set_chmap = snd_pcm_hw_set_chmap,
;

static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = 
	.status = snd_pcm_hw_status,
	.state = snd_pcm_hw_state,
	.hwsync = snd_pcm_hw_hwsync,
	.delay = snd_pcm_hw_delay,
	.prepare = snd_pcm_hw_prepare,
	.reset = snd_pcm_hw_reset,
	.start = snd_pcm_hw_start,
	.drop = snd_pcm_hw_drop,
	.drain = snd_pcm_hw_drain,
	.pause = snd_pcm_hw_pause,
	.rewindable = snd_pcm_hw_rewindable,
	.rewind = snd_pcm_hw_rewind,
	.forwardable = snd_pcm_hw_forwardable,
	.forward = snd_pcm_hw_forward,
	.resume = snd_pcm_hw_resume,
	.link = snd_pcm_hw_link,
	.link_slaves = snd_pcm_hw_link_slaves,
	.unlink = snd_pcm_hw_unlink,
	.writei = snd_pcm_hw_writei,    //播放数据流回调
	.writen = snd_pcm_hw_writen,
	.readi = snd_pcm_hw_readi,
	.readn = snd_pcm_hw_readn,
	.avail_update = snd_pcm_hw_avail_update,
	.mmap_commit = snd_pcm_hw_mmap_commit,
	.htimestamp = snd_pcm_hw_htimestamp,
	.poll_descriptors = NULL,
	.poll_descriptors_count = NULL,
	.poll_revents = NULL,
;

上文中的pcm->fast_ops->writei即snd_pcm_hw_writei。
至此alsa-lib中的snd_pcm_open解析流程结束。

以上是关于ALSA音频架构 -- snd_pcm_open函数分析的主要内容,如果未能解决你的问题,请参考以下文章

android audio/linux alsa音频-应用层基础

音频ALSA架构简介

alsa接口

Linux音频子系统 - ALSA ASoC

Linux音频驱动学习之:ASOC分析

Linux ALSA 音频系统:物理链路篇