Refactor loading sound resource as GIO stream
[projects/chimara/chimara.git] / libchimara / schannel.c
index 617adc19eb90b46736ecf817468beadf7db6c80b..b30e1707096e7a213d132c1097e6ff42969ed046 100644 (file)
@@ -2,9 +2,9 @@
 #include <glib.h>
 #include <glib/gi18n-lib.h>
 #include <libchimara/glk.h>
-#ifdef GSTREAMER_SOUND
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
 #include <gst/gst.h>
-#endif
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 #include "magic.h"
 #include "schannel.h"
 #include "chimara-glk-private.h"
 
 #define VOLUME_TIMER_RESOLUTION 1.0 /* In milliseconds */
 
-extern GPrivate *glk_data_key;
+#ifdef GSTREAMER_0_10_SOUND
+#define OGG_MIMETYPE "application/ogg"
+#endif
+#ifdef GSTREAMER_1_0_SOUND
+#define OGG_MIMETYPE "audio/ogg"
+#endif
 
-#ifdef GSTREAMER_SOUND
+extern GPrivate glk_data_key;
+
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
 /* Stop any currently playing sound on this channel, and remove any
  format-specific GStreamer elements from the channel. */
 static void
@@ -25,6 +32,11 @@ clean_up_after_playing_sound(schanid_t chan)
 {
        if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
                WARNING_S(_("Could not set GstElement state to"), "NULL");
+       if(chan->source)
+       {
+               gst_bin_remove(GST_BIN(chan->pipeline), chan->source);
+               chan->source = NULL;
+       }
        if(chan->demux)
        {
                gst_bin_remove(GST_BIN(chan->pipeline), chan->demux);
@@ -116,7 +128,7 @@ static void
 on_type_found(GstElement *typefind, guint probability, GstCaps *caps, schanid_t s)
 {
        gchar *type = gst_caps_to_string(caps);
-       if(strcmp(type, "application/ogg") == 0) {
+       if(strcmp(type, OGG_MIMETYPE) == 0) {
                s->demux = gst_element_factory_make("oggdemux", NULL);
                s->decode = gst_element_factory_make("vorbisdec", NULL);
                if(!s->demux || !s->decode) {
@@ -158,10 +170,53 @@ on_type_found(GstElement *typefind, guint probability, GstCaps *caps, schanid_t
                WARNING_S(_("Unexpected audio type in blorb"), type);
        }
 
+       /* This is necessary in case this handler occurs in the middle of a state
+       change */
+       gst_element_sync_state_with_parent(s->decode);
+       if(s->demux != NULL)
+               gst_element_sync_state_with_parent(s->demux);
+
 finally:
        g_free(type);
 }
-#endif /* GSTREAMER_SOUND */
+
+/* Load a sound resource into a GInputStream, by whatever method */
+static GInputStream *
+load_resource_into_giostream(glui32 snd)
+{
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
+       GInputStream *retval;
+
+       if(glk_data->resource_map == NULL) {
+               if(glk_data->resource_load_callback == NULL) {
+                       WARNING(_("No resource map has been loaded yet."));
+                       return NULL;
+               }
+               char *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
+               if(filename == NULL) {
+                       WARNING(_("Error loading resource from alternative location."));
+                       return NULL;
+               }
+
+               GError *err = NULL;
+               GFile *file = g_file_new_for_path(filename);
+               retval = G_INPUT_STREAM(g_file_read(file, NULL, &err));
+               if(retval == NULL)
+                       IO_WARNING(_("Error loading resource from file"), filename, err->message);
+               g_free(filename);
+               g_object_unref(file);
+       } else {
+               giblorb_result_t resource;
+               giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
+               if(result != giblorb_err_None) {
+                       WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
+                       return NULL;
+               }
+               retval = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
+       }
+       return retval;
+}
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 
 /**
  * glk_schannel_create:
@@ -215,8 +270,8 @@ glk_schannel_create(glui32 rock)
 schanid_t
 glk_schannel_create_ext(glui32 rock, glui32 volume)
 {
-#ifdef GSTREAMER_SOUND
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
 
        schanid_t s = g_new0(struct glk_schannel_struct, 1);
        s->magic = MAGIC_SCHANNEL;
@@ -243,12 +298,11 @@ glk_schannel_create_ext(glui32 rock, glui32 volume)
        gst_object_unref(bus);
 
        /* Create GStreamer elements to put in the pipeline */
-       s->source = gst_element_factory_make("giostreamsrc", NULL);
        s->typefind = gst_element_factory_make("typefind", NULL);
        s->convert = gst_element_factory_make("audioconvert", NULL);
        s->filter = gst_element_factory_make("volume", NULL);
        s->sink = gst_element_factory_make("autoaudiosink", NULL);
-       if(!s->source || !s->typefind || !s->convert || !s->filter || !s->sink) {
+       if(!s->typefind || !s->convert || !s->filter || !s->sink) {
                WARNING(_("Could not create one or more GStreamer elements"));
                goto fail;
        }
@@ -258,9 +312,10 @@ glk_schannel_create_ext(glui32 rock, glui32 volume)
 
        /* Put the elements in the pipeline and link as many together as we can
         without knowing the type of the audio stream */
-       gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL);
-       /* Link elements: Source -> typefinder -> ??? -> Converter -> Volume filter -> Sink */
-       if(!gst_element_link(s->source, s->typefind) || !gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
+       gst_bin_add_many(GST_BIN(s->pipeline), s->typefind, s->convert, s->filter, s->sink, NULL);
+
+       /* Link elements: ??? -> Converter -> Volume filter -> Sink */
+       if(!gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
                WARNING(_("Could not link GStreamer elements"));
                goto fail;
        }
@@ -273,7 +328,7 @@ fail:
        return NULL;
 #else
        return NULL;
-#endif /* GSTREAMER_SOUND */
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -288,8 +343,8 @@ glk_schannel_destroy(schanid_t chan)
 {
        VALID_SCHANNEL(chan, return);
 
-#ifdef GSTREAMER_SOUND
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
 
        if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
                WARNING_S(_("Could not set GstElement state to"), "NULL");
@@ -308,7 +363,7 @@ glk_schannel_destroy(schanid_t chan)
        
        chan->magic = MAGIC_FREE;
        g_free(chan);
-#endif
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -330,8 +385,8 @@ glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
 {
        VALID_SCHANNEL_OR_NULL(chan, return NULL);
 
-#ifdef GSTREAMER_SOUND
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        GList *retnode;
        
        if(chan == NULL)
@@ -347,7 +402,7 @@ glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
        return retval;
 #else
        return NULL;
-#endif /* GSTREAMER_SOUND */
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -437,10 +492,7 @@ glui32
 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
 {
        VALID_SCHANNEL(chan, return 0);
-#ifdef GSTREAMER_SOUND
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
-       GInputStream *stream;
-
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
        /* Stop the previous sound */
        clean_up_after_playing_sound(chan);
 
@@ -450,54 +502,34 @@ glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
                return 1;
        }
 
-       /* Load the sound into a GInputStream, by whatever method */
-       if(!glk_data->resource_map) {
-               if(!glk_data->resource_load_callback) {
-                       WARNING(_("No resource map has been loaded yet."));
-                       return 0;
-               }
-               gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
-               if(!filename) {
-                       WARNING(_("Error loading resource from alternative location."));
-                       return 0;
-               }
+       GInputStream *stream = load_resource_into_giostream(snd);
+       if(stream == NULL)
+               return 0;
 
-               GError *err = NULL;
-               GFile *file = g_file_new_for_path(filename);
-               stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
-               if(!stream) {
-                       IO_WARNING(_("Error loading resource from file"), filename, err->message);
-                       g_free(filename);
-                       g_object_unref(file);
-                       return 0;
-               }
-               g_free(filename);
-               g_object_unref(file);
-       } else {
-               giblorb_result_t resource;
-               giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
-               if(result != giblorb_err_None) {
-                       WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
-                       return 0;
-               }
-               stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
+       chan->source = gst_element_factory_make("giostreamsrc", NULL);
+       g_object_set(chan->source, "stream", stream, NULL);
+       g_object_unref(stream); /* Now owned by GStreamer element */
+       gst_bin_add(GST_BIN(chan->pipeline), chan->source);
+       if(!gst_element_link(chan->source, chan->typefind)) {
+               WARNING(_("Could not link GStreamer elements"));
+               clean_up_after_playing_sound(chan);
+               return 0;
        }
 
        chan->repeats = repeats;
        chan->resource = snd;
        chan->notify = notify;
-       g_object_set(chan->source, "stream", stream, NULL);
-       g_object_unref(stream); /* Now owned by GStreamer element */
        
        /* Play the sound; unless the channel is paused, then pause it instead */
        if(!gst_element_set_state(chan->pipeline, chan->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
                WARNING_S(_("Could not set GstElement state to"), chan->paused? "PAUSED" : "PLAYING");
+               clean_up_after_playing_sound(chan);
                return 0;
        }
        return 1;
 #else
        return 0;
-#endif
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -542,10 +574,8 @@ glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray
        for(count = 0; count < chancount; count++)
                VALID_SCHANNEL(chanarray[count], return 0);
 
-#ifdef GSTREAMER_SOUND
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
-       GInputStream *stream;
-
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        if(!glk_data->resource_map && !glk_data->resource_load_callback) {
                WARNING(_("No resource map has been loaded yet."));
                return 0;
@@ -559,43 +589,24 @@ glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray
                /* Stop the previous sound */
                clean_up_after_playing_sound(chanarray[count]);
 
-               /* Load the sound into a GInputStream, by whatever method */
-               if(!glk_data->resource_map) {
-                       gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, sndarray[count], glk_data->resource_load_callback_data);
-                       if(!filename) {
-                               WARNING(_("Error loading resource from alternative location."));
-                               skiparray[count] = TRUE;
-                               continue;
-                       }
+               GInputStream *stream = load_resource_into_giostream(sndarray[count]);
+               if(stream == NULL) {
+                       skiparray[count] = TRUE;
+                       continue;
+               }
 
-                       GError *err = NULL;
-                       GFile *file = g_file_new_for_path(filename);
-                       stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
-                       if(!stream) {
-                               IO_WARNING(_("Error loading resource from file"), filename, err->message);
-                               g_free(filename);
-                               g_object_unref(file);
-                               skiparray[count] = TRUE;
-                               continue;
-                       }
-                       g_free(filename);
-                       g_object_unref(file);
-               } else {
-                       giblorb_result_t resource;
-                       giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, sndarray[count]);
-                       if(result != giblorb_err_None) {
-                               WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
-                               skiparray[count] = TRUE;
-                               continue;
-                       }
-                       stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
+               chanarray[count]->source = gst_element_factory_make("giostreamsrc", NULL);
+               g_object_set(chanarray[count]->source, "stream", stream, NULL);
+               g_object_unref(stream); /* Now owned by GStreamer element */
+               gst_bin_add(GST_BIN(chanarray[count]->pipeline), chanarray[count]->source);
+               if(!gst_element_link(chanarray[count]->source, chanarray[count]->typefind)) {
+                       WARNING(_("Could not link GStreamer elements"));
+                       clean_up_after_playing_sound(chanarray[count]);
                }
 
                chanarray[count]->repeats = 1;
                chanarray[count]->resource = sndarray[count];
                chanarray[count]->notify = notify;
-               g_object_set(chanarray[count]->source, "stream", stream, NULL);
-               g_object_unref(stream); /* Now owned by GStreamer element */
        }
 
        /* Start all the sounds as close to each other as possible. */
@@ -608,6 +619,7 @@ glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray
                if(!gst_element_set_state(chanarray[count]->pipeline, chanarray[count]->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
                        WARNING_S(_("Could not set GstElement state to"), chanarray[count]->paused? "PAUSED" : "PLAYING");
                        skiparray[count] = TRUE;
+                       clean_up_after_playing_sound(chanarray[count]);
                        continue;
                }
                successes++;
@@ -616,7 +628,7 @@ glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray
        return successes;
 #else
        return 0;
-#endif
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -630,7 +642,7 @@ void
 glk_schannel_stop(schanid_t chan)
 {
        VALID_SCHANNEL(chan, return);
-#ifdef GSTREAMER_SOUND
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
        clean_up_after_playing_sound(chan);
 #endif
 }
@@ -658,6 +670,7 @@ glk_schannel_pause(schanid_t chan)
        /* Mark the channel as paused even if there is no sound playing yet */
        chan->paused = TRUE;
 
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
        GstState state;
        if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
                WARNING(_("Could not get GstElement state"));
@@ -670,6 +683,7 @@ glk_schannel_pause(schanid_t chan)
                WARNING_S(_("Could not set GstElement state to"), "PAUSED");
                return;
        }
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -697,6 +711,7 @@ glk_schannel_unpause(schanid_t chan)
        /* Mark the channel as not paused in any case */
        chan->paused = FALSE;
 
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
        GstState state;
        if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
                WARNING(_("Could not get GstElement state"));
@@ -709,6 +724,7 @@ glk_schannel_unpause(schanid_t chan)
                WARNING_S(_("Could not set GstElement state to"), "PLAYING");
                return;
        }
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -753,7 +769,7 @@ volume_glk_to_gstreamer(glui32 volume_glk)
        return CLAMP(((double)volume_glk / 0x10000), 0.0, 10.0);
 }
 
-#ifdef GSTREAMER_SOUND
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
 static gboolean
 volume_change_timeout(schanid_t chan)
 {
@@ -784,7 +800,7 @@ volume_change_timeout(schanid_t chan)
 
        return TRUE;
 }
-#endif /* GSTREAMER_SOUND */
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 
 /**
  * glk_schannel_set_volume_ext:
@@ -825,7 +841,7 @@ glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32
        VALID_SCHANNEL(chan, return);
        /* Silently ignore out-of-range volume values */
 
-#ifdef GSTREAMER_SOUND
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
        /* Interrupt a previous volume change */
        if(chan->volume_timer_id > 0)
                g_source_remove(chan->volume_timer_id);
@@ -852,7 +868,7 @@ glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32
 
        /* Set up a timer for the volume */
        chan->volume_timer_id = g_timeout_add(VOLUME_TIMER_RESOLUTION, (GSourceFunc)volume_change_timeout, chan);
-#endif
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }
 
 /**
@@ -871,8 +887,8 @@ glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32
 void 
 glk_sound_load_hint(glui32 snd, glui32 flag)
 {
-#ifdef GSTREAMER_SOUND
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+#if defined(GSTREAMER_0_10_SOUND) || defined(GSTREAMER_1_0_SOUND)
+       ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
        giblorb_result_t resource;
        giblorb_err_t result;
 
@@ -903,5 +919,5 @@ glk_sound_load_hint(glui32 snd, glui32 flag)
                        return;
                }
        }
-#endif /* GSTREAMER_SOUND */
+#endif /* GSTREAMER_0_10_SOUND || GSTREAMER_1_0_SOUND */
 }