ALSA: cannot recovery from underrun, prepare failed: Broken pipe
Asked Answered
H

1

6

I'm writing a program that reads from two mono ALSA devices and writes them to one stereo ALSA device.

I use three threads and ping-pong buffer to manage them. Two reading threads and one writing threads. Their configurations are as follows:

// 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;

Two reading threads would write ping buffer and then pong buffer. The writing thread would wait for any of two buffer ready, lock it, read from it, and then unlock it.

But when I run this program, xrun appears and can't be recovered.

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

Below is my code for writing to ALSA playback device:

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;
}

Other diagnostic info:

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)]
Hollie answered 24/10, 2014 at 9:25 Comment(1)
The problem is elsewhere in your program; writen is called too late.Ontario
H
6

It took me about 3 days to find solution. Thanks for @CL. tips of "writen is called too late".

Issue:

  • Thread switching time is not constant.

Solution:

  • Insert an empty buffer before you invoke "writen" at the first time. The time length of this buffer could be any value to avoid multi-thread switching. I set it to 150ms.
  • Or you can set thread priority to high while I can't do this. Refer to ALSA: Ways to prevent underrun for speaker.

Problem diagnostic:

The fact is:

  • "readi" return every 171ms (4096/24000 = 0.171). Reading thread set buffer as ready.
  • Once buffer is ready, "writen" is invoked in writing thread. The buffer is copied to ALSA playback device. And it'll take playback device 171ms to play this part of buffer.
  • If playback device has finished playing all the buffer, and no new buffer is written. "Underrun" occurred.

The real scenario here:

  • At 0ms, "readi" starts. At 171ms "readi" finishes.
  • At 172ms, (1ms for thread switching), "writen" starts. At 343ms, "underrun" shall happen, if no new buffer written.
  • At 171ms, "readi" starts again. At 342ms "readi" finishes.
  • At this time, thread switching takes 2ms. Before "writen" starts at 344ms, "underrun" occurred at 343ms

When CPU load is high, it's not guarantee how long "thread switching" shall take. That's why you can insert an empty buffer at first write. And turn scenario into:

  • At 0ms, "readi" starts. At 171ms "readi" finishes.
  • At 172ms, (1ms for thread switching), "writen" starts with an 150ms-long buffer. At 493ms, "underrun" shall happen, if no new buffer written.
  • At 171ms, "readi" starts again. At 342ms "readi" finishes.
  • At this time, thread switching takes 50ms. "writen" starts at 392ms, "underrun" won't occur at all.
Hollie answered 6/11, 2014 at 2:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.