alsa sample rate跟踪 <4>

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中不会对采样率,声道,格式等进行处理。

你可能感兴趣的:(alsa sample rate跟踪 <4>)