[nas] Patch for libaudiooss

Tobias Diedrich td at informatik.uni-hannover.de
Wed Jul 18 14:26:20 MDT 2001


Hi,

I tried to use libaudiooss with mplayer (www.google.com/search?q=linux+mplayer),
which did not work for two reasons:

1) The GET[OI]SPACE implementation of libaudiooss does not work
2) Before calling write() mplayer checks if the write would block and does
   not call it in that case. Because libaudiooss implements the
   NAS Eventloop in the blocking write() codepath, it is never called.

The solution is to use a separate thread for the Eventloop, which is what
this patch does.
I also emulate a fragmented buffer, because mplayer uses GETOSPACE to
determine how much data to write to the buffer.

Tested and working with mplayer, mpg123 and mp3blaster.

Please try this patch, I would be especially interested to hear wether you
still need the WINE_HACK with this.

On to some questions:

Is it possible to use Buckets for buffering of a continous audio stream ?
For example if I have 8 Buckets and each Bucket has a 
AuMakeStateChangeAction which will start the next Bucket if this one
stops would that give me a output without dropouts ?

My problem is that the LOW_WATERMARK event is just not enough feedback.
It would be good to have an event generated each time a fragment is played,
but to still have a big buffer, so I could use a smaller fragment size
than possible with this patch.


diff -urN audiooss-0.9.13/Imakefile audiooss-new/Imakefile
--- audiooss-0.9.13/Imakefile	Sun Sep 10 00:37:52 2000
+++ audiooss-new/Imakefile	Wed Jul 18 21:58:19 2001
@@ -76,7 +76,7 @@
 
    OBJS = audiooss.o nasaudio.o
 
-REQUIREDLIBS = -L/usr/X11R6/lib -laudio -lX11 -ldl
+REQUIREDLIBS = -L/usr/X11R6/lib -laudio -lX11 -ldl -lpthread
 
 #if ProjectX >= 5
 LibraryObjectRule()
diff -urN audiooss-0.9.13/audiooss.c audiooss-new/audiooss.c
--- audiooss-0.9.13/audiooss.c	Wed Jan 31 01:39:09 2001
+++ audiooss-new/audiooss.c	Wed Jul 18 22:02:25 2001
@@ -42,9 +42,7 @@
 /* You'll probably start breaking programs if you fiddle with the constants
    below. But by all means, have a try! */
 
-#define MAX_FRAGMENTS	4
-#define FRAGMENT_SIZE	4096
-#define BUFFER_SIZE	MAX_FRAGMENTS * FRAGMENT_SIZE
+#define MAX_WRITE 32768
 
 #if DSP_DEBUG == 1
 #define DPRINTF(format, args...)	fprintf(stderr, format, ## args)
@@ -94,10 +92,6 @@
 
 				/* setup some defaults */
 static nasInfo NASInfo = { AuFormatLinearUnsigned8, 8000, 1} ;
-static int fragmentscounter_out = MAX_FRAGMENTS;
-static int fragmentscounter_in = MAX_FRAGMENTS;
-static int written_since_last_call = 0;         /* For SNDCTL_DSP_GETOSPACE */
-static int read_since_last_call = 0;            /* For SNDCTL_DSP_GETISPACE */
 static int selectcounter = 0;                   /* For calling select() with multiple fd sets */
 int wine_hack = 0;                       /* Workaround for Wine - see SNDCTL_DSP_GETOSPACE */
 static int volume = 100;
@@ -172,7 +166,6 @@
 				/* and re-open */
   nas_close();
   
-  nas_init();
   nas_open(info->format, info->rate, info->channels);
 }
 
@@ -191,11 +184,10 @@
 
   DPRINTF("mutex: locking write() pid=%d\n", getpid());
   pthread_mutex_lock(&nasmutex);
-  len = MIN(len,BUFFER_SIZE);
+  len = MIN(len, MAX_WRITE);
 
   nas_write((void *)buf, len);
   DPRINTF("WRITE: called for %d bytes\n", len);
-  written_since_last_call = 1;
 
   DPRINTF("mutex: write() unlocking\n");
   pthread_mutex_unlock(&nasmutex);
@@ -229,8 +221,8 @@
       if (sndfd == MAGIC_FD_NUM) { errno = EACCES; return -1; }
 
       if (mixerfd == -1) {
-        nas_init();
-      	nas_open(NASInfo.format, NASInfo.rate, NASInfo.channels);
+      	if (!nas_open(NASInfo.format, NASInfo.rate, NASInfo.channels))
+		return -1;
       }
 
       sndfd = MAGIC_FD_NUM;
@@ -251,7 +243,6 @@
       /* Hack so that if you open a standalone mixer app, you're likely
          to modify the volume of the device you want. */
       if (sndfd == -1) {
-      	nas_init();
 #ifdef MIXER_HACK
         nas_open(AuFormatLinearSigned16LSB, 44100, 2);
 #else
@@ -414,9 +405,9 @@
       break;
 
     case SNDCTL_DSP_GETBLKSIZE:
-      DPRINTF("DSP: getblksize (exp), returning %d\n", FRAGMENT_SIZE);
+      DPRINTF("DSP: getblksize (exp), returning %d\n", nas_frag_size());
       YELL;
-      *arg = FRAGMENT_SIZE * 2;
+      *arg = nas_frag_size();
       need_to_rebuild = 0;
       break;
 
@@ -451,14 +442,10 @@
 
       info = (struct audio_buf_info *) arg;
 
-      if (written_since_last_call && (fragmentscounter_out > 0)) fragmentscounter_out--;
-      if (!written_since_last_call && (fragmentscounter_out < MAX_FRAGMENTS)) fragmentscounter_out++;
-      written_since_last_call = 0;
-
-      info->fragments = fragmentscounter_out;
+      info->fragments = nas_free_frags();
       if (wine_hack) info->fragments++; /* Quick workaround to get smooth playback in wine */
-      info->fragstotal=MAX_FRAGMENTS;
-      info->fragsize=FRAGMENT_SIZE;
+      info->fragstotal= nas_frag_count();
+      info->fragsize= nas_frag_size();
       info->bytes= (info->fragsize * info->fragments) ;
 
       DPRINTF("fragments = %d, fragstotal = %d, fragsize = %d, bytes = %d\n",
@@ -475,13 +462,9 @@
 
       info = (struct audio_buf_info *) arg;
 
-      if (read_since_last_call && (fragmentscounter_in > 0)) fragmentscounter_in--;
-      if (!read_since_last_call && (fragmentscounter_in < MAX_FRAGMENTS)) fragmentscounter_in++;
-      read_since_last_call = 0;
-
-      info->fragments = fragmentscounter_in ;
-      info->fragstotal=MAX_FRAGMENTS;
-      info->fragsize=FRAGMENT_SIZE;
+      info->fragments = 0;
+      info->fragstotal = 0;
+      info->fragsize = 0;
       info->bytes= (info->fragsize * info->fragments) ;
 
       DPRINTF("fragments = %d, fragstotal = %d, fragsize = %d, bytes = %d\n",
@@ -831,7 +814,7 @@
 	    DPRINTF("audiooss: select() with both read and write part. My turn now.\n");
 	}
     DPRINTF ("audiooss: hijacking /dev/dsp select() [output]\n");
-    return FRAGMENT_SIZE;
+    return nas_frag_size();
   }
   else
   {
@@ -917,6 +900,11 @@
 
   if (!func)
     func = (FILE* (*) (const char *, const char *)) dlsym (REAL_LIBC, "fopen");
+  
+  if (!pathname) {
+      filep = (*func) (pathname, mode);
+      return(filep);
+  }
 
   if ((!strcmp (pathname, "/dev/dsp")) || (!strcmp (pathname, "/dev/dsp0")) || (!strcmp (pathname, "/dev/adsp")) || (!strcmp (pathname, "/dev/adsp0")) || (!strcmp (pathname, "/dev/audio")) || (!strcmp (pathname, "/dev/audio0")))
   {
@@ -926,7 +914,6 @@
 	DPRINTF("%s: NASInfo.format set to AuFormatULAW8\n", pathname);
 	}
 
-      nas_init();
       nas_open(NASInfo.format, NASInfo.rate, NASInfo.channels);
 
       sndfd = MAGIC_FD_NUM;
@@ -1021,8 +1008,6 @@
 
   nas_write((void *)ptr, size * nmemb);
   DPRINTF("FWRITE: called for %d bytes\n", size * nmemb);
-
-  written_since_last_call = 1;
 
   return nmemb;
 }
diff -urN audiooss-0.9.13/nasaudio.c audiooss-new/nasaudio.c
--- audiooss-0.9.13/nasaudio.c	Wed Jan 31 01:41:44 2001
+++ audiooss-new/nasaudio.c	Wed Jul 18 22:00:58 2001
@@ -9,31 +9,31 @@
 |*|  Network Audio System driver by Willem Monsuwe (willem at stack.nl)
 \*/
 
-/* All right. The "buffering problem". The problem is that the buffer must
-   a) be big enough to deal with the largest possible write within
-      (fragment_size * max_fragments) limits.
-   b) the distance top of buffer -> low water must be great enough that
-      LowWater events are actually sent before the buffer is empty, or
-      we _will_ have underruns.
-   c) the distance top of buffer -> low water must be small enough that
-      handling events does not take so much time that it causes underruns.
-
-   At least, this is how it seems to me now. As everything else, this is based on
-   practice, not theory. Anyone want to tell me the whys of this, feel free.
-
-   -- Erik */
+/* The Buffering problem:
+ * FRAGMENT_SIZE must be big enough to prevent buffer underruns
+ * FRAGMENT_SIZE should be small to not delay the sound too much
+ * 16384 bytes seems to be the smallest power of 2 value 
+ * not causing regular buffer underruns on my system
+ * (This is a too big value for latency reasons)
+ * 
+ * Maybe one should try to using buckets for buffering
+ **/
 
 #include <errno.h>
+#include <pthread.h>
 #include <audio/audiolib.h>
 #include "nasaudio.h"
 
+#define FRAG_SIZE 16384
+#define FRAG_COUNT 4
+#define MIN_BUFFER_SIZE FRAG_SIZE * FRAG_COUNT
+#define max(a,b) ((a > b) ? a : b)
+
 static AuServer*  aud = 0;
 static AuFlowID   flow;
 static AuDeviceID dev;
 
-static gint buf_free = -1;
-
-static gint do_pause = 0, paused = 0, written = 0, really = 0;
+static gint do_pause = 0, paused = 0, close = 0;
 static gint bps;
 static struct timeval last_tv;
 static unsigned char format;
@@ -42,27 +42,73 @@
 static unsigned char format;
 static int set_vol, volume = 100;
 
-static gint buffer_size;
+static int frag_size = FRAG_SIZE;
+static void *buffer = NULL;
+static int buffer_size = 0;
+static int buffer_used = 0;
+static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; 
 
-NASConfig nas_cfg;
+pthread_t *event_thread = NULL;
 
-void buffer_reopen(int buf);
+void buffer_resize(int newlen);
 
-void nas_init(void)
+int nas_frag_count()
 {
-#if 0
-	ConfigFile *cfgfile;
-	gchar *filename;
-#endif
+	return buffer_size / frag_size;
+}
 
-	memset(&nas_cfg, 0, sizeof(NASConfig));
+int nas_frag_size()
+{
+	return frag_size;
+}
 
-	nas_cfg.server = strdup("");
-	nas_cfg.bufsize = 1000; /* Don't increase this much - we depend on there
-				   being generated AuNotifyLowWater events
-				   for smooth playback - and don't decrease it further
-				   either. Here Be Dragons. */
+int nas_free_frags()
+{
+	if (buffer_size == 0) buffer_resize(MIN_BUFFER_SIZE);
+	return (buffer_size - buffer_used) / frag_size;
+}
 
+void * event_loop(void *data)
+{
+	while (!close) {
+		AuEvent ev;
+		AuNextEvent(aud, AuTrue, &ev);
+		AuDispatchEvent(aud, &ev);
+	}
+}
+
+void
+writeBuffer(void *data, int len)
+{
+	pthread_mutex_lock(&buffer_mutex);
+	if (len > buffer_size)
+		buffer_resize(len);
+	memcpy(buffer + buffer_used, data, len);
+	buffer_used += len;
+	pthread_mutex_unlock(&buffer_mutex);
+}
+
+/* readBuffer
+ * always writes frag_size bytes of data
+ * if the buffer does not contain that much, it will be padded with zeroes
+ */
+void
+readBuffer(void)
+{
+	gettimeofday(&last_tv, 0);
+	pthread_mutex_lock(&buffer_mutex);
+	if (!buffer) 
+		buffer_resize(MIN_BUFFER_SIZE);
+	if (buffer_used < frag_size) {
+		memset(buffer + buffer_used, 0, frag_size - buffer_used);
+		AuWriteElement(aud, flow, 0, frag_size, buffer, AuFalse, NULL);
+		buffer_used = 0;
+	} else {
+		AuWriteElement(aud, flow, 0, frag_size, buffer, AuFalse, NULL);
+		buffer_used -= frag_size;
+		memmove(buffer, buffer + frag_size, buffer_used);
+	}
+	pthread_mutex_unlock(&buffer_mutex);
 }
 
 void
@@ -70,29 +116,27 @@
 {
 	if (!aud) return;
 	if (paused) return;
-	if (len > buffer_size) buffer_reopen(len); /* Larger than the buffer? Close, enlarge, reopen. */
-	while (len > buf_free) { /* We think the buffer is full? Yikes! Ask the server for events,
-				    in the hope that some of them is LowWater events telling us more
-				    of the buffer is free now than what we think. */
-		AuEvent ev;
-		/* fprintf(stderr, "written = %d, really = %d, len = %d, buf_free = %d\n", written, really, len, buf_free); */
-		AuNextEvent(aud, AuTrue, &ev);
-		AuDispatchEvent(aud, &ev);
+	if (!event_thread) {
+		event_thread = malloc(sizeof(pthread_t));
+		pthread_create(event_thread, NULL, event_loop, NULL);
 	}
-	buf_free -= len;
-	AuWriteElement(aud, flow, 0, len, ptr, AuFalse, NULL);
-	written += len;
+	while (len > (buffer_size - buffer_used)) {
+		usleep(10000);
+	}
+	writeBuffer(ptr, len);
 }
 
 void
 nas_close(void)
 {
 	if (!aud) return;
-	while (really < written) { /* Flush the output buffer before closing */
-		AuEvent ev;
-		/* fprintf(stderr, "written = %d, really = %d, len = %d, buf_free = %d\n", written, really, len, buf_free); */
-		AuNextEvent(aud, AuTrue, &ev);
-		AuDispatchEvent(aud, &ev);
+	while (buffer_used > 0) {
+		usleep(10000);
+	}
+	if (event_thread) {
+		close = 1;
+		pthread_join(*event_thread, NULL);
+		event_thread = NULL;
 	}
 	AuStopFlow(aud, flow, NULL);
 	AuCloseServer(aud);
@@ -122,25 +166,14 @@
 
 		switch (event->kind) {
 		case AuElementNotifyKindLowWater:
-			if (buf_free >= 0) {
-				really += event->num_bytes;
-				gettimeofday(&last_tv, 0);
-				buf_free += event->num_bytes;
-			} else {
-				buf_free = event->num_bytes;
-			}
+			readBuffer();
 			break;
 		case AuElementNotifyKindState:
 			switch (event->cur_state) {
 			case AuStatePause:
 				if (event->reason != AuReasonUser) {
-					if (buf_free >= 0) {
-						really += event->num_bytes;
-						gettimeofday(&last_tv, 0);
-						buf_free += event->num_bytes;
-					} else {
-						buf_free = event->num_bytes;
-					}
+					readBuffer(); // buffer underrun, refill buffer
+					readBuffer();
 				}
 				break;
 			}
@@ -150,6 +183,19 @@
 	return AuTrue;
 }
 
+static AuBool
+error_handler(AuServer* aud, AuErrorEvent* ev)
+{
+	fprintf(stderr,"libaudiooss: error\n"
+		"error_code: %d\n"
+		"request_code: %d\n"
+		"minor_code: %d\n", 
+		ev->error_code,
+		ev->request_code,
+		ev->minor_code);
+	return AuTrue;
+}
+
 int
 nas_getdelay(AFormat fmt, gint rate, gint nch)
 {
@@ -167,55 +213,29 @@
 	temp2 /= 1000;
 	temp += temp2;
 
-	/* really = what NAS has confirmed it has written to the card, 
-	   temp = what we think it has written since */
-
-	retval = (written - (really + temp));
+	/* Two fragments should be in nas' buffer (2*frag_size)
+	 * Add whats in our buffer (buffer_used)
+	 * And substract what is already played (temp)
+	 * This does not take the Soundcard's buffer and
+	 * network delay into account
+	 */
+	retval = buffer_used + 2*frag_size - temp; 
 
-	/* Fudge value - data in nas' buffer, soundcard buffer, network delay...
-           seems to work fine over loopback tcp/ip, at least. */
-	retval += (9 * buffer_size) / 8; 
-
-	if (retval > (written / 4)) retval = (written / 4) - temp;
+//	fprintf(stderr, "%d  \n", temp);
 
 	return (retval);
 }
 
 void
-buffer_reopen(int buf)
+buffer_resize(int newlen)
 {
-	AuElement elms[3];
-	AFormat fmt = format;
-	gint rate = int_rate;
-	gint nch = int_nch;
-
-	buffer_size = buf;
-
-	nas_close();
-
-	aud = AuOpenServer(nas_cfg.server, 0, NULL, 0, NULL, NULL);
-	if (!aud) return;
-	dev = find_device(nch);
-	if ((dev == AuNone) || (!(flow = AuCreateFlow(aud, NULL)))) {
-		AuCloseServer(aud);
-		aud = 0;
-		return;
-	}
-
-	AuMakeElementImportClient(elms, rate, format, nch, AuTrue,
-				buffer_size, buffer_size / 4, 0, NULL);
-	AuMakeElementExportDevice(elms+1, 0, dev, rate,
-				AuUnlimitedSamples, 0, NULL);
-	AuSetElements(aud, flow, AuTrue, 2, elms, NULL);
-	AuRegisterEventHandler(aud, AuEventHandlerIDMask, 0, flow,
-				event_handler, (AuPointer) NULL);
-
-	gettimeofday(&last_tv, 0);
-	really = written = 0;
-	paused = do_pause = 0;
-	buf_free = -1;
-	AuStartFlow(aud, flow, NULL);
-	return;
+	void *newbuf = malloc(buffer_size = max(newlen, MIN_BUFFER_SIZE));
+	void *oldbuf = buffer;
+	
+	memcpy(newbuf, buffer, buffer_used);
+	buffer = newbuf;
+	if (oldbuf != NULL)
+		free(oldbuf);
 }
 
 int
@@ -255,40 +275,39 @@
 nas_open(AFormat fmt, gint rate, gint nch)
 {
 	AuElement elms[3];
+	int bytes_per_sample = max(1, nch * AuSizeofFormat(format));
+	int frag_samples = frag_size / bytes_per_sample;
 
 	format = fmt;
 	int_rate = rate;
 	int_nch = nch;
 	bps = rate * nch * AuSizeofFormat(format);;
 
-	buffer_size = (nas_cfg.bufsize * rate) / 1000;
-	if (buffer_size < 4096)
-		buffer_size = 4096; 
-
-	if (buffer_size > 32768)
-		buffer_size = 32768; /* So that the buffer won't get unmanageably big. */
-
-	aud = AuOpenServer(nas_cfg.server, 0, NULL, 0, NULL, NULL);
-	if (!aud) return 0;
+	if (!(aud = AuOpenServer(NULL, 0, NULL, 0, NULL, NULL))) {
+		fprintf(stderr, "could not open nas audio server\n");
+		return 0;
+	};
 	dev = find_device(nch);
 	if ((dev == AuNone) || (!(flow = AuCreateFlow(aud, NULL)))) {
+		fprintf(stderr, "find_device or createflow failed\n");
 		AuCloseServer(aud);
 		aud = 0;
 		return 0;
 	}
 
 	AuMakeElementImportClient(elms, rate, format, nch, AuTrue,
-				buffer_size, buffer_size / 4, 0, NULL);
+				frag_samples * 2, frag_samples, 0, NULL);
 	AuMakeElementExportDevice(elms+1, 0, dev, rate,
 				AuUnlimitedSamples, 0, NULL);
 	AuSetElements(aud, flow, AuTrue, 2, elms, NULL);
 	AuRegisterEventHandler(aud, AuEventHandlerIDMask, 0, flow,
 				event_handler, (AuPointer) NULL);
+	AuSetErrorHandler(aud, error_handler);
 
 	gettimeofday(&last_tv, 0);
-	really = written = 0;
 	paused = do_pause = 0;
-	buf_free = -1;
+	close = 0;
 	AuStartFlow(aud, flow, NULL);
+
 	return 1;
 }

-- 
Tobias							     PGP-Key: 0x9AC7E0BC
echo ${SIGNATURE}



More information about the Nas mailing list