alsa-lib 插件 dsnoop 实现简单分析

news2024/10/9 1:11:29

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_DMIXSND_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 *opsconst 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 设备,如 dsnoopdsnoop:1
  • stream:流的类型,播放流和录制流分别为 SND_PCM_STREAM_PLAYBACKSND_PCM_STREAM_CAPTURE
  • mode:打开模式,可选值为 SND_PCM_NONBLOCKSND_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() 函数的执行过程如下:

  1. 调用 snd_config_update_ref() 函数更新 ALSA 顶层配置 snd_config 并获取它的引用。
  2. 调用 snd_pcm_open_noupdate() 函数根据 ALSA 顶层配置打开 PCM 设备。
  3. 释放 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 顶层配置文件:

  1. 环境变量 ALSA_CONFIG_PATH 指向的 ALSA 顶层配置文件路径列表。
  2. 顶层配置目录下的 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
	}
]

hooksfunc 配置指定 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_loadsnd_config_hook_load() 函数将在 snd_config_hooks() 函数处理 hooks 过程中被调用。

如在 snd_config_update_ref() 函数中看到的,加载的 ALSA 顶层配置被保存在全局的 snd_configsnd_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_

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2197651.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里面试: RocketMQ如何实现每秒上十万QPS的超高吞吐量读取的?

这玩意儿表面看上去挺牛逼&#xff0c;但其实背后的逻辑和套路&#xff0c;在咱们开发里见过的那些招数&#xff0c;都能找到影子。 今天小北和大家一起系统化的梳理梳理一遍&#xff0c;让大家功力猛增&#xff0c;吊打面试官。 1. 消息存储&#xff1a;巧妙利用顺序写 先说…

ARTS Week 43

Algorithm 本周的算法题为 1822. 数组元素积的符号 已知函数 signFunc(x) 将会根据 x 的正负返回特定值&#xff1a; 如果 x 是正数&#xff0c;返回 1 。 如果 x 是负数&#xff0c;返回 -1 。 如果 x 是等于 0 &#xff0c;返回 0 。 给你一个整数数组 nums 。令 product 为数…

SimpleFoc以及SVPWM学习补充记录

SimpleFoc SimpleFOC移植STM32&#xff08;一&#xff09;—— 简介 FOC控制的过程是这样的&#xff1a; 对电机三相电流进行采样得到 Ia,Ib,Ic。将 Ia,Ib,Ic 经过Clark变换得到 I_alpha I_beta。将 I_alpha I_beta 经过Park变换得到 Id,Iq。计算 Id,Iq 和其设定值 Id_ref 和…

Spring JDBC - Spring JDBC模版使用

前言 Spring JdbcTemplate是Spring Framework提供的一个强大的数据库访问工具&#xff0c;它简化了数据库操作的过程&#xff0c;为开发者提供了一个高级的数据库访问抽象层。 JdbcTemplate是Spring JDBC模块中的一个核心类&#xff0c;它位于org.springframework.jdbc.core包中…

开源项目都是怎么推广的?

大家好&#xff0c;我是爱折腾的刘大逵。跟我接触过的技术们都知道&#xff0c;一年一年的都在折腾着做一些项目&#xff0c;年年有进步&#xff0c;年年有想法&#xff0c;年年在折腾。今天给大家分享GITEE如何上推荐&#xff01; GITEE推荐有什么用&#xff1f; 众所周知&a…

C++版iwanna2

第二篇目录 程序的流程图程序游玩的效果下一篇博客要说的东西 程序的流程图 #mermaid-svg-lFW0ZjCdi5Xvl3gE {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-lFW0ZjCdi5Xvl3gE .error-icon{fill:#552222;}#mermaid-s…

《神经网络》—— 循环神经网络RNN(Recurrent Neural Network)

文章目录 一、RNN 简单介绍二、RNN 基本结构1.隐藏中的计算2.输出层的计算3.循环 三、RNN 优缺点1.优点2.缺点 一、RNN 简单介绍 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一种用于处理序列数据的神经网络架构。 与传统的前馈神经网络&#xff08…

聆听国宝“心声” 《寻找国宝传颂人——中国国宝大会》走进辽宁

热爱不止&#xff0c;传颂不停。近日&#xff0c;中央广播电视总台《寻找国宝传颂人——中国国宝大会》大型融媒体活动在辽宁省博物馆开展第二场地方展演。辽宁省博物馆是新中国成立后的第一座博物馆&#xff0c;馆藏文物近12万件&#xff0c;珍贵文物数万件&#xff0c;且以藏…

计算机网络:数据链路层详解

目录 一、点对点信道&#xff1a; &#xff08;1&#xff09;封装成帧 &#xff08;2&#xff09;透明传输 &#xff08;3&#xff09;差错检测 二、点对点协议 &#xff08;1&#xff09;数据链路层的特点 &#xff08;2&#xff09;PPP协议的组成 &#xff08;3&…

“我养你啊“英语怎么说?别说成I raise you!成人学英语到蓝天广场附近

“我养你啊”这句经典台词出自周星驰自导自演的电影《喜剧之王》。在这部电影中&#xff0c;周星驰饰演的尹天仇对张柏芝饰演的柳飘飘说出了这句深情而动人的台词。这句台词出现在柳飘飘即将离去之时&#xff0c;尹天仇鼓起勇气&#xff0c;用它作为对柳飘飘个人困境的承诺&…

docker compose入门5—创建一个3副本的应用

1. 定义服务 version: 3.8 services:web:image: gindemo:v2deploy:replicas: 3ports:- "9090" 2. 启动服务 docker compose -f docker-compose.yml up -d 3. 查看服务 docker compose ps 4. 访问服务

pycharm生成的exe执行后报错

元素 application 显示为元素 urn:schemas-microsoft-com:asm.v1^dependentAssembly (此版本的 Windows 不支持)的子元素。 日志名称: Application 来源: SideBySide 日期: 2024/10/8 14:14:12 事件 ID: 72 任务类别: 无 级别…

大数据新视界 --大数据大厂之 Presto 性能优化秘籍:加速大数据交互式查询

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

UE4 材质学习笔记05(凹凸偏移和视差映射/扭曲着色器)

一.凹凸偏移和视差映射 1.偏移映射 这需要一个高度图并且它的分辨率很低&#xff0c;只有256*256&#xff0c;事实上&#xff0c;如果高度图的分辨率比较低并且有点模糊&#xff0c;效果反而会更好 然后将高度图输出到BumpOffset节点的height插槽中&#xff0c; 之后利用得到…

登录注册静态网页实现(HTML,CSS)

实现效果图 实现效果 使用HTML编写页面结构&#xff0c;CSS美化界面&#xff0c;点击注册&#xff0c;跳转到注册界面&#xff0c;均为静态网页&#xff0c;是课上的一个小作业~ 使用正则表达式对输入进行验证&#xff0c;包括邮箱格式验证&#xff0c;用户名格式验证。 正则…

MATLAB数字水印系统

课题介绍 本课题为基于MATLAB的小波变换dwt和离散余弦dct的多方法对比数字水印系统。带GUI交互界面。有一个主界面GUI&#xff0c;可以调用dwt方法的子界面和dct方法的子界面。流程包括&#xff0c;读取宿主图像和水印图像&#xff0c;嵌入&#xff0c;多种方法的攻击&#xf…

九、4 串口接收(代码)

&#xff08;1&#xff09;在发送的基础上加上接收的部分 RX对应PA10&#xff0c;需要初始化PA10 &#xff08;2&#xff09;串口配置 如果使用查询&#xff0c;到这里就结束了&#xff0c;如果使用中断还需要在串口配置下面开启中断&#xff0c;配置NVIC &#xff08;3&…

《深度学习》神经语言模型 Word2vec CBOW项目解析、npy/npz文件解析

目录 一、关于word2vec 1、什么是word2vec 2、常用训练算法 1&#xff09;CBOW 2&#xff09;SkipGram 二、关于npy、npz文件 1、npy文件 1&#xff09;定义 2&#xff09;特性 3&#xff09;用途 4&#xff09;保存及读取 运行结果&#xff1a; 运行结果&#xf…

使用.mdf及.ldf恢复SQL SERVER数据库

文章目录 [toc]1.使用.mdf和对应的.ldf文件恢复数据库1.1 将对应的.mdf和.ldf复制到SQL SERVER路径下1.2 打开SSMS 1.使用.mdf和对应的.ldf文件恢复数据库 1.1 将对应的.mdf和.ldf复制到SQL SERVER路径下 一般默认路径是&#xff1a;C:\Program Files\Microsoft SQL Server\MS…

【源码+文档】基于Java的新能源停车场管理系统的设计与实现

&#x1f6a9;如何选题&#xff1f; 如何选题、让题目的难度在可控范围&#xff0c;以及如何在选题过程以及整个毕设过程中如何与老师沟通&#xff0c;这些问题是需要大家在选题前需要考虑的&#xff0c;具体的方法我会在文末详细为你解答。 &#x1f6ad;如何快速熟悉一个项…