# Audio 开发调试说明

## 概述

Audio 主要是实现数字音频播放（ Playback）和模拟音频采集（ Capture）的功能，体现在消费终端中常见的称呼可能是喇叭，麦克风，耳机等各种音频设备。要实现这些音频设备的功能，多数时候会使用到 I2S(Inter-IC Sound) 总线，中文叫集成电路内置音频总线 , 是飞利浦半导体公司 ( 现为恩智浦半导体公司 ) 针对数字音频设备之间的数据传输制定的一种总线标准。

## 特点

芯片上有两路全双工的 I2S 信号线，在 Master 模式下，最高速率能达到 40Mbps。
- 支持主从两种模式
- RX 支持 1/2/4/8/16 通道的音频输入。
- TX 支持 1/2 通道的音频输出。
- 支持 8/16/32/44.1/48/64 KHz 采样率。
- 支持 16/24/32 bit 采样深度。

TDM (Time-Division Multiplexing) 模式需要 32bit 对齐。

## 功能描述

### 典型应用

Audio 的典型应用有有两个场景，一种是 playback、一种是 capture。
具体到硬件应用上，全双工都是采用如下连接方式进行连接。区别在于一种是没有控制信号，一种是多了一组控制信号线。

![ 典型应用 1 ](./_static/_images/38-Audio_Debugging_Guide/audio_driver_typical_1.png)

上图是 CODEC 通过 I2S 和 SOC 信号连接，在时钟信号都正确的情况下， playback 通过 SD Out / DATA OUT 信号线送出数据给 CODEC， capture 通过 SD In / DATA In 接收到 Codec 来的数据。

这种不需要控制的 Codec 在市面上是存在的，还有部分是有控制信号线的。例如下图， CODEC 和 SOC 通过 I2C 连接，传输控制信号，一般这种会涉及到部分的路由控制或者说比较复杂的 Codec Driver。

![ 典型应用 2 ](./_static/_images/38-Audio_Debugging_Guide/audio_driver_typical_2.png)


### 功能原理

I2S 的传输主要涉及到如下几类数据线，
- MCLK / SYSCLK   代表的是主时钟（ Master Clock），或者说是系统时钟，用于 SOC 和 CODEC 之间同步。
- SCK / BCLK   代表串行时钟信号（ Serial Clock），或者说是位时钟。 SCK 是模块内的同步信号 , 从模式时由外部提供 , 主模式时由模块内部自己产生。一个脉冲对应一位数字音频数据。
- WS / LRCK   代表采样时钟（ Word Select），也叫左右声道选择信号（ Left Right Clock）
- SD / DATA 代表串行数据信号（ Serial），也叫数据线，它主要用于传输数字音频的数据。如果是全双工，会有两根这样的线，一根用于 SOC 发送（常用于 Playback），一根用于 SOC 接收（常用于 Capture）。

![ 信号示例 ](./_static/_images/38-Audio_Debugging_Guide/audio_driver_i2s.png)


我们在 Audio 开发中也经常会遇到这几个概念
- 采样率：每秒钟取得声音样本的次数，可以理解为 ADC、 DAC 的值每秒修改多少次，常见的采样率是 8 / 16 / 32 / 44.1 / 48 / 64 KHz 等，它的频率和 WS/LRCK 的频率是基本一致的，比如我们播放采样率位 16 KHz 的音频， WS / LRCK 这个引脚也应该是 16 KHz 的频率。
- 位宽：可以理解为一个响度的量程，位宽越大，数字声音的精度越高，声音的表现会更加细腻，也常有深度的说法，位深等等。体现在上图中就是 SD / DATA 的一个声道里面的的数量， 0-15 就是有 16bit。常见的深度有 16 bit , 24 bit 等等。
- 通道数：通常理解为连接的麦克风、喇叭的数量。比如左右声道，就形如两个通道。


**时钟关系：**
Audio 的时钟比较多，这里单独说明一下几个时钟的关系。上文我们说到采样率的数值基本就是 WS / LRCK 的频率，以此我们可以得到 SCK / BCLK 的频率，它的计算公式是：位宽 X 通道数 X 采样率。比如在播放一个音频文件，它的格式为 16bit 的位深，双通道， 16 KHz 的采样率，那么 SCK / BCLK 的频率理论上应该是 16 x 2 x 16KHz = 512 KHz ，如果我们调试的时候发现 SCK 不是 512 KHz，那么就需要 debug ， 看看 Master 侧给的时钟是否有异常。
此时 MCLK / SYSCLK 的时钟可能有多种频率，但基本会有如下规律， MCLK 一般是 BCLK 的 2 / 4 /8 倍， 也就是说可能是 1.024M、 2.048M、 4.096M， MCLK 一般也是 WS 的 256 倍，也就是 256 x 16KHz = 1.048M ， 但这个频率主要是看 CODEC 和 SOC 侧的设置，不一定严格满足某一种，但一定是其中一种，否则出来的声音会有瑕疵的。

### 工作方式

Audio 的工作其实涉及一连串内容，贯穿了整个系统。我们需要保证每个节点都正常的情况下，整个 Audio 才能正常工作起来 ，有点类似摄像头的 pipeline。

![ 信号示例 ](./_static/_images/38-Audio_Debugging_Guide/audio_driver_alsa.png)

在确保硬件接线正确的前提下，当 CODEC 正常上电（如果有控制接口的，还需要确认能否正常控制，比如 I2C 通信正常）， SOC 侧设置正确，正常接入 Alsa 框架，保证声卡能够正常启动和使用。

然后我们再使用 tinymix 或者 amixer 这样的 Audio 工具。详细的使用可以参考本文 [功能使用](#span-id-usage) 部分的说明。

## 驱动代码
uboot 下基本没有相关配置，所以代码和配置主要集中在 kernel 部分。
由于 X5 EVB 板端没有 Codec 芯片，所以这边以 40 pin 外接的音频板 ( WM8960 ) 为例来说明，其余的音频板可以根据实际情况，参考 功能使用部分 kernel 阶段 进行调试。

### kernel 下驱动代码及配置、设备树

DTS 部分：
在板子对应的设备树中做添加，比如对应的设备树是 x5-evb-lp4-1_b.dts。

```
&i2c5 {
        wm8960:wm8960@1a{
                compatible = "wlf,wm8960";
                reg = <0x1a>;
                #sound-dai-cells = <0>;
                status = "okay";
        };
};

......

&dw_i2s1 {
        status = "okay";
        dwc-master = <1>; /*这里的 dwc-master 表示 soc 侧是 master*/
};

......

&hobot_sound_machine{

        status = "okay";
        #address-cells = <1>;
        #size-cells = <0>;
        simple-audio-card,name = "duplex-audio-i2s1";

        simple-audio-card,dai-link@0 {
                link-name = "dai-link0";
                reg = <0>;
                format = "i2s";
                bitclock-master = <&snd1_mm>;
                frame-master = <&snd1_mm>;
                snd1_mm: cpu {
                        sound-dai = <&dw_i2s1 1>;
                };
                codec {
                        sound-dai = <&wm8960>;
                };
        };

};
```
我们结合 alsa 框架，要确认如下代码位置。\
codec 即 codec-dai 代码位置： kernel/sound/soc/codecs/xxx.c ， 这部分代码就比较多，它包含了已经上传到 linux 主线的 codec 代码。\
platform 即 cpu-dai 代码位置： kernel/sound/soc/dwc/dwc-i2s.c ， 这部分代码跟随平台，一般不会很多。\
machine 代码位置： kernel/sound/soc/hobot ， 这部分也是和 SOC 相关，主要是串联 codec-dai 和 cpu-dai。

打开相关 codec config 开关，比如这里就要确认 codec、 machine、 platform 的相关宏定义。
```
CONFIG_SND_DESIGNWARE_I2S=y
CONFIG_SND_SOC_WM8960=m
CONFIG_SND_HOBOT_SOUND_MACHINE=m
```
我们也建议通过 make menuconfig 的方式去 enable 需要的 codec，参考如下步骤：\
主线上没有的 codec ， 将 codec 驱动文件增加到 kernel/sound/soc/codecs/ 目录下。\
修改 sound/soc/codecs/Kconfig 以及 Makefile，将 codec 加入驱动编译。\
其中， Kconfig 可以参考如下代码添加。
```
config SND_SOC_XXX
    tristate "XXX Audio Codec"
```

Makefile 可以参考如下代码添加

```
obj-$(CONFIG_SND_SOC_XXX)    += XXX.o
```

完成了刚刚提到的步骤，就可以和主线上存在的 codec 一样，通过 make menuconfig 的方式去 enable 需要的 codec 了，路径可以参考如下提示：

```
-> Device Drivers
    <*> Sound card support --->
        <*> Advanced Linux Sound Architecture --->
            <*> ALSA for SoC audio support --->
                    CODEC drivers --->
                        <M> XXX Audio Codec
```


## <span id="usage"/> 功能使用

### kernel 阶段使用
我们在使用 Audio 的时候，需要打通多个环节，我们以上文提到的工作方式为线索，从下到上进行打通。


![ 连接示例 ](./_static/_images/38-Audio_Debugging_Guide/audio_driver_40pin_wm8960_connect.png)

首先就是 Codec 正常注册环节，同样以 WM8960 为例（接线图如上述所示），首先要保证该 Codec 的 I2C 通信正常，这里主要是要调试 Codec 本身的驱动，\
如果是 SPI 通信的，可以结合 SPI 调试手册进行调试，同理， I2C 通信可以结合 I2C 调试手册进行。\
这里我们要确认 3 个点，是否正常上电； I2C 地址； I2C 速率。

按照上文给的设备树和代码进行配置，正常启动之后，我们手动加载 ko，参考如下命令：
```
modprobe designware_i2s i2s_ms=1
modprobe snd-soc-wm8960
modprobe snd-soc-hobot-sound-machine
```

我们可以在对应的 I2C 总线上探测到设备地址，甚至可以找到该地址对应设备，找到打印寄存器的文件节点。
```
# 探测设备地址

root@buildroot:~# i2cdetect -y -r 5
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- 
root@buildroot:~#
```

```
# 找到可以打印寄存器的节点

cat /sys/kernel/debug/regmap/5-001aregisters > 0x1a_registers.txt
```

其次我们就可以检查 codec-dai 是否有正常注册上了，正常情况下， codec 驱动中有类似如下函数

```
snd_soc_register_codec
devm_snd_soc_register_component
```

正常情况下不会报错，如果注册失败，会返回具体报错问题。一般是设备树节点配置有问题，这里可以结合每个 codec 驱动的代码进行 debug。

然后我们就需要检查 cpu-dai 部分，这里本质上是对 I2S 控制器进行注册和检查，对应代码和配置上文都有提到，这里主要检查我们使用 I2S 和 CODEC 配合的部分

- 确认 I2S 控制器模式， SOC 侧和 codec 侧都需要确认。
- 确认位宽是否有限制，常见的位宽是 16/24/32bit。
- 确认传输格式，左对齐右对齐等等。
- 确认采样率是否有限制，比如有些 CODEC 的 mclk 固定之后，只有固定的采样率。


比如我们的 I2S 主从模式就是在 dts 中配置的。同样的，如果注册有异常， kernel 中会打印报错日志，根据具体情况进行判断；但如果是在播放或者录制时候的调试，那么就需要再次确认刚刚提到的 I2S 和 CODEC 配合的部分，大多数情况就是这里没有设置好，导致点亮环节出现无声、卡顿、声音异常等情况。


最后我们就需要通过 machine 将 codec dai 和 cpu dai，这里的配置涉及到 dts 、 deconfig 选择。配置完成之后我们就可以在系统中检查声卡是否注册成功，检查是否有对应的 pcm 设备了。


```
# 检查声卡， X5 EVB 上默认有一张 dummy 声卡

root@buildroot:/proc/asound# cat cards
 0 [guaaudio       ]: gua-audio - gua-audio
                      gua-audio
root@buildroot:/proc/asound#


# 正常注册之后，可以看到两张声卡
root@buildroot:~# cat /proc/asound/cards
 0 [guaaudio       ]: gua-audio - gua-audio
                      gua-audio
 1 [duplexaudioi2s1]: simple-card - duplex-audio-i2s1
                      duplex-audio-i2s1


## 检查 pcm 设备 , 可以看到 id 和 name 等等信息，都能和 codec 中的对上。

root@buildroot:~# cat /proc/asound/duplexaudioi2s1/pcm0c/info
card: 1
device: 0
subdevice: 0
stream: CAPTURE
id: i2s1-wm8960-hifi wm8960-hifi-0
name: i2s1-wm8960-hifi wm8960-hifi-0
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1
root@buildroot:~# cat /proc/asound/duplexaudioi2s1/pcm0p/info
card: 1
device: 0
subdevice: 0
stream: PLAYBACK
id: i2s1-wm8960-hifi wm8960-hifi-0
name: i2s1-wm8960-hifi wm8960-hifi-0
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1
root@buildroot:~#
```

我们常见的问题是 dts 中的 machine 部分没有配置正确，导致声卡注册不上。这里我们就要确认清楚 machine 的配置。

完成上述使用配置之后， kernel 空间的部分基本完成，接下来是用户态的使用。



### 用户态阶段使用
这里需要配合常见的嵌入式音频工具 tinyalsa ，一般有三个基本的， tinymix 调试通路， tinycap 实施录制， tinyplay 实施播放。

tinymix 说明：
```
# tinymix -h
    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
```

示例命令：\
查看当前 codec 支持设置的属性（每一款 Codec 对应的通路一般都不一样，所以这里只是举例，需要根据实际情况操作，比如下文对 WM8960 的操作，就是按照 WM8960 CODEC 中的设置进行的）

```
#:/userdata# tinymix contents
```

通过 ctr 设置属性：
```
#:/userdata# tinymix set 6 120
#:/userdata# tinymix get 6
120 (range 0->255)
```

通过 name 设置属性：
```
#:~# tinymix set "ADC4 PGA gain" 1
#:~# tinymix get "ADC4 PGA gain"
1 (range 0->31)
```

tinycap 说明：
```
#:/userdata# tinycap
Usage: tinycap {file.wav | --} [-D card] [-d device] [-c channels] [-r
rate] [-b bits] [-p period_size] [-n n_periods] [-t time_in_seconds]
Use -- for filename to send raw PCM to stdout
```

示例命令：
```
tinycap /userdata/test.wav -D 0 -d 0 -t 5
```

tinyplay 说明：
```
#:/userdata# tinyplay
usage: tinyplay file.wav [options]
options:
-D | --card <card number> The device to receive the audio
-d | --device <device number> The card to receive the audio
-p | --period-size <size> The size of the PCM's period
-n | --period-count <count> The number of PCM periods
-i | --file-type <file-type > The type of file to read (raw or wav)
-c | --channels <count> The amount of channels per frame
-r | --rate <rate> The amount of frames per second
-b | --bits <bit-count> The number of bits in one sample
-M | --mmap Use memory mapped IO to play audio
```
示例命令：
```
tinyplay [file.wav] -D 0 -d 1
```

确定好工具之后，我们就需要确认音频路由。我们首先要阅读 Audio Codec 的手册，最好是能找到类似这样的图：
![ WM8960 通路参考 ](./_static/_images/38-Audio_Debugging_Guide/audio_driver_wm8960_route.png)

它可以形象的理解 Audio 路由的连接。

然后，我们再结合 tinymix 命令确认路由：
我们使用的 Device 编号是 1 ，所以我们这边操作的时候 -D 后需要跟 1
```
tinymix -D 1 contents
```

接着我们就可以打通路由，进行 Audio 录制和播放了。\
录制：
```
tinymix -D 1 set 'Left Input Boost Mixer LINPUT1 Volume' 3
tinymix -D 1 set 'Right Input Boost Mixer RINPUT1 Volume' 3

tinymix -D 1 set 'Left Input Boost Mixer LINPUT1 Volume' 1
tinymix -D 1 set 'Right Input Boost Mixer RINPUT1 Volume' 1

tinymix -D 1 set 'Capture Volume' 40,40
tinymix -D 1 set 'ADC PCM Capture Volume' 200,200

tinymix -D 1 set 'Left Boost Mixer LINPUT1 Switch' 1
tinymix -D 1 set 'Right Boost Mixer RINPUT1 Switch' 1

tinymix -D 1 set 'Left Input Mixer Boost Switch' 1
tinymix -D 1 set 'Right Input Mixer Boost Switch' 1

tinymix -D 1 set 'Capture Switch' 1,1

tinycap /userdata/2chn_test.wav -D 1 -d 0 -c 2 -b 16 -r 48000 -p 512 -n 4 -t 5

```

播放：
```
tinymix -D  1 set 'Left Output Mixer PCM Playback Switch' 1
tinymix -D  1 set 'Right Output Mixer PCM Playback Switch' 1
tinymix -D  1 set 'Speaker DC Volume' 3
tinymix -D  1 set 'Speaker AC Volume' 3
tinymix -D  1 set 'Speaker Playback Volume' 127 ， 127
tinymix -D  1 set 'Playback Volume' 255 ， 255
tinymix -D  1 set 'Left Output Mixer PCM Playback Switch' 1
tinymix -D  1 set 'Right Output Mixer PCM Playback Switch' 1

tinyplay /userdata/2chn_test.wav -D 1 -d 0

```


## 常见问题


### Q1 ：无声问题如何定位。
我们同样以工作方式中提到的节点思路，每个点进行排查。首先分场景，是播放无声，还是录制无声。其次是排查软件和排查硬件；排查顺序一般以音源为顺序进行排查，比如播放，我们首先排查软件下发的音源是否异常， i2s 状态是否正确， codec 寄存器状态是否正确，最后到 I2S 信号是否正确，时钟是否正确，当然方便的情况下，检查音源没有异常的时候，直接量信号也可以，这样就能直接排查板端状态，而不是硬件侧。录制则是反过来的，比如下图，我们可以看到 I2S 硬件信号是正常的。

黄色的代表 DATA 线\
蓝色代表 BCLK\
紫色代表 LRCLK

![ Q1 ](./_static/_images/38-Audio_Debugging_Guide/audio_driver_q1.png)


### Q2: LOG 比较少，无法定位问题，怎么办？
调整 debug log 等级
```
echo "8 4 1 7" > /proc/sys/kernel/printk
echo -n "file dwc-i2s.c +p" > /sys/kernel/debug/dynamic_debug/control
```


### Q3 ：如何判断声卡注册成功和 pcm 设备对应关系？
Alsa 有 procfs，挂载目录为：/proc/asound， ALSA 使用 /proc/asound 目录下的文件保存设备信息和控制目的。调试时重点关注的信息介绍如下。\
/proc/asound/cards : 已注册声卡的列表。通过查看该节点，检查当前系统注册的声卡列表或者检查声卡是否注册成功。
/proc/asound/pcm : 分配的 pcm 流设备的信息。通过查看该节点，找到当前声卡支持的设备列表。这有助于测试时选择如何设置设备节点的 card/device 值。\
/proc/asound/cardX/pcmY\[c, p\]/ : 系统中声卡对应的每个 pcm 流设备都有一个类似上面的 procfs 目录。其中 X 代表声卡号，/proc/asound/cards 或者 /dev/snd 下的设备节点信息可确认； Y 代表设备号，/proc/asound/pcm 或者 /dev/snd 下的设备节点信息可确认。 c/p 分别代表 capture/playback。该目录可查看 PCM 设备的信息以及 status。
- info
    pcm 流的一般信息，比如声卡号、设备号、流类型、所绑定的 codec 类型等
- hw\_params
    pcm 流打开状态下，可以查看 pcm 的基础参数配置。比如采样率、位宽、通道数、 period\_size、 buffer\_size 等。
    配置的 period\_size、 buffer\_size 可能与这里打印的值不同。实际以这里查看为准，对应的是硬件的实际参数

    ```
    #:/proc/asound/card0/pcm0c/sub0# cat hw_params
    access: RW_INTERLEAVED
    format: S16_LE
    subformat: STD
    channels: 2
    rate: 48000 (48000/1)
    period_size: 1024
    buffer_size: 4096
    ```
- sw\_params
    pcm 流打开状态下，查看 start\_threshold、 stop\_threshold、 silence\_threshold 等。重点关注 start\_threshold、 stop\_threshold 阈值
    start\_threshold:
    该值设置太大，从开始播放到声音出来延时太长，会导致太短促的声音播不出来
    stop\_threshold:
    判断是否触发 xrun 的条件。当可用空间大小超过该值时，会触发 xrun。
    ```
    #:/proc/asound/card0/pcm0c/sub0# cat sw_params
    tstamp_mode: ENABLE
    period_step: 1
    avail_min: 1
    start_threshold: 1
    stop_threshold: 40960
    silence_threshold: 0
    silence_size: 0
    boundary: 4611686018427387904
    ```
- status
    可查看当前 substream 的状态 (running、 xrun 等 )， appl\_ptr/hw\_ptr 指针的值。其中， hw\_ptr 和 appl\_ptr 可以识别底层硬件无法传输或接收任何数据的情况。
    比如，启动测试但是中断未正常触发，数据传输将会停滞。此时， hw\_ptr/appl\_ptr 指针将持续得不到更新


### Q4 : xrun 是什么问题，遇到了该怎么定位和解决？
音频在播放时偶现或者连续某个时间段出现断断续续、声音类似“呲呲”或者“爆破”的杂音，
一般是发生了 xrun。 xrun 丢帧受系统性能限制不可避免，在满足使用场景的需求下，允许有一定的丢帧率，只能通过某些方法优化达到尽量规避。如果出现 xrun 频发，无法恢复的情况，就需要排查代码实现上是否有缺陷

**可能出现 xrun 的场景**

播放时，应用会不断把音频数据填入驱动 buffer，驱动 buffer 经过 I2S
送给 codec 播放。当应用填入慢了，导致驱动 buffer 为空，就会触发 underrun 导致丢帧，可能会有声音异常的现象

录音时， codec 转化的数字信号经过 I2S
填入驱动 buffer，应用会从驱动 buffer 中读取音频数据。当应用读取的速度赶不上写入的速度，超过 stop\_threshold 阈值就会触发 overrun

触发 xrun 异常的使用场景
- 音频数据来自 storage, IO 操作耗时
- 访问 RAM disk 和读写 pcm 设备单线程运行
- 语音进程优先级较低，其他优先级更高的任务抢占


**定位 xrun**

xrun 问题比较多种多样，我们尽可能将问题搜集整理，以此来提供更丰富的参考。

**检查 xrun 是否由于 IO 操作耗时导致**

- 音频文件写入 ram disk 或者特定的设备文件，参考命令
```
mkdir /data/audio_test
tinycap /data/audio_test/test.wav

tinycap /dev/null
```
注意，以上只能用于定位 xrun 是否由于 IO 操作耗时导致，不能作为解决 xrun 的方案

- 优化应用程序测试

使用独立的线程访问媒体存储和读写 PCM 设备。

以录音为例，写文件创建单独的线程和创建 ring buffer(ring
buffer 大小可自由调整 )， pcm\_read 的 buffer 写入 ring buffer， fwrite 从 ring
buffer 读取数据。只要 fwrite 陷入时间不超过 ring
buffer 的限制，就不会发生 xrun 导致数据。

**依靠/proc 下配置一些功能来查看信息**

开启编译 xrun\_debug 的 config 配置项。编译并重新烧写镜像。（如果 xrun\_debug 存在，表示该功能处于使能状态，不需要执行这一步）
```
CONFIG_SND_PCM_XRUN_DEBUG=y
CONFIG_SND_VERBOSE_PROCFS=y
CONFIG_SND_DEBUG=y
```

对应位置 /proc/asound/cardX/pcmY[c,p]/xrun_debug
比如，往 xrun\_debug 写入 3 ，也就是启用基本调试和堆栈功能，可以查看 PCM 流是否由于某种原因而停止

```
# Enable basic debugging and dump stack
# Usefull to just see, if PCM stream is stopped for a reason (usually wrong audio process timing from scheduler)
echo 3 > /proc/asound/card0/pcm0p/xrun_debug
```

**通过 ftrace 来分析问题**

Ftrace 是内核故障调试和性能分析工具，用于分析发生 xrun 的可能原因。检查是否有长时间中断被占用导致调度不及时的情况。

- 开启编译 ftrace 的 config 配置项。编译并重新烧写镜像。
```
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_STACK_TRACER=y
CONFIG_DYNAMIC_FTRACE=y
```
- 生成 trace 文件
```
echo > /sys/kernel/debug/tracing/trace
echo 1 > /sys/kernel/debug/tracing/events/signal/enable
app_test  # 执行你的应用
killed
echo 0 > /sys/kernel/debug/tracing/events/signal/enable
cat /sys/kernel/debug/tracing/trace > /userdata/trace.txt
```
或者使用 trace-cmd 工具，这里贴一下详细代码。
```
#!/bin/sh
date
rm /userdata/trace.dat
echo "" > /userdata/log/usr/message
trace-cmd record -e irq -e sched_switch -e sched_wakeup -o /userdata/trace.dat &
cnt=0
while true;do
    nr=$(grep -r "audio unexpected delay count" /userdata/log/usr/message | wc -l)
    echo "nr " $nr
    if [ $nr -gt 1 ];then
    echo "find the error message,send the SIGINT Term Interrupt to the trace-cmd !"
    kill -2 $(pidof trace-cmd)
    date
    exit 0
    else
    if [ $cnt -gt 100 ];then
        date
        cnt=0
        echo "rm the trace.dat file,and restart the trace-cmd"
        kill -9 $(pidof trace-cmd)
        rm -rf /userdata/trace.dat
        trace-cmd record -e irq -e sched_switch -e sched_wakeup -o /userdata/trace.dat &
    fi
    fi
    cnt=$((cnt+1))
    sleep 1
done
```

- 分析 trace 文件

分析 trace 文件的方式有多种。比如 google trace viewer 工具、 kernelshark 等
如果使用 google trace viewer 工具需要修改源码，回退支持 lazy 的 trace 输出，使输出的 trace 文件满足谷歌 trace viewer 的格式要求。 patch 如下：
```
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index 29db703f6880..86de03221287 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -870,6 +870,10 @@ config HIST_TRIGGERS_DEBUG

       If unsure, say N.

+config OLD_TRACE
+   bool "old ftrace"
+   default y
+
 endif # FTRACE

 endif # TRACING_SUPPORT
diff --git a/kernel/trace/trace_output.c b/kernel/trace/trace_output.c
index bc24ae8e3613..e0df7a412c95 100644
--- a/kernel/trace/trace_output.c
+++ b/kernel/trace/trace_output.c
@@ -482,16 +482,23 @@ int trace_print_lat_fmt(struct trace_seq *s, struct trace_entry *entry)
    hardirq              ? 'h' :
    softirq              ? 's' :
                   '.' ;
-
+#ifndef CONFIG_OLD_TRACE
    trace_seq_printf(s, "%c%c%c%c",
         irqs_off, need_resched, need_resched_lazy,
         hardsoft_irq);
-
+#else
+   trace_seq_printf(s, "%c%c%c",
+            irqs_off, need_resched,
+            hardsoft_irq);
+#endif
    if (entry->preempt_count)
    trace_seq_printf(s, "%x", entry->preempt_count);
    else
    trace_seq_putc(s, '.');

+
+
+#ifndef CONFIG_OLD_TRACE
    if (entry->preempt_lazy_count)
    trace_seq_printf(s, "%x", entry->preempt_lazy_count);
    else
@@ -501,7 +508,7 @@ int trace_print_lat_fmt(struct trace_seq *s, struct trace_entry *entry)
    trace_seq_printf(s, "%x", entry->migrate_disable);
    else
    trace_seq_putc(s, '.');
-
+#endif
    return !trace_seq_has_overflowed(s);
 }
```
关于 ftrace 的生成和使用，更多请参考官方文档描述\
<https://www.kernel.org/doc/html/latest/trace/ftrace.html>\
<https://perfetto.dev/docs/data-sources/cpu-scheduling>\
<https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/trace-cmd.git/>\
<https://kernelshark.org/Documentation.html#graph-info-line>\

总的来讲，面对 xrun 问题也有一些大方向可以排查。
-   提高线程优先级 ( 设置实时线程 + 优先权值 )
-   调大 period\_size，改变 DMA 传输数据量
-   异步实现 I/O 读写和 ALSA 设备读写

### Q5 : 打开设备节点报错等相关问题：

- 出现无法打开指定 PCM 设备的提示，没有找到这个文件或者目录
```
Unable to open PCM device cannot open device (4) for card (1):
    No such file or directory
```
驱动加载失败，导致 /dev/snd 下没有生成设备节点；设置的 card/device 值错误导致找不到对应的设备节点
其中，/dev/snd 下没有生成设备节点的可能原因：

原因一， I2S/codec 驱动加载失败\
原因二， DTS 中各驱动节点的 status 属性未设置“ okay”状态，导致驱动加载时不会执行任何操作。


- 出现无法打开 PCM 设备的提示
```
Unable to open PCM device ()
```

检查测试设置的参数值，是否超过 I2S 和 codec 驱动中对于 snd\_soc\_dai\_driver 定义的 rate、 format、 channel 最值范围交集；
检查测试设置的 period\_size/period\_count， 是否超过驱动定义的有效范围。

- 打开设备节点卡住，应用无任何 log 日志打印
    ALSA 框架允许单个设备同一时刻只能打开一次。检查应用实现上，是否存在上次执行还未退出的情况下，又启动应用

- pcm\_read/pcm\_write 返回异常值为 -5
调整 ALSA 框架 log 打印等级，检查是否由于中断异常或者硬件链接异常导致

- pcm\_read/pcm\_write 返回异常值为 -32
一般是发生了 xrun

- pcm\_read 返回异常值为 -16
正常录音的情况 (X5 做 slave) 下，时钟突然断掉或者硬件链接中断会发生该异常

- 录制数据均为 0 或者播放没有声音\
    1 ，示波器测量 I2S 的时钟线频率是否正常， data 线是否有数据输出。\
    2 ，如果时钟频率不符合预期或者量到某个时钟信号持续保持低电平的情况，检查 I2S\
    寄存器的配置是否符合预期。比如主从模式、时钟比值状态\
    3 ，如果以上没有问题，就需要检查 codec 芯片是否正常工作。播放一个正弦波，测量 codec 侧模拟信号的\
    输出，如果这里没有输出，确认 codec 的时钟比值配置和 X5 端是否匹配，以及当前 X5 端的时钟比值是否在 codec 可支持范围。\
    4 ，如果 codec 的输出也能量到准确的信号，检查功放芯片是否正常工作。确认功放芯片各管脚幅值是否正常

- 录制 / 播放噪声

