alsa sample rate跟踪 <4>
接下来,要看看open流程中都往pcm上挂了哪些东东。
aplay的main函数中调用snd_pcm_open函数,并传入了一个snd_pcm_t指针handle的地址:
static snd_pcm_t *handle; err = snd_pcm_open(&handle, pcm_name, stream, open_mode);
这个时候handle还是干净的。
snd_pcm_open中将snd_pcm_t指针的地址直接传给了函数snd_pcm_open_noupdate:
return snd_pcm_open_noupdate(pcmp, snd_config, name, stream, mode, 0);
snd_pcm_open_noupdate中将snd_pcm_t指针的地址直接传给了函数snd_pcm_open_conf:
err = snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode);
snd_pcm_open_conf中将snd_pcm_t指针的地址直接传给了函数_snd_pcm_plug_open:
// 此处 open_func 为_snd_pcm_plug_open函数 err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
_snd_pcm_plug_open中定义了一个snd_pcm_t指针,并将其地址传给了函数snd_pcm_open_slave:
snd_pcm_t *spcm; err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
snd_pcm_open_slave中将snd_pcm_t指针的地址直接传给了函数snd_pcm_open_named_slave:
return snd_pcm_open_named_slave(pcmp, NULL, root, conf, stream, mode, parent_conf);
snd_pcm_open_named_slave中将snd_pcm_t指针的地址直接传给了函数snd_pcm_open_noupdate:
return snd_pcm_open_noupdate(pcmp, root, str, stream, mode, hop + 1);
snd_pcm_open_noupdate中将snd_pcm_t指针的地址直接传给了函数snd_pcm_open_conf:
err = snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode);
snd_pcm_open_conf中将snd_pcm_t指针的地址直接传给了函数_snd_pcm_dmix_open:
// 此处 open_func 为_snd_pcm_dmix_open函数 err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
_snd_pcm_dmix_open中处理:
定义slave_params结构体:
struct slave_params params;
初始化slave_params结构体:
/* the default settings, it might be invalid for some hardware */ params.format = SND_PCM_FORMAT_S16; params.rate = 48000; // 原来这儿有个48000,怪不得asound.conf是否指定rate 48000都会调用rate plug。 params.channels = 2; params.period_time = -1; params.buffer_time = -1; bsize = psize = -1; params.periods = 3;
将snd_pcm_t指针的地址和params传给了函数snd_pcm_dmix_open:
err = snd_pcm_dmix_open(pcmp, name, &dopen, ¶ms, root, sconf, stream, mode);
snd_pcm_dmix_open函数中相关处理:
首先定义:
snd_pcm_t *pcm = NULL, *spcm = NULL; snd_pcm_direct_t *dmix = NULL;
为dmix分配空间:
dmix = calloc(1, sizeof(snd_pcm_direct_t));
对dmix成员进行赋值:
dmix->ipc_key = opts->ipc_key; dmix->ipc_perm = opts->ipc_perm; dmix->ipc_gid = opts->ipc_gid; dmix->semid = -1; dmix->shmid = -1;
给pcm分配空间并初始化:
ret = snd_pcm_new(&pcm, dmix->type = SND_PCM_TYPE_DMIX, name, stream, mode);
将snd_pcm_dmix_ops赋值给pcm的ops,并将dmix赋值给pcm的private_data:
pcm->ops = &snd_pcm_dmix_ops; pcm->fast_ops = &snd_pcm_dmix_fast_ops; pcm->private_data = dmix;
初始化dmix另外一些成员:
dmix->state = SND_PCM_STATE_OPEN; dmix->slowptr = opts->slowptr; dmix->max_periods = opts->max_periods; dmix->sync_ptr = snd_pcm_dmix_sync_ptr;
以spcm为参数调用snd_pcm_open_slave:
ret = snd_pcm_open_slave(&spcm, root, sconf, stream, mode | SND_PCM_NONBLOCK, NULL);
snd_pcm_open_slave的调用过程见上面_snd_pcm_plug_open的分析。
最终调用到了下面这一步:
snd_pcm_open_conf中将snd_pcm_t指针的地址直接传给了函数_snd_pcm_hw_open:
// 此处 open_func 为_snd_pcm_hw_open函数 err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
_snd_pcm_hw_open中将snd_pcm_t指针的地址直接传给了函数snd_pcm_hw_open:
err = snd_pcm_hw_open(pcmp, name, card, device, subdevice, stream, mode | (nonblock ? SND_PCM_NONBLOCK : 0), 0, sync_ptr_ioctl);
snd_pcm_hw_open中的处理:
调用snd_open_device打开设备:(snd_open_device中调用open实现设备打开)
fd = snd_open_device(filename, fmode); // 这儿的filename是dev目录下的设备文件,如:/dev/snd/pcmC1D1p
将snd_pcm_t指针的地址直接传给了函数snd_pcm_hw_open_fd:
return snd_pcm_hw_open_fd(pcmp, name, fd, 0, sync_ptr_ioctl);
snd_pcm_hw_open_fd中的处理:
定义:
snd_pcm_t *pcm = NULL; snd_pcm_hw_t *hw = NULL;
通过ioctl对fd进行一些操作。
为hw分配空间:
hw = calloc(1, sizeof(snd_pcm_hw_t));
初始化hw成员:
hw->version = ver; hw->card = info.card; hw->device = info.device; hw->subdevice = info.subdevice; hw->fd = fd; hw->sync_ptr_ioctl = sync_ptr_ioctl; /* no restriction */ hw->format = SND_PCM_FORMAT_UNKNOWN; hw->rate = 0; hw->channels = 0;
给pcm分配空间并初始化:
ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode);
将snd_pcm_hw_ops赋值给pcm的ops,将hw赋值给pcm的private_data:
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->monotonic = monotonic;
将pcm赋值给传入的snd_pcm_t指针的地址:
*pcmp = pcm;
从snd_pcm_open_slave出来。
回到snd_pcm_dmix_open:
调用snd_pcm_direct_initialize_slave函数进行初始化:
// params是_snd_pcm_dmix_open中定义并初始化的结构体 ret = snd_pcm_direct_initialize_slave(dmix, spcm, params);
函数snd_pcm_direct_initialize_slave的注释:
/* * this function initializes hardware and starts playback operation with * no stop threshold (it operates all time without xrun checking) * also, the driver silences the unused ring buffer areas for us */
snd_pcm_direct_initialize_slave中设置了hw_params的各种参数,然后调用了snd_pcm_hw_params函数:
ret = snd_pcm_hw_params(spcm, hw_params);
将hw_params的参数保存到snd_pcm_dmix_open的dmix中:
/* store some hw_params values to shared info */ dmix->shmptr->hw.format = snd_mask_value(hw_param_mask(hw_params, SND_PCM_HW_PARAM_FORMAT)); dmix->shmptr->hw.rate = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_RATE); dmix->shmptr->hw.buffer_size = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_BUFFER_SIZE); dmix->shmptr->hw.buffer_time = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_BUFFER_TIME); dmix->shmptr->hw.period_size = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIOD_SIZE); dmix->shmptr->hw.period_time = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIOD_TIME); dmix->shmptr->hw.periods = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIODS);
然后调用了snd_pcm_sw_params函数:
ret = snd_pcm_sw_params(spcm, sw_params);
接下来调用了snd_pcm_start:
ret = snd_pcm_start(spcm);
从snd_pcm_direct_initialize_slave出来。
回到snd_pcm_dmix_open:
将spcm赋值给dmix:
dmix->spcm = spcm;
给pcm的部分成员赋值:
pcm->poll_fd = dmix->poll_fd; pcm->poll_events = POLLIN; /* it's different than other plugins */ pcm->mmap_rw = 1;
将pcm赋值给传入的snd_pcm_t指针的地址:
*pcmp = pcm;
从snd_pcm_open_slave出来。
回到_snd_pcm_plug_open:
_snd_pcm_plug_open中将snd_pcm_t指针的地址和spcm传给了函数snd_pcm_plug_open:
err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, rate_converter, route_policy, ttable, ssize, cused, sused, spcm, 1);
snd_pcm_plug_open函数中定义了一个snd_pcm_plug_t指针,并分配了snd_pcm_plug_t对象。
然后初始化snd_pcm_plug_t对象,并将该对象赋值给snd_pcm_t指针的private_data。
并将snd_pcm_plug_ops赋值给snd_pcm_t指针的ops成员。
代码列出来,可能更明了:
/** * \brief Creates a new Plug PCM * \param pcmp Returns created PCM handle * \param name Name of PCM * \param sformat Slave (destination) format * \param slave Slave PCM handle * \param close_slave When set, the slave PCM handle is closed with copy PCM * \retval zero on success otherwise a negative error code * \warning Using of this function might be dangerous in the sense * of compatibility reasons. The prototype might be freely * changed in future. */ int snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, int schannels, int srate, const snd_config_t *rate_converter, enum snd_pcm_plug_route_policy route_policy, snd_pcm_route_ttable_entry_t *ttable, unsigned int tt_ssize, unsigned int tt_cused, unsigned int tt_sused, snd_pcm_t *slave, int close_slave) { snd_pcm_t *pcm; // 定义指针 snd_pcm_plug_t *plug; int err; assert(pcmp && slave); // 分配空间 plug = calloc(1, sizeof(snd_pcm_plug_t)); if (!plug) return -ENOMEM; plug->sformat = sformat; plug->schannels = schannels; plug->srate = srate; plug->rate_converter = rate_converter; // 将slave pcm赋值过来 plug->gen.slave = plug->req_slave = slave; plug->gen.close_slave = close_slave; plug->route_policy = route_policy; plug->ttable = ttable; plug->tt_ssize = tt_ssize; plug->tt_cused = tt_cused; plug->tt_sused = tt_sused; err = snd_pcm_new(&pcm, SND_PCM_TYPE_PLUG, name, slave->stream, slave->mode); if (err < 0) { free(plug); return err; } pcm->ops = &snd_pcm_plug_ops; pcm->fast_ops = slave->fast_ops; pcm->fast_op_arg = slave->fast_op_arg; // 将plug赋值到pcm的private_data pcm->private_data = plug; pcm->poll_fd = slave->poll_fd; pcm->poll_events = slave->poll_events; pcm->mmap_shadow = 1; pcm->monotonic = slave->monotonic; snd_pcm_link_hw_ptr(pcm, slave); snd_pcm_link_appl_ptr(pcm, slave); *pcmp = pcm; return 0; }
小结一下。
aplay传入的snd_pcm_t指针地址在snd_pcm_plug_open中被赋值
其中:
pcm->ops = &snd_pcm_plug_ops; pcm->fast_ops = slave->fast_ops; pcm->private_data = plug; plug->gen.slave = plug->req_slave = slave;
slave在snd_pcm_dmix_open中被赋值:
pcm->ops = &snd_pcm_dmix_ops; pcm->fast_ops = &snd_pcm_dmix_fast_ops; pcm->private_data = dmix; dmix->spcm = spcm;
spcm在snd_pcm_hw_open_fd中被赋值:
pcm->ops = &snd_pcm_hw_ops; pcm->fast_ops = &snd_pcm_hw_fast_ops; pcm->private_data = hw; pcm->poll_fd = fd; hw->fd = fd;
其中fd是打开的设备文件句柄。
结构体的赋值已经基本清晰。
下面来看看snd_pcm_hw_params的函数调用关系:
snd_pcm_hw_params
/ \
/ \
_snd_pcm_hw_params snd_pcm_prepare
/
/
snd_pcm_plug_hw_params
/ \
/ \
snd_pcm_plug_insert_plugins _snd_pcm_hw_params
/ /
/ /
snd_pcm_plug_change_rate snd_pcm_rate_hw_params
/ /
/ /
snd_pcm_rate_open snd_pcm_hw_params_slave
/
/
snd_pcm_generic_hw_params
/
/
_snd_pcm_hw_params
/
/
snd_pcm_direct_hw_params
开发linux audio driver的同学应该很熟悉hw:x,x设备。
可以aplay -D hw:x,x xxx.wav来指定使用hw:x,x设备播放音频。
这样指定之后,alsa lib将不会对音频进行处理,也就是说除了hw之外,其他的plug都不会被使用。
在open的时候,只调用了_snd_pcm_hw_open。
在set params时,也只调用了snd_pcm_hw_hw_params。
这样的话,声音数据直接扔给了audio driver,alsa lib中不会对采样率,声道,格式等进行处理。