ALSA: правильный способ воспроизведения аудио - режим блокировки и потоков - PullRequest
0 голосов
/ 30 апреля 2018

Я не могу найти правильный способ написания своего цикла для воспроизведения звука и / или я неправильно понимаю значение параметров start_threshold, stop_threshold и util_min для Alsa. Я хотел бы достичь минимальной задержки и знать правильные параметры, которые нужно изменить, чтобы моя цепочка захвата-воспроизведения-воспроизведения была устойчивой к изменениям времени обработки звука. В этом примере я читаю сэмплы из файла, чтобы сосредоточиться на части воспроизведения.

Это моя конфигурация устройства вывода, открытая в режиме блокировки (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 и слышу только шум. Это действительно странно для меня.

Где моя ошибка?

Спасибо.

Ответы [ 2 ]

0 голосов
/ 01 мая 2018

EDIT:

Оказывается, виновником является PulseAudio. Отложив это в сторону через pasuspender, все работает, как я ожидаю.

Я сделал шаг вперед. Я могу получить более стабильную настройку вывода звука buffer_size, start_threshold, stop_threshold в 3 раза больше размера периода и avail_min в качестве размера периода. Я до сих пор не понимаю разницу между util_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% надежно.

Есть подсказки?

Спасибо.

0 голосов
/ 30 апреля 2018

В начале буфер пуст, поэтому вы должны заполнить его как можно быстрее, не дожидаясь.

Чтобы уменьшить задержку, уменьшите размер буфера.
Чтобы уменьшить риск опустошения, увеличьте размер буфера.
Вы не можете сделать оба одновременно. Вы должны выбрать размер буфера, который уравновешивает эти две цели для вашего конкретного случая.

Порог запуска указывает, что устройство автоматически начинает воспроизведение, когда в буфере находится столько кадров. Вы должны установить это в размер буфера.

Порог остановки указывает, что устройство останавливается, когда количество доступных (свободных) кадров достигает этого значения. Вы должны оставить его по умолчанию, размер буфера.

Параметр vend_min указывает, сколько кадров должно быть доступно, прежде чем прерывание приведет к фактическому пробуждению вашего приложения.

...