I think I have fixed all the problems I found with the GStreamer
backend.  See attached libcanberra-02-gstreamer.diff patch.  This patch
does the following:

1) No longer free variables in the bus_cb function.  This isn't safe
   since GStreamer isn't finished tearing things down.  Before this
   change, I noticed that when driver_play failed to set state to
   GST_STATE_PLAYING it would free the pipeline before GStreamer
   had finished tearing things down.

   Instead I create a new thread in driver_play and when we get
   EOS/ERROR in the bus_cb, I signal the other thread to start.
   This other thread sets the state to GST_STATE_NULL which
   properly closes the GStreamer backend, solving the problem of leaving
   the file descriptor open.  Then it frees things.  This seems to
   work fine with no warnings or errors seen before this patch.

2) The way variables were being freed in driver_play seemed pretty
   broken.  You shouldn't free plugins by themselves.  Instead just
   free the pipeline and this will free the plugins as well.  Now
   I set a boolean just before we set state to GST_STATE_PLAYING.
   If that boolean is set, then we know that the thread_func will
   call outstanding_free to free everything.  If it isn't set then
   driver_play will free things at the end by calling outstanding_free.
   In this case we know some error occurred before we called
   gst_element_set_state and the variables may need to be freed to
   avoid leaks.

3) A little cleanup in the driver_play function.

The second patch (libcanberra-01-solaris.diff) is needed for libcanberra
to compile on Solaris.  I don't think this is needed on Linux, so I
surrounded the needed "#include" with "#ifdef __sun".

Can these patches go upstream?

Thanks,

Brian



Using libcanberra the GStreamer backend, I notice a bug. When you run gnome-sound-properties, and then enable "Play alerts and sound effects" on the "Sounds" tab, then click on one of the little arrow buttons to
preview the sound, it plays okay.  However, the second time you try
clicking on any little arrow preview buttons it crashes.  It does this
even if you wait for the first sound to stop playing before playing
the second sound.

After spending several hours digging into this, it seems there are
several problems here:

1) It is now more clear to me that libcanberra assumes that multiple
    programs could play sounds at the same time, so it probably does
    not make sense to use an audiosink which blocks when playing, like
    the sunaudiosink I am currently using.

    However, libcanberra probably should behave better when using
    a blocking sink and not crash.  It probably should just skip
    sounds it can't play.

    Perhaps on Solaris we need to use the ESD GStreamer sink or the
    PulseAudio plugin instead.

2) The libcanberra GStreamer backend does not ever close the open file
    descriptor on the sound device.  So, after playing one sound, it
    will fail to play future sounds because subsequent opens fail with
    EBUSY.  It seems the libcanberra code needs to listen for EOS
    somehow and call gst_element_set_state on the pipeline to NULL.
    I see other GStreamer programs do this by calling
    g_signal_connect on the "message::eos" signal, but I think it
    might not be appropriate to do this from libcanberra since you
    may not always be sure that a glib event loop is running.

3) When the driver_play function calls gst_element_set_state to
    set the state to GST_STATE_PLAYING, and this fails (like due to
    the file descriptor being left open), it crashes.  The crash is on
    line 393 of src/gstreamer.c.  This line looks as follows:

         CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);

    Note that this line only gets called if the call to
    gst_element_set_state to GST_STATE_PLAYING returns
    GST_STATE_CHANGED_FAILURE.

    On the error, it displays these messages:

    ---

    (gnome-sound-properties:15054): GStreamer-CRITICAL **: file
    gstelement.c: line 2030: assertion `GST_IS_ELEMENT (element)' failed
    (gnome-sound-properties:15054): GLib-CRITICAL **: file gthread.c:
    line 347: assertion `mutex' failed
    Assertion '*_head == _item' failed at gstreamer.c:393, function ().
    Aborting.
    [EMAIL PROTECTED] ([EMAIL PROTECTED]) signal ABRT (Abort) in _lwp_kill at 
0xfc4617c5
    0xfc4617c5: _lwp_kill+0x0015:   jae      _lwp_kill+0x23 [ 0xfc4617d3,
    .+0xe ]

    ---

    The problem seems to be related to how the bus_cb function works.
    When you play a successful sound, it calls bus_cb after the sound
    is done playing and since the message iS GST_MESSAGE_EOS, and then
    breaks out of the case, and calls CA_LLIST_REMOVE and
    outstanding_free().  This doesn't seem to cause any problems.

    However, in the failure case, the message is GST_MESSAGE_ERR.  In
    this case it also breaks out of the case and calls CA_LLIST_REMOVE
    and outstanding_free().  Since outstanding_free unrefs the pipeline
    before the gst_element_set_state function has completed, this
    causes the "GST_IS_ELEMENT (element)" assertion above.  On my
    system, the gst_element_set_state is in the process of returning
    from the error when the pipeline is freed, and the assertion is
    from the gst_element_abort_state function when the structure
    suddenly turns to garbage.

    Even though calling CA_LLIST_REMOVE and outstanding_free seems to
    work okay in the "success" case, I am not sure it is a good idea
    to be unrefing the pipeline before GStreamer is done processing
    the gst_element_set_state function.

    The fact that we call CA_LLIST_REMOVE in the bus_cb function causes
    the entry to be removed already, so when we try to remove it again
    on line 393, it fails and this causes the crash.

    I notice if I comment out the calls to CA_LLIST_REMOVE and
    outstanding_free in the bus_cb function that it no longer crashes.
    Instead it just plays the first sound okay and fails to play future
    sounds without a crash (due to the file descriptor being left open).
    However, I suspect this isn't the right fix and instead just makes
    libcanberra leak memory.

At this point I'm not sure how to fix these problems, but it seems the
GStreamer code needs to be made more robust when the
gst_element_set_state function fails.  It also should set the state to
NULL when the file is done playing so any open file handles can be
closed.  I suspect the approach taken with the bus_cb function is not
the correct way to be doing memory cleanup.

Any ideas of how to improve the code would be appreciated.

Brian
_______________________________________________
libcanberra-discuss mailing list
[email protected]
https://tango.0pointer.de/mailman/listinfo/libcanberra-discuss

--- libcanberra-0.8/src/common.h-orig   2008-08-21 00:21:50.594788000 -0500
+++ libcanberra-0.8/src/common.h        2008-08-21 00:22:25.218467000 -0500
@@ -21,6 +21,10 @@
   <http://www.gnu.org/licenses/>.
 ***/
 
+#ifdef __sun
+#include <sys/varargs.h>
+#endif
+
 #include "canberra.h"
 #include "macro.h"
 #include "mutex.h"
--- libcanberra-0.10/src/gstreamer.c-orig       2008-10-30 02:01:23.980831000 
-0500
+++ libcanberra-0.10/src/gstreamer.c    2008-10-30 02:34:00.030254000 -0500
@@ -50,6 +50,8 @@ struct outstanding {
     void *userdata;
     GstElement *pipeline;
     struct ca_context *context;
+    pthread_cond_t cond;
+    pthread_mutex_t mutex;
 };
 
 struct private {
@@ -242,16 +244,9 @@ static GstBusSyncReply bus_cb(GstBus *bu
     if (!out->dead && out->callback)
         out->callback(out->context, out->id, err, out->userdata);
 
-    ca_mutex_lock(p->outstanding_mutex);
-
-    CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
-
-    if (!p->outstanding && p->signal_semaphore)
-        sem_post(&p->semaphore);
-
-    outstanding_free(out);
-
-    ca_mutex_unlock(p->outstanding_mutex);
+    pthread_mutex_lock(&out->mutex);
+    pthread_cond_signal(&out->cond);
+    pthread_mutex_unlock(&out->mutex);
 
     return GST_BUS_DROP;
 }
@@ -310,35 +305,75 @@ static void on_pad_added(GstElement *ele
     gst_caps_unref(caps);
 }
 
+static void* thread_func(void *userdata) {
+    struct outstanding *out = userdata;
+    struct private *p;
+
+    p = PRIVATE(out->context);
+
+    pthread_detach(pthread_self());
+
+    /* Wait until we receive EOS */
+    pthread_mutex_lock(&out->mutex);
+    pthread_cond_wait (&out->cond, &out->mutex);
+
+    ca_mutex_lock(p->outstanding_mutex);
+
+    /* Set pipeline back to NULL to close things */
+    if (gst_element_set_state(out->pipeline,
+                              GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
+        goto fail;
+    }
+
+    CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
+
+    if (!p->outstanding && p->signal_semaphore)
+        sem_post(&p->semaphore);
+
+    outstanding_free(out);
+
+fail:
+    ca_mutex_unlock(p->outstanding_mutex);
+    pthread_mutex_unlock(&out->mutex);
+    return NULL;
+}
+
 int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, 
ca_finish_callback_t cb, void *userdata) {
     struct private *p;
-    struct outstanding *out = NULL;
+    struct outstanding *out;
     ca_sound_file *f;
     GstElement *decodebin, *sink, *audioconvert, *audioresample, *bin;
     GstBus *bus;
     GstPad *audiopad;
+    pthread_t thread;
+    gboolean set_state;
     int ret;
 
     ca_return_val_if_fail(c, CA_ERROR_INVALID);
     ca_return_val_if_fail(proplist, CA_ERROR_INVALID);
     ca_return_val_if_fail(!userdata || cb, CA_ERROR_INVALID);
 
-    f = NULL;
-    sink = NULL;
-    decodebin = NULL;
-
-    p = PRIVATE(c);
+    out           = NULL;
+    f             = NULL;
+    decodebin     = NULL;
+    sink          = NULL;
+    audioconvert  = NULL;
+    audioresample = NULL;
+    bin           = NULL;
+    p             = PRIVATE(c);
+    set_state     = FALSE;
 
-    if ((ret = ca_lookup_sound_with_callback(&f, ca_gst_sound_file_open, NULL, 
&p->theme, c->props, proplist)) < 0)
+    if ((ret = ca_lookup_sound_with_callback(&f, ca_gst_sound_file_open,
+         NULL, &p->theme, c->props, proplist)) < 0)
         goto fail;
 
     if (!(out = ca_new0(struct outstanding, 1)))
         return CA_ERROR_OOM;
 
-    out->id = id;
+    out->id       = id;
     out->callback = cb;
     out->userdata = userdata;
-    out->context = c;
+    out->context  = c;
 
     if (!(out->pipeline = gst_pipeline_new(NULL))
         || !(decodebin = gst_element_factory_make("decodebin2", NULL))
@@ -351,7 +386,8 @@ int driver_play(ca_context *c, uint32_t 
 
     bin = gst_bin_new("audiobin");
 
-    g_signal_connect(decodebin, "new-decoded-pad", G_CALLBACK (on_pad_added), 
bin);
+    g_signal_connect(decodebin, "new-decoded-pad",
+                     G_CALLBACK (on_pad_added), bin);
 
     bus = gst_pipeline_get_bus(GST_PIPELINE (out->pipeline));
     gst_bus_set_sync_handler(bus, bus_cb, out);
@@ -361,11 +397,6 @@ int driver_play(ca_context *c, uint32_t 
                      f->fdsrc, decodebin, NULL);
 
     if (!gst_element_link(f->fdsrc, decodebin)) {
-        f->fdsrc = NULL;
-        decodebin = NULL;
-        audioconvert = NULL;
-        audioresample = NULL;
-        sink = NULL;
         goto fail;
     }
 
@@ -374,13 +405,10 @@ int driver_play(ca_context *c, uint32_t 
 
     audiopad = gst_element_get_pad(audioconvert, "sink");
     gst_element_add_pad(bin, gst_ghost_pad_new("sink", audiopad));
-
     gst_object_unref(audiopad);
 
     gst_bin_add(GST_BIN (out->pipeline), bin);
 
-    decodebin = NULL;
-    sink = NULL;
     ca_free(f);
     f = NULL;
 
@@ -388,11 +416,18 @@ int driver_play(ca_context *c, uint32_t 
     CA_LLIST_PREPEND(struct outstanding, p->outstanding, out);
     ca_mutex_unlock(p->outstanding_mutex);
 
-    if (gst_element_set_state(out->pipeline, GST_STATE_PLAYING) == 
GST_STATE_CHANGE_FAILURE) {
+    if (pthread_create(&thread, NULL, thread_func, out) < 0) {
         ca_mutex_lock(p->outstanding_mutex);
         CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
         ca_mutex_unlock(p->outstanding_mutex);
+        ret=CA_ERROR_OOM;
+        goto fail;
+    }
+    
+    set_state = TRUE;
 
+    if (gst_element_set_state(out->pipeline,
+                              GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
         ret = CA_ERROR_NOTAVAILABLE;
         goto fail;
     }
@@ -400,22 +435,21 @@ int driver_play(ca_context *c, uint32_t 
     return CA_SUCCESS;
 
 fail:
-    if (f && f->fdsrc)
-        gst_object_unref(f->fdsrc);
-
-    if (f)
+    if (f) {
         ca_free(f);
 
-    if (sink)
-        gst_object_unref(sink);
-
-    if (decodebin)
-        gst_object_unref(decodebin);
-
-    if (out && out->pipeline)
-        gst_object_unref(out->pipeline);
+        if (f->fdsrc)
+            gst_object_unref(f->fdsrc);
+    }
 
-    ca_free(out);
+    /*
+     * If we did not start playing, then we need to free any allocated
+     * variables.  If the pipeline did start, the free will be done by
+     * thread_func() .
+     */
+    if (set_state == FALSE) {
+       outstanding_free(out);
+    }
 
     return ret;
 }
_______________________________________________
libcanberra-discuss mailing list
[email protected]
https://tango.0pointer.de/mailman/listinfo/libcanberra-discuss

Reply via email to