Initial import
authorAntonio Ospite <ao2@ao2.it>
Wed, 15 Oct 2014 13:39:28 +0000 (14:39 +0100)
committerAntonio Ospite <ao2@ao2.it>
Wed, 15 Oct 2014 13:39:28 +0000 (15:39 +0200)
Makefile [new file with mode: 0644]
gst-aseq-appsrc.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..adf7d18
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,42 @@
+CFLAGS = -std=c99 -pedantic -pedantic-errors -Wall -g3 -O2
+CFLAGS += -fno-common \
+       -fvisibility=hidden \
+       -Wall \
+       -Wdeclaration-after-statement \
+       -Wextra \
+       -Wformat=2 \
+       -Winit-self \
+       -Winline \
+       -Wpacked \
+       -Wpointer-arith \
+       -Wlarger-than-65500 \
+       -Wmissing-declarations \
+       -Wmissing-format-attribute \
+       -Wmissing-noreturn \
+       -Wmissing-prototypes \
+       -Wnested-externs \
+       -Wold-style-definition \
+       -Wredundant-decls \
+       -Wsign-compare \
+       -Wstrict-aliasing=2 \
+       -Wstrict-prototypes \
+       -Wundef \
+       -Wunreachable-code \
+       -Wunsafe-loop-optimizations \
+       -Wunused-but-set-variable \
+       -Wno-unused-parameter \
+       -Wwrite-strings \
+       -Wp,-D_FORTIFY_SOURCE=2 \
+       -fstack-protector \
+       --param=ssp-buffer-size=4
+
+# for strdup
+CFLAGS += -D_POSIX_C_SOURCE=200809L
+
+CFLAGS += $(shell pkg-config --cflags gstreamer-1.0 gstreamer-app-1.0 alsa)
+LDLIBS := $(shell pkg-config --libs gstreamer-1.0 gstreamer-app-1.0 alsa)
+
+gst-aseq-appsrc: gst-aseq-appsrc.o
+
+clean:
+       rm -f *.o *~ gst-aseq-appsrc
diff --git a/gst-aseq-appsrc.c b/gst-aseq-appsrc.c
new file mode 100644 (file)
index 0000000..b4f6c39
--- /dev/null
@@ -0,0 +1,488 @@
+/* GStreamer
+ *
+ * gst-aseq-appsrc: a GStreamer appsrc for the ALSA MIDI sequencer API
+ *
+ * Copyright (C) 2014  Antonio Ospite <ao2@ao2.it>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/* Code _heavily_ inspired to aseqdump and amidicat:
+ * http://git.alsa-project.org/?p=alsa-utils.git;a=tree;f=seq/aseqdump
+ * http://krellan.com/amidicat/
+ */
+
+#include <gst/gst.h>
+#include <gst/app/gstappsrc.h>
+
+#include <string.h>
+#include <sys/poll.h>
+#include <alsa/asoundlib.h>
+#include <glib-unix.h>
+
+#define DEFAULT_BUFSIZE 65536
+#define DEFAULT_CLIENT_NAME "gst-aseq-appsrc"
+#define DEFAULT_TICK_PERIOD_MS 10
+#define DEFAULT_POLL_TIMEOUT_MS (DEFAULT_TICK_PERIOD_MS / 2)
+
+GST_DEBUG_CATEGORY (mysrc_debug);
+#define GST_CAT_DEFAULT mysrc_debug
+
+typedef struct _App App;
+
+struct _App
+{
+  GstElement *pipeline;
+  GstElement *appsrc;
+
+  GMainLoop *loop;
+
+  snd_seq_t *seq;
+  int port_count;
+  snd_seq_addr_t *ports;
+  snd_midi_event_t *parser;
+  unsigned char *buffer;
+
+  struct pollfd *pfds;
+  int npfds;
+
+  guint64 tick;
+};
+
+App s_app;
+
+static int
+init_seq (App * app)
+{
+  int ret;
+
+  ret = snd_seq_open (&app->seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+  if (ret < 0) {
+    GST_ERROR ("Cannot open sequencer - %s", snd_strerror (ret));
+    goto error;
+  }
+
+  ret = snd_seq_set_client_name (app->seq, DEFAULT_CLIENT_NAME);
+  if (ret < 0) {
+    GST_ERROR ("Cannot set client name - %s", snd_strerror (ret));
+    goto error_seq_close;
+  }
+
+  return 0;
+
+error_seq_close:
+  snd_seq_close (app->seq);
+error:
+  return ret;
+}
+
+/* parses one or more port addresses from the string */
+static int
+parse_ports (const char *arg, App * app)
+{
+  char *buf, *s, *port_name;
+  int ret;
+
+  GST_DEBUG ("ports: %s", arg);
+
+  /* make a copy of the string because we're going to modify it */
+  buf = strdup (arg);
+  if (!buf) {
+    GST_ERROR ("Out of memory");
+    ret = -ENOMEM;
+    goto out;
+  }
+
+  for (port_name = s = buf; s; port_name = s + 1) {
+    /* Assume that ports are separated by commas.  We don't use
+     * spaces because those are valid in client names. */
+    s = strchr (port_name, ',');
+    if (s)
+      *s = '\0';
+
+    app->port_count++;
+    app->ports =
+        realloc (app->ports, app->port_count * sizeof (snd_seq_addr_t));
+    if (!app->ports) {
+      GST_ERROR ("Out of memory");
+      ret = -ENOMEM;
+      goto out_free_buf;
+    }
+
+    ret =
+        snd_seq_parse_address (app->seq, &app->ports[app->port_count - 1],
+        port_name);
+    if (ret < 0) {
+      GST_ERROR ("Invalid port %s - %s", port_name, snd_strerror (ret));
+      goto error_free_ports;
+    }
+  }
+
+  goto out_free_buf;
+
+error_free_ports:
+  free (app->ports);
+out_free_buf:
+  free (buf);
+out:
+  return ret;
+}
+
+static int
+create_port (App * app)
+{
+  int ret;
+
+  ret = snd_seq_create_simple_port (app->seq, DEFAULT_CLIENT_NAME,
+      SND_SEQ_PORT_CAP_WRITE |
+      SND_SEQ_PORT_CAP_SUBS_WRITE,
+      SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
+  if (ret < 0)
+    GST_ERROR ("Cannot create port - %s", snd_strerror (ret));
+
+  return ret;
+}
+
+static void
+connect_ports (App * app)
+{
+  int i;
+  int ret;
+
+  for (i = 0; i < app->port_count; ++i) {
+    ret =
+        snd_seq_connect_from (app->seq, 0, app->ports[i].client,
+        app->ports[i].port);
+    if (ret < 0)
+      /* warning */
+      GST_WARNING ("Cannot connect from port %d:%d - %s",
+          app->ports[i].client, app->ports[i].port, snd_strerror (ret));
+  }
+}
+
+static void
+push_buffer (App * app, gpointer data, guint size)
+{
+  GstBuffer *buffer;
+  GstClockTime time;
+  int ret;
+  gpointer local_data;
+
+  /* read the next chunk */
+  buffer = gst_buffer_new ();
+
+  time = app->tick * DEFAULT_TICK_PERIOD_MS * GST_MSECOND;
+
+  GST_BUFFER_DTS (buffer) = time;
+  GST_BUFFER_PTS (buffer) = time;
+  GST_BUFFER_OFFSET (buffer) = time;
+  GST_BUFFER_DURATION (buffer) = DEFAULT_TICK_PERIOD_MS * GST_MSECOND;
+
+  local_data = g_malloc (size);
+  memcpy (local_data, data, size);
+
+  gst_buffer_append_memory (buffer,
+      gst_memory_new_wrapped (0, local_data, size, 0, size, local_data,
+          g_free));
+
+  GST_DEBUG ("feed buffer %p, tick %" G_GUINT64_FORMAT " size: %u",
+      (gpointer) buffer, app->tick, size);
+  g_signal_emit_by_name (app->appsrc, "push-buffer", buffer, &ret);
+  gst_buffer_unref (buffer);
+
+  app->tick += 1;
+}
+
+static void
+push_tick_buffer (App * app)
+{
+  app->buffer[0] = 0xf9;
+  push_buffer (app, app->buffer, 1);
+}
+
+/* This method is called by the need-data signal callback, we feed data into the
+ * appsrc.
+ */
+static void
+feed_data (GstElement * appsrc, guint size, App * app)
+{
+  long size_ev = 0;
+  int err;
+  int ret;
+
+  snd_seq_poll_descriptors (app->seq, app->pfds, app->npfds, POLLIN);
+  ret = poll (app->pfds, app->npfds, DEFAULT_POLL_TIMEOUT_MS);
+  if (ret < 0) {
+    GST_ERROR ("ERROR in poll: %s", strerror (errno));
+  } else if (ret == 0) {
+    push_tick_buffer (app);
+  } else {
+    do {
+      snd_seq_event_t *event;
+      err = snd_seq_event_input (app->seq, &event);
+      if (err < 0)
+        break;
+      if (event) {
+        size_ev =
+            snd_midi_event_decode (app->parser, app->buffer, DEFAULT_BUFSIZE,
+            event);
+        if (size_ev < 0) {
+          /* ENOENT indicates an event that is not a MIDI message, silently skip it */
+          if (-ENOENT == size_ev) {
+            GST_WARNING ("Warning: Received non-MIDI message");
+            push_tick_buffer (app);
+          } else {
+            GST_ERROR ("Error decoding event from ALSA to output: %s",
+                strerror (-size_ev));
+            break;
+          }
+        } else {
+          push_buffer (app, app->buffer, size_ev);
+        }
+      }
+    } while (err > 0);
+  }
+
+  return;
+}
+
+/* this callback is called when pipeline has constructed a source object to read
+ * from. Since we provided the appsrc:// uri to pipeline, this will be the
+ * appsrc that we must handle. We set up a signals to push data into appsrc. */
+static void
+found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
+{
+  /* get a handle to the appsrc */
+  g_object_get (orig, pspec->name, &app->appsrc, NULL);
+
+  GST_DEBUG ("got appsrc %p", (gpointer) app->appsrc);
+
+  /* configure the appsrc, we will push a buffer to appsrc when it needs more
+   * data */
+  g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
+}
+
+static gboolean
+bus_message (GstBus * bus, GstMessage * message, App * app)
+{
+  GST_DEBUG ("got message %s",
+      gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
+
+  switch (GST_MESSAGE_TYPE (message)) {
+    case GST_MESSAGE_ERROR:
+    {
+      GError *error = NULL;
+      gchar *dbg_info = NULL;
+
+      gst_message_parse_error (message, &error, &dbg_info);
+      g_printerr ("ERROR from element %s: %s",
+          GST_OBJECT_NAME (message->src), error->message);
+      g_printerr ("Debugging info: %s", (dbg_info) ? dbg_info : "none");
+      g_error_free (error);
+      g_free (dbg_info);
+      g_main_loop_quit (app->loop);
+      break;
+    }
+    case GST_MESSAGE_EOS:
+      g_main_loop_quit (app->loop);
+      break;
+    default:
+      break;
+  }
+  return TRUE;
+}
+
+static int
+app_init (App * app, char *ports)
+{
+  int ret;
+
+  app->tick = 0;
+
+  ret = init_seq (app);
+  if (ret < 0)
+    goto err;
+
+  if (ports) {
+    ret = parse_ports (ports, app);
+    if (ret < 0)
+      goto err_seq_close;
+  }
+
+  ret = create_port (app);
+  if (ret < 0)
+    goto err_free_ports;
+
+  connect_ports (app);
+
+  ret = snd_seq_nonblock (app->seq, 1);
+  if (ret < 0) {
+    GST_ERROR ("Cannot set nonblock mode - %s", snd_strerror (ret));
+    goto err_free_ports;
+  }
+
+  snd_midi_event_new (DEFAULT_BUFSIZE, &app->parser);
+  snd_midi_event_init (app->parser);
+  snd_midi_event_reset_decode (app->parser);
+
+  snd_midi_event_no_status (app->parser, 1);
+
+  app->buffer = malloc (DEFAULT_BUFSIZE);
+  if (app->buffer == NULL) {
+    ret = -ENOMEM;
+    goto err_free_parser;
+  }
+
+  app->npfds = snd_seq_poll_descriptors_count (app->seq, POLLIN);
+  app->pfds = alloca (sizeof (*app->pfds) * app->npfds);
+  if (app->pfds == NULL) {
+    ret = -ENOMEM;
+    goto err_free_buffer;
+  }
+
+  return 0;
+
+err_free_buffer:
+  free (app->buffer);
+err_free_parser:
+  snd_midi_event_free (app->parser);
+err_free_ports:
+  free (app->ports);
+err_seq_close:
+  snd_seq_close (app->seq);
+err:
+  return ret;
+}
+
+static void
+app_finalize (App * app)
+{
+  /* free the resources */
+  free (app->buffer);
+  free (app->ports);
+  snd_midi_event_free (app->parser);
+  snd_seq_close (app->seq);
+}
+
+static gboolean
+on_sigint (gpointer user_data)
+{
+  GMainLoop *loop = (GMainLoop *) user_data;
+  g_message ("Caught SIGINT. Initiating shutdown.");
+  g_main_loop_quit (loop);
+  return FALSE;
+}
+
+int
+main (int argc, char *argv[])
+{
+  App *app = &s_app;
+  GstBus *bus;
+  GstCaps *caps;
+  int ret;
+
+  GOptionContext *ctx;
+  GError *err = NULL;
+  gchar *ports = NULL;
+  GOptionEntry options[] = {
+    {"ports", 'p', 0, G_OPTION_ARG_STRING, &ports,
+        "Comma separated list of sequencer ports", "client:port,..."},
+    {NULL}
+  };
+
+  ctx = g_option_context_new (NULL);
+  g_option_context_add_main_entries (ctx, options, NULL);
+  g_option_context_add_group (ctx, gst_init_get_option_group ());
+  if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
+    if (err)
+      g_printerr ("Error initializing: %s\n", GST_STR_NULL (err->message));
+    else
+      g_printerr ("Error initializing: Unknown error!\n");
+    exit (1);
+  }
+  g_option_context_free (ctx);
+
+  gst_init (&argc, &argv);
+
+  GST_DEBUG_CATEGORY_INIT (mysrc_debug, "mysrc", 0,
+      "ALSA MIDI sequencer appsrc pipeline");
+
+  ret = app_init (app, ports);
+  if (ret < 0)
+    return ret;
+  free (ports);
+
+  if (app->port_count > 0)
+    printf ("Waiting for data.\n");
+  else
+    printf ("Waiting for data at port %d:0.\n", snd_seq_client_id (app->seq));
+
+  /* create a mainloop to get messages */
+  app->loop = g_main_loop_new (NULL, FALSE);
+
+  app->pipeline =
+      gst_parse_launch
+      ("appsrc name=mysource ! fluiddec ! audioconvert ! autoaudiosink", NULL);
+  g_assert (app->pipeline);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
+  g_assert (bus);
+
+  /* add watch for messages */
+  gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
+
+  /* get the appsrc */
+  app->appsrc = gst_bin_get_by_name (GST_BIN (app->pipeline), "mysource");
+  g_assert (app->appsrc);
+  g_assert (GST_IS_APP_SRC (app->appsrc));
+  g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
+
+  g_object_set (app->appsrc, "format", GST_FORMAT_TIME, NULL);
+  g_object_set (app->appsrc, "is-live", TRUE, NULL);
+
+  /* set the caps on the source */
+  caps = gst_caps_new_simple ("audio/x-midi-event", NULL, NULL);
+  gst_app_src_set_caps (GST_APP_SRC (app->appsrc), caps);
+  gst_caps_unref (caps);
+
+  /* get notification when the source is created so that we get a handle to it
+   * and can configure it */
+  g_signal_connect (app->pipeline, "deep-notify::source",
+      (GCallback) found_source, app);
+
+  /* go to playing and wait in a mainloop. */
+  gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
+
+  /* this mainloop is stopped when we receive an error or EOS, or on SIGINT */
+  g_unix_signal_add (SIGINT, on_sigint, app->loop);
+  g_main_loop_run (app->loop);
+
+  g_main_loop_unref (app->loop);
+
+  GST_DEBUG ("stopping");
+
+  gst_element_set_state (app->pipeline, GST_STATE_NULL);
+
+  gst_object_unref (app->appsrc);
+
+  gst_object_unref (bus);
+
+  gst_object_unref (app->pipeline);
+
+  app_finalize (app);
+
+  return 0;
+}