ALSA(Advanced Linux Sound Architecture)Linux标准音频驱动框架, 对于android系统来说使用的是一个精简版的ALSA架构,即TinyALSA, 有一部分ALSA的接口是在用户空间,供上层调用来连接kernel。
根据音频数据的流向再把音频内核分为以下三个层次:
- Tinyalsa
- ALSA Core
- ASoC
1. TinyAlsa
1.1tinyAlsa简介
TinyAlsa 是 Android 社区为了满足移动嵌入式设备需求而开发的一个轻量级、精简的 ALSA 用户空间库和工具集 。它的核心目标是提供一个最小化的 ALSA API 实现,用于替代 GNU/Linux 发行版中常见的、功能庞大而复杂的 alsa-lib
。
您可以把它理解为 ALSA 的"嵌入式版本"。
在标准的 Linux 桌面发行版中,应用程序通过 alsa-lib
这个庞大的库来访问音频设备。alsa-lib
功能非常全面,包括:
-
复杂的插件系统(用于格式转换、重采样、多路复用等)
-
复杂的配置文件和解析器 (
alsa.conf
) -
支持众多不常用的音频格式和功能
对于 Android 这样的嵌入式系统,alsa-lib
显得过于臃肿, tinyalsa与alsa对比如下:
特性 | 标准 alsa-lib | TinyAlsa |
---|---|---|
体积和依赖 | 庞大,依赖多 | 极其轻量,代码量小,依赖少 |
配置 | 支持复杂的配置文件 | 无配置文件或非常简单,通常通过代码硬编码或传递参数 |
功能 | 功能全面,插件丰富 | 功能精简,只保留最基本、必需的 PCM 和 Control 操作 |
目标平台 | 通用桌面/服务器 Linux | Android 及其他嵌入式 Linux |
设计哲学 | "大而全" | **"小而美",满足基本需求即可 |
因此,TinyAlsa 应运而生,它剥离了 alsa-lib
中绝大多数嵌入式设备用不到的高级功能和插件,只保留了最核心的与内核 ALSA 驱动交互的能力。
1.2 TinyAlsa核心组件
tinyalsa在android的external/tinyalsa目录中,TinyAlsa 主要包含两部分:
(1)库文件 (libtinyalsa.so
)
这是一个轻量级的动态库,提供了操作音频设备的核心 API。主要头文件包括:
-
tinyalsa/pcm.h
:用于播放和录制 PCM 音频数据。-
关键结构体:
struct pcm
-
关键函数:
pcm_open
,pcm_write
,pcm_read
,pcm_close
,pcm_get_params
等。
-
-
tinyalsa/mixer.h
:用于控制音频路径、音量等。-
关键结构体:
struct mixer
,struct mixer_ctl
-
关键函数:
mixer_open
,mixer_get_ctl_by_name
,ctl_set_value
等。
-
(2)命令工具集
这些工具对于开发和调试音频问题至关重要,通常位于 Android 设备的 /system/bin/
下:
-
tinyplay
: 用于播放原始 PCM 音频文件。- 用法:
tinyplay /sdcard/test_44100_s16le.wav
- 用法:
-
tinycap
: 用于录制音频并保存为原始 PCM 文件。- 用法:
tinycap /sdcard/recording.wav -r 48000 -c 2
- 用法:
-
tinymix
: 用于查看和设置混音器控件,是调试音频路由和音量的神器。- 用法:
tinymix
(查看所有控件),tinymix "Playback Path" 1
(设置某个控件的值)
- 用法:
-
tinypcminfo
: 用于查看 PCM 设备的能力和参数,如支持的采样率、声道数、格式等。- 用法:
tinypcminfo -D 0
- 用法:
1.3.TinyAlsa 在 Android 音频架构中的位置
Application\] -\> \[Java/Native Framework\] -\> \[AudioFlinger\] -\> \[Audio HAL
|
v
TinyAlsa (libtinyalsa.so)\] \<-- 在这里! \| v \[Kernel ALSA Driver \& /dev/snd/ nodes
它的主要使用者是 Audio HAL 的实现。
设备制造商在编写他们的 audio.primary.[platform].so
(即 Primary Audio HAL)时,通常会选择链接 libtinyalsa.so
,然后使用 TinyAlsa 的 API 来:
-
打开/关闭 PCM 设备 (
pcm_open
,pcm_close
) -
写入播放数据 (
pcm_write
) 或读取录制数据 (pcm_read
) -
通过 Mixer API 设置音量、切换音频路径(如从扬声器切换到耳机)
1.4代码示例
cpp
#include <tinyalsa/pcm.h>
int play_audio(const char *data, size_t size) {
// 1. 配置 PCM 参数
struct pcm_config config = {
.channels = 2, // 立体声
.rate = 44100, // 采样率 44.1kHz
.format = PCM_FORMAT_S16_LE, // 采样格式:有符号16位小端
.period_size = 1024, // 周期大小(帧数)
.period_count = 4, // 周期个数
.start_threshold = 0,
.stop_threshold = 0,
.silence_threshold = 0,
};
// 2. 打开 PCM 设备。0 是声卡号,0 是设备号(0代表播放)
struct pcm *pcm = pcm_open(0, 0, PCM_OUT, &config);
if (!pcm || !pcm_is_ready(pcm)) {
// 处理错误
return -1;
}
// 3. 写入音频数据
int ret = pcm_write(pcm, data, size);
// 4. 关闭设备,释放资源
pcm_close(pcm);
return ret;
}
2. ALSA Core
ALSA Core 指的是 Linux 内核中的 ALSA 子系统核心,它是整个 ALSA 架构的心脏和基石。虽然用户通常接触的是 ALSA 的用户空间库和工具,但真正驱动硬件、处理音频数据流的是这个内核模块。AlSA Core提供了:
-
统一的音频设备驱动框架。
-
音频数据流和控制的抽象模型。
-
与用户空间交互的标准接口。
它的主要目标是为所有音频设备(从简单的板载声卡到复杂的专业音频接口)提供一个一致、高效的驱动模型。
2.1核心组件
(1)声卡 (struct snd_card
)
-
这是 ALSA 驱动中最顶层的抽象,代表一个物理或虚拟的音频设备。
-
一个
snd_card
可以包含多个组件(如 PCM、Control、Mixer 等)。 -
在
/proc/asound/
目录下,每个注册的声卡都会有一个对应的条目(如card0
)。、
(2)PCM 设备 (struct snd_pcm
)
-
PCM(脉冲编码调制) 是处理数字音频流的核心接口,负责播放(playback) 和录制(capture)。
-
它管理着音频数据流的生命周期:
-
硬件参数: 采样率、位深、声道数、缓冲区大小、周期大小。
-
DMA 缓冲区管理 : 负责在内核空间与音频硬件之间传输数据,通常使用环形缓冲区。
-
中断处理: 当一个音频周期(period)的数据传输完成时,硬件会产生中断,PCM 核心据此来更新缓冲区指针并唤醒用户空间应用程序。
-
- 在用户空间,PCM 设备表现为
/dev/snd/pcmC0D0p
(Card 0, Device 0, playback)这样的设备节点。
(3)控制接口 (struct snd_ctl
)
-
用于处理所有非音频流的控制命令,例如:
-
音量控制
-
通道开关
-
输入源选择(如麦克风 vs 线路输入)
-
各种开关(静音、幻象电源等)
-
-
用户空间的混音器程序(如
alsamixer
,tinymix
)就是通过这个接口与驱动通信的。
(4)原始 MIDI 接口 (struct snd_rawmidi
)
- 提供对 MIDI 设备的支持,用于传输音乐控制信息(如音符开关、控制改变等)。
(5)定时器 (struct snd_timer
)
- 为音频应用提供高精度的定时服务,特别是在处理 MIDI 序列和同步时非常关键。
(6)序列器 (struct snd_seq
)
- 一个更高级的 MIDI 子系统,支持多端口、队列和连接,用于复杂的音乐软件。
2.2 软件层次结构
+-------------------------------------------------------+
| User Space Applications |
| (e.g., aplay, tinyplay, Audio HAL, Media Player) |
+-------------------------------------------------------+
| ALSA User Library (alsa-lib) |
+-------------------------------------------------------+
| System Call Interface (ioctl, read, write) |
+-------------------------------------------------------+
| +---------------------------------------------------+ |
| | ALSA Core (Kernel) | |
| +---------------------------------------------------+ |
| | PCM | Control | MIDI | Timer | ... | | |
| | (Abstraction Layer & Core Logic) | |
| +---------------------------------------------------+ |
| | ALSA Driver for Specific Hardware | |
| | (e.g., snd_hda_intel, snd_soc_xxx, snd_usb_audio)| |
| +---------------------------------------------------+ |
+-------------------------------------------------------+
| Hardware (Sound Card) |
+-------------------------------------------------------+
(1) 硬件特定驱动层
-
这是直接与硬件交互的代码,由芯片厂商或社区开发。
-
例如:
snd_hda_intel
(用于 Intel HDA 声卡),snd_usb_audio
(用于 USB 音频设备),以及各种 SoC 音频驱动。
(2)ALSA Core 抽象层:
-
这一层提供了统一的 API 和数据结构(如
snd_pcm_ops
,snd_ctl_ops
),让硬件驱动开发者能够专注于硬件操作,而无需重复实现通用的音频逻辑。 -
它处理了缓冲区管理、电源管理、设备文件创建、proc/sysfs 接口等通用任务。
(3)用户空间接口层:
- ALSA Core 通过
/dev/snd/
下的设备节点和ioctl
调用向用户空间暴露接口。
2.3数据流
让我们跟踪一段音频数据从用户空间到硬件的旅程,以理解 ALSA Core 的作用:
-
用户空间调用 :应用程序(通过
alsa-lib
)调用write
系统调用,将 PCM 数据写入/dev/snd/pcmC0D0p
。 -
驱动入口 :
write
调用进入内核,最终到达该 PCM 设备文件对应的驱动函数(在 ALSA Core 中定义)。 -
数据拷贝 :驱动将用户空间的数据拷贝到内核中预先分配好的 DMA 缓冲区。这个缓冲区是音频硬件可以直接访问的内存区域。
-
启动流 :当缓冲区中有足够的数据(达到一个 period 的大小)时,驱动启动硬件开始传输。
-
DMA 传输 :音频硬件通过 DMA(直接内存访问) 自动从 DMA 缓冲区中读取数据,并将其发送到数字模拟转换器(DAC),整个过程无需 CPU 参与。
-
周期中断 :当硬件处理完一个 period 的数据后,它会触发一个硬件中断。
-
中断处理:CPU 响应中断,执行对应的 ALSA 中断处理程序。该程序会:
-
更新缓冲区指针,标记已传输的数据空间为可用。
-
如果驱动支持,触发高精度定时器来提供更精确的时序。
-
唤醒正在等待可用缓冲区的用户空间应用程序。
-
-
循环往复:应用程序被唤醒后,继续写入新的音频数据,整个过程重复进行,直到音频流停止。
2.4 ASoC Core的关键特性
-
内核级支持:作为 Linux 内核的一部分,提供高效的、直接访问硬件的途径。
-
完整的框架:提供了 PCM、Control、Mixer、MIDI、Timer 等完整的音频组件模型。
-
硬件抽象:通过统一的 Ops(操作函数集)结构体,屏蔽了不同音频硬件的差异。
-
高效的数据传输:基于 DMA 和中断机制,实现了低 CPU 占用率的音频数据传输。
-
可扩展性:支持从简单的 UAC(USB Audio Class)到复杂的多通道专业声卡。
-
嵌入式优化:通过 ASoC 子框架,为嵌入式 SoC 提供了强大的支持。
3.ASoC
ASoC是ALSA系统在嵌入式系统或SoC(System on Chip)上的一个子框架和扩展。它的目标是为嵌入式平台上的音频提供更好的支持,尤其是那些集成了音频功能的SoC(如手机处理器、平板电脑处理器等)。
在嵌入式系统中,音频系统通常由两部分组成:一是SoC内部集成的音频控制器(如I2S、PCM接口),二是外部的音频编解码器(Codec)。此外,还可能有一些复杂的板级配置(如连接多个Codec、复杂的时钟和电源管理)。传统的ALSA驱动模型在这种情况下显得不够灵活,因此ASoC应运而生。
ASoC将嵌入式音频系统分为三个部分:
-
Platform Driver:负责SoC这边的音频接口,比如DMA控制器、I2S/PCM接口等。
-
Codec Driver:负责外部的编解码器,通常通过I2C/SPI等总线控制,并提供音频的采集和播放功能。
-
Machine Driver:作为"胶水"将Platform和Codec驱动结合起来,并处理板级特定的配置(如音频路由、时钟、电源等)。
这样分离的好处是:
-
代码复用:同一个Codec驱动可以在不同的SoC平台上使用,同一个SoC的Platform驱动也可以搭配不同的Codec。
-
逻辑清晰:各司其职,便于维护和调试。
下面分别介绍下这三部分
3.1Platform Driver
Platform Driver 处理 SoC 平台本身的音频功能,主要分为两部分:
-
CPU DAI (Digital Audio Interface) Driver:
-
驱动 SoC 内部的数字音频接口控制器,如 I²S、PCM、AC97 等。
-
配置接口参数:主/从模式、时钟、帧同步信号、数据格式等。
-
通常对应
snd_soc_dai_driver
。
-
-
Audio DMA Driver:
-
驱动 SoC 内部的 DMA 控制器,负责在内存和音频接口之间搬运音频数据。
-
管理 DMA 缓冲区、周期中断等。
-
通常对应
snd_soc_platform_driver
(新内核中已整合到snd_soc_component_driver
)。
-
示例:高通的 SoC 会有自己的 Platform Driver,负责其 Hexagon DSP 和 I²S 接口。
3.2Codec Driver
Codec Driver 驱动外部 或内部 的音频编解码器芯片,负责所有与音频信号处理相关的功能:
-
DAC/ADC:数字/模拟转换。
-
Mixer:音频路由和混音。
-
PGA (Programmable Gain Amplifier):可编程增益放大器,控制音量。
-
BIAS 和电源管理:为麦克风等提供偏置电压。
-
Codec DAI:描述 Codec 侧的数字音频接口。
-
Controls:暴露各种控制项(如 "Playback Volume", "Capture Switch")给用户空间。
Codec Driver 是平台无关的。同一个 WM8994 Codec 驱动既可以用在三星的手机上,也可以用在其他使用该 Codec 的开发板上。
3.3Machine Driver
Machine Driver 是连接 Platform 和 Codec 的 "胶水代码" ,它包含了板级特定的配置信息:
-
定义 DAI Link:指定使用哪个 Platform 的 CPU DAI 和哪个 Codec 的 Codec DAI 进行连接。
-
音频路由:定义音频路径,例如:
-
"MIC1 -> ADC1"
(录音路径) -
"DAC1 -> HP Out"
(播放路径)
-
-
时钟配置:配置主时钟(MCLK)、位时钟(BCLK)等。
-
初始化配置:设置上电时的默认音量、上电/下电序列等。
示例:一个具体的手机主板,其 Machine Driver 会指明 SoC 的 I²S0 接口连接到 Cirrus Logic CS42L73 Codec,并定义耳机插入时音频路由如何切换。
3.4ASoC 的关键特性与机制
DAPM (Dynamic Audio Power Management)
DAPM 是 ASoC 框架中一个极其重要的特性,它的核心目标是根据音频路径动态管理电源,实现最低功耗。
-
工作原理:
-
将音频系统中的每个部件(Mixer、ADC、DAC、放大器等)建模为一个 "widget"。
-
每个 widget 有电源状态(开/关)。
-
DAPM 根据用户空间的音频流设置(如
tinymix
)和预定义的音频路由,自动计算出一条完整的、从源(如麦克风)到目的(如扬声器)的完整路径。 -
只打开这条路径上的所有 widget,其他无关的 widget 全部保持关闭。
-
-
举例:当播放音乐到扬声器时,DAPM 会自动打开 DAC、扬声器放大器等,但会关闭耳机放大器、ADC、麦克风偏置等。一旦播放停止,整个路径会自动断电。
DAI (Digital Audio Interface) 和 DAI Link
-
DAI:代表一个数字音频接口。无论是 SoC 端的 I²S 还是 Codec 端的 I²S,都抽象为一个 DAI。
-
DAI Link :在 Machine Driver 中定义,用于连接一个 CPU DAI 和一个 Codec DAI,形成一个逻辑上的音频子系统的"管道"。
音频路由与控件
-
路由:在设备树(Device Tree)或 Machine Driver 代码中静态定义,描述了信号在 Codec 内部或板级上的可能路径。
-
控件 :通过 ALSA Control 接口暴露给用户空间,让
alsamixer
或tinymix
等工具可以动态改变路由和设置参数。
3.5ASoC的特性
(1)模块化与代码复用:Platform、Codec、Machine 三者分离,极大地提高了驱动代码的复用性。
(2)强大的电源管理:DAPM 机制在不牺牲功能的前提下,最大限度地降低了音频子系统的功耗,这对移动设备至关重要。
(3)统一的框架:为各种复杂的嵌入式音频硬件提供了一个标准、统一的驱动模型。
(4)易于调试和扩展:清晰的分层使得定位问题和添加新硬件支持变得更加容易。