ALSA相关

本文详细解析了ALSA(高级Linux音频架构)音频驱动的工作原理和技术细节,包括PCM模块初始化、snd_soc_dai结构体定义及其操作函数集合,以及驱动初始化流程等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


音频数据流向:
         | 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中。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值