[xiph-commits] r16855 - in trunk/ao: . doc include/ao src src/plugins/arts

xiphmont at svn.xiph.org xiphmont at svn.xiph.org
Sat Jan 30 05:38:05 PST 2010


Author: xiphmont
Date: 2010-01-30 05:38:02 -0800 (Sat, 30 Jan 2010)
New Revision: 16855

Modified:
   trunk/ao/configure.ac
   trunk/ao/doc/ao_initialize.html
   trunk/ao/include/ao/ao_private.h
   trunk/ao/src/audio_out.c
   trunk/ao/src/plugins/arts/ao_arts.c
Log:
More of the endless hacking to make aRts support more reliable than aRts 
itself.  I think at this point we're guarding against anything that can 
go wrong.

Multi-stream playback support is also implemented but disabled; although 
it works, it will randomly trigger segfaults in the remote aRts server.



Modified: trunk/ao/configure.ac
===================================================================
--- trunk/ao/configure.ac	2010-01-30 10:54:41 UTC (rev 16854)
+++ trunk/ao/configure.ac	2010-01-30 13:38:02 UTC (rev 16855)
@@ -310,8 +310,9 @@
 
   if test "x$ac_cv_path_ARTSC_CONFIG" != x
   then
-	ARTS_CFLAGS=`$ac_cv_path_ARTSC_CONFIG --cflags`
-	ARTS_LIBS=`$ac_cv_path_ARTSC_CONFIG --libs`
+	PKG_CHECK_MODULES(ARTSGLIB, glib-2.0 gthread-2.0)
+	ARTS_CFLAGS="`$ac_cv_path_ARTSC_CONFIG --cflags` $ARTSGLIB_CFLAGS"
+	ARTS_LIBS="`$ac_cv_path_ARTSC_CONFIG --libs` $ARTSGLIB_LIBS"
 	SAVELIBS=$LIBS
 	LIBS="$LIBS $ARTS_LIBS"
 	AC_CHECK_FUNCS(arts_suspended)

Modified: trunk/ao/doc/ao_initialize.html
===================================================================
--- trunk/ao/doc/ao_initialize.html	2010-01-30 10:54:41 UTC (rev 16854)
+++ trunk/ao/doc/ao_initialize.html	2010-01-30 13:38:02 UTC (rev 16855)
@@ -20,9 +20,17 @@
 <p>This function initializes the internal libao data structures and
 loads all of the available plugins.  The system and user <a
 href="config.html">configuration files</a> are also read at this time
-if available.  The library must be initialized before any other libao
-functions can be used.
+if available.  ao_initialize() must be called in the main thread and
+before any other libao functions can be used.
 
+<p><i>More background on initialization in the main thread:
+ao_initialize() must be called in the main thread because several
+sound system interfaces used by libao must be initialized in the main
+thread.  One example is the system aRts interface, which stores some
+global state in thread-specific keys that it fails to delete on
+shutdown. If aRts is initialized in a non-main thread that later
+exits, these undeleted keys will cause a segmentation fault.</i>
+
 <br><br>
 <table border=0 color=black cellspacing=0 cellpadding=7>
 <tr bgcolor=#cccccc>

Modified: trunk/ao/include/ao/ao_private.h
===================================================================
--- trunk/ao/include/ao/ao_private.h	2010-01-30 10:54:41 UTC (rev 16854)
+++ trunk/ao/include/ao/ao_private.h	2010-01-30 13:38:02 UTC (rev 16855)
@@ -81,6 +81,7 @@
         int input_channels;
         int output_channels;
         int bytewidth;
+        int rate;
         char *output_matrix;
         int  *permute_channels;
 	void *internal; /* Pointer to driver-specific data */

Modified: trunk/ao/src/audio_out.c
===================================================================
--- trunk/ao/src/audio_out.c	2010-01-30 10:54:41 UTC (rev 16854)
+++ trunk/ao/src/audio_out.c	2010-01-30 13:38:02 UTC (rev 16855)
@@ -753,6 +753,7 @@
         /* set up any other housekeeping */
         device->input_channels = sformat.channels;
         device->bytewidth = (sformat.bits+7)>>3;
+        device->rate = sformat.rate;
 
 	/* Open the device */
 	result = funcs->open(device, &sformat);

Modified: trunk/ao/src/plugins/arts/ao_arts.c
===================================================================
--- trunk/ao/src/plugins/arts/ao_arts.c	2010-01-30 10:54:41 UTC (rev 16854)
+++ trunk/ao/src/plugins/arts/ao_arts.c	2010-01-30 13:38:02 UTC (rev 16855)
@@ -3,6 +3,7 @@
  *  ao_arts.c
  *
  *      Copyright (C) Rik Hemsley (rikkus) <rik at kde.org> 2000
+ *     Modifications Copyright (C) 2010 Monty <monty at xiph.org>
  *
  *  This file is part of libao, a cross-platform library.  See
  *  README for a history of this source code.
@@ -29,24 +30,26 @@
 
 #include <stdio.h>
 #include <errno.h>
+#include <string.h>
 #include <pthread.h>
 
+#include <glib.h>
 #include <artsc.h>
 #include <ao/ao.h>
 #include <ao/plugin.h>
 
-/* we must serialize server setup/teardown communication as its state
-   is process-global */
+/* we must serialize all aRtsc library access as virtually every
+   operation accesses global state */
 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 static int server_open_count = 0;
 
-static char *ao_arts_options[] = {"matrix","verbose","quiet","debug"};
+static char *ao_arts_options[] = {"matrix","verbose","quiet","debug","multi"};
 static ao_info ao_arts_info =
 {
 	AO_TYPE_LIVE,
 	"aRts output",
 	"arts",
-	"Rik Hemsley (rikkus) <rik at kde.org>",
+	"Monty <monty at xiph.org>",
 	"Outputs to the aRts soundserver.",
 	AO_FMT_NATIVE,
 #ifdef HAVE_ARTS_SUSPENDED
@@ -55,12 +58,14 @@
 	15,
 #endif
 	ao_arts_options,
-        4
+        5
 };
 
 typedef struct ao_arts_internal
 {
-	arts_stream_t stream;
+  arts_stream_t stream;
+  int allow_multi;
+  int buffersize;
 } ao_arts_internal;
 
 
@@ -89,9 +94,31 @@
   return 0;
 }
 
-
 ao_info *ao_plugin_driver_info(void)
 {
+  /* this is a dirty but necessary trick.  aRts's C library for
+     clients calls g_thread_init() internally in arts_init() whether
+     your app uses glib or not.  This call sets several
+     thread-specific keys and stashes glib static data state in the
+     calling thread.  Later, ao_close() calls arts_free(), glib is
+     dlclose()d, but the keys aren't deleted and this will cause a
+     segfault when the thread that originally called arts_init() exits
+     and pthreads tries to clean up. In addition, g_thread_init() must
+     be called outside of mutextes, and access to arts_init() below is
+     and must be locked.
+
+     So we tackle this problem in two ways; one, call g_thread_init()
+     here, which will be during ao_initialize().  It is documented
+     that ao_initialize() must be called in the app's main
+     thread. Second, be sure to link with glib-2.0, which means that
+     the glib static context is never unloaded by aRts (this alone is
+     currently enough in practice to avoid problems, but that's partly
+     by accident. The g_thread_init() here avoids it randomly breaking
+     again in the future by following documentation exactly). */
+
+  if (!g_thread_supported ())
+    g_thread_init(0);
+
   return &ao_arts_info;
 }
 
@@ -113,7 +140,26 @@
 
 int ao_plugin_set_option(ao_device *device, const char *key, const char *value)
 {
-  return 1; /* No options */
+  ao_arts_internal *internal = (ao_arts_internal *) device->internal;
+
+  if (!strcmp(key, "multi")) {
+    if(!strcmp(value,"yes") || !strcmp(value,"y") ||
+       !strcmp(value,"true") || !strcmp(value,"t") ||
+       !strcmp(value,"1"))
+      {
+        internal->allow_multi = 1;
+        return 1;
+      }
+    if(!strcmp(value,"no") || !strcmp(value,"n") ||
+       !strcmp(value,"false") || !strcmp(value,"f") ||
+       !strcmp(value,"0"))
+      {
+        internal->allow_multi = 0;
+        return 1;
+      }
+    return 0;
+  }
+  return 1;
 }
 
 int ao_plugin_open(ao_device *device, ao_sample_format *format)
@@ -132,24 +178,29 @@
   pthread_mutex_lock(&mutex);
   if(!server_open_count)
     errorcode = arts_init();
+  else{
+    if(!internal->allow_multi){
+      /* multiple-playback access disallowed; it's disallowed by
+         default as it tends to crash the aRts server. */
+      adebug("Multiple-open access disallowed and playback already in progress.\n");
+      pthread_mutex_unlock(&mutex);
+      return 0;
+    }
+  }
 
   if (0 != errorcode){
     pthread_mutex_unlock(&mutex);
     aerror("Could not connect to server => %s.\n",arts_error_text(errorcode));
     return 0; /* Could not connect to server */
-  }else
-    server_open_count++;
-  pthread_mutex_unlock(&mutex);
+  }
 
-
   device->driver_byte_format = AO_FMT_NATIVE;
   internal->stream = arts_play_stream(format->rate,
                                       format->bits,
                                       format->channels,
                                       "libao stream");
+
   if(!internal->stream){
-    pthread_mutex_lock(&mutex);
-    server_open_count--;
     if(!server_open_count)arts_free();
     pthread_mutex_unlock(&mutex);
 
@@ -157,13 +208,34 @@
     return 0;
   }
 
-  if(!device->output_matrix){
-    /* set up out matrix such that users are warned about > stereo playback */
-    if(format->channels<=2)
-      device->output_matrix=strdup("L,R");
-    //else no matrix, which results in a warning
+  if(arts_stream_set(internal->stream, ARTS_P_BLOCKING, 0)){
+    arts_close_stream(internal->stream);
+    internal->stream=NULL;
+    if(!server_open_count)arts_free();
+    pthread_mutex_unlock(&mutex);
+
+    aerror("Could not set audio stream to nonblocking.\n");
+    return 0;
   }
 
+  if((internal->buffersize = arts_stream_get(internal->stream, ARTS_P_BUFFER_SIZE))<=0){
+    arts_close_stream(internal->stream);
+    internal->stream=NULL;
+    if(!server_open_count)arts_free();
+    pthread_mutex_unlock(&mutex);
+
+    aerror("Could not get audio buffer size.\n");
+    return 0;
+  }
+
+  server_open_count++;
+  pthread_mutex_unlock(&mutex);
+
+  if(!device->output_matrix)
+    device->output_matrix=strdup("L,R");
+
+  adebug("thread %p: playback stream created!\n",internal->stream);
+  adebug("thread %p: buffer size = %d bytes\n",internal->stream,internal->buffersize);
   return 1;
 }
 
@@ -172,23 +244,104 @@
 		uint_32 num_bytes)
 {
   ao_arts_internal *internal = (ao_arts_internal *) device->internal;
+  int spindetect=0;
+  int i;
 
-  if (arts_write(internal->stream, output_samples,
-                 num_bytes) < num_bytes)
-    return 0;
-  else
-    return 1;
+  pthread_mutex_lock(&mutex);
+
+  /* the while loop below is another dirty but servicable hack needed
+     for two reasons:
+
+     1) for multiple-stream playback, there is no way to block on
+     more than one stream object at a time.  One can neither
+     select/poll, nor can we block on multiple writes at a time as
+     access to arts_write must be locked globally.  So we run in
+     nonblocking mode and write to the server based on audio timing.
+
+     2) Although aRts allegedly delivers errors on write failure, I've
+     never observed it actually do so in practice.  Most of the time
+     when something goes wrong, it returns a short count or zero, but
+     there are also cases where the write simply blocks forever
+     because the server logged an error and stopped answering without
+     informing the client or dropping the connection.  Again, we have
+     to run in nonblocking moda and look for an output pattern that
+     indicates the server disappeared out from under us (no successful
+     writes over a period that should certainly have starved
+     playback) */
+
+  while(1){
+    int accwrote=0;
+
+    /* why the multiple rapid-fire writes below?
+
+       aRts in nonblocking mode does not service internal buffering
+       state outside of the write call.  Further, the internal buffer
+       state appears to be pipelined; although the server may be
+       waiting or even starved for data, a non blocking write call
+       will often return immediately without actually writing
+       anything, regardless of internal buffer fullness.  Several more
+       calls (all returning 0, due to the full internal buffer) will
+       suddenly cause the internal state to actually flush data to the
+       server.  Thus the multiple writes in sequence are a way of
+       having the aRts internal state step through the sequence
+       necessary to actually submit data to the server.
+    */
+
+    for(i=0;i<5;i++){
+      int wrote = arts_write(internal->stream, output_samples, num_bytes);
+      if(wrote < 0){
+        /* although it's vanishingly unlikely that aRtsc will actually
+           bother reporting any errors, we might as well be ready for
+           one. */
+        pthread_mutex_unlock(&mutex);
+        aerror("Write error\n");
+        return 0;
+      }
+      accwrote+=wrote;
+      num_bytes -= wrote;
+      output_samples += wrote;
+    }
+
+    if(accwrote)
+      spindetect=0;
+    else
+      spindetect++;
+
+    if(spindetect==100){
+        pthread_mutex_unlock(&mutex);
+        aerror("Write thread spinning; has the aRts server crashed?\n");
+        return 0;
+    }
+
+    if(num_bytes>0){
+      long wait = internal->buffersize*1000/(device->output_channels*device->bytewidth*device->rate);
+      pthread_mutex_unlock(&mutex);
+      wait = (wait/8)*1000;
+      if(wait<1)wait=1;
+      if(wait>500000)wait=500000;
+      adebug("thread %p: wrote %d bytes\n",internal->stream,accwrote);
+      adebug("thread %p: playback stream waiting %dus\n",internal->stream,wait);
+      usleep(wait);
+      pthread_mutex_lock(&mutex);
+    }else{
+      pthread_mutex_unlock(&mutex);
+      adebug("thread %p: wrote %d bytes\n",internal->stream,accwrote);
+      break;
+    }
+  }
+
+  return 1;
 }
 
 
 int ao_plugin_close(ao_device *device)
 {
   ao_arts_internal *internal = (ao_arts_internal *) device->internal;
+  pthread_mutex_lock(&mutex);
   if(internal->stream)
     arts_close_stream(internal->stream);
   internal->stream = NULL;
 
-  pthread_mutex_lock(&mutex);
   server_open_count--;
   if(!server_open_count)arts_free();
   pthread_mutex_unlock(&mutex);



More information about the commits mailing list