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