ALSA: невозможно восстановить из недопустимого состояния, подготовка не удалась: сломанная труба

Я пишу программу, которая читает с двух моно устройств ALSA и записывает их на одно стерео устройство ALSA.

Я использую три потока и буфер для пинг-понга, чтобы управлять ими. Две темы для чтения и одна для записи. Их конфигурации следующие:

// Capture ALSA device
alsaBufferSize = 16384;
alsaCaptureChunkSize = 4096;
bitsPerSample = 16;
samplingFrequency = 24000;
numOfChannels = 1;
block = true;
accessType = SND_PCM_ACCESS_RW_INTERLEAVED;

// Playback device (only list params that are different from above)
alsaBufferSize = 16384 * 2;
numOfChannels = 2;
accessType = SND_PCM_ACCESS_RW_NON_INTERLEAVED;

Два читающих потока будут записывать ping-буфер и затем pong-буфер. Пишущий поток ожидал готовности любого из двух буферов, блокировал его, считывал из него, а затем разблокировал его.

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

ALSA lib pcm.c:7316:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7319:(snd_pcm_recover) cannot recovery from underrun, prepare failed: Broken pipe

Ниже приведен мой код для записи на устройство воспроизведения ALSA:

bool CALSAWriter::writen(uint8_t**  a_pOutputBuffer, uint32_t a_rFrames)
{

bool ret = false;

// 1. write audio chunk from ALSA
const snd_pcm_sframes_t alsaCaptureChunkSize = static_cast<snd_pcm_sframes_t>(a_rFrames); //(m_pALSACfg->alsaCaptureChunkSize);

const snd_pcm_sframes_t writenFrames = snd_pcm_writen(m_pALSAHandle, (void**)a_pOutputBuffer, alsaCaptureChunkSize);

if (0 < writenFrames)
{// write succeeded

ret = true;

}
else
{// write failed
logPrint("CALSAWriter WRITE FAILED for  writen farmes = %d ", writenFrames);
ret = false;
const int alsaReadError = static_cast<int>(writenFrames);// alsa error is of int type

if (ALSA_OK == snd_pcm_recover(m_pALSAHandle, alsaReadError, 0))
{// recovery succeeded
a_rFrames = 0;// only recovery was done, no write at all was done
}
else
{
logPrint("CALSAWriter: failed to recover from ALSA write error: %s (%i)", snd_strerror(alsaReadError), alsaReadError);
ret = false;
}
}

// 2. check current buffer load
snd_pcm_sframes_t framesInBuffer = 0;
snd_pcm_sframes_t delayedFrames = 0;

snd_pcm_avail_delay(m_pALSAHandle, &framesInBuffer, &delayedFrames);

// round to nearest int, cast is safe, buffer size is no bigger than uint32_t
const int32_t ONE_HUNDRED_PERCENTS = 100;
const uint32_t bufferLoadInPercents = ONE_HUNDRED_PERCENTS *
static_cast<int32_t>(framesInBuffer) / static_cast<int32_t>(m_pALSACfg->alsaBufferSize);

logPrint("write: ALSA buffer percentage: %u, delayed frames: %d",  bufferLoadInPercents, delayedFrames);

return ret;
}

Другая диагностическая информация:

02:53:00.465047  log info V 1 [write: ALSA buffer percentage: 75, delayed frames: 4096]
02:53:00.635758  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4160]
02:53:00.805714  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4152]
02:53:00.976781  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4144]
02:53:01.147948  log info V 1 [write: ALSA buffer percentage: 0, delayed frames: 0]
02:53:01.317113  log error V 1 [CALSAWriter WRITE FAILED for  writen farmes = -32 ]
02:53:01.317795  log error V 1 [CALSAWriter: failed to recover from ALSA write error: Broken pipe (-32)]

2

Решение

У меня ушло около 3 дней, чтобы найти решение. Спасибо за @CL. Подсказки «написано называется слишком поздно».

Выпуск:

  • Время переключения потока не является постоянным.

Решение:

  • Вставьте пустой буфер, прежде чем вызывать «writen» в первый раз. Продолжительность этого буфера может быть любым значением, чтобы избежать многопоточного переключения. Я установил его на 150 мс.
  • Или вы можете установить высокий приоритет потока, а я не могу этого сделать. Ссылаться на ALSA: способы предотвращения опустошения для динамика.

Диагностика проблем:

Дело в том:

  • «readi» возвращает каждые 171 мс (4096/24000 = 0,171). Чтение потока устанавливает буфер как готовый.
  • Как только буфер готов, «writeen» вызывается в потоке записи. Буфер копируется на устройство воспроизведения ALSA. И для воспроизведения этой части буфера потребуется устройство воспроизведения 171 мс.
  • Если устройство воспроизведения завершило воспроизведение всего буфера, и новый буфер не записывается. Произошло «Подкат».

Реальный сценарий здесь:

  • В 0ms, «readi» начинается. В 171ms «Готово» заканчивается.
  • В 172ms, (1 мс для переключения потоков), «writeen» запускается. В 343ms, «опустошение» должно произойти, если не записан новый буфер.
  • В 171ms, «readi» начинается снова. В 342ms «Готово» заканчивается.
  • В это время переключение потоков занимает 2 мс. Прежде чем «написано» начинается в 344ms, «опустошение» произошло в 343ms

Когда загрузка процессора высока, это не гарантирует, сколько времени займет «переключение потоков». Вот почему вы можете вставить пустой буфер при первой записи. И превратить сценарий в:

  • В 0ms, «readi» начинается. В 171ms «Готово» заканчивается.
  • В 172ms, (1 мс для переключения потоков), «writeen» начинается с буфера длиной 150 мс. В 493ms, «опустошение» должно произойти, если не записан новый буфер.
  • В 171ms, «readi» начинается снова. В 342ms «Готово» заканчивается.
  • В это время переключение потоков занимает 50мс. «Написано» начинается с 392ms, «опустошение» вообще не произойдет.
3

Другие решения


По вопросам рекламы [email protected]