3 * gst-aseq-appsrc: a GStreamer appsrc for the ALSA MIDI sequencer API
5 * Copyright (C) 2014 Antonio Ospite <ao2@ao2.it>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
23 /* Code _heavily_ inspired to aseqdump and amidicat:
24 * http://git.alsa-project.org/?p=alsa-utils.git;a=tree;f=seq/aseqdump
25 * http://krellan.com/amidicat/
29 #include <gst/app/gstappsrc.h>
33 #include <alsa/asoundlib.h>
34 #include <glib-unix.h>
36 #define DEFAULT_BUFSIZE 65536
37 #define DEFAULT_CLIENT_NAME "gst-aseq-appsrc"
38 #define DEFAULT_TICK_PERIOD_MS 10
39 #define DEFAULT_POLL_TIMEOUT_MS (DEFAULT_TICK_PERIOD_MS / 2)
41 GST_DEBUG_CATEGORY (mysrc_debug);
42 #define GST_CAT_DEFAULT mysrc_debug
44 typedef struct _App App;
55 snd_seq_addr_t *ports;
56 snd_midi_event_t *parser;
57 unsigned char *buffer;
72 ret = snd_seq_open (&app->seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
74 GST_ERROR ("Cannot open sequencer - %s", snd_strerror (ret));
78 ret = snd_seq_set_client_name (app->seq, DEFAULT_CLIENT_NAME);
80 GST_ERROR ("Cannot set client name - %s", snd_strerror (ret));
87 snd_seq_close (app->seq);
92 /* parses one or more port addresses from the string */
94 parse_ports (const char *arg, App * app)
96 char *buf, *s, *port_name;
99 GST_DEBUG ("ports: %s", arg);
101 /* make a copy of the string because we're going to modify it */
104 GST_ERROR ("Out of memory");
109 for (port_name = s = buf; s; port_name = s + 1) {
110 /* Assume that ports are separated by commas. We don't use
111 * spaces because those are valid in client names. */
112 s = strchr (port_name, ',');
118 realloc (app->ports, app->port_count * sizeof (snd_seq_addr_t));
120 GST_ERROR ("Out of memory");
126 snd_seq_parse_address (app->seq, &app->ports[app->port_count - 1],
129 GST_ERROR ("Invalid port %s - %s", port_name, snd_strerror (ret));
130 goto error_free_ports;
145 create_port (App * app)
149 ret = snd_seq_create_simple_port (app->seq, DEFAULT_CLIENT_NAME,
150 SND_SEQ_PORT_CAP_WRITE |
151 SND_SEQ_PORT_CAP_SUBS_WRITE,
152 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
154 GST_ERROR ("Cannot create port - %s", snd_strerror (ret));
160 connect_ports (App * app)
165 for (i = 0; i < app->port_count; ++i) {
167 snd_seq_connect_from (app->seq, 0, app->ports[i].client,
171 GST_WARNING ("Cannot connect from port %d:%d - %s",
172 app->ports[i].client, app->ports[i].port, snd_strerror (ret));
177 push_buffer (App * app, gpointer data, guint size)
184 /* read the next chunk */
185 buffer = gst_buffer_new ();
187 time = app->tick * DEFAULT_TICK_PERIOD_MS * GST_MSECOND;
189 GST_BUFFER_DTS (buffer) = time;
190 GST_BUFFER_PTS (buffer) = time;
191 GST_BUFFER_OFFSET (buffer) = time;
192 GST_BUFFER_DURATION (buffer) = DEFAULT_TICK_PERIOD_MS * GST_MSECOND;
194 local_data = g_malloc (size);
195 memcpy (local_data, data, size);
197 gst_buffer_append_memory (buffer,
198 gst_memory_new_wrapped (0, local_data, size, 0, size, local_data,
201 GST_DEBUG ("feed buffer %p, tick %" G_GUINT64_FORMAT " size: %u",
202 (gpointer) buffer, app->tick, size);
203 g_signal_emit_by_name (app->appsrc, "push-buffer", buffer, &ret);
204 gst_buffer_unref (buffer);
210 push_tick_buffer (App * app)
212 app->buffer[0] = 0xf9;
213 push_buffer (app, app->buffer, 1);
216 /* This method is called by the need-data signal callback, we feed data into the
220 feed_data (GstElement * appsrc, guint size, App * app)
226 snd_seq_poll_descriptors (app->seq, app->pfds, app->npfds, POLLIN);
227 ret = poll (app->pfds, app->npfds, DEFAULT_POLL_TIMEOUT_MS);
229 GST_ERROR ("ERROR in poll: %s", strerror (errno));
230 } else if (ret == 0) {
231 push_tick_buffer (app);
234 snd_seq_event_t *event;
235 err = snd_seq_event_input (app->seq, &event);
240 snd_midi_event_decode (app->parser, app->buffer, DEFAULT_BUFSIZE,
243 /* ENOENT indicates an event that is not a MIDI message, silently skip it */
244 if (-ENOENT == size_ev) {
245 GST_WARNING ("Warning: Received non-MIDI message");
246 push_tick_buffer (app);
248 GST_ERROR ("Error decoding event from ALSA to output: %s",
249 strerror (-size_ev));
253 push_buffer (app, app->buffer, size_ev);
262 /* this callback is called when pipeline has constructed a source object to read
263 * from. Since we provided the appsrc:// uri to pipeline, this will be the
264 * appsrc that we must handle. We set up a signals to push data into appsrc. */
266 found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
268 /* get a handle to the appsrc */
269 g_object_get (orig, pspec->name, &app->appsrc, NULL);
271 GST_DEBUG ("got appsrc %p", (gpointer) app->appsrc);
273 /* configure the appsrc, we will push a buffer to appsrc when it needs more
275 g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
279 bus_message (GstBus * bus, GstMessage * message, App * app)
281 GST_DEBUG ("got message %s",
282 gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
284 switch (GST_MESSAGE_TYPE (message)) {
285 case GST_MESSAGE_ERROR:
287 GError *error = NULL;
288 gchar *dbg_info = NULL;
290 gst_message_parse_error (message, &error, &dbg_info);
291 g_printerr ("ERROR from element %s: %s",
292 GST_OBJECT_NAME (message->src), error->message);
293 g_printerr ("Debugging info: %s", (dbg_info) ? dbg_info : "none");
294 g_error_free (error);
296 g_main_loop_quit (app->loop);
299 case GST_MESSAGE_EOS:
300 g_main_loop_quit (app->loop);
309 app_init (App * app, char *ports)
315 ret = init_seq (app);
320 ret = parse_ports (ports, app);
325 ret = create_port (app);
331 ret = snd_seq_nonblock (app->seq, 1);
333 GST_ERROR ("Cannot set nonblock mode - %s", snd_strerror (ret));
337 snd_midi_event_new (DEFAULT_BUFSIZE, &app->parser);
338 snd_midi_event_init (app->parser);
339 snd_midi_event_reset_decode (app->parser);
341 snd_midi_event_no_status (app->parser, 1);
343 app->buffer = malloc (DEFAULT_BUFSIZE);
344 if (app->buffer == NULL) {
346 goto err_free_parser;
349 app->npfds = snd_seq_poll_descriptors_count (app->seq, POLLIN);
350 app->pfds = alloca (sizeof (*app->pfds) * app->npfds);
351 if (app->pfds == NULL) {
353 goto err_free_buffer;
361 snd_midi_event_free (app->parser);
365 snd_seq_close (app->seq);
371 app_finalize (App * app)
373 /* free the resources */
376 snd_midi_event_free (app->parser);
377 snd_seq_close (app->seq);
381 on_sigint (gpointer user_data)
383 GMainLoop *loop = (GMainLoop *) user_data;
384 g_message ("Caught SIGINT. Initiating shutdown.");
385 g_main_loop_quit (loop);
390 main (int argc, char *argv[])
400 GOptionEntry options[] = {
401 {"ports", 'p', 0, G_OPTION_ARG_STRING, &ports,
402 "Comma separated list of sequencer ports", "client:port,..."},
406 ctx = g_option_context_new (NULL);
407 g_option_context_add_main_entries (ctx, options, NULL);
408 g_option_context_add_group (ctx, gst_init_get_option_group ());
409 if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
411 g_printerr ("Error initializing: %s\n", GST_STR_NULL (err->message));
413 g_printerr ("Error initializing: Unknown error!\n");
416 g_option_context_free (ctx);
418 gst_init (&argc, &argv);
420 GST_DEBUG_CATEGORY_INIT (mysrc_debug, "mysrc", 0,
421 "ALSA MIDI sequencer appsrc pipeline");
423 ret = app_init (app, ports);
428 if (app->port_count > 0)
429 printf ("Waiting for data.\n");
431 printf ("Waiting for data at port %d:0.\n", snd_seq_client_id (app->seq));
433 /* create a mainloop to get messages */
434 app->loop = g_main_loop_new (NULL, FALSE);
438 ("appsrc name=mysource ! fluiddec ! audioconvert ! autoaudiosink", NULL);
439 g_assert (app->pipeline);
441 bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
444 /* add watch for messages */
445 gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
448 app->appsrc = gst_bin_get_by_name (GST_BIN (app->pipeline), "mysource");
449 g_assert (app->appsrc);
450 g_assert (GST_IS_APP_SRC (app->appsrc));
451 g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
453 g_object_set (app->appsrc, "format", GST_FORMAT_TIME, NULL);
454 g_object_set (app->appsrc, "is-live", TRUE, NULL);
456 /* set the caps on the source */
457 caps = gst_caps_new_simple ("audio/x-midi-event", NULL, NULL);
458 gst_app_src_set_caps (GST_APP_SRC (app->appsrc), caps);
459 gst_caps_unref (caps);
461 /* get notification when the source is created so that we get a handle to it
462 * and can configure it */
463 g_signal_connect (app->pipeline, "deep-notify::source",
464 (GCallback) found_source, app);
466 /* go to playing and wait in a mainloop. */
467 gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
469 /* this mainloop is stopped when we receive an error or EOS, or on SIGINT */
470 g_unix_signal_add (SIGINT, on_sigint, app->loop);
471 g_main_loop_run (app->loop);
473 g_main_loop_unref (app->loop);
475 GST_DEBUG ("stopping");
477 gst_element_set_state (app->pipeline, GST_STATE_NULL);
479 gst_object_unref (app->appsrc);
481 gst_object_unref (bus);
483 gst_object_unref (app->pipeline);