2015-10-01 19:43:21 +00:00
|
|
|
/* GStreamer
|
|
|
|
* Copyright (C) 2014 Antonio Ospite <ao2@ao2.it>
|
|
|
|
*
|
|
|
|
* gstalsamidisrc.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
|
2017-01-23 19:36:11 +00:00
|
|
|
* @title: alsamidisrc
|
2015-10-01 19:43:21 +00:00
|
|
|
* @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.
|
|
|
|
*
|
2017-01-23 19:36:11 +00:00
|
|
|
* ## Example launch line
|
2015-10-01 19:43:21 +00:00
|
|
|
* |[
|
|
|
|
* gst-launch -v alsamidisrc ports=129:0 ! fluiddec ! audioconvert ! autoaudiosink
|
2017-01-23 19:36:11 +00:00
|
|
|
* ]|
|
|
|
|
* This pipeline will listen for events from the sequencer device at port 129:0,
|
2015-10-01 19:43:21 +00:00
|
|
|
* and generate notes using the fluiddec element.
|
2017-01-23 19:36:11 +00:00
|
|
|
*
|
2015-10-01 19:43:21 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "gstalsamidisrc.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"
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-09-25 13:14:45 +00:00
|
|
|
/*
|
|
|
|
* Prevent Valgrind from reporting cached configuration as memory leaks, see:
|
|
|
|
* http://git.alsa-project.org/?p=alsa-lib.git;a=blob;f=MEMORY-LEAK;hb=HEAD
|
|
|
|
*/
|
|
|
|
snd_config_update_free_global ();
|
|
|
|
|
2015-10-01 19:43:21 +00:00
|
|
|
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.
|
|
|
|
*
|
2017-10-09 16:50:23 +00:00
|
|
|
* Commas are used instead of spaces because spaces are valid in client
|
2015-10-01 19:43:21 +00:00
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2017-10-04 20:31:23 +00:00
|
|
|
static int
|
|
|
|
start_queue_timer (GstAlsaMidiSrc * alsamidisrc)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = snd_seq_start_queue (alsamidisrc->seq, alsamidisrc->queue, NULL);
|
|
|
|
if (ret < 0) {
|
2019-12-11 09:07:12 +00:00
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "Timer event output error: %s",
|
2017-10-04 20:31:23 +00:00
|
|
|
snd_strerror (ret));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snd_seq_drain_output (alsamidisrc->seq);
|
|
|
|
if (ret < 0)
|
2019-12-11 09:07:12 +00:00
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "Drain output error: %s",
|
2017-10-04 20:31:23 +00:00
|
|
|
snd_strerror (ret));
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-10-05 10:10:50 +00:00
|
|
|
static void
|
|
|
|
schedule_next_tick (GstAlsaMidiSrc * alsamidisrc)
|
|
|
|
{
|
|
|
|
snd_seq_event_t ev;
|
|
|
|
snd_seq_real_time_t time;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
snd_seq_ev_clear (&ev);
|
|
|
|
snd_seq_ev_set_source (&ev, 0);
|
|
|
|
snd_seq_ev_set_dest (&ev, snd_seq_client_id (alsamidisrc->seq), 0);
|
|
|
|
|
|
|
|
ev.type = SND_SEQ_EVENT_TICK;
|
|
|
|
|
|
|
|
alsamidisrc->tick += 1;
|
|
|
|
GST_TIME_TO_TIMESPEC (alsamidisrc->tick * MIDI_TICK_PERIOD_MS * GST_MSECOND,
|
|
|
|
time);
|
|
|
|
|
2020-08-04 12:17:35 +00:00
|
|
|
snd_seq_ev_schedule_real (&ev, alsamidisrc->queue, 0, &time);
|
2017-10-05 10:10:50 +00:00
|
|
|
|
|
|
|
ret = snd_seq_event_output (alsamidisrc->seq, &ev);
|
|
|
|
if (ret < 0)
|
2019-12-11 09:07:12 +00:00
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "Event output error: %s",
|
2017-10-05 10:10:50 +00:00
|
|
|
snd_strerror (ret));
|
|
|
|
|
|
|
|
ret = snd_seq_drain_output (alsamidisrc->seq);
|
|
|
|
if (ret < 0)
|
2019-12-11 09:07:12 +00:00
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "Event drain error: %s", snd_strerror (ret));
|
2017-10-05 10:10:50 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 19:43:21 +00:00
|
|
|
static int
|
|
|
|
create_port (GstAlsaMidiSrc * alsamidisrc)
|
|
|
|
{
|
2017-10-04 20:31:23 +00:00
|
|
|
snd_seq_port_info_t *pinfo;
|
2015-10-01 19:43:21 +00:00
|
|
|
int ret;
|
|
|
|
|
2017-10-04 20:31:23 +00:00
|
|
|
snd_seq_port_info_alloca (&pinfo);
|
|
|
|
snd_seq_port_info_set_name (pinfo, DEFAULT_CLIENT_NAME);
|
|
|
|
snd_seq_port_info_set_type (pinfo,
|
2015-10-01 19:43:21 +00:00
|
|
|
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
|
2017-10-04 20:31:23 +00:00
|
|
|
snd_seq_port_info_set_capability (pinfo,
|
|
|
|
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
|
|
|
|
|
|
|
|
ret = snd_seq_alloc_named_queue (alsamidisrc->seq, DEFAULT_CLIENT_NAME);
|
|
|
|
if (ret < 0) {
|
2019-12-11 09:07:12 +00:00
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "Cannot allocate queue: %s",
|
2017-10-04 20:31:23 +00:00
|
|
|
snd_strerror (ret));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sequencer queues are "per-system" entities, so it's important to remember
|
|
|
|
* the queue id to make sure alsamidisrc refers to this very one in future
|
|
|
|
* operations, and not to some other port created by another sequencer user.
|
|
|
|
*/
|
|
|
|
alsamidisrc->queue = ret;
|
|
|
|
|
|
|
|
snd_seq_port_info_set_timestamping (pinfo, 1);
|
|
|
|
snd_seq_port_info_set_timestamp_real (pinfo, 1);
|
|
|
|
snd_seq_port_info_set_timestamp_queue (pinfo, alsamidisrc->queue);
|
|
|
|
|
|
|
|
ret = snd_seq_create_port (alsamidisrc->seq, pinfo);
|
|
|
|
if (ret < 0) {
|
2015-10-01 19:43:21 +00:00
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "Cannot create port - %s",
|
|
|
|
snd_strerror (ret));
|
2017-10-04 20:31:23 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Conversely, it's not strictly necessary to remember the port id because
|
|
|
|
* ports are per-client and alsamidisrc is only creating one port (id = 0).
|
|
|
|
*
|
|
|
|
* If multiple ports were to be created, the ids could be retrieved with
|
|
|
|
* something like:
|
|
|
|
*
|
|
|
|
* alsamidisrc->port = snd_seq_port_info_get_port(pinfo);
|
|
|
|
*/
|
|
|
|
|
|
|
|
ret = start_queue_timer (alsamidisrc);
|
|
|
|
if (ret < 0)
|
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "Cannot start timer for queue: %d - %s",
|
|
|
|
alsamidisrc->queue, snd_strerror (ret));
|
2015-10-01 19:43:21 +00:00
|
|
|
|
|
|
|
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);
|
2017-12-04 09:49:35 +00:00
|
|
|
static gboolean gst_alsa_midi_src_unlock (GstBaseSrc * basesrc);
|
|
|
|
static gboolean gst_alsa_midi_src_unlock_stop (GstBaseSrc * basesrc);
|
2017-10-05 10:10:50 +00:00
|
|
|
static void gst_alsa_midi_src_state_changed (GstElement * element,
|
|
|
|
GstState oldstate, GstState newstate, GstState pending);
|
2015-10-01 19:43:21 +00:00
|
|
|
|
|
|
|
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>");
|
2016-03-03 07:46:24 +00:00
|
|
|
gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
|
2015-10-01 19:43:21 +00:00
|
|
|
|
|
|
|
gstbase_src_class->start = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_start);
|
|
|
|
gstbase_src_class->stop = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_stop);
|
2017-12-04 09:49:35 +00:00
|
|
|
gstbase_src_class->unlock = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_unlock);
|
|
|
|
gstbase_src_class->unlock_stop =
|
|
|
|
GST_DEBUG_FUNCPTR (gst_alsa_midi_src_unlock_stop);
|
2015-10-01 19:43:21 +00:00
|
|
|
gstpush_src_class->create = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_create);
|
2017-10-05 10:10:50 +00:00
|
|
|
gstelement_class->state_changed =
|
|
|
|
GST_DEBUG_FUNCPTR (gst_alsa_midi_src_state_changed);
|
2015-10-01 19:43:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-04 18:43:50 +00:00
|
|
|
static void
|
|
|
|
push_buffer (GstAlsaMidiSrc * alsamidisrc, gpointer data, guint size,
|
2017-10-05 10:10:50 +00:00
|
|
|
GstClockTime time, GstBufferList * buffer_list)
|
2015-10-01 19:43:21 +00:00
|
|
|
{
|
|
|
|
gpointer local_data;
|
|
|
|
GstBuffer *buffer;
|
|
|
|
|
|
|
|
buffer = gst_buffer_new ();
|
|
|
|
|
|
|
|
GST_BUFFER_DTS (buffer) = time;
|
|
|
|
GST_BUFFER_PTS (buffer) = time;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2017-10-04 18:43:50 +00:00
|
|
|
gst_buffer_list_add (buffer_list, buffer);
|
2015-10-01 19:43:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-10-05 10:10:50 +00:00
|
|
|
push_tick_buffer (GstAlsaMidiSrc * alsamidisrc, GstClockTime time,
|
|
|
|
GstBufferList * buffer_list)
|
2015-10-01 19:43:21 +00:00
|
|
|
{
|
|
|
|
alsamidisrc->buffer[0] = MIDI_TICK;
|
2017-10-05 10:10:50 +00:00
|
|
|
push_buffer (alsamidisrc, alsamidisrc->buffer, 1, time, buffer_list);
|
2015-10-01 19:43:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static GstFlowReturn
|
|
|
|
gst_alsa_midi_src_create (GstPushSrc * src, GstBuffer ** buf)
|
|
|
|
{
|
|
|
|
GstAlsaMidiSrc *alsamidisrc;
|
|
|
|
GstBufferList *buffer_list;
|
2017-10-05 10:10:50 +00:00
|
|
|
GstClockTime time;
|
2015-10-01 19:43:21 +00:00
|
|
|
long size_ev = 0;
|
|
|
|
int err;
|
|
|
|
int ret;
|
|
|
|
guint len;
|
|
|
|
|
|
|
|
alsamidisrc = GST_ALSA_MIDI_SRC (src);
|
|
|
|
|
|
|
|
buffer_list = gst_buffer_list_new ();
|
|
|
|
|
2017-10-05 10:10:50 +00:00
|
|
|
poll:
|
2017-12-04 09:49:35 +00:00
|
|
|
ret = gst_poll_wait (alsamidisrc->poll, GST_CLOCK_TIME_NONE);
|
2017-10-05 10:10:50 +00:00
|
|
|
if (ret <= 0) {
|
2017-12-04 09:49:35 +00:00
|
|
|
if (ret < 0 && errno == EBUSY) {
|
|
|
|
GST_INFO_OBJECT (alsamidisrc, "flushing");
|
2018-04-12 20:23:50 +00:00
|
|
|
gst_buffer_list_unref (buffer_list);
|
2017-12-04 09:49:35 +00:00
|
|
|
return GST_FLOW_FLUSHING;
|
|
|
|
}
|
2015-10-01 19:43:21 +00:00
|
|
|
GST_ERROR_OBJECT (alsamidisrc, "ERROR in poll: %s", strerror (errno));
|
|
|
|
} 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) {
|
2017-10-05 10:10:50 +00:00
|
|
|
time = GST_TIMESPEC_TO_TIME (event->time.time) - alsamidisrc->delay;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Special handling is needed because decoding SND_SEQ_EVENT_TICK is
|
|
|
|
* not supported by alsa-lib.
|
|
|
|
*/
|
|
|
|
if (event->type == SND_SEQ_EVENT_TICK) {
|
|
|
|
push_tick_buffer (alsamidisrc, time, buffer_list);
|
|
|
|
schedule_next_tick (alsamidisrc);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-10-01 19:43:21 +00:00
|
|
|
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");
|
2017-10-05 10:10:50 +00:00
|
|
|
goto poll;
|
2015-10-01 19:43:21 +00:00
|
|
|
} else {
|
|
|
|
GST_ERROR_OBJECT (alsamidisrc,
|
|
|
|
"Error decoding event from ALSA to output: %s",
|
|
|
|
strerror (-size_ev));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
} else {
|
2017-10-05 10:10:50 +00:00
|
|
|
push_buffer (alsamidisrc, alsamidisrc->buffer, size_ev, time,
|
|
|
|
buffer_list);
|
2015-10-01 19:43:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
|
2017-10-09 16:50:23 +00:00
|
|
|
/*
|
2015-10-01 19:43:21 +00:00
|
|
|
* 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;
|
|
|
|
|
2017-12-04 09:49:35 +00:00
|
|
|
{
|
|
|
|
struct pollfd *pfds;
|
|
|
|
int npfds, i;
|
|
|
|
|
|
|
|
npfds = snd_seq_poll_descriptors_count (alsamidisrc->seq, POLLIN);
|
|
|
|
pfds = g_newa (struct pollfd, npfds);
|
|
|
|
|
|
|
|
snd_seq_poll_descriptors (alsamidisrc->seq, pfds, npfds, POLLIN);
|
|
|
|
|
|
|
|
alsamidisrc->poll = gst_poll_new (TRUE);
|
|
|
|
for (i = 0; i < npfds; ++i) {
|
|
|
|
GstPollFD fd = GST_POLL_FD_INIT;
|
|
|
|
|
|
|
|
fd.fd = pfds[i].fd;
|
|
|
|
gst_poll_add_fd (alsamidisrc->poll, &fd);
|
|
|
|
gst_poll_fd_ctl_read (alsamidisrc->poll, &fd, TRUE);
|
|
|
|
gst_poll_fd_ctl_write (alsamidisrc->poll, &fd, FALSE);
|
|
|
|
}
|
|
|
|
}
|
2015-10-01 19:43:21 +00:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2017-12-04 09:49:35 +00:00
|
|
|
if (alsamidisrc->poll != NULL) {
|
|
|
|
gst_poll_free (alsamidisrc->poll);
|
|
|
|
alsamidisrc->poll = NULL;
|
|
|
|
}
|
2017-10-04 17:41:58 +00:00
|
|
|
g_free (alsamidisrc->ports);
|
2015-10-01 19:43:21 +00:00
|
|
|
g_free (alsamidisrc->buffer);
|
|
|
|
snd_midi_event_free (alsamidisrc->parser);
|
|
|
|
g_free (alsamidisrc->seq_ports);
|
|
|
|
snd_seq_close (alsamidisrc->seq);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
2017-10-05 10:10:50 +00:00
|
|
|
|
2017-12-04 09:49:35 +00:00
|
|
|
static gboolean
|
|
|
|
gst_alsa_midi_src_unlock (GstBaseSrc * basesrc)
|
|
|
|
{
|
|
|
|
GstAlsaMidiSrc *alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
|
|
|
|
|
|
|
|
gst_poll_set_flushing (alsamidisrc->poll, TRUE);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_alsa_midi_src_unlock_stop (GstBaseSrc * basesrc)
|
|
|
|
{
|
|
|
|
GstAlsaMidiSrc *alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
|
|
|
|
|
|
|
|
gst_poll_set_flushing (alsamidisrc->poll, FALSE);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2017-10-05 10:10:50 +00:00
|
|
|
static void
|
|
|
|
gst_alsa_midi_src_state_changed (GstElement * element, GstState oldstate,
|
|
|
|
GstState newstate, GstState pending)
|
|
|
|
{
|
|
|
|
GstAlsaMidiSrc *alsamidisrc;
|
|
|
|
|
|
|
|
alsamidisrc = GST_ALSA_MIDI_SRC (element);
|
|
|
|
|
|
|
|
if (newstate == GST_STATE_PLAYING) {
|
|
|
|
GstClockTime gst_time;
|
|
|
|
GstClockTime base_time;
|
|
|
|
GstClockTime running_time;
|
|
|
|
GstClockTime queue_time;
|
|
|
|
GstClock *clock;
|
|
|
|
snd_seq_queue_status_t *status;
|
|
|
|
|
|
|
|
clock = gst_element_get_clock (element);
|
2017-11-30 09:46:44 +00:00
|
|
|
if (clock == NULL) {
|
|
|
|
GST_WARNING_OBJECT (element, "No clock present");
|
|
|
|
return;
|
|
|
|
}
|
2017-10-05 10:10:50 +00:00
|
|
|
gst_time = gst_clock_get_time (clock);
|
|
|
|
gst_object_unref (clock);
|
|
|
|
base_time = gst_element_get_base_time (element);
|
|
|
|
running_time = gst_time - base_time;
|
|
|
|
|
|
|
|
snd_seq_queue_status_malloc (&status);
|
|
|
|
snd_seq_get_queue_status (alsamidisrc->seq, alsamidisrc->queue, status);
|
|
|
|
queue_time =
|
|
|
|
GST_TIMESPEC_TO_TIME (*snd_seq_queue_status_get_real_time (status));
|
|
|
|
snd_seq_queue_status_free (status);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The fact that the ALSA sequencer queue started before the pipeline
|
|
|
|
* transition to the PLAYING state ensures that the pipeline delay is
|
|
|
|
* always positive.
|
|
|
|
*/
|
|
|
|
alsamidisrc->delay = queue_time - running_time;
|
|
|
|
|
|
|
|
if (alsamidisrc->tick == 0) {
|
|
|
|
schedule_next_tick (alsamidisrc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|