2 * Copyright (C) 2014 Antonio Ospite <ao2@ao2.it>
4 * gstalsamidisrc.c: Source element for ALSA MIDI sequencer events
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
22 * SECTION:element-alsamidisrc
24 * @see_also: #GstPushSrc
26 * The alsamidisrc element is an element that fetches ALSA MIDI sequencer
27 * events and makes them available to elements understanding
28 * audio/x-midi-events in their sink pads.
30 * It can be used to generate notes from a MIDI input device.
32 * ## Example launch line
34 * gst-launch -v alsamidisrc ports=129:0 ! fluiddec ! audioconvert ! autoaudiosink
36 * This pipeline will listen for events from the sequencer device at port 129:0,
37 * and generate notes using the fluiddec element.
45 #include "gstalsamidisrc.h"
47 GST_DEBUG_CATEGORY_STATIC (gst_alsa_midi_src_debug);
48 #define GST_CAT_DEFAULT gst_alsa_midi_src_debug
51 * The MIDI specification declares some status bytes undefined:
53 * - 0xF4 System common - Undefined (Reserved)
54 * - 0xF5 System common - Undefined (Reserved)
55 * - 0xF9 System real-time - Undefined (Reserved)
56 * - 0xFD System real-time - Undefined (Reserved)
58 * See: http://www.midi.org/techspecs/midimessages.php#2
60 * Some other documents define status 0xf9 as a tick message with a period of
63 * - http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/tick.htm
64 * - http://www.sequencer.de/synth/index.php/MIDI_Format#0xf9_-_MIDI_Tick
66 * Even if non-standard it looks like this convention is quite widespread.
68 * For instance Fluidsynth uses 0xF9 as a "midi tick" message:
69 * http://sourceforge.net/p/fluidsynth/code-git/ci/master/tree/fluidsynth/src/midi/fluid_midi.h#l62
71 * And then so does the midiparse element in order to be compatible with
72 * Fluidsynth and the fluiddec element.
74 * Do the same to behave like midiparse.
76 #define MIDI_TICK 0xf9
77 #define MIDI_TICK_PERIOD_MS 10
79 /* Functions specific to the Alsa MIDI sequencer API */
81 #define DEFAULT_BUFSIZE 65536
82 #define DEFAULT_CLIENT_NAME "alsamidisrc"
85 init_seq (GstAlsaMidiSrc * alsamidisrc)
89 ret = snd_seq_open (&alsamidisrc->seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
91 GST_ERROR_OBJECT (alsamidisrc, "Cannot open sequencer - %s",
97 * Prevent Valgrind from reporting cached configuration as memory leaks, see:
98 * http://git.alsa-project.org/?p=alsa-lib.git;a=blob;f=MEMORY-LEAK;hb=HEAD
100 snd_config_update_free_global ();
102 ret = snd_seq_set_client_name (alsamidisrc->seq, DEFAULT_CLIENT_NAME);
104 GST_ERROR_OBJECT (alsamidisrc, "Cannot set client name - %s",
106 goto error_seq_close;
112 snd_seq_close (alsamidisrc->seq);
117 /* Parses one or more port addresses from the string */
119 parse_ports (const char *arg, GstAlsaMidiSrc * alsamidisrc)
125 GST_DEBUG_OBJECT (alsamidisrc, "ports: %s", arg);
128 * Assume that ports are separated by commas.
130 * Commas are used instead of spaces because spaces are valid in client
133 ports_list = g_strsplit (arg, ",", 0);
135 alsamidisrc->port_count = g_strv_length (ports_list);
136 alsamidisrc->seq_ports = g_try_new (snd_seq_addr_t, alsamidisrc->port_count);
137 if (!alsamidisrc->seq_ports) {
138 GST_ERROR_OBJECT (alsamidisrc, "Out of memory");
140 goto out_free_ports_list;
143 for (i = 0; i < alsamidisrc->port_count; i++) {
144 gchar *port_name = ports_list[i];
146 ret = snd_seq_parse_address (alsamidisrc->seq, &alsamidisrc->seq_ports[i],
149 GST_ERROR_OBJECT (alsamidisrc, "Invalid port %s - %s", port_name,
151 goto error_free_seq_ports;
155 goto out_free_ports_list;
157 error_free_seq_ports:
158 g_free (alsamidisrc->seq_ports);
160 g_strfreev (ports_list);
165 start_queue_timer (GstAlsaMidiSrc * alsamidisrc)
169 ret = snd_seq_start_queue (alsamidisrc->seq, alsamidisrc->queue, NULL);
171 GST_ERROR_OBJECT (alsamidisrc, "Timer event output error: %s",
176 ret = snd_seq_drain_output (alsamidisrc->seq);
178 GST_ERROR_OBJECT (alsamidisrc, "Drain output error: %s",
185 schedule_next_tick (GstAlsaMidiSrc * alsamidisrc)
188 snd_seq_real_time_t time;
191 snd_seq_ev_clear (&ev);
192 snd_seq_ev_set_source (&ev, 0);
193 snd_seq_ev_set_dest (&ev, snd_seq_client_id (alsamidisrc->seq), 0);
195 ev.type = SND_SEQ_EVENT_TICK;
197 alsamidisrc->tick += 1;
198 GST_TIME_TO_TIMESPEC (alsamidisrc->tick * MIDI_TICK_PERIOD_MS * GST_MSECOND,
201 snd_seq_ev_schedule_real (&ev, alsamidisrc->queue, 0, &time);
203 ret = snd_seq_event_output (alsamidisrc->seq, &ev);
205 GST_ERROR_OBJECT (alsamidisrc, "Event output error: %s",
208 ret = snd_seq_drain_output (alsamidisrc->seq);
210 GST_ERROR_OBJECT (alsamidisrc, "Event drain error: %s", snd_strerror (ret));
214 create_port (GstAlsaMidiSrc * alsamidisrc)
216 snd_seq_port_info_t *pinfo;
219 snd_seq_port_info_alloca (&pinfo);
220 snd_seq_port_info_set_name (pinfo, DEFAULT_CLIENT_NAME);
221 snd_seq_port_info_set_type (pinfo,
222 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
223 snd_seq_port_info_set_capability (pinfo,
224 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
226 ret = snd_seq_alloc_named_queue (alsamidisrc->seq, DEFAULT_CLIENT_NAME);
228 GST_ERROR_OBJECT (alsamidisrc, "Cannot allocate queue: %s",
234 * Sequencer queues are "per-system" entities, so it's important to remember
235 * the queue id to make sure alsamidisrc refers to this very one in future
236 * operations, and not to some other port created by another sequencer user.
238 alsamidisrc->queue = ret;
240 snd_seq_port_info_set_timestamping (pinfo, 1);
241 snd_seq_port_info_set_timestamp_real (pinfo, 1);
242 snd_seq_port_info_set_timestamp_queue (pinfo, alsamidisrc->queue);
244 ret = snd_seq_create_port (alsamidisrc->seq, pinfo);
246 GST_ERROR_OBJECT (alsamidisrc, "Cannot create port - %s",
252 * Conversely, it's not strictly necessary to remember the port id because
253 * ports are per-client and alsamidisrc is only creating one port (id = 0).
255 * If multiple ports were to be created, the ids could be retrieved with
258 * alsamidisrc->port = snd_seq_port_info_get_port(pinfo);
261 ret = start_queue_timer (alsamidisrc);
263 GST_ERROR_OBJECT (alsamidisrc, "Cannot start timer for queue: %d - %s",
264 alsamidisrc->queue, snd_strerror (ret));
270 connect_ports (GstAlsaMidiSrc * alsamidisrc)
275 for (i = 0; i < alsamidisrc->port_count; ++i) {
277 snd_seq_connect_from (alsamidisrc->seq, 0,
278 alsamidisrc->seq_ports[i].client, alsamidisrc->seq_ports[i].port);
280 /* Issue a warning and try the other ports */
281 GST_WARNING_OBJECT (alsamidisrc, "Cannot connect from port %d:%d - %s",
282 alsamidisrc->seq_ports[i].client, alsamidisrc->seq_ports[i].port,
287 /* GStreamer specific functions */
289 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
292 GST_STATIC_CAPS ("audio/x-midi-event"));
294 #define DEFAULT_PORTS NULL
304 GST_DEBUG_CATEGORY_INIT (gst_alsa_midi_src_debug, "alsamidisrc", 0, "alsamidisrc element");
305 #define gst_alsa_midi_src_parent_class parent_class
306 G_DEFINE_TYPE_WITH_CODE (GstAlsaMidiSrc, gst_alsa_midi_src, GST_TYPE_PUSH_SRC,
309 static void gst_alsa_midi_src_set_property (GObject * object, guint prop_id,
310 const GValue * value, GParamSpec * pspec);
311 static void gst_alsa_midi_src_get_property (GObject * object, guint prop_id,
312 GValue * value, GParamSpec * pspec);
314 static gboolean gst_alsa_midi_src_start (GstBaseSrc * basesrc);
315 static gboolean gst_alsa_midi_src_stop (GstBaseSrc * basesrc);
316 static gboolean gst_alsa_midi_src_unlock (GstBaseSrc * basesrc);
317 static gboolean gst_alsa_midi_src_unlock_stop (GstBaseSrc * basesrc);
318 static void gst_alsa_midi_src_state_changed (GstElement * element,
319 GstState oldstate, GstState newstate, GstState pending);
322 gst_alsa_midi_src_create (GstPushSrc * src, GstBuffer ** buf);
325 gst_alsa_midi_src_class_init (GstAlsaMidiSrcClass * klass)
327 GObjectClass *gobject_class;
328 GstElementClass *gstelement_class;
329 GstBaseSrcClass *gstbase_src_class;
330 GstPushSrcClass *gstpush_src_class;
332 gobject_class = G_OBJECT_CLASS (klass);
333 gstelement_class = GST_ELEMENT_CLASS (klass);
334 gstbase_src_class = GST_BASE_SRC_CLASS (klass);
335 gstpush_src_class = GST_PUSH_SRC_CLASS (klass);
337 gobject_class->set_property = gst_alsa_midi_src_set_property;
338 gobject_class->get_property = gst_alsa_midi_src_get_property;
340 g_object_class_install_property (gobject_class, PROP_PORTS,
341 g_param_spec_string ("ports", "Ports",
342 "Comma separated list of sequencer ports (e.g. client:port,...)",
343 DEFAULT_PORTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
345 gst_element_class_set_static_metadata (gstelement_class,
348 "Push ALSA MIDI sequencer events around", "Antonio Ospite <ao2@ao2.it>");
349 gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
351 gstbase_src_class->start = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_start);
352 gstbase_src_class->stop = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_stop);
353 gstbase_src_class->unlock = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_unlock);
354 gstbase_src_class->unlock_stop =
355 GST_DEBUG_FUNCPTR (gst_alsa_midi_src_unlock_stop);
356 gstpush_src_class->create = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_create);
357 gstelement_class->state_changed =
358 GST_DEBUG_FUNCPTR (gst_alsa_midi_src_state_changed);
362 gst_alsa_midi_src_init (GstAlsaMidiSrc * alsamidisrc)
364 alsamidisrc->ports = DEFAULT_PORTS;
366 gst_base_src_set_format (GST_BASE_SRC (alsamidisrc), GST_FORMAT_TIME);
367 gst_base_src_set_live (GST_BASE_SRC (alsamidisrc), TRUE);
371 gst_alsa_midi_src_set_property (GObject * object, guint prop_id,
372 const GValue * value, GParamSpec * pspec)
376 src = GST_ALSA_MIDI_SRC (object);
381 src->ports = g_value_dup_string (value);
384 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
390 gst_alsa_midi_src_get_property (GObject * object, guint prop_id, GValue * value,
395 g_return_if_fail (GST_IS_ALSA_MIDI_SRC (object));
397 src = GST_ALSA_MIDI_SRC (object);
401 g_value_set_string (value, src->ports);
404 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
410 push_buffer (GstAlsaMidiSrc * alsamidisrc, gpointer data, guint size,
411 GstClockTime time, GstBufferList * buffer_list)
416 buffer = gst_buffer_new ();
418 GST_BUFFER_DTS (buffer) = time;
419 GST_BUFFER_PTS (buffer) = time;
421 local_data = g_memdup (data, size);
423 gst_buffer_append_memory (buffer,
424 gst_memory_new_wrapped (0, local_data, size, 0, size, local_data,
427 GST_MEMDUMP_OBJECT (alsamidisrc, "MIDI data:", local_data, size);
429 gst_buffer_list_add (buffer_list, buffer);
433 push_tick_buffer (GstAlsaMidiSrc * alsamidisrc, GstClockTime time,
434 GstBufferList * buffer_list)
436 alsamidisrc->buffer[0] = MIDI_TICK;
437 push_buffer (alsamidisrc, alsamidisrc->buffer, 1, time, buffer_list);
441 gst_alsa_midi_src_create (GstPushSrc * src, GstBuffer ** buf)
443 GstAlsaMidiSrc *alsamidisrc;
444 GstBufferList *buffer_list;
451 alsamidisrc = GST_ALSA_MIDI_SRC (src);
453 buffer_list = gst_buffer_list_new ();
456 ret = gst_poll_wait (alsamidisrc->poll, GST_CLOCK_TIME_NONE);
458 if (ret < 0 && errno == EBUSY) {
459 GST_INFO_OBJECT (alsamidisrc, "flushing");
460 gst_buffer_list_unref (buffer_list);
461 return GST_FLOW_FLUSHING;
463 GST_ERROR_OBJECT (alsamidisrc, "ERROR in poll: %s", strerror (errno));
465 /* There are events available */
467 snd_seq_event_t *event;
468 err = snd_seq_event_input (alsamidisrc->seq, &event);
470 break; /* Processed all events */
473 time = GST_TIMESPEC_TO_TIME (event->time.time) - alsamidisrc->delay;
476 * Special handling is needed because decoding SND_SEQ_EVENT_TICK is
477 * not supported by alsa-lib.
479 if (event->type == SND_SEQ_EVENT_TICK) {
480 push_tick_buffer (alsamidisrc, time, buffer_list);
481 schedule_next_tick (alsamidisrc);
486 snd_midi_event_decode (alsamidisrc->parser, alsamidisrc->buffer,
487 DEFAULT_BUFSIZE, event);
489 /* ENOENT indicates an event that is not a MIDI message, silently skip it */
490 if (-ENOENT == size_ev) {
491 GST_WARNING_OBJECT (alsamidisrc,
492 "Warning: Received non-MIDI message");
495 GST_ERROR_OBJECT (alsamidisrc,
496 "Error decoding event from ALSA to output: %s",
497 strerror (-size_ev));
501 push_buffer (alsamidisrc, alsamidisrc->buffer, size_ev, time,
508 len = gst_buffer_list_length (buffer_list);
512 /* Pop the _last_ buffer in the list */
513 *buf = gst_buffer_copy (gst_buffer_list_get (buffer_list, len - 1));
514 gst_buffer_list_remove (buffer_list, len - 1, 1);
518 * If there are no more buffers left, free the list, otherwise push all the
519 * _previous_ buffers left in the list.
521 * The one popped above will be pushed last when this function returns.
524 gst_buffer_list_unref (buffer_list);
526 gst_pad_push_list (GST_BASE_SRC (src)->srcpad, buffer_list);
531 gst_buffer_list_unref (buffer_list);
532 return GST_FLOW_ERROR;
536 gst_alsa_midi_src_start (GstBaseSrc * basesrc)
538 GstAlsaMidiSrc *alsamidisrc;
541 alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
543 alsamidisrc->tick = 0;
544 alsamidisrc->port_count = 0;
546 ret = init_seq (alsamidisrc);
550 if (alsamidisrc->ports) {
551 ret = parse_ports (alsamidisrc->ports, alsamidisrc);
553 goto error_seq_close;
556 ret = create_port (alsamidisrc);
558 goto error_free_seq_ports;
560 connect_ports (alsamidisrc);
562 ret = snd_seq_nonblock (alsamidisrc->seq, 1);
564 GST_ERROR_OBJECT (alsamidisrc, "Cannot set nonblock mode - %s",
566 goto error_free_seq_ports;
569 snd_midi_event_new (DEFAULT_BUFSIZE, &alsamidisrc->parser);
570 snd_midi_event_init (alsamidisrc->parser);
571 snd_midi_event_reset_decode (alsamidisrc->parser);
573 snd_midi_event_no_status (alsamidisrc->parser, 1);
575 alsamidisrc->buffer = g_try_malloc (DEFAULT_BUFSIZE);
576 if (alsamidisrc->buffer == NULL)
577 goto error_free_parser;
583 npfds = snd_seq_poll_descriptors_count (alsamidisrc->seq, POLLIN);
584 pfds = g_newa (struct pollfd, npfds);
586 snd_seq_poll_descriptors (alsamidisrc->seq, pfds, npfds, POLLIN);
588 alsamidisrc->poll = gst_poll_new (TRUE);
589 for (i = 0; i < npfds; ++i) {
590 GstPollFD fd = GST_POLL_FD_INIT;
593 gst_poll_add_fd (alsamidisrc->poll, &fd);
594 gst_poll_fd_ctl_read (alsamidisrc->poll, &fd, TRUE);
595 gst_poll_fd_ctl_write (alsamidisrc->poll, &fd, FALSE);
602 snd_midi_event_free (alsamidisrc->parser);
603 error_free_seq_ports:
604 g_free (alsamidisrc->seq_ports);
606 snd_seq_close (alsamidisrc->seq);
612 gst_alsa_midi_src_stop (GstBaseSrc * basesrc)
614 GstAlsaMidiSrc *alsamidisrc;
616 alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
618 if (alsamidisrc->poll != NULL) {
619 gst_poll_free (alsamidisrc->poll);
620 alsamidisrc->poll = NULL;
622 g_free (alsamidisrc->ports);
623 g_free (alsamidisrc->buffer);
624 snd_midi_event_free (alsamidisrc->parser);
625 g_free (alsamidisrc->seq_ports);
626 snd_seq_close (alsamidisrc->seq);
632 gst_alsa_midi_src_unlock (GstBaseSrc * basesrc)
634 GstAlsaMidiSrc *alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
636 gst_poll_set_flushing (alsamidisrc->poll, TRUE);
641 gst_alsa_midi_src_unlock_stop (GstBaseSrc * basesrc)
643 GstAlsaMidiSrc *alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
645 gst_poll_set_flushing (alsamidisrc->poll, FALSE);
650 gst_alsa_midi_src_state_changed (GstElement * element, GstState oldstate,
651 GstState newstate, GstState pending)
653 GstAlsaMidiSrc *alsamidisrc;
655 alsamidisrc = GST_ALSA_MIDI_SRC (element);
657 if (newstate == GST_STATE_PLAYING) {
658 GstClockTime gst_time;
659 GstClockTime base_time;
660 GstClockTime running_time;
661 GstClockTime queue_time;
663 snd_seq_queue_status_t *status;
665 clock = gst_element_get_clock (element);
667 GST_WARNING_OBJECT (element, "No clock present");
670 gst_time = gst_clock_get_time (clock);
671 gst_object_unref (clock);
672 base_time = gst_element_get_base_time (element);
673 running_time = gst_time - base_time;
675 snd_seq_queue_status_malloc (&status);
676 snd_seq_get_queue_status (alsamidisrc->seq, alsamidisrc->queue, status);
678 GST_TIMESPEC_TO_TIME (*snd_seq_queue_status_get_real_time (status));
679 snd_seq_queue_status_free (status);
682 * The fact that the ALSA sequencer queue started before the pipeline
683 * transition to the PLAYING state ensures that the pipeline delay is
686 alsamidisrc->delay = queue_time - running_time;
688 if (alsamidisrc->tick == 0) {
689 schedule_next_tick (alsamidisrc);