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/ac97csound/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

参考:

<内核Alsa之ASoC>

<Linux音频子系统>

<Linux Sound Subsystem Documentation>

Linux ALSA介绍的更多相关文章

  1. 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三)

    作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第三篇,主要讲述接收端程序的原理和过程. 第一 ...

  2. Linux ALSA声卡驱动之八:ASoC架构中的Platform

    1.  Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...

  3. 01 Linux入门介绍

    一.Linux 初步介绍 Linux的优点 免费的,开源的 支持多线程,多用户 安全性好 对内存和文件管理优越 系统稳定 消耗资源少 Linux的缺点 操作相对困难 一些专业软件以及游戏支持度不足 L ...

  4. Linux Epoll介绍和程序实例

    Linux Epoll介绍和程序实例 1. Epoll是何方神圣? Epoll但是当前在Linux下开发大规模并发网络程序的热门人选,Epoll 在Linux2.6内核中正式引入,和select类似, ...

  5. Linux入门介绍

    Linux入门介绍 一.Linux 初步介绍 Linux的优点 免费的,开源的 支持多线程,多用户 安全性好 对内存和文件管理优越 系统稳定 消耗资源少 Linux的缺点 操作相对困难 一些专业软件以 ...

  6. 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一)

    作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第一篇,作为前言和概述. 第二篇:基于Oran ...

  7. 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二)

    作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第二篇,主要讲述发送端程序的原理和过程. 第一 ...

  8. Linux 系统目录介绍

    bin : bin 是Binary 二进制的缩写,就是可执行文件了.Bin目录下是用户常用的命令. sbin: 此目录下也是二进制文件 ,不过这里的命令是 超级用户如 root 这样的用户使用的. e ...

  9. Linux命令介绍

    资料链接:(Linux基本命令介绍)http://note.youdao.com/share/?id=36c07917f8d3e6437c1e764c3516a3f2&type=note#/ ...

随机推荐

  1. Leetcode代码补全——链表

    通过补全代码可以更深刻的体会到,链表就是一个存储方式,通过一单元的存储指向下一单元,而查看单元内容通过头部开始的指针依次遍历.这是leetcode里融合两个链表的题目,具体代码如下: #encodin ...

  2. python接口测试(一)——http请求及token获取

    使用python对当前的接口进行简单的测试 1.接口测试是针对软件对外提供服务得接口得输入输出进行得测试,验证接口功能与接口描述文档得一致性 返回结果可以为字符串,json,xml等 2.接口的请求方 ...

  3. PostgreSQL基本配置

    记一下Postgresql的基本操作,在Ubuntu下使用apt-get安装是不会像MySQL那样都配置好了,而是要安装后再配置: 1. 基本安装 # 安装postgresql和pgadmin(一个管 ...

  4. Ubuntu 进阶命令——长期不定时更新

    有时候远程连接服务器忽然中断或者不小心关掉了终端界面,正在运行的命令或者程序就会被强制停止.这时候,我们可以借助一些命令来避免这种情况的发生. nohup 不挂断地运行命令 & 在后台运行命令 ...

  5. Android基本组件

    ①Activity和View负责与用户交互 ②Service通常位于后台,拥有独立的生命周期,为其他组件提供后台服务和监控其他组件运行状态 ③BroadcastReceiver广播消息接收器,类似事件 ...

  6. HDU 3689 Infinite monkey theorem(DP+trie+自动机)(2010 Asia Hangzhou Regional Contest)

    Description Could you imaging a monkey writing computer programs? Surely monkeys are smart among ani ...

  7. URAL 1736 Chinese Hockey(网络最大流)

    Description Sergey and Denis closely followed the Chinese Football Championship, which has just come ...

  8. No node available for block: blk

    刚才利用hadoop和mahout运行kmean是算法,一开始利用了10个节点,一个master,9个slave,运行了7分钟,我为了看速度的变化,就改用伪分布的形式,但是一开始运行就报错了: 17/ ...

  9. zookeeper3.4.6完全分布式安装

    首先在官网下载zookeeper3.4.6安装包,解压到/usr/local目录下 然后改名为zookeeper. 环境变量配置:sudo vim /etc/profile  添加环境变量如下图 然后 ...

  10. webstorm-前端javascript开发神器中文教程和技巧分享(转)

    webstorm是一款前端javascript开发编辑的神器,此文介绍webstorm的中文教程和技巧分享. webstorm8.0.3中文汉化版下载: 百度网盘下载:http://pan.baidu ...