内容简介:ALSA(即Advanced Linux Sound Architecture), 是目前Linux的主流音频体系结构, 提供了音频和MIDI的支持, 其架构图如下所示TIP: 笔者的代码分析基于linux-4.14.19
1. 介绍
ALSA(即Advanced Linux Sound Architecture), 是目前 Linux 的主流音频体系结构, 提供了音频和MIDI的支持, 其架构图如下所示
TIP: 笔者的代码分析基于linux-4.14.19
2. 初始化
系统启动中ALSA初始化过程如下
alsa_sound_init() /* 注册alsa字符设备 */ register_chrdev(116, "alsa", &snd_fops) /* 创建/proc/asound目录及下属version、devices、cards、modules等文件 */ snd_info_init() const struct file_operations snd_fops = { .owner = THIS_MODULE, .open = snd_open, .llseek = noop_llseek, };
从用户空间打开PCM设备过程如下
snd_pcm_open("default", SND_PCM_STREAM_PLAYBACK) // alsa-lib接口 open("/dev/snd/controlC0") // 打开控制设备; 主设备116, 次设备0 open("/dev/snd/pcmC0D0p") // 打开PCM设备; 主设备116, 次设备16 snd_open() // 根据主设备号找到该入口 snd_minors[minor] // 根据次设备号找到对应操作集 snd_ctl_f_ops::open() // 控制设备打开方法 snd_ctl_open() snd_pcm_f_ops::open() // PCM设备打开方法 snd_pcm_playback_open() snd_lookup_minor_data() // 根据次设备号查找对应PCM设备(snd_pcm) snd_pcm_open() // 打开PCM播放子流
3. 核心层
核心层为用户空间提供逻辑设备接口, 同时为驱动提供接口来驱动硬件设备, 主要位于sound/core目录下
3.1 数据结构
该层包含的主要数据结构包括
- snd_card 表示一个声卡实例, 包含多个声卡设备 - snd_device 表示一个声卡设备部件 - snd_pcm 表示一个PCM设备, 声卡设备的一种, 用于播放和录音 - snd_control 表示Control设备, 声卡设备的一种, 用于控制声卡 - snd_pcm_str 表示PCM流, 分为playback和capture - snd_pcm_substream PCM子流, 用于音频的播放或录制 - snd_pcm_ops PCM流操作集
各结构体之间主要关系图如下所示
snd_card主要字段如下
struct snd_card { int number; /* 索引 */ char id[16]; /* 标识符 */ char driver[16]; /* 驱动名称 */ char shortname[32]; /* 短名 */ char longname[80]; /* 名字 */ void *private_data; /* 声卡私有数据*/ void (*private_free) (struct snd_card *); /* 私有数据释放回调 */ struct list_head devices; /* 该声卡下所有设备*/ struct list_head controls; /* 该声卡下所有控制设备*/ struct list_head files_list; /* 声卡管理文件 */ struct device *dev; /* 声卡相关的device */ struct device card_dev; /* 用于sysfs, 代表该声卡 */ bool registered; /* 是否注册标记 */ };
snd_device主要字段如下
struct snd_device { struct list_head list; /* 所有注册的声卡设备链表 */ struct snd_card *card; /* 设备所属声卡 */ enum snd_device_state state; /* 设备状态*/ enum snd_device_type type; /* 设备类型*/ void *device_data; /* 指向具体的声卡设备, 如snd_pcm */ struct snd_device_ops *ops; /* 设备操作集*/ };
snd_pcm主要字段如下
struct snd_pcm { struct snd_card *card; /* 该PCM设备所属声卡*/ struct list_head list; /* 所有注册的PCM设备链表 */ int device; /* PCM索引 */ unsigned int info_flags; /* SNDRV_PCM_INFO_ */ char id[64]; /* PCM设备标识 */ char name[80]; /* PCM设备名 */ struct snd_pcm_str streams[2]; /* 指向PCM设备的capture(1)和playback(0)流 */ void *private_data; /* PCM设备私有数据*/ void (*private_free) (struct snd_pcm *); /* 私有数据释放回调 */ };
3.2 接口
该层主要接口如下
/* 创建和初始化声卡结构体 */ int snd_card_new(struct device *parent, int idx, const char *xid, struct module *, int extra_size, struct snd_card **card_ret); /* 释放声卡结构体 */ int snd_card_free(struct snd_card * card); /* 注册声卡 */ int snd_card_register(struct snd_card * card); /* 创建声卡设备部件, 通常由snd_pcm_new和snd_card_new自动完成 */ int snd_device_new(struct snd_card *, enum snd_device_type type, void *device_data, struct snd_device_ops *ops); /* 注册声卡设备部件, 通常由snd_card_register自动完成 */ int snd_device_register(struct snd_card *card, void *device_data); /* 创建PCM设备 */ int snd_pcm_new(struct snd_card *, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm); /* 创建PCM流, 通常snd_pcm_new会自动创建capture和playback两个PCM流 */ int snd_pcm_new_stream(struct snd_pcm * pcm, int stream, int substream_count); /* 设置PCM设备操作集 */ void snd_pcm_set_ops(struct snd_pcm *, int direction, const struct snd_pcm_ops *ops);
snd_card_new完成了如下事宜
1. 分配snd_card+extra_size空间大小
2. 如果extra_size大于0,将private_data指向extra_size所在首地址
3. 如果指定了xid, 将其拷贝至snd_card::id中, 即声卡标识符
4. 根据idx获取可用的声卡索引并赋值给snd_card::number
5. 分别将parent、module赋值给snd_card::dev、snd_card::module
6. 初始化链表snd_card::devices、snd_card::controls、snd_card::ctl_files、snd_card::files_list
7. 调用device_initialize()初始化snd_card::card_dev, 并设置snd_card::card_dev相关成员变量, 用于sysfs
8. 调用snd_ctl_create()创建控制接口
8.1 调用snd_device_initialize初始化snd_card::ctl_dev, 并设置相关成员变量, 用于sysfs
8.2 调用snd_device_new(SNDRV_DEV_CONTROL, ops)创建声卡控制设备部件
static struct snd_device_ops ops = { .dev_free = snd_ctl_dev_free, .dev_register = snd_ctl_dev_register, .dev_disconnect = snd_ctl_dev_disconnect, };
9. 调用snd_info_card_create()创建proc对应文件系统
snd_card_register完成了如下事宜
1. 如果声卡未注册(snd_card::registered), 调用device_add(snd_card::card_dev)将声卡添加到sysfs
2. 调用snd_device_register_all(snd_card)注册该声卡下所有声卡设备(即snd_card::devices链表), 即完成snd_device_register相同的功能
2.1 遍历snd_card::devices链表, 依次调用__snd_device_register注册声卡设备
2.1.1 调用snd_device::snd_device_ops::dev_register注册该设备, 对于Control设备, 即snd_ctl_dev_register; 对于PCM设备, 即snd_pcm_dev_register; 最终则都会调用snd_register_device
2.1.1.1 snd_ctl_dev_register: 调用snd_register_device(snd_ctl_f_ops)注册该Control设备
2.1.1.2 snd_pcm_dev_register: 调用snd_pcm_add将该PCM设备添加至全局PCM链表snd_pcm_devices中, 然后调用snd_register_device(snd_pcm_f_ops)注册该PCM设备
2.1.1.x.1 snd_register_device: 分配snd_minor空间, 设置type、card、device、f_ops、card_ptr等成员变量; 通过snd_find_free_minor找到合适的minor并通过MKDEV(116, minor)创建设备节点, 然后通过device_add向系统添加该设备; 最后将该声卡设备添加至全局声卡主设备的次设备数组snd_minors中
3. 将该声卡放入全局静态声卡数组snd_cards中
4. 调用init_info_for_card()向proc文件系统注册该声卡
snd_pcm_new完成了如下事宜
1. 分配snd_pcm空间, 并设置snd_pcm::card、snd_pcm::device等成员变量
2. 调用snd_pcm_new_stream(SNDRV_PCM_STREAM_PLAYBACK)创建playback_count个子流用于播放
3. 调用snd_pcm_new_stream(SNDRV_PCM_STREAM_CAPTURE)创建capture_count个子流用于录制
4. 调用snd_device_new(SNDRV_DEV_PCM, ops)添加PCM设备
static struct snd_device_ops ops = { .dev_free = snd_pcm_dev_free, .dev_register = snd_pcm_dev_register, .dev_disconnect = snd_pcm_dev_disconnect, };
snd_pcm_new_stream完成了如下事宜
1. 设置snd_pcm::stream[playback or catpure]对应stream、pcm、substream_count成员变量
2. 调用snd_device_initialize()初始化snd_pcm::stream::dev, 并设置相关成员变量, 用于sysfs
3. 调用snd_pcm_stream_proc_init(snd_pcm_str)初始化对应proc文件系统
4. 分配substream_count个snd_pcm_substream并进行相应初始化
3.3 实现
核心驱动的一般实现步骤如下
1. 调用snd_card_create创建声卡实例(struct snd_card)
2. 定义声卡的私有结构体用于存放该声卡的一些资源信息, 如中断资源、IO资源、DMA资源等
3. 硬件初始化, 包括数字音频接口初始化、DMA控制器初始化、编解码器初始化
4. 调用snd_pcm_new创建逻辑设备, 并实现其操作集snd_pcm_ops
5. 调用snd_card_register注册声卡实例及声卡设备
具体实例可参考 sound/atmel/ac97c 和 sound/spi/at73c213 的实现
4. ASOC层
在移动设备中, 为了更好的提供ALSA支持, 在核心层的基础上出现了ASOC(ALSA System on Chip)层
ASOC层代码位于sound/soc/*, 主要由如下三部分组成
- Codec : 负责配置编解码器提供音频捕获和回放功能
- Platform : 主要负责SoC平台音频DMA和音频接口的配置和控制, 包括时钟、DMA、I2S、PCM等
- Machine : Codec、Platform、输入输出设备提供了一个载体
4.1 DAI
DAI(Digital Audio Interfaces), 即数字音频接口
ASOC支持三种主流DAI: AC97、I2S和PCM
AC97: 通常用于PC声卡, 为5线接口, 每个AC97帧为21uS长, 被分为13个时隙
- BCLK: 由AC97驱动, 为12.288 MHz
- SYNC: 同步信号, 由Controler驱动, 为48 kHz
- SDATDIN: 用于capture, AC97->Controler
- SDATAOUT: 用于playback, Controler->AC97
- RESET: 由Controler生成, 用于唤醒AC97
I2S是HiFi、STB和便携式设备中常用的4线DAI
- SCLK: 串行时钟
- LRCK: 也称WS, 声道选择线
- Tx: 用于传输音频数据
- Rx: 用于接收音频数据
PCM是另一种4线接口, 与I2S非常相似, 可以支持更灵活的协议
- BCLK: 位时钟, 根据采样率而变化
- SYNC: 同步信号
- Tx: 用于传输音频数据
- Rx: 用于接收音频数据
4.2 Codec
Codec驱动应该实现为通用与硬件无关的,用于配置编解码器、FM、MODEM、BT或外部DSP, 以提供playback和capture, 这部分代码通常位于sound/soc/codecs/*
每个Codec驱动必须��供如下功能
1. Codec DAI和PCM配置
2. 使用RegMap实现的Codec控制IO
3. Mixers和Audio控制
4. Codec音频操作
5. DAPM描述
6. DAPM事件处理
7. DAC静音控制(可选)
4.2.1 数据结构
Codec层主要结构体包括snd_soc_codec、snd_soc_codec_driver、snd_soc_dai、snd_soc_dai_driver
snd_soc_codec代表一个Codec设备, 其主要字段如下
struct snd_soc_codec { struct device *dev; /* 指向Codec设备的指针 */ const struct snd_soc_codec_driver *driver; /* 该Codec对应的驱动 */ struct list_head list; /* runtime */ unsigned int cache_init:1; /* 指示Codec cache是否初始化 */ /* codec IO */ void *control_data; /* 控制IO数据 */ hw_write_t hw_write; /* 控制IO函数 */ void *reg_cache; /* component */ struct snd_soc_component component; };
snd_soc_codec_driver代表一个Codec驱动, 其主要字段如下
struct snd_soc_codec_driver { /* 操作集 */ int (*probe)(struct snd_soc_codec *); int (*remove)(struct snd_soc_codec *); int (*suspend)(struct snd_soc_codec *); int (*resume)(struct snd_soc_codec *); struct snd_soc_component_driver component_driver; /* codec wide operations */ int (*set_sysclk)(struct snd_soc_codec *codec, int clk_id, int source, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); int (*set_jack)(struct snd_soc_codec *codec, struct snd_soc_jack *jack, void *data); /* Codec IO相关函数 */ struct regmap *(*get_regmap)(struct device *); unsigned int (*read)(struct snd_soc_codec *, unsigned int); int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); /* 偏置电压配置函数 */ int (*set_bias_level)(struct snd_soc_codec *, enum snd_soc_bias_level level); };
snd_soc_dai代表DAI运行时数据, 其主要字段如下
struct snd_soc_dai { const char *name; /* 名称 */ int id; /* 索引 */ struct device *dev; /* DAI设备 */ /* 驱动操作集 */ struct snd_soc_dai_driver *driver; /* DAI运行时信息 */ unsigned int capture_active:1; unsigned int playback_active:1; unsigned int symmetric_rates:1; unsigned int symmetric_channels:1; unsigned int symmetric_samplebits:1; unsigned int probed:1; unsigned int active; struct snd_soc_dapm_widget *playback_widget; struct snd_soc_dapm_widget *capture_widget; /* DAI DMA data */ void *playback_dma_data; /* 用于管理playback DMA */ void *capture_dma_data; /* 用于管理capture DMA */ /* Symmetry data - only valid if symmetry is being enforced */ unsigned int rate; unsigned int channels; unsigned int sample_bits; /* parent platform/codec */ struct snd_soc_codec *codec; /* 绑定的Codec */ struct snd_soc_component *component; /* 绑定的platform */ struct list_head list; };
snd_soc_dai_driver代表一个DAI驱动, 其主要字段如下
struct snd_soc_dai_driver { /* DAI描述 */ const char *name; unsigned int id; unsigned int base; struct snd_soc_dobj dobj; /* DAI驱动回调 */ int (*probe)(struct snd_soc_dai *dai); int (*remove)(struct snd_soc_dai *dai); int (*suspend)(struct snd_soc_dai *dai); int (*resume)(struct snd_soc_dai *dai); /* compress dai */ int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num); /* Optional Callback used at pcm creation*/ int (*pcm_new)(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); /* DAI is also used for the control bus */ bool bus_control; /* 操作集 */ const struct snd_soc_dai_ops *ops; const struct snd_soc_cdai_ops *cops; /* DAI能力 */ struct snd_soc_pcm_stream capture; struct snd_soc_pcm_stream playback; unsigned int symmetric_rates:1; unsigned int symmetric_channels:1; unsigned int symmetric_samplebits:1; /* probe ordering - for components with runtime dependencies */ int probe_order; int remove_order; };
4.2.2 接口
int snd_soc_register_codec(struct device *dev, const struct snd_soc_codec_driver *, struct snd_soc_dai_driver *, int num_dai);
snd_soc_register_codec完成了如下事宜
1. 分配snd_soc_codec空间
2. 调用snd_soc_component_initialize()初始化snd_soc_codec::snd_soc_component
3. snd_soc_codec::snd_soc_component操作集初始化
4. DAPM相关初始化
5. 调用snd_soc_register_dais()注册num_dai个DAI
6. 将该Codec添加至全局Codec链表codec_list中
4.2.3 实现
Codec的一般实现步骤如下
1. 获取Codec设备资源
2. 实现snd_soc_codec_driver结构体
3. 实现snd_soc_dai_driver结构体
4. 实现snd_soc_dai_ops结构体, 并赋值给snd_soc_dai_driver::ops
5. 调用snd_soc_register_codec()注册Codec
4.3 Platform
Platform驱动可分为三个部分: 音频DMA驱动、SoC DAI驱动和DSP驱动
这些驱动代码应该只和SoC CPU有关而和Board无关
FIXME: Later
4.4 Machine
Machine/Board驱动用来将所有的部件驱动(Codecs、Platforms和DAIs)进行关联
FIXME: Later
Linux公社的RSS地址 : https://www.linuxidc.com/rssFeed.aspx
本文永久更新链接地址: https://www.linuxidc.com/Linux/2019-01/156223.htm
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Flutter 完整开发实战详解(十六、详解自定义布局实战)
- 数据结构 1 线性表详解 链表、 栈 、 队列 结合JAVA 详解
- 详解Openstack环境准备
- Java泛型详解
- iOS RunLoop 详解
- Raft协议详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP and MySQL Web Development
Luke Welling、Laura Thomson / Sams / July 25, 2007 / $49.99
Book Description PHP and MySQL Web Development teaches you to develop dynamic, secure, commerical Web sites. Using the same accessible, popular teaching style of the three previous editions, this b......一起来看看 《PHP and MySQL Web Development》 这本书的介绍吧!