alsa-lib之snd_pcm_open

目录

版本获取

  • 前言
  • 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流程已经分析结束,后续还会介绍一下数据流怎么传输的流程~


版权声明:本文为mahao1107原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。