05-音频驱动.md 23 KB


title: Linux音频驱动开发 tags:

  • 嵌入式Linux
  • 音频驱动
  • ALSA
  • I2S
  • WM8960
  • ES8388
  • SAI created: 2024-01-15 updated: 2024-01-15 pdf_ref: 71_第六十五章 Linux 音频驱动实验.pdf ---

Linux音频驱动开发

1. 音频硬件概述

1.1 为什么需要音频CODEC芯片

音频系统需要将声音信号转换为数字信号(ADC)或将数字信号转换为声音信号(DAC)。虽然可以使用独立的ADC和DAC芯片,但专业音频CODEC芯片集成了更多功能:

功能 说明
ADC/DAC 模数/数模转换
DSP单元 音效处理、降噪
标准接口 I2S、I2C等统一接口
功率放大 内置耳机/喇叭驱动
混音器 多路音频信号混合

音频CODEC主要性能指标:

  • 采样率:8KHz、44.1KHz、48KHz、96KHz、192KHz等
  • 采样位数:8位、16位、24位、32位
  • 信噪比(SNR):越高音质越好
  • 总谐波失真(THD):越低音质越好

1.2 WM8960音频芯片

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引脚功能:

  • 输入接口:LINPUT1/RINPUT1、LINPUT2/RINPUT2、LINPUT3/RINPUT3
  • 输出接口:SPK_LP/SPK_LN(左声道喇叭)、SPK_RP/SPK_RN(右声道喇叭)、HP_L/HP_R(耳机)
  • 数字音频接口:ADCDAT、ADCLRC、DACDAT、DACLRC、BCLK、MCLK

1.3 ES8388音频芯片

ES8388是上海屹恒微电子的低功耗立体声CODEC,主要特性:

模块 特性
ADC 24位,8K~96K采样率,95dB动态范围
DAC 24位,8K~96K采样率,96dB动态范围
功耗 播放7mW,录放同步16mW
接口 I2C/SPI配置,I2S/DSP/PCM音频格式
驱动 40mW耳机驱动,免耦电容输出

1.4 I2S总线接口

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数据格式:

  • 标准I2S:数据在WS变化后第2个BCLK脉冲开始传输
  • 左对齐(Left Justified):数据在WS变化后立即开始传输
  • 右对齐(Right Justified):数据在WS变化前结束

1.5 I.MX6ULL SAI外设

SAI(Synchronous Audio Interface)是I.MX6ULL的同步音频接口,主要特性:

特性 参数
工作模式 全双工、帧同步串行接口
支持协议 I2S、AC97、TDM、音频DSP
帧长度 最大32个字
字大小 8bit或32bit
FIFO深度 每个收发通道32×32bit
错误处理 FIFO错误后支持平滑重启

2. ALSA架构

2.1 ALSA子系统架构

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

2.2 ASoC三驱动架构详解

ASoC(ALSA System on Chip)将音频驱动分为三个独立组件:

组件 作用 典型文件
CPU DAI SoC音频接口驱动 fsl_sai.c
Codec 音频编解码器驱动 wm8960.ces8388.c
Machine 板级音频路由配置 imx-audio-wm8960.c

这种架构的优势:

  • 可重用性:同一Codec可配合不同SoC使用
  • 可配置性:Machine驱动定义音频路由
  • 可维护性:各组件独立开发维护

2.3 PCM设备

PCM(Pulse-Code Modulation)设备用于音频数据的播放和录制:

/dev/snd/pcmC0D0p    # 声卡0 设备0 播放(playback)
/dev/snd/pcmC0D0c    # 声卡0 设备0 录音(capture)
/dev/snd/pcmC0D1p    # 声卡0 设备1 播放

2.4 Control设备

Control设备用于配置声卡参数(音量、开关等):

/dev/snd/controlC0    # 声卡0 控制设备

可通过amixer工具操作:

amixer scontrols      # 查看所有简单控制项
amixer scontents      # 查看控制项当前值
amixer sset 'Headphone' 100   # 设置耳机音量
amixer sget 'Headphone'       # 获取耳机音量

2.5 核心数据结构

结构体 作用
snd_card 声卡主结构体,包含所有音频设备
snd_pcm PCM设备结构体
snd_pcm_hardware PCM硬件参数定义
snd_soc_dai_link DAI链接配置
snd_soc_card 声卡配置结构体

3. 音频设备树配置

3.1 I2S节点配置

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";
};

3.2 Audio Codec节点

WM8960节点(I2C1)

&i2c1 {
    codec1: wm8960@1a {
        compatible = "wlf,wm8960";
        reg = <0x1a>;
        clocks = <&clks IMX6UL_CLK_SAI2>;
        clock-names = "mclk";
        wlf,shared-lrclk;
    };
};

ES8388节点(I2C1)

&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  = <&reg_audio_pwr>;
        DVDD-supply  = <&reg_audio_pwr>;
        HPVDD-supply = <&reg_audio_pwr>;
        PVDD-supply  = <&reg_audio_pwr>;

        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_es8388_hp_det_b>;
    };
};

3.3 Sound节点配置

WM8960 Sound节点

/ {
    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";
    };
};

ES8388 Sound节点

/ {
    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";
    };
};

3.4 引脚配置

&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
            >;
        };
    };
};

4. 音频驱动API

4.1 声卡注册API

// 创建声卡
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);

4.2 PCM设备API

// 创建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);

4.3 ASoC注册API

// 注册声卡
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);

5. 完整源码分析

5.1 Machine驱动框架

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),
};

5.2 I2S控制器驱动

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,
};

5.3 Codec驱动

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,
};

5.4 DAPM动态音频电源管理

// 音频路由定义
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" },
};

6. 实验验证

6.1 声卡设置工具

# 查看声卡信息
cat /proc/asound/cards

# 查看PCM设备
cat /proc/asound/pcm

# 查看所有控制项
amixer controls

# 查看简单控制项
amixer scontrols

# 查看控制值
amixer scontents

6.2 播放测试

# 设置声卡参数
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

6.3 录音测试

# 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

6.4 Line IN录音配置

#!/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

6.5 开机自动配置

# 保存声卡设置
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

6.6 内核配置

# 取消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

7. 跨平台对比

特性 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支持 完善 较好 完善

关键差异:

  • I.MX6ULL:SAI接口灵活,支持ASRC异步采样率转换
  • STM32:HAL库支持好,但Linux驱动社区贡献较少
  • RK3568:性能强劲,支持多路音频同时工作

8. 面试精选

Q1: ASoC三驱动架构是什么?为什么要这样设计?

:ASoC将音频驱动分为CPU DAI、Codec和Machine三个独立组件:

  1. CPU DAI驱动:处理SoC音频接口(如I2S/SAI),负责DMA传输和时钟配置
  2. Codec驱动:处理音频编解码,配置ADC/DAC、混音器、音量控制
  3. Machine驱动:板级配置,定义音频路由和DAI链接

设计优势

  • 可重用性:同一Codec可配合不同SoC使用
  • 可配置性:通过设备树灵活配置音频路由
  • 可维护性:各组件独立开发,降低耦合度

Q2: I2S协议的时钟信号有哪些?如何计算?

:I2S需要3-4根信号线:

信号 功能 频率计算
BCLK 位时钟 = 2 × 采样率 × 采样位数
LRCK 帧时钟 = 采样率
DATA 音频数据 -
MCLK 主时钟 = 256×fs 或 384×fs

示例:44.1KHz/16位立体声

  • BCLK = 2 × 44100 × 16 = 1.4112MHz
  • LRCK = 44.1KHz
  • MCLK = 256 × 44100 = 11.2896MHz

Q3: WM8960和ES8388的主要区别是什么?

参数 WM8960 ES8388
厂商 Wolfson(欧胜) 上海屹恒微
ADC性能 94dB SNR 95dB SNR
DAC性能 98dB SNR 96dB SNR
最大采样率 48KHz 96KHz
功放 双路D类1W 40mW耳机
特色 3D增强 超低功耗

选择建议

  • 追求性价比和喇叭驱动:WM8960
  • 追求高采样率和低功耗:ES8388

Q4: 如何让单声道MIC录制立体声音频?

:修改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

Q5: ALSA框架中PCM设备和Control设备的作用是什么?

PCM设备

  • 用途:音频数据的播放和录制
  • 设备节点:/dev/snd/pcmC0D0p(播放)、/dev/snd/pcmC0D0c(录音)
  • 操作:通过alsa-libsnd_pcm_writei/snd_pcm_readi进行数据传输

Control设备

  • 用途:配置声卡参数(音量、开关、路由等)
  • 设备节点:/dev/snd/controlC0
  • 操作:通过amixer工具或alsa-libsnd_ctl_*系列函数

两者分工明确:PCM负责数据流,Control负责参数配置。