From 5dcfc065d123f6643b12ed1766c7976e58b6941a Mon Sep 17 00:00:00 2001 Message-Id: <5dcfc065d123f6643b12ed1766c7976e58b6941a.1714924707.git.mdw@distorted.org.uk> From: Mark Wooding Date: Wed, 11 Jul 2018 12:54:27 +0100 Subject: [PATCH] plugins/tracklength-gstreamer.c: Rewrite to use `GstDiscoverer'. Organization: Straylight/Edgeware From: Mark Wooding The old version worked, but was fiddly, and very slow at some MP3 files. It seems that if you start playing for a few milliseconds and then ask, then you get a good answer, but since I found this out by tripping over the `GstDiscoverer' machinery that GStreamer already has, it seemed like a better idea to just use that. Which means almost all of the code has gone. --- configure.ac | 2 +- plugins/tracklength-gstreamer.c | 246 +++++--------------------------- 2 files changed, 39 insertions(+), 209 deletions(-) diff --git a/configure.ac b/configure.ac index 1ba7144..3b0429f 100644 --- a/configure.ac +++ b/configure.ac @@ -463,7 +463,7 @@ for i in $want_gstreamer; do *) AC_MSG_ERROR([unrecognized GStreamer version]) ;; esac PKG_CHECK_MODULES([GSTREAMER], - [gstreamer-$v gstreamer-app-$v gstreamer-audio-$v], + [gstreamer-$v gstreamer-app-$v gstreamer-audio-$v gstreamer-pbutils-$v], [have_gstreamer=$v], [have_gstreamer=no]) case $have_gstreamer in no) continue ;; esac AC_DEFINE_UNQUOTED([HAVE_GSTREAMER_$V], [1]) diff --git a/plugins/tracklength-gstreamer.c b/plugins/tracklength-gstreamer.c index 1ccc6eb..0d37a09 100644 --- a/plugins/tracklength-gstreamer.c +++ b/plugins/tracklength-gstreamer.c @@ -31,8 +31,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 +43,58 @@ #define END ((void *)0) -static int inited_gstreamer = 0; +static GstDiscoverer *disco = 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 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); -} + if(!uri) goto end; -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; - - 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; - } + 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 + 500000000)/1000000000; 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); - 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; } -- [mdw]