linux - ALSA:播放音频的正确方法 - 阻塞模式和线程
问题描述
我无法获得正确的方法来编写循环播放音频和/或我不正确理解 Alsa 的 start_threshold、stop_threshold 和avail_min 参数的含义。我想达到最低延迟并知道要修改的正确参数,以使我的捕获过程播放链能够容忍音频处理时间的变化。在此示例中,我正在从文件中读取样本,以专注于播放部分。
这是我的输出设备配置,以阻塞模式打开(Ubuntu 16.04,PulseAudio 存在,default
设备):
ALSA <-> PulseAudio PCM I/O Plugin
Its setup is:
stream : PLAYBACK
access : RW_INTERLEAVED
format : FLOAT_LE
subformat : STD
channels : 1
rate : 44100
exact rate : 44100 (44100/1)
msbits : 32
buffer_size : 6144
period_size : 2048
period_time : 46439
tstamp_mode : NONE
tstamp_type : GETTIMEOFDAY
period_step : 1
avail_min : 4096
period_event : 0
start_threshold : 2048
stop_threshold : 2048
silence_threshold: 0
silence_size : 0
boundary : 6917529027641081856
我的播放线程从另一个线程获取音频缓冲区,该线程从文件中读取样本(因此比播放速率快)。这是播放功能(Cython 代码):
cdef int play_buffer(self, buffer_t *buf) nogil:
cdef int rc
cdef long t0, t1
t0 = timestamp_us()
rc = snd_pcm_writei(self.handle, buf.data, buf.period_size)
t1 = timestamp_us()
if rc >= 0:
printf("[%ld / %6ld] snd_pcm_writei: OK %d\n", t0, t1 - t0, rc)
else:
printf("[%ld / %6ld] snd_pcm_writei: ERR %d [%s]\n", t0, t1 - t0, rc, snd_strerror(rc))
if rc < 0:
if rc == -errno.EPIPE:
rc = snd_pcm_prepare(self.handle)
printf("snd_pcm_prepare: %d\n", rc)
else:
printf("play_buffer ERR %d\n", rc)
return WRITE_ERROR
elif rc != buf.period_size:
printf('snd_pcm_writei(): short write %d != %d', rc, buf.period_size)
return OK
这是输出(timestamp_us()
以微秒为单位返回系统时间):
[1525102519583090 / 540] snd_pcm_writei: OK 2048
[1525102519585406 / 16] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
[1525102519587798 / 393] snd_pcm_writei: OK 2048
[1525102519590018 / 3] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
[1525102519592303 / 415] snd_pcm_writei: OK 2048
[1525102519594523 / 3] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
[1525102519596823 / 1905] snd_pcm_writei: OK 2048
[1525102519599242 / 12] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
[1525102519601707 / 8023] snd_pcm_writei: OK 2048
[1525102519609754 / 45] snd_pcm_writei: OK 2048
[1525102519609811 / 27] snd_pcm_writei: OK 2048
[1525102519609847 / 28] snd_pcm_writei: OK 2048
[1525102519611328 / 40] snd_pcm_writei: OK 2048
[1525102519613546 / 48501] snd_pcm_writei: OK 2048
[1525102519662079 / 50678] snd_pcm_writei: OK 2048
[1525102519712804 / 49487] snd_pcm_writei: OK 2048
[1525102519762318 / 50521] snd_pcm_writei: OK 2048
[1525102519812868 / 49497] snd_pcm_writei: OK 2048
[1525102519862394 / 49630] snd_pcm_writei: OK 2048
[1525102519912051 / 49875] snd_pcm_writei: OK 2048
[1525102519961953 / 50647] snd_pcm_writei: OK 2048
[1525102520012655 / 49949] snd_pcm_writei: OK 2048
[1525102520062632 / 49713] snd_pcm_writei: OK 2048
[1525102520112373 / 49495] snd_pcm_writei: OK 2048
[1525102520161898 / 62] snd_pcm_writei: OK 2048
[1525102520161977 / 50485] snd_pcm_writei: OK 2048
[1525102520212490 / 49514] snd_pcm_writei: OK 2048
.. continues with no errors ...
一开始我不明白这些 EPIPE 错误的原因;我所期望的是,snd_pcm_writei
要么立即返回,要么在返回之前等待(或多或少)一段时间,因为我处于阻塞模式,而且我提供样本的速度比播放采样率所需的速度更快。当错误序列结束时,播放正常。此外,如果我将实时优先级设置为我的线程 ( pthread_setschedparam()
),我会得到一个无穷无尽的 OK 2048 / ERR -32 列表,并且我只听到噪音。这对我来说真的很奇怪。
我的错在哪里?
谢谢。
解决方案
编辑:
原来罪魁祸首是PulseAudio。把它放在一边pasuspender
按我的预期工作。
我向前迈了一步。我能够获得更稳定的声音输出设置buffer_size
,到周期大小start_threshold
的stop_threshold
3 倍和avail_min
周期大小。我仍然不明白avail_min和start_threshold之间的区别,但这工作得更好,即使是如下所示的小周期大小:
ALSA <-> PulseAudio PCM I/O Plugin
Its setup is:
stream : PLAYBACK
access : RW_INTERLEAVED
format : FLOAT_LE
subformat : STD
channels : 1
rate : 44100
exact rate : 44100 (44100/1)
msbits : 32
buffer_size : 192
period_size : 64
period_time : 1451
tstamp_mode : NONE
tstamp_type : GETTIMEOFDAY
period_step : 1
avail_min : 64
period_event : 0
start_threshold : 192
stop_threshold : 192
silence_threshold: 0
silence_size : 0
boundary : 6917529027641081856
我仍然收到一些 EPIPE 错误,通常只有一个在播放流的开头:
[1525158345182486 / 64] snd_pcm_writei: OK 64
[1525158345182609 / 23] snd_pcm_writei: OK 64
[1525158345182833 / 862] snd_pcm_writei: OK 64
[1525158345183718 / 3] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
[1525158345184915 / 38] snd_pcm_writei: OK 64
[1525158345184962 / 46] snd_pcm_writei: OK 64
[1525158345185018 / 1240] snd_pcm_writei: OK 64
[1525158345186281 / 33] snd_pcm_writei: OK 64
但是我仍然有实时调度的问题。如果我为我的线程设置实时调度,有时我会得到一个没有 EPIPE 错误的干净输出,但大多数时候我会得到一个无休止的错误序列,我只听到噪音:
[1525158709952740 / 30] snd_pcm_writei: OK 64
[1525158709952781 / 14] snd_pcm_writei: OK 64
[1525158709952809 / 2163] snd_pcm_writei: OK 64
[1525158709954994 / 4] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
[1525158709956346 / 29] snd_pcm_writei: OK 64
[1525158709956385 / 15] snd_pcm_writei: OK 64
[1525158709956405 / 2250] snd_pcm_writei: OK 64
[1525158709958673 / 3] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
[1525158709959930 / 31] snd_pcm_writei: OK 64
[1525158709959971 / 15] snd_pcm_writei: OK 64
[1525158709959998 / 2334] snd_pcm_writei: OK 64
[1525158709962355 / 3] snd_pcm_writei: ERR -32 [Broken pipe]
snd_pcm_prepare: 0
使用更大的周期大小,我有更多机会获得干净的音频流,但仍然不是 100% 可靠。
有什么提示吗?
谢谢。
推荐阅读
- typescript - 扩展 TypeScript 接口后将可选属性转换为必需属性
- c - STRTOK 像另一列一样获取中间名
- maven - 有没有一种简单的方法来使用 common.properties 文件中的变量,就像我从常量(Constants.Password)中读取的一样
- windows - Vagrant Up 错误:VBoxManage.exe:错误:VM 会话在任何尝试打开电源之前已关闭
- sql - 将字符串拆分为多列TSQL
- kubernetes-helm - Helm cli - 可以搜索但无法获取 - 401 错误
- sql - 使用 case when 命令时如何从日期中提取年份
- twig - 通过核心扩展更改 number_format 过滤器的默认值
- sql - SQL Server:如何将行转换为列
- r - R中的四分位变异系数