X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/f8a3bdc870992858b2e3bf66c33d1a10393de35e..a78895812191386f2da97e3795247d0ab9cc5e91:/plugins/tracklength-gstreamer.c diff --git a/plugins/tracklength-gstreamer.c b/plugins/tracklength-gstreamer.c index 1ccc6eb..93622fe 100644 --- a/plugins/tracklength-gstreamer.c +++ b/plugins/tracklength-gstreamer.c @@ -20,8 +20,6 @@ */ #include "tracklength.h" -#include "speaker-protocol.h" - /* Ugh. It turns out that libxml tries to define a function called * `attribute', and it's included by GStreamer for some unimaginable reason. * So undefine it here. We'll want GCC attributes for special effects, but @@ -31,8 +29,7 @@ #include #include -#include -#include +#include /* The only application we have for `attribute' is declaring function * arguments as being unused, because we have a lot of callback functions @@ -44,227 +41,58 @@ #define END ((void *)0) -static int inited_gstreamer = 0; - -enum { ST_PAUSE, ST_PAUSED, ST_PLAY, ST_EOS, ST_BOTCHED = -1 }; - -struct tracklength_state { - const char *path; - int state; - GMainLoop *loop; - GstElement *pipeline; - GstElement *sink; -}; +static GstDiscoverer *disco = 0; -static void decoder_pad_arrived(GstElement *elt, GstPad *pad, gpointer p) { - /* The `decodebin' element dreamed up a pad. If it's for audio output, - * attach it to a dummy sink so that it doesn't become sad. - */ +long disorder_tracklength(const char UNUSED *track, const char *path) { + GError *err = 0; + gchar *dir = 0, *abs = 0, *uri = 0; + GstDiscovererInfo *info = 0; + long length = -1; + GstClockTime t; - struct tracklength_state *state = p; - GstCaps *caps = 0; - GstStructure *s; - gchar *padname = 0; - guint i, n; + if (!path) goto end; -#ifdef HAVE_GSTREAMER_0_10 - caps = gst_pad_get_caps(pad); if(!caps) goto end; -#else - caps = gst_pad_get_current_caps(pad); if(!caps) goto end; -#endif - for(i = 0, n = gst_caps_get_size(caps); i < n; i++) { - s = gst_caps_get_structure(caps, i); - if(!strncmp(gst_structure_get_name(s), "audio/", 6)) goto match; + if(!disco) { + gst_init(0, 0); + disco = gst_discoverer_new(5*GST_SECOND, &err); if(!disco) goto end; } - goto end; -match: - padname = gst_pad_get_name(pad); - if(!padname) { - disorder_error(0, "error checking `%s': " - "failed to get GStreamer pad name (out of memory?)", - state->path); - goto botched; - } - if(!gst_element_link_pads(elt, padname, state->sink, "sink")) { - disorder_error(0, "error checking `%s': " - "failed to link GStreamer elements `%s' and `sink'", - state->path, GST_OBJECT_NAME(elt)); - goto botched; + if (g_path_is_absolute(path)) + uri = g_filename_to_uri(path, 0, &err); + else { + dir = g_get_current_dir(); + abs = g_build_filename(dir, path, END); + uri = g_filename_to_uri(abs, 0, &err); } - goto end; - -botched: - state->state = ST_BOTCHED; - g_main_loop_quit(state->loop); -end: - if(caps) gst_caps_unref(caps); - if(padname) g_free(padname); -} - -static void bus_message(GstBus UNUSED *bus, GstMessage *msg, gpointer p) { - /* Our bus sent us a message. If it's interesting, maybe switch state, and - * wake the main loop up. - */ - - struct tracklength_state *state = p; - GstState newstate; + if(!uri) goto end; - switch(GST_MESSAGE_TYPE(msg)) { - case GST_MESSAGE_ERROR: - /* An error. Mark the operation as botched and wake up the main loop. */ - disorder_error(0, "error checking `%s': %s", state->path, - gst_structure_get_string(gst_message_get_structure(msg), - "debug")); - state->state = ST_BOTCHED; - g_main_loop_quit(state->loop); - break; - case GST_MESSAGE_STATE_CHANGED: - /* A state change. If we've reached `PAUSED', then notify the main loop. - * This turns out to be a point at which the duration becomes available. - * If not, then the main loop will set us playing. - */ - gst_message_parse_state_changed(msg, 0, &newstate, 0); - if(state->state == ST_PAUSE && newstate == GST_STATE_PAUSED && - GST_MESSAGE_SRC(msg) == GST_OBJECT(state->pipeline)) { - g_main_loop_quit(state->loop); - state->state = ST_PAUSED; - } - break; - case GST_MESSAGE_EOS: - /* The end of the stream. Wake up the loop, and set the state to mark this - * as being the end of the line. - */ - state->state = ST_EOS; - g_main_loop_quit(state->loop); + info = gst_discoverer_discover_uri(disco, uri, &err); if(!info) goto end; + switch(gst_discoverer_info_get_result(info)) { + case GST_DISCOVERER_OK: + t = gst_discoverer_info_get_duration(info); + length = (t + GST_SECOND/2)/GST_SECOND; break; - case GST_MESSAGE_DURATION_CHANGED: - /* Something thinks it knows a duration. Wake up the main loop just in - * case. - */ + case GST_DISCOVERER_TIMEOUT: + disorder_info("discovery timed out probing `%s'", path); + goto swallow_error; + case GST_DISCOVERER_MISSING_PLUGINS: + disorder_info("unrecognized file `%s' (missing plugins?)", path); + goto swallow_error; + swallow_error: + if(err) { g_error_free(err); err = 0; } + length = 0; break; default: - /* Something else happened. Whatevs. */ - break; - } -} - -static int query_track_length(struct tracklength_state *state, - long *length_out) -{ - /* Interrogate the pipeline to find the track length. Return zero on - * success, or -1 on failure. This is annoying and nonportable. - */ - - gint64 t; -#ifdef HAVE_GSTREAMER_0_10 - GstFormat fmt = GST_FORMAT_TIME; -#endif - -#ifdef HAVE_GSTREAMER_0_10 - if(!gst_element_query_duration(state->pipeline, &fmt, &t) || - fmt != GST_FORMAT_TIME) - return -1; -#else - if(!gst_element_query_duration(state->pipeline, GST_FORMAT_TIME, &t)) - return -1; -#endif - *length_out = (t + 500000000)/1000000000; - return 0; -} - -long disorder_tracklength(const char UNUSED *track, const char *path) { - /* Discover the length of a track. */ - - struct tracklength_state state; - GstElement *source, *decode, *sink; - GstBus *bus = 0; - int running = 0; - long length = -1; - - /* Fill in the state structure. */ - state.path = path; - state.state = ST_PAUSE; - state.pipeline = 0; - state.loop = 0; - - /* Set up the GStreamer machinery. */ - if(!inited_gstreamer) gst_init(0, 0); - - /* Create the necessary pipeline elements. */ - source = gst_element_factory_make("filesrc", "file"); - decode = gst_element_factory_make("decodebin", "decode"); - sink = state.sink = gst_element_factory_make("fakesink", "sink"); - state.pipeline = gst_pipeline_new("pipe"); - if(!source || !decode || !sink) { - disorder_error(0, "failed to create GStreamer elements: " - "need base and good plugins"); - goto end; - } - g_object_set(source, "location", path, END); - - /* Add the elements to the pipeline. It will take over responsibility for - * them. - */ - gst_bin_add_many(GST_BIN(state.pipeline), source, decode, sink, END); - - /* Link the elements together as far as we can. Arrange to link the decoder - * onto our (dummy) sink when it's ready to produce output. - */ - if(!gst_element_link(source, decode)) { - disorder_error(0, "error checking `%s': " - "failed to link GStreamer elements `file' and `decode'", - path); goto end; } - g_signal_connect(decode, "pad-added", - G_CALLBACK(decoder_pad_arrived), &state); - - /* Fetch the bus and listen for messages. */ - bus = gst_pipeline_get_bus(GST_PIPELINE(state.pipeline)); - gst_bus_add_signal_watch(bus); - g_signal_connect(bus, "message", G_CALLBACK(bus_message), &state); - - /* Turn the handle until we have an answer. The message handler will wake us - * up if: the pipeline reports that the duration has changed (suggesting that - * something might know what it is); we successfully reach the initial - * `paused' state; or we hit the end of the stream (by which point we ought - * to know where we are). - */ - state.loop = g_main_loop_new(0, FALSE); - gst_element_set_state(state.pipeline, GST_STATE_PAUSED); running = 1; - for(;;) { - g_main_loop_run(state.loop); - if(!query_track_length(&state, &length)) goto end; - switch(state.state) { - case ST_BOTCHED: goto end; - case ST_EOS: - disorder_error(0, "error checking `%s': " - "failed to fetch duration from GStreamer pipeline", - path); - goto end; - case ST_PAUSED: - gst_element_set_state(state.pipeline, GST_STATE_PLAYING); - state.state = ST_PLAY; - break; - } - } end: - /* Well, it's too late to worry about anything now. */ - if(running) gst_element_set_state(state.pipeline, GST_STATE_NULL); - if(bus) { - gst_bus_remove_signal_watch(bus); - gst_object_unref(bus); - } - if(state.pipeline) - gst_object_unref(state.pipeline); - else { - if(source) gst_object_unref(source); - if(decode) gst_object_unref(decode); - if(sink) gst_object_unref(sink); + if(err) { + disorder_error(0, "error probing `%s': %s", path, err->message); + g_error_free(err); } - if(state.loop) g_main_loop_unref(state.loop); + if(info) gst_discoverer_info_unref(info); + g_free(dir); g_free(abs); g_free(uri); return length; }