[xiph-commits] r3703 - in browser_plugin/trunk: . src/audio

j at svn.annodex.net j at svn.annodex.net
Sat Aug 9 07:50:21 PDT 2008


Author: j
Date: 2008-08-09 07:50:20 -0700 (Sat, 09 Aug 2008)
New Revision: 3703

Modified:
   browser_plugin/trunk/configure.ac
   browser_plugin/trunk/src/audio/sydney_audio_pulseaudio.c
Log:
use pulseaudio callback api, avoids crashes and should be more flexible

Modified: browser_plugin/trunk/configure.ac
===================================================================
--- browser_plugin/trunk/configure.ac	2008-08-09 06:19:35 UTC (rev 3702)
+++ browser_plugin/trunk/configure.ac	2008-08-09 14:50:20 UTC (rev 3703)
@@ -66,7 +66,7 @@
 dnl
 dnl  Detect pulseaudio
 dnl
-PKG_CHECK_MODULES(PULSE, libpulse-simple, HAVE_PULSE=yes, HAVE_PULSE=no)
+PKG_CHECK_MODULES(PULSE, libpulse, HAVE_PULSE=yes, HAVE_PULSE=no)
 AC_SUBST(PULSE_CFLAGS)
 AC_SUBST(PULSE_LIBS)
 

Modified: browser_plugin/trunk/src/audio/sydney_audio_pulseaudio.c
===================================================================
--- browser_plugin/trunk/src/audio/sydney_audio_pulseaudio.c	2008-08-09 06:19:35 UTC (rev 3702)
+++ browser_plugin/trunk/src/audio/sydney_audio_pulseaudio.c	2008-08-09 14:50:20 UTC (rev 3703)
@@ -30,7 +30,6 @@
 #include <string.h>
 #include <pthread.h>
 #include <pulse/pulseaudio.h>
-#include <pulse/simple.h>
 #include "sydney_audio.h"
 
 /* Pulseaudio implementation based heavily on sydney_audio_alsa.c */
@@ -64,20 +63,18 @@
 };
 
 struct sa_stream {
-  pa_simple*        output_stream;
-  pa_sample_spec    output_spec;
+  pa_context*       context;
+  pa_stream*        stream;
+  pa_sample_spec    sample_spec;
+  pa_threaded_mainloop* m;
 
   pthread_t         thread_id;
   pthread_mutex_t   mutex;
+
   char              playing;
+  int64_t           bytes_written;
   char              client_name[255];
-  int64_t           bytes_written;
 
-  /* audio format info */
-  unsigned int      rate;
-  unsigned int      n_channels;
-  unsigned int      bytes_per_ch;
-
   /* buffer list */
   sa_buf          * bl_head;
   sa_buf          * bl_tail;
@@ -97,12 +94,67 @@
 #error BUF_LIMIT must be at least 2!
 #endif
 
-static void audio_callback(void* s);
+static void audio_callback(void* data);
+static void stream_write_callback(pa_stream *stream, size_t length, void *userdata);
+static void stream_latency_update_callback(pa_stream *stream, void *userdata);
+static void context_state_callback(pa_context *c, void *userdata);
 static sa_buf *new_buffer(void);
 
 
+
 /*
  * -----------------------------------------------------------------------------
+ * Pulseaudio callback functions
+ * -----------------------------------------------------------------------------
+ */
+ 
+static void context_state_callback(pa_context *c, void *userdata) {
+  sa_stream_t* s = (sa_stream_t*)userdata;
+  switch (pa_context_get_state(c)) {
+    case PA_CONTEXT_READY:
+    case PA_CONTEXT_TERMINATED:
+    case PA_CONTEXT_FAILED:
+      pa_threaded_mainloop_signal(s->m, 0);
+      break;
+    case PA_CONTEXT_UNCONNECTED:
+    case PA_CONTEXT_CONNECTING:
+    case PA_CONTEXT_AUTHORIZING:
+    case PA_CONTEXT_SETTING_NAME:
+      break;
+  }
+}
+
+static void stream_state_callback(pa_stream *stream, void *userdata) {
+  sa_stream_t* s = (sa_stream_t*)userdata;
+  switch (pa_stream_get_state(stream)) {
+
+      case PA_STREAM_READY:
+      case PA_STREAM_FAILED:
+      case PA_STREAM_TERMINATED:
+        pa_threaded_mainloop_signal(s->m, 0);
+        break;
+      case PA_STREAM_UNCONNECTED:
+      case PA_STREAM_CREATING:
+        break;
+  }
+}
+
+static void stream_write_callback(pa_stream *stream, size_t length, void *userdata)
+{
+  sa_stream_t* s = (sa_stream_t*)userdata;
+  pa_threaded_mainloop_signal(s->m, 0);
+}
+
+static void stream_latency_update_callback(pa_stream *stream, void *userdata)
+{
+  sa_stream_t* s = (sa_stream_t*)userdata;
+  pa_threaded_mainloop_signal(s->m, 0);
+}
+
+
+
+/*
+ * -----------------------------------------------------------------------------
  * Startup and shutdown functions
  * -----------------------------------------------------------------------------
  */
@@ -117,6 +169,7 @@
   unsigned  int       n_channels
 ) {
   sa_stream_t   * s = 0;
+  char *server = NULL;
 
   /*
    * Make sure we return a NULL stream pointer on failure.
@@ -143,59 +196,102 @@
     free(s);
     return SA_ERROR_OOM;
   }
+
   if (pthread_mutex_init(&s->mutex, NULL) != 0) {
     free(s->bl_head);
     free(s);
     return SA_ERROR_SYSTEM;
   }
 
-  s->output_stream  = NULL;
-  s->thread_id    = 0;
-  s->playing      = 0;
+  s->stream        = NULL;
+  s->m             = NULL;
+  s->thread_id     = 0;
+  s->playing       = 0;
   s->bytes_written = 0;
-  s->rate         = rate;
-  s->n_channels   = n_channels;
-  s->bytes_per_ch = 2;
-  s->bl_tail      = s->bl_head;
-  s->n_bufs       = 1;
 
+  s->bl_tail       = s->bl_head;
+  s->n_bufs        = 1;
+
+  s->sample_spec.format = PA_SAMPLE_S16LE;
+  s->sample_spec.channels = n_channels;
+  s->sample_spec.rate = rate;
+
   strcpy(s->client_name, client_name);
 
+  /* Set up a new main loop */
+  s->m = pa_threaded_mainloop_new();
+  pa_threaded_mainloop_start(s->m);
+
+  pa_threaded_mainloop_lock(s->m);
+
+  /* Create a new connection context */
+  if (!(s->context = pa_context_new(pa_threaded_mainloop_get_api(s->m), "OggPlay"))) {
+    fprintf(stderr, "pa_context_new() failed.\n");
+    goto unlock_and_fail;
+  }
+  pa_context_set_state_callback(s->context, context_state_callback, s);
+
+  pa_context_connect(s->context, server, 0, NULL);
+
+  /* Wait until the context is ready */
+  pa_threaded_mainloop_wait(s->m);
+  if (pa_context_get_state(s->context) != PA_CONTEXT_READY) {
+    fprintf(stderr, "creating Pulseaudio Context failed\n");
+    goto unlock_and_fail;
+  }
+  pa_threaded_mainloop_unlock(s->m);
+
   *_s = s;
   return SA_SUCCESS;
+
+unlock_and_fail:
+  pa_threaded_mainloop_unlock(s->m);
+  free(s);
+  return SA_ERROR_OOM;
 }
 
-
 int
 sa_stream_open(sa_stream_t *s) {
   if (s == NULL) {
     return SA_ERROR_NO_INIT;
   }
-  if (s->output_stream != NULL) {
+  if (s->stream != NULL) {
     return SA_ERROR_INVALID;
   }
 
-  s->output_spec.format = PA_SAMPLE_S16LE;
-  s->output_spec.channels = s->n_channels;
-  s->output_spec.rate = s->rate;
+  pa_threaded_mainloop_lock(s->m);
+  if (!(s->stream = pa_stream_new(s->context, s->client_name, &s->sample_spec, NULL))) {
+    fprintf(stderr, "pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(s->context)));
+    goto unlock_and_fail;
+  }
 
-  s->output_stream = pa_simple_new(NULL,               // Use the default server.
-                   "OggPlay",                        // Our application's name.
-                   PA_STREAM_PLAYBACK,
-                   NULL,                             // Use the default device.
-                   s->client_name,                       // Description of our stream.
-                   &s->output_spec,                  // Our sample format.
-                   NULL,                             // Use default channel map
-                   NULL,                             // Use default buffering attributes.
-                   NULL                              // Ignore error code.
-                   );
+  pa_stream_set_state_callback(s->stream, stream_state_callback, s);
+  pa_stream_set_write_callback(s->stream, stream_write_callback, s);
+  pa_stream_set_latency_update_callback(s->stream, stream_latency_update_callback, s);
 
-  if (!s->output_stream)
+  if (pa_stream_connect_playback(s->stream, NULL, NULL, 0, NULL, NULL) < 0) {
+    fprintf(stderr, "pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(s->context)));
+    goto unlock_and_fail;
+  }
+
+  /* Wait until the stream is ready */
+  pa_threaded_mainloop_wait(s->m);
+
+  if (pa_stream_get_state(s->stream) != PA_STREAM_READY) {
+    fprintf(stderr, "Failed to connect stream: %s", pa_strerror(pa_context_errno(s->context)));
+    goto unlock_and_fail;
+  }
+  pa_threaded_mainloop_unlock(s->m);
+
+  if (!s->stream)
     return SA_ERROR_NO_DEVICE;
   return SA_SUCCESS;
+
+unlock_and_fail:
+  pa_threaded_mainloop_unlock(s->m);
+  return SA_ERROR_NO_DEVICE;
 }
 
-
 int
 sa_stream_destroy(sa_stream_t *s) {
   if (s == NULL) {
@@ -203,13 +299,29 @@
   }
 
   pthread_mutex_lock(&s->mutex);
-  /*
-   * This causes the thread sending data to pulseaudio to shutdown
-   */
   s->thread_id = 0;
-
   pthread_mutex_unlock(&s->mutex);
 
+  pa_threaded_mainloop_lock(s->m);
+  pa_stream_disconnect(s->stream);
+  s->stream = NULL;
+  pa_context_disconnect(s->context);
+  pa_context_unref(s->context);
+  s->context = NULL;
+  pa_threaded_mainloop_unlock(s->m);
+
+  pa_threaded_mainloop_stop(s->m);
+  pa_threaded_mainloop_free(s->m);
+
+  pthread_mutex_destroy(&s->mutex);
+
+  while (s->bl_head != NULL) {
+    sa_buf  * next = s->bl_head->next;
+    free(s->bl_head);
+    s->bl_head = next;
+  }
+  free(s);
+
   return SA_SUCCESS;
 }
 
@@ -225,12 +337,15 @@
 sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
   int result = SA_SUCCESS;
 
-  if (s == NULL || s->output_stream == NULL) {
+  if (s == NULL || s->stream == NULL) {
     return SA_ERROR_NO_INIT;
   }
   if (nbytes == 0) {
     return SA_SUCCESS;
   }
+
+  pthread_mutex_lock(&s->mutex);
+
   /*
    * Append the new data to the end of our buffer list.
    */
@@ -296,16 +411,13 @@
     } /* if (nbytes <= avail), else */
 
   } /* while (1) */
-
   pthread_mutex_unlock(&s->mutex);
-
   /*
    * Once we have our first block of audio data, enable the audio callback
    * function. This doesn't need to be protected by the mutex, because
    * s->playing is not used in the audio callback thread, and it's probably
    * better not to be inside the lock when we enable the audio callback.
    */
-
   if (!s->playing) {
     s->playing = 1;
     if (pthread_create(&s->thread_id, NULL, (void *)audio_callback, s) != 0) {
@@ -317,24 +429,41 @@
 
 static void audio_callback(void* data)
 {
-  int error;
   sa_stream_t* s = (sa_stream_t*)data;
+  unsigned int bytes_per_frame = s->sample_spec.channels * pa_sample_size(&s->sample_spec);
+  size_t buffer_size = s->sample_spec.rate * bytes_per_frame;
+  char* buffer = malloc(buffer_size);
 
-  //what is a good value here for length?
-  unsigned int bytes_per_frame = s->n_channels * s->bytes_per_ch;
-  unsigned int length = 4096 * bytes_per_frame;
-
-  char* buffer = malloc(length);
-
   while(1) {
     char* dst = buffer;
-    unsigned int bytes_to_copy = length;
-    int bytes = length;
+    size_t bytes_to_copy, bytes;
 
+    pa_threaded_mainloop_lock(s->m);
+    while(1) {
+      if (s == NULL || s->stream == NULL) {
+        if (s != NULL && s->m != NULL) 
+          pa_threaded_mainloop_unlock(s->m);
+        goto free_buffer;
+      }
+      if ((bytes_to_copy = pa_stream_writable_size(s->stream)) == (size_t) -1) {
+        fprintf(stderr, "pa_stream_writable_size() failed: %s", pa_strerror(pa_context_errno(s->context)));
+        pa_threaded_mainloop_unlock(s->m);
+        goto free_buffer;
+      }
+      if(bytes_to_copy > 0)
+        break;
+      pa_threaded_mainloop_wait(s->m);
+    }
+    pa_threaded_mainloop_unlock(s->m);
+    if (bytes_to_copy > buffer_size)
+      bytes_to_copy = buffer_size;
+    bytes = bytes_to_copy;
+
     pthread_mutex_lock(&s->mutex);
-    if (!s->thread_id)
+    if (!s->thread_id) {
+      pthread_mutex_unlock(&s->mutex);
       break;
-
+    }
     /*
      * Consume data from the start of the buffer list.
      */
@@ -348,8 +477,8 @@
          */
         memcpy(dst, s->bl_head->data + s->bl_head->start, bytes_to_copy);
         s->bl_head->start += bytes_to_copy;
-        s->bytes_written += bytes_to_copy;
         break;
+    
       } else {
         sa_buf* next = 0;
         /*
@@ -359,8 +488,6 @@
         s->bl_head->start += avail;
         dst += avail;
         bytes_to_copy -= avail;
-        s->bytes_written += avail;
-
         /*
          * We want to free the now-empty buffer, but not if it's also the
          * current tail. If it is the tail, we don't have enough data to fill
@@ -375,50 +502,39 @@
         s->bl_head = next;
         s->n_bufs--;
       } /* if (avail >= bytes_to_copy), else */
-      
     } /* while (1) */
-    pthread_mutex_unlock(&s->mutex);
 
-    if(bytes>0) {
-      if(pa_simple_write(s->output_stream, buffer, bytes, &error) < 0) {
-        printf( __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error));
+    if(bytes > 0) {
+      pa_threaded_mainloop_lock(s->m);
+      if (pa_stream_write(s->stream, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE) < 0) {
+        fprintf(stderr, "pa_stream_write() failed: %s", pa_strerror(pa_context_errno(s->context)));
+        pa_threaded_mainloop_unlock(s->m);
+        return;
       }
+      pa_stream_update_timing_info(s->stream, NULL, NULL);
+      s->bytes_written += bytes;
+      pa_threaded_mainloop_unlock(s->m);
     }
+    pthread_mutex_unlock(&s->mutex);
   }
-
+free_buffer:
   free(buffer);
+}
 
-  if (s->output_stream != NULL) {
-    pa_simple_free(s->output_stream);
-  }
 
-  /*
-   * Release resources.
-   */
-
-  //does this need checking?
-  pthread_mutex_destroy(&s->mutex);
-
-  while (s->bl_head != NULL) {
-    sa_buf  * next = s->bl_head->next;
-    free(s->bl_head);
-    s->bl_head = next;
-  }
-  free(s);
-}
-
 /*
  * -----------------------------------------------------------------------------
  * General query and support functions
  * -----------------------------------------------------------------------------
  */
 
+
 int
 sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
   sa_buf  * b;
   size_t    used = 0;
 
-  if (s == NULL || s->output_stream == NULL) {
+  if (s == NULL || s->stream == NULL) {
     return SA_ERROR_NO_INIT;
   }
 
@@ -441,48 +557,42 @@
 int
 sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
   pa_usec_t usec;
-  int64_t latency;
-
-  if (s == NULL || s->output_stream == NULL) {
+  if (s == NULL || s->stream == NULL) {
     return SA_ERROR_NO_INIT;
   }
   if (position != SA_POSITION_WRITE_SOFTWARE) {
     return SA_ERROR_NOT_SUPPORTED;
   }
-
-  usec = pa_simple_get_latency(s->output_stream, NULL);
-  latency = pa_usec_to_bytes(usec, &s->output_spec);
-
-  // this part causes a crash sometimes
-  // pthread_mutex_lock.c:87: __pthread_mutex_lock: Assertion `mutex->__data.__owner == 0' failed.
-  pthread_mutex_lock(&s->mutex);
-  *pos  = s->bytes_written - latency;
-  pthread_mutex_unlock(&s->mutex);
+  pa_threaded_mainloop_lock(s->m);
+  if(pa_stream_get_time(s->stream,  &usec) != PA_ERR_NODATA) {
+    *pos = pa_usec_to_bytes(usec, &s->sample_spec);
+  }
+  else {
+    *pos  = s->bytes_written;
+  }
+  pa_threaded_mainloop_unlock(s->m);
   return SA_SUCCESS;
 }
 
 
 int
 sa_stream_pause(sa_stream_t *s) {
-  if (s == NULL || s->output_stream == NULL) {
+  if (s == NULL || s->stream == NULL) {
     return SA_ERROR_NO_INIT;
   }
-
-  pa_simple_flush(s->output_stream, NULL);
   return SA_SUCCESS;
 }
 
 
 int
 sa_stream_resume(sa_stream_t *s) {
-  if (s == NULL || s->output_stream == NULL) {
+  if (s == NULL || s->stream == NULL) {
     return SA_ERROR_NO_INIT;
   }
 
-  pthread_mutex_lock(&s->mutex);
-
+  pa_threaded_mainloop_lock(s->m);
   s->bytes_written = 0;
-  pthread_mutex_unlock(&s->mutex);
+  pa_threaded_mainloop_unlock(s->m);
   return SA_SUCCESS;
 }
 
@@ -511,10 +621,10 @@
 sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
   pa_cvolume cv;
 
-  if (s == NULL || s->output_stream == NULL) {
+  if (s == NULL || s->stream == NULL) {
     return SA_ERROR_NO_INIT;
   }
-  pa_cvolume_set(&cv, s->n_channels, pa_sw_volume_from_dB(vol));
+  pa_cvolume_set(&cv, s->sample_spec.channels, pa_sw_volume_from_dB(vol));
 
   return SA_SUCCESS;
 }
@@ -522,7 +632,7 @@
 
 int
 sa_stream_get_volume_abs(sa_stream_t *s, float *vol) {
-  if (s == NULL || s->output_stream == NULL) {
+  if (s == NULL || s->stream == NULL) {
     return SA_ERROR_NO_INIT;
   }
   printf("sa_stream_get_volume_abs not implemented\n");



More information about the commits mailing list