/* -*- Mode: C; c-basic-offset: 4 -*- */ /* Copyright (C) 2002, 2003 Andy Wingo This library is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "gstjack.h" #include /* TODO: - work out the src side (caps setting, etc) future core TODO: - make a jack clock provider - add GST_ELEMENT_FIXED_DATA_RATE, GST_ELEMENT_QOS, GST_ELEMENT_CHANGES_DATA_RATE element flags, and make the scheduler sensitive to them */ /* elementfactory information */ static GstElementDetails gst_jack_bin_details = { "Jack Bin", "Generic/Bin", "Jack processing bin", "Andy Wingo ", }; static GstElementDetails gst_jack_sink_details = { "Jack Sink", "Sink/Audio", "Output to a Jack processing network", "Andy Wingo ", }; static GstElementDetails gst_jack_src_details = { "Jack Src", "Source/Audio", "Input from a Jack processing network", "Andy Wingo ", }; static GHashTable *port_name_counts = NULL; static GstElementClass *parent_class = NULL; static void gst_jack_base_init (gpointer g_class); static void gst_jack_src_base_init (gpointer g_class); static void gst_jack_sink_base_init (gpointer g_class); static void gst_jack_init(GstJack *this); static void gst_jack_class_init(GstJackClass *klass); static void gst_jack_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gst_jack_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static GstPadTemplate* gst_jack_src_request_pad_factory(); static GstPadTemplate* gst_jack_sink_request_pad_factory(); static GstPad* gst_jack_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name); static GstElementStateReturn gst_jack_change_state (GstElement *element); static GstPadLinkReturn gst_jack_link (GstPad *pad, GstCaps *caps); static void gst_jack_loop (GstElement *element); enum { ARG_0, ARG_PORT_NAME_PREFIX, }; GType gst_jack_get_type (void) { static GType jack_type = 0; if (!jack_type) { static const GTypeInfo jack_info = { sizeof(GstJackClass), gst_jack_base_init, NULL, NULL, NULL, NULL, sizeof(GstJack), 0, NULL, }; jack_type = g_type_register_static (GST_TYPE_ELEMENT, "GstJack", &jack_info, 0); } return jack_type; } GType gst_jack_sink_get_type (void) { static GType jack_type = 0; if (!jack_type) { static const GTypeInfo jack_info = { sizeof(GstJackClass), gst_jack_sink_base_init, NULL, (GClassInitFunc)gst_jack_class_init, NULL, NULL, sizeof(GstJack), 0, (GInstanceInitFunc)gst_jack_init, }; jack_type = g_type_register_static (GST_TYPE_JACK, "GstJackSink", &jack_info, 0); } return jack_type; } GType gst_jack_src_get_type (void) { static GType jack_type = 0; if (!jack_type) { static const GTypeInfo jack_info = { sizeof(GstJackClass), gst_jack_src_base_init, NULL, (GClassInitFunc)gst_jack_class_init, NULL, NULL, sizeof(GstJack), 0, (GInstanceInitFunc)gst_jack_init, }; jack_type = g_type_register_static (GST_TYPE_JACK, "GstJackSrc", &jack_info, 0); } return jack_type; } static void gst_jack_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_set_details (element_class, &gst_jack_bin_details); } static void gst_jack_src_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_add_pad_template (element_class, gst_jack_src_request_pad_factory ()); gst_element_class_set_details (element_class, &gst_jack_src_details); } static void gst_jack_sink_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_add_pad_template (element_class, gst_jack_sink_request_pad_factory ()); gst_element_class_set_details (element_class, &gst_jack_sink_details); } static void gst_jack_class_init(GstJackClass *klass) { GObjectClass *object_class; GstElementClass *element_class; GParamSpec *pspec; gchar *prefix; object_class = (GObjectClass *)klass; element_class = (GstElementClass *)klass; if (parent_class == NULL) parent_class = g_type_class_ref(GST_TYPE_ELEMENT); object_class->get_property = gst_jack_get_property; object_class->set_property = gst_jack_set_property; if (GST_IS_JACK_SINK_CLASS (klass)) prefix = "gst-out-"; else prefix = "gst-in-"; pspec = g_param_spec_string ("port-name-prefix", "Port name prefix", "String to prepend to jack port names", prefix, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property (object_class, ARG_PORT_NAME_PREFIX, pspec); element_class->change_state = gst_jack_change_state; element_class->request_new_pad = gst_jack_request_new_pad; } static void gst_jack_init(GstJack *this) { if (G_OBJECT_TYPE (this) == GST_TYPE_JACK_SRC) this->direction = GST_PAD_SRC; else if (G_OBJECT_TYPE (this) == GST_TYPE_JACK_SINK) this->direction = GST_PAD_SINK; else g_assert_not_reached (); gst_element_set_loop_function (GST_ELEMENT (this), gst_jack_loop); } static void gst_jack_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GstJack *this = (GstJack*)object; switch (prop_id) { case ARG_PORT_NAME_PREFIX: if (this->port_name_prefix) g_free (this->port_name_prefix); this->port_name_prefix = g_strdup (g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); return; } } static void gst_jack_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GstJack *this = (GstJack*)object; switch (prop_id) { case ARG_PORT_NAME_PREFIX: g_value_set_string (value, this->port_name_prefix); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstPadTemplate* gst_jack_src_request_pad_factory (void) { static GstPadTemplate *template = NULL; if (!template) { GstCaps *caps; caps = gst_caps_new ("src", "audio/x-raw-float", GST_AUDIO_FLOAT_STANDARD_PAD_TEMPLATE_PROPS); template = gst_pad_template_new ("%s", GST_PAD_SRC, GST_PAD_REQUEST, caps, NULL); } return template; } static GstPadTemplate* gst_jack_sink_request_pad_factory (void) { static GstPadTemplate *template = NULL; if (!template) { GstCaps *caps; caps = gst_caps_new ("sink", "audio/x-raw-float", GST_AUDIO_FLOAT_STANDARD_PAD_TEMPLATE_PROPS); template = gst_pad_template_new ("%s", GST_PAD_SINK, GST_PAD_REQUEST, caps, NULL); } return template; } static GstPad* gst_jack_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name) { GstJack *this; gchar *newname; GList *l, **pad_list; GstJackPad *pad; gint count; g_return_val_if_fail ((this = GST_JACK (element)), NULL); if (!this->bin) pad_list = &this->pads; else if (this->direction == GST_PAD_SRC) pad_list = &this->bin->src_pads; else pad_list = &this->bin->sink_pads; if (name) { l = *pad_list; while (l) { if (strcmp (GST_JACK_PAD(l)->name, name) == 0) { g_warning("requested port name %s already in use.", name); return NULL; } l = l->next; } newname = g_strdup (name); } else { if (this->direction == GST_PAD_SINK) newname = g_strdup ("alsa_pcm:playback_1"); else newname = g_strdup ("alsa_pcm:capture_1"); } pad = g_new0 (GstJackPad, 1); if (!port_name_counts) port_name_counts = g_hash_table_new (g_str_hash, g_str_equal); count = GPOINTER_TO_INT (g_hash_table_lookup (port_name_counts, this->port_name_prefix)); g_hash_table_insert (port_name_counts, g_strdup (this->port_name_prefix), GINT_TO_POINTER (count+1)); pad->name = g_strdup_printf ("%s%d", this->port_name_prefix, count); pad->peer_name = newname; pad->pad = gst_pad_new_from_template (templ, newname); gst_element_add_pad (GST_ELEMENT (this), pad->pad); gst_pad_set_link_function (pad->pad, gst_jack_link); this->pads = g_list_append (this->pads, pad); g_print ("returning from request_new_pad, pad %s created, to connect to %s\n", pad->name, pad->peer_name); return pad->pad; } static GstElementStateReturn gst_jack_change_state (GstElement *element) { GstJack *this; GList *l = NULL, **pads; GstJackPad *pad; GstCaps *caps; g_return_val_if_fail (element != NULL, FALSE); this = GST_JACK (element); switch (GST_STATE_PENDING (element)) { case GST_STATE_NULL: JACK_DEBUG ("%s: NULL", GST_OBJECT_NAME (GST_OBJECT (this))); break; case GST_STATE_READY: JACK_DEBUG ("%s: READY", GST_OBJECT_NAME (GST_OBJECT (this))); if (!this->bin) { if (!(this->bin = (GstJackBin*)gst_element_get_managing_bin (element)) || !GST_IS_JACK_BIN (this->bin)) { this->bin = NULL; g_warning ("jack element %s needs to be contained in a jack bin.", GST_OBJECT_NAME (element)); return GST_STATE_FAILURE; } /* fixme: verify that all names are unique */ l = this->pads; pads = (this->direction == GST_PAD_SRC) ? &this->bin->src_pads : &this->bin->sink_pads; while (l) { pad = GST_JACK_PAD (l); JACK_DEBUG ("%s: appending pad %s:%s to list", GST_OBJECT_NAME (this), pad->name, pad->peer_name); *pads = g_list_append (*pads, pad); l = g_list_next (l); } } break; case GST_STATE_PAUSED: JACK_DEBUG ("%s: PAUSED", GST_OBJECT_NAME (GST_OBJECT (this))); if (GST_STATE (element) == GST_STATE_READY) { /* we're in READY->PAUSED */ l = this->pads; while (l) { pad = GST_JACK_PAD (l); caps = gst_pad_get_caps (pad->pad); gst_caps_set (caps, "rate", GST_PROPS_INT_TYPE, (gint)this->bin->rate, NULL); gst_caps_set (caps, "buffer-frames", GST_PROPS_INT_TYPE, (gint)this->bin->nframes, NULL); if (gst_pad_try_set_caps (pad->pad, caps) <= 0) return GST_STATE_FAILURE; l = g_list_next (l); } } break; case GST_STATE_PLAYING: JACK_DEBUG ("%s: PLAYING", GST_OBJECT_NAME (GST_OBJECT (this))); break; } JACK_DEBUG ("%s: state change finished", GST_OBJECT_NAME (this)); if (GST_ELEMENT_CLASS (parent_class)->change_state) return GST_ELEMENT_CLASS (parent_class)->change_state (element); return GST_STATE_SUCCESS; } static GstPadLinkReturn gst_jack_link (GstPad *pad, GstCaps *caps) { GstJack *this; gint rate, buffer_frames; this = GST_JACK (GST_OBJECT_PARENT (pad)); if (GST_CAPS_IS_FIXED (caps)) { gst_caps_get_int (caps, "rate", &rate); gst_caps_get_int (caps, "buffer-frames", &buffer_frames); if (this->bin && (rate != this->bin->rate || buffer_frames != this->bin->nframes)) return GST_PAD_LINK_REFUSED; return GST_PAD_LINK_OK; } return GST_PAD_LINK_DELAYED; } static void gst_jack_loop (GstElement *element) { GstJack *this; GList *pads; gint len; GstJackPad *pad; GstBuffer *buffer; this = GST_JACK (element); len = this->bin->nframes * sizeof (sample_t); pads = this->pads; while (pads) { pad = GST_JACK_PAD (pads); if (this->direction == GST_PAD_SINK) { buffer = GST_BUFFER (gst_pad_pull (pad->pad)); if (GST_IS_EVENT (buffer)) { GstEvent *event = GST_EVENT (buffer); switch (GST_EVENT_TYPE (buffer)) { case GST_EVENT_EOS: gst_element_set_eos (element); gst_event_unref (event); return; default: gst_pad_event_default (pad->pad, event); return; } } /* if the other plugins only give out buffer-frames or less (as they should), if the length of the GstBuffer is different from nframes then the buffer is short and we will get EOS next */ memcpy (pad->data, GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer)); if (len != GST_BUFFER_SIZE (buffer)) memset (pad->data + GST_BUFFER_SIZE (buffer), 0, len - GST_BUFFER_SIZE (buffer)); gst_buffer_unref (buffer); } else { buffer = gst_buffer_new (); gst_buffer_set_data (buffer, pad->data, len); GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_DONTFREE); gst_pad_push (pad->pad, GST_DATA (buffer)); } pads = g_list_next (pads); } } static gboolean plugin_init (GstPlugin *plugin) { if (!gst_element_register (plugin, "jackbin", GST_RANK_NONE, GST_TYPE_JACK_BIN)) return FALSE; if (!gst_element_register (plugin, "jacksrc", GST_RANK_NONE, GST_TYPE_JACK_SRC)) return FALSE; if (!gst_element_register (plugin, "jacksink", GST_RANK_NONE, GST_TYPE_JACK_SINK)) return FALSE; return TRUE; } GST_PLUGIN_DEFINE ( GST_VERSION_MAJOR, GST_VERSION_MINOR, "jack", "Jack Plugin Library", plugin_init, VERSION, "GPL", GST_COPYRIGHT, GST_PACKAGE, GST_ORIGIN)