zoukankan      html  css  js  c++  java
  • linux alsa pcm(此pcm非硬件pcm接口)

    转:https://blog.csdn.net/crycheng/article/details/7095899

    CODEC :音频芯片的控制,比如静音、打开(关闭)ADC(DAC)、设置ADC(DAC)的增益、耳机模式的检测等操作。
    I2S   :数字音频接口,用于CPU和Codec之间的数字音频流raw data的传输。每当有playback或record操作时,snd_soc_dai_ops.prepare()会被调用,启动I2S总线。
    PCM   :我不知道为什么会取这个模块名,它其实是定义DMA操作的,用于将音频数据通过DMA传到I2S控制器的FIFO中。

    这里的PCM实际是就是更新和管理音频数据流的地址,分配DMA等等,将RAM中存放的音频数据的地址传给I2S,不是PCM协议。
    音频数据流向:
         | DMA |                     | I2S/PCM/AC97 |
    RAM --------> I2SControllerFIFO -----------------> CODEC ----> SPK/Headset


    PCM模块初始化:

    [html] view plain copy
     
    1. struct snd_soc_platform rk29_soc_platform = {  
    2.     .name       = "rockchip-audio",  
    3.     .pcm_ops    = &rockchip_pcm_ops,  
    4.     .pcm_new    = rockchip_pcm_new,  
    5.     .pcm_free   = rockchip_pcm_free_dma_buffers,  
    6. };  
    7. EXPORT_SYMBOL_GPL(rk29_soc_platform);  
    8.   
    9. static int __init rockchip_soc_platform_init(void)  
    10. {  
    11.         DBG("Enter::%s, %d ", __FUNCTION__, __LINE__);  
    12.     return snd_soc_register_platform(&rk29_soc_platform);  
    13. }  
    14. module_init(rockchip_soc_platform_init);  
    15.   
    16. static void __exit rockchip_soc_platform_exit(void)  
    17. {  
    18.     snd_soc_unregister_platform(&rk29_soc_platform);  
    19. }  

    调用snd_soc_register_platform()向ALSA core注册一个snd_soc_platform结构体。


    成员pcm_new需要调用dma_alloc_writecombine()给DMA分配一块write-combining的内存空间,并把这块缓冲区的相关信息保存到substream->dma_buffer中,相当于构造函数。pcm_free则相反。这些成员函数都还算简单,看看代码即可以理解其流程。

    snd_pcm_ops

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

    [html] view plain copy
     
    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. };  


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

    open

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

    [html] view plain copy
     
    1. static int rockchip_pcm_open(struct snd_pcm_substream *substream)  
    2. {  
    3.     struct snd_pcm_runtime *runtime = substream->runtime;  
    4.     struct rockchip_runtime_data *prtd;  
    5.   
    6.     DBG("Enter::%s----%d ",__FUNCTION__,__LINE__);  
    7.   
    8.     snd_soc_set_runtime_hwparams(substream, &rockchip_pcm_hardware);  
    9.   
    10.     prtd = kzalloc(sizeof(struct rockchip_runtime_data), GFP_KERNEL);  
    11.     if (prtd == NULL)  
    12.         return -ENOMEM;  
    13.   
    14.     spin_lock_init(&prtd->lock);  
    15.   
    16.     runtime->private_data = prtd;  
    17.     return 0;  
    18. }  


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

    [html] view plain copy
     
    1. int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,  
    2.     const struct snd_pcm_hardware *hw)  
    3. {  
    4.     struct snd_pcm_runtime *runtime = substream->runtime;  
    5.     runtime->hw.info = hw->info;  
    6.     runtime->hw.formats = hw->formats;  
    7.     runtime->hw.period_bytes_min = hw->period_bytes_min;  
    8.     runtime->hw.period_bytes_max = hw->period_bytes_max;  
    9.     runtime->hw.periods_min = hw->periods_min;  
    10.     runtime->hw.periods_max = hw->periods_max;  
    11.     runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;  
    12.     runtime->hw.fifo_size = hw->fifo_size;  
    13.     return 0;  
    14. }  

    关于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。

    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)来分配。

    关于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模块中实现这个中断。

    [html] view plain copy
     
    1. void rockchip_pcm_dma_irq(s32 ch, void *data)  
    2. {      
    3.         struct snd_pcm_substream *substream = data;  
    4.     struct rockchip_runtime_data *prtd;  
    5.     unsigned long flags;  
    6.       
    7.     DBG("Enter::%s----%d ",__FUNCTION__,__LINE__);  
    8.   
    9.     prtd = substream->runtime->private_data;  
    10.     if (substream)  
    11.         snd_pcm_period_elapsed(substream);  
    12.     spin_lock(&prtd->lock);  
    13.     prtd->dma_loaded--;  
    14.     if (prtd->state & ST_RUNNING) {  
    15.         rockchip_pcm_enqueue(substream);  
    16.     }  
    17.         spin_unlock(&prtd->lock);  
    18.         local_irq_save(flags);  
    19.     if (prtd->state & ST_RUNNING) {  
    20.         if (prtd->dma_loaded) {  
    21.             if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)  
    22.                 audio_start_dma(substream, DMA_MODE_WRITE);  
    23.             else  
    24.                 audio_start_dma(substream, DMA_MODE_READ);  
    25.         }  
    26.     }  
    27.     local_irq_restore(flags);     
    28. }  

    prepare

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

    trigger

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

    [html] view plain copy
     
    1. static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd)  
    2. {  
    3.     struct rockchip_runtime_data *prtd = substream->runtime->private_data;  
    4.     int ret = 0;  
    5.     /**************add by qiuen for volume*****/  
    6.     struct snd_soc_pcm_runtime *rtd = substream->private_data;  
    7.     struct snd_soc_dai *pCodec_dai = rtd->dai->codec_dai;  
    8.     int vol = 0;  
    9.     int streamType = 0;  
    10.       
    11.     DBG("Enter::%s----%d ",__FUNCTION__,__LINE__);  
    12.       
    13.     if(cmd==SNDRV_PCM_TRIGGER_VOLUME){  
    14.         vol = substream->number % 100;  
    15.         streamType = (substream->number / 100) % 100;  
    16.         DBG("enter:vol=%d,streamType=%d ",vol,streamType);  
    17.         if(pCodec_dai->ops->set_volume)  
    18.             pCodec_dai->ops->set_volume(streamType, vol);  
    19.     }  
    20.     /****************************************************/  
    21.     spin_lock(&prtd->lock);  
    22.   
    23.     switch (cmd) {  
    24.     case SNDRV_PCM_TRIGGER_START:  
    25.             DBG(" START  ");  
    26.         prtd->state |= ST_RUNNING;  
    27.         rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_START);  
    28.         /*  
    29.         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {  
    30.             audio_start_dma(substream, DMA_MODE_WRITE);  
    31.         } else {  
    32.             audio_start_dma(substream, DMA_MODE_READ);  
    33.         }  
    34.         */  
    35. #ifdef CONFIG_ANDROID_POWER          
    36.         android_lock_suspend(&audio_lock);  
    37.         DBG("%s::start audio , lock system suspend " , __func__ );  
    38. #endif        
    39.         break;  
    40.     case SNDRV_PCM_TRIGGER_RESUME:  
    41.         DBG(" RESUME  ");  
    42.         break;  
    43.     case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:  
    44.         DBG(" RESTART  ");  
    45.         break;  
    46.   
    47.     case SNDRV_PCM_TRIGGER_STOP:  
    48.     case SNDRV_PCM_TRIGGER_SUSPEND:  
    49.     case SNDRV_PCM_TRIGGER_PAUSE_PUSH:  
    50.         DBG(" STOPS  ");  
    51.         prtd->state &= ~ST_RUNNING;  
    52.         rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_STOP);  
    53.         //disable_dma(prtd->params->channel);  
    54. #ifdef CONFIG_ANDROID_POWER          
    55.         android_unlock_suspend(&audio_lock );  
    56.         DBG("%s::stop audio , unlock system suspend " , __func__ );  
    57. #endif  
    58.           
    59.         break;  
    60.     default:  
    61.         ret = -EINVAL;  
    62.         break;  
    63.     }  
    64.   
    65.     spin_unlock(&prtd->lock);  
    66.     return ret;  
    67. }  

    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_runtime是pcm运行时的信息。当打开一个pcm子流时,pcm运行时实例就会分配给这个子流。它拥有很多多种信息:hw_params和sw_params配置拷贝,缓冲区指针,mmap记录,自旋锁等。snd_pcm_runtime对于驱动程序操作集函数是只读的,仅pcm中间层可以改变或更新这些信息。

  • 相关阅读:
    原生小程序音频播放
    Vue定义全局过滤器filter
    系统扩展性之引入外部包
    oracle update join
    OAuth2
    oracle pl/sql
    MySQL同步工具otter的使用介绍(一)
    python批量安装apk
    mac brew安装redis
    antd 修改Modal的底部按钮颜色
  • 原文地址:https://www.cnblogs.com/newjiang/p/9026071.html
Copyright © 2011-2022 走看看