现代操作系统-音频处理技术1 Linux驱动底层
应用数学基础: 香农-奈奎斯特采样定理
角速度倒数是频率;
采样一段最大角速度为ω的信息,理想状态下需要2ω的速度才能完全采样,否则就会产生混叠Aliasing(即较高频被对称采样到较低频段),而限制于前后端,一般还需要大于该角速度保证得到最基本的采样点。
例如采样一段 20 to 22,000 Hz, 低通滤波20Hz以下后,还需要22Khz 的两倍即 44Hz,外加容错一些速率。
另外经典的44.1 kHz兼容 PAL 和 NTSC 制式视频
Ps. 采样频率和声音频率/声波不是一个东西,一个是采样点间隔,一个是波。
公式:
奈奎斯特频率数学表达式: f_Nyquist > 2 * f_max
混叠频率公式计算:f_alias = |f_signal - n * f_s| (n为整数),通常会找到一个在 0 到 f_s/2 之间的频率。例如,用 3Hz 采样 2Hz 信号,会产生一个 |2 - 3| = 1Hz 的混叠信号。
另外要求无限陡峭的理想重建滤波器(高低/带通滤波器)无法做到理想实现(吉布斯现象等),所以需要工程师引入安全裕量,即使用更高一些的采样频率。
更有趣的地方:https://www.zhihu.com/question/25267977/answer/3566324846
半导体电路还会影响产生:谐波失真和互调失真,而高频的采样意义也在于此。http://www.cntronics.com/connect-art/80045183
Windows10/11音频架构堆栈图

图源巨硬
Android 音频架构

图源谷歌

图源einfochips

图源 zyuanyun
AudioTrack音频输出/AudioRecord音频输入采集/AudioSystem控制
Linux发行版桌面音频架构
AudioFlinger是安卓系统的混音架构,软件上的多路音频经过该框架后,进行混音,转换为单独的数据输出到ALSA驱动,也是现代操作系统音频管理的案例。
而纯Linux桌面也有对应的混音器,如Gnome使用的GStreamer/pipewire-alsa


知名KDE桌面的Kmix
源码是cpp的,支持多个底层,ALSA的抽象层支持实现在:
https://github.com/KDE/kmix/blob/b07c04e2bc66bf8c3a30e979189ead9f5dd090d4/backends/mixer_alsa9.cpp#L461
另外asla-libs也带了一个简单的混音插件,支持配置文件 https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html
安卓底层是Linux的ALSA体系(Advanced Linux Sound Architecture)
ALSA application 测试工具ALSA utils如aplay、arecord等二进制可执行程序
ALSA lib 提供操作驱动的函数库
ALSA driver 底层驱动
其中为了解决复用性问题, 内核引入了ASoC架构

图源_kerneler
目录
- kernel
- sound
- core 核心中间层-ALSA核心
- soc 芯片厂商层-SOC驱动
- 总多SOC厂商驱动
- codecs 外设厂商驱动
- arm 芯片架构arm驱动
- mips 芯片架构mips驱动
- usb 连接方式:usb驱动
- pci 连接方式:pci驱动
- 还有很多
- sound
ASOC(Alsa System on Chip)架构

DMA负责SOC内数据搬运
I2S是一种DAI
AFIx也是一种DAI
DAC数模转换
SPK/HP 模拟信号设备,如耳机/扬声器
架构分层(以RK芯片为栗子)
Machine 机器设备: 搭配RK3588芯片平台的某款量产上市用户终端产品,由终端厂商编写
Platform 平台架构: RK3588,负责DMA和I2S等控制,由SOC厂商编写
Codec 编解码器: 由Codec厂商编写,最终实现数模转化输出音频模拟信号波形。TI芯片有 德州仪器-音频编解码器 系列产品
Machine 配置
一般只是写个设备树:
将Platform和Codec通讯打通即可
当然需要定制化的话可以修改驱动代码。如果是GKI则需要留意修改范围。
更多的还是在调优方面
系统中可能有多个Codecs
以下图源NXP AN12202
I2S (Inter-IC Sound) 引脚时序图

图源NXP
TDM (Time-Division Multiplexed)
PCM (Pulse Code Modulation) 引脚时序图

SAI (Synchronous Audio Interface) 架构图及其引脚时序图


这里介绍两个非常简单的Codec芯片供参考:
德州仪器PCM3008/新款为TAC5142
官网:https://www.ti.com/product/PCM3008
控制接口:GPIO {de_Emphasis控制dem0/dem1 电源控制pdad/pdda}
驱动代码 kernel/sound/soc/codecs/pcm3008.c
德州仪器pcm3060芯片
介绍:24-bit Asynchronous Stereo Audio Codec with 96/192kHz sampling rate
音频接口:I2S, LR
控制接口:可选iic/spi
框图:

驱动:
-
/Documentation/devicetree/bindings/sound/pcm3060.txt
也可以在这里下载:https://www.ti.com.cn/tool/cn/PCM3XXX-DRIVERS
硬件连接图:

还有人用树莓派Pico+PCM3060做了48kHz/16-bit DAC耳机:https://github.com/ploopyco/headphones
Platform 驱动
# platform probe:
struct platform_driver
Platform-Dai
介绍一个概念 Digital Audio Interface (DAI)
DAI根据硬件连接位置区分为 SOC侧 和 CODEC侧 的DAI
soc dai驱动对应N个 I2S/PCM 数字音频接口。用来连接platform和machine。
如RK芯片的SOC DAI驱动有
- kernel/sound/soc/rockchip/
- rockchip_i2s.c
- rockchip_sai.c
- 等等
struct snd_soc_dai_driver {
.ops = struct snd_soc_dai_ops
}
devm_snd_soc_register_component(&pdev->dev,
&rockchip_sai_component,
dai, 1);
snd_soc_dai_ops定义了DAI的能力集及其操作,即定义多个rates/fmt支持及其切换函数
至于网上说的 snd_soc_register_dais实际上是:
devm_snd_soc_register_component-》snd_soc_register_component-》snd_soc_add_component-》snd_soc_register_dais
Codec驱动
struct snd_soc_codec
一个Codec一般支持多个dai接口,如pcm3060支持 I2S/LR 两种,2个数字输入,2个数字输出,共4个数字音频接口
Codec-DAI接口
可以是CPU侧的DAI(I2S/PDM), 也可以是Codec侧的DAI(AFIx)
如 sound/soc/codecs/pcm3060.c
struct snd_soc_dai_driver {
[多组]= {
.ops = &struct snd_soc_dai_ops
},
}
snd_soc_dai_ops定义了DAI的能力集及其操作,即定义多个rates/fmt支持及其切换函数
Codec-控制接口/DAPM动态电源管理
如 I2C/SPI/UART/GPIO等,是SOC控制Codec的物理通路
如ES8388是一个带IIC的Codec芯片,所以IIC就是它的控制接口。
如Ti PCM3070是一个带IIC/SPI的Codec芯片,那么它有两个可选的控制接口,我们需要根据Machice选用对应的驱动IIC/SPI。
ES8388/PCM3070驱动需要注册IIC总线到Linux内核。
Kcontrol是mixer混音/mux多路开关/控制音量/调节器等等,最终作用到codec芯片的寄存器
DAPM动态电源管理
static const struct snd_soc_component_driver pcm3060_soc_comp_driver = {
# Kcontrol 寄存器控制
.controls = pcm3060_dapm_controls, #结构体struct snd_kcontrol_new
.num_controls = ARRAY_SIZE(pcm3060_dapm_controls),
# dapm 动态音频电源管理
.dapm_widgets = pcm3060_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(pcm3060_dapm_widgets),
.dapm_routes = pcm3060_dapm_map,
.num_dapm_routes = ARRAY_SIZE(pcm3060_dapm_map),
.endianness = 1,
};
devm_snd_soc_register_component(dev, &pcm3060_soc_comp_driver,
pcm3060_dai,
ARRAY_SIZE(pcm3060_dai));
Codec芯片原理解析
还是用PCM3060做栗子

模拟音频输入端:
VinL/VinR -> Single-Ended to Diff 单端转差分 -》 SDM积分微分ΔΣ调制/模数转换 -> 带数字高通滤波器HPF的降采样 -> Audio Interface and Clock Control 数字芯片 => IIS等DAI接口
数字音频输出端:
VoutR-/+和VoutL-/+ <- 带buff的低通滤波器LPF <- 多级数模转换/ΔΣ调制 <- 数字插值 <- 数字芯片 <- IIS等DAI接口
Common and Reference 提供电源控制和参考
Mode Control 提供模式切换控制等
关于ΔΣ调制器:
- 精度音频ADC几乎都采用ΔΣ调制器,但其需要差分输入来匹配ΔΣ调制器对共模噪声和高线性度的要求。
- 噪声传导有两种模式:
2.1 差模传导,电流抵消总和为0。
2.2 共模传导,又称非对称噪声或线路对地的噪声,经杂散电容等泄漏的噪声电流经由大地返回电源线线的噪声
计算 see: https://www.eet-china.com/mp/a54105.html
总之,噪声电流值相同的情况下,共模传导远大于差模传导
文章末尾
放一篇 RK3588搭配ES8388 Codec芯片的架构分析(作者arnoldlu):
Linux音频(1):alsa架构和RK3588 PCM实例


设备树配置
i2s0_8ch: i2s@fe470000 {
compatible = "rockchip,rk3588-i2s-tdm";
reg = <0x0 0xfe470000 0x0 0x1000>;
interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru MCLK_I2S0_8CH_TX>, <&cru MCLK_I2S0_8CH_RX>, <&cru HCLK_I2S0_8CH>;
clock-names = "mclk_tx", "mclk_rx", "hclk";
assigned-clocks = <&cru CLK_I2S0_8CH_TX_SRC>, <&cru CLK_I2S0_8CH_RX_SRC>;
assigned-clock-parents = <&cru PLL_AUPLL>, <&cru PLL_AUPLL>;
dmas = <&dmac0 0>, <&dmac0 1>;
dma-names = "tx", "rx";
power-domains = <&power RK3588_PD_AUDIO>;
resets = <&cru SRST_M_I2S0_8CH_TX>, <&cru SRST_M_I2S0_8CH_RX>;
reset-names = "tx-m", "rx-m";
rockchip,clk-trcm = <1>;
pinctrl-names = "default";
pinctrl-0 = <&i2s0_lrck
&i2s0_sclk
&i2s0_sdi0
&i2s0_sdi1
&i2s0_sdi2
&i2s0_sdi3
&i2s0_sdo0
&i2s0_sdo1
&i2s0_sdo2
&i2s0_sdo3>;
#sound-dai-cells = <0>;
status = "disabled";
};

浙公网安备 33010602011771号