文章架构分为两段:
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
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控制器 => 声卡
音频数据格式转换, 就是采样率的转换等.
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;
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;
->info.out
->int rate;
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;
1. alsa_sound_init()
申请设备号116作为alsa这一类设备.
2. nx_i2s_probe()
i2s控制器初始化, 其设备信息保存在dts. 创建i2s_dai结构体 nx_i2s_set_plat_param(), 获取dts的信息,如采样率等信息,将采样率这些信息保存到i2s_dai
3. es8316_i2c_probe()
创建codec_dai结构体,snd_soc_register_dais()函数, 将code_dai加入codec_cmpnt
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
cpu_cmpnt
->list => list (i2s_dai
codec_cmpnt
->list => list (codec_dai
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;
->int sample_rate;
->struct nx_pcm_dma_param play;
->dma_addr_t peri_addr;
->struct device *dev;
->char *dma_ch_name;
struct snd_soc_component *cmpnt;
->char *name;
->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
->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;
->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;
->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;
->const char *stream_name;
->const char *cpu_dai_name;
->const char *codec_dai_name;
->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