From: Seungha Yang Date: Tue, 27 Jul 2021 09:33:18 +0000 (+0900) Subject: jack: Add port-names property to select ports explicitly X-Git-Tag: 1.19.3~509^2~69 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=4a5197dc27a3300111df5ee44a613a58ccdabd47;p=platform%2Fupstream%2Fgstreamer.git jack: Add port-names property to select ports explicitly By this new property, user can select physical port to connect, and element will pick requested port instead of random ones. User should provide full port name including "client_name:" prefix. An example is jackaudiosrc port-names="system:capture_1,system:capture_3" ! ... jackaudiosink port-names="system:playback_2" In addition to "port-names" property, a new connect type "explicit" is added so that element can post error message if requested "port-names" contains invalid port(s). Part-of: --- diff --git a/docs/gst_plugins_cache.json b/docs/gst_plugins_cache.json index 44f006d..c23a7b7 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 06c3ad0..59682bb 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 ad238fd..84693f6 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 3d0dbc1..5b483a8 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 bf4d93e..455edd2 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 ccdca5a..4d18f7a 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 ffba824..088289d 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 11f4f91..40784fa 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 d6b08b9..3657c60 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;