alsa-lib主要是给抽象出来的一套ALSA应用程序的用户空间库,供具体的应用程序调用。alsa-utils 主要是相关的操作APP,可以充当官方demo,供开发人员参考。前文已经给出ALSA音频架构。本文主要详细分析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中进行了相应的操作。
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解析流程结束。