目录
版本获取
- 前言
- snd_pcm_open时序图
- 代码流程
版本获取
使用版本
alsa 1.2.5.1 release | Changes v1.2.5 v1.2.5.1
https://www.alsa-project.org/wiki/Main_Page
https://github.com/alsa-project
前言
我们都知道alsa-lib包含开发人员编译 ALSA 应用程序的用户空间库, 提供接口给应用调用的。前面我们了解过ALSA框架 ,本节我们主要介绍一下alsa-lib中snd_pcm_open流程。
snd_pcm_open时序图

在snd_pcm_open中主要做了两件事。
1、调用snd_config_update_ref函数以在asla.conf中获得snd_config_t结构的配置树到snd_config全局变量中。
2、调用snd_pcm_open_noupdate函数创建pcm设备。
下面让我们一起看下具体代码流程~
代码流程
alsa-lib最为重要的一个接口是snd_pcm_open函数,下面我们以播放为例,具体来看一下上层应用是怎么打开设备,下面我们分析一下代码流程。
1、在alsa-utils的main函数中调用snd_pcm_open打开pcm设备。
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); //从alsa.conf配置中,获得snd_config_t结构的配置树到snd_config全局变量中
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, 播放的话name就是PCM
stream SND_PCM_STREAM_PLAYBACK 或 SND_PCM_STREAM_CAPTURE,分别表示播放和录音的PCM流
mode 打开pcm句柄时的一些附加参数 SND_PCM_NONBLOCK 非阻塞打开(默认阻塞打开), SND_PCM_ASYNC 异步模式打开
返回值 0 表示打开成功,负数表示失败,对应错误码
使用snd_config_update_ref函数加载alsa.conf文件的内容到snd_config_t结构中。而alsa-lib-1.2.5.1/src/conf.c就是来完成load这个文件的。
//alsa.conf在板子中的路径如下 /usr/share/alsa/alsa.conf
2、接下来看一下snd_pcm_open_noupdate函数,在这个函数中才是真正构建配置树的函数。
snd_pcm_open_noupdate参数和snd_pcm_open参数一致,多了root和 hop,root参数就是传入snd_config_t结构配置树, hop 这个参数暂时不清楚是做什么用的。函数如下:
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;
//在root中查找PCM interface
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;
}
3、snd_pcm_open_conf, 下面我们来重点分析下snd_pcm_open_conf函数。
//用作查找对应的库文件
//lib = buf1;
//sprintf(buf1, "libasound_module_pcm_%s.so", str);
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)
{
...
open_name = buf; //open_name 是“_snd_pcm_hw_open”
sprintf(buf, "_snd_pcm_%s_open", str); //str是hw
...
...
lib = buf1;
sprintf(buf1, "libasound_module_pcm_%s.so", str);
...
//snd_dlobj_cache_get通过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函数指针,获取到之后去调用此库函数。
4、_snd_pcm_hw_open,下面来分析一下这个函数
SND_DLSYM_BUILD_VERSION(_snd_pcm_hw_open, SND_PCM_DLSYM_VERSION);
_snd_pcm_hw_open函数调用snd_pcm_hw_open, 然后在snd_ctl_hw_open中打开control()设备
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)
{
char filename[sizeof(SNDRV_FILE_PCM_STREAM_PLAYBACK) + 20];
const char *filefmt;
int ret = 0, fd = -1;
int attempt = 0;
snd_pcm_info_t info;
int fmode;
snd_ctl_t *ctl;
assert(pcmp);
//打开control设备,并new一个
if ((ret = snd_ctl_hw_open(&ctl, NULL, card, 0)) < 0)
return ret;
switch (stream) {
case SND_PCM_STREAM_PLAYBACK:
filefmt = SNDRV_FILE_PCM_STREAM_PLAYBACK;
break;
case SND_PCM_STREAM_CAPTURE:
filefmt = SNDRV_FILE_PCM_STREAM_CAPTURE;
break;
default:
SNDERR("invalid stream %d", stream);
return -EINVAL;
}
sprintf(filename, filefmt, card, device);
__again:
if (attempt++ > 3) {
ret = -EBUSY;
goto _err;
}
ret = snd_ctl_pcm_prefer_subdevice(ctl, subdevice);
if (ret < 0)
goto _err;
fmode = O_RDWR;
if (mode & SND_PCM_NONBLOCK)
fmode |= O_NONBLOCK;
if (mode & SND_PCM_ASYNC)
fmode |= O_ASYNC;
if (mode & SND_PCM_APPEND)
fmode |= O_APPEND;
fd = snd_open_device(filename, fmode); //打开pcm设备
if (fd < 0) {
ret = -errno;
SYSMSG("open '%s' failed (%i)", filename, ret);
goto _err;
}
if (subdevice >= 0) {
memset(&info, 0, sizeof(info));
if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0) {
ret = -errno;
SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", ret);
goto _err;
}
if (info.subdevice != (unsigned int) subdevice) {
close(fd);
fd = -1;
goto __again;
}
}
snd_ctl_close(ctl);
return snd_pcm_hw_open_fd(pcmp, name, fd, sync_ptr_ioctl); //new一个pcm设备
_err:
if (fd >= 0)
close(fd);
snd_ctl_close(ctl);
return ret;
}
- 使用snd_ctl_hw_open函数,打开一个ctl设备,并new一个ctl设备,同时注册struct _snd_ctl_ops回调,给上层提供接口
int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)
{
fd = snd_open_device(filename, fmode);
err = snd_ctl_new(&ctl, SND_CTL_TYPE_HW, name);
ctl->ops = &snd_ctl_hw_ops;
}
- 使用snd_pcm_hw_open_fd函数new一个pcm设备,并且注册snd_pcm_ops_t类型回调,给上层提供接口。
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;
...
}
static const snd_pcm_ops_t snd_pcm_hw_ops = {
.close = snd_pcm_hw_close,
.hw_params = snd_pcm_hw_hw_params,
};
static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = {
.prepare = snd_pcm_hw_prepare,
.reset = snd_pcm_hw_reset,
.start = snd_pcm_hw_start,
.pause = snd_pcm_hw_pause,
.writei = snd_pcm_hw_writei, //给alsa-utils提供接口,播放数据流回调
.writen = snd_pcm_hw_writen,
.readi = snd_pcm_hw_readi,
.readn = snd_pcm_hw_readn,
};
到这里alsa-lib的snd_pcm_open流程已经分析结束,后续还会介绍一下数据流怎么传输的流程~