mardi 4 août 2015

ALSA Linux C API Producer Consumer Audio Dies and XRun is Unrecoverable

I have a Qt 5.4 Application that runs on Raspberry Pi ( Raspbian Linux ). The main thread receives TCP packets whenever a remote sender decides and stops receiving them after random amounts of time. When it receives audio data, the data is pushed into a global, QSemaphore protected audio buffer. The main thread spawns a separate QThread that consumes data out of the buffer, when available, and plays it through ALSA. The problem is that the audio data begins playing ( and sounding ) fine but after roughly 11 packets received, the initial call to "snd_pcm_sframes_t available = snd_pcm_avail(m_PlaybackHandle);" returns a negative value and is never recoverable. I've tried various combinations of the following to recover the stream:

  • snd_pcm_drop(handle);
  • snd_pcm_drain(handle);
  • snd_pcm_prepare(handle);
  • static int xrun_recovery(snd_pcm_t *handle, int err);

I think I am hitting some kind of underrun/overrun issue but I am not sure who to fix and/or debug it. I do not reset the Audio Handle so I'd like to understand why it is randomly stopping playback and becomes unrecoverable.

  1. How can I debug this issue?
  2. Any ideas as to why this is happening?
  3. I really appreciate any help! Thanks.

Here is the producer:

// If Audio data - write to ALSA
if ( pck->evt_endpoint_data.endpoint == m_AudioEndpoint ) {

#ifdef BLUEGIGA_DEBUG
    qDebug() << "BlueGigaUart: Received Audio Data..." << pck->evt_endpoint_data.data.len;
#endif

    // Latch the endpoint
    m_CurrentEndpoint = m_AudioEndpoint;

    // Increment number received
    m_NumberTCPPacketsReceived++;

    // Create byte array to transfer including Audio Data
    QByteArray audio = QByteArray::fromRawData((const char*)recv_buffer,pck->evt_endpoint_data.data.len);

    // Push data into global audio buffer
    char readByte;
    for (int i = 0; i < audio.size(); ++i) {
        audioBufferIndex++;
        int subscript = audioBufferIndex % MAX_BUFFER_SIZE;
        readByte = audio.at(i);
        freeBytes.acquire();
        singleAudioBuffer[subscript] = readByte;
        usedBytes.release();
    }

    // Turn on the run bit
    m_AudioController->startPlayingAudio();

}

Here is my Thread run() function that consumes:

/**
 * Consume and play Audio data in a background thread
 */
void AudioController::run() {

    // Constantly consume and write audio data to ALSA from global buffer
    // in this background thread
    int counter = 0;
    int multiplier = 5;

    // Check if ALSA is configured - and if not then configure it
    if ( !m_AlsaConfigured ) {
        configureAlsa();
    }

    /** This Audio Buffer buffers enough data so that the Handset can hicup
     * and we will still have enough data to play to ALSA through it all
     */
    QByteArray *globalAudioBuffer = new QByteArray();
    globalAudioBuffer->clear();

    // Do this forever
    forever {

        usedBytes.acquire();
        char data = singleAudioBuffer[counter % MAX_BUFFER_SIZE];
        QByteArray audio = QByteArray::fromRawData((const char *)&data,1);
        globalAudioBuffer->append(audio);
        freeBytes.release();
        counter++;

        // Do we have enough buffers' worth?
        if ( globalAudioBuffer->length() >= ( EXPECTED_AUDIO_SIZE * multiplier ) ) {

            // Check if ALSA is configured - and if not then configure it
            if ( !m_AlsaConfigured ) {
#ifdef true
                qDebug() << "AudioController: Configuring ALSA...";
#endif
                configureAlsa();
            }

            // Push data into global audio buffer
            int bufferSize = 2400000;
            qint16 audioData[bufferSize];
            memset(audioData,0x00,bufferSize); // Zero it out
            int i = -1;
            while ( i++ < globalAudioBuffer->size() && (i++ < bufferSize) ) {
                if ( i >= globalAudioBuffer->size() ) {
                    break;
                }
                quint8 readByte = globalAudioBuffer->at(i);
                qint16 finalByte = readByte - 127;
                finalByte = finalByte * 258;
                audioData[i] = finalByte;
            }

            if ( m_ShouldPlayAudio && !m_NonTcpAudioPlaying ) {

                // Write the data to the ALSA device
                int error;
                fprintf(stderr,"snd_pcm_avail %d \n",snd_pcm_avail(m_PlaybackHandle));
                snd_pcm_sframes_t available = snd_pcm_avail(m_PlaybackHandle);
                fprintf(stderr,snd_strerror(available));
                if ( available < 0  ) {
                    /*
                     *  WHAT THE HECK THIS LOCKS UP?! After 11 packets of 239 bytes I get a negative available value
                     */
                    //snd_pcm_drop(m_PlaybackHandle);
                    //snd_pcm_prepare(m_PlaybackHandle);
                    //globalAudioBuffer->clear();
                    //continue;
                }
                //snd_pcm_drop(m_PlaybackHandle);
                //snd_pcm_prepare(m_PlaybackHandle);
                if ((error = snd_pcm_writei (m_PlaybackHandle, globalAudioBuffer->data(), globalAudioBuffer->size() < available ? globalAudioBuffer->size() : available)) != (globalAudioBuffer->size() < available ? globalAudioBuffer->size() : available)) {

                    if ((error != -EAGAIN) && (error < 0)) {

                        fprintf(stderr, "ALSA Play Error: %s\n", snd_strerror(error));

                        // Recover from underrun
                        if (xrun_recovery(m_PlaybackHandle, error) < 0) {

                            // No longer configured
                            m_AlsaConfigured = false;

                            // The handle becomes NULL
                            m_PlaybackHandle = NULL;

                            // Reset ALSA
                            configureAlsa();

                        }

                    }
                }

            }

            qDebug() << "Clearing buffer...";
            globalAudioBuffer->clear();

        }

    }

}

Here is the xrun recovery function:

/**
* ALSA Underrun and suspend recovery
*/
static int xrun_recovery(snd_pcm_t *handle, int err)
{

#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Recovering from Audio Xrun ( Overrun / Underrun )...";
#endif

    if (err == -EPIPE) { /* under-run */
        err = snd_pcm_prepare(handle);
        if (err < 0)

#ifdef AUDIO_DEBUG
            qDebug() << "Cannot recover from underrun - prepare failed!";
#endif
            printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));

            // Resetting ALSA now

#ifdef AUDIO_DEBUG
            qDebug() << "Cannot recover from underrun - Resetting ALSA Now!";
#endif

            // Cleanup and Close ALSA
            snd_pcm_drain(handle);
            snd_pcm_close(handle);
            handle = NULL;
            return -1;

        } else if (err == -ESTRPIPE) {
            while ((err = snd_pcm_resume(handle)) == -EAGAIN)
                sleep(1); /* wait until the suspend flag is released */
            if (err < 0) {
                err = snd_pcm_prepare(handle);
            if (err < 0)

#ifdef AUDIO_DEBUG
                qDebug() << "Cannot recover from suspend - prepare failed!";
#endif

                printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));

#ifdef AUDIO_DEBUG
                qDebug() << "Cannot recover from suspend - Resetting ALSA Now!";
#endif

                // Cleanup and Close ALSA
                snd_pcm_drain(handle);
                snd_pcm_close(handle);
                handle = NULL;
                return -1;

        }
        return -1;
    }
    return err;

}

Variables declared for this code:

#include <QSemaphore>

#define PRODUCER_DATA_SIZE 1000000000
#define MAX_BUFFER_SIZE 2048

extern long long int audioBufferIndex;
extern char singleAudioBuffer[];
extern QSemaphore freeBytes;
extern QSemaphore usedBytes;

ALSA setup code:

/**
 * Configure ALSA
 */
void AudioController::configureAlsa() {

#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Configuring ALSA...";
#endif

    // Error handling
    int err;

    // Device to Write to
    const char *snd_device_out = "default";

    if ((err = snd_pcm_open (&m_PlaybackHandle, snd_device_out, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        fprintf (stderr, "Cannot open audio device %s (%s)\n",
                 snd_device_out,
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot open Audio Device...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    if ((err = snd_pcm_hw_params_malloc (&m_HwParams)) < 0) {
        fprintf (stderr, "Cannot allocate hardware parameter structure (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot open allocate hardware parameter structure...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    if ((err = snd_pcm_hw_params_any (m_PlaybackHandle, m_HwParams)) < 0) {
        fprintf (stderr, "Cannot initialize hardware parameter structure (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot initialize hardware parameter structure...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    if ((err = snd_pcm_hw_params_set_access (m_PlaybackHandle, m_HwParams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        fprintf (stderr, "Cannot set access type (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot set access type...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    //if ((err = snd_pcm_hw_params_set_format (m_PlaybackHandle, m_HwParams, SND_PCM_FORMAT_U8)) < 0) { // Unsigned 8 bit
    if ((err = snd_pcm_hw_params_set_format (m_PlaybackHandle, m_HwParams, SND_PCM_FORMAT_S16)) < 0) { // Signed 16 bit
        fprintf (stderr, "Cannot set sample format (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot set sample formate...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    uint sample_rate = 8000;
    if ((err = snd_pcm_hw_params_set_rate (m_PlaybackHandle, m_HwParams, sample_rate, 0)) < 0) { // 8 KHz
        fprintf (stderr, "Cannot set sample rate (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot set sample rate...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    if ((err = snd_pcm_hw_params_set_channels (m_PlaybackHandle, m_HwParams, 1)) < 0) { // 1 Channel Mono
        fprintf (stderr, "Cannot set channel count (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot set channel count...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    if ((err = snd_pcm_hw_params (m_PlaybackHandle, m_HwParams)) < 0) {
        fprintf (stderr, "Cannot set parameters (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot set parameters...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    snd_pcm_hw_params_free (m_HwParams);

    // Flush handle prepare for playback
    snd_pcm_drop(m_PlaybackHandle);

    if ((err = snd_pcm_prepare (m_PlaybackHandle)) < 0) {
        fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
                 snd_strerror (err));
#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Cannot prepare audio interface...";
#endif

        // Reset Modules and try again
        configureAlsa();

    }

    // It is now configured
    m_AlsaConfigured = true;

#ifdef AUDIO_DEBUG
    qDebug() << "AudioController: Done Configuring ALSA...";
#endif


}

UPDATE: I've added a minimal compilable example here: git@github.com:phishstang65/AudioPlayer.git

Output from running this example looks like this:

AudioController: Done Configuring ALSA...
Playing now...
Playing now...
Playing now...
Playing now...
Playing now...
1195 snd_pcm_avail 2097152 
Playing now...
Playing now...
Playing now...
Playing now...
Playing now...
2390 snd_pcm_avail -32 
Error
Broken pipe
Playing now...
Playing now...

Aucun commentaire:

Enregistrer un commentaire