Initial import
[gst-aseq-appsrc.git] / gst-aseq-appsrc.c
1 /* GStreamer
2  *
3  * gst-aseq-appsrc: a GStreamer appsrc for the ALSA MIDI sequencer API
4  *
5  * Copyright (C) 2014  Antonio Ospite <ao2@ao2.it>
6  *
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.
11  *
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.
16  *
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.
21  */
22
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/
26  */
27
28 #include <gst/gst.h>
29 #include <gst/app/gstappsrc.h>
30
31 #include <string.h>
32 #include <sys/poll.h>
33 #include <alsa/asoundlib.h>
34 #include <glib-unix.h>
35
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)
40
41 GST_DEBUG_CATEGORY (mysrc_debug);
42 #define GST_CAT_DEFAULT mysrc_debug
43
44 typedef struct _App App;
45
46 struct _App
47 {
48   GstElement *pipeline;
49   GstElement *appsrc;
50
51   GMainLoop *loop;
52
53   snd_seq_t *seq;
54   int port_count;
55   snd_seq_addr_t *ports;
56   snd_midi_event_t *parser;
57   unsigned char *buffer;
58
59   struct pollfd *pfds;
60   int npfds;
61
62   guint64 tick;
63 };
64
65 App s_app;
66
67 static int
68 init_seq (App * app)
69 {
70   int ret;
71
72   ret = snd_seq_open (&app->seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
73   if (ret < 0) {
74     GST_ERROR ("Cannot open sequencer - %s", snd_strerror (ret));
75     goto error;
76   }
77
78   ret = snd_seq_set_client_name (app->seq, DEFAULT_CLIENT_NAME);
79   if (ret < 0) {
80     GST_ERROR ("Cannot set client name - %s", snd_strerror (ret));
81     goto error_seq_close;
82   }
83
84   return 0;
85
86 error_seq_close:
87   snd_seq_close (app->seq);
88 error:
89   return ret;
90 }
91
92 /* parses one or more port addresses from the string */
93 static int
94 parse_ports (const char *arg, App * app)
95 {
96   char *buf, *s, *port_name;
97   int ret;
98
99   GST_DEBUG ("ports: %s", arg);
100
101   /* make a copy of the string because we're going to modify it */
102   buf = strdup (arg);
103   if (!buf) {
104     GST_ERROR ("Out of memory");
105     ret = -ENOMEM;
106     goto out;
107   }
108
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, ',');
113     if (s)
114       *s = '\0';
115
116     app->port_count++;
117     app->ports =
118         realloc (app->ports, app->port_count * sizeof (snd_seq_addr_t));
119     if (!app->ports) {
120       GST_ERROR ("Out of memory");
121       ret = -ENOMEM;
122       goto out_free_buf;
123     }
124
125     ret =
126         snd_seq_parse_address (app->seq, &app->ports[app->port_count - 1],
127         port_name);
128     if (ret < 0) {
129       GST_ERROR ("Invalid port %s - %s", port_name, snd_strerror (ret));
130       goto error_free_ports;
131     }
132   }
133
134   goto out_free_buf;
135
136 error_free_ports:
137   free (app->ports);
138 out_free_buf:
139   free (buf);
140 out:
141   return ret;
142 }
143
144 static int
145 create_port (App * app)
146 {
147   int ret;
148
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);
153   if (ret < 0)
154     GST_ERROR ("Cannot create port - %s", snd_strerror (ret));
155
156   return ret;
157 }
158
159 static void
160 connect_ports (App * app)
161 {
162   int i;
163   int ret;
164
165   for (i = 0; i < app->port_count; ++i) {
166     ret =
167         snd_seq_connect_from (app->seq, 0, app->ports[i].client,
168         app->ports[i].port);
169     if (ret < 0)
170       /* warning */
171       GST_WARNING ("Cannot connect from port %d:%d - %s",
172           app->ports[i].client, app->ports[i].port, snd_strerror (ret));
173   }
174 }
175
176 static void
177 push_buffer (App * app, gpointer data, guint size)
178 {
179   GstBuffer *buffer;
180   GstClockTime time;
181   int ret;
182   gpointer local_data;
183
184   /* read the next chunk */
185   buffer = gst_buffer_new ();
186
187   time = app->tick * DEFAULT_TICK_PERIOD_MS * GST_MSECOND;
188
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;
193
194   local_data = g_malloc (size);
195   memcpy (local_data, data, size);
196
197   gst_buffer_append_memory (buffer,
198       gst_memory_new_wrapped (0, local_data, size, 0, size, local_data,
199           g_free));
200
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);
205
206   app->tick += 1;
207 }
208
209 static void
210 push_tick_buffer (App * app)
211 {
212   app->buffer[0] = 0xf9;
213   push_buffer (app, app->buffer, 1);
214 }
215
216 /* This method is called by the need-data signal callback, we feed data into the
217  * appsrc.
218  */
219 static void
220 feed_data (GstElement * appsrc, guint size, App * app)
221 {
222   long size_ev = 0;
223   int err;
224   int ret;
225
226   snd_seq_poll_descriptors (app->seq, app->pfds, app->npfds, POLLIN);
227   ret = poll (app->pfds, app->npfds, DEFAULT_POLL_TIMEOUT_MS);
228   if (ret < 0) {
229     GST_ERROR ("ERROR in poll: %s", strerror (errno));
230   } else if (ret == 0) {
231     push_tick_buffer (app);
232   } else {
233     do {
234       snd_seq_event_t *event;
235       err = snd_seq_event_input (app->seq, &event);
236       if (err < 0)
237         break;
238       if (event) {
239         size_ev =
240             snd_midi_event_decode (app->parser, app->buffer, DEFAULT_BUFSIZE,
241             event);
242         if (size_ev < 0) {
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);
247           } else {
248             GST_ERROR ("Error decoding event from ALSA to output: %s",
249                 strerror (-size_ev));
250             break;
251           }
252         } else {
253           push_buffer (app, app->buffer, size_ev);
254         }
255       }
256     } while (err > 0);
257   }
258
259   return;
260 }
261
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. */
265 static void
266 found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
267 {
268   /* get a handle to the appsrc */
269   g_object_get (orig, pspec->name, &app->appsrc, NULL);
270
271   GST_DEBUG ("got appsrc %p", (gpointer) app->appsrc);
272
273   /* configure the appsrc, we will push a buffer to appsrc when it needs more
274    * data */
275   g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
276 }
277
278 static gboolean
279 bus_message (GstBus * bus, GstMessage * message, App * app)
280 {
281   GST_DEBUG ("got message %s",
282       gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
283
284   switch (GST_MESSAGE_TYPE (message)) {
285     case GST_MESSAGE_ERROR:
286     {
287       GError *error = NULL;
288       gchar *dbg_info = NULL;
289
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);
295       g_free (dbg_info);
296       g_main_loop_quit (app->loop);
297       break;
298     }
299     case GST_MESSAGE_EOS:
300       g_main_loop_quit (app->loop);
301       break;
302     default:
303       break;
304   }
305   return TRUE;
306 }
307
308 static int
309 app_init (App * app, char *ports)
310 {
311   int ret;
312
313   app->tick = 0;
314
315   ret = init_seq (app);
316   if (ret < 0)
317     goto err;
318
319   if (ports) {
320     ret = parse_ports (ports, app);
321     if (ret < 0)
322       goto err_seq_close;
323   }
324
325   ret = create_port (app);
326   if (ret < 0)
327     goto err_free_ports;
328
329   connect_ports (app);
330
331   ret = snd_seq_nonblock (app->seq, 1);
332   if (ret < 0) {
333     GST_ERROR ("Cannot set nonblock mode - %s", snd_strerror (ret));
334     goto err_free_ports;
335   }
336
337   snd_midi_event_new (DEFAULT_BUFSIZE, &app->parser);
338   snd_midi_event_init (app->parser);
339   snd_midi_event_reset_decode (app->parser);
340
341   snd_midi_event_no_status (app->parser, 1);
342
343   app->buffer = malloc (DEFAULT_BUFSIZE);
344   if (app->buffer == NULL) {
345     ret = -ENOMEM;
346     goto err_free_parser;
347   }
348
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) {
352     ret = -ENOMEM;
353     goto err_free_buffer;
354   }
355
356   return 0;
357
358 err_free_buffer:
359   free (app->buffer);
360 err_free_parser:
361   snd_midi_event_free (app->parser);
362 err_free_ports:
363   free (app->ports);
364 err_seq_close:
365   snd_seq_close (app->seq);
366 err:
367   return ret;
368 }
369
370 static void
371 app_finalize (App * app)
372 {
373   /* free the resources */
374   free (app->buffer);
375   free (app->ports);
376   snd_midi_event_free (app->parser);
377   snd_seq_close (app->seq);
378 }
379
380 static gboolean
381 on_sigint (gpointer user_data)
382 {
383   GMainLoop *loop = (GMainLoop *) user_data;
384   g_message ("Caught SIGINT. Initiating shutdown.");
385   g_main_loop_quit (loop);
386   return FALSE;
387 }
388
389 int
390 main (int argc, char *argv[])
391 {
392   App *app = &s_app;
393   GstBus *bus;
394   GstCaps *caps;
395   int ret;
396
397   GOptionContext *ctx;
398   GError *err = NULL;
399   gchar *ports = NULL;
400   GOptionEntry options[] = {
401     {"ports", 'p', 0, G_OPTION_ARG_STRING, &ports,
402         "Comma separated list of sequencer ports", "client:port,..."},
403     {NULL}
404   };
405
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)) {
410     if (err)
411       g_printerr ("Error initializing: %s\n", GST_STR_NULL (err->message));
412     else
413       g_printerr ("Error initializing: Unknown error!\n");
414     exit (1);
415   }
416   g_option_context_free (ctx);
417
418   gst_init (&argc, &argv);
419
420   GST_DEBUG_CATEGORY_INIT (mysrc_debug, "mysrc", 0,
421       "ALSA MIDI sequencer appsrc pipeline");
422
423   ret = app_init (app, ports);
424   if (ret < 0)
425     return ret;
426   free (ports);
427
428   if (app->port_count > 0)
429     printf ("Waiting for data.\n");
430   else
431     printf ("Waiting for data at port %d:0.\n", snd_seq_client_id (app->seq));
432
433   /* create a mainloop to get messages */
434   app->loop = g_main_loop_new (NULL, FALSE);
435
436   app->pipeline =
437       gst_parse_launch
438       ("appsrc name=mysource ! fluiddec ! audioconvert ! autoaudiosink", NULL);
439   g_assert (app->pipeline);
440
441   bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
442   g_assert (bus);
443
444   /* add watch for messages */
445   gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
446
447   /* get the appsrc */
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);
452
453   g_object_set (app->appsrc, "format", GST_FORMAT_TIME, NULL);
454   g_object_set (app->appsrc, "is-live", TRUE, NULL);
455
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);
460
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);
465
466   /* go to playing and wait in a mainloop. */
467   gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
468
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);
472
473   g_main_loop_unref (app->loop);
474
475   GST_DEBUG ("stopping");
476
477   gst_element_set_state (app->pipeline, GST_STATE_NULL);
478
479   gst_object_unref (app->appsrc);
480
481   gst_object_unref (bus);
482
483   gst_object_unref (app->pipeline);
484
485   app_finalize (app);
486
487   return 0;
488 }