Audio开发指南

REVISION HISTORY

Revision No.
Description
Date
1.0 Initial release 04/05/2023
1.1 1.Channel params
2.Headset hot-plug
3.Dmix plug
08/08/2023

前言

本章主要描述Audio的相关概念、代码结构。

1. 概述

1.1. 概述

本章指导客户了解 sigmastar 平台 alsa 架构和使用方法,达到初步使用 alsa 和 定位音频问题的目的。

sigmastar 平台 alsa 架构遵从标准 alsa 架构,标准 alsa 解耦方案框图如下。在维持标准系统架构不变的前提下,将私有业务行为融合并同步于标准 asla 系统中,主要涉及 2 个方面,音量控制方案&外设通路播放。

遵从标准 alsa-lib 实现解耦,也是解耦方案的核心所在。向上提供接口供用户层 application (如 alsa-utils 或者 参考示例 prog_alsa_demo)调用,向下调用 alsa 标准接口实现音量、设备切换等功能。涉及主要方案如下:

| Application   |       alsa-utils       |  |      tinyalsa            |
|               | aplay  arecord  amixer |  | tinyplay tinycap tinymix |
|----------------------------------------------------------------------|
| User Space    |       alsa-lib         |  |      tinyalsa-lib        |
|----------------------------------------------------------------------|
| Kernel Space  |               alsa Core & asoc core                  |
|----------------------------------------------------------------------|
| Device driver |                    sound driver                      |
|----------------------------------------------------------------------|
|                                      Hardware                        |

1.2. 概念

名称 描述
DAI Diaital Audio Interfaces 数字音频接口
CPU DAI 主控端的 Audio Data Interface,比如 I2S,Spdif,Pdm,Tdm
CODEC DAI 即 Codec
DAI_LINK 绑定 Cpu_Dai 和 Codec_Dai 为一个声卡,等同于 Machine Driver
DMAENGINE 用于 Cpu 和 I2S/Spdif 等 Dai 之间的 Dma 传输引擎,实际是通过 Dma 来进行数据的搬运
DAPM 动态音频电源管理,用于动态管理 Codec 等的电源管理,根据通路的开启配置开关,以达到保证功能的前提下功耗尽量小
JACK 耳机的接口检测,大部分使用 Codec 自身的检测机制,小部分使用 IO 来进行模拟

1.3. Audio ALSA 框架和Audio音频拓扑图

Pioneer5_ALSA_For_Customer

1.4. 代码结构

项目 功能 路径
Sound soc 主要包含公共部分代码,包括 dapm 控制, jack,dmaengine, core 等等 sound/soc/
Sigmastar platform Simastar 平台的 cpu dai 的驱动以及自定义声卡 machine driver sound/soc/sstar
Codec driver 所有的 codec driver 存放位置 sound/soc/codecs

1.5. 通路参数支持

通路类型 采样率(Sampling rate/ khz) 位深(Bit width) 通道(channel) 格式(pcm format)
I2S0_TX 8/16/32/44.1/48/96/192 16 ½/4/8 standard,left justified
I2S0_RX 8/16/32/44.1/48/96/192 16 ½/4/8 standard,left justified
I2S0_MCK 11289.6/12288/16384/19200/24000/48000 NA NA NA
I2S1_TX 8/16/32/44.1/48/96/192 16 ½ standard,left justified
I2S1_RX 8/16/32/44.1/48/96/192 16 ½ standard,left justified
I2S1_MCK 11289.6/12288/16384/19200/24000/48000 NA NA NA
LINE_IN 8/16/32/48 16 2 NA
LINE_OUT 8/11.025/12/16/22.05/24/32/44.1/48 16 2 NA
SPDIF_TX 32/44.1/48 16 2 LPCM/RAW
HDMI_RX 32/44.1/48 16 2 NA
DMIC 8/16/32/48 16 8 NA
ECHO0_RX 8/16/32/48 16 2 NA
ECHO0_TX 8/16/32/48 16 2 NA
ECHO1_RX 8/16/32/48 16 2 NA
ECHO1_TX 8/16/32/48 16 2 NA

2. Audio 开发指南

本章描述如何添加声卡,调试声卡。一个声卡包含 cpu_dai, codec_dai, 以及 dai_link 组成,分别对应 cpu dai 的 dirver,比如 bach driver, bach2 driver;codec driver;dai_link driver,也就 是 machine driver, 比如 sound/soc/sstar/pioneer5/sstar_asoc_card.c。

如果需要对接第三方Codec Alsa Driver,请参考对接第三方Codec Driver

如果仅需要配置I2S,请参考DTS配置中的I2S Format设定

2.1. 关键字说明

  • DMA

    DMA 即 direct memory access 直接存储器访问,DMA 传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实现和完成的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为 RAM 和 IO 设备开辟一条直接传输数据的通道,使得 CPU 的效率大大提高。

  • WDMA

    WDMA 即 direct memory access writer 直接存储器访问写入器。

  • RDMA

    RDMA 即 direct memory access reader 直接存储器访问读取器。

  • MUX

    MUX 即 multiplexer 数据选择器,在多路数据传送中,能够根据需要将其中任意多路选出来的电路。WDMA 前面的 Mux 为 WDMA 选择多个数据源,可支持选择 ½/4 个数据源(数据源可以相同也可以不同),每个数据源各有 2 声道,即选择 2/4/8 个声道的数据由 WDMA 写道 DRAM,起到多路开关的作用。而接近输出外设接口(I2S_TX/HDMI/DAC 等)的 Mux 实现的是多选一的作用。

  • DPGA

    DPGA 即 digital programmable gain amplifier 数字可编程增益放大器,是一种通用性很强的放大器,其放大倍数可以根据需要用程序进行控制。

  • DMIC

    DMIC 即 digital microphone interface 数字麦克风接口,audio codec 仅仅提供 DMIC 接口,并非完整的 DMIC。DMIC 接口提供 DMIC 工作所需要的时钟信号,接收从 DMIC 来的 PDM 信号。

  • ADC

    ADC 即 analog digital conversion 模拟数字转换,将模拟信号转换为数据信号的电子元件。

  • I2S

    I2S 即 inter-IC sound 集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准。Sigmastar 的 I2S 总线仅支持标准 I2S 数据格式以及左对齐的 I2S 数据格式。同时也支持 TDM(Time-Division Multiplexing)时分复用技术,将不同的信号相互交织在不同的时间段内,沿着同一个信道传输,可同时支持 ½/4/8 通道数据传输。

  • DAC

    DAC 即 digital analog conversion 数字模拟转换,将数字信号转换为模拟信号的电子元件。

  • SRC

    SRC 即 sample rate convert 采样率转换,对语音数字信号进行采样率转换。

  • HDMI

    HDMI(high definition multimedia interface, 高清多媒体接口),audio output 输出接口,是一种全数字化视频和音频发送接口,可以发送未压缩的音频及视频信号。

  • Mixer

    Mixer 即混音,使用线性叠加平均的算法(如果某一路音频的音量特别小,整个混音结果音量会被拉低),混音后可以设置输出的采样率。

  • device(音频输入/输出设备)

    audio output 的 device 指的是 audio codec 的 RDMA。device 是 ASOC 对 audio codec 中 DMA 的抽象,1个card 下只挂载 1 个device,audio codec 中的 RDMA 和 output device 为 一一对应的关系。如 card 0、device 0 对应 audio codec 中的 RDMA0,card 1、device 0 对应于audio codec 中的 RDMA1,以此类推。WDMA 和 input device 也是一一对应的关系。如 card 0、device 0 对应 audio codec 中的 WDMA0,card 1、device 0 对应于audio codec 中的 WDMA1。ASOC 的数据流均以 DMA 为中心进行串接。

    ASOC 中还包括另一类 device,即 input 设备不经过 DMA,直接将数据流送至 output 设备,例如(AMIC -> DAC)。这一类 device 称为直通通路 device,即 passthrough ,目前仅 AI_MCH_VIR_SEL/AO_VIR_MUX_SEL 节点支持。

  • interface(音频输出外设)

    output 的 interface 指的是 audio codec 的音频输出外设接口的抽象,如 DAC/SPDIF/I2S_TX/HDMI_TX 等接口。

    input 的 interface 指的是 audio codec 的音频输入外设接口的抽象,如 AMIC/DMIC/I2S_RX/HDMI_RX 等接口。

  • output attach

    output 的 attach 指的是将 interface 挂载到 RDMA 上,对于 output device 而言,attach 是将 RDMA 的输出信号连接到具体的 interface,使外设输出音频信号。output 支持动态 attach。

    硬件混音:即当同一个 output interface 被 attach 到 2 个 output device 时候,output interface 的输出为 2 个 output device 输出混音后的结果。

    直通通路:当使用场景中包含 passthrough 时候,需要先 attach input,再 attach output,否则将不能正常使用。

    分发:即当多个 output interface 被 attach 到 1 个 output device 时候,从 RDMA 输出的信号经过 DPGA 做增益调节后,分成多个支路分别到达各个 interface,各个外设同时输出声音。

  • input attach

    input 的 attach 指的是将 interface 挂载到 WDMA 对应的 MUX 上,即将 device 和 interface 关联起来。对于 input device 而言,attach 是设定 WDMA 对应的MUX,选择哪些 interface 的数据可以通过 MUX ,由 WDMA 做后续的数据搬移。input 不支持动态 attach。

  • detach

    detach 指的是 将 interface 和 RDMA/WDMA 的连接断开。

  • output echo

    output 的 echo 指的是 AEC 的回采参考数据。由 ALSA 框图可以看出,SRC 的输入是 RDMA 输出并经过 DPGA 放大的信号,SRC 的输出则是对输入进行重采样后的信号,可通过 multi channel 送进 WDMA,作为 AEC 算法的回声参考数据。

  • input echo

    input 的 echo 指的是 audio codec 提供的 AEC 参考数据。对于 input device 而言,echo 表示 ALSA 框图中 SRC 的 输出信号。而对于 output device 而言,echo 表示将 output device 的输出连接到 SRC 的输入。下图以简单框图表示 input device 和 output device 同时使用 echo 的 数据流情况,应用即可获取到已经对齐的 AEC far end 和 near end 的数据。

                                               _________
                                           ___|   ADC   | <- AMIC
     _____      ________      _______     |    —————————
    | APP | <- | WDMA 0 | <- |  MCH  | <--|
     —————      ————————      ———————     |     ________
                                          |___ |  SRC   |
                                                ————————
                                                  ^
                             |\                   |
     _____      ________     | \                  |    _____
    | APP | -> | RDMA 0 | -> |DPGA------------------> | DAC | -> Speaker
     —————      ————————     | /                       —————
                             |/
    
  • GAIN

    AI的Gain在ALSA架构上分成两类,一类是与Device相关联的DPGA Gain,即ALSA框图中的DPGA,另一类则是Interface所独有的Gain,目前拥有Gain的Interface有ADC以及Dmic接口。对于AI而言,若Interface本身没有独立的Interface Gain且也不与Dpga相连,则没法做Gain的设定。对AO而言,目前仅能通过Dpga调整Gain

    DPGA即Digital Programmable Gain Amplifier数字可编程增益放大器,是一种通用性很强的放大器,其放大倍数可以根据需要用程序进行控制。

  • format

    即表示用什么数据形式来表示一个音频采样样本,目前仅支持 S16_LE 格式(PCM linear 16 bit little endian)

  • sample rate

    对于 input 表示 录音设备在单位时间内对模拟信号采样的次数,采样频率越高,机械波的波形越真实越自然。

    对于 output 表示播放采样率。

  • period size

    对于 output,period size 表示默认的起播条件(缓存中的样本数大于period size,才会起播)

  • I2S Mode

    决定了 I2S 的工作模式,是标准的 I2S 模式还是 TDM I2S 模式(2 channel 或 多 channel),是 Master 还是 Slave (Master 提供同步时钟,Slave 接收同步时钟)。一般来说,工作模式没有什么限制,能与外接的 codec 时钟匹配上即可。

  • I2S BitWidth

    收发数据的位宽,目前仅支持 16bit,硬件只能处理 16 bit。

  • I2S format

    即 I2S 的对齐方式,目前仅支持 I2S Philips 以及 Left-justified 对齐。

    I2S Philips 的对齐格式,样本数据的第一个数据位出现在 WCLK(左右通道切换时钟)跳变的第一个 BCLK(串行时钟)后。

    Left-justified 对齐格式,样本数据的第一个数据位出现在 WCLK(左右通道切换时钟)跳变的第一个 BCLK(串行时钟)内,且 WCLK 的极性与 I2S Philips 的对齐格式相反

  • MCLK

    称为主时钟,也叫系统时钟(system clock),一般是采样频率的 256 或 384 倍,作用是为了使系统间能够更好的同步,并不是必须的。目前支持 11.2896M、12.288M、16.384M、19.2M、24M、48M 等。

  • 4-wire/6-wire Mode

    Sigmastar 的 I2S 有 2 种接线方式。

    一种是 4-wire 模式,包括 RX_WCK、RX_BCK、RX_SDI、TX_SDO 四根线,此模式下,TX 没有独立的 clock,所有的 clock 均有 RX 提供,故在此模式下 TX 需要依赖 RX 来使用,没法单独使用 TX,且 I2S TX 的参数需要与 I2S RX 一致。

    另一种是 6-wire 模式,包括 RX_WCK、RX_BCK、RX_SDI、TX_WCK、TX_BCK、TX_SDO 六根线,此模式下 RX 和 TX 各自独立,没有关联。

    4-wire/6-wire Mode 的选择需要根据具体场景来决定。属于同一组 I2S 的 RX/TX 不能一边设置成 4-wire Mode,另一边设置成 6-wire Mode。

  • slot

    表示 I2S 传输的声道数,目前 I2S 模式下支持 2 slot,TDM 模式下支持 4/8/16 slot,但是 I2S TX 的有效数据为 2 slot,当对 I2S TX 设置大于 2 的 slot 个数时候,除了 slot 0 和 slot 1,其他的均为无效数据。

  • passthrough (直通通道)

    passthrough 意思指 input 设备直接将数据流送至 output 设备,不经过 DMA,以 ADC -> DAC 为例,

                                             /|    ______
                        ------- <- DPGA -  <- |   | ADC  | <- AMIC
                        |                    \|    ——————
                        |
                        |
                        V    |\           _______
                        ---> |  - DPGA ->|       |
                             |/          |       |     _____
                                         | Mixer | -> | DAC | -> Speaker
     _____      ________     |\          |       |     —————
    | MIU | -> | RDMA 0 | ->   - DPGA -> |       |
     —————      ————————     |/           ———————
    
  • input 的 attach 节点

    AI_MCH_01_SEL:即 MUX 的 0/1 声道

  • output 的 attach 节点

    DMA 是给非硬件混音使用,DMA+SRC 是给硬件混音使用

  • spdif 的 attach 节点

    LDMA 是给 lpcm 格式使用,LDMA+SRC 是给 lpcm 做硬件混音使用,NLDMA 是给 nlpcm 格式播放 compress 使用

  • CHANNEL_MODE

    STEREO:正常的立体声模式

    DOUBLE_MONO:左右 2 个声道输出为同样的单声道数据

    DOUBLE_LEFT:左右 2 个声道输出为左声道数据

    DOUBLE_RIGHT:左右 2 个声道输出为右声道数据

    EXCHANGE:左右 2 个声道输出为左右声道互换数据

    ONLY_LEFT:只有左声道输出为单声道数据

    ONLY_RIGHT:只有右声道输出为单声道数据

2.2. simple-card

Simple card即简单通用的machine driver, 如果simple-card框架足够满足需求,建议优先使用simple card框架,简单,方便,且易用。

  • menuconfig 中打开 ASoC Simple sound card

    打开simple-card,则自定义machine-driver不生效。

    make menuconfig
    Device Drivers --->
        <*> Sound card support --->
            <*> Advanced Linux Sound Architecture --->
                <*> ALSA for SoC audio support --->
                    <*> ASoC support for SigmaStar
                    <*> ASoC Simple sound card support
    
  • 产品的DTS中添加Simple Card Node

    asoc_sound_simple {
        status = "ok";
        compatible = "simple-audio-card";
        simple-audio-card,format = "i2s";
        simple-audio-card,name = "sigmastar,asoc-card";
        simple-audio-card,mclk-fs = <256>;
        simple-audio-card,cpu {
            sound-dai = <&bach1>;
        };
        simple-audio-card,codec {
            sound-dai = <&dummy_codec1>;
        };
    };
    bach1: bach1 {
        status = "ok";
        compatible = "sstar,bach";
        sound-dma = <&dma1>;
        #sound-dai-cells = <0>;
        reg = <0x1F2A0400 0x800>;
    };
    

2.3. 自定义的Machine Driver

  • 当打开simple-card时,Machine Driver不生效.

    menuconfig中关闭simple-card,使自定义machine-driver生效。

    make menuconfig
    Device Drivers --->
        <*> Sound card support --->
            <*> Advanced Linux Sound Architecture --->
                <*> ALSA for SoC audio support --->
                    <*> ASoC support for SigmaStar
                    < > ASoC Simple sound card
    

2.4. 对接第三方Codec Driver

2.4.1. 自定义Machine Driver对接第三方Codec Driver

  • 添加codec driver,比如添加:sound/soc/codec/es8328.c,如果不需要使用I2S的接口的话,可以默认不添加,默认使用sstar_dummy_codec.c。

  • 修改sound/soc/codec/Kconfig以及Makefile加入驱动编译。

    sound/soc/codec/Kconfig:
        config SND_SOC_ES8328_SPI
            tristate "Everest Semi ES8328 CODEC (SPI)"
            depends on SPI_MASTER
            select SND_SOC_ES8328
    
    sound/soc/codec/Makefile:
        snd-soc-es8328-objs := es8328.o
        obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
    
  • menuconfig 中 enable card 以及 codec

    关闭simple-card,使自定义machine-driver生效。

    make menuconfig
    Device Drivers --->
        <*> Sound card support --->
            <*> Advanced Linux Sound Architecture --->
                <*> ALSA for SoC audio support --->
                    <*> ASoC support for SigmaStar
                    < > ASoC Simple sound card
                    CODEC drivers --->
                        <*> SSTAR DUMMY AUX CODEC driver
                        <*> Everest Semi ES8328 CODEC(SPI)
    
  • 在产品的DTS中添加Card Note,根据需要修改DTSI中的设定,如果使用I2S的接口,需要在DTS中设定I2S的相关格式,相关格式的设定规则请参照Sigmastar_Linux_User_Guide_Audio的文档。

    soc {
        sound {
            // I2S RX0 TDM
            i2s-rx0-tdm-mode    = <2>;  // 1:master       2:slave
            i2s-rx0-tdm-fmt     = <1>;  // 1:i2s justify  2:left justify
            i2s-rx0-tdm-wiremode = <2>; // 1:4wire        2:6 wire
            i2s-rx0-channel  = <4>;
            i2s-rx0-tdm-ws-pgm = <0>; // 0: OFF  1: ON
            i2s-rx0-tdm-ws-width = <0>; // value: 0~31 (width = value + 1)
            i2s-rx0-tdm-ws-inv = <0>; // 0: normal  1: inverse WCK
            i2s-rx0-tdm-bck-inv = <0>; // 0: normal  1: inverse BCK
            i2s-rx0-tdm-ch-swap = <0 0 0>; // 0: OFF  1: ON
    
            // I2S TX0 TDM
            i2s-tx0-tdm-mode    = <1>;  // 1:master       2:slave
            i2s-tx0-tdm-fmt     = <1>;  // 1:i2s justify  2:left justify
            i2s-tx0-tdm-wiremode = <2>; // 1:4wire        2:6 wire
            i2s-tx0-channel  = <4>;
            i2s-tx0-tdm-ws-pgm = <0>; // 0: OFF  1: ON
            i2s-tx0-tdm-ws-width = <0>; // value: 0~31 (width = value + 1)
            i2s-tx0-tdm-ws-inv = <0>; // 0: normal  1: inverse WCK
            i2s-tx0-tdm-bck-inv = <0>; // 0: normal  1: inverse BCK
            i2s-tx0-tdm-ch-swap = <0 0 0>; // 0: OFF  1: ON
            i2s-tx0-tdm-active-slot = <0xFF>; // value: 0x00 ~ 0xFF (bit0->slot0, bit1->slot1, ... )
    
            status = "ok";
        };
    
        asoc_sound {
            compatible = "sstar, asoc-card";
            sstar-audio-card,name = "sigmastar,asoc-card";
            sstar-audio-card,cpu {
                sound-dai = <&bach1>;
            };
            sstar-audio-card,codec {
                sound-dai = <&dummy_codec1>, <&es8328>;
            };
        };
    
        bach1:bach1 {
            mclk0-freq = <24000000>;
            status = "okay";
        };
    
        i2c1{
            status = "ok";
            es8328:es8328@18{
                status = "ok";
                #sound-dai-cells = <0>;
                codec-name = "es8328-hifi-analog";
                compatible = "everest,es8328";
                reg = <0x18>;
            };
        };
    };
    
  • 在machine driver(sstar_asoc_card.c)中添加对应的codec关联

    static int sstar_asoc_card_probe(struct platform_device *pdev)
    {
        if (np_codec_0)
        {
            card                                 = &sstar_asoc_card;
            card->dai_link[0].codecs->of_node    = np_codec_0;
            card->dai_link[0].codecs->dai_name   = "sstar-dummy-codec";
            card->dai_link[0].cpus->of_node      = np_cpu_d0;
            card->dai_link[0].platforms->of_node = np_cpu_d0;
            card->dai_link[1].codecs->of_node = np_codec_1;
            card->dai_link[1].codecs->dai_name = "ES8328 HiFi";
            card->dai_link[1].cpus->of_node = np_cpu_d0;
            card->dai_link[1].platforms->of_node = np_cpu_d0;
        }
    
    static struct snd_soc_dai_link sstar_asoc_card_dailinks[] = {
        [0] = {
            .name        = "HiFi",
            .stream_name = "HiFi PCM",
            .init        = sstar_init,
            .ops         = &sstar_mc_ops,
            .dai_fmt     = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
                SND_SOC_DAIFMT_CBS_CFS,
            SND_SOC_DAILINK_REG(private),
        },
        [1] = {
            .name = "aux-snd",
            .stream_name = "aux-snd-control",
            .init = sstar_init,
            .ops = &sstar_mc_ops,
            .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
                SND_SOC_DAIFMT_CBS_CFS,
            SND_SOC_DAILINK_REG(auxiliary),
        }
    };
    

2.4.2. simple-card对接第三方Codec Driver

  • 添加codec driver,比如添加:sound/soc/codec/es8388.c,

    修改sound/soc/codec/Kconfig以及Makefile加入驱动编译。

    sound/soc/codec/Kconfig:
        config SND_SOC_ES8388
            tristate "Everest Semi ES8388 CODEC"
    
    sound/soc/codec/Makefile:
        snd-soc-es8388-objs := es8388.o
        obj-$(CONFIG_SND_SOC_ES8388)+= snd-soc-es8388.o
    
  • menuconfig 中打开 ASoC Simple sound card 以及 codec。

    打开simple-card,则自定义machine-driver不生效。

    make menuconfig
    Device Drivers --->
        <*> Sound card support --->
            <*> Advanced Linux Sound Architecture --->
                <*> ALSA for SoC audio support --->
                    <*> ASoC support for SigmaStar
                    <*> ASoC Simple sound card support
                    CODEC drivers --->
                        <*> SSTAR DUMMY AUX CODEC driver
                        <*> Everest Semi ES8388 CODEC
    
  • 产品的DTS中添加Simple Card Node

    asoc_sound_simple {
        status = "ok";
        compatible = "simple-audio-card";
        simple-audio-card,format = "i2s";
        simple-audio-card,name = "sigmastar,asoc-card";
        simple-audio-card,mclk-fs = <256>;
        simple-audio-card,cpu {
            sound-dai = <&bach1>;
        };
        simple-audio-card,codec {
            sound-dai = <&es8388>;
        };
    };
    bach1: bach1 {
        status = "ok";
        compatible = "sstar,bach";
        sound-dma = <&dma1>;
        #sound-dai-cells = <0>;
        reg = <0x1F2A0400 0x800>;
        mclk-freq = <12288000>;
    };
    

3. 声卡控制

3.1. 音量控制

  • 所有通路使用 mixer control 接口完成音量调整。

  • 音量作用在 interface 相连接的 DPGA 上

    Playback:
    ________     _________     |\              _______
    |  MIU | -> | RDMA 0  | -> |  - DPGA  ->  |  DAC  | -> Speaker
    ————————     —————————     |/              ———————
    
    Capture:
    ________     _________      _______            /|     _____
    |  MIU | <- | WDMA 0  | <- |  MUX  | <- DPGA -  | <- | ADC | <- AMIC
    ————————     —————————      ———————            \|     —————
    
  • 支持音量调节的 control 节点

    调节范围(0-1023),最小值为-63.875db,最大值为64db, 步长为0.125db。

    设为0即为静音。

    console:/ # tinymix -D 0 contents  or    tinymix -D 1 contents
    Number of controls: 50
    ctl    type num name                     value
    0  INT  1 DAC_0 Playback Volume       535 (range 0->1023)
    1  INT  1 DAC_1 Playback Volume       535 (range 0->1023)
    2  INT  1 I2S_TXA_0 Playback Volume   511 (range 0->1023)
    3  INT  1 I2S_TXA_1 Playback Volume   511 (range 0->1023)
    4  INT  1 I2S_TXB_0 Playback Volume   511 (range 0->1023)
    5  INT  1 I2S_TXB_1 Playback Volume   511 (range 0->1023)
    6  INT  1 SPDIF_TX_0 Playback Volume  511 (range 0->1023)
    7  INT  1 SPDIF_TX_1 Playback Volume  511 (range 0->1023)
    8  INT  1 ADC_A_0 Capture Volume      700 (range 0->1023)
    9  INT  1 ADC_A_1 Capture Volume      700 (range 0->1023)
    10 INT  1 ADC_B_0 Capture Volume      700 (range 0->1023)
    11 INT  1 ADC_B_1 Capture Volume      700 (range 0->1023)
    12 INT  1 I2S_RXA_0 Capture Volume    511 (range 0->1023)
    13 INT  1 I2S_RXA_1 Capture Volume    511 (range 0->1023)
    14 INT  1 I2S_RXA_2 Capture Volume    511 (range 0->1023)
    15 INT  1 I2S_RXA_3 Capture Volume    511 (range 0->1023)
    16 INT  1 I2S_RXA_4 Capture Volume    511 (range 0->1023)
    17 INT  1 I2S_RXA_5 Capture Volume    511 (range 0->1023)
    18 INT  1 I2S_RXA_6 Capture Volume    511 (range 0->1023)
    19 INT  1 I2S_RXA_7 Capture Volume    511 (range 0->1023)
    20 INT  1 I2S_RXB_0 Capture Volume    511 (range 0->1023)
    21 INT  1 I2S_RXB_1 Capture Volume    511 (range 0->1023)
    22 INT  1 HDMI_RX_0 Capture Volume    511 (range 0->1023)
    23 INT  1 HDMI_RX_1 Capture Volume    511 (range 0->1023)
    24 INT  1 DMIC_0 Capture Volume       511 (range 0->1023)
    25 INT  1 DMIC_1 Capture Volume       511 (range 0->1023)
    26 INT  1 DMIC_2 Capture Volume       511 (range 0->1023)
    27 INT  1 DMIC_3 Capture Volume       511 (range 0->1023)
    28 INT  1 DMIC_4 Capture Volume       511 (range 0->1023)
    29 INT  1 DMIC_5 Capture Volume       511 (range 0->1023)
    30 INT  1 DMIC_6 Capture Volume       511 (range 0->1023)
    31 INT  1 DMIC_7 Capture Volume       511 (range 0->1023)
    32 INT  2 ECHO Capture Volume         511, 511 (range 0->1023)
    34 INT  2 ADC01_ANOLG_GAIN            0, 0 (range 0->19)
    35 INT  2 ADC23_ANOLG_GAIN            0, 0 (range 0->19)
    ... ...
    
  • 调整 ANOLG Gain

    支持音量调节的 control 节点

    console:/ # tinymix -D 0 contents  or    tinymix -D 1 contents
    Number of controls: 30
    ctl   type  num  name                                value
    34    INT   2    ADC01_ANOLG_GAIN                    0, 0 (range 0->19)
    35    INT   2    ADC23_ANOLG_GAIN                    0, 0 (range 0->19)
    

    调节范围:

    两个值分别代表左声道和右声道
    区间(0~19),最小值为0db, 最大值为57db, 步长为3db.
    默认值为0,代表的增益为0db。
    

    调节方法:

    ADC01_ANOLG_GAIN 的左右声道 gain 设置为 10
    console:/ # tinymix -D 0 set ADC01_ANOLG_GAIN 10 10
    
    ADC23_ANOLG_GAIN 的左右声道 gain 设置为 10
    console:/ # tinymix -D 0 set ADC01_ANOLG_GAIN 10 10
    
    alsa-utils 使用参考:
    console:/ # ./amixer cset name='ADC01_ANOLG_GAIN' 10 10
    console:/ # ./amixer cset name='ADC23_ANOLG_GAIN' 10 10
    
    设置前:
    console:/ # tinymix -D 0 contents
    Number of controls: 30
    ctl   type  num  name                                value
    34    INT   2    ADC01_ANOLG_GAIN                    0, 0 (range 0->19)
    35    INT   2    ADC23_ANOLG_GAIN                    0, 0 (range 0->19)
    设置后:
    console:/ # tinymix -D 0 contents
    Number of controls: 30
    ctl   type  num  name                                value
    34    INT   2    ADC01_ANOLG_GAIN                    10, 10 (range 0->19)
    35    INT   2    ADC23_ANOLG_GAIN                    10, 10 (range 0->19)
    
  • 调整 DAC 输出音量大小

    tinyalsa 使用参考:
    DAC_0 Playback Volume 的 dpga gain 设置为 911
    console:/ # tinymix -D 0 set 0 911
    
    DAC_1 Playback Volume 的 dpga gain 设置为 911
    console:/ # tinymix -D 0 set 1 911
    
    alsa-utils 使用参考:
    console:/ # ./amixer cset name='DAC_0 Playback Volume' 911
    console:/ # ./amixer cset name='DAC_1 Playback Volume' 911
    
    设置前:
    console:/ # tinymix -D 0 contents
    Number of controls: 50
    ctl    type    num    name                   value
    0    INT    1    DAC_0 Playback Volume       511 (range 0->1023)
    1    INT    1    DAC_1 Playback Volume       511 (range 0->1023)
    ... ...
    设置后:
    console:/ # tinymix -D 0 contents
    Number of controls: 50
    ctl    type    num    name                   value
    0    INT    1    DAC_0 Playback Volume       911 (range 0->1023)
    1    INT    1    DAC_1 Playback Volume       911 (range 0->1023)
    ... ...
    

3.2. 设备树配置

  • 主从模式

    属性名称 描述 取值范围
    i2s-rx0-tdm-mode 配置主从模式 1:主模式
    2:从模式
    i2s-tx0-tdm-mode 配置主从模式 1:主模式
    2:从模式
    i2s-rx1-tdm-mode 配置主从模式 1:主模式
    2:从模式
    i2s-tx1-tdm-mode 配置主从模式 1:主模式
    2:从模式
  • I2S格式

    属性名称 描述 取值范围
    i2s-rx0-tdm-fmt 配置I2S格式 1:标准格式
    2:左对齐格式
    i2s-tx0-tdm-fmt 配置I2S格式 1:标准格式
    2:左对齐格式
    i2s-rx1-tdm-fmt 配置I2S格式 1:标准格式
    2:左对齐格式
    i2s-tx1-tdm-fmt 配置I2S格式 1:标准格式
    2:左对齐格式

    标准格式:

    I2S模式属于左对齐中的一种特例,也叫PHILIPS模式,是由标准左对齐格式再延迟一个时钟位变化来的。

    左对齐格式:

    和标准格式对比可以看出,标准左对齐格式的数据的MSB没有相对于BCLK延迟一个时钟。

  • I2S wiremode

    属性名称 描述 取值范围
    i2s-rx0-tdm-wiremode 配置I2S接线模式 1:4线模式
    2:6线模式
    i2s-tx0-tdm-wiremode 配置I2S接线模式 1:4线模式
    2:6线模式
    i2s-rx1-tdm-wiremode 配置I2S接线模式 1:4线模式
    2:6线模式
    i2s-tx1-tdm-wiremode 配置I2S接线模式 1:4线模式
    2:6线模式

    四线模式下:

    外部codec提供时钟时,i2s-rx-tdm-mode和i2s-tx-tdm-mode均设为从模式. 外部codec不提供时钟时,i2s-rx-tdm-mode和i2s-tx-tdm-mode均设为主模式. lookback时(TXRX对接),i2s-rx-tdm-mode和i2s-tx-tdm-mode均设为主模式.

  • I2S 通道数

    属性名称 描述 取值范围
    i2s-rx0-channel 配置I2S 通道数 1、2、4、8
    i2s-tx0-channel 配置I2S 通道数 1、2、4、8
    i2s-rx1-channel 配置I2S 通道数 1、2
    i2s-tx1-channel 配置I2S 通道数 1、2
  • I2S tdm配置

    属性名称 描述 取值范围
    i2s-rx0-tdm-ws-pgm TDM可编程模式 0: OFF
    1: ON
    i2s-rx1-tdm-ws-pgm TDM可编程模式 0: OFF
    1: ON
    i2s-tx0-tdm-ws-pgm TDM可编程模式 0: OFF
    1: ON
    i2s-tx1-tdm-ws-pgm TDM可编程模式 0: OFF
    1: ON
    i2s-rx0-tdm-ws-width 配置tdm WCK的宽度 0~31 (width = value + 1)
    i2s-rx1-tdm-ws-width 配置tdm WCK的宽度 0~31 (width = value + 1)
    i2s-tx0-tdm-ws-width 配置tdm WCK的宽度 0~31 (width = value + 1)
    i2s-tx-1tdm-ws-width 配置tdm WCK的宽度 0~31 (width = value + 1)

    示例:

    i2s-rx0-tdm-ws-pgm = <0>;
    i2s-rx0-tdm-ws-width = <0>;
    i2s-tx0-tdm-ws-pgm = <1>;
    i2s-tx0-tdm-ws-width = <0xf>;
    

    不打开pgm:width属性不生效,WCK占空比固定为50%

    打开pgm:此时width属性生效,WCK宽度为(width+1)*bclk

  • I2S wck翻转

    属性名称 描述 取值范围
    i2s-rx0-tdm-ws-inv I2S wck翻转 0: 不翻转
    1: 翻转
    i2s-tx0-tdm-ws-inv I2S wck翻转 0: 不翻转
    1: 翻转
    i2s-rx1-tdm-ws-inv I2S wck翻转 0: 不翻转
    1: 翻转
    i2s-tx1-tdm-ws-inv I2S wck翻转 0: 不翻转
    1: 翻转

    不翻转:

    翻转:

  • I2S rx-ch-swap

    属性名称 描述 取值范围
    i2s-rx0-tdm-ch-swap 配置tx i2s通道交换 <0 0 0>
    <0 0 1>
    <0 1 0>
    <0 1 1>
    i2s-rx1-tdm-ch-swap 配置tx i2s通道交换 <0 0 0>
    <0 0 1>
    <0 1 0>
    <0 1 1>

    P5首位目前只支持0

    示例:

    i2s-rx0-tdm-ch-swap = <0 0 0>;
    i2s-rx1-tdm-ch-swap = <0 1 0>;
    

    <0 0 0>:

    <0 0 1>:

    <0 1 0>:

    <0 1 1>:

  • I2S tx-ch-swap

    属性名称 描述 取值范围
    i2s-tx0-tdm-ch-swap 配置tx i2s通道交换 <0 0 0>
    <0 0 1>
    <0 1 0>
    <0 1 1>
    i2s-tx1-tdm-ch-swap 配置tx i2s通道交换 <0 0 0>
    <0 0 1>
    <0 1 0>
    <0 1 1>

    P5首位目前只支持0

    <0 0 0>:

    <0 0 1>:

    <0 1 0>:

    <0 1 1>:

  • I2S tx 有效通道

    属性名称 描述 取值范围
    i2s-tx0-tdm-active-slot 配置I2S tx 有效通道 0x00 ~ 0xFF (bit0->slot0, bit1->slot1, ... )
    i2s-tx1-tdm-active-slot 配置I2S tx 有效通道 0x00 ~ 0xFF (bit0->slot0, bit1->slot1, ... )
  • I2S BCK翻转

    属性名称 描述 取值范围
    i2s-rx0-codec-bck-inv 配置I2S BCK翻转 0: 不翻转
    1: 翻转
    i2s-tx0-codec-bck-inv 配置I2S BCK翻转 0: 不翻转
    1: 翻转
    i2s-rx1-codec-bck-inv 配置I2S BCK翻转 0: 不翻转
    1: 翻转
    i2s-tx1-codec-bck-inv 配置I2S BCK翻转 0: 不翻转
    1: 翻转
  • 高通滤波

    属性名称 描述 取值范围
    hpf-adc1-dmic2ch-level 高通滤波level < switch value >
    switch:
    0:not use this level
    1:use this level
    value: 0~0xf
    hpf-adc2-dmic2ch-level 高通滤波level < switch value >
    switch:
    0:not use this level
    1:use this level
    value: 0~0xf
    hpf-dmic4ch-level 高通滤波level < switch value >
    switch:
    0:not use this level
    1:use this level
    value: 0~0xf

    示例:

    hpf-adc1-dmic2ch-level = <1 0xf>;
    hpf-adc2-dmic2ch-level = <1 0xf>;
    hpf-dmic4ch-level      = <1 0x0>;
    

    HPF关闭 截止频率20Hz:

    HPF 0x0 截止频率20Hz:

    HPF 0x1 截止频率23Hz:

    HPF 0x2 截止频率26Hz:

    HPF 0x3 截止频率32Hz:

    HPF 0x4 截止频率38Hz:

    HPF 0x5 截止频率52Hz:

    HPF 0x6 截止频率65Hz:

    HPF 0x7 截止频率95Hz:

    HPF 0x8 截止频率140Hz:

    HPF 0x9 截止频率190Hz:

    HPF 0xA 截止频率250Hz:

    HPF 0xB 截止频率370Hz:

    HPF 0xC 截止频率480Hz:

    HPF 0xD 截止频率700Hz:

    HPF 0xE 截止频率950Hz:

    HPF 0xF 截止频率1500Hz:

  • 防止杂音、爆破音

    属性名称 描述 取值范围
    dac_anti_noisy_on 软件延迟300ms,防止启播/启录状态杂音 0:OFF
    1:ON
    keep_adc_power_on ADC电源常供电 0:OFF
    1:ON
    keep_dac_power_on DAC电源常供电 0:OFF
    1:ON
  • dmic bck mode

    属性名称 描述 取值范围
    dmic-bck-mode-8k 配置8K BCK 1~7
    dmic-bck-mode-16k 配置16K BCK 8~14
    dmic-bck-mode-32k 配置32K BCK 15
    dmic-bck-mode-48k 配置48K BCK 16

  • 控制打印等级

    属性名称 描述 取值范围
    debug-level 控制打印等级 0x00000001 : debug fot test
    0x00000002 : debug DMA status
    0x00000004 : debug bach and hdmi rx frequency difference
    0x00000008 : debug irq status
    0x00000010 : debug audio delay status
    0x00000020 : debug Indicates the creation status of a path
    0x00000040 : debug hdmirx audio format report
    0x00000080 : debug audio clk
    0x00000100 : debug audio i2s

    动态调整参考debug-level

3.3. 音频通路控制

系统提供 ADC、DMIC、ECHO_RX、I2S_RX、HDMI_RX、DAC、SPDIF_TX、I2S_TX、ECHO_TX 的音频通路音频输入输出。

系统提供 aplay、arecord、amixer 原生 alsa 框架、compress 播放的音频输出,硬件混音支持。

————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    interface(devide node)      |              CPU DAI                  |           DMA Engine
    -----------------------------------------------------------------------------------------------------
                                                DAC
                                                I2S_TX_A
                                                I2S_TX_B             <---       R_DMA_0_Playback
                                                ECHO_TX
                            /                    SPDIF_TX
        card_0 - device_0 -         ---------------------------------------------------------------------
                            \        AI_MCH_01_SEL        ADC_A
                                    AI_MCH_23_SEL        ADC_B
                                    AI_MCH_45_SEL        DMIC
                                    AI_MCH_67_SEL   ->   I2S_RX_A    --->       W_DMA_0_Capture
                                    AI_MCH_89_SEL        I2S_RX_B
                                    AI_MCH_AB_SEL        HDMI_RX
                                    AI_MCH_CD_SEL        ECHO_RX
  /                                  AI_MCH_EF_SEL
-              ------------------------------------------------------------------------------------------
  \                                              DAC
                                                I2S_TX_A
                                                I2S_TX_B             <---       R_DMA_1_Playback
                                                ECHO_TX
                            /                    SPDIF_TX
        card_1 - device_0 -         ---------------------------------------------------------------------
                            \        AI_MCH_01_SEL        ADC_A
                                    AI_MCH_23_SEL        ADC_B
                                    AI_MCH_45_SEL        DMIC
                                    AI_MCH_67_SEL   ->   I2S_RX_A    --->       W_DMA_1_Capture
                                    AI_MCH_89_SEL        I2S_RX_B
                                    AI_MCH_AB_SEL        HDMI_RX
                                    AI_MCH_CD_SEL        ECHO_RX
                                    AI_MCH_EF_SEL
————————————————————————————————————————————————————————————————————————————————————————————————————————————————

声卡 0、1 支持的 control 节点,通路指 name 为 *_SEL 的节点

当前输入输出通路支持 playback、capture、passthrough、aec、compress、volume adjust、channel mode 等使用场景

console:/ # tinymix -D 0  contents
Number of controls: 50
ctl    type num name                     value
... ...
33 ENUM 1 CHANNEL_MODE_PLAYBACK       > STEREO, DOUBLE_MONO, DOUBLE_LEFT, DOUBLE_RIGHT, EXCHANGE, ONLY_LEFT, ONLY_RIGHT,
34 ENUM 1 AI_MCH_01_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
35 ENUM 1 AI_MCH_23_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
36 ENUM 1 AI_MCH_45_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
37 ENUM 1 AI_MCH_67_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
38 ENUM 1 AI_MCH_89_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
39 ENUM 1 AI_MCH_AB_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
40 ENUM 1 AI_MCH_CD_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
41 ENUM 1 AI_MCH_EF_SEL               > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
42 ENUM 1 AI_MCH_VIR_SEL              > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
43 ENUM 1 AO_VIR_MUX_SEL              > VIR_DETACH, DAC0, SPDIF_LPCM_TX, SPDIF_NLPCM_TX, I2S_TXA, I2S_TXB, ECHO_0,
44 ENUM 1 HDMI_TX_SEL                 > AO_DETACH, DMA, DMA+SRC,
45 ENUM 1 DAC_SEL                     > AO_DETACH, DMA, DMA+SRC,
46 ENUM 1 ECHO_SEL                    > AO_DETACH, DMA, DMA+SRC,
47 ENUM 1 SPDIF_TX_SEL                > AO_DETACH, LDMA, LDMA+SRC, NLDMA,
48 ENUM 1 I2S_TXA_SEL                 > AO_DETACH, DMA, DMA+SRC,
49 ENUM 1 I2S_TXB_SEL                 > AO_DETACH, DMA, DMA+SRC,

console:/ # tinymix -D 1 contents
Number of controls: 17
ctl type num name                  value
0  ENUM 1 CHANNEL_MODE_PLAYBACK  > STEREO, DOUBLE_MONO, DOUBLE_LEFT, DOUBLE_RIGHT, EXCHANGE, ONLY_LEFT, ONLY_RIGHT,
1  ENUM 1 AI_MCH_01_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
2  ENUM 1 AI_MCH_23_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
3  ENUM 1 AI_MCH_45_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
4  ENUM 1 AI_MCH_67_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
5  ENUM 1 AI_MCH_89_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
6  ENUM 1 AI_MCH_AB_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
7  ENUM 1 AI_MCH_CD_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
8  ENUM 1 AI_MCH_EF_SEL          > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
9  ENUM 1 AI_MCH_VIR_SEL         > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
10 ENUM 1 AO_VIR_MUX_SEL         > VIR_DETACH, DAC0, SPDIF_LPCM_TX, SPDIF_NLPCM_TX, I2S_TXA, I2S_TXB, ECHO_0,
11 ENUM 1 HDMI_TX_SEL            > AO_DETACH, DMA, DMA+SRC,
12 ENUM 1 DAC_SEL                > AO_DETACH, DMA, DMA+SRC,
13 ENUM 1 ECHO_SEL               > AO_DETACH, DMA, DMA+SRC,
14 ENUM 1 SPDIF_TX_SEL           > AO_DETACH, LDMA, LDMA+SRC, NLDMA,
15 ENUM 1 I2S_TXA_SEL            > AO_DETACH, DMA, DMA+SRC,
16 ENUM 1 I2S_TXB_SEL            > AO_DETACH, DMA, DMA+SRC,
  • 音频输出设备策略:

    支持喇叭、SPDIF、I2S_TX 的设备输出。支持设备间的切换。

    播放过程中,动态调整输出设备流程:

     ___________________      ______      __________
    |  attach (speaker) | -> | open | -> | playback | -> (开始动态切换) ->
     ———————————————————      ——————      ——————————
    
     ___________      ________      _______      _________
    |  detach   | -> | attach | -> | close | -> | detach  |
    |  (speaker)|    | (spdif)|    |       |    | (spdif) |
     ———————————      ————————      ———————      —————————
    
    (因为是 attach/detach 不同 interface, 所以切换过程中会存在断音)
    

    静置状态下,调整外接设备流程:

                                __________
                            -> | playback | -
    ________      ______  /    ——————————   \     _______      ________
    | attach | -> | open |                     -> | close | -> | detach |
    ————————      ——————  \    __________   /     ———————      ————————
                            -> | capture  | -
                                ——————————
    
  • 连接(attach)通路

    • 单独或同时连接 DAC + SPDIF 输出

      连接操作:

      tinyalsa 使用参考:
      console:/ # tinymix -D 0 set 45 1   // R_DMA attach 到 DAC
      console:/ # tinymix -D 0 set 47 1   // R_DMA attach 到 SPDIF
      alsa-utils 使用参考:
      console:/ # ./amixer cset name='AI_MCH_01_SEL' 1
      console:/ # ./amixer cset name='AI_MCH_23_SEL' 1
      
      查看状态:
      console:/ # tinymix -D 0 contents
      ... ...
      45  ENUM  1  DAC_SEL         AO_DETACH, > DMA, DMA+SRC,
      46  ENUM  1  ECHO_SEL        > AO_DETACH, DMA, DMA+SRC,
      47  ENUM  1  SPDIF_TX_SEL    AO_DETACH, > LDMA, LDMA+SRC, NLDMA,
      ... ...
      
    • 单独或同时连接 AMIC + DMIC 输入

      • 单声道 mono 的处理:
        连接操作:
        tinyalsa 使用参考:
            multichannel 的 0、1 声道 attach 到 AMIC 的 0、1
        console:/ # tinymix -D 0 set 34 1
        
        alsa-utils 使用参考:
        console:/ # ./amixer cset name='AI_MCH_01_SEL' 1
        
        注意:capture 取数据的时候,即 mono 默认取 multichannel 的 0 声道
        
    • 多声道:AMIC + DMIC 同时输入 8 声道数据

      例如:AMIC 数据存放在 0、1 声道,DMIC 数据存放在 2、3、4、5、6、7 声道

      连接操作:
      tinyalsa 使用参考:
      console:/ # tinymix -D 0 set 34 1   // multichannel 的 0、1 声道 attach 到 AMIC 的 0、1
      console:/ # tinymix -D 0 set 35 3   // multichannel 的 2、3 声道 attach 到 DMIC 的 0、1
      console:/ # tinymix -D 0 set 36 4   // multichannel 的 4、5 声道 attach 到 DMIC 的 2、3
      console:/ # tinymix -D 0 set 37 5   // multichannel 的 6、7 声道 attach 到 DMIC 的 4、5
      alsa-utils 使用参考:
      console:/ # ./amixer cset name='AI_MCH_01_SEL' 1
      console:/ # ./amixer cset name='AI_MCH_23_SEL' 3
      console:/ # ./amixer cset name='AI_MCH_45_SEL' 4
      console:/ # ./amixer cset name='AI_MCH_67_SEL' 5
      
      查看状态:
      console:/ # tinymix -D 0 contents
      ... ...
      34    ENUM    1    AI_MCH_01_SEL                           AI_DETACH, > ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
      35    ENUM    1    AI_MCH_23_SEL                           AI_DETACH, ADC_A, ADC_B, > DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
      36    ENUM    1    AI_MCH_45_SEL                           AI_DETACH, ADC_A, ADC_B, DMIC_A_01, > DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
      37    ENUM    1    AI_MCH_67_SEL                           AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, > DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
      38    ENUM    1    AI_MCH_89_SEL                           > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
      39    ENUM    1    AI_MCH_AB_SEL                           > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
      40    ENUM    1    AI_MCH_CD_SEL                           > AI_DETACH, ADC_A, ADC_B, DMIC_A_01, DMIC_A_23, DMIC_A_45, DMIC_A_67, ECHO_01, HDMI_01, I2S_RXA_0_1, I2S_RXA_2_3, I2S_RXA_4_5, I2S_RXA_6_7, I2S_RXB_0_1,
      ... ...
      
    • 连接直通(passthrough)通路

      例如:ADC 到 DAC 的直通

      连接操作:
      tinyalsa 使用参考:
      console:/ # tinymix -D 0 set 42 1
      console:/ # tinymix -D 0 set 43 1
      alsa-utils 使用参考:
      console:/ # ./amixer cset name='AI_MCH_VIR_SEL' 1
      console:/ # ./amixer cset name='AO_VIR_MUX_SEL' 1
      
    • I2S 通路的连接

      I2S_TX、I2S_RX 的 主从模式以及时钟信号等相关配置,请在 kernel 的 dtsi 文件中配置。

      连接操作:
      tinyalsa 使用参考:
      console:/ # tinymix -D 0 set 48 1
      alsa-utils 使用参考:
      console:/ # ./amixer cset name='I2S_TXA_SEL' 1
      
  • 断开(detach)通路

    断开通路的时候,只需要把对应通路切换到 DETACH 状态(AI_DETACH / AO_DETACH / VIR_DETACH)即可,例如:detach 通路 DAC

    tinyalsa 使用参考:
    console:/ # tinymix -D 0 set 45 0
    alsa-utils 使用参考:
    console:/ # ./amixer cset name='DAC_SEL' 0
    

3.4. Compress播放

compress目前只支持在外接功放上进行播放

连接操作:
tinyalsa 使用参考:
console:/ # tinymix -D 0 set 47 3   // NLDMA attach 到 SPDIF
alsa-utils 使用参考:
console:/ # ./amixer cset name='SPDIF_TX_SEL' 3

播放操作:
console:/ # tinyplay demo.sdf -D 0 -d 0 -c 2 -r 48000 -p 512 -n 4

3.5. 耳机检测

如果使用自定义的machine driver,则在DTS打开GPIO检测脚及防抖动时间如下:

dummy_codec1: dummy_codec1 {
    compatible = "sstar,dummy-codec1";
    #sound-dai-cells = <0>;
    headset-det-gpio = <&gpio PAD_SR_PDN1 0>; /* 0:GPIO_ACTIVE_HIGH 1:GPIO_ACTIVE_LOW */
    headset-debounce-time = <150>;
    status = "okay";
};

如果使用simple-card,则打开simple-audio-card,hp-det-gpio:

i2s_sound2 {
    compatible = "simple-audio-card";
    simple-audio-card,format = "i2s2";
    simple-audio-card,name = "sigmastar,i2s2-codec";
    simple-audio-card,mclk-fs = <256>;
    /* 0:GPIO_ACTIVE_HIGH 1:GPIO_ACTIVE_LOW */
    simple-audio-card,hp-det-gpio = <&gpio PAD_SR_PDN1 0>;
    status = "disabled";
    simple-audio-card,cpu {
        sound-dai = <&i2s_bach2>;
    };
    simple-audio-card,codec {
        sound-dai = <&dummy_codec2>;
    };
};

4. 常用调试方法

4.1. procfs

4.1.1. 确认声卡是否注册成功

/ # cat  /proc/asound/cards
    0 [sstarasoccard  ]: sstar-asoc-card - sstar-asoc-card
                        sstar-asoc-card
    1 [sstarasoc2card ]: sstar-asoc2-car - sstar-asoc2-card
                        sstar-asoc2-card
/ # ls /dev/snd/
controlC0  controlC1  pcmC0D0c   pcmC0D0p   pcmC1D0c   pcmC1D0p   timer

4.1.2. 确认I2S TDM状态

/ # cat /proc/audio/tdm
TDM RxA
pgm         = <0>
width       = <0>
Wck inv     = <0>
Bck inv     = <0>
Short FF    = <0>
nFreOffset  = <0>, nFreOffsetVal is <0>
ch-swap     = <0 0 0 0>

TDM RxB
pgm         = <0>
width       = <0>
Wck inv     = <0>
Bck inv     = <0>
Short FF    = <0>
nFreOffset  = <0>, nFreOffsetVal is <0>
ch-swap     = <0 0 0 0>

TDM TxA
pgm         = <0>
width       = <0>
Wck inv     = <0>
Bck inv     = <0>
Short FF    = <0>
nFreOffset  = <0>, nFreOffsetVal is <0>
ch-swap     = <0 0 0 0>
tx active slot = <0xff>

TDM TxB
pgm         = <0>
width       = <0>
Wck inv     = <0>
Bck inv     = <0>
Short FF    = <0>
nFreOffset  = <0>, nFreOffsetVal is <0>
ch-swap     = <0 0 0 0>
tx active slot = <0xff>

4.1.3. 确认DMA状态

/ # cat /proc/audio/dma
Mux Id 1:Src1_Left   2:Src1_Right  3:Src2_Left  4:Src2_Left
Mix Id 1:Src1_1_Left   2:Src1_1_Right  3:Src1_2_Left 4:Src1_2_Right 5:Src2_Left  6:Src2_Left
Mix from Id :0x1:Src1_Left   0x2:Src1_Right  0x4:Src2_Left  0x8:Src2_Right
Mix to Id :0:DAC_A_0  1:DAC_B_0 4:HDMI_A_0 5:HDMI_A_1 6:ECHO_A_0 7:ECHO_A_1 8:I2S_TX_A_0 9:I2S_TX_A_1 10:I2S_TX_B_0 11:I2S_TX_B_1 12:SPDIF_TX_A_0 13:SPDIF_TX_A_1

-------------AIO_DMA_AI_A--------------
nWrPos : 0    nActCache : 19041    nSetCache : 0    nLevelCnt : 0
-------------AIO_DMA_AI_B--------------
nWrPos : 0    nActCache : 0    nSetCache : 0    nLevelCnt : 0
-------------AIO_DMA_AO_A--------------
nWrPos : 3603    nLevelCnt : 0
##########Left Path#########
Mix Id : 0
Mux Id : 0
nSrcUse : 0
Mix from : 0
Mix to : 0

#########Right Path#########
Mix Id : 0
Mux Id : 0
nSrcUse : 0
Mix from : 0
Mix to : 0
nDmaSampleRate : 9

-------------AIO_DMA_AO_B--------------
nWrPos : 0    nLevelCnt : 0
##########Left Path#########
Mix Id : 0
Mux Id : 0
nSrcUse : 0
Mix from : 0
Mix to : 0

#########Right Path#########
Mix Id : 0
Mux Id : 0
nSrcUse : 0
Mix from : 0
Mix to : 0
nDmaSampleRate : 0

------------AIO_DMA_AO_DIRECT_A--------
##########Left Path#########
Mix Id : 0
Mux Id : 0
nSrcUse : 0
Mix from : 0
Mix to : 0

#########Right Path#########
Mix Id : 0
Mux Id : 0
nSrcUse : 0
Mix from : 0
Mix to : 0
nDmaSampleRate : 0

4.1.4. 启用sinegen状态

  • 查看sinegen状态

    / # cat sinegen
    
    [AUDIO Sine Gen]
    --------
    SineGen ID   = 0
    Enable       = 0
    Freq         = 0
    Gain         = 0
    
  • 设置sinegen

    设置ID(0~8):
        echo 0 > /proc/audio/sinegen
            SINEGEN_AI_DMA_A = 0
            SINEGEN_AI_DMA_B = 1
            SINEGEN_AI_DMA_C = 2
            SINEGEN_AI_DMA_D = 3
            SINEGEN_AO_DMA_A = 4
            SINEGEN_AO_DMA_B = 5
            SINEGEN_AO_DMA_C = 6
            SINEGEN_AO_DMA_D = 7
            SINEGEN_AI_IF_DMIC_A = 8
    
    设置使能(0~1):
        echo 1 > /proc/audio/sinegen_enable
    设置Freq(0~15):
        echo 10 > /proc/audio/sinegen_freq
    设置Gain(0~15):
        echo 10 > /proc/audio/sinegen_gain
    

4.1.5. debug level

echo rx 1 -debug 0xffff > /proc/audio/dma

debug level:
        0x00000001 : debug fot test
        0x00000002 : debug DMA status
        0x00000004 : debug bach and hdmi rx frequency difference
        0x00000008 : debug irq status
        0x00000010 : debug audio delay status
        0x00000020 : debug Indicates the creation status of a path
        0x00000040 : debug hdmirx audio format report
        0x00000080 : debug audio clk
        0x00000100 : debug audio i2s

4.1.6. 查看运行时alsa状态

  • 查看playback状态(card0为例):

    / # cat /proc/asound/card0/pcm0p/sub0/status
        state: RUNNING
        owner_pid   : 1120
        trigger_time: 991.910025784
        tstamp      : 999.065843452
        delay       : 1596
        avail       : 324
        avail_max   : 968
        -----
        hw_ptr      : 343480
        appl_ptr    : 345076
    
    / # cat /proc/asound/card0/pcm0p/sub0/sw_params
        tstamp_mode: ENABLE
        period_step: 1
        avail_min: 960
        start_threshold: 960
        stop_threshold: 1920
        silence_threshold: 1920
        silence_size: 0
        boundary: 2013265920
    
    / # cat /proc/asound/card0/pcm0p/sub0/hw_params
        access: RW_INTERLEAVED
        format: S16_LE
        subformat: STD
        channels: 2
        rate: 48000 (48000/1)
        period_size: 960
        buffer_size: 1920
    
  • 查看capture状态(card0为例):

    / # cat /proc/asound/card0/pcm0c/sub0/status
        state: RUNNING
        owner_pid   : 1129
        trigger_time: 1280.633992485
        tstamp      : 1283.509836152
        delay       : 3520
        avail       : 3520
        avail_max   : 9616
        -----
        hw_ptr      : 138016
        appl_ptr    : 134496
    
    / # cat /proc/asound/card0/pcm0c/sub0/sw_params
        tstamp_mode: ENABLE
        period_step: 1
        avail_min: 9600
        start_threshold: 1
        stop_threshold: 384000
        silence_threshold: 0
        silence_size: 0
        boundary: 1258291200
    
    / # cat /proc/asound/card0/pcm0c/sub0/hw_params
        access: RW_INTERLEAVED
        format: S16_LE
        subformat: STD
        channels: 2
        rate: 48000 (48000/1)
        period_size: 9600
        buffer_size: 38400
    

4.2. Xrun debug

Xrun debug 一般用于 debug underrun 或者 overrun,出现此两者情况时内核会 打印 log 协助问题的定位分析。Menuconfig 中需要开启如下选项:

[*] Advanced Linux Sound Architecture --->
    [*] Debug
        [*] More verbose debug
        [*] Enable PCM ring buffer overrun/underrun debugging0

然后在对应声卡/proc/asound/card0/pcm0p/xrun_debug 中写入相应的值,值如下:

#define XRUN_DEBUG_BASIC         (1<<0)
#define XRUN_DEBUG_STACK         (1<<1) /* dump also stack */
#define XRUN_DEBUG_JIFFIESCHECK  (1<<2) /* do jiffies check */

​比如 echo 1 > xrun_debug 或者 echo 3 > xrun_debug 或者 echo 7 > xrun_debug 开启所有 debug 信息检测。

XRUN 包含 playback 的 under_run 和 capture 的 over_run,当出现 XRUN 的时候,需要改变每次 read/write data 的大小,一般是加大即可。

例如:使用 buffer size 的方式 代替 period size 的方式读写,具体请参考 read/write 示例中的注释。(tinyplay 和 aplay 中默认是每次写 period size 大小)

注意:设置的 period size 大小要是采样率的整数倍

4.3. tiny-alsa

4.3.1. tinypcminfo

查询声卡支持的采样率、格式、声道数等信息。

Usage: ./tinypcminfo -D card -d device

PCM out:
      Access:    0x000009
   Format[0]:    0x000004
   Format[1]:    00000000
 Format Name:    S16_LE
   Subformat:    0x000001
        Rate:    min=8000Hz    max=48000Hz
    Channels:    min=1         max=2
 Sample bits:    min=16        max=16
 Period size:    min=8         max=262144
Period count:    min=2         max=40960

PCM in:
      Access:    0x000009
   Format[0]:    0x000004
   Format[1]:    00000000
 Format Name:    S16_LE
   Subformat:    0x000001
        Rate:    min=8000Hz    max=48000Hz
    Channels:    min=1         max=8
 Sample bits:    min=16        max=16
 Period size:    min=2         max=262144
Period count:    min=2         max=4096

4.3.2. tinyplay

Usage: tinyplay file.wav [-D card] [-d device] [-p period_size] [-n n_periods]

播放test 音频文件:

tinyplay test.wav -D 0 -d 0 -p 1024 -n 3
Playing sample: 2 ch, 48000 hz, 32 bit

4.3.3. tinycap

Usage: tinycap file.wav [-D card] [-d device] [-c channels] [-r rate] [-b bits] [-p period_size] [-n n_periods]

48k 采样率录制音频:

tinycap test.wav -D 0 -d 0 –c 2 –r 48000 –b 16 –p 1024 –n 3

4.3.4. tinymix

控制 codec 内部的通路开关, 音量控制等。效果等同于 amixer

tinymix
usage: tinymix [options] <command>
options:
    -h, --help               : prints this help message and exits
    -v, --version            : prints this version of tinymix and exits
    -D, --card NUMBER        : specifies the card number of the mixer

commands:
    get NAME|ID              : prints the values of a control
    set NAME|ID VALUE(S) ... : sets the value of a control
        VALUE(S): integers, percents, and relative values
            Integers: 0, 100, -100 ...
            Percents: 0%, 100% ...
            Relative values: 1+, 1-, 1%+, 2%+ ...
    controls                 : lists controls of the mixer
    contents                 : lists controls of the mixer and their contents

4.4. alsa-utils

4.4.1. aplay

Usage: aplay [OPTION]... [FILE]...
-h, --help help
--version print current version
-l, --list-devices list all soundcards and digital audio devices
-L, --list-pcms list device names
-D, --device=NAME select PCM by name
-q, --quiet quiet mode
-t, --file-type TYPE file type (voc, wav, raw or au)
-c, --channels=# channels
-f, --format=FORMAT sample format (case insensitive)
-r, --rate=# sample rate
-d, --duration=# interrupt after # seconds
-s, --samples=# interrupt after # samples per channel
-M, --mmap mmap stream
-N, --nonblock nonblocking mode
-F, --period-time=# distance between interrupts is # microseconds
-B, --buffer-time=# buffer duration is # microseconds
--period-size=# distance between interrupts is # frames
--buffer-size=# buffer duration is # frames
-A, --avail-min=# min available space for wakeup is # microseconds
-R, --start-delay=# delay for automatic PCM start is # microseconds
(relative to buffer size if <= 0)
-T, --stop-delay=# delay for automatic PCM stop is # microseconds from xrun
-v, --verbose show PCM structure and setup (accumulative)
-V, --vumeter=TYPE enable VU meter (TYPE: mono or stereo)
-I, --separate-channels one file for each channel
-i, --interactive allow interactive operation from stdin
-m, --chmap=ch1,ch2,.. Give the channel map to override or follow
--disable-resample disable automatic rate resample
--disable-channels disable automatic channel conversions
--disable-format disable automatic format conversions
--disable-softvol disable software volume control (softvol)
--test-position test ring buffer position
--test-coef=# test coefficient for ring buffer position (default 8)
expression for validation is: coef * (buffer_size / 2)
--test-nowait do not wait for ring buffer - eats whole CPU
--max-file-time=# start another output file when the old file has recorded
for this many seconds
--process-id-file write the process ID here

示例:

./aplay Demo_new.wav

4.4.2. arecord

Usage: arecord [OPTION]... [FILE]...
-h, --help help
--version print current version
-l, --list-devices list all soundcards and digital audio devices
-L, --list-pcms list device names
-D, --device=NAME select PCM by name
-q, --quiet quiet mode
-t, --file-type TYPE file type (voc, wav, raw or au)
-c, --channels=# channels
-f, --format=FORMAT sample format (case insensitive)
-r, --rate=# sample rate
-d, --duration=# interrupt after # seconds
-s, --samples=# interrupt after # samples per channel
-M, --mmap mmap stream
-N, --nonblock nonblocking mode
-F, --period-time=# distance between interrupts is # microseconds
-B, --buffer-time=# buffer duration is # microseconds
--period-size=# distance between interrupts is # frames
--buffer-size=# buffer duration is # frames
-A, --avail-min=# min available space for wakeup is # microseconds
-R, --start-delay=# delay for automatic PCM start is # microseconds
(relative to buffer size if <= 0)
-T, --stop-delay=# delay for automatic PCM stop is # microseconds from xrun
-v, --verbose show PCM structure and setup (accumulative)
-V, --vumeter=TYPE enable VU meter (TYPE: mono or stereo)
-I, --separate-channels one file for each channel
-i, --interactive allow interactive operation from stdin
-m, --chmap=ch1,ch2,.. Give the channel map to override or follow
--disable-resample disable automatic rate resample
--disable-channels disable automatic channel conversions
--disable-format disable automatic format conversions
--disable-softvol disable software volume control (softvol)
--test-position test ring buffer position
--test-coef=# test coefficient for ring buffer position (default 8)
expression for validation is: coef * (buffer_size / 2)
--test-nowait do not wait for ring buffer - eats whole CPU
--max-file-time=# start another output file when the old file has recorded
for this many seconds
--process-id-file write the process ID here
--use-strftime apply the strftime facility to the output file name
--dump-hw-params dump hw_params of the device
--fatal-errors treat all errors as fatal

示例:

./arecord -r 8000 -c 2 -f S16_LE test.wav

4.4.3. amixer

显示通路配置/增益配置的参考信息:

./amixer contents

配置音频通路:

./amixer sset 'AI_MCH_01_SEL' 'ADC_A'

4.4.4. DMIX

  • 直接使用-Dplug参数进行测试:

    ./amixer sset DAC_SEL AO_DETACH
    ./amixer sset DAC_SEL DMA
    ./aplay -Dplug:dmix --period-size=1024 --buffer-size=2048 -r 48000 -c 2 -f s16_le 48K_16bit_STERO_30s.wav
    
  • 修改alsa.conf如下:

    pcm.!default {
        type asym
        playback.pcm {
            type plug
            slave.pcm "dmixer"
        }
        capture.pcm {
            type plug
            slave {
                pcm "hw:0,0"
            }
        }
    }
    
    pcm.dmixer {
        type dmix
        ipc_key 1024
        ipc_perm 0666
        slave {
            pcm "hw:0,0"
            rate 48000
            period_time  0
            period_size 1024
            buffer_size 4096
        }
        bindings {
            0 0   # map from 0 to 0
            1 1   # map from 1 to 1
        }
    }
    测试命令:
    ./amixer sset DAC_SEL AO_DETACH
    ./amixer sset DAC_SEL DMA
    ./aplay -r 16000 -c 2 -f s16_le 16K_16bit_STEREO_30s_-30db.wav
    
  • 参考上述alsa.conf, 使用管道测试AI和音频混音:

    ./tinymix set AI_MCH_01_SEL 0
    ./tinymix set AI_MCH_01_SEL ADC_A
    ./tinymix set 74 0
    ./tinymix set 74 DMA
    ./arecord -D hw:0,0 --period-size=1024 --buffer-size=4096 -r 48000 -c 2 -f s16_le -t raw | ./aplay -r 48000 -c 2 -f s16_le -t raw &
    ./aplay -r 16000 -c 2 -f s16_le 16K_16bit_STEREO_30s_-30db.wav
    

4.5. Alsa原生debug接口

cat /proc/asound/card0/pcm0p/sub0/status
    state: RUNNING
    owner_pid   : 827
    trigger_time: 3488.618828415
    tstamp      : 3496.126734250
    delay       : 14560
    avail       : 4640
    avail_max   : 10128
    -----
    hw_ptr      : 360380
    appl_ptr    : 374940

cat /proc/asound/card0/pcm0p/sub0/sw_params
    tstamp_mode: ENABLE
    period_step: 1
    avail_min: 9600
    start_threshold: 9600
    stop_threshold: 19200
    silence_threshold: 19200
    silence_size: 0
    boundary: 1258291200

cat /proc/asound/card0/pcm0p/sub0/hw_params
    access: RW_INTERLEAVED
    format: S16_LE
    subformat: STD
    channels: 2
    rate: 48000 (48000/1)
    period_size: 9600
    buffer_size: 19200

5. 参考示例

attach / detach 核心逻辑参考 alsa-utils 中的接口方法:

alsa-utils-1.2.5.1\amixer\amixer.c

static int cset(int argc, char *argv[], int roflag, int keep_handle)

attach / detach 核心逻辑参考 prog_alsa_demo 中的:

// set control contents for one control
static int aio_control_contents_set_by_name(unsigned int card_id, char *name, char *value)
{
    FUNC_ENTER();
    int                   err      = 0;
    char                  card[64] = "default";
    static snd_ctl_t *    handle   = NULL;
    snd_ctl_elem_info_t * info;
    snd_ctl_elem_id_t *   id;
    snd_ctl_elem_value_t *control;
    snd_ctl_elem_info_alloca(&info);
    snd_ctl_elem_id_alloca(&id);
    snd_ctl_elem_value_alloca(&control);

    if (!name || !value)
    {
        PrintErr("Error param, specify a full control identifier: [name='name']\n");
        return -EINVAL;
    }

    sprintf(card, "hw:%i", card_id);

    if (snd_ctl_ascii_elem_id_parse(id, name))
    {
        PrintErr("Wrong control identifier:(%s)\n", name);
        return -EINVAL;
    }
    if (handle == NULL && (err = snd_ctl_open(&handle, card, 0)) < 0)
    {
        PrintErr("Control(%s) open error:(%s)\n", card, snd_strerror(err));
        return err;
    }
    snd_ctl_elem_info_set_id(info, id);
    if ((err = snd_ctl_elem_info(handle, info)) < 0)
    {
        PrintErr("Cannot find the given element from control(%s)\n", card);
        snd_ctl_close(handle);
        handle = NULL;
        return err;
    }
    snd_ctl_elem_info_get_id(info, id);
    snd_ctl_elem_value_set_id(control, id);
    if ((err = snd_ctl_elem_read(handle, control)) < 0)
    {
        PrintErr("Cannot read the given element from control(%s)\n", card);
        snd_ctl_close(handle);
        handle = NULL;
        return err;
    }
    err = snd_ctl_ascii_value_parse(handle, control, info, value);
    if (err < 0)
    {
        PrintErr("Control(%s) parse error:(%s)\n", card, snd_strerror(err));
        snd_ctl_close(handle);
        handle = NULL;
        return err;
    }
    if ((err = snd_ctl_elem_write(handle, control)) < 0)
    {
        PrintErr("Control(%s) element write error:(%s)\n", card, snd_strerror(err));
        snd_ctl_close(handle);
        handle = NULL;
        return err;
    }

    snd_ctl_close(handle);
    handle = NULL;
    FUNC_EXIT();
    return 0;
}

open 逻辑参考 prog_alsa_demo 中的:

static int aio_open(struct ctx *ctx, snd_pcm_stream_t stream, unsigned int card_id, unsigned int device_id, unsigned int channels_param, unsigned int rate_param, unsigned int dma_rate_param)
{
    int  ret      = DEMO_SUCCESS;
    snd_pcm_t **      in_out_pcm;
    if(stream == SND_PCM_STREAM_CAPTURE){
        in_out_pcm = &ctx->in_pcm;
    }
    else {
        in_out_pcm = &ctx->out_pcm;
    }
    snd_pcm_hw_params_t *hw_params;
    snd_pcm_sw_params_t *sw_params;
    snd_output_t *log;
    snd_output_stdio_attach(&log, stderr, 0);
    /* Allocate a hardware parameters object. */
    snd_pcm_hw_params_alloca(&hw_params);
    snd_pcm_sw_params_alloca(&sw_params);
    size_t n;
    int start_delay = 0;
    int stop_delay = 0;
    snd_pcm_uframes_t start_threshold, stop_threshold;

    char card[64] = "default";
    sprintf(card, "hw:%i,%i", card_id, device_id);

    /* Open PCM device for playing (playback). */
    ret = snd_pcm_open(in_out_pcm, card, stream, 0 /*mode*/);
    if (ret < 0)
    {
        PrintErr("Unable to open PCM device (%s)\n", snd_strerror(ret));
        return DEMO_FAIL;
    }

    /* Fill it in with default values. */
    snd_pcm_hw_params_any(*in_out_pcm, hw_params);

#if SSTAR_CUSTOMIZED
    // for dma sample rate
    snd_pcm_hw_params_set_dma_rate(hw_params, dma_rate_param);
#else
    (void)dma_rate_param;
#endif

#if 0
    unsigned int latency = 1000000 * DEFAULT_PERIOD_SIZE / rate_param * DEFAULT_PERIOD_COUNT;
    /* set alsa basic param, only support SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED */
    ret = snd_pcm_set_params(*in_out_pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, channels_param, rate_param,
                            1 /*soft_resample*/, latency /*in us*/);
    if (ret < 0)
    {
        PrintErr("Unable to set params (%s)\n", snd_strerror(ret));
        return DEMO_FAIL;
    }
#else
    int dir;
    unsigned int channels = channels_param;
    unsigned int rate = rate_param;
    snd_pcm_uframes_t period_frames = DEFAULT_PERIOD_SIZE;
    unsigned int period_count = DEFAULT_PERIOD_COUNT;

    /* Set the desired hardware parameters. */
    /* Interleaved mode */
    snd_pcm_hw_params_set_access(*in_out_pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);

    /* Signed 16-bit little-endian format */
    snd_pcm_hw_params_set_format(*in_out_pcm, hw_params, SND_PCM_FORMAT_S16_LE);

    /* channels (stereo) */
    snd_pcm_hw_params_set_channels(*in_out_pcm, hw_params, channels);

    /* sampling rate */
    snd_pcm_hw_params_set_rate_near(*in_out_pcm, hw_params, &rate, &dir);

    /* Set period size to frames. */
    snd_pcm_hw_params_set_period_size_near(*in_out_pcm, hw_params, &period_frames, &dir);
    snd_pcm_hw_params_set_periods_near(*in_out_pcm, hw_params, &period_count, &dir);

    /* Write the parameters to the driver */
    ret = snd_pcm_hw_params(*in_out_pcm, hw_params);
    if (ret < 0) {
        PrintErr("unable to set hw parameters (%s)\n", snd_strerror(ret));
        return DEMO_FAIL;
    }
#endif

    ret = snd_pcm_sw_params_current(*in_out_pcm, sw_params);
    if (ret < 0) {
        PrintErr("Unable to get current sw params.");
        return DEMO_FAIL;
    }

    if(stream == SND_PCM_STREAM_CAPTURE){
        start_delay = 1;
    }
    /* round up to closest transfer boundary */
    n = DEFAULT_PERIOD_SIZE * DEFAULT_PERIOD_COUNT;
    if (start_delay <= 0) {
        start_threshold = n + (double) rate_param * start_delay / 1000000;
    } else{
        start_threshold = (double) rate_param * start_delay / 1000000;
    }
    if (start_threshold < 1){
        start_threshold = 1;
    }
    if (start_threshold > n){
        start_threshold = n;
    }
    snd_pcm_sw_params_set_start_threshold(*in_out_pcm, sw_params, start_threshold);

    if (stop_delay <= 0) {
        stop_threshold = n + (double) rate_param * stop_delay / 1000000;
    }
    else {
        stop_threshold = (double) rate_param * stop_delay / 1000000;
    }
    snd_pcm_sw_params_set_stop_threshold(*in_out_pcm, sw_params, stop_threshold);

    if (snd_pcm_sw_params(*in_out_pcm, sw_params) < 0) {
        snd_pcm_sw_params_dump(sw_params, log);
        PrintErr("Unable to get current sw params.");
        return DEMO_FAIL;
    }

    // for dump info
    PrintInfo("===hw_params_dump====\n");
    snd_pcm_hw_params_dump(hw_params, log);
    PrintInfo("=====================\n");

    PrintInfo("===sw_params_dump====\n");
    snd_pcm_sw_params_dump(sw_params, log);
    PrintInfo("=====================\n");

    PrintInfo("===pcm_dump==========\n");
    snd_pcm_dump(*in_out_pcm, log);
    PrintInfo("=====================\n");

    snd_output_close(log);

    FUNC_EXIT();
    return ret;
}

close 逻辑参考 prog_alsa_demo 中的:

snd_pcm_close(ctx->in_pcm);
snd_pcm_close(ctx->out_pcm);

read 逻辑参考 prog_alsa_demo 中的:

static int sample_capture(struct ctx *ctx)
{
    FUNC_ENTER();
    int    ret = DEMO_SUCCESS;
    int rc;
    char *buffer;
    unsigned int size;
    unsigned int total_frames_read;
    unsigned int bytes_per_frame;
    // 每次读取 buffer size 大小 的数据
    snd_pcm_uframes_t frames = snd_pcm_bytes_to_frames(ctx->in_pcm, DEFAULT_PERIOD_SIZE * DEFAULT_PERIOD_COUNT);
    // 每次读取 period size 大小 的数据
    //snd_pcm_uframes_t frames = snd_pcm_bytes_to_frames(ctx->in_pcm, DEFAULT_PERIOD_SIZE);

    size = snd_pcm_frames_to_bytes(ctx->in_pcm, frames);
    buffer = malloc(size);
    if (!buffer) {
        PrintErr("unable to allocate %zu bytes\n", size);
        return -1;
    }

    bytes_per_frame = snd_pcm_frames_to_bytes(ctx->in_pcm, 1);
    total_frames_read = 0;

    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

    while (!close_flag) {
        if (ctx->in_pcm) {
            rc = snd_pcm_readi(ctx->in_pcm, (void*)buffer, frames);
            if (rc == -EPIPE) {
                /* EPIPE means overrun */
                PrintErr("overrun occurred\n");
                snd_pcm_prepare(ctx->in_pcm);
            } else if (rc < 0) {
                PrintErr("error from read: %s\n", snd_strerror(rc));
                break;
            } else if (rc != (int)frames) {
                PrintErr("short read, read %d frames\n", rc);
            } else {
                total_frames_read += frames;
                if (fwrite(buffer, bytes_per_frame, frames, ctx->in_file) != frames) {
                    PrintErr("Error capturing sample\n");
                    break;
                }
                PrintInfo("total_read data(%u), read frames(%u) \n", total_frames_read, frames);
            }
        } else {
            PrintErr("pcm null.\n");
            ret = E_ERR_NOT_INIT;
            break;
        }
    }

    free(buffer);
    FUNC_EXIT();
    return 0;
}

write 逻辑参考 prog_alsa_demo 中的:

static int sample_playback(struct ctx *ctx)
{
    FUNC_ENTER();
    int    ret = DEMO_SUCCESS;
    char * buffer;
    size_t buffer_size         = 0;
    size_t num_read            = 0;
    size_t remaining_data_size = ctx->chunk_header.sz;
    size_t played_data_size    = 0;
    size_t read_size           = 0;

    // frames to bytes
    // 每次读取 buffer size 大小 的数据
    buffer_size = snd_pcm_frames_to_bytes(ctx->out_pcm, DEFAULT_PERIOD_SIZE*DEFAULT_PERIOD_COUNT);
    // 每次读取 period size 大小 的数据
    // buffer_size = snd_pcm_frames_to_bytes(ctx->out_pcm, DEFAULT_PERIOD_SIZE);
    buffer      = (char *)malloc(buffer_size);
    if (!buffer)
    {
        PrintErr("unable to allocate %zu bytes\n", buffer_size);
        return -1;
    }

    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

    PrintInfo("data size(%u), buffer_size(%u)\n", remaining_data_size, buffer_size);
    do
    {
        memset(buffer, 0, sizeof(buffer));
        read_size                = remaining_data_size > buffer_size ? buffer_size : remaining_data_size;
        num_read                 = fread(buffer, 1, read_size, ctx->out_file);
        snd_pcm_uframes_t frames = snd_pcm_bytes_to_frames(ctx->out_pcm, num_read);
        PrintInfo("remaining data size(%u), write frames(%u) \n", remaining_data_size, frames);
        if (num_read > 0)
        {
            if (ctx->out_pcm)
            {
                int ret = snd_pcm_writei(ctx->out_pcm, (void *)buffer, frames);
                if (ret == -EPIPE)
                {
                    PrintErr("underrun occured\n");
                }
                else if (ret < 0)
                {
                    PrintErr("error from writei (%d:%s)\n", ret, snd_strerror(ret));
                    break;
                }
            }
            else
            {
                PrintErr("pcm null.\n");
                break;
            }

            remaining_data_size -= num_read;
            played_data_size += snd_pcm_frames_to_bytes(ctx->out_pcm, frames);
        }
    } while (!close_flag && num_read > 0 && remaining_data_size > 0);

    free(buffer);
    PrintInfo("Played (%zu) bytes. Remains (%zu) bytes.\n", played_data_size, remaining_data_size);

    FUNC_EXIT();
    return 0;
}

6. FAQ

6.1. 声卡注册失败

  • 根据kmsg定位原因:比如如下log表示声卡实例化失败

    asoc-simple-card soc:asoc_sound: ASoC: failed to instantiate card -110
    
  • CODEC fail

    使用 万用表 和 示波器 测量 CODEC 电压,时钟;配合 i2c_read_write 确认 i2c 设备通信是否正常

6.2. 播放无声

  • 确认音频源为 非静音文件

  • 使用 aplay 或者 tinyplay 播放,定位问题是发生在用户态还是内核态

  • 播放等待10秒以上确认是否为 I/O error 问题

  • 使用 amixer 或者 tinymix 检查 CODEC 内部 DAC 通路是否打开,音量是否静音

  • 查看 寄存器 配置,配合芯片手册或者 CODEC 手册确认配置是否正确:IOMUX,DAI,CODEC。

  • 使用 万用表 和 示波器 测量电压,时钟,数据。确认电压,时钟正常,数据线上有波形;测量CODEC 近端 模拟输出信号是否正常,测量 PA 使能 gpio 电平,逐级定位问题点

6.3. 播放失真

  • 使用 aplay 或者 tinyplay 播放 1k0 音频文件

  • 使用 示波器 测量模拟输出的正弦波是否正常,是否出现削顶失真

  • 调整 数字或者模拟增益,观察 CODEC 芯片输出端波形,对比指标测试数据是否相符

  • 逐级检查引入失真的节点

6.4. 录音无声

  • CODEC 端通过 信号发生器 生成 1k0 波形输入

  • 使用 arecord 或者 tinycap 录音,定位问题是发生在用户态还是内核态

  • 录音等待10秒以上确认是否为 I/O error 问题

  • 使用 amixer 或者 tinymix 检查 CODEC 内部 ADC 通路是否打开,音量是否静音

  • 查看 寄存器 配置,配合芯片手册或者 CODEC 手册确认配置是否正确:IOMUX,DAI,CODEC。

  • 使用 万用表 和 示波器 测量电压,时钟,数据。确认电压,时钟正常,数据线上有波形;测量

  • CODEC 近端 模拟输入信号是否正常,逐级定位问题点

6.5. 录音失真

  • CODEC 端通过 信号发生器 生成 1k0 波形输入
  • 使用 arecord 或者 tincap 录音,通过 loopback 输出,使用 示波器 测量;或者通过 PC 工具 分析
  • 调整 数字或者模拟增益,观察 loopback 波形,对比指标测试数据是否相符
  • 逐级检查引入失真的节点

6.6. 速率过快或者过慢

  • 查看 clk summary 确认时钟(MCLK, BCLK, LRCK)是否准确
  • 使用 示波器 确认时钟信号是否准确
  • 使用 PC 工具 录制输出信号,通过波形或者频谱分析,确认数据是否丢失或者增加
  • 使用 逻辑分析仪 dump 芯片输出端数据,确认数据是否丢失或者增加

6.7. 规律性断音

  • 规律性的断音问题通常发生于异源系统中,比如:UAC 应用场景,BT 语音应用场景,网络音频推流等
  • 根本原因为时钟是异步的,随着时间的推移出现累积误差。该类问题可通过 音频时钟补偿 解决。
  • buffer 边界的处理异常也会导致规律性的断音,杂音。可通过 xrun 分析问题。

6.8. 杂音

  • 确认时钟信号是否准确,检查 jitter 是否过大。
  • 确认时钟上是否有毛刺,特别是在边沿有效值判定范围电压内。
  • 确认 CODEC 电源和地 情况,CODEC 对电源噪声敏感,任何耦合进电源或地的噪声都将导致CODEC性能下降,底噪增大,出现杂音。
  • 硬件采用差分电路 抑制共模噪声。
  • 检查硬件 PCB 布局,排查噪声来源。