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: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/1037>
This commit is contained in:
Seungha Yang 2021-07-27 18:33:18 +09:00
parent 4ed342db5d
commit 4a5197dc27
9 changed files with 323 additions and 35 deletions

View file

@ -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"
}
]
},

View file

@ -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);

View file

@ -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;
/**

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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 <wim.taymans@gmail.com>");
@ -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 *

View file

@ -56,6 +56,7 @@ struct _GstJackAudioSink {
gchar *port_pattern;
guint transport;
gboolean low_latency;
gchar *port_names;
/* our client */
GstJackAudioClient *client;

View file

@ -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 *

View file

@ -73,6 +73,7 @@ struct _GstJackAudioSrc
gchar *port_pattern;
guint transport;
gboolean low_latency;
gchar *port_names;
/* our client */
GstJackAudioClient *client;