Save I/O connections per device (#9344)

This commit is contained in:
Robin Gareus
2023-05-24 02:43:07 +02:00
parent c11f8532ca
commit aaf3013211
3 changed files with 168 additions and 72 deletions

View File

@@ -134,11 +134,11 @@ public:
uint32_t externally_connected () const { return _externally_connected; } uint32_t externally_connected () const { return _externally_connected; }
uint32_t internally_connected () const { return _internally_connected; } uint32_t internally_connected () const { return _internally_connected; }
void increment_external_connections() { _externally_connected++; } void increment_external_connections ();
void decrement_external_connections() { if (_externally_connected) _externally_connected--; } void decrement_external_connections ();
void increment_internal_connections() { _internally_connected++; } void increment_internal_connections ();
void decrement_internal_connections() { if (_internally_connected) _internally_connected--; } void decrement_internal_connections ();
PBD::Signal1<void,bool> MonitorInputChanged; PBD::Signal1<void,bool> MonitorInputChanged;
@@ -201,15 +201,23 @@ private:
uint32_t _externally_connected; uint32_t _externally_connected;
uint32_t _internally_connected; uint32_t _internally_connected;
/** ports that we are connected to, kept so that we can typedef std::set<std::string> ConnectionSet;
reconnect to the backend when required /* ports that we are connected to, kept so that we can
*/ * reconnect to the backend when required
std::set<std::string> _connections; */
mutable Glib::Threads::RWLock _connections_lock;
ConnectionSet _int_connections;
std::map<std::string, ConnectionSet> _ext_connections;
static uint32_t _resampler_quality; // 8 <= q <= 96 static uint32_t _resampler_quality; // 8 <= q <= 96
static uint32_t _resampler_latency; // = _resampler_quality - 1 static uint32_t _resampler_latency; // = _resampler_quality - 1
void port_connected_or_disconnected (std::weak_ptr<Port>, std::weak_ptr<Port>, bool); void port_connected_or_disconnected (std::weak_ptr<Port>, std::string, std::weak_ptr<Port>, std::string, bool);
int connect_internal (std::string const &);
void insert_connection (std::string const&);
void erase_connection (std::string const&);
void signal_drop (); void signal_drop ();
void session_global_drop (); void session_global_drop ();
void drop (); void drop ();

View File

@@ -548,34 +548,7 @@ IO::state () const
} }
for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) { for (PortSet::const_iterator i = _ports.begin(); i != _ports.end(); ++i) {
node->add_child_nocopy (i->get_state ());
vector<string> connections;
XMLNode* pnode = new XMLNode (X_("Port"));
pnode->set_property (X_("type"), i->type());
pnode->set_property (X_("name"), i->name());
if (i->get_connections (connections)) {
vector<string>::const_iterator ci;
std::sort (connections.begin(), connections.end());
for (n = 0, ci = connections.begin(); ci != connections.end(); ++ci, ++n) {
/* if its a connection to our own port,
return only the port name, not the
whole thing. this allows connections
to be re-established even when our
client name is different.
*/
XMLNode* cnode = new XMLNode (X_("Connection"));
cnode->set_property (X_("other"), _session.engine().make_port_name_relative (*ci));
pnode->add_child_nocopy (*cnode);
}
}
node->add_child_nocopy (*pnode);
} }
return *node; return *node;

View File

@@ -91,7 +91,7 @@ Port::Port (std::string const & n, DataType t, PortFlags f)
PortDrop.connect_same_thread (drop_connection, boost::bind (&Port::session_global_drop, this)); PortDrop.connect_same_thread (drop_connection, boost::bind (&Port::session_global_drop, this));
PortSignalDrop.connect_same_thread (drop_connection, boost::bind (&Port::signal_drop, this)); PortSignalDrop.connect_same_thread (drop_connection, boost::bind (&Port::signal_drop, this));
port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5)); port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _2, _3, _4, _5));
} }
/** Port destructor */ /** Port destructor */
@@ -160,7 +160,7 @@ Port::drop ()
} }
void void
Port::port_connected_or_disconnected (std::weak_ptr<Port> w0, std::weak_ptr<Port> w1, bool con) Port::port_connected_or_disconnected (std::weak_ptr<Port> w0, std::string n1, std::weak_ptr<Port> w1, std::string n2, bool con)
{ {
std::shared_ptr<Port> p0 = w0.lock (); std::shared_ptr<Port> p0 = w0.lock ();
std::shared_ptr<Port> p1 = w1.lock (); std::shared_ptr<Port> p1 = w1.lock ();
@@ -168,13 +168,88 @@ Port::port_connected_or_disconnected (std::weak_ptr<Port> w0, std::weak_ptr<Port
std::shared_ptr<Port> pself = AudioEngine::instance()->get_port_by_name (name()); std::shared_ptr<Port> pself = AudioEngine::instance()->get_port_by_name (name());
if (p0 == pself) { if (p0 == pself) {
if (con) {
insert_connection (n2);
} else {
erase_connection (n2);
}
ConnectedOrDisconnected (p0, p1, con); // emit signal ConnectedOrDisconnected (p0, p1, con); // emit signal
} }
if (p1 == pself) { if (p1 == pself) {
if (con) {
insert_connection (n1);
} else {
erase_connection (n1);
}
ConnectedOrDisconnected (p1, p0, con); // emit signal ConnectedOrDisconnected (p1, p0, con); // emit signal
} }
} }
void
Port::insert_connection (std::string const& pn)
{
#if 1 // include external JACK clients
if (!AudioEngine::instance()->port_is_mine (pn))
#else
if (port_manager->port_is_physical (pn))
#endif
{
std::string const bid (AudioEngine::instance()->backend_id (receives_input ()));
Glib::Threads::RWLock::WriterLock lm (_connections_lock);
_ext_connections[bid].insert (pn);
_int_connections.erase (pn); // XXX
} else {
Glib::Threads::RWLock::WriterLock lm (_connections_lock);
_int_connections.insert (pn);
}
}
void
Port::erase_connection (std::string const& pn)
{
#if 1 // include external JACK clients
if (!AudioEngine::instance()->port_is_mine (pn))
#else
if (port_manager->port_is_physical (pn))
#endif
{
std::string const bid (AudioEngine::instance()->backend_id (receives_input ()));
Glib::Threads::RWLock::WriterLock lm (_connections_lock);
_ext_connections[bid].erase (pn);
} else {
Glib::Threads::RWLock::WriterLock lm (_connections_lock);
_int_connections.erase (pn);
}
}
void
Port::increment_external_connections ()
{
_externally_connected++;
}
void
Port::decrement_external_connections ()
{
if (_externally_connected) {
_externally_connected--;
}
}
void
Port::increment_internal_connections ()
{
_internally_connected++;
}
void
Port::decrement_internal_connections ()
{
if (_internally_connected) {
_internally_connected--;
}
}
/** @return true if this port is connected to anything */ /** @return true if this port is connected to anything */
bool bool
Port::connected () const Port::connected () const
@@ -194,7 +269,12 @@ Port::disconnect_all ()
get_connections (connections); get_connections (connections);
port_engine.disconnect_all (_port_handle); port_engine.disconnect_all (_port_handle);
_connections.clear (); {
std::string const bid (AudioEngine::instance()->backend_id (receives_input ()));
Glib::Threads::RWLock::WriterLock lm (_connections_lock);
_int_connections.clear ();
_ext_connections[bid].clear ();
}
/* a cheaper, less hacky way to do boost::shared_from_this() ... /* a cheaper, less hacky way to do boost::shared_from_this() ...
*/ */
@@ -202,7 +282,7 @@ Port::disconnect_all ()
for (vector<string>::const_iterator c = connections.begin(); c != connections.end() && pself; ++c) { for (vector<string>::const_iterator c = connections.begin(); c != connections.end() && pself; ++c) {
std::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (*c); std::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (*c);
if (pother) { if (pother) {
pother->_connections.erase (_name); pother->erase_connection (_name);
ConnectedOrDisconnected (pself, pother, false); // emit signal ConnectedOrDisconnected (pself, pother, false); // emit signal
} }
} }
@@ -229,11 +309,17 @@ Port::connected_to (std::string const & o) const
} }
int int
Port::get_connections (std::vector<std::string> & c) const Port::get_connections (std::vector<std::string>& c) const
{ {
if (!port_manager->running()) { if (!port_manager->running()) {
c.insert (c.end(), _connections.begin(), _connections.end()); std::string const bid (AudioEngine::instance()->backend_id (receives_input ()));
return c.size(); Glib::Threads::RWLock::ReaderLock lm (_connections_lock);
c.insert (c.end(), _int_connections.begin(), _int_connections.end());
try {
c.insert (c.end(), _ext_connections.at(bid).begin(), _ext_connections.at(bid).end());
} catch (std::out_of_range&) {
}
return c.size ();
} }
if (_port_handle) { if (_port_handle) {
@@ -244,7 +330,7 @@ Port::get_connections (std::vector<std::string> & c) const
} }
int int
Port::connect (std::string const & other) Port::connect_internal (std::string const & other)
{ {
std::string const other_name = AudioEngine::instance()->make_port_name_non_relative (other); std::string const other_name = AudioEngine::instance()->make_port_name_non_relative (other);
std::string const our_name = AudioEngine::instance()->make_port_name_non_relative (_name); std::string const our_name = AudioEngine::instance()->make_port_name_non_relative (_name);
@@ -262,6 +348,13 @@ Port::connect (std::string const & other)
DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2\n", other_name, our_name)); DEBUG_TRACE (DEBUG::Ports, string_compose ("Connect %1 to %2\n", other_name, our_name));
r = port_engine.connect (other_name, our_name); r = port_engine.connect (other_name, our_name);
} }
return r;
}
int
Port::connect (std::string const& other)
{
int r = connect_internal (other);
if (r == 0) { if (r == 0) {
/* Connections can be saved on either or both sides. The code above works regardless /* Connections can be saved on either or both sides. The code above works regardless
@@ -274,11 +367,11 @@ Port::connect (std::string const & other)
* *
* This is also nicer when reading the session file's <Port><Connection>. * This is also nicer when reading the session file's <Port><Connection>.
*/ */
_connections.insert (other); insert_connection (other);
std::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (other); std::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (other);
if (pother) { if (pother) {
pother->_connections.insert (_name); pother->insert_connection (_name);
} }
} }
@@ -300,7 +393,7 @@ Port::disconnect (std::string const & other)
} }
if (r == 0) { if (r == 0) {
_connections.erase (other); erase_connection (other);
} }
/* a cheaper, less hacky way to do boost::shared_from_this() ... */ /* a cheaper, less hacky way to do boost::shared_from_this() ... */
@@ -308,7 +401,7 @@ Port::disconnect (std::string const & other)
std::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (other); std::shared_ptr<Port> pother = AudioEngine::instance()->get_port_by_name (other);
if (r == 0 && pother) { if (r == 0 && pother) {
pother->_connections.erase (_name); pother->erase_connection (_name);
} }
if (pself && pother) { if (pself && pother) {
@@ -601,7 +694,7 @@ Port::reestablish ()
reset (); reset ();
port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _3, _5)); port_manager->PortConnectedOrDisconnected.connect_same_thread (engine_connection, boost::bind (&Port::port_connected_or_disconnected, this, _1, _2, _3, _4, _5));
return 0; return 0;
} }
@@ -611,22 +704,36 @@ Port::reconnect ()
{ {
/* caller must hold process lock; intended to be used only after reestablish() */ /* caller must hold process lock; intended to be used only after reestablish() */
if (_connections.empty ()) { std::string const bid (AudioEngine::instance()->backend_id (receives_input ()));
Glib::Threads::RWLock::WriterLock lm (_connections_lock);
if (_int_connections.empty () && _ext_connections[bid].empty ()) {
return 0; /* OK */ return 0; /* OK */
} }
DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() Connect %1 to %2 destinations\n",name(), _connections.size())); DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() Connect %1 to %2 destinations\n",name(), _int_connections.size() + _ext_connections[bid].size()));
int count = 0; int count = 0;
std::set<string>::iterator i = _connections.begin();
while (i != _connections.end()) { ConnectionSet::iterator i = _int_connections.begin();
std::set<string>::iterator current = i++; while (i != _int_connections.end()) {
if (connect (*current)) { ConnectionSet::iterator current = i++;
if (connect_internal (*current)) {
DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() failed to connect %1 to %2\n", name(), (*current))); DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() failed to connect %1 to %2\n", name(), (*current)));
_connections.erase (current); _int_connections.erase (current);
} else {
++count;
} }
else { }
i = _ext_connections[bid].begin();
while (i != _ext_connections[bid].end()) {
ConnectionSet::iterator current = i++;
if (connect_internal (*current)) {
DEBUG_TRACE (DEBUG::Ports, string_compose ("Port::reconnect() failed to connect %1 to %2\n", name(), (*current)));
_ext_connections[bid].erase (current);
} else {
++count; ++count;
} }
} }
@@ -669,23 +776,30 @@ Port::get_state () const
XMLNode* root = new XMLNode (state_node_name); XMLNode* root = new XMLNode (state_node_name);
root->set_property (X_("name"), AudioEngine::instance()->make_port_name_relative (name())); root->set_property (X_("name"), AudioEngine::instance()->make_port_name_relative (name()));
root->set_property (X_("type"), type ());
if (receives_input()) { if (receives_input()) {
root->set_property (X_("direction"), X_("input")); root->set_property (X_("direction"), X_("Input"));
} else { } else {
root->set_property (X_("direction"), X_("output")); root->set_property (X_("direction"), X_("Output"));
} }
vector<string> c; Glib::Threads::RWLock::ReaderLock lm (_connections_lock);
for (auto const& c : _int_connections) {
get_connections (c);
for (vector<string>::const_iterator i = c.begin(); i != c.end(); ++i) {
XMLNode* child = new XMLNode (X_("Connection")); XMLNode* child = new XMLNode (X_("Connection"));
child->set_property (X_("other"), *i); child->set_property (X_("other"), AudioEngine::instance()->make_port_name_relative (c));
root->add_child_nocopy (*child); root->add_child_nocopy (*child);
} }
for (auto const& hwc : _ext_connections) {
for (auto const& c : hwc.second) {
XMLNode* child = new XMLNode (X_("ExtConnection"));
child->set_property (X_("for"), hwc.first);
child->set_property (X_("other"), c);
root->add_child_nocopy (*child);
}
}
return *root; return *root;
} }
@@ -703,19 +817,20 @@ Port::set_state (const XMLNode& node, int)
const XMLNodeList& children (node.children()); const XMLNodeList& children (node.children());
_connections.clear (); _int_connections.clear ();
_ext_connections.clear ();
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) { for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
if ((*c)->name() != X_("Connection")) { if ((*c)->name() == X_("Connection") && (*c)->get_property (X_("other"), str)) {
_int_connections.insert (AudioEngine::instance()->make_port_name_non_relative (str));
continue; continue;
} }
if (!(*c)->get_property (X_("other"), str)) { std::string hw;
continue; if ((*c)->name() == X_("ExtConnection") && (*c)->get_property (X_("for"), hw) && (*c)->get_property (X_("other"), str)) {
_ext_connections[hw].insert (str);
} }
_connections.insert (str);
} }
return 0; return 0;