chiark / gitweb /
plugins/tracklength-gstreamer.c: Rewrite to use `GstDiscoverer'.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 11 Jul 2018 11:54:27 +0000 (12:54 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 11 Jul 2018 11:54:27 +0000 (12:54 +0100)
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
plugins/tracklength-gstreamer.c

index 1ba7144..3b0429f 100644 (file)
@@ -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])
index 1ccc6eb..0d37a09 100644 (file)
@@ -31,8 +31,7 @@
 
 #include <glib.h>
 #include <gst/gst.h>
-#include <gst/app/gstappsink.h>
-#include <gst/audio/audio.h>
+#include <gst/pbutils/gstdiscoverer.h>
 
 /* The only application we have for `attribute' is declaring function
  * arguments as being unused, because we have a lot of callback functions
 
 #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;
 }