Chinaunix首页 | 论坛 | 博客
  • 博客访问: 512995
  • 博文数量: 92
  • 博客积分: 3146
  • 博客等级: 中校
  • 技术积分: 2314
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-27 10:20
文章分类

全部博文(92)

文章存档

2014年(3)

2013年(17)

2012年(16)

2011年(22)

2010年(34)

分类: 嵌入式

2011-07-27 18:57:15

之前写过一个音频驱动Codec分析:ALSACodec分析,那时忽略了阐述最基本的概念。要了解一个东西,首先要明白它是什么它起到什么作用,然后才会更好对它的工作流程更好的分析。所以这里提一下:

Codec:音频芯片的控制,比如静音、打开(关闭)ADCDAC)、设置ADCDAC)的增益、耳机模式的检测等操作。

I2S:数字音频接口,用于CPUCodec之间的数字音频流raw data的传输。每当有playbackrecord操作时,snd_soc_dai_ops. prepare()会被调用,启动I2S总线。

PCM:我不知道为什么会取这个模块名,它其实是定义DMA操作的,用于将音频数据通过DMA传到I2S控制器的FIFO中。

音频数据流向:RAM--(dma)-->I2S Controller FIFO--(i2s)-->Codec-->Speaker/Headset

 

PCM模块初始化

调用snd_soc_register_platform()ALSACore注册一个snd_soc_platform结构体。

  1. struct snd_soc_platform s3c_soc_platform = {
  2.        .name = " s3c-pcm-audio",
  3.        .pcm_ops = & s3c_pcm_ops,
  4.        .pcm_new = s3c_pcm_new,
  5.        .pcm_free = s3c_pcm_free_dma_buffers,
  6.        .suspend = s3c_pcm_suspend,
  7.        .resume = s3c_pcm_resume,
  8. };
struct snd_soc_platform s3c_soc_platform = { .name = " s3c-pcm-audio", .pcm_ops = & s3c_pcm_ops, .pcm_new = s3c_pcm_new, .pcm_free = s3c_pcm_free_dma_buffers, .suspend = s3c_pcm_suspend, .resume = s3c_pcm_resume, };

成员pcm_new需要调用dma_alloc_writecombine()DMA分配一块write-combining的内存空间,并把这块缓冲区的相关信息保存到substream->dma_buffer,相当于构造函数。pcm_free则相反。

这些成员函数都还算简单,看看代码即可以理解其流程。

 

snd_pcm_ops

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

  1. struct snd_pcm_ops {
  2.        int (*open)(struct snd_pcm_substream *substream);
  3.        int (*close)(struct snd_pcm_substream *substream);
  4.        int (*ioctl)(struct snd_pcm_substream * substream,
  5.                    unsigned int cmd, void *arg);
  6.        int (*hw_params)(struct snd_pcm_substream *substream,
  7.                       struct snd_pcm_hw_params *params);
  8.        int (*hw_free)(struct snd_pcm_substream *substream);
  9.        int (*prepare)(struct snd_pcm_substream *substream);
  10.        int (*trigger)(struct snd_pcm_substream *substream, int cmd);
  11.        snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
  12.        int (*copy)(struct snd_pcm_substream *substream, int channel,
  13.                   snd_pcm_uframes_t pos,
  14.                   void __user *buf, snd_pcm_uframes_t count);
  15.        int (*silence)(struct snd_pcm_substream *substream, int channel,
  16.                      snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
  17.        struct page *(*page)(struct snd_pcm_substream *substream,
  18.                           unsigned long offset);
  19.        int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
  20.        int (*ack)(struct snd_pcm_substream *substream);
  21. };
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); };

我们主要实现openclosehw_paramshw_freepreparetrigger接口。

 

open函数

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

其一般实现如下:

  1. static int s3c_pcm_open(struct snd_pcm_substream *substream)
  2. {
  3.        struct snd_soc_pcm_runtime *rtd = substream->private_data;
  4.        struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
  5.        struct snd_pcm_runtime *runtime = substream->runtime;
  6.        struct audio_stream_a *s = runtime->private_data;
  7.        int ret;

  8.        if (!cpu_dai->active) {
  9.               audio_dma_request(&s[0], audio_dma_callback); //为playback stream分配DMA
  10.               audio_dma_request(&s[1], audio_dma_callback); //为capture stream分配DMA
  11.        }
  12.        
  13.        //设定runtime硬件参数
  14.        snd_soc_set_runtime_hwparams(substream, &loon_pcm_hardware);

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

  18.        return ret;
  19. }
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, &loon_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; }

 

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

 
  1. static const struct snd_pcm_hardware s3c_pcm_hardware = {
  2.        .info = SNDRV_PCM_INFO_INTERLEAVED |
  3.                                 SNDRV_PCM_INFO_BLOCK_TRANSFER |
  4.                                 SNDRV_PCM_INFO_MMAP |
  5.                                 SNDRV_PCM_INFO_MMAP_VALID |
  6.                                 SNDRV_PCM_INFO_PAUSE |
  7.                                 SNDRV_PCM_INFO_RESUME,
  8.        .formats = SNDRV_PCM_FMTBIT_S16_LE |
  9.                                 SNDRV_PCM_FMTBIT_U16_LE |
  10.                                 SNDRV_PCM_FMTBIT_U8 |
  11.                                 SNDRV_PCM_FMTBIT_S8,
  12.        .channels_min = 2,
  13.        .channels_max = 2,
  14.        .buffer_bytes_max = 128*1024,
  15.        .period_bytes_min = PAGE_SIZE,
  16.        .period_bytes_max = PAGE_SIZE*2,
  17.        .periods_min = 2,
  18.        .periods_max = 128,
  19.        .fifo_size = 32,
  20. };
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.

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

 

关于DMA的中断处理

另外留意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模块中实现这个中断。

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

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

  13.        spin_lock(&s->dma_lock);
  14.        if (s->periods > 0)
  15.               s->periods--;

  16.        audio_process_dma(s); //dma启动
  17.        spin_unlock(&s->dma_lock);
  18. }
/* * 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); }

 

hw_params函数

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

  1. static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
  2.                            struct snd_pcm_hw_params *params)
  3. {
  4.        struct snd_pcm_runtime *runtime = substream->runtime;
  5.        int err = 0;

  6.        snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
  7.        runtime->dma_bytes = params_buffer_bytes(params);
  8.        return err;
  9. }
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_freehw_params的相反操作,调用snd_pcm_set_runtime_buffer(substream, NULL)即可。

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

 

prepare函数

pcm“准备好了”调用该函数。在这里根据channelsbuffer_bytes等来设定DMA传输参数,跟具体硬件平台相关。

注:每次调用snd_pcm_prepare()的时候均会调用prepare函数。

 

trigger函数

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

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

  5.        spin_lock(&prtd->lock);
  6.  
  7.        switch (cmd) {
  8.        case SNDRV_PCM_TRIGGER_START:
  9.        case SNDRV_PCM_TRIGGER_RESUME:
  10.        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  11.               prtd->state |= ST_RUNNING;
  12.               dma_ctrl(prtd->params->channel, DMAOP_START); //DMA开启
  13.               break;

  14.        case SNDRV_PCM_TRIGGER_STOP:
  15.        case SNDRV_PCM_TRIGGER_SUSPEND:
  16.        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  17.               prtd->state &= ~ST_RUNNING;
  18.               dma_ctrl(prtd->params->channel, DMAOP_STOP); //DMA停止
  19.               break;

  20.        default:
  21.               ret = -EINVAL;
  22.               break;
  23.        }

  24.        spin_unlock(&prtd->lock);

  25.        return ret;
  26. }
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

 

pointer函数

static snd_pcm_uframes_t wmt_pcm_pointer(struct snd_pcm_substream *substream)

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

 

snd_pcm_runtime

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


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

阅读(2064) | 评论(0) | 转发(0) |
0

上一篇:工作队列work queue

下一篇:ALSA之codec分析

给主人留下些什么吧!~~