+++ /dev/null
-/* GStreamer
- * Copyright (C) 2014 Antonio Ospite <ao2@ao2.it>
- *
- * alsamidisrc.c: Source element for ALSA MIDI sequencer events
- *
- * 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.
- */
-/**
- * SECTION:element-alsamidisrc
- * @see_also: #GstPushSrc
- *
- * The alsamidisrc element is an element that fetches ALSA MIDI sequencer
- * events and makes them available to elements understanding
- * audio/x-midi-events in their sink pads.
- *
- * It can be used to generate notes from a MIDI input device.
- *
- * <refsect2>
- * <title>Example launch line</title>
- * |[
- * gst-launch-1.0 -v alsamidisrc ports=129:0 ! fluiddec ! audioconvert ! autoaudiosink
- * ]| This pipeline will listen for events from the sequencer device at port 129:0,
- * and generate notes using the fluiddec element.
- *
- * |[
- * vkeybd & gst-launch-1.0 -v alsamidisrc ports=128:0 ! fluiddec ! audioconvert ! autoaudiosink
- * ]| Use a software midi keyboard to play fluiddec.
- * </refsect2>
- */
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include "alsamidisrc.h"
-
-GST_DEBUG_CATEGORY_STATIC (gst_alsa_midi_src_debug);
-#define GST_CAT_DEFAULT gst_alsa_midi_src_debug
-
-/*
- * The MIDI specification declares some status bytes undefined:
- *
- * - 0xF4 System common - Undefined (Reserved)
- * - 0xF5 System common - Undefined (Reserved)
- * - 0xF9 System real-time - Undefined (Reserved)
- * - 0xFD System real-time - Undefined (Reserved)
- *
- * See: http://www.midi.org/techspecs/midimessages.php#2
- *
- * Some other documents define status 0xf9 as a tick message with a period of
- * 10ms:
- *
- * - http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/tick.htm
- * - http://www.sequencer.de/synth/index.php/MIDI_Format#0xf9_-_MIDI_Tick
- *
- * Even if non-standard it looks like this convention is quite widespread.
- *
- * For instance Fluidsynth uses 0xF9 as a "midi tick" message:
- * http://sourceforge.net/p/fluidsynth/code-git/ci/master/tree/fluidsynth/src/midi/fluid_midi.h#l62
- *
- * And then so does the midiparse element in order to be compatible with
- * Fluidsynth and the fluiddec element.
- *
- * Do the same to behave like midiparse.
- */
-#define MIDI_TICK 0xf9
-#define MIDI_TICK_PERIOD_MS 10
-
-/* Functions specific to the Alsa MIDI sequencer API */
-
-#define DEFAULT_BUFSIZE 65536
-#define DEFAULT_CLIENT_NAME "alsamidisrc"
-#define DEFAULT_POLL_TIMEOUT_MS (MIDI_TICK_PERIOD_MS / 2)
-
-static int
-init_seq (GstAlsaMidiSrc * alsamidisrc)
-{
- int ret;
-
- ret = snd_seq_open (&alsamidisrc->seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
- if (ret < 0) {
- GST_ERROR_OBJECT (alsamidisrc, "Cannot open sequencer - %s",
- snd_strerror (ret));
- goto error;
- }
-
- ret = snd_seq_set_client_name (alsamidisrc->seq, DEFAULT_CLIENT_NAME);
- if (ret < 0) {
- GST_ERROR_OBJECT (alsamidisrc, "Cannot set client name - %s",
- snd_strerror (ret));
- goto error_seq_close;
- }
-
- return 0;
-
-error_seq_close:
- snd_seq_close (alsamidisrc->seq);
-error:
- return ret;
-}
-
-/* Parses one or more port addresses from the string */
-static int
-parse_ports (const char *arg, GstAlsaMidiSrc * alsamidisrc)
-{
- gchar **ports_list;
- guint i;
- int ret = 0;
-
- GST_DEBUG_OBJECT (alsamidisrc, "ports: %s", arg);
-
- /*
- * Assume that ports are separated by commas.
- *
- * Commas are used instead of spaces because those are valid in client
- * names.
- */
- ports_list = g_strsplit (arg, ",", 0);
-
- alsamidisrc->port_count = g_strv_length (ports_list);
- alsamidisrc->seq_ports = g_try_new (snd_seq_addr_t, alsamidisrc->port_count);
- if (!alsamidisrc->seq_ports) {
- GST_ERROR_OBJECT (alsamidisrc, "Out of memory");
- ret = -ENOMEM;
- goto out_free_ports_list;
- }
-
- for (i = 0; i < alsamidisrc->port_count; i++) {
- gchar *port_name = ports_list[i];
-
- ret = snd_seq_parse_address (alsamidisrc->seq, &alsamidisrc->seq_ports[i],
- port_name);
- if (ret < 0) {
- GST_ERROR_OBJECT (alsamidisrc, "Invalid port %s - %s", port_name,
- snd_strerror (ret));
- goto error_free_seq_ports;
- }
- }
-
- goto out_free_ports_list;
-
-error_free_seq_ports:
- g_free (alsamidisrc->seq_ports);
-out_free_ports_list:
- g_strfreev (ports_list);
- return ret;
-}
-
-static int
-create_port (GstAlsaMidiSrc * alsamidisrc)
-{
- int ret;
-
- ret = snd_seq_create_simple_port (alsamidisrc->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_OBJECT (alsamidisrc, "Cannot create port - %s",
- snd_strerror (ret));
-
- return ret;
-}
-
-static void
-connect_ports (GstAlsaMidiSrc * alsamidisrc)
-{
- int i;
- int ret;
-
- for (i = 0; i < alsamidisrc->port_count; ++i) {
- ret =
- snd_seq_connect_from (alsamidisrc->seq, 0,
- alsamidisrc->seq_ports[i].client, alsamidisrc->seq_ports[i].port);
- if (ret < 0)
- /* Issue a warning and try the other ports */
- GST_WARNING_OBJECT (alsamidisrc, "Cannot connect from port %d:%d - %s",
- alsamidisrc->seq_ports[i].client, alsamidisrc->seq_ports[i].port,
- snd_strerror (ret));
- }
-}
-
-/* GStreamer specific functions */
-
-static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
- GST_PAD_SRC,
- GST_PAD_ALWAYS,
- GST_STATIC_CAPS ("audio/x-midi-event"));
-
-#define DEFAULT_PORTS NULL
-
-enum
-{
- PROP_0,
- PROP_PORTS,
- PROP_LAST,
-};
-
-#define _do_init \
- GST_DEBUG_CATEGORY_INIT (gst_alsa_midi_src_debug, "alsamidisrc", 0, "alsamidisrc element");
-#define gst_alsa_midi_src_parent_class parent_class
-G_DEFINE_TYPE_WITH_CODE (GstAlsaMidiSrc, gst_alsa_midi_src, GST_TYPE_PUSH_SRC,
- _do_init);
-
-static void gst_alsa_midi_src_set_property (GObject * object, guint prop_id,
- const GValue * value, GParamSpec * pspec);
-static void gst_alsa_midi_src_get_property (GObject * object, guint prop_id,
- GValue * value, GParamSpec * pspec);
-
-static gboolean gst_alsa_midi_src_start (GstBaseSrc * basesrc);
-static gboolean gst_alsa_midi_src_stop (GstBaseSrc * basesrc);
-
-static GstFlowReturn
-gst_alsa_midi_src_create (GstPushSrc * src, GstBuffer ** buf);
-
-static void
-gst_alsa_midi_src_class_init (GstAlsaMidiSrcClass * klass)
-{
- GObjectClass *gobject_class;
- GstElementClass *gstelement_class;
- GstBaseSrcClass *gstbase_src_class;
- GstPushSrcClass *gstpush_src_class;
-
- gobject_class = G_OBJECT_CLASS (klass);
- gstelement_class = GST_ELEMENT_CLASS (klass);
- gstbase_src_class = GST_BASE_SRC_CLASS (klass);
- gstpush_src_class = GST_PUSH_SRC_CLASS (klass);
-
- gobject_class->set_property = gst_alsa_midi_src_set_property;
- gobject_class->get_property = gst_alsa_midi_src_get_property;
-
- g_object_class_install_property (gobject_class, PROP_PORTS,
- g_param_spec_string ("ports", "Ports",
- "Comma separated list of sequencer ports (e.g. client:port,...)",
- DEFAULT_PORTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
- gst_element_class_set_static_metadata (gstelement_class,
- "AlsaMidi Source",
- "Source",
- "Push ALSA MIDI sequencer events around", "Antonio Ospite <ao2@ao2.it>");
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&srctemplate));
-
- gstbase_src_class->start = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_start);
- gstbase_src_class->stop = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_stop);
- gstpush_src_class->create = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_create);
-}
-
-static void
-gst_alsa_midi_src_init (GstAlsaMidiSrc * alsamidisrc)
-{
- alsamidisrc->ports = DEFAULT_PORTS;
-
- gst_base_src_set_format (GST_BASE_SRC (alsamidisrc), GST_FORMAT_TIME);
- gst_base_src_set_live (GST_BASE_SRC (alsamidisrc), TRUE);
-}
-
-static void
-gst_alsa_midi_src_set_property (GObject * object, guint prop_id,
- const GValue * value, GParamSpec * pspec)
-{
- GstAlsaMidiSrc *src;
-
- src = GST_ALSA_MIDI_SRC (object);
-
- switch (prop_id) {
- case PROP_PORTS:
- g_free (src->ports);
- src->ports = g_value_dup_string (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-gst_alsa_midi_src_get_property (GObject * object, guint prop_id, GValue * value,
- GParamSpec * pspec)
-{
- GstAlsaMidiSrc *src;
-
- g_return_if_fail (GST_IS_ALSA_MIDI_SRC (object));
-
- src = GST_ALSA_MIDI_SRC (object);
-
- switch (prop_id) {
- case PROP_PORTS:
- g_value_set_string (value, src->ports);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static GstBuffer *
-prepare_buffer (GstAlsaMidiSrc * alsamidisrc, gpointer data, guint size)
-{
- GstClockTime time;
- gpointer local_data;
- GstBuffer *buffer;
-
- buffer = gst_buffer_new ();
-
- time = alsamidisrc->tick * MIDI_TICK_PERIOD_MS * GST_MSECOND;
-
- GST_BUFFER_DTS (buffer) = time;
- GST_BUFFER_PTS (buffer) = time;
- GST_BUFFER_OFFSET (buffer) = time;
- GST_BUFFER_DURATION (buffer) = MIDI_TICK_PERIOD_MS * GST_MSECOND;
-
- local_data = g_memdup (data, size);
-
- gst_buffer_append_memory (buffer,
- gst_memory_new_wrapped (0, local_data, size, 0, size, local_data,
- g_free));
-
- GST_MEMDUMP_OBJECT (alsamidisrc, "MIDI data:", local_data, size);
-
- alsamidisrc->tick += 1;
-
- return buffer;
-}
-
-static void
-push_buffer (GstAlsaMidiSrc * alsamidisrc, gpointer data, guint size,
- GstBufferList * buffer_list)
-{
- gst_buffer_list_add (buffer_list, prepare_buffer (alsamidisrc, data, size));
-}
-
-static void
-push_tick_buffer (GstAlsaMidiSrc * alsamidisrc, GstBufferList * buffer_list)
-{
- alsamidisrc->buffer[0] = MIDI_TICK;
- push_buffer (alsamidisrc, alsamidisrc->buffer, 1, buffer_list);
-}
-
-static GstFlowReturn
-gst_alsa_midi_src_create (GstPushSrc * src, GstBuffer ** buf)
-{
- GstAlsaMidiSrc *alsamidisrc;
- GstBufferList *buffer_list;
- long size_ev = 0;
- int err;
- int ret;
- guint len;
-
- alsamidisrc = GST_ALSA_MIDI_SRC (src);
-
- buffer_list = gst_buffer_list_new ();
-
- snd_seq_poll_descriptors (alsamidisrc->seq, alsamidisrc->pfds,
- alsamidisrc->npfds, POLLIN);
-
- /*
- * The file descriptors are polled with a timeout _less_ than 10ms (the MIDI
- * tick period) in order not to loose events because of possible overlaps
- * with MIDI ticks.
- *
- * If the polling times out (no new events) then a MIDI-tick event gets
- * generated in order to keep the pipeline alive and progressing.
- *
- * If new events are present, then they are decoded and queued in
- * a buffer_list. One buffer per event will be queued, all with different
- * timestamps (see the prepare_buffer() function); maybe this can be
- * optimized but a as a proof-of-concept mechanism it works OK.
- */
- ret = poll (alsamidisrc->pfds, alsamidisrc->npfds, DEFAULT_POLL_TIMEOUT_MS);
- if (ret < 0) {
- GST_ERROR_OBJECT (alsamidisrc, "ERROR in poll: %s", strerror (errno));
- } else if (ret == 0) {
- push_tick_buffer (alsamidisrc, buffer_list);
- } else {
- /* There are events available */
- do {
- snd_seq_event_t *event;
- err = snd_seq_event_input (alsamidisrc->seq, &event);
- if (err < 0)
- break; /* Processed all events */
-
- if (event) {
- size_ev =
- snd_midi_event_decode (alsamidisrc->parser, alsamidisrc->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_OBJECT (alsamidisrc,
- "Warning: Received non-MIDI message");
- push_tick_buffer (alsamidisrc, buffer_list);
- } else {
- GST_ERROR_OBJECT (alsamidisrc,
- "Error decoding event from ALSA to output: %s",
- strerror (-size_ev));
- goto error;
- }
- } else {
- push_buffer (alsamidisrc, alsamidisrc->buffer, size_ev, buffer_list);
- }
- }
- } while (err > 0);
- }
-
- len = gst_buffer_list_length (buffer_list);
- if (len == 0)
- goto error;
-
- /* Pop the _last_ buffer in the list */
- *buf = gst_buffer_copy (gst_buffer_list_get (buffer_list, len - 1));
- gst_buffer_list_remove (buffer_list, len - 1, 1);
- --len;
-
- /*
- * If there are no more buffers left, free the list, otherwise push all the
- * _previous_ buffers left in the list.
- *
- * The one popped above will be pushed last when this function returns.
- */
- if (len == 0)
- gst_buffer_list_unref (buffer_list);
- else
- gst_pad_push_list (GST_BASE_SRC (src)->srcpad, buffer_list);
-
- return GST_FLOW_OK;
-
-error:
- gst_buffer_list_unref (buffer_list);
- return GST_FLOW_ERROR;
-}
-
-static gboolean
-gst_alsa_midi_src_start (GstBaseSrc * basesrc)
-{
- GstAlsaMidiSrc *alsamidisrc;
- int ret;
-
- alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
-
- alsamidisrc->tick = 0;
- alsamidisrc->port_count = 0;
-
- ret = init_seq (alsamidisrc);
- if (ret < 0)
- goto err;
-
- if (alsamidisrc->ports) {
- ret = parse_ports (alsamidisrc->ports, alsamidisrc);
- if (ret < 0)
- goto error_seq_close;
- }
-
- ret = create_port (alsamidisrc);
- if (ret < 0)
- goto error_free_seq_ports;
-
- connect_ports (alsamidisrc);
-
- ret = snd_seq_nonblock (alsamidisrc->seq, 1);
- if (ret < 0) {
- GST_ERROR_OBJECT (alsamidisrc, "Cannot set nonblock mode - %s",
- snd_strerror (ret));
- goto error_free_seq_ports;
- }
-
- snd_midi_event_new (DEFAULT_BUFSIZE, &alsamidisrc->parser);
- snd_midi_event_init (alsamidisrc->parser);
- snd_midi_event_reset_decode (alsamidisrc->parser);
-
- snd_midi_event_no_status (alsamidisrc->parser, 1);
-
- alsamidisrc->buffer = g_try_malloc (DEFAULT_BUFSIZE);
- if (alsamidisrc->buffer == NULL)
- goto error_free_parser;
-
- alsamidisrc->npfds =
- snd_seq_poll_descriptors_count (alsamidisrc->seq, POLLIN);
- alsamidisrc->pfds =
- g_try_malloc (sizeof (*alsamidisrc->pfds) * alsamidisrc->npfds);
- if (alsamidisrc->pfds == NULL)
- goto error_free_buffer;
-
- return TRUE;
-
-error_free_buffer:
- g_free (alsamidisrc->buffer);
-error_free_parser:
- snd_midi_event_free (alsamidisrc->parser);
-error_free_seq_ports:
- g_free (alsamidisrc->seq_ports);
-error_seq_close:
- snd_seq_close (alsamidisrc->seq);
-err:
- return FALSE;
-}
-
-static gboolean
-gst_alsa_midi_src_stop (GstBaseSrc * basesrc)
-{
- GstAlsaMidiSrc *alsamidisrc;
-
- alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
-
- g_free (alsamidisrc->pfds);
- g_free (alsamidisrc->buffer);
- snd_midi_event_free (alsamidisrc->parser);
- g_free (alsamidisrc->seq_ports);
- snd_seq_close (alsamidisrc->seq);
-
- return TRUE;
-}