snd_pcm_ops

ALSA驱动主要是实现snd_pcm_ops中的相关函数。

1. snd_pcm_ops

接着我们看一下snd_pcm_ops结构体,该结构体的操作函数集的实现是本模块的主体。

struct snd_pcm_ops {
       int (*open)(struct snd_pcm_substream *substream);
       int (*close)(struct snd_pcm_substream *substream);
       int (*ioctl)(struct snd_pcm_substream * substream,
                   unsigned int cmd, void *arg);
       int (*hw_params)(struct snd_pcm_substream *substream,
                      struct snd_pcm_hw_params *params);
       int (*hw_free)(struct snd_pcm_substream *substream);
       int (*prepare)(struct snd_pcm_substream *substream);
       int (*trigger)(struct snd_pcm_substream *substream, int cmd);
       snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
       int (*copy)(struct snd_pcm_substream *substream, int channel,
                  snd_pcm_uframes_t pos,
                  void __user *buf, snd_pcm_uframes_t count);
       int (*silence)(struct snd_pcm_substream *substream, int channel, 
                     snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
       struct page *(*page)(struct snd_pcm_substream *substream,
                          unsigned long offset);
       int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
       int (*ack)(struct snd_pcm_substream *substream);
};


我们主要实现open、close、hw_params、hw_free、prepare和trigger接口。

2. open

       open函数为PCM模块设定支持的传输模式、数据格式、通道数、period等参数,并为playback/capture stream分配相应的DMA通道。其一般实现如下:

static int s3c_pcm_open(struct snd_pcm_substream *substream)
{
       struct snd_soc_pcm_runtime *rtd = substream->private_data;
       struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
       struct snd_pcm_runtime *runtime = substream->runtime;
       struct audio_stream_a *s = runtime->private_data;
       int ret;

       if (!cpu_dai->active) {
              audio_dma_request(&s[0], audio_dma_callback); //为playback stream分配DMA
              audio_dma_request(&s[1], audio_dma_callback); //为capture stream分配DMA
       }
       
       //设定runtime硬件参数
       snd_soc_set_runtime_hwparams(substream, &s3c_pcm_hardware);

       /* Ensure that buffer size is a multiple of period size */
       ret = snd_pcm_hw_constraint_integer(runtime,
                             SNDRV_PCM_HW_PARAM_PERIODS);

       return ret;
}

     其中硬件参数要根据芯片的数据手册来定义,如:

static const struct snd_pcm_hardware s3c_pcm_hardware = {
       .info            = SNDRV_PCM_INFO_INTERLEAVED |
                                SNDRV_PCM_INFO_BLOCK_TRANSFER |
                                SNDRV_PCM_INFO_MMAP |
                                SNDRV_PCM_INFO_MMAP_VALID |
                                SNDRV_PCM_INFO_PAUSE |
                                SNDRV_PCM_INFO_RESUME,
       .formats         = SNDRV_PCM_FMTBIT_S16_LE |
                                SNDRV_PCM_FMTBIT_U16_LE |
                                SNDRV_PCM_FMTBIT_U8 |
                                SNDRV_PCM_FMTBIT_S8,
       .channels_min     = 2,
       .channels_max     = 2,
       .buffer_bytes_max = 128*1024,
       .period_bytes_min = PAGE_SIZE,
       .period_bytes_max = PAGE_SIZE*2,
       .periods_min      = 2,
       .periods_max      = 128,
       .fifo_size        = 32,
};

       关于peroid的概念有这样的描述:The “period” is a term that corresponds to a fragment in the OSS world. The period defines the size at which a PCM interrupt is generated. peroid的概念很重要,建议去alsa官网找相关详细说明了解一下。

       上层ALSA lib可以通过接口来获得这些参数的,如snd_pcm_hw_params_get_buffer_size_max()来取得buffer_bytes_max。


3. 关于DMA的中断处理

     另外留意open函数中的audio_dma_request(&s[0], audio_dma_callback);中的audio_dma_callback,这是dma的中断函数,这里以callback的形式存在,其实到dma的底层还是这样的形式:static irqreturn_t dma_irq_handler(int irq, void *dev_id),在DMA中断处理dma_irq_handler()中调用callback。这些跟具体硬件平台的DMA实现相关,如果没有类似的机制,那么还是要在pcm模块中实现这个中断。

/* 
 *  This is called when dma IRQ occurs at the end of each transmited block
 */
static void audio_dma_callback(void *data)
{
       struct audio_stream_a *s = data;    

       /* 
        * If we are getting a callback for an active stream then we inform
        * the PCM middle layer we've finished a period
        */
       if (s->active)
              snd_pcm_period_elapsed(s->stream);

       spin_lock(&s->dma_lock);
       if (s->periods > 0) 
              s->periods--;    

       audio_process_dma(s); //dma启动
       spin_unlock(&s->dma_lock);
}

4. hw_params

     hw_params函数为substream(每打开一个playback或capture,ALSA core均产生相应的一个substream)设定DMA的源(目的)地址,以及DMA缓冲区的大小。

static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
                           struct snd_pcm_hw_params *params)
{
       struct snd_pcm_runtime *runtime = substream->runtime;
       int err = 0;

       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
       runtime->dma_bytes = params_buffer_bytes(params);
       return err;
}


        hw_free是hw_params的相反操作,调用snd_pcm_set_runtime_buffer(substream, NULL)即可。
注:代码中的dma_buffer是DMA缓冲区,它通过4个字段定义:dma_area、dma_addr、dma_bytes和dma_private。其中dma_area是缓冲区逻辑地址,dma_addr是缓冲区的物理地址,dma_bytes是缓冲区的大小,dma_private是ALSA的DMA管理用到的。dma_buffer是在pcm_new()中初始化的;当然也可以把分配dma缓冲区的工作放到这部分来实现,但考虑到减少碎片,故还是在pcm_new中以最大size(即buffer_bytes_max)来分配。

5. prepare

       当pcm“准备好了”调用该函数。在这里根据channels、buffer_bytes等来设定DMA传输参数,跟具体硬件平台相关。注:每次调用snd_pcm_prepare()的时候均会调用prepare函数。


6. trigger

      当pcm开始、停止、暂停的时候都会调用trigger函数。

static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
       struct runtime_data *prtd = substream->runtime->private_data;
       int ret = 0;

       spin_lock(&prtd->lock);
 
       switch (cmd) {
       case SNDRV_PCM_TRIGGER_START:
       case SNDRV_PCM_TRIGGER_RESUME:
       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
              prtd->state |= ST_RUNNING;
              dma_ctrl(prtd->params->channel, DMAOP_START); //DMA开启
              break;

       case SNDRV_PCM_TRIGGER_STOP:
       case SNDRV_PCM_TRIGGER_SUSPEND:
       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
              prtd->state &= ~ST_RUNNING;
              dma_ctrl(prtd->params->channel, DMAOP_STOP); //DMA停止
              break;

       default:
              ret = -EINVAL;
              break;
       }

       spin_unlock(&prtd->lock);

       return ret;
}


      Trigger函数里面的操作应该是原子的,不要在调用这些操作时进入睡眠,trigger函数应尽量小,甚至仅仅是触发DMA。

7. pointer

static snd_pcm_uframes_t wmt_pcm_pointer(struct snd_pcm_substream *substream)
      PCM中间层通过调用这个函数来获取缓冲区的位置。一般情况下,在中断函数中调用snd_pcm_period_elapsed()或在pcm中间层更新buffer的时候调用它。然后pcm中间层会更新指针位置和计算缓冲区可用空间,唤醒那些在等待的线程。这个函数也是原子的。
 

8. snd_pcm_runtime


     我们会留意到ops各成员函数均需要取得一个snd_pcm_runtime结构体指针,这个指针可以通过substream->runtime来获得。snd_pcm_runtime是pcm运行时的信息。当打开一个pcm子流时,pcm运行时实例就会分配给这个子流。它拥有很多信息:hw_params和sw_params配置拷贝,缓冲区指针,mmap记录,自旋锁等。snd_pcm_runtime对于驱动程序操作集函数是只读的,仅pcm中间层可以改变或更新这些信息

转自:http://blog.csdn.net/sepnic/article/details/6146378

你可能感兴趣的:(snd_pcm_ops)