ALSA相關


音頻數據流向:
         | DMA |                                    | I2S/PCM/AC97 |

RAM -------------> I2SControllerFIFO ---------------------------> CODEC ----> SPK/Headset

PCM模塊初始化:

struct snd_soc_platform s3c_soc_platform = {  
           .name         = "s3c-pcm-audio",  
           .pcm_ops      = &s3c_pcm_ops,  //OK
           .pcm_new      = s3c_pcm_new,   //OK
           .pcm_free     = s3c_pcm_free_dma_buffers,  //OK
           .suspend      = s3c_pcm_suspend,  
           .resume       = s3c_pcm_resume,  
    };  

    調用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結構體如下:

struct snd_pcm_ops {  
           int (*open)(struct snd_pcm_substream *substream);  //OK
           int (*close)(struct snd_pcm_substream *substream); //OK 
           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);      //OK
           int (*hw_free)(struct snd_pcm_substream *substream);   //OK
           int (*prepare)(struct snd_pcm_substream *substream);   //OK
           int (*trigger)(struct snd_pcm_substream *substream, int cmd);  //OK
           snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); //PCM中間層通過調用這個函數來獲取緩衝區的位置。 
           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函數爲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_callback,這是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;  
    }  

  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. }; 
上層ALSA lib可以通過接口來獲得這些參數的,如snd_pcm_hw_params_get_buffer_size_max()來取得buffer_bytes_max。 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;  
       /*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()中初始化的。*/
       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);  
       runtime->dma_bytes = params_buffer_bytes(params);  
       return err;  
} 

prepare

當pcm“準備好了”調用該函數。在這裏根據channels、buffer_bytes等來設定DMA傳輸參數,跟具體硬件平臺相關。注:每次調用snd_pcm_prepare()的時候均會調用prepare函數。

trigger

當pcm開始、停止、暫停的時候都會調用trigger函數。


ALSA: Advanced Linux Sound Architecture,它包括內核驅動集合、API庫和工具。用戶層程序直接調用libsound的API庫,不需要打開設備等操作,因此編程者不需要了解底層細節。在嵌入式中,音頻數據傳輸一般用I2S接口,控制一般用I2c或SPI接口。如下僅以嵌入式聲卡爲例,其驅動代碼一般放在sound/soc下面。

struct snd_soc_dai結構體:

/*     Digital Audio Interface runtime data.  
       Holds runtime data for a DAI. */  
    struct snd_soc_dai {  
        /* DAI description */  
        char *name;  //模塊聲卡名稱
        unsigned int id;  
        int ac97_control;  
      
        struct device *dev;  
        void *ac97_pdata;   /* platform_data for the ac97 codec */  
      
        /* DAI callbacks */  
        int (*probe)(struct platform_device *pdev, struct snd_soc_dai *dai);  
        void (*remove)(struct platform_device *pdev, struct snd_soc_dai *dai);  
        int (*suspend)(struct snd_soc_dai *dai);  
        int (*resume)(struct snd_soc_dai *dai);  
      
        /* ops */  
        struct snd_soc_dai_ops *ops; //聲卡操作函數集合指針,實現的有hw_params(硬件參數設定)、digital_mute(靜音操作)、set_fmt(格式配置)等,這些函數的實現均與硬件相關,根據硬件的數據手冊來實現。    
        
        /* DAI capabilities */  
        struct snd_soc_pcm_stream capture;  //錄音參數設定
        struct snd_soc_pcm_stream playback; //播放參數設定,均包含channel數目、PCM_RATE和PCM_FMTBIT等信息。
        unsigned int symmetric_rates:1;     //
      
        /* DAI runtime info */  
        struct snd_pcm_runtime *runtime;  
        struct snd_soc_codec *codec;  
        unsigned int active;  
        unsigned char pop_wait:1;  
        void *dma_data;  
      
        /* DAI private data */  
        void *private_data;  
      
        /* parent platform */  
        struct snd_soc_platform *platform;  
      
        struct list_head list;  
    };  
結構體定義範例:
struct snd_soc_dai uda134x_dai = {  
        .name = "UDA134X",  
        /* playback capabilities */  
        .playback = {  
            .stream_name = "Playback",  
            .channels_min = 1,  
            .channels_max = 2,  
            .rates = UDA134X_RATES,  
            .formats = UDA134X_FORMATS,  
        },  
        /* capture capabilities */  
        .capture = {  
            .stream_name = "Capture",  
            .channels_min = 1,  
            .channels_max = 2,  
            .rates = UDA134X_RATES,  
            .formats = UDA134X_FORMATS,  
        },  
        /* pcm operations */  
        .ops = &uda134x_dai_ops,  
    };  
/* codec device */  
    struct snd_soc_codec_device {  
        int (*probe)(struct platform_device *pdev);  
        int (*remove)(struct platform_device *pdev);  
        int (*suspend)(struct platform_device *pdev, pm_message_t state);  
        int (*resume)(struct platform_device *pdev);  
    };  
Probe指聲卡的探測與初始化,remove指聲卡的卸載,suspend指聲卡的休眠,resume指聲卡從休眠狀態下恢復。詳細介紹probe函數。
  1. static int uda134x_soc_probe(struct platform_device *pdev)  
  2. {  
  3.     //獲得snd_soc_device結構體 
  4. //在聲卡的初始化過程中,其實首先是調用sound/soc/<SOC>下的相關驅動的probe函數,在probe有platform_set_drvdata()的操作,
  5. //這裏有個將指針類型的轉換:(struct snd_soc_device *s) ==> (struct platform_device *)。
  6.     struct snd_soc_device *socdev = platform_get_drvdata(pdev);  
  7.     struct snd_soc_codec *codec;  
  8.     …  
  9.     //爲codec分配內存  
  10.     socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);  
  11.     if (socdev->card->codec == NULL)  
  12.         return ret;  
  13.   
  14.     codec = socdev->card->codec;  
  15.   
  16.     …  
  17.   
  18.     //初始化codec  
  19.     codec->name = "uda134x";  
  20.     codec->owner = THIS_MODULE;  
  21.     codec->dai = &uda134x_dai; //指向上面定義好的dai  
  22.     codec->num_dai = 1;  
  23.     codec->read = uda134x_read_reg_cache; //控制接口—讀  
  24.     codec->write = uda134x_write;         //控制接口—寫  
  25.   
  26.     …  
  27.   
  28.     mutex_init(&codec->mutex);  
  29.     INIT_LIST_HEAD(&codec->dapm_widgets);  
  30.     INIT_LIST_HEAD(&codec->dapm_paths);  
  31.       
  32.     …  
  33.   
  34.     /* register pcms */ 
  35. /*創建一個PCM實例以便播放數據流。函數裏重要的是如下兩句:
    1. ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);  //create and initialize a soundcard structure
    2. ret = soc_new_pcm(socdev, &card->dai_link[i], i);//創建播放流/錄音流的子流,將所有播放流/錄音流的子流操作函數設爲soc_pcm_ops。
    */
  36.     ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);  
  37.   
  38.     …      
  39.   /*將操作集合掛到card->control鏈表上來,這個集合實現了音頻播放時各個參數的設置,主要有.info、.get和.set。
  40. 如playback volume control:SOC_DOUBLE_R_TLV("Playback Volume", SNDCARD_REG_L_GAIN, SNDCARD_REG_R_GAIN, 0, 192, 0, digital_tlv),
  41. 其中SNDCARD_REG_L_GAIN和SNDCARD_REG_R_GAIN分別是左右聲道音量增益寄存器偏移。最終要調用的函數都是在soc-core.c裏面的,
  42. 這裏只是提供一些跟硬件相關的參數,大爲增加了代碼的複用性。*/
  43.     ret = snd_soc_add_controls(codec, uda134x_snd_controls, ARRAY_SIZE(uda134x_snd_controls));  
  44.   
  45.     …       
  46.   
  47.     /* register card */  
  48.     ret = snd_soc_init_card(socdev);  

/* SoC Device - the audio subsystem */  
    struct snd_soc_device {  
        struct device *dev;  
        struct snd_soc_card *card;  
        struct snd_soc_codec_device *codec_dev;  
        void *codec_data;  
    };  
這個結構體用於向內核註冊一個device。初始化一般如下:
  1. static struct snd_soc_device SOC_SNDCARD_snd_devdata = {  
  2.     .card = &snd_soc_s3c24xx_uda134x,  
  3.     .codec_dev = &soc_codec_dev_uda134x,//就是CODEC定義的snd_soc_codec_device結構體  
  4.     .codec_data = &s3c24xx_uda134x,     //私有數據,一般存放SNDCARD控制接口信息,如I2C從設備地址等  
  5. }; 

對於module_init,其實用platform_driver_register註冊一個platform_driver結構體的方式也好,還是直接寫一個init也好,都問題不大。前者更貼近Linux的驅動模型。Probe的一般過程如下:
static int s3c24xx_uda134x_probe(struct platform_device *pdev)  
    {              
        s3c24xx_uda134x_snd_devdata.codec_dev = &soc_codec_dev_uda134x;  
        s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);  
        platform_set_drvdata(s3c24xx_uda134x_snd_device, & s3c24xx_uda134x_snd_devdata);  
        s3c24xx_uda134x_snd_devdata.dev = & s3c24xx_uda134x_snd_device->dev;  
        platform_device_add(s3c24xx_uda134x_snd_device); //codec中導出的結構體在這裏註冊 
    }  


/* SoC card */  
    struct snd_soc_card {  
        char *name;  
        struct device *dev;  
      
        struct list_head list;  
      
        int instantiated;  
      
        int (*probe)(struct platform_device *pdev);  
        int (*remove)(struct platform_device *pdev);  
      
        /* the pre and post PM functions are used to do any PM work before and 
         * after the codec and DAI's do any PM work. */  
        int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);  
        int (*suspend_post)(struct platform_device *pdev, pm_message_t state);  
        int (*resume_pre)(struct platform_device *pdev);  
        int (*resume_post)(struct platform_device *pdev);  
      
        /* callbacks */  
        int (*set_bias_level)(struct snd_soc_card *,  
                      enum snd_soc_bias_level level);  
      
        /* CPU <--> Codec DAI links  */  
        struct snd_soc_dai_link *dai_link;  
        int num_links;  
      
        struct snd_soc_device *socdev;  
      
        struct snd_soc_codec *codec;  
      
        struct snd_soc_platform *platform;  
        struct delayed_work delayed_work;  
        struct work_struct deferred_resume_work;  
    };  
定義這個結構體是讓snd_soc_register_card()註冊一個card的。初始化範例:
  1. static struct snd_soc_card snd_soc_s3c24xx_uda134x = {  
  2.     .name = "S3C24XX_UDA134X",  
  3.     .platform = &s3c24xx_soc_platform,  
  4.     .dai_link = &s3c24xx_uda134x_dai_link,  
  5.     .num_links = 1,  
  6. }; 
/* SoC machine DAI configuration, glues a codec and cpu DAI together */  
    struct snd_soc_dai_link  {  
        char *name;         /* Codec name */  
        char *stream_name;      /* Stream name */  
      
        /* DAI */  
        struct snd_soc_dai *codec_dai;  
        struct snd_soc_dai *cpu_dai;  
      
        /* machine stream operations */  
        struct snd_soc_ops *ops;  
      
        /* codec/machine specific init - e.g. add machine controls */  
        int (*init)(struct snd_soc_codec *codec);  
      
        /* Symmetry requirements */  
        unsigned int symmetric_rates:1;  
      
        /* Symmetry data - only valid if symmetry is being enforced */  
        unsigned int rate;  
      
        /* DAI pcm */  
        struct snd_pcm *pcm;  
    };  
因爲一個平臺可以運行多個音頻設備,snd_soc_dai_link的作用也在這,將CODEC定義的snd_soc_dai掛到一個鏈表上。
name指定codec名稱;.codec_dai指向CODEC定義的snd_soc_dai結構體;.cpu_dai指向I2S定義的snd_soc_dai結構體;.ops接下來分析。
  1. /* SoC audio ops */  
  2. struct snd_soc_ops {  
  3.     int (*startup)(struct snd_pcm_substream *);  
  4.     void (*shutdown)(struct snd_pcm_substream *);  
  5.     int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);  
  6.     int (*hw_free)(struct snd_pcm_substream *);  
  7.     int (*prepare)(struct snd_pcm_substream *);  
  8.     int (*trigger)(struct snd_pcm_substream *, int);  
  9. };
底層硬件操作—

CODEC:控制接口及芯片基本初始化

PCM:pcm dma操作

I2S:i2s配置操作

之後i2s和pcm其實都跟codec差不多了,只需要理解alsa-core、<soc>、<codec、pcm、i2s>三層的關係。其中codec、pcm、i2s可以看做同層的,分別對於音頻設備的control、dma、i2s接口;<codec、pcm、i2s>會分別export相關結構體給<soc>層,<soc>層將音頻設備三部分與CPU Spec聯結起來,其probe順序是<SOC>.probe-><codec, pcm, i2s>.probe;另外<codec、pcm、i2s>在各自的module_init中將自身註冊到alsa-core中。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章