LINUX的ALSA驱动框架+ALSA_LIB代码阅读一条龙

#说明

文章架构分为两段:

1. 应用层的alsa_lib+ 部分alsa驱动代码阅读分析;

2. 专门的alsa驱动代码阅读分析.

由于篇幅过长,代码的各个结构体的出处,对应的函数等内容此文章未贴出,可以通过下面的两个链接阅读原文.

alsa_lib代码阅读

https://gitee.com/suiren/s5p6818_alsa_driver_simplify/blob/master/note/alsa_app_read.c

alsa驱动框架代码阅读

https://gitee.com/suiren/s5p6818_alsa_driver_simplify/blob/master/note/alsa_read2.c

#app + alsa_lib 执行流程总结.

1. app为要执行的alsa测试程序, 代码获取及编译方法可参考该blog.

Alsa_lib及alsa测试应用程序静态编译说明_芝麻狐RX的博客-CSDN博客

default 选择默认的pcm设备, audio.wav为音频文件. 通过这一条命令, 使用情景分析法,阅读alsa_lib,alsa框架和驱动代码.
./app default audio.wav

2.   snd_pcm_open (&capture_handle, argv[1],SND_PCM_STREAM_PLAYBACK,0);
     依次打开 opt/share/alsa/alsa.conf,opt/share/alsa/cards/aliases.conf, opt/share/alsa/pcm/default.conf,share/alsa/pcm/dmix.conf 等文件, 其中dmix.conf是非必要的.命令中的"default" 参数, 与以上文件, 找到所期望的设备节点, 即controlC0,和pcmC0D0p. 该过程对应alsa_lib中的函数    _config_search_definition(), 而真正打开pcmC0D0p设备节点的函数为 snd_pcm_hw_open().


2.1  snd_pcm_open() -> snd_pcm_hw_open() 函数会直接open() pcmC0D0p设备文件, 获得设备节点fd. open() 将调用的是驱动的snd_open().snd_open() 申请DMA通道, 用于与i2s控制器传输音频数据.接下来会创建snd_pcm_t *pcm结构体, 由于alsa_lib在后面的执行流程里创建多个snd_pcm_t *pcm结构, 就将此处创建的pcm结构体称为pcm0吧.

3.1  snd_pcm_hw_params_malloc() 为 snd_pcm_hw_params_t *hw_params 结构体分配内存. hw_params结构体用于保存音频参数,如采样率,声道数等.


3.2  snd_pcm_hw_params_any() 初始化 hw_params. 设置hw_params的各个参数的min和max. 一般min是设置为0. max为alsa_lib的默认值.


3.3  snd_pcm_hw_params_set_format(),  snd_pcm_hw_params_set_rate_near(),  snd_pcm_hw_params_set_channels() 作用是设置hw_params内的值.


3.4  alsa_lib在使用hw_params去保存设置参数时, 不是直接地去保存其值, 而是先将判断欲设置的值, 与hw_params的期望值进行比较. 以下为个人理解.
        譬如设置采样速率rate, 音频文件的rate为44100, 而hw_params的期望min为8000, 期望max为96000, 则音频文件的rate > alsa系统的min,那么hw_params中rate的min将被替换为44100. 之所以替换min,而非max, 是避免精度损失吧.而之所以能够避免精度损失, 是因为最终hw_params的rate的
期望值会传给驱动, 驱动会根据自身的硬件能力,其或许不支持设置min的值,又或许不支持设置max的值, 从而选择best的rate值.
        以上确实是个人理解, 因为我当前使用的开发板的alsa驱动, 直接就是使用dts的默认值.....

4.   snd_pcm_hw_params() 将hw_params 传给驱动, 驱动选择best的参数并返回.


4.1  snd_pcm_hw_params() -> snd_pcm_rate_open()会又创建一个 snd_pcm_t *pcm结构体, 称其为pcm2吧.同时也会创建snd_pcm_rate_t *rate结构体.


4.2  snd_pcm_hw_params() -> snd_pcm_rate_hw_params() -> snd_pcm_hw_hw_params(),会执行ioctl(pcm_hw->fd,SNDRV_PCM_IOCTL_HW_PARAMS,params),该ioctl()对于驱动函数为snd_pcm_hw_params(), 作用是将hw_params传为驱动, 而驱动则会判断自己的best参数是否在hw_params的范围内, 若是,则修改hw_params为驱动的best参数,并返回给应用, 否则返回错误. 应用将best参数保存到rate->info.out内.


4.3  snd_pcm_hw_params() -> snd_pcm_rate_hw_params() -> snd_pcm_mmap(), 将执行 mmap(), 驱动将与i2s控制器所对应的DMA的buffer映射给应用,应用将mmap()返回的地址,保存到pcm0->mmap_channels->addr. 这里的mmap_channels也是分为mmap_channels[0]和[1]的. pcm0->running_areas所指向的内存地址也改为pcm0->mmap_channels->addr的值.同时将用户设置的pcm参数,即我们根据音频文件而设置的参数, 保存到rate->info.in.


4.3  snd_pcm_hw_params() -> snd_pcm_rate_hw_params() -> linear_init(), 将根据rate->info.in 和rate->info.out,即输入音频数据是一套参数,输出的音频数据又是另一套参数, 设置音频数据的转换函数以及转换参数.我这边使用的是转换函数linear_expand_s16, 进行采样率从44100 到48000的转换.

5    snd_pcm_prepare(), 主要就是执行 ioctl(fd, SNDRV_PCM_IOCTL_PREPARE); 该ioctl()对于驱动函数为snd_pcm_prepare(). ioctl()->snd_pcm_prepare()  判断声卡的状态为完全开启,并设置数据流的状态为SNDRV_PCM_STATE_PREPARED.

6    snd_pcm_writei(),
6.1  snd_pcm_writei()->snd_pcm_area_copy() , 将作为参数传入的音频数据, 复制到pcm2->running_areas. running_areas分为 running_areas[0]和[1], 即对应左右声道.


6.2  snd_pcm_writei() -> snd_pcm_rate_write_areas1(),使用linear_init所设置的转换函数及参数,将pcm2->running_areas的数据转换并保存到pcm0->running_areas. pcm0->running_areas所在的内存是通过mmap获得的.snd_pcm_writei() -> snd_pcm_rate_start(), 将执行 ioctl(hw->fd, SNDRV_PCM_IOCTL_START);该ioctl()对应驱动函数为snd_pcm_action(), 作用为设置好i2s控制器的i2s的传输模式,采样位深,模式为i2s模式,使能i2s的DMA传输功能. 这样i2s控制器就开始通过DMA获取音频数据,发送给声卡(codec).

############驱动相关分析############


# 应用层中执行的函数, 与驱动的函数的对应关系.
#1. snd_pcm_open()->open()->snd_open()
#2. snd_pcm_hw_params() -> snd_pcm_rate_hw_params() -> snd_pcm_hw_hw_params() -> ioctl(pcm_hw->fd, SNDRV_PCM_IOCTL_HW_PARAMS, params) ->snd_pcm_hw_params()
#3. snd_pcm_hw_params() -> snd_pcm_rate_hw_params() -> snd_pcm_mmap() -> mmap() -> snd_pcm_mmap()
#4. snd_pcm_prepare() ->  ioctl(fd, SNDRV_PCM_IOCTL_PREPARE);
#5. snd_pcm_writei() -> ioctl(hw->fd, SNDRV_PCM_IOCTL_START);

###########音频数据传输路径###########

音频文件 => pcm_buffer(用户个人应用程序分配) => pcm_buffer(alsa_lib分配) => (音频数据格式转换) => DMA_buffer(驱动分配并映射给alsa_lib) => i2s控制器 => 声卡

音频数据格式转换, 就是采样率的转换等.

#下面为alsa_lib代码的各个结构体间的关系表.

snd_pcm_info_t info;
    /* RO/WR (control): stream direction */
    ->int stream;

snd_pcm_hw_t *hw 
    ->int version;
    ->int fd; <>
    ->int card, device, subdevice;
    ->volatile struct snd_pcm_mmap_status * mmap_status;
    ->struct snd_pcm_mmap_control *mmap_control;  
    ->struct snd_pcm_sync_ptr *sync_ptr; < > <>

snd_pcm_t *pcm;
    ->name "pcm0"
    ->int stream;
    ->snd_pcm_t *op_arg;  
    ->snd_pcm_t *fast_op_arg;
    ->void *private_data;
    ->int poll_fd;
    ->snd_pcm_rbptr_t hw;
        ->volatile snd_pcm_uframes_t *ptr;
        ->int fd ; < >
    ->snd_pcm_rbptr_t appl;
        ->volatile snd_pcm_uframes_t *ptr;
        ->int fd ;
    ->snd_pcm_channel_info_t *mmap_channels;
        =>mmap_channels[1] < >
            ->u.mmap.fd
            ->u.mmap.offset
            ->void *addr; < >
    ->snd_pcm_channel_area_t *running_areas;
        =>running_areas[1]
            ->void *addr;
    ->unsigned int rate;        /* rate in Hz */

struct snd_pcm_sync_ptr *sync_ptr; < >
    ->s.status
        ->hw_ptr < >
    ->c.control
        ->appl_ptr < >

snd_pcm_format_t sformat; <>

snd_pcm_plug_t *plug;
    ->snd_pcm_format_t sformat;
    ->snd_pcm_t *req_slave;
    ->snd_pcm_generic_t gen;
        ->snd_pcm_t *slave;    

snd_pcm_t *plug_pcm;
    ->name "pcm1"
    ->snd_pcm_t *op_arg;  
    ->snd_pcm_t *fast_op_arg;
    ->void *private_data;
    ->int poll_fd;

snd_pcm_t *capture_handle;
################end open######

snd_pcm_hw_params_t *hw_params; < > < > < > < > < > <<

#############end snd_pcm_hw_params_malloc #########

snd_pcm_plug_params_t clt_params;
snd_pcm_plug_params_t slv_params; < >
    ->snd_pcm_format_t sformat

snd_pcm_rate_t *rate;
    ->snd_pcm_generic_t gen;
        ->snd_pcm_t *slave;
    ->snd_pcm_format_t sformat;
    ->void *obj;
    ->snd_pcm_uframes_t appl_ptr;
    ->snd_pcm_uframes_t hw_ptr;
    ->info.in
        ->int rate; <44100>
    ->info.out
        ->int rate; <48000>

snd_pcm_t *params_pcm;
    ->name "pcm2"
    ->snd_pcm_t *op_arg;  
    ->snd_pcm_t *fast_op_arg;
    ->void *private_data;
    ->int poll_fd;
    ->snd_pcm_rbptr_t hw;
        ->volatile snd_pcm_uframes_t *ptr;
    ->snd_pcm_rbptr_t appl;
        ->volatile snd_pcm_uframes_t *ptr;
    ->snd_pcm_channel_info_t *mmap_channels;
        =>mmap_channels[0]
            ->void *addr;
        =>mmap_channels[1]
            ->void *addr;
    ->snd_pcm_channel_area_t *running_areas;
        =>running_areas[0]
            ->void *addr;
        =>running_areas[1]
            ->void *addr;

struct rate_linear *rate_linear;
    ->unsigned int pitch;
    ->int16_t *old_sample;
        =>old_sample[1]

###############end snd_pcm_hw_params##############

char *buffer;

snd_pcm_channel_area_t areas[2];
    =>areas[0]
        ->void *addr;
    =>areas[1]
        ->void *addr;

const snd_pcm_channel_area_t *pcm_areas;

char *dst =>  pcm2->running_areas ->addr; <>
char *src => areas[0]->addr;

上面为alsa_lib+部分alsa驱动框架的代码阅读总结.

下面为alsa驱动框架的专门分析

########驱动初始化流程##########


1. alsa_sound_init()
   申请设备号116作为alsa这一类设备.

2. nx_i2s_probe()
          i2s控制器初始化, 其设备信息保存在dts. 创建i2s_dai结构体  nx_i2s_set_plat_param(), 获取dts的信息,如采样率等信息,将采样率这些信息保存到i2s_dai中, 以位域的形式.初始化控制器的时钟, 并开启i2s接口, 但其他i2s功能依旧关闭中. snd_soc_register_dais()函数, 将i2s_dai加入cpu_cmpnt 链表, 设置i2s_dai->driver.

3. es8316_i2c_probe()
        创建codec_dai结构体,snd_soc_register_dais()函数, 将code_dai加入codec_cmpnt链表. 设置codec_dai->driver.

4. nx_simple_card_probe()
          解析dts, 获得simple_card节点下的cpu_dai和codec_dai的名字.
        devm_snd_soc_register_card(), 根据dts的cpu_dai和codec_dai的名字, 遍历component_list->cpu_cmpnt链表 和component_list->codec_cmpnt链表, 找到对应的cpu_dai和codec_dai.
         devm_snd_soc_register_card()-> soc_probe_link_components() , 对已经获得的cpu_dai和codec_dai进行probe,通过dai->driver->probe函数. dai->driver的指向由snd_soc_register_dais()函数设置.通过codec_dai->driver->probe(), 才真正地设置codec的寄存器,对其进行初始化.           snd_ctl_create() 设置设备节点名为 controlC%d.  对应struct snd_card *card结构体, 通过结构体可以获得cpu_dai和codec_dai,可以通过这些dai结构体,进而设置其对应的硬件的寄存器.
   snd_pcm_new_stream() 设置另一个的设备节点名为 pcmC%iD%i%c, 对应着是struct snd_pcm_str streams结构体,该结构体用于进行数据传输. platform->driver->pcm_new()=>nx_pcm_new(), 分配DMA内存, 用于保存音频数据, i2s控制器从这里获取数据.

simple_card dts:
    simple_card: sound {
        compatible = "nexell,simple-audio-card";

        simple-audio-card,dai-link@0 {
            format = "i2s";
            cpu {
                sound-dai = <&i2s_0 0>;
            };

            codec {
                sound-dai = <&es8316>;
            };
        };
    };

驱动总结:


驱动中有多条链表.
component_list 上挂接着cpu_cmpnt链表 和 codec_cmpnt链表.
cpu_cmpnt链表上挂接已注册的cpu_dai.
codec_cmpnt链表上挂接已注册的codec_dai.

        struct simple_card_data card_data结构体中struct snd_soc_pcm_runtime *rtd结构体 的包含自己所需要的cpu_dai和codec_dai.
        我认为cpu_dai对应着数据传送方式,即i2s控制器,而codec_dai对应为声卡,我这里是es8316.

component_list => list ( cpu_cmpnt) => list (codec_cmpnt)

cpu_cmpnt
    ->list => list (i2s_dai)

codec_cmpnt
    ->list => list (codec_dai)

#alsa驱动框架各个结构体的关系表.

component_list;
platform_list;
codec_list;  

struct platform_device *i2s_pdev
    ->struct device dev;
        ->void *driver_data

struct nx_i2s_snd_param *par;
    ->struct device *dev;
    ->struct snd_soc_dai_driver dai_drv;
        ->struct snd_soc_pcm_stream playback;
            ->unsigned int rates; < value == 1<<7 >
    ->int sample_rate; <48000>
    ->struct nx_pcm_dma_param play;
        ->dma_addr_t peri_addr;
        ->struct device *dev;
        ->char *dma_ch_name; <"i2s">


struct snd_soc_component *cmpnt;
    ->char *name; <"c0055000.i2s">
    ->struct device *dev;
    ->struct snd_soc_dai_driver *dai_drv;
    ->struct list_head dai_list;
    ->struct regmap *regmap;
    ->const struct snd_soc_component_driver *driver;
    ->struct list_head list;

struct snd_soc_dai *i2s_dai;
    ->struct device *dev;
    ->struct snd_soc_component *component;
    ->struct snd_soc_dai_driver *driver;
    ->struct list_head list;
    ->char *name <"c0055000.i2s">
    ->void *playback_dma_data;
    ->unsigned int rate;
    ->unsigned int channels;
    ->unsigned int sample_bits;

struct snd_soc_platform *soc_platform;
    ->struct device *dev;
    ->const struct snd_soc_platform_driver *driver;
    ->struct list_head list;
    ->struct snd_soc_component component;
        ->char *name;
        ->struct device *dev;
        ->const struct snd_soc_component_driver *driver;

static const struct snd_soc_component_driver nx_i2s_component;

struct snd_soc_platform_driver nx_pcm_platform;  
    ->struct snd_soc_component_driver component_driver;

##########

struct i2c_client *i2c
    ->struct device *dev;
        ->void *driver_data;

struct es8316_priv *es8316;

struct snd_soc_codec *codec;
    ->struct device *dev;
    ->const struct snd_soc_codec_driver *driver;
    ->struct list_head list;
    ->void *control_data;
    ->struct snd_soc_component component;
        ->char *name; <"ES8316.0-0011">
        ->struct snd_soc_card *card; 
        ->struct snd_soc_dapm_context dapm;
            ->struct snd_soc_card *card; 
        ->struct snd_soc_codec *codec;
        ->char *name;
        ->struct device *dev;
        ->const struct snd_soc_component_driver *driver;
        ->struct snd_soc_dai_driver *dai_drv;
        ->struct list_head dai_list;
        ->struct list_head list;

struct snd_soc_dai *codec_dai;
    ->struct device *dev; 
    ->struct snd_soc_component *component;
    ->struct snd_soc_dai_driver *driver;
    ->struct list_head list;
    ->char *name; <"ES8316 HiFi">
    ->struct snd_soc_codec *codec;

static struct snd_soc_codec_driver soc_codec_dev_es8316;
static struct snd_soc_dai_driver es8316_dai;

###############

struct platform_device *simple_card_pdev
    ->struct device dev;

struct simple_card_data *simple_card_priv;
    struct simple_dai_props *dai_props;
    ->struct snd_soc_card snd_card;
        ->struct list_head dapm_list;
        ->struct device *dev;
            ->void *driver_data;
        ->char *name;
        ->void *drvdata
        ->struct snd_card *snd_card;
        ->struct snd_soc_dapm_context dapm;
            ->struct list_head list;
            ->struct device *dev;
            ->struct snd_soc_card *card;
        ->struct snd_soc_pcm_runtime *rtd;
            ->struct snd_pcm *pcm;
            ->struct device *dev;
                ->struct device *parent;
                ->void *driver_data
            ->struct snd_soc_card *card;
            ->struct snd_soc_dai_link *dai_link;
            ->struct snd_soc_dai **codec_dais;
            ->struct snd_soc_dai *codec_dai;
            ->struct snd_soc_dai *cpu_dai;
            ->struct snd_soc_codec *codec;
            ->struct snd_soc_platform *platform;
        ->struct snd_soc_pcm_runtime *rtd_aux;
        ->struct snd_soc_dai_link *dai_link;
            ->const char *name; <"c0055000.i2s-ES8316 HiFi">
            ->const char *stream_name;     <"c0055000.i2s-ES8316 HiFi">
            ->const char *cpu_dai_name; <"c0055000.i2s">
            ->const char *codec_dai_name; <"ES8316 HiFi">
            ->struct snd_soc_dai_link_component *codecs;

struct snd_card *card;
    ->struct device *dev;
    ->struct list_head devices;
        ->struct list_head *prev;
    ->struct device ctl_dev; 
        ->struct device *parent;
    ->struct device card_dev;
        ->struct device *parent;

struct snd_device *clt_dev;
    ->struct snd_card *card;
    ->void *device_data;
    ->struct list_head list;

struct snd_pcm *pcm;
    ->void *private_data;
    ->struct snd_card *card;
    ->struct snd_pcm_str streams[2];
        =>streams[SNDRV_PCM_STREAM_PLAYBACK]
            ->struct snd_pcm *pcm;
            ->struct snd_pcm_substream *substream;
            ->struct device dev;
                ->struct device *parent;

struct snd_pcm_substream *substream;
    ->struct snd_pcm *pcm;
    ->struct snd_pcm_str *pstr;
    ->void *file;  
    ->struct snd_pcm_runtime *runtime;
        ->void *private_data
        ->struct snd_pcm_hardware hw; <>
        ->unsigned int rate;
        ->unsigned int channels
        ->dma_addr_t dma_addr;    
    ->void *private_data
    ->struct snd_dma_buffer dma_buffer;
        ->unsigned char *area;
        ->struct snd_dma_device dev;
            ->struct device *dev;

struct snd_device *pcm_dev;
    ->struct snd_card *card;
    ->void *device_data;
    ->struct list_head list;

snd_minors[x];
snd_minors[x2];

struct snd_minor *clt_preg;
    ->void *private_data;
    ->struct device *dev;

struct snd_minor *pcm_preg;
    ->void *private_data;
    ->struct device *dev;

###################snd_open_device##############
struct snd_pcm_runtime *runtime;
    ->struct snd_pcm_mmap_status *status;
    ->struct snd_pcm_mmap_control *control;

struct nx_pcm_runtime_data *prtd;
    ->struct device *dev;
    ->struct nx_pcm_dma_param *dma_param;
    ->struct dma_chan  *dma_chan;

static struct snd_pcm_hardware nx_pcm_hardware;

struct snd_pcm_file *pcm_file;
    ->struct snd_pcm_substream *substream;

struct file *file
    ->void *private_data 

你可能感兴趣的:(linux,arm开发,驱动开发)