title: Linux音频驱动开发 tags:
音频系统需要将声音信号转换为数字信号(ADC)或将数字信号转换为声音信号(DAC)。虽然可以使用独立的ADC和DAC芯片,但专业音频CODEC芯片集成了更多功能:
| 功能 | 说明 |
|---|---|
| ADC/DAC | 模数/数模转换 |
| DSP单元 | 音效处理、降噪 |
| 标准接口 | I2S、I2C等统一接口 |
| 功率放大 | 内置耳机/喇叭驱动 |
| 混音器 | 多路音频信号混合 |
音频CODEC主要性能指标:
WM8960是Wolfson公司的低功耗立体声音频CODEC,主要特性:
┌─────────────────────────────────────────────────────────────┐
│ WM8960 主要特性 │
├─────────────────────────────────────────────────────────────┤
│ ADC参数: │
│ - SNR: 94dB (3.3V, 48KHz) │
│ - THD: -82dB │
│ - 24位高精度 │
├─────────────────────────────────────────────────────────────┤
│ DAC参数: │
│ - SNR: 98dB (3.3V, 48KHz) │
│ - THD: -84dB │
│ - 24位高精度 │
├─────────────────────────────────────────────────────────────┤
│ 功能特性: │
│ - 3D增强效果 │
│ - 双路D类功放(1W/8Ω) │
│ - 耳机驱动 │
│ - 数字麦克风接口 │
│ - 支持8K~48K采样率 │
└─────────────────────────────────────────────────────────────┘
WM8960引脚功能:
ES8388是上海屹恒微电子的低功耗立体声CODEC,主要特性:
| 模块 | 特性 |
|---|---|
| ADC | 24位,8K~96K采样率,95dB动态范围 |
| DAC | 24位,8K~96K采样率,96dB动态范围 |
| 功耗 | 播放7mW,录放同步16mW |
| 接口 | I2C/SPI配置,I2S/DSP/PCM音频格式 |
| 驱动 | 40mW耳机驱动,免耦电容输出 |
I2S(Inter-IC Sound)是飞利浦公司提出的数字音频传输总线,需要3根信号线(全双工需要4根):
| 信号线 | 名称 | 功能 |
|---|---|---|
| SCK/BCLK | 位时钟 | 每个数据位对应一个时钟脉冲 |
| WS/LRCK | 帧时钟 | 切换左右声道,频率=采样率 |
| SD/DATA | 串行数据 | 实际音频数据 |
| MCLK | 主时钟 | 系统同步时钟,通常为256fs或384fs |
时钟计算公式:
BCLK = 2 × 采样率 × 采样位数
例如:44.1KHz、16位立体声
BCLK = 2 × 44100 × 16 = 1.4112MHz
I2S时序图:
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
WS/LRCK│ │ │ │ │ │ │ │ │ │
└───┘ └───┘ └───┘ └───┘ └───┘
←─── LEFT ───→←─── RIGHT ───→
┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐
BCLK │└┐│└┐│└┐│└┐│└┐│└┐│└┐│└┐│└┐│└┐│└┐│└┐│
└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘└─┘
DATA D15 D14 D13 ... D1 D0 D15 D14 ... D1 D0
←─ LEFT ─→ ←─ RIGHT ─→
I2S数据格式:
SAI(Synchronous Audio Interface)是I.MX6ULL的同步音频接口,主要特性:
| 特性 | 参数 |
|---|---|
| 工作模式 | 全双工、帧同步串行接口 |
| 支持协议 | I2S、AC97、TDM、音频DSP |
| 帧长度 | 最大32个字 |
| 字大小 | 8bit或32bit |
| FIFO深度 | 每个收发通道32×32bit |
| 错误处理 | FIFO错误后支持平滑重启 |
ALSA(Advanced Linux Sound Architecture)是Linux内核的标准音频框架:
graph TB
subgraph "用户空间"
APP[应用程序] --> ALSALIB[alsa-lib]
ALSALIB --> PCM[PCM设备]
ALSALIB --> CTL[Control设备]
end
subgraph "内核空间"
PCM --> SOC[ASoC框架]
CTL --> SOC
subgraph "ASoC三驱动架构"
SOC --> CPU[CPU DAI驱动]
SOC --> CODEC[Codec驱动]
SOC --> MACHINE[Machine驱动]
end
CPU --> SAI[SAI控制器]
CODEC --> WM[WM8960/ES8388]
MACHINE --> GLUE[音频路由 glue]
end
SAI <--> I2S[I2S总线]
I2S <--> WM
ASoC(ALSA System on Chip)将音频驱动分为三个独立组件:
| 组件 | 作用 | 典型文件 |
|---|---|---|
| CPU DAI | SoC音频接口驱动 | fsl_sai.c |
| Codec | 音频编解码器驱动 | wm8960.c、es8388.c |
| Machine | 板级音频路由配置 | imx-audio-wm8960.c |
这种架构的优势:
PCM(Pulse-Code Modulation)设备用于音频数据的播放和录制:
/dev/snd/pcmC0D0p # 声卡0 设备0 播放(playback)
/dev/snd/pcmC0D0c # 声卡0 设备0 录音(capture)
/dev/snd/pcmC0D1p # 声卡0 设备1 播放
Control设备用于配置声卡参数(音量、开关等):
/dev/snd/controlC0 # 声卡0 控制设备
可通过amixer工具操作:
amixer scontrols # 查看所有简单控制项
amixer scontents # 查看控制项当前值
amixer sset 'Headphone' 100 # 设置耳机音量
amixer sget 'Headphone' # 获取耳机音量
| 结构体 | 作用 |
|---|---|
snd_card |
声卡主结构体,包含所有音频设备 |
snd_pcm |
PCM设备结构体 |
snd_pcm_hardware |
PCM硬件参数定义 |
snd_soc_dai_link |
DAI链接配置 |
snd_soc_card |
声卡配置结构体 |
I.MX6ULL使用SAI2连接音频Codec:
&sai2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai2>;
assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>,
<&clks IMX6UL_CLK_SAI2>;
assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <0>, <11289600>;
status = "okay";
};
&i2c1 {
codec1: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clks IMX6UL_CLK_SAI2>;
clock-names = "mclk";
wlf,shared-lrclk;
};
};
&i2c1 {
codec: es8388@11 {
compatible = "alientek,es8388";
reg = <0x11>;
clock-names = "mclk";
clocks = <&clks IMX6UL_CLK_SAI2>;
hp-det-gpio = <&gpio5 4 GPIO_ACTIVE_HIGH>;
AVDD-supply = <®_audio_pwr>;
DVDD-supply = <®_audio_pwr>;
HPVDD-supply = <®_audio_pwr>;
PVDD-supply = <®_audio_pwr>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_es8388_hp_det_b>;
};
};
/ {
sound {
compatible = "fsl,imx6ul-evk-wm8960",
"fsl,imx-audio-wm8960";
model = "wm8960-audio";
cpu-dai = <&sai2>;
audio-codec = <&codec1 &codec2>;
asrc-controller = <&asrc>;
codec-master;
gpr = <&gpr 4 0x100000 0x100000>;
hp-det = <3 0>;
audio-routing =
"Headphone Jack", "HP_L",
"Headphone Jack", "HP_R",
"Ext Spk", "SPK_LP",
"Ext Spk", "SPK_LN",
"Ext Spk", "SPK_RP",
"Ext Spk", "SPK_RN",
"LINPUT2", "Mic Jack",
"LINPUT3", "Mic Jack",
"RINPUT1", "Main MIC",
"RINPUT2", "Main MIC",
"Mic Jack", "MICB",
"Main MIC", "MICB",
"CPU-Playback", "ASRC-Playback",
"Playback", "CPU-Playback",
"ASRC-Capture", "CPU-Capture",
"CPU-Capture", "Capture";
};
};
/ {
regulators {
reg_audio_pwr: regulator-audio-pwr {
compatible = "regulator-fixed";
regulator-name = "audio-pwr";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
enable-active-high;
};
};
sound-es8388 {
compatible = "fsl,imx-audio-es8388";
model = "es8388-audio";
audio-cpu = <&sai2>;
audio-codec = <&codec>;
audio-asrc = <&asrc>;
gpr = <&gpr 4 0x100000 0x100000>;
audio-routing =
"Headphone Jack", "LOUT1",
"Headphone Jack", "ROUT1",
"Ext Spk", "LOUT2",
"Ext Spk", "ROUT2",
"LINPUT1", "Line In Jack",
"RINPUT1", "Line In Jack",
"LINPUT2", "Mic Jack",
"RINPUT2", "Mic Jack",
"Mic Jack", "Mic Bias",
"CPU-Playback", "ASRC-Playback",
"Playback", "CPU-Playback",
"ASRC-Capture", "CPU-Capture",
"CPU-Capture", "Capture";
};
};
&iomuxc {
imx6ul-evk {
pinctrl_sai2: sai2grp {
fsl,pins = <
MX6UL_PAD_JTAG_TDI__SAI2_TX_BCLK 0x17088
MX6UL_PAD_JTAG_TDO__SAI2_TX_SYNC 0x17088
MX6UL_PAD_JTAG_TRST_B__SAI2_TX_DATA 0x11088
MX6UL_PAD_JTAG_TCK__SAI2_RX_DATA 0x11088
MX6UL_PAD_JTAG_TMS__SAI2_MCLK 0x17088
>;
};
};
};
// 创建声卡
struct snd_card *snd_card_new(struct device *parent,
const char *id,
struct module *owner,
int extra_size);
// 注册声卡
int snd_card_register(struct snd_card *card);
// 释放声卡
void snd_card_free(struct snd_card *card);
// 创建PCM设备
int snd_pcm_new(struct snd_card *card,
const char *id,
int device,
int playback_count,
int capture_count,
struct snd_pcm **rpcm);
// 设置PCM硬件参数
int snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm,
const struct snd_pcm_hardware *hw,
struct device *dev,
size_t size);
// 注册声卡
int snd_soc_register_card(struct snd_soc_card *card);
// 注册DAI驱动
int snd_soc_register_dai(struct snd_soc_component *component,
struct snd_soc_dai_driver *dai_drv);
// 注册Codec驱动
int snd_soc_register_component(struct device *dev,
const struct snd_soc_component_driver *cmpnt_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai);
Machine驱动是连接CPU DAI和Codec的胶水层:
// imx-audio-wm8960.c 关键结构体
static struct snd_soc_dai_link imx_wm8960_dai = {
.name = "HiFi",
.stream_name = "HiFi",
.cpu_dai_name = "sai2",
.codec_dai_name = "wm8960-hifi",
.platform_name = "fsl-sai",
.codec_name = "1-001a",
.init = imx_wm8960_init,
.ops = &imx_wm8960_hifi_ops,
};
static struct snd_soc_card imx_wm8960_card = {
.name = "imx6ul-evk",
.owner = THIS_MODULE,
.dai_link = &imx_wm8960_dai,
.num_links = 1,
.dapm_widgets = imx_wm8960_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets),
.dapm_routes = imx_wm8960_routes,
.num_dapm_routes = ARRAY_SIZE(imx_wm8960_routes),
};
I.MX6ULL SAI驱动核心结构:
// fsl_sai.c 关键回调
static struct snd_soc_dai_driver fsl_sai_dai = {
.playback = {
.stream_name = "CPU-Playback",
.channels_min = 1,
.channels_max = 32,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.capture = {
.stream_name = "CPU-Capture",
.channels_min = 1,
.channels_max = 32,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &fsl_sai_pcm_dai_ops,
.ops = &fsl_sai_pcm_dai_ops,
};
WM8960 Codec驱动关键代码:
// wm8960.c 寄存器默认值
static const struct reg_default wm8960_reg_defaults[] = {
{ 0x0, 0x00a7 }, // 左输入音量
{ 0x1, 0x00a7 }, // 右输入音量
{ 0x2, 0x0000 }, // 左输出音量
{ 0x3, 0x0000 }, // 右输出音量
// ... 共58个寄存器
{ 0x17, 0x01c4 }, // ADC控制 - 左右声道使用左ADC
{ 0x37, 0x00e9 }, // 类DAC控制
};
// DAI驱动
static struct snd_soc_dai_driver wm8960_dai = {
.name = "wm8960-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8960_RATES,
.formats = WM8960_FORMATS,
},
.ops = &wm8960_hifi_dai_ops,
};
// 音频路由定义
static const struct snd_kcontrol_new imx_wm8960_controls[] = {
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
SOC_DAPM_PIN_SWITCH("Ext Spk"),
SOC_DAPM_PIN_SWITCH("Mic Jack"),
};
static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", imx_hifi_hp_event),
SND_SOC_DAPM_SPK("Ext Spk", imx_hifi_spk_event),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
};
static const struct snd_soc_dapm_route imx_wm8960_routes[] = {
{ "Headphone Jack", NULL, "HP_L" },
{ "Headphone Jack", NULL, "HP_R" },
{ "Ext Spk", NULL, "SPK_LP" },
{ "Ext Spk", NULL, "SPK_LN" },
{ "Ext Spk", NULL, "SPK_RP" },
{ "Ext Spk", NULL, "SPK_RN" },
};
# 查看声卡信息
cat /proc/asound/cards
# 查看PCM设备
cat /proc/asound/pcm
# 查看所有控制项
amixer controls
# 查看简单控制项
amixer scontrols
# 查看控制值
amixer scontents
# 设置声卡参数
amixer sset Headphone 100,100
amixer sset Speaker 1 20,120
amixer sset 'Right Output Mixer PCM' on
amixer sset 'Left Output Mixer PCM' on
# 播放WAV文件
aplay test.wav
# 播放指定设备
aplay -D plughw:0,0 test.wav
# 查看播放信息
aplay -l
# MIC录音配置脚本
#!/bin/sh
amixer cset name='Capture Volume' 90,90
amixer sset 'PCM Playback' on
amixer sset 'Playback' 256
amixer sset 'Right Output Mixer PCM' on
amixer sset 'Left Output Mixer PCM' on
amixer sset 'ADC PCM' 200
amixer sset Headphone 125,125
amixer sset Speaker 125,125
amixer sset 'Speaker AC' 4
amixer sset 'Speaker DC' 4
# 录制10秒CD质量音频
arecord -f cd -d 10 record.wav
# 录制完成后播放
aplay record.wav
#!/bin/sh
amixer cset name='Capture Volume' 100,100
amixer sset 'PCM Playback' on
amixer sset 'Playback' 256
amixer sset 'Right Output Mixer PCM' on
amixer sset 'Left Output Mixer PCM' on
amixer sset 'ADC PCM' 200
# Line IN输入配置
amixer sset 'Left Input Mixer Boost' on
amixer sset 'Left Boost Mixer LINPUT2' on
amixer sset 'Left Input Boost Mixer LINPUT2' 127
amixer sset 'Right Input Mixer Boost' on
amixer sset 'Right Boost Mixer RINPUT3' on
amixer sset 'Right Input Boost Mixer RINPUT3' 127
# 录制音频
arecord -f cd -d 10 line_in.wav
# 保存声卡设置
alsactl -f /var/lib/alsa/asound.state store
# 恢复声卡设置
alsactl -f /var/lib/alsa/asound.state restore
# 在/etc/init.d/rcS中添加开机恢复
if [ -f "/var/lib/alsa/asound.state" ]; then
echo "ALSA: Restoring mixer setting......"
/sbin/alsactl -f /var/lib/alsa/asound.state restore &
fi
# 取消OSS API
-> Device Drivers
-> Sound card support (SOUND=y)
-> Advanced Linux Sound Architecture (SND=y)
-> <> OSS Mixer API
-> <> OSS PCM (digital audio) API
# 使能WM8960驱动
-> Device Drivers
-> Sound card support (SOUND=y)
-> Advanced Linux Sound Architecture (SND=y)
-> ALSA for SoC audio support (SND_SOC=y)
-> SoC Audio for Freescale CPUs
-> <*> Asynchronous Sample Rate Converter (ASRC)
-> <*> SoC Audio support for i.MX boards with wm8960
| 特性 | I.MX6ULL | STM32F4/F7 | RK3568 |
|---|---|---|---|
| 音频接口 | SAI | SAI/I2S | I2S |
| 支持协议 | I2S/AC97/TDM | I2S/TDM/PCM | I2S/PCM |
| FIFO深度 | 32×32bit | 32×16bit | 64×32bit |
| DMA通道 | 独立收发 | 共享 | 独立收发 |
| 主从模式 | 支持 | 支持 | 支持 |
| 常用Codec | WM8960/ES8388 | WM8960/CS43L22 | ES8388/RT5640 |
| Linux支持 | 完善 | 较好 | 完善 |
关键差异:
答:ASoC将音频驱动分为CPU DAI、Codec和Machine三个独立组件:
设计优势:
答:I2S需要3-4根信号线:
| 信号 | 功能 | 频率计算 |
|---|---|---|
| BCLK | 位时钟 | = 2 × 采样率 × 采样位数 |
| LRCK | 帧时钟 | = 采样率 |
| DATA | 音频数据 | - |
| MCLK | 主时钟 | = 256×fs 或 384×fs |
示例:44.1KHz/16位立体声
答:
| 参数 | WM8960 | ES8388 |
|---|---|---|
| 厂商 | Wolfson(欧胜) | 上海屹恒微 |
| ADC性能 | 94dB SNR | 95dB SNR |
| DAC性能 | 98dB SNR | 96dB SNR |
| 最大采样率 | 48KHz | 96KHz |
| 功放 | 双路D类1W | 40mW耳机 |
| 特色 | 3D增强 | 超低功耗 |
选择建议:
答:修改WM8960的R23寄存器(地址0x17)的bit3:2:
| bit3:2 | 左声道来源 | 右声道来源 |
|---|---|---|
| 00 | 左ADC | 右ADC(默认) |
| 01 | 左ADC | 左ADC |
| 10 | 右ADC | 右ADC |
| 11 | 右ADC | 左ADC |
修改方法:将wm8960_reg_defaults中0x17寄存器的值从0x01c0改为0x01c4。
答:
PCM设备:
/dev/snd/pcmC0D0p(播放)、/dev/snd/pcmC0D0c(录音)alsa-lib的snd_pcm_writei/snd_pcm_readi进行数据传输Control设备:
/dev/snd/controlC0amixer工具或alsa-lib的snd_ctl_*系列函数两者分工明确:PCM负责数据流,Control负责参数配置。