alsa sample rate跟踪 <2>

  • Post author:
  • Post category:其他


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传入。

真相大白。



版权声明:本文为njuitjf原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。