diff --git a/docs/gst_plugins_cache.json b/docs/gst_plugins_cache.json index 44f006d851..c23a7b7450 100644 --- a/docs/gst_plugins_cache.json +++ b/docs/gst_plugins_cache.json @@ -8356,6 +8356,18 @@ "type": "gboolean", "writable": true }, + "port-names": { + "blurb": "Comma-separated list of port name including \"client_name:\" prefix", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "ready", + "readable": true, + "type": "gchararray", + "writable": true + }, "port-pattern": { "blurb": "A pattern to select which ports to connect to (NULL = first physical ports)", "conditionally-available": false, @@ -8465,6 +8477,18 @@ "type": "gboolean", "writable": true }, + "port-names": { + "blurb": "Comma-separated list of port name including \"client_name:\" prefix", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "ready", + "readable": true, + "type": "gchararray", + "writable": true + }, "port-pattern": { "blurb": "A pattern to select which ports to connect to (NULL = first physical ports)", "conditionally-available": false, @@ -8525,6 +8549,11 @@ "desc": "Automatically connect ports to as many physical ports as possible", "name": "auto-forced", "value": "2" + }, + { + "desc": "Connect ports to explicitly requested physical ports", + "name": "explicit", + "value": "3" } ] }, diff --git a/ext/jack/gstjack.c b/ext/jack/gstjack.c index 06c3ad0435..59682bb7b6 100644 --- a/ext/jack/gstjack.c +++ b/ext/jack/gstjack.c @@ -37,6 +37,9 @@ gst_jack_connect_get_type (void) {GST_JACK_CONNECT_AUTO_FORCED, "Automatically connect ports to as many physical ports as possible", "auto-forced"}, + {GST_JACK_CONNECT_EXPLICIT, + "Connect ports to explicitly requested physical ports", + "explicit"}, {0, NULL, NULL}, }; GType tmp = g_enum_register_static ("GstJackConnect", jack_connect_enums); diff --git a/ext/jack/gstjack.h b/ext/jack/gstjack.h index ad238fdb76..84693f6140 100644 --- a/ext/jack/gstjack.h +++ b/ext/jack/gstjack.h @@ -45,7 +45,17 @@ GST_ELEMENT_REGISTER_DECLARE (jackaudiosink); typedef enum { GST_JACK_CONNECT_NONE, GST_JACK_CONNECT_AUTO, - GST_JACK_CONNECT_AUTO_FORCED + GST_JACK_CONNECT_AUTO_FORCED, + + /** + * GstJackConnect::explicit + * + * In this mode, the element will try to connect to explicitly requested + * port specified by "port-names". + * + * Since: 1.20 + */ + GST_JACK_CONNECT_EXPLICIT, } GstJackConnect; /** diff --git a/ext/jack/gstjackaudioclient.c b/ext/jack/gstjackaudioclient.c index 3d0dbc194e..5b483a810a 100644 --- a/ext/jack/gstjackaudioclient.c +++ b/ext/jack/gstjackaudioclient.c @@ -635,3 +635,54 @@ gst_jack_audio_client_get_transport_state (GstJackAudioClient * client) client->conn->transport_state = GST_STATE_VOID_PENDING; return state; } + +/** + * gst_jack_audio_client_get_port_names_from_string: + * @jclient: a jack_client_t handle + * @port_names: comma-separated jack port name(s) + * @port_flags: JackPortFlags + * + * Returns: a newly-allocated %NULL-terminated array of strings or %NULL + * if @port_names contains invalid port name. Use g_strfreev() to free it. + */ +gchar ** +gst_jack_audio_client_get_port_names_from_string (jack_client_t * jclient, + const gchar * port_names, gint port_flags) +{ + gchar **p = NULL; + guint i, len; + + g_return_val_if_fail (jclient != NULL, NULL); + + if (!port_names) + return NULL; + + p = g_strsplit (port_names, ",", 0); + len = g_strv_length (p); + + if (len < 1) + goto invalid; + + for (i = 0; i < len; i++) { + jack_port_t *port = jack_port_by_name (jclient, p[i]); + int flags; + + if (!port) { + GST_WARNING ("Couldn't get jack port by name %s", p[i]); + goto invalid; + } + + flags = jack_port_flags (port); + if ((flags & port_flags) != port_flags) { + GST_WARNING ("Port flags 0x%x doesn't match expected flags 0x%x", + flags, port_flags); + goto invalid; + } + } + + return p; + +invalid: + g_strfreev (p); + return NULL; +} diff --git a/ext/jack/gstjackaudioclient.h b/ext/jack/gstjackaudioclient.h index bf4d93e52a..455edd2e78 100644 --- a/ext/jack/gstjackaudioclient.h +++ b/ext/jack/gstjackaudioclient.h @@ -56,6 +56,10 @@ gboolean gst_jack_audio_client_set_active (GstJackAudioClient * GstState gst_jack_audio_client_get_transport_state (GstJackAudioClient *client); +gchar ** gst_jack_audio_client_get_port_names_from_string (jack_client_t *jclient, + const gchar *port_names, + gint port_flags); + G_END_DECLS #endif /* __GST_JACK_AUDIO_CLIENT_H__ */ diff --git a/ext/jack/gstjackaudiosink.c b/ext/jack/gstjackaudiosink.c index ccdca5a54a..4d18f7a36f 100644 --- a/ext/jack/gstjackaudiosink.c +++ b/ext/jack/gstjackaudiosink.c @@ -401,7 +401,6 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, { GstJackAudioSink *sink; GstJackRingBuffer *abuf; - const char **ports; gint sample_rate, buffer_size; gint i, rate, bpf, channels, res; jack_client_t *client; @@ -459,18 +458,39 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, /* if we need to automatically connect the ports, do so now. We must do this * after activating the client. */ if (sink->connect == GST_JACK_CONNECT_AUTO - || sink->connect == GST_JACK_CONNECT_AUTO_FORCED) { + || sink->connect == GST_JACK_CONNECT_AUTO_FORCED + || sink->connect == GST_JACK_CONNECT_EXPLICIT) { + const char **available_ports = NULL; + const char **jack_ports = NULL; + char **user_ports = NULL; + /* find all the physical input ports. A physical input port is a port * associated with a hardware device. Someone needs connect to a physical * port in order to hear something. */ - if (sink->port_pattern == NULL) { - ports = jack_get_ports (client, NULL, NULL, - JackPortIsPhysical | JackPortIsInput); - } else { - ports = jack_get_ports (client, sink->port_pattern, NULL, - JackPortIsInput); + if (sink->port_names) { + user_ports = gst_jack_audio_client_get_port_names_from_string (client, + sink->port_names, JackPortIsInput); + + if (user_ports) + available_ports = (const char **) user_ports; } - if (ports == NULL) { + + if (!available_ports && sink->connect == GST_JACK_CONNECT_EXPLICIT) + goto wrong_port_names; + + if (!available_ports) { + if (!sink->port_pattern) { + jack_ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical | JackPortIsInput); + } else { + jack_ports = jack_get_ports (client, sink->port_pattern, NULL, + JackPortIsInput); + } + + available_ports = jack_ports; + } + + if (!available_ports) { /* no ports? fine then we don't do anything except for posting a warning * message. */ GST_ELEMENT_WARNING (sink, RESOURCE, NOT_FOUND, (NULL), @@ -480,7 +500,7 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, for (i = 0; i < channels; i++) { /* stop when all input ports are exhausted */ - if (ports[i] == NULL) { + if (!available_ports[i]) { /* post a warning that we could not connect all ports */ GST_ELEMENT_WARNING (sink, RESOURCE, NOT_FOUND, (NULL), ("No more physical ports, leaving some ports unconnected")); @@ -489,11 +509,18 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, GST_DEBUG_OBJECT (sink, "try connecting to %s", jack_port_name (sink->ports[i])); /* connect the port to a physical port */ - res = jack_connect (client, jack_port_name (sink->ports[i]), ports[i]); - if (res != 0 && res != EEXIST) + res = jack_connect (client, + jack_port_name (sink->ports[i]), available_ports[i]); + if (res != 0 && res != EEXIST) { + jack_free (jack_ports); + g_strfreev (user_ports); + goto cannot_connect; + } } - jack_free (ports); + + jack_free (jack_ports); + g_strfreev (user_ports); } done: @@ -528,7 +555,12 @@ cannot_connect: GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL), ("Could not connect output ports to physical ports (%d:%s)", res, g_strerror (res))); - jack_free (ports); + return FALSE; + } +wrong_port_names: + { + GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, (NULL), + ("Invalid port-names was provided")); return FALSE; } } @@ -699,6 +731,7 @@ enum PROP_PORT_PATTERN, PROP_TRANSPORT, PROP_LOW_LATENCY, + PROP_PORT_NAMES, PROP_LAST }; @@ -807,6 +840,19 @@ gst_jack_audio_sink_class_init (GstJackAudioSinkClass * klass) GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstJackAudioSink:port-names: + * + * Comma-separated list of port name including "client_name:" prefix + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_PORT_NAMES, + g_param_spec_string ("port-names", "Port Names", + "Comma-separated list of port name including \"client_name:\" prefix", + NULL, GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + gst_element_class_set_static_metadata (gstelement_class, "Audio Sink (Jack)", "Sink/Audio", "Output audio to a JACK server", "Wim Taymans "); @@ -857,6 +903,8 @@ gst_jack_audio_sink_dispose (GObject * object) sink->port_pattern = NULL; } + g_clear_pointer (&sink->port_names, g_free); + G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -896,6 +944,10 @@ gst_jack_audio_sink_set_property (GObject * object, guint prop_id, case PROP_LOW_LATENCY: sink->low_latency = g_value_get_boolean (value); break; + case PROP_PORT_NAMES: + g_free (sink->port_names); + sink->port_names = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -932,6 +984,9 @@ gst_jack_audio_sink_get_property (GObject * object, guint prop_id, case PROP_LOW_LATENCY: g_value_set_boolean (value, sink->low_latency); break; + case PROP_PORT_NAMES: + g_value_set_string (value, sink->port_names); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -950,14 +1005,42 @@ gst_jack_audio_sink_getcaps (GstBaseSink * bsink, GstCaps * filter) if (sink->client == NULL) goto no_client; + if (sink->connect == GST_JACK_CONNECT_EXPLICIT && !sink->port_names) + goto no_port_names; + client = gst_jack_audio_client_get_client (sink->client); - if (sink->connect == GST_JACK_CONNECT_AUTO) { + if (sink->connect == GST_JACK_CONNECT_AUTO || + sink->connect == GST_JACK_CONNECT_EXPLICIT) { + max = 0; + + if (sink->port_names) { + gchar **user_ports = + gst_jack_audio_client_get_port_names_from_string (client, + sink->port_names, JackPortIsInput); + + if (user_ports) { + max = g_strv_length (user_ports); + } else { + GST_ELEMENT_WARNING (sink, RESOURCE, NOT_FOUND, + ("Invalid \"port-names\" was requested"), + ("Requested \"port-names\" %s contains invalid name", + sink->port_names)); + } + + g_strfreev (user_ports); + } + + if (max > 0) + goto found; + + if (sink->connect == GST_JACK_CONNECT_EXPLICIT) + goto no_port_names; + /* get a port count, this is the number of channels we can automatically * connect. */ ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical | JackPortIsInput); - max = 0; if (ports != NULL) { for (; ports[max]; max++); jack_free (ports); @@ -968,7 +1051,13 @@ gst_jack_audio_sink_getcaps (GstBaseSink * bsink, GstCaps * filter) * pads. */ max = G_MAXINT; } - min = MIN (1, max); + +found: + if (sink->connect == GST_JACK_CONNECT_EXPLICIT) { + min = max; + } else { + min = MIN (1, max); + } rate = jack_get_sample_rate (client); @@ -996,6 +1085,13 @@ no_client: /* base class will get template caps for us when we return NULL */ return NULL; } +no_port_names: + { + GST_ELEMENT_ERROR (sink, RESOURCE, SETTINGS, + ("User must provide valid port names"), + ("\"port-names\" contains invalid name or NULL string")); + return NULL; + } } static GstAudioRingBuffer * diff --git a/ext/jack/gstjackaudiosink.h b/ext/jack/gstjackaudiosink.h index ffba824311..088289de46 100644 --- a/ext/jack/gstjackaudiosink.h +++ b/ext/jack/gstjackaudiosink.h @@ -56,6 +56,7 @@ struct _GstJackAudioSink { gchar *port_pattern; guint transport; gboolean low_latency; + gchar *port_names; /* our client */ GstJackAudioClient *client; diff --git a/ext/jack/gstjackaudiosrc.c b/ext/jack/gstjackaudiosrc.c index 11f4f91573..40784fa4a8 100644 --- a/ext/jack/gstjackaudiosrc.c +++ b/ext/jack/gstjackaudiosrc.c @@ -407,7 +407,6 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, { GstJackAudioSrc *src; GstJackRingBuffer *abuf; - const char **ports; gint sample_rate, buffer_size; gint i, bpf, rate, channels, res; jack_client_t *client; @@ -467,20 +466,38 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, /* if we need to automatically connect the ports, do so now. We must do this * after activating the client. */ if (src->connect == GST_JACK_CONNECT_AUTO - || src->connect == GST_JACK_CONNECT_AUTO_FORCED) { + || src->connect == GST_JACK_CONNECT_AUTO_FORCED + || src->connect == GST_JACK_CONNECT_EXPLICIT) { + const char **available_ports = NULL; + const char **jack_ports = NULL; + char **user_ports = NULL; + /* find all the physical output ports. A physical output port is a port * associated with a hardware device. Someone needs connect to a physical * port in order to capture something. */ - if (src->port_pattern == NULL) { - ports = jack_get_ports (client, NULL, NULL, - JackPortIsPhysical | JackPortIsOutput); - } else { - ports = jack_get_ports (client, src->port_pattern, NULL, - JackPortIsOutput); + if (src->port_names) { + user_ports = gst_jack_audio_client_get_port_names_from_string (client, + src->port_names, JackPortIsOutput); + + if (user_ports) + available_ports = (const char **) user_ports; } - if (ports == NULL) { + if (!available_ports && src->connect == GST_JACK_CONNECT_EXPLICIT) + goto wrong_port_names; + + if (!available_ports) { + if (!src->port_pattern) { + jack_ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical | JackPortIsOutput); + } else { + jack_ports = jack_get_ports (client, src->port_pattern, NULL, + JackPortIsOutput); + } + } + + if (!available_ports) { /* no ports? fine then we don't do anything except for posting a warning * message. */ GST_ELEMENT_WARNING (src, RESOURCE, NOT_FOUND, (NULL), @@ -490,7 +507,7 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, for (i = 0; i < channels; i++) { /* stop when all output ports are exhausted */ - if (ports[i] == NULL) { + if (!available_ports[i]) { /* post a warning that we could not connect all ports */ GST_ELEMENT_WARNING (src, RESOURCE, NOT_FOUND, (NULL), ("No more physical ports, leaving some ports unconnected")); @@ -500,11 +517,18 @@ gst_jack_ring_buffer_acquire (GstAudioRingBuffer * buf, jack_port_name (src->ports[i])); /* connect the physical port to a port */ - res = jack_connect (client, ports[i], jack_port_name (src->ports[i])); - if (res != 0 && res != EEXIST) + res = jack_connect (client, + available_ports[i], jack_port_name (src->ports[i])); + if (res != 0 && res != EEXIST) { + jack_free (jack_ports); + g_strfreev (user_ports); + goto cannot_connect; + } } - jack_free (ports); + + jack_free (jack_ports); + g_strfreev (user_ports); } done: @@ -539,7 +563,12 @@ cannot_connect: GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Could not connect input ports to physical ports (%d:%s)", res, g_strerror (res))); - jack_free (ports); + return FALSE; + } +wrong_port_names: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Invalid port-names was provided")); return FALSE; } } @@ -701,6 +730,7 @@ enum PROP_PORT_PATTERN, PROP_TRANSPORT, PROP_LOW_LATENCY, + PROP_PORT_NAMES, PROP_LAST }; @@ -825,6 +855,19 @@ gst_jack_audio_src_class_init (GstJackAudioSrcClass * klass) GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstJackAudioSrc:port-names: + * + * Comma-separated list of port name including "client_name:" prefix + * + * Since: 1.20 + */ + g_object_class_install_property (gobject_class, PROP_PORT_NAMES, + g_param_spec_string ("port-names", "Port Names", + "Comma-separated list of port name including \"client_name:\" prefix", + NULL, GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + gst_element_class_add_static_pad_template (gstelement_class, &src_factory); gst_element_class_set_static_metadata (gstelement_class, @@ -875,6 +918,8 @@ gst_jack_audio_src_dispose (GObject * object) src->port_pattern = NULL; } + g_clear_pointer (&src->port_names, g_free); + G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -912,6 +957,10 @@ gst_jack_audio_src_set_property (GObject * object, guint prop_id, case PROP_LOW_LATENCY: src->low_latency = g_value_get_boolean (value); break; + case PROP_PORT_NAMES: + g_free (src->port_names); + src->port_names = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -946,6 +995,9 @@ gst_jack_audio_src_get_property (GObject * object, guint prop_id, case PROP_LOW_LATENCY: g_value_set_boolean (value, src->low_latency); break; + case PROP_PORT_NAMES: + g_value_set_string (value, src->port_names); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -964,14 +1016,42 @@ gst_jack_audio_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter) if (src->client == NULL) goto no_client; + if (src->connect == GST_JACK_CONNECT_EXPLICIT && !src->port_names) + goto no_port_names; + client = gst_jack_audio_client_get_client (src->client); - if (src->connect == GST_JACK_CONNECT_AUTO) { + if (src->connect == GST_JACK_CONNECT_AUTO || + src->connect == GST_JACK_CONNECT_EXPLICIT) { + max = 0; + + if (src->port_names) { + gchar **user_ports = + gst_jack_audio_client_get_port_names_from_string (client, + src->port_names, JackPortIsOutput); + + if (user_ports) { + max = g_strv_length (user_ports); + } else { + GST_ELEMENT_WARNING (src, RESOURCE, NOT_FOUND, + ("Invalid \"port-names\" was requested"), + ("Requested \"port-names\" %s contains invalid name", + src->port_names)); + } + + g_strfreev (user_ports); + } + + if (max > 0) + goto found; + + if (src->connect == GST_JACK_CONNECT_EXPLICIT) + goto no_port_names; + /* get a port count, this is the number of channels we can automatically * connect. */ ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical | JackPortIsOutput); - max = 0; if (ports != NULL) { for (; ports[max]; max++); @@ -983,7 +1063,13 @@ gst_jack_audio_src_getcaps (GstBaseSrc * bsrc, GstCaps * filter) * pads. */ max = G_MAXINT; } - min = MIN (1, max); + +found: + if (src->connect == GST_JACK_CONNECT_EXPLICIT) { + min = max; + } else { + min = MIN (1, max); + } rate = jack_get_sample_rate (client); @@ -1011,6 +1097,13 @@ no_client: /* base class will get template caps for us when we return NULL */ return NULL; } +no_port_names: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("User must provide valid port names"), + ("\"port-names\" contains invalid name or NULL string")); + return NULL; + } } static GstAudioRingBuffer * diff --git a/ext/jack/gstjackaudiosrc.h b/ext/jack/gstjackaudiosrc.h index d6b08b982a..3657c60cea 100644 --- a/ext/jack/gstjackaudiosrc.h +++ b/ext/jack/gstjackaudiosrc.h @@ -73,6 +73,7 @@ struct _GstJackAudioSrc gchar *port_pattern; guint transport; gboolean low_latency; + gchar *port_names; /* our client */ GstJackAudioClient *client;