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\n", __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\n",__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\n",__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\n",__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\n",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 \n");  
  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\n" , __func__ );  
  38. #endif        
  39.         break;  
  40.     case SNDRV_PCM_TRIGGER_RESUME:  
  41.         DBG(" RESUME \n");  
  42.         break;  
  43.     case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:  
  44.         DBG(" RESTART \n");  
  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 \n");  
  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\n" , __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中間層可以改變或更新這些信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM