alsa sample rate跟踪 <2>
接上篇,下面要调查:
1、snd_pcm_hw_hw_params,snd_pcm_plug_hw_params,snd_pcm_rate_hw_params,snd_pcm_generic_hw_params和snd_pcm_direct_hw_params是如何被调用的。
2、以及snd_pcm_hw_params和几处单独的_snd_pcm_hw_params是如何被调用的。
先看看第一话题。
snd_pcm_hw_hw_params:
经过前面分析可知,snd_pcm_hw_hw_params赋值给了snd_pcm_hw_ops结构体。
函数snd_pcm_hw_open_fd中使用了snd_pcm_hw_ops结构体。
/**
* \brief Creates a new hw PCM
* \param pcmp Returns created PCM handle
* \param name Name of PCM
* \param fd File descriptor
* \param mmap_emulation Obsoleted parameter
* \param sync_ptr_ioctl Boolean flag for sync_ptr ioctl
* \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_hw_open_fd(snd_pcm_t **pcmp, const char *name,
int fd, int mmap_emulation ATTRIBUTE_UNUSED,
int sync_ptr_ioctl)
{
...
snd_pcm_t *pcm = NULL;
...
pcm->ops = &snd_pcm_hw_ops;
...
*pcmp = pcm;
return 0;
}
搜索alsa lib代码,发现有两处调用了snd_pcm_hw_open_fd函数,函数snd_pcm_hw_open和函数snd_pcm_direct_open_secondary_client。
经确认,在我的测试过程中,是函数snd_pcm_hw_open调用了函数snd_pcm_hw_open_fd。
按照以前对alsa lib的理解,snd_pcm_XX_open应该在snd_pcm_open_conf中被调用,snd_pcm_hw_open当然也不例外。
有以下调用链:
snd_pcm_open -》 snd_pcm_open_noupdate -》 snd_pcm_open_conf
snd_pcm_open是在aplay中被调用的。
snd_pcm_plug_hw_params:
snd_pcm_plug_hw_params被赋值给了结构体snd_pcm_plug_ops。
static const snd_pcm_ops_t snd_pcm_plug_ops = {
...
.hw_params = snd_pcm_plug_hw_params,
...
}
函数snd_pcm_plug_open中使用了结构体snd_pcm_plug_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;
...
pcm->ops = &snd_pcm_plug_ops;
...
*pcmp = pcm;
return 0;
}
比snd_pcm_hw_hw_params简单些,少了一层。
函数snd_pcm_plug_open也是在函数snd_pcm_open_conf中被调用。
snd_pcm_rate_hw_params&snd_pcm_generic_hw_params:
snd_pcm_rate_hw_params与snd_pcm_plug_hw_params类似。
被赋值给结构体snd_pcm_rate_ops,函数snd_pcm_rate_open中将结构体赋值给pcm。
看了下snd_pcm_rate_hw_params的实现,发现snd_pcm_generic_hw_params的调用也被包含其中:
static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_pcm_t *slave = rate->gen.slave;
snd_pcm_rate_side_info_t *sinfo, *cinfo;
unsigned int channels, cwidth, swidth, chn;
int err = snd_pcm_hw_params_slave(pcm, params,
snd_pcm_rate_hw_refine_cchange,
snd_pcm_rate_hw_refine_sprepare,
snd_pcm_rate_hw_refine_schange,
snd_pcm_generic_hw_params);
...
}
snd_pcm_hw_params_slave的实现:
int snd_pcm_hw_params_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
int (*cchange)(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *sparams),
int (*sprepare)(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params),
int (*schange)(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *sparams),
int (*sparams)(snd_pcm_t *pcm,
snd_pcm_hw_params_t *sparams))
{
snd_pcm_hw_params_t slave_params;
int err;
err = sprepare(pcm, &slave_params);
assert(err >= 0);
err = schange(pcm, params, &slave_params);
assert(err >= 0);
err = sparams(pcm, &slave_params);
if (err < 0)
cchange(pcm, params, &slave_params);
return err;
}
snd_pcm_direct_hw_params:
使用snd_pcm_direct_hw_params的地方比较多,它分别被赋值给了snd_pcm_dmix_ops,snd_pcm_dshare_ops和snd_pcm_dsnoop_ops三个结构体。
经过分析,我的测试中使用的是snd_pcm_dmix_ops。
函数snd_pcm_dmix_open中将snd_pcm_dmix_ops赋值给了pcm->ops:
pcm->ops = &snd_pcm_dmix_ops;
snd_pcm_dmix_open也是在 函数snd_pcm_open_conf中被调用。
通过在函数snd_pcm_open_conf中加log发现,我的测试过程中依次调用了下面三个open函数:
_snd_pcm_plug_open
_snd_pcm_dmix_open
_snd_pcm_hw_open
上面三个函数又分别调用了以下三个函数:
snd_pcm_plug_open
snd_pcm_dmix_open
snd_pcm_hw_open
这三个函数之所以被调用,是因为我们的asound.conf中有以下内容:
pcm.dshare_1 {
type dmix
ipc_key 2048
slave {
pcm "hw:0,0"
channels 2
format S16_LE
rate 48000
}
}
pcm.mydev {
type plug
slave {
pcm "dshare_1"
}
}
因为有plug,所以snd_pcm_plug_open被调用。
因为有dmix,所以snd_pcm_dmix_open被调用。
播放声音,最终要打开硬件的声卡设备,所以snd_pcm_hw_open最终被调用。
接着看第二个问题:
通过log发现,snd_pcm_hw_params被调用过两次:
第一次调用在:pcm_direct.c中的snd_pcm_direct_initialize_slave函数。
第二次调用在:aplay.c中的set_params函数。
第二次调用是应用程序主动调用,就不多看了。
看看第一次。
有个插曲,开始打了点log有点迷惑,有以下log:
snd_pcm_open_conf – open_name=_snd_pcm_plug_open
snd_pcm_open_conf – open_name=_snd_pcm_dmix_open
snd_pcm_open_conf – open_name=_snd_pcm_hw_open
…
snd_pcm_dmix_open(pcm_dmix.c) – calling snd_pcm_direct_initialize_slave
snd_pcm_direct_initialize_slave(pcm_direct.c) – calling snd_pcm_hw_params
开始的理解是某个地方通过层层调用,调用到snd_pcm_open_conf,有依次调用了上述三个open函数。
这样就产生了两个疑问:
1、如果是依次调用,snd_pcm_dmix_open中的log为什么会出现在snd_pcm_open_conf – open_name=_snd_pcm_hw_open之后?
2、从snd_pcm_open开始,到snd_pcm_open_conf,怎么也找不到while或for,依次调用是怎么产生的?
后来才明白,不是依次调用,而是层层调用。
看看调用关系:(只看我们关心的函数)
snd_pcm_open
/ \
/ \
snd_config_update snd_pcm_open_noupdate
\
\
snd_pcm_open_conf
\
\
_snd_pcm_plug_open
/ \
/ \
snd_pcm_open_slave snd_pcm_plug_open
/
/
snd_pcm_open_named_slave
/ \
/ \
snd_pcm_open_noupdate snd_pcm_open_conf
\
\
_snd_pcm_dmix_open
\
\
snd_pcm_dmix_open
/ \
/ \
snd_pcm_direct_initialize_slave snd_pcm_open_slave
/ / \ \
/ / \ \
snd_pcm_hw_params_any / snd_pcm_hw_params snd_pcm_open_named_slave
/ / \
/ / \
snd_pcm_hw_params_set_rate_near snd_pcm_open_noupdate snd_pcm_open_conf
\
\
_snd_pcm_hw_open
/ \
/ \
snd_open_device snd_pcm_hw_open_fd
snd_pcm_hw_params调用的地方找到了。
从上面的log可知,第一次调用snd_pcm_hw_params时,sample rate为48000,这个48000是怎么来的呢?
snd_pcm_hw_params的params是函数snd_pcm_direct_initialize_slave传入的参数hw_params。
中函数snd_pcm_direct_initialize_slave中,首先调用snd_pcm_hw_params_any将hw_params的rate初始化成8000。
然后调用snd_pcm_hw_params_set_rate_near将hw_params的rate中的rate设置为与slave params一致。
slave params既是snd_pcm_direct_initialize_slave的传入参数params。
要寻找48000,那就顺着snd_pcm_direct_initialize_slave往上找。
终于找到了,在函数_snd_pcm_dmix_open中定义了slave_params结构体,并对其进行初始化。
其中将rate初始化为48000,然后调用snd_pcm_dmix_open时将slave params传入。
真相大白。