506409e0b008bc96fe5178f458b04117a851daed
[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 (mysource_debug);
42 #define GST_CAT_DEFAULT mysource_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 *seq_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 static 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   /*
79    * Prevent Valgrind from reporting cached configuration as memory leaks, see:
80    * http://git.alsa-project.org/?p=alsa-lib.git;a=blob;f=MEMORY-LEAK;hb=HEAD
81    */
82   snd_config_update_free_global();
83
84   ret = snd_seq_set_client_name (app->seq, DEFAULT_CLIENT_NAME);
85   if (ret < 0) {
86     GST_ERROR ("Cannot set client name - %s", snd_strerror (ret));
87     goto error_seq_close;
88   }
89
90   return 0;
91
92 error_seq_close:
93   snd_seq_close (app->seq);
94 error:
95   return ret;
96 }
97
98 /* Parses one or more port addresses from the string */
99 static int
100 parse_ports (const char *arg, App * app)
101 {
102   gchar **ports_list;
103   guint i;
104   int ret = 0;
105
106   GST_DEBUG ("ports: %s", arg);
107
108   /*
109    * Assume that ports are separated by commas.
110    *
111    * Commas are used instead of spaces because those are valid in client
112    * names.
113    */
114   ports_list = g_strsplit (arg, ",", 0);
115
116   app->port_count = g_strv_length (ports_list);
117   app->seq_ports = g_try_new (snd_seq_addr_t, app->port_count);
118   if (!app->seq_ports) {
119     GST_ERROR ("Out of memory");
120     ret = -ENOMEM;
121     goto out_free_ports_list;
122   }
123
124   for (i = 0; i < (guint)app->port_count; i++) {
125     gchar *port_name = ports_list[i];
126
127     ret = snd_seq_parse_address (app->seq, &app->seq_ports[i],
128         port_name);
129     if (ret < 0) {
130       GST_ERROR ("Invalid port %s - %s", port_name,
131           snd_strerror (ret));
132       goto error_free_seq_ports;
133     }
134   }
135
136   goto out_free_ports_list;
137
138 error_free_seq_ports:
139   g_free (app->seq_ports);
140 out_free_ports_list:
141   g_strfreev (ports_list);
142   return ret;
143 }
144
145 static int
146 create_port (App * app)
147 {
148   int ret;
149
150   ret = snd_seq_create_simple_port (app->seq, DEFAULT_CLIENT_NAME,
151       SND_SEQ_PORT_CAP_WRITE |
152       SND_SEQ_PORT_CAP_SUBS_WRITE,
153       SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
154   if (ret < 0)
155     GST_ERROR ("Cannot create port - %s", snd_strerror (ret));
156
157   return ret;
158 }
159
160 static void
161 connect_ports (App * app)
162 {
163   int i;
164   int ret;
165
166   for (i = 0; i < app->port_count; ++i) {
167     ret =
168         snd_seq_connect_from (app->seq, 0, app->seq_ports[i].client,
169         app->seq_ports[i].port);
170     if (ret < 0)
171       /* warning */
172       GST_WARNING ("Cannot connect from port %d:%d - %s",
173           app->seq_ports[i].client, app->seq_ports[i].port, snd_strerror (ret));
174   }
175 }
176
177 static void
178 push_buffer (App * app, gpointer data, guint size)
179 {
180   GstClockTime time;
181   gpointer local_data;
182   GstBuffer *buffer;
183   int ret;
184
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
192   local_data = g_memdup (data, size);
193
194   gst_buffer_append_memory (buffer,
195       gst_memory_new_wrapped (0, local_data, size, 0, size, local_data,
196           g_free));
197
198   GST_MEMDUMP ("MIDI data:", local_data, size);
199
200   GST_DEBUG ("feed buffer %p, tick %" G_GUINT64_FORMAT " size: %u",
201       (gpointer) buffer, app->tick, size);
202   g_signal_emit_by_name (app->appsrc, "push-buffer", buffer, &ret);
203   gst_buffer_unref (buffer);
204
205   app->tick += 1;
206 }
207
208 static void
209 push_tick_buffer (App * app)
210 {
211   app->buffer[0] = 0xf9;
212   push_buffer (app, app->buffer, 1);
213 }
214
215 /* This method is called by the need-data signal callback, we feed data into the
216  * appsrc.
217  */
218 static void
219 feed_data (GstElement * appsrc, guint size, App * app)
220 {
221   long size_ev = 0;
222   int err;
223   int ret;
224
225   snd_seq_poll_descriptors (app->seq, app->pfds, app->npfds, POLLIN);
226   ret = poll (app->pfds, app->npfds, DEFAULT_POLL_TIMEOUT_MS);
227   if (ret < 0) {
228     GST_ERROR ("ERROR in poll: %s", strerror (errno));
229     gst_app_src_end_of_stream (GST_APP_SRC (appsrc));
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 && err != -EAGAIN) {
237         GST_ERROR ("Error in snd_seq_event_input: %s", snd_strerror (err));
238         gst_app_src_end_of_stream (GST_APP_SRC (appsrc));
239         break;
240       }
241       if (event) {
242         size_ev =
243             snd_midi_event_decode (app->parser, app->buffer, DEFAULT_BUFSIZE,
244             event);
245         if (size_ev < 0) {
246           /* ENOENT indicates an event that is not a MIDI message, silently skip it */
247           if (-ENOENT == size_ev) {
248             GST_WARNING ("Warning: Received non-MIDI message");
249             push_tick_buffer (app);
250           } else {
251             GST_ERROR ("Error decoding event from ALSA to output: %s",
252                 strerror (-size_ev));
253             gst_app_src_end_of_stream (GST_APP_SRC (appsrc));
254             break;
255           }
256         } else {
257           push_buffer (app, app->buffer, size_ev);
258         }
259       }
260     } while (err > 0);
261   }
262
263   return;
264 }
265
266 /* this callback is called when pipeline has constructed a source object to read
267  * from. Since we provided the appsrc:// uri to pipeline, this will be the
268  * appsrc that we must handle. We set up a signals to push data into appsrc. */
269 static void
270 found_source (GObject * object, GObject * orig, GParamSpec * pspec, App * app)
271 {
272   /* get a handle to the appsrc */
273   g_object_get (orig, pspec->name, &app->appsrc, NULL);
274
275   GST_DEBUG ("got appsrc %p", (gpointer) app->appsrc);
276
277   /* configure the appsrc, we will push a buffer to appsrc when it needs more
278    * data */
279   g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
280 }
281
282 static gboolean
283 bus_message (GstBus * bus, GstMessage * message, App * app)
284 {
285   GST_DEBUG ("got message %s",
286       gst_message_type_get_name (GST_MESSAGE_TYPE (message)));
287
288   switch (GST_MESSAGE_TYPE (message)) {
289     case GST_MESSAGE_ERROR:
290     {
291       GError *error = NULL;
292       gchar *dbg_info = NULL;
293
294       gst_message_parse_error (message, &error, &dbg_info);
295       g_printerr ("ERROR from element %s: %s",
296           GST_OBJECT_NAME (message->src), error->message);
297       g_printerr ("Debugging info: %s", (dbg_info) ? dbg_info : "none");
298       g_error_free (error);
299       g_free (dbg_info);
300       g_main_loop_quit (app->loop);
301       break;
302     }
303     case GST_MESSAGE_EOS:
304       g_main_loop_quit (app->loop);
305       break;
306     default:
307       break;
308   }
309   return TRUE;
310 }
311
312 static int
313 app_init (App * app, char *ports)
314 {
315   int ret;
316
317   app->tick = 0;
318
319   ret = init_seq (app);
320   if (ret < 0)
321     goto err;
322
323   if (ports) {
324     ret = parse_ports (ports, app);
325     if (ret < 0)
326       goto err_seq_close;
327   }
328
329   ret = create_port (app);
330   if (ret < 0)
331     goto err_free_ports;
332
333   connect_ports (app);
334
335   ret = snd_seq_nonblock (app->seq, 1);
336   if (ret < 0) {
337     GST_ERROR ("Cannot set nonblock mode - %s", snd_strerror (ret));
338     goto err_free_ports;
339   }
340
341   snd_midi_event_new (DEFAULT_BUFSIZE, &app->parser);
342   snd_midi_event_init (app->parser);
343   snd_midi_event_reset_decode (app->parser);
344
345   snd_midi_event_no_status (app->parser, 1);
346
347   app->buffer = malloc (DEFAULT_BUFSIZE);
348   if (app->buffer == NULL) {
349     ret = -ENOMEM;
350     goto err_free_parser;
351   }
352
353   app->npfds = snd_seq_poll_descriptors_count (app->seq, POLLIN);
354   app->pfds = malloc (sizeof (*app->pfds) * app->npfds);
355   if (app->pfds == NULL) {
356     ret = -ENOMEM;
357     goto err_free_buffer;
358   }
359
360   return 0;
361
362 err_free_buffer:
363   free (app->buffer);
364 err_free_parser:
365   snd_midi_event_free (app->parser);
366 err_free_ports:
367   g_free (app->seq_ports);
368 err_seq_close:
369   snd_seq_close (app->seq);
370 err:
371   return ret;
372 }
373
374 static void
375 app_finalize (App * app)
376 {
377   /* free the resources */
378   free (app->pfds);
379   free (app->buffer);
380   snd_midi_event_free (app->parser);
381   g_free (app->seq_ports);
382   snd_seq_close (app->seq);
383 }
384
385 static gboolean
386 on_sigint (gpointer user_data)
387 {
388   GMainLoop *loop = (GMainLoop *) user_data;
389   g_message ("Caught SIGINT. Initiating shutdown.");
390   g_main_loop_quit (loop);
391   return FALSE;
392 }
393
394 int
395 main (int argc, char *argv[])
396 {
397   App *app = &s_app;
398   GstBus *bus;
399   GstCaps *caps;
400   int ret;
401
402   GOptionContext *ctx;
403   GError *err = NULL;
404   gchar *ports = NULL;
405   gboolean verbose = FALSE;
406   GOptionEntry options[] = {
407     {"ports", 'p', 0, G_OPTION_ARG_STRING, &ports,
408         "Comma separated list of sequencer ports", "client:port,..."},
409     {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
410          "Output status information and property notifications", NULL},
411     {NULL}
412   };
413
414   ctx = g_option_context_new (NULL);
415   g_option_context_add_main_entries (ctx, options, NULL);
416   g_option_context_add_group (ctx, gst_init_get_option_group ());
417   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
418     if (err)
419       g_printerr ("Error initializing: %s\n", GST_STR_NULL (err->message));
420     else
421       g_printerr ("Error initializing: Unknown error!\n");
422     exit (1);
423   }
424   g_option_context_free (ctx);
425
426   gst_init (&argc, &argv);
427
428   GST_DEBUG_CATEGORY_INIT (mysource_debug, "mysource", 0,
429       "ALSA MIDI sequencer appsrc pipeline");
430
431   ret = app_init (app, ports);
432   if (ret < 0)
433     return ret;
434   free (ports);
435
436   if (app->port_count > 0)
437     printf ("Waiting for data.\n");
438   else
439     printf ("Waiting for data at port %d:0.\n", snd_seq_client_id (app->seq));
440
441   /* create a mainloop to get messages */
442   app->loop = g_main_loop_new (NULL, FALSE);
443
444   app->pipeline =
445       gst_parse_launch
446       ("appsrc name=mysource ! fluiddec ! audioconvert ! autoaudiosink", NULL);
447   g_assert (app->pipeline);
448
449   if (verbose)
450     g_signal_connect (app->pipeline, "deep-notify", G_CALLBACK (gst_object_default_deep_notify), NULL);
451
452   bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
453   g_assert (bus);
454
455   /* add watch for messages */
456   gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);
457
458   /* get the appsrc */
459   app->appsrc = gst_bin_get_by_name (GST_BIN (app->pipeline), "mysource");
460   g_assert (app->appsrc);
461   g_assert (GST_IS_APP_SRC (app->appsrc));
462   g_signal_connect (app->appsrc, "need-data", G_CALLBACK (feed_data), app);
463
464   g_object_set (app->appsrc, "format", GST_FORMAT_TIME, NULL);
465   g_object_set (app->appsrc, "is-live", TRUE, NULL);
466
467   /* set the caps on the source */
468   caps = gst_caps_new_simple ("audio/x-midi-event", NULL, NULL);
469   gst_app_src_set_caps (GST_APP_SRC (app->appsrc), caps);
470   gst_caps_unref (caps);
471
472   /* get notification when the source is created so that we get a handle to it
473    * and can configure it */
474   g_signal_connect (app->pipeline, "deep-notify::source",
475       (GCallback) found_source, app);
476
477   /* go to playing and wait in a mainloop. */
478   gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
479
480   /* this mainloop is stopped when we receive an error or EOS, or on SIGINT */
481   g_unix_signal_add (SIGINT, on_sigint, app->loop);
482   g_main_loop_run (app->loop);
483
484   g_main_loop_unref (app->loop);
485
486   GST_DEBUG ("stopping");
487
488   gst_element_set_state (app->pipeline, GST_STATE_NULL);
489
490   gst_object_unref (app->appsrc);
491
492   gst_object_unref (bus);
493
494   gst_object_unref (app->pipeline);
495
496   app_finalize (app);
497
498   return 0;
499 }