alsa-lib 提供的 API 接口和 Android 等系统中使用的 tinyalsa 提供的 API 接口差异巨大,alsa-lib 的复杂度及支持的功能特性,与 tinyalsa 的有着数量级上的差异。插件机制及 dmix 和 dsnoop 等内置 PCM 插件是 alsa-lib 支持的众多高级特性的一部分。ALSA 音频内核设备驱动,在特定时刻只能运行在特定模式和一组参数下,通常只能支持由单个应用程序打开。dmix 和 dsnoop PCM 插件则提供了通过 alsa-lib API,多个应用程序共享音频硬件设备的能力,dmix PCM 插件将多个音频流混音之后送进硬件设备,dsnoop 插件将一个音频采集流分割为多个分别提供给不同的应用程序,它的工作方式与 dmix 相反,支持从多个客户端并发地读取共享采集缓冲区。这里简单分析 alsa-lib 的 PCM 插件 dsnoop 的实现。
alsa-lib 中的 PCM 设备
alsa-lib 支持的 PCM 设备类型众多,alsa-lib/include/pcm.h 中 PCM 设备类型的声明如下:
/** PCM handle */
typedef struct _snd_pcm snd_pcm_t;
/** PCM type */
enum _snd_pcm_type {
/** Kernel level PCM */
SND_PCM_TYPE_HW = 0,
/** Hooked PCM */
SND_PCM_TYPE_HOOKS,
/** One or more linked PCM with exclusive access to selected
channels */
SND_PCM_TYPE_MULTI,
/** File writing plugin */
SND_PCM_TYPE_FILE,
/** Null endpoint PCM */
SND_PCM_TYPE_NULL,
/** Shared memory client PCM */
SND_PCM_TYPE_SHM,
/** INET client PCM (not yet implemented) */
SND_PCM_TYPE_INET,
/** Copying plugin */
SND_PCM_TYPE_COPY,
/** Linear format conversion PCM */
SND_PCM_TYPE_LINEAR,
/** A-Law format conversion PCM */
SND_PCM_TYPE_ALAW,
/** Mu-Law format conversion PCM */
SND_PCM_TYPE_MULAW,
/** IMA-ADPCM format conversion PCM */
SND_PCM_TYPE_ADPCM,
/** Rate conversion PCM */
SND_PCM_TYPE_RATE,
/** Attenuated static route PCM */
SND_PCM_TYPE_ROUTE,
/** Format adjusted PCM */
SND_PCM_TYPE_PLUG,
/** Sharing PCM */
SND_PCM_TYPE_SHARE,
/** Meter plugin */
SND_PCM_TYPE_METER,
/** Mixing PCM */
SND_PCM_TYPE_MIX,
/** Attenuated dynamic route PCM (not yet implemented) */
SND_PCM_TYPE_DROUTE,
/** Loopback server plugin (not yet implemented) */
SND_PCM_TYPE_LBSERVER,
/** Linear Integer <-> Linear Float format conversion PCM */
SND_PCM_TYPE_LINEAR_FLOAT,
/** LADSPA integration plugin */
SND_PCM_TYPE_LADSPA,
/** Direct Mixing plugin */
SND_PCM_TYPE_DMIX,
/** Jack Audio Connection Kit plugin */
SND_PCM_TYPE_JACK,
/** Direct Snooping plugin */
SND_PCM_TYPE_DSNOOP,
/** Direct Sharing plugin */
SND_PCM_TYPE_DSHARE,
/** IEC958 subframe plugin */
SND_PCM_TYPE_IEC958,
/** Soft volume plugin */
SND_PCM_TYPE_SOFTVOL,
/** External I/O plugin */
SND_PCM_TYPE_IOPLUG,
/** External filter plugin */
SND_PCM_TYPE_EXTPLUG,
/** Mmap-emulation plugin */
SND_PCM_TYPE_MMAP_EMUL,
SND_PCM_TYPE_LAST = SND_PCM_TYPE_MMAP_EMUL
};
这些 PCM 设备类型中,只有 SND_PCM_TYPE_HW
类型会与内核及音频硬件设备交互,会访问 PCM 设备文件,即 PCM 硬件设备。其它包括 SND_PCM_TYPE_DMIX
和 SND_PCM_TYPE_DSNOOP
等在内的 PCM 设备类型都是虚拟设备,它们为应用程序提供 PCM 设备操作接口,但并不直接访问 PCM 设备文件。
alsa-lib 用 snd_pcm_t
对象描述 PCM 设备,不同类型的 PCM 设备对应于不同的 snd_pcm_t
对象实现。在 alsa-lib 中,PCM 设备即 snd_pcm_t
对象,snd_pcm_t
对象即 PCM 设备。snd_pcm_t
类型的定义 (位于 alsa-lib/src/pcm/pcm_local.h) 如下:
typedef struct _snd_pcm_rbptr {
snd_pcm_t *master;
volatile snd_pcm_uframes_t *ptr;
int fd;
off_t offset;
int link_dst_count;
snd_pcm_t **link_dst;
void *private_data;
void (*changed)(snd_pcm_t *pcm, snd_pcm_t *src);
} snd_pcm_rbptr_t;
typedef struct _snd_pcm_channel_info {
unsigned int channel;
void *addr; /* base address of channel samples */
unsigned int first; /* offset to first sample in bits */
unsigned int step; /* samples distance in bits */
enum { SND_PCM_AREA_SHM, SND_PCM_AREA_MMAP, SND_PCM_AREA_LOCAL } type;
union {
struct {
struct snd_shm_area *area;
int shmid;
} shm;
struct {
int fd;
off_t offset;
} mmap;
} u;
char reserved[64];
} snd_pcm_channel_info_t;
typedef struct {
int (*close)(snd_pcm_t *pcm);
int (*nonblock)(snd_pcm_t *pcm, int nonblock); /* always locked */
int (*async)(snd_pcm_t *pcm, int sig, pid_t pid);
int (*info)(snd_pcm_t *pcm, snd_pcm_info_t *info);
int (*hw_refine)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
int (*hw_params)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
int (*hw_free)(snd_pcm_t *pcm);
int (*sw_params)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); /* always locked */
int (*channel_info)(snd_pcm_t *pcm, snd_pcm_channel_info_t *info);
void (*dump)(snd_pcm_t *pcm, snd_output_t *out);
int (*mmap)(snd_pcm_t *pcm);
int (*munmap)(snd_pcm_t *pcm);
snd_pcm_chmap_query_t **(*query_chmaps)(snd_pcm_t *pcm);
snd_pcm_chmap_t *(*get_chmap)(snd_pcm_t *pcm);
int (*set_chmap)(snd_pcm_t *pcm, const snd_pcm_chmap_t *map);
} snd_pcm_ops_t;
typedef struct {
int (*status)(snd_pcm_t *pcm, snd_pcm_status_t *status); /* locked */
int (*prepare)(snd_pcm_t *pcm); /* locked */
int (*reset)(snd_pcm_t *pcm); /* locked */
int (*start)(snd_pcm_t *pcm); /* locked */
int (*drop)(snd_pcm_t *pcm); /* locked */
int (*drain)(snd_pcm_t *pcm); /* need own locking */
int (*pause)(snd_pcm_t *pcm, int enable); /* locked */
snd_pcm_state_t (*state)(snd_pcm_t *pcm); /* locked */
int (*hwsync)(snd_pcm_t *pcm); /* locked */
int (*delay)(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp); /* locked */
int (*resume)(snd_pcm_t *pcm); /* need own locking */
int (*link)(snd_pcm_t *pcm1, snd_pcm_t *pcm2);
int (*link_slaves)(snd_pcm_t *pcm, snd_pcm_t *master);
int (*unlink)(snd_pcm_t *pcm);
snd_pcm_sframes_t (*rewindable)(snd_pcm_t *pcm); /* locked */
snd_pcm_sframes_t (*rewind)(snd_pcm_t *pcm, snd_pcm_uframes_t frames); /* locked */
snd_pcm_sframes_t (*forwardable)(snd_pcm_t *pcm); /* locked */
snd_pcm_sframes_t (*forward)(snd_pcm_t *pcm, snd_pcm_uframes_t frames); /* locked */
snd_pcm_sframes_t (*writei)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); /* need own locking */
snd_pcm_sframes_t (*writen)(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size); /* need own locking */
snd_pcm_sframes_t (*readi)(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size); /* need own locking */
snd_pcm_sframes_t (*readn)(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size); /* need own locking */
snd_pcm_sframes_t (*avail_update)(snd_pcm_t *pcm); /* locked */
snd_pcm_sframes_t (*mmap_commit)(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t size); /* locked */
int (*htimestamp)(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, snd_htimestamp_t *tstamp); /* locked */
int (*poll_descriptors_count)(snd_pcm_t *pcm); /* locked */
int (*poll_descriptors)(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space); /* locked */
int (*poll_revents)(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents); /* locked */
int (*may_wait_for_avail_min)(snd_pcm_t *pcm, snd_pcm_uframes_t avail);
int (*mmap_begin)(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames); /* locked */
} snd_pcm_fast_ops_t;
struct _snd_pcm {
void *open_func;
char *name;
snd_pcm_type_t type;
snd_pcm_stream_t stream;
int mode;
long minperiodtime; /* in us */
int poll_fd_count;
int poll_fd;
unsigned short poll_events;
int setup: 1,
compat: 1;
snd_pcm_access_t access; /* access mode */
snd_pcm_format_t format; /* SND_PCM_FORMAT_* */
snd_pcm_subformat_t subformat; /* subformat */
unsigned int channels; /* channels */
unsigned int rate; /* rate in Hz */
snd_pcm_uframes_t period_size;
unsigned int period_time; /* period duration */
snd_interval_t periods;
snd_pcm_tstamp_t tstamp_mode; /* timestamp mode */
snd_pcm_tstamp_type_t tstamp_type; /* timestamp type */
unsigned int period_step;
snd_pcm_uframes_t avail_min; /* min avail frames for wakeup */
int period_event;
snd_pcm_uframes_t start_threshold;
snd_pcm_uframes_t stop_threshold;
snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
noise is nearest than this */
snd_pcm_uframes_t silence_size; /* Silence filling size */
snd_pcm_uframes_t boundary; /* pointers wrap point */
unsigned int info; /* Info for returned setup */
unsigned int msbits; /* used most significant bits */
unsigned int rate_num; /* rate numerator */
unsigned int rate_den; /* rate denominator */
unsigned int hw_flags; /* actual hardware flags */
snd_pcm_uframes_t fifo_size; /* chip FIFO size in frames */
snd_pcm_uframes_t buffer_size;
snd_interval_t buffer_time;
unsigned int sample_bits;
unsigned int frame_bits;
snd_pcm_rbptr_t appl;
snd_pcm_rbptr_t hw;
snd_pcm_uframes_t min_align;
unsigned int mmap_rw: 1; /* use always mmapped buffer */
unsigned int mmap_shadow: 1; /* don't call actual mmap,
* use the mmaped buffer of the slave
*/
unsigned int donot_close: 1; /* don't close this PCM */
unsigned int own_state_check:1; /* plugin has own PCM state check */
snd_pcm_channel_info_t *mmap_channels;
snd_pcm_channel_area_t *running_areas;
snd_pcm_channel_area_t *stopped_areas;
const snd_pcm_ops_t *ops;
const snd_pcm_fast_ops_t *fast_ops;
snd_pcm_t *op_arg;
snd_pcm_t *fast_op_arg;
void *private_data;
struct list_head async_handlers;
#ifdef THREAD_SAFE_API
int need_lock; /* true = this PCM (plugin) is thread-unsafe,
* thus it needs a lock.
*/
int lock_enabled; /* thread-safety lock is enabled on the system;
* it's set depending on $LIBASOUND_THREAD_SAFE.
*/
pthread_mutex_t lock;
#endif
};
snd_pcm_t
对象的 const snd_pcm_ops_t *ops
和 const snd_pcm_fast_ops_t *fast_ops
成员分别指向由众多函数指针组成的结构,它们决定着特定类型 PCM 设备各个操作的行为。
打开 dsnoop PCM 设备
alsa-lib 的 snd_pcm_open()
接口用于打开 PCM 设备,这个接口的声明 (位于 alsa-lib/include/pcm.h) 如下:
int snd_pcm_open(snd_pcm_t **pcm, const char *name,
snd_pcm_stream_t stream, int mode);
snd_pcm_open()
接口各个参数的含义如下:
- pcmp:返回的
snd_pcm_t
对象,PCM 句柄。 - name:PCM 句柄的 ASCII 标识符,用于指定要打开的 PCM 设备,如 dsnoop 或 dsnoop:1。
- stream:流的类型,播放流和录制流分别为
SND_PCM_STREAM_PLAYBACK
和SND_PCM_STREAM_CAPTURE
。 - mode:打开模式,可选值为
SND_PCM_NONBLOCK
和SND_PCM_ASYNC
。
arecord 命令可以指定通过 dsnoop 虚拟设备采集音频数据,如:
$ arecord -f S16_LE -r 48000 -c 2 -D dsnoop foobar.wav
arecord 命令支持设置 dsnoop 虚拟设备绑定到非默认的声卡 0,如绑定到硬件声卡 1 采集音频数据:
$ arecord -f S16_LE -r 48000 -c 2 -D dsnoop:1 foobar.wav
arecord 命令的 -D
参数,将在打开 PCM 设备时,作为 snd_pcm_open()
接口的 name
参数,指定要打开的 PCM 设备。
snd_pcm_open()
函数定义 (位于 alsa-lib/src/pcm/pcm.c) 如下:
/**
* \brief Opens a PCM
* \param pcmp Returned PCM handle
* \param name ASCII identifier of the PCM handle
* \param stream Wanted stream
* \param mode Open mode (see #SND_PCM_NONBLOCK, #SND_PCM_ASYNC)
* \return 0 on success otherwise a negative error code
*/
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;
}
UCM 设备是名称以 “_ucm” 开头的设备,这里忽略 UCM 设备的情况。snd_pcm_open()
函数的执行过程如下:
- 调用
snd_config_update_ref()
函数更新 ALSA 顶层配置 snd_config 并获取它的引用。 - 调用
snd_pcm_open_noupdate()
函数根据 ALSA 顶层配置打开 PCM 设备。 - 释放 ALSA 顶层配置 snd_config 的引用。
更新 ALSA 顶层配置的 snd_config_update_ref()
函数定义 (位于 alsa-lib/src/conf.c) 如下:
struct _snd_config {
char *id;
snd_config_type_t type;
int refcount; /* default = 0 */
union {
long integer;
long long integer64;
char *string;
double real;
const void *ptr;
struct {
struct list_head fields;
bool join;
} compound;
} u;
struct list_head list;
snd_config_t *parent;
int hop;
};
. . . . . .
const char *snd_config_topdir(void)
{
static char *topdir;
if (!topdir) {
topdir = getenv("ALSA_CONFIG_DIR");
if (!topdir || *topdir != '/' || strlen(topdir) >= PATH_MAX)
topdir = ALSA_CONFIG_DIR;
}
return topdir;
}
. . . . . .
snd_config_t *snd_config = NULL;
#ifndef DOC_HIDDEN
struct finfo {
char *name;
dev_t dev;
ino64_t ino;
time_t mtime;
};
struct _snd_config_update {
unsigned int count;
struct finfo *finfo;
};
#endif /* DOC_HIDDEN */
static snd_config_update_t *snd_config_global_update = NULL;
. . . . . .
int snd_config_update_r(snd_config_t **_top, snd_config_update_t **_update, const char *cfgs)
{
int err;
const char *configs, *c;
unsigned int k;
size_t l;
snd_config_update_t *local;
snd_config_update_t *update;
snd_config_t *top;
assert(_top && _update);
top = *_top;
update = *_update;
configs = cfgs;
if (!configs) {
configs = getenv(ALSA_CONFIG_PATH_VAR);
if (!configs || !*configs) {
const char *topdir = snd_config_topdir();
char *s = alloca(strlen(topdir) +
strlen("alsa.conf") + 2);
sprintf(s, "%s/alsa.conf", topdir);
configs = s;
}
}
for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {
c += l;
k++;
if (!*c)
break;
c++;
}
if (k == 0) {
local = NULL;
goto _reread;
}
local = (snd_config_update_t *)calloc(1, sizeof(snd_config_update_t));
if (!local)
return -ENOMEM;
local->count = k;
local->finfo = calloc(local->count, sizeof(struct finfo));
if (!local->finfo) {
free(local);
return -ENOMEM;
}
for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {
char name[l + 1];
memcpy(name, c, l);
name[l] = 0;
err = snd_user_file(name, &local->finfo[k].name);
if (err < 0)
goto _end;
c += l;
k++;
if (!*c)
break;
c++;
}
for (k = 0; k < local->count; ++k) {
struct stat64 st;
struct finfo *lf = &local->finfo[k];
if (stat64(lf->name, &st) >= 0) {
lf->dev = st.st_dev;
lf->ino = st.st_ino;
lf->mtime = st.st_mtime;
} else {
SNDERR("Cannot access file %s", lf->name);
free(lf->name);
memmove(&local->finfo[k], &local->finfo[k+1], sizeof(struct finfo) * (local->count - k - 1));
k--;
local->count--;
}
}
if (!update)
goto _reread;
if (local->count != update->count)
goto _reread;
for (k = 0; k < local->count; ++k) {
struct finfo *lf = &local->finfo[k];
struct finfo *uf = &update->finfo[k];
if (strcmp(lf->name, uf->name) != 0 ||
lf->dev != uf->dev ||
lf->ino != uf->ino ||
lf->mtime != uf->mtime)
goto _reread;
}
err = 0;
_end:
if (err < 0) {
if (top) {
snd_config_delete(top);
*_top = NULL;
}
if (update) {
snd_config_update_free(update);
*_update = NULL;
}
}
if (local)
snd_config_update_free(local);
return err;
_reread:
*_top = NULL;
*_update = NULL;
if (update) {
snd_config_update_free(update);
update = NULL;
}
if (top) {
snd_config_delete(top);
top = NULL;
}
err = snd_config_top(&top);
if (err < 0)
goto _end;
if (!local)
goto _skip;
for (k = 0; k < local->count; ++k) {
snd_input_t *in;
err = snd_input_stdio_open(&in, local->finfo[k].name, "r");
if (err >= 0) {
err = snd_config_load(top, in);
snd_input_close(in);
if (err < 0) {
SNDERR("%s may be old or corrupted: consider to remove or fix it", local->finfo[k].name);
goto _end;
}
} else {
SNDERR("cannot access file %s", local->finfo[k].name);
}
}
_skip:
err = snd_config_hooks(top, NULL);
if (err < 0) {
SNDERR("hooks failed, removing configuration");
goto _end;
}
*_top = top;
*_update = local;
return 1;
}
. . . . . .
/**
* \brief Updates #snd_config and takes its reference.
* \return 0 if #snd_config was up to date, 1 if #snd_config was
* updated, otherwise a negative error code.
*
* Unlike #snd_config_update, this function increases a reference counter
* so that the obtained tree won't be deleted until unreferenced by
* #snd_config_unref.
*
* This function is supposed to be thread-safe.
*/
int snd_config_update_ref(snd_config_t **top)
{
int err;
if (top)
*top = NULL;
snd_config_lock();
err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL);
if (err >= 0) {
if (snd_config) {
if (top) {
snd_config->refcount++;
*top = snd_config;
}
} else {
err = -ENODEV;
}
}
snd_config_unlock();
return err;
}
snd_config_update_ref()
函数根据从顶层配置文件读取的内容更新配置树,它是 snd_config_update_r()
函数的线程安全封装。snd_config_update_r()
函数的 cfgs
参数接受以冒号 (‘:’) 分割的顶层配置文件名列表,在snd_pcm_open()
/snd_config_update_ref()
的场景中,cfgs
参数为空,取默认顶层配置文件。
snd_config_update_r()
函数按一定的优先级在多个目录中查找默认的 ALSA 顶层配置文件:
- 环境变量 ALSA_CONFIG_PATH 指向的 ALSA 顶层配置文件路径列表。
- 顶层配置目录下的 alsa.conf 文件。顶层配置目录按一定的优先级来查找:
- 环境变量 ALSA_CONFIG_DIR 指向的目录。
- /usr/share/alsa。
通常默认的 ALSA 顶层配置文件为 /usr/share/alsa/alsa.conf。对于 ALSA 顶层配置文件的路径,snd_config_update_r()
函数支持 “~/” 的形式,它会通过 snd_user_file()
函数获得用户目录路径,并进而获得配置文件的绝对路径。
alsa-lib 用 snd_config_update_t
对象描述 ALSA 顶层配置文件集,并用 finfo
对象描述其中的一个配置文件。snd_config_update_r()
函数在获得 ALSA 顶层配置文件路径列表之后,将它们转换为 snd_config_update_t
对象的表示,这会处理文件路径中的 “~/”,并过滤掉无法访问的文件。
snd_config_update_r()
函数的 _update
参数用来传入老的配置,并用来传出新加载的配置。snd_config_update_r()
函数对比老的配置的配置文件信息和新获取的配置文件的信息,如果两者存在差异(包括文件路径、文件所在的设备号、文件的 inode 号和最近修改时间等方面),则加载新的配置,否则释放新创建的 snd_config_update_t
对象并返回。
加载新配置时,如果通过 _top
和 _update
参数传入的老的配置已经存在,它们会先被释放掉。随后,通过 snd_config_top()
函数重新为 ALSA 顶层配置分配 snd_config_t
对象,通过 snd_input_stdio_open()
/snd_config_load()
/snd_input_close()
这组函数逐个加载配置文件,通过 snd_config_hooks()
函数处理 hooks。最后,通过传入的 _top
和 _update
参数返回加载的配置。
ALSA 配置文件支持声明 hook,即一个函数,如 ALSA 顶层配置文件 /usr/share/alsa/alsa.conf 中声明的 hook:
@hooks [
{
func load
files [
"/etc/alsa/conf.d"
"/etc/asound.conf"
"~/.asoundrc"
]
errors false
}
]
hooks 的 func 配置指定 hook 名称,而不直接对应 alsa-lib 内部的函数名,hook_func 用于指定 hook 的函数名,如 /usr/share/alsa/alsa.conf.d/pulse.conf 配置文件:
hook_func.pulse_load_if_running {
lib "libasound_module_conf_pulse.so"
func "conf_pulse_hook_load_if_running"
}
@hooks [
{
func pulse_load_if_running
files [
"/usr/share/alsa/pulse-alsa.conf"
]
errors false
}
]
当没有为 hook 指定 hook 函数名时,hook 函数名将为 snd_config_hook_[func],如 snd_config_hook_load
,snd_config_hook_load()
函数将在 snd_config_hooks()
函数处理 hooks 过程中被调用。
如在 snd_config_update_ref()
函数中看到的,加载的 ALSA 顶层配置被保存在全局的 snd_config
和 snd_config_global_update
对象中。
ALSA 顶层配置文件的内容,如 /usr/share/alsa/alsa.conf 文件。关于 ALSA 配置文件更详细的信息,可以参考它的 WiKi 页面 Asoundrc。通常 /usr/share/alsa/alsa.conf 配置文件是 ALSA 配置文件的主入口点,它负责包含系统上 .asoundrc-format-type 格式文件的完整列表。
回到 snd_pcm_open()
函数,它在加载了 ALSA 顶层配置之后,调用 snd_pcm_open_noupdate()
函数打开 PCM 音频设备。snd_pcm_open_noupdate()
函数定义 (位于 alsa-lib/src/pcm/pcm.c) 如下:
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_