05-音频驱动.md 34 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类功放(8Ω负载下每通道1W)                          │
│  - 集成耳机接口                                              │
│  - 集成麦克风接口                                            │
│  - 采样率支持8K/11.025K/12K/16K/22.05K/24K/32K/44.1K/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架构

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。原书第六十五章未讲解 ASoC 框架内部结构,本实验直接使能内核自带的 WM8960/ES8388 驱动。

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节点

说明(原书 65.2/65.3.2):WM8960 所挂 I2C 总线随底板版本不同——V2.4 以前挂 I2C2,V2.4 及以后挂 I2C1;ES8388 始终挂 I2C1。V2.4/V2.8 及以后的 sound 节点通过 audio-codec = <&codec1 &codec2> 同时引用两个 WM8960 子节点。

WM8960节点(I2C1,V2.4及以后)

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

WM8960节点(I2C2,V2.4以前)

&i2c2 {
    codec2: 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

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。原书第六十五章仅介绍“使能内核自带音频驱动”,未涉及自己编写 ASoC/ALSA 驱动所需的注册 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

// 注册声卡(Machine驱动入口)
int snd_soc_register_card(struct snd_soc_card *card);
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card);

// 注册DAI驱动
int snd_soc_register_dai(struct snd_soc_component *component,
                         struct snd_soc_dai_driver *dai_drv);

// 注册Codec/CPU DAI组件(cmpnt_drv 提供 probe/remove/controls/dapm 等)
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);
int devm_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);

关键结构体:

  • snd_soc_component_driver:组件(Codec/CPU DAI/Platform)通用回调集,含 .controls.dapm_widgets.dapm_routes.probe.remove 等。
  • snd_soc_dai_ops:DAI 级操作集,核心回调为 .hw_params(配置采样率、位宽、时钟)、.set_fmt(设置 I2S 格式与主从)、.set_sysclk.trigger.startup.shutdown
  • snd_soc_dai_driver 通过 .ops 指向一个 snd_soc_dai_ops,通过 .playback/.capture 描述 snd_soc_pcm_stream(声道数、采样率、格式)。

5. 完整源码分析

⚠️ 来源说明:本节大部分内容不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。原书第六十五章只在 65.5.3 小节摘录了 wm8960.cwm8960_reg_defaults 数组用于修改单声道 MIC 录音,并未展开分析 imx-audio-wm8960.c/fsl_sai.c 的源码。下表中 5.3 的寄存器数组片段对应原书内容,其余为扩展。

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

5.3 Codec驱动

WM8960 Codec驱动关键代码:

// wm8960.c 寄存器默认值(原书示例代码 65.5.3.2,共 R0~R55 计 56 个寄存器)
static const struct reg_default wm8960_reg_defaults[] = {
    { 0x0, 0x00a7 },
    // ...
    { 0x16, 0x00c3 },
    /*{ 0x17, 0x01c0 },*/   // 原默认值:左右声道各自使用本侧 ADC
    { 0x17, 0x01c4 },      // 修改后:左右声道数据均使用左 ADC
    { 0x18, 0x0000 },
    // ...
    { 0x37, 0x00e9 },      // R55 Class-D Control 2
};

// 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录音测试(WM8960)

在开发板根文件系统的 /music 目录下创建 mic_in_config.sh(对应原书示例代码 65.5.3.1),完整内容如下:

#!/bin/sh
# 正点原子@ALIENTEK
# 设置捕获的音量
amixer cset name='Capture Volume' 90,90

# PCM
amixer sset 'PCM Playback' on
amixer sset 'Playback' 256
amixer sset 'Right Output Mixer PCM' on
amixer sset 'Left Output Mixer PCM' on

# ADC PCM
amixer sset 'ADC PCM' 200

# 耳机/喇叭(扬声器)设置播放音量,直流/交流
# Turn on Headphone
amixer sset 'Headphone Playback ZC' on
# Set the volume of your headphones(98% volume, 127 is the MaxVolume)
amixer sset Headphone 125,125
# Turn on the speaker
amixer sset 'Speaker Playback ZC' on
# Set the volume of your Speaker(98% volume, 127 is the MaxVolume)
amixer sset Speaker 125,125
# Set the volume of your Speaker AC(80% volume, 100 is the MaxVolume)
amixer sset 'Speaker AC' 4
# Set the volume of your Speaker DC(80% volume, 5 is the MaxVolume)
amixer sset 'Speaker DC' 4

# 音频输入,左声道管理
# Turn on Left Input Mixer Boost
amixer sset 'Left Input Mixer Boost' off
amixer sset 'Left Boost Mixer LINPUT1' off
amixer sset 'Left Input Boost Mixer LINPUT1' 0
amixer sset 'Left Boost Mixer LINPUT2' off
amixer sset 'Left Input Boost Mixer LINPUT2' 0
# Turn off Left Boost Mixer LINPUT3
amixer sset 'Left Boost Mixer LINPUT3' off
amixer sset 'Left Input Boost Mixer LINPUT3' 0

# 音频输入,右声道管理
# Turn on Right Input Mixer Boost
amixer sset 'Right Input Mixer Boost' on
amixer sset 'Right Boost Mixer RINPUT1' off
amixer sset 'Right Input Boost Mixer RINPUT2' 0
amixer sset 'Right Boost Mixer RINPUT2' on
amixer sset 'Right Input Boost Mixer RINPUT2' 127
amixer sset 'Right Boost Mixer RINPUT3' off
amixer sset 'Right Input Boost Mixer RINPUT3' 0
chmod 777 mic_in_config.sh   # 给予可执行权限
./mic_in_config.sh           # 运行
arecord -f cd -d 10 record.wav   # 录制10秒CD质量音频
aplay record.wav                 # 录制完成后播放

说明:-f cd 表示录音质量为 CD 级别(16bit/44.1kHz/立体声),-d 指定录音时间(单位 s)。ALPHA 开发板的 MIC 只接了左声道,因此录出的音频只有左声道有数据。

单声道 MIC 录制立体声音频

要让只接左声道的 MIC 录出双声道,可修改 WM8960 的 R23 寄存器(地址 0x17)的 bit3:2,让左右声道 ADC 都使用左 ADC 数据。wm8960_reg_defaults0x17 默认值为 0x01C0,改为 0x01C4 即可(详见第 8 节 Q4)。修改后需重新编译内核并启动。

6.4 LINE IN录音测试(WM8960)

Line IN 接口不能接话筒,需使用一根 3.5mm 公对公音频线连接手机/电脑与开发板 Line in 接口。

创建 line_in_config.sh(对应原书示例代码 65.5.4.2):

#!/bin/sh
# 正点原子@ALIENTEK
# 设置捕获的音量
amixer cset name='Capture Volume' 100,100

# PCM
amixer sset 'PCM Playback' on
amixer sset 'Playback' 256
amixer sset 'Right Output Mixer PCM' on
amixer sset 'Left Output Mixer PCM' on

# ADC PCM
amixer sset 'ADC PCM' 200

# 耳机/喇叭(扬声器)设置播放音量,直流/交流
amixer sset 'Headphone Playback ZC' on
amixer sset Headphone 125,125
amixer sset 'Speaker Playback ZC' on
amixer sset Speaker 125,125
amixer sset 'Speaker AC' 4
amixer sset 'Speaker DC' 4

# 音频输入,左声道管理
# Turn off Left Input Mixer Boost
amixer sset 'Left Input Mixer Boost' on
# 关闭其他通道输入
amixer sset 'Left Boost Mixer LINPUT1' off
amixer sset 'Left Input Boost Mixer LINPUT1' 0
# 关闭麦克风左声道输入
amixer sset 'Left Boost Mixer LINPUT2' on
amixer sset 'Left Input Boost Mixer LINPUT2' 127
# Line_in 右声道输入关闭
amixer sset 'Left Boost Mixer LINPUT3' off
amixer sset 'Left Input Boost Mixer LINPUT3' 0

# 音频输入,右声道管理
# Turn on Right Input Mixer Boost
amixer sset 'Right Input Mixer Boost' on
amixer sset 'Right Boost Mixer RINPUT1' off
amixer sset 'Right Input Boost Mixer RINPUT1' 0
amixer sset 'Right Boost Mixer RINPUT2' off
amixer sset 'Right Input Boost Mixer RINPUT2' 0

# 要想设置成音频输入,请打开 RINPUT3,看原理图可知
# 其他的声道通过上面的配置可关闭,这样是为了避免干扰,需要的时候就打开
# RINPUT3 打开(关键点)
amixer sset 'Right Boost Mixer RINPUT3' on
amixer sset 'Right Input Boost Mixer RINPUT3' 127
chmod 777 line_in_config.sh
./line_in_config.sh
arecord -f cd -d 10 record.wav

ALPHA 开发板 Line IN 接的是左右双声道,因此录制出来是立体声,不像 MIC 只有左声道。若在 MIC 实验里把 R23 改为 0x01C4,做 Line IN 测试前应先改回 0x01C0(也可不改,不影响测试)。

6.4.1 ES8388 录音测试(V2.8 及以后底板)

出厂文件系统已预置音频测试脚本 /home/root/shell/audio/atk-audio.sh,直接在文件系统中执行 ./atk-audio.sh,按提示即可进行板载 MIC 录音测试和 Line IN 录音测试。

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

# 使能ES8388驱动(V2.8及以后底板)
SND_SOC_FSL_ASOC_CARD = y   # i.MX ASoC card 支持
SND_SOC_ES8388 = y          # ES8388 codec 驱动

ES8388 使能后重新编译内核与设备树,启动后 ALSA 设备列表中应出现 es8388-audio(与设备树 sound-es8388 节点 model 一致的声卡名)。

6.7 alsa-lib / alsa-utils 移植

源码下载地址 http://www.alsa-project.org/main/index.php/Main_Page,原书使用 1.2.2 版本(光盘路径:例程源码 → 第三方库源码 → alsa-lib-1.2.2.tar.bz2alsa-utils-1.2.2.tar.bz2)。

关键:配置文件路径一致性。alsa-lib 编译时会生成含绝对路径的配置文件,因此 Ubuntu 与开发板根文件系统必须创建同名目录:

# Ubuntu
cd /usr/share
sudo mkdir arm-alsa
# 开发板根文件系统
mkdir /usr/share/arm-alsa -p

移植 alsa-lib

tar -vxjf alsa-lib-1.2.2.tar.bz2
cd alsa-lib-1.2.2/
./configure --host=arm-linux-gnueabihf --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib --with-configdir=/usr/share/arm-alsa
make
sudo make install

libatopology.la 编译失败(sudo 切到 root 后找不到交叉编译器),先 sudo -s 切到 root,再 source /etc/profile,然后直接 make install(此时无需 sudo),完成后 su zuozhongkai 回到原用户。

编译结果:alsa-lib/lib 下文件拷贝到开发板 /usr/lib/usr/share/arm-alsa 下文件拷贝到开发板 /usr/share/arm-alsa

cd alsa-lib
sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/lib/ -af
cd /usr/share/arm-alsa
sudo cp * /home/zuozhongkai/linux/nfs/rootfs/usr/share/arm-alsa/ -raf

移植 alsa-utils

tar -vxjf alsa-utils-1.2.2.tar.bz2
cd alsa-utils-1.2.2/
./configure --host=arm-linux-gnueabihf --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-utils --with-alsa-inc-prefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/include/ --with-alsa-prefix=/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/lib/ --disable-alsamixer --disable-xmlto
make
sudo make install

说明:--disable-alsamixer 是因为 alsamixer 依赖 ncurses 库且原书未能成功交叉编译,但可用同功能的命令行工具 amixer(alsamixer 的图形化版本)。原书另用 buildroot 编译出 alsamixer 再拷贝进根文件系统。

将编译出的 binsbinshare 分别拷贝到开发板 /bin/sbin/usr/share,并在 /etc/profile 中加入:

export ALSA_CONFIG_PATH=/usr/share/arm-alsa/alsa.conf

6.8 mplayer 播放器移植与使用

mplayer 依赖 zlib 与 alsa-lib。

1)移植 zlib(zlib-1.2.11.tar.gz):

tar -vxzf zlib-1.2.11.tar.gz
cd zlib-1.2.11/
CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld AD=arm-linux-gnueabihf-as ./configure --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/zlib
make
make install
sudo cp lib/* /home/zuozhongkai/linux/nfs/rootfs/lib/ -rfa

2)移植 mplayer(MPlayer-1.4.tar.gz):

tar -vxzf MPlayer-1.4.tar.gz
cd MPlayer-1.4/
./configure --cc=arm-linux-gnueabihf-gcc --host-cc=gcc --target=arm-linux-gnueabihf --disable-ossaudio --enable-alsa --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/mplayer --extra-cflags="-I /home/zuozhongkai/linux/IMX6ULL/tool/zlib/include -I /home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/include" --extra-ldflags="-L/home/zuozhongkai/linux/IMX6ULL/tool/zlib/lib -lz -L/home/zuozhongkai/linux/IMX6ULL/tool/alsa-lib/lib -lasound" --enable-fbdev --disable-mencoder
make

编译后编辑 config.mak,把 INSTALLSTRIP = -s 后面的 -s 去掉,否则 make install 会失败。然后:

make install
sudo cp bin/mplayer /home/zuozhongkai/linux/nfs/rootfs/bin/ -f

使用:

mplayer test.flac      # 播放音频
mplayer test.avi -fs   # 全屏/居中播放视频

播放过程中常用按键:9 增加音量、0 减小音量、左/右键倒退/快进 10 秒、上/下键倒退/快进 1 分钟、空格暂停/播放。I.MX6ULL 性能有限且无硬件视频解码,视频分辨率建议 640×480 左右。

6.9 alsamixer 简介

alsamixer 是 amixer 的图形化版本,直接输入 alsamixer 打开。功能键:F1 帮助、F2 系统信息、F3 播放设置、F4 录音设置、F6 选择声卡;左右键选择设置项,上下键调整大小,MM 表示静音,按 M 切换 MM/OO


7. 跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

特性 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. 面试精选

⚠️ 来源说明:本节面试题不属于《I.MX6U嵌入式Linux驱动开发指南》正文内容,为扩展知识;仅 Q4 涉及的 WM8960 R23 寄存器取值来自原书 65.5.3 小节。

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负责参数配置。


内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第六十五章 Linux 音频驱动实验