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中进行了相应的 *** 作。
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解析流程结束。
音视频
点赞文章给优秀博主打call~
细跟高跟凉鞋
精选推荐
广告
android使用Alsa Aloop录制系统内部声音 原创2022-04-24 19:12:49
这歌声无聊可是辉煌
码龄10年
关注
alsa aloop是alsa提供的内部loopback功能,可以用来实现录制系统内部声音,在没有硬件支持loopback功能时可以采用这种软件loopback的实现来代替。
alsa aloop实现了一个虚拟的声卡。
在kernel的编译配置选项文件中加入CONFIG_SND_ALOOP=y之后烧录到平丛坦带台中声卡会多出两个pcm设备:
即
00-00: Loopback PCM : Loopback PCM : playback 8 : capture 8
00-01: Loopback PCM : Loopback PCM : playback 8 : capture 8
注意到此时原先板载的实际声卡的index将变为1,因为aloop注册比较早
有了这两个节点之后就可以使用tinyplay和tinycap来验证。
使用方法为:
tinyplay 写入card 0 device 0,同时tinycap从card 0 device 1录制
tinyplay /data/8ch_16bit.wav -d 0&tinycap /data/test.wav -d 1 -c 8 -r 48000 -b 16 <
[1] 8228
Playing sample: 8 ch, 48000 hz, 16 bit
Capturing sample: 8 ch, 48000 hz, 16 bit
注意的是打开的参数如采样率 位深和ch数要与录制的时候保持信搭一致,否则会提示参数非法而打开失败。
这一步验证完成之后要实现在android里使用aloop录制系统声音则需要在audo hal中pcm_open,pcm_close,pcm_write实际声卡的地方一样加上 *** 作aloop的虚拟声卡0,
在android P上调试发现使用pcm_open打开aloop device输出时传递的alsa config参数也渗芦有限制:
period_size = 1024
period_count = 4
period_size 和period_count 需要,不超过1024和4,否则一样会open失败 提示参数非法。
最后贴下alsa aloop的数据流向:
提示连接庆念被拒绝,1,首先需要确认你的声卡驱动是否正常,
2,确认函数参数是否正确
3,确认构建配置树是否配誉哗困置成功
由于提主提供的资料有限,没法提供更芦察多建议!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)