【问题标题】:ALSA: proper way to playback audio - blocking mode and threadsALSA:播放音频的正确方法 - 阻塞模式和线程
【发布时间】:2018-10-10 17:54:44
【问题描述】:

我无法正确编写循环来播放音频和/或我不正确理解 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 列表,我只听到噪音。这对我来说真的很奇怪。

我的错在哪里?

谢谢。

【问题讨论】:

    标签: linux alsa


    【解决方案1】:

    编辑:

    原来罪魁祸首是 PulseAudio。通过pasuspender 把它放在一边,一切都按我的预期工作。

    我向前迈了一步。我能够获得更稳定的声音输出设置buffer_sizestart_thresholdstop_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% 可靠。

    有什么提示吗?

    谢谢。

    【讨论】:

      【解决方案2】:

      一开始缓冲区是空的,所以你应该尽快填满它,不要等待。

      要减少延迟,请减小缓冲区大小。
      要降低欠载的风险,请增加缓冲区大小。
      你不能同时做这两件事。您必须为您的特定情况选择平衡这两个目标的缓冲区大小。

      开始阈值指定设备在缓冲区中有那么多帧时自动开始播放。您应该将其设置为缓冲区大小。

      停止阈值指定设备在可用(空闲)帧数达到此值时停止。您应该将其保留为默认值,即缓冲区大小。

      avail_min 参数指定在中断导致您的应用程序实际被唤醒之前必须有多少帧可用。

      【讨论】:

      • 我明白这一点。但是我的第二个 snd_pcm_writei() 在第一个(成功)之后 2.5 毫秒出现,而周期时间为 46 毫秒。我认为它已经足够快了。为什么会报错?
      • 因为你设置的停止阈值太低了。
      • 好的。我开始明白了。谢谢。但是,如果我推送太多数据或等到我可以确定下一个 snd_pcm_writei() 会成功,我怎么能阻止 snd_pcm_writei() 呢? start_threshold 和avail_min 参数有什么区别?
      猜你喜欢
      • 2012-01-07
      • 2013-05-06
      • 1970-01-01
      • 2020-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-09
      • 1970-01-01
      相关资源
      最近更新 更多