基础
数据流框图
xrun概念
xrun是 “underrun”(欠速)和 “overrun”(溢出)的一种特殊情况,主要出现在数字音频工作站或音频处理应用程序中。但是underrun和overrun不只是音频领域的专业术语,在计算机网络、视频处理等领域也都有这个含义。本篇重点对音频领域的xrun做说明。
Underrun
Underrun 通常发生在数据发送端无法及时提供足够的数据以满足接收端的需求时(==供小于求==),放音出现。
Overrun
Overrun 通常发生在数据接收端无法及时处理所接收到的数据时(==供大于求==),录音出现。
过程分析
放音流程:
- 将音频文件从Flash读到DDR上。此时数据存储在堆栈空间buffer中
- 处理原始数据。此时可能会对原始数据进行处理包括重采样、增益等等
- 传输音频数据。将处理完成的数据通过DMA通道传输到音频控制器上,进而通过I2S或者其它总线传输到CODEC上最终通过喇叭放音
如上条目所示是放音的基本流程。分析以上步骤可以发现如果该流程是同步串行执行的话将十分耗费处理器的性能,所以在实际中,第一步和第三步通常是异步执行的,因此这就涉及到数据同步的问题。假设软硬件初始化完毕,本次的数据已经传输完毕而新的数据没有及时供应,也就是第一步的数据没有到位,此时就会导致underrun的发生
同理,对于录音来讲,就是放音的逆过程。
发生原因
在alsa中发生xrun最直接的表现就是调用snd_pcm_writei或者snd_pcm_readi时,返回值返回-EPIPE。这是因为 ALSA 驱动 buffer 没有数据可以丢给 codec 或者codec数据太多ALSA驱动接收不过来所导致的。而导致这种情况发生的原因包括:
生产者与消费者线程的优先级不一致导致的系统调度
DDR的buffer大小申请不合理
中断频繁
DMA配置策略导致没有及时将数据搬到FIFO
硬件播放格式与源数据格式不一致
Linux xrun调试方法
内核配置选中 CONFIG_SND_DEBUG、CONFIG_SND_PCM_XRUN_DEBUG和CONFIG_SND_VERBOSE_PROCFS
重新编译内核并烧录
配置调试参数
echo xxx > /proc/asound/card#/pcm0p/xrun_debug
(“#”替换为声卡号 0 / 1 / 2 …)1
2
3
4
5
6
7
81 Basic debugging - show xruns in ksyslog interface
2 Dump stack - dump stack for basic debugging
4 Jiffies check - compare the position with kernel jiffies (a sort of in-kernel monotonic clock),
show what's changed when basic debugging is enabled
8 Dump positions on each period update call
16 Dump positions on each hardware pointer update call
32 Enable logging of last 10 ring buffer positions
64 Show the last 10 ring buffer position only once (when first error situation occured)推荐使用的参数组合数字:53、11、29
播放音乐,当发生underrun的时候会有如下打印
1
audiocodec soc@xxxxxxxx:sound@0:XRUN:pcmC0D0p:0
xrun解决方案
通常对于函数返回值的判断需要进行一定处理
1
2
3
4
5if (rc == -EPIPE) {
/* EPIPE means underrun */
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(handle);
}调整生产者线程(将音频数据填充至buffer)和消费者线程(将buffer的数据写入硬件snd_pcm_writei)的优先级
调整period_size和period_count。 period_size:每次传输的数据长度。值越小,时延越小,cpu占用就越高;period_count:缓冲区period的个数,缓冲区越大发生XRUN的机会就越少。
配置软件参数:silence_threshold和stop_threshold
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
// snd_pcm_uframes_t buf_size = MINIMUM_SAMPLE_SET_SIZE;
// snd_pcm_hw_params_set_buffer_size_near(alsa_handle, params, &buf_size);
snd_pcm_uframes_t boundary = 0;
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_alloca(&sw_params);
/* Get current software configuration */
snd_pcm_sw_params_current(alsa_handle, sw_params);
/* Get boundary for ring pointers from a software configuration container */
snd_pcm_sw_params_get_boundary(sw_params, &boundary);
/* Set stop threshold inside a software configuration container */
/* 这里的2倍可以通过writei的次数与发生underrun的次数来判断 */
snd_pcm_sw_params_set_stop_threshold(alsa_handle, sw_params, buf_size * 2);
/* Set silence size inside a software configuration container */
snd_pcm_sw_params_set_silence_size(alsa_handle, sw_params, boundary);
/* Write sw parameters to the driver */
snd_pcm_sw_params(alsa_handle, sw_params);
}