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.
- How can I debug this issue?
- Any ideas as to why this is happening?
- 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...