On Thu, 2006-03-30 at 13:09 +0200, Damien Sandras wrote: > Hi, > > I'm interested in a patch, then I can see with Craig if we can submit it > to CVS. > I have attached the patches for of the alsa driver/plugin for pwlib. I have done a little cleanup (hopefully without breaking anything), but the implementation is a bit of a hack, and will have to be re-written in an object oriented style. It is just to show that the choppy audio I experienced can be fixed with an application level hack, and also as a bit of an educational exercise for myself. Regards, -- Craig Shelley EMail: craig microtron org uk Jabber: shell jabber earth li
--- sound_alsa.cxx.orig 2006-03-30 13:38:42.514035250 +0100 +++ sound_alsa.cxx 2006-03-30 15:12:15.464822750 +0100 @@ -160,6 +160,7 @@ card_nr = 0; os_handle = NULL; + d.buffptra = d.bufffill = d.state = 0; } @@ -271,6 +272,90 @@ return devicenames[0]; } +void *buffer_fill(void *X) +{ + struct buffer_handler* d = (struct buffer_handler *) X; + long r=0; + int buf_i, max_try=0; + + //Check os_handle is valid + if (d->os_handle <= 0) { + PTRACE (1, "ALSA\tBUFFER THREAD: Exiting (bad os handle)"); + return NULL; + } + + //Set the schedule priority of this thread + pthread_t thread_id = pthread_self(); + struct sched_param param; + int policy; + policy = SCHED_FIFO; + param.sched_priority=99; + pthread_setschedparam(thread_id, policy, ¶m); + pthread_getschedparam(thread_id, &policy, ¶m); + if (policy != SCHED_FIFO || param.sched_priority != 99) + cerr << "WARNING: Unable to set realtime thread priority, this may cause audio dropouts.\n"; + + //Wait for the go signal + PTRACE(1, "ALSA\tBUFFER THREAD: started, waiting for go signal"); + while (d->state == 0) usleep(10000); + PTRACE(1, "ALSA\tBUFFER THREAD: go signal received, Running!"); + + do { + //If there are one or more periods free in the buffer + if (d->bufferSize - d->bufffill > d->storedSize) { + //Do we need to lock here? + pthread_mutex_lock(&d->a_mutex); + buf_i = d->buffptra + d->bufffill; + pthread_mutex_unlock(&d->a_mutex); + + //Wrap the pointer + if (buf_i >= (d->bufferSize)) + buf_i -= d->bufferSize; + + //if (i++ > 20) {i=0; PTRACE (1, "ALSA\tBUFFER THREAD: buffer=" << d->bufffill * 100 /d->bufferSize << "%");} + + //Read the data + r = snd_pcm_readi (d->os_handle, (char *) &(d->buffer)[buf_i], d->storedSize / d->frameBytes); + + if (r == d->storedSize / d->frameBytes) { //Success? + //Update the pointers + pthread_mutex_lock(&d->a_mutex); + d->bufffill += r * d->frameBytes; + pthread_mutex_unlock(&d->a_mutex); + max_try = 0; + } + else if (r >= 0) + PTRACE (1, "ALSA\tIncomplete read r=" << r << " buffer=" << d->bufffill * 100 /d->bufferSize << "%"); + else { + if (r == -EPIPE) { /* under-run */ + PTRACE (1, "ALSA\tBUFFER THREAD: Hardware XRUN (" << r << ") buffer=" << d->bufffill * 100 /d->bufferSize << "%"); + snd_pcm_prepare (d->os_handle); + } + else if (r == -ESTRPIPE) { + PTRACE (1, "ALSA\tBUFFER THREAD: Hardware Suspended (" << r << ") buffer=" << d->bufffill * 100 /d->bufferSize << "%"); + while ((r = snd_pcm_resume (d->os_handle)) == -EAGAIN) + sleep(1); /* wait until the suspend flag is released */ + if (r < 0) + snd_pcm_prepare (d->os_handle); + } + else + PTRACE (1, "ALSA\tBUFFER THREAD: Could not read unknown error code: (" << r << ") buffer=" << d->bufffill * 100 /d->bufferSize << "%"); + max_try++; + } + } + else { + PTRACE (1, "ALSA\tBUFFER THREAD: Software buffer full, this may cause a hardware XRUN"); + //Sleep for a hundredth of a period + usleep(1000000 * d->storedSize / d->frameBytes / d->mSampleRate / 100); + } + } + while (max_try < 5 && d->state == 1); + + PTRACE(1, "ALSA\tBUFFER THREAD: finished, max_try=" <<max_try << " state=" << d->state << " buffer=" << d->bufffill * 100 /d->bufferSize << "%"); + + pthread_exit((void *) 0); + return NULL; +} BOOL PSoundChannelALSA::Open (const PString & _device, Directions _dir, @@ -338,6 +423,21 @@ PTRACE (1, "ALSA\tDevice " << real_device_name << " Opened"); + if (_dir == Recorder) { + int ret; + pthread_attr_t attr; + + d.os_handle = os_handle; + d.frameBytes = 12345; + d.storedSize = 54321; + + pthread_mutex_init(&d.a_mutex, NULL); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + ret = pthread_create(&(d.buffer_fill_thread),&attr,buffer_fill,&d); // create threads + PTRACE (1, "ALSA\tDevice ret= " << ret); + } + buffer_clear=FALSE; return TRUE; } @@ -418,6 +518,13 @@ cerr << msg << endl; } + //Recalculate the stored size based on what alsa had to offer + storedSize = period_size * (frameBytes ? frameBytes : 2); + + //Try to get a bigger buffer (this only seems to work for playback) + //FIXME this is a hack, it should be done in opal, or somewhere else + storedPeriods = 15; + if ((err = (int) snd_pcm_hw_params_set_periods_near (os_handle, hw_params, (unsigned int *) &storedPeriods, 0)) < 0) { msg << "Cannot set periods to " << snd_strerror (err); PTRACE (1, "ALSA\t" << msg); @@ -434,8 +541,20 @@ unsigned int buffer_time = period_time * storedPeriods; PTRACE(3, "Alsa\tBuffer time is " << buffer_time); PTRACE(3, "Alsa\tPeriod time is " << period_time); + PTRACE(3, "Alsa\tSample Rate is " << mSampleRate); + PTRACE(3, "ALSA\tframeBytes= " << frameBytes << " Bytes per frame"); + PTRACE(3, "ALSA\tstoredPeriods= " << storedPeriods << " Periods"); + PTRACE(3, "ALSA\tstoredSize= " << storedSize << " Bytes per period"); + PTRACE(3, "ALSA\tperiod_size= " << period_size << " Frames"); + PTRACE(3, "ALSA\tBUFFERSIZE= " << BUFFSIZE << " Bytes, bufferSize=" << d.bufferSize); + + d.frameBytes=frameBytes; + d.storedSize = storedSize; + d.bufferSize = (BUFFSIZE / storedSize) * storedSize; + d.mSampleRate = mSampleRate; + /* Is this completely redundant? The buffers have already been configured above? // Ignore errors here if ((err = snd_pcm_hw_params_set_buffer_time_near (os_handle, hw_params, &buffer_time, NULL)) < 0) { msg << "Cannot set buffer_time to " << (buffer_time / 1000) << " ms " << snd_strerror (err); @@ -448,6 +567,7 @@ PTRACE (1, "ALSA\t" << msg); cerr << msg << endl; } + */ if ((err = snd_pcm_hw_params (os_handle, hw_params)) < 0) { msg << "Cannot set parameters " << snd_strerror (err); @@ -470,6 +590,13 @@ if (!os_handle) return FALSE; + if (d.state > 0) { + d.state=2; + pthread_join(d.buffer_fill_thread, NULL); + d.state=0; + } + snd_pcm_drain (os_handle); + snd_pcm_close (os_handle); os_handle = NULL; @@ -491,6 +618,19 @@ if (!isInitialised && !Setup(len) || !len || !os_handle) return FALSE; + //Pre-fill the alsa buffers with silence to prevent underrun when the call first starts + if (!buffer_clear) { + char *silence; + silence = (char *) malloc(storedSize * storedPeriods); + if (silence > 0) { + memset ((char *) silence, 0, storedSize * storedPeriods); + r = snd_pcm_writei (os_handle, silence, storedSize * storedPeriods/frameBytes); + free(silence); + + } + buffer_clear = TRUE; + } + do { @@ -528,55 +668,57 @@ BOOL PSoundChannelALSA::Read (void * buf, PINDEX len) { - long r = 0; - char *buf2 = (char *) buf; - int pos = 0, max_try = 0; + int in_i, out_i, max_try=0; lastReadCount = 0; - PWaitAndSignal m(device_mutex); if (!isInitialised && !Setup(len) || !len || !os_handle) return FALSE; - memset ((char *) buf, 0, len); - - do { - - /* the number of frames to read is the buffer length - divided by the size of one frame */ - r = snd_pcm_readi (os_handle, (char *) &buf2 [pos], len / frameBytes); - if (r > 0) { - pos += r * frameBytes; - len -= r * frameBytes; - lastReadCount += r * frameBytes; - } - else { - if (r == -EPIPE) { /* under-run */ - snd_pcm_prepare (os_handle); - } - else if (r == -ESTRPIPE) { - while ((r = snd_pcm_resume (os_handle)) == -EAGAIN) - sleep(1); /* wait until the suspend flag is released */ - if (r < 0) - snd_pcm_prepare (os_handle); - } - - PTRACE (1, "ALSA\tCould not read"); - max_try++; + if (d.state == 0) { + PTRACE (1, "ALSA\tSending go signal"); + d.state = 1; + } + + //Wait until the buffer has enough data + while (d.bufffill < len && max_try < 10) { + //Wait for 1/5 of the length period + usleep(200000 * len / frameBytes / mSampleRate); + max_try++; + } + + out_i=0; + //Is there enough data in the buffer? + if (d.bufffill >= len) { + pthread_mutex_lock(&d.a_mutex); + in_i = d.buffptra; + pthread_mutex_unlock(&d.a_mutex); + + //Copy the data from the buffer FIXME use memcpy + while (out_i<len) { + if (in_i >= d.bufferSize) + in_i -= d.bufferSize; + + buf2[out_i] = d.buffer[in_i]; + in_i++; + out_i++; } - } while (len > 0 && max_try < 5); - - - if (len != 0) { - memset ((char *) &buf2 [pos], 0, len); - lastReadCount += len; - - PTRACE (1, "ALSA\tRead Error, filling with zeros"); + //Update the buffer pointer variables + pthread_mutex_lock(&d.a_mutex); + d.bufffill -= len; + d.buffptra += len; + if (d.buffptra >= d.bufferSize) + d.buffptra -= d.bufferSize; + pthread_mutex_unlock(&d.a_mutex); } - - + else { + memset ((char *) buf2, 0, len); + PTRACE (1, "ALSA\tBuffer Underrun, filling with zeros"); + } + + lastReadCount = len; return TRUE; }
--- sound_alsa.h.orig 2006-03-30 13:38:52.094634000 +0100 +++ sound_alsa.h 2006-03-30 15:02:26.300002250 +0100 @@ -42,6 +42,23 @@ #define LOOPBACK_BUFFER_SIZE 5000 #define BYTESINBUF ((startptr<endptr)?(endptr-startptr):(LOOPBACK_BUFFER_SIZE+endptr-startptr)) +#define BUFFSIZE 2560 +struct buffer_handler +{ + snd_pcm_t *os_handle; /* Handle, different from the PChannel handle */ + /** Number of bytes in a ALSA frame. a frame may only be 4ms long*/ + volatile int frameBytes; + volatile int storedSize; + volatile int bufferSize; + volatile int mSampleRate; + volatile int bufffill; + volatile int buffptra; + volatile char buffer[BUFFSIZE]; + volatile int state; + pthread_t buffer_fill_thread; + pthread_mutex_t a_mutex; +}; + class PSoundChannelALSA: public PSoundChannel { public: @@ -90,6 +107,8 @@ private: + struct buffer_handler d; + BOOL buffer_clear; static void UpdateDictionary(PSoundChannel::Directions); BOOL Volume (BOOL, unsigned, unsigned &); PSoundChannel::Directions direction;
Attachment:
signature.asc
Description: This is a digitally signed message part