Re: [GnomeMeeting-devel-list] Choppy Audio / Audio Dropout



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, &param);
+  pthread_getschedparam(thread_id, &policy, &param);
+  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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]