From: Antonio Ospite Date: Wed, 15 Oct 2014 13:39:28 +0000 (+0100) Subject: Initial import X-Git-Url: https://git.ao2.it/gst-aseq-appsrc.git/commitdiff_plain/f3c9cf5e37824ba42c01df0cc6d36234fef64a0c Initial import --- f3c9cf5e37824ba42c01df0cc6d36234fef64a0c diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..b4f6c39 --- /dev/null +++ b/gst-aseq-appsrc.c @@ -0,0 +1,488 @@ +/* GStreamer + * + * gst-aseq-appsrc: a GStreamer appsrc for the ALSA MIDI sequencer API + * + * Copyright (C) 2014 Antonio Ospite + * + * 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 +#include + +#include +#include +#include +#include + +#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; +}