/* * Copyright (c) 1999-2000, Eric Moon. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions, and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // NodeManager.cpp #include "NodeManager.h" #include "AddOnHost.h" #include "Connection.h" #include "NodeGroup.h" #include "NodeRef.h" #include #include #include #include #include #include #include // Locale Kit #undef B_CATALOG #define B_CATALOG (&sCatalog) #include #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "NodeManager" #include "set_tools.h" #include "functional_tools.h" #include "node_manager_impl.h" using namespace std; __USE_CORTEX_NAMESPACE #define D_METHOD(x) //PRINT (x) #define D_MESSAGE(x) //PRINT (x) #define D_ROSTER(x) //PRINT (x) #define D_LOCK(x) //PRINT (x) static BCatalog sCatalog("x-vnd.Cortex.NodeManager"); // -------------------------------------------------------- // // messaging constants // -------------------------------------------------------- // // B_MEDIA_CONNECTION_BROKEN const char* const _connectionField = "__connection_id"; const char* const _sourceNodeField = "__source_node_id"; const char* const _destNodeField = "__destination_node_id"; // -------------------------------------------------------- // // *** hooks // -------------------------------------------------------- // // [e.moon 7nov99] these hooks are called during processing of // BMediaRoster messages, before any notification is sent to // observers. For example, if a B_MEDIA_NODES_CREATED message // were received describing 3 new nodes, nodeCreated() would be // called 3 times before the notification was sent. void NodeManager::nodeCreated( NodeRef* ref) {} void NodeManager::nodeDeleted( const NodeRef* ref) {} void NodeManager::connectionMade( Connection* connection) {} void NodeManager::connectionBroken( const Connection* connection) {} void NodeManager::connectionFailed( const media_output & output, const media_input & input, const media_format & format, status_t error) {} // -------------------------------------------------------- // // helpers // -------------------------------------------------------- // class _for_each_state { public: // marks nodes visited set visited; }; // [e.moon 28sep99] graph crawling // - does NOT apply operation to origin node. // - if inGroup is non-0, visits only nodes in that group. // // [e.moon 13oct99]: no longer supports locking (use _lockAllGroups() to // be sure all nodes are locked, if necessary.) template void _do_for_each_connected( NodeManager* manager, NodeRef* origin, NodeGroup* inGroup, bool recurse, Op operation, _for_each_state* state) { // PRINT(("### _do_for_each_connected()\n")); ASSERT(manager->IsLocked()); ASSERT(origin); ASSERT(state); status_t err __attribute__((unused)); if(state->visited.find(origin->id()) != state->visited.end()) { // PRINT(("### already visited\n")); // already visited return; } // used to walk connections vector connections; // mark visited state->visited.insert(origin->id()); // walk input connections origin->getInputConnections(connections); for(uint32 n = 0; n < connections.size(); ++n) { if(!connections[n].isValid()) continue; // PRINT(("# source: %ld\n", connections[n].sourceNode())); NodeRef* targetRef; err = manager->getNodeRef( connections[n].sourceNode(), &targetRef); ASSERT(err == B_OK); ASSERT(targetRef); if(inGroup && targetRef->group() != inGroup) { // PRINT(("# .group mismatch\n")); // don't need to visit return; } // invoke operation // if(lockRef) // targetRef->lock(); operation(targetRef); // if(lockRef) // targetRef->unlock(); // recurse? if(recurse) _do_for_each_connected( manager, targetRef, inGroup, true, operation, state); } // walk output connections connections.clear(); origin->getOutputConnections(connections); for(uint32 n = 0; n < connections.size(); ++n) { // PRINT(("# dest: %ld\n", connections[n].destinationNode())); if(!connections[n].isValid()) continue; NodeRef* targetRef; err = manager->getNodeRef( connections[n].destinationNode(), &targetRef); ASSERT(err == B_OK); ASSERT(targetRef); if(inGroup && targetRef->group() != inGroup) { // PRINT(("# .group mismatch\n")); // don't need to visit return; } // invoke operation // if(lockRef) // targetRef->lock(); operation(targetRef); // if(lockRef) // targetRef->unlock(); // recurse? if(recurse) _do_for_each_connected( manager, targetRef, inGroup, true, operation, state); } } // dtor helpers inline void NodeManager::_clearGroup( NodeGroup* group) { Autolock _l(group); D_METHOD(( "NodeManager::_clearGroup()\n")); // stop group before clearing [10aug99] group->_stop(); int32 n; while((n = group->countNodes()) > 0) { group->removeNode(n-1); } // // [e.moon 7nov99] release the group // status_t err = remove_observer(this, group); // if(err < B_OK) { // // spew diagnostics // PRINT(( // "!!! NodeManager::_clearGroup(): remove_observer(group %ld):\n" // " %s\n", // group->id(), // strerror(err))); // } } inline void NodeManager::_freeConnection( Connection* connection) { ASSERT(connection); D_METHOD(( "NodeManager::_freeConnection(%ld)\n", connection->id())); status_t err; // break if internal & still valid if( connection->isValid() && connection->flags() & Connection::INTERNAL && !(connection->flags() & Connection::LOCKED)) { D_METHOD(( "! breaking connection:\n" " source node: %ld\n" " source id: %ld\n" " source port: %ld\n" " dest node: %ld\n" " dest id: %ld\n" " dest port: %ld\n", connection->sourceNode(), connection->source().id, connection->source().port, connection->destinationNode(), connection->destination().id, connection->destination().port)); // do it D_ROSTER(("# roster->Disconnect()\n")); err = roster->Disconnect( connection->sourceNode(), connection->source(), connection->destinationNode(), connection->destination()); if(err < B_OK) { D_METHOD(( "!!! BMediaRoster::Disconnect('%s' -> '%s') failed:\n" " %s\n", connection->outputName(), connection->inputName(), strerror(err))); } } // delete delete connection; } // -------------------------------------------------------- // // *** ctor/dtor // -------------------------------------------------------- // NodeManager::~NodeManager() { D_METHOD(( "~NodeManager()\n")); ASSERT(IsLocked()); // make a list of nodes to be released list deadNodes; for(node_ref_map::const_iterator it = m_nodeRefMap.begin(); it != m_nodeRefMap.end(); ++it) { deadNodes.push_back((*it).second); } // ungroup all nodes // [e.moon 13oct99] making PPC compiler happy // for_each( // m_nodeGroupSet.begin(), // m_nodeGroupSet.end(), // bound_method( // *this, // &NodeManager::_clearGroup // ) // ); for(node_group_set::iterator it = m_nodeGroupSet.begin(); it != m_nodeGroupSet.end(); ++it) { _clearGroup(*it); } // delete groups ptr_set_delete( m_nodeGroupSet.begin(), m_nodeGroupSet.end()); m_nodeGroupSet.clear(); // deallocate all connections; disconnect internal nodes // [e.moon 13oct99] making PPC compiler happy // for_each( // m_conSourceMap.begin(), // m_conSourceMap.end(), // unary_map_function( // m_conSourceMap, // bound_method( // *this, // &NodeManager::_freeConnection // ) // ) // ); for(con_map::iterator it = m_conSourceMap.begin(); it != m_conSourceMap.end(); ++it) { _freeConnection((*it).second); } m_conSourceMap.clear(); m_conDestinationMap.clear(); // release all nodes for(list::const_iterator it = deadNodes.begin(); it != deadNodes.end(); ++it) { (*it)->release(); } if(m_nodeRefMap.size()) { // +++++ nodes will only remain if they have observers; cope! PRINT(("*** %ld nodes remaining!\n", m_nodeRefMap.size())); deadNodes.clear(); for(node_ref_map::const_iterator it = m_nodeRefMap.begin(); it != m_nodeRefMap.end(); ++it) deadNodes.push_back((*it).second); ptr_set_delete( deadNodes.begin(), deadNodes.end()); } // for_each( // m_nodeRefMap.begin(), // m_nodeRefMap.end(), // unary_map_function( // m_nodeRefMap, // mem_fun( // &NodeRef::release // ) // ) // ); // // // delete all nodes // ptr_map_delete( // m_nodeRefMap.begin(), // m_nodeRefMap.end()); // // // PRINT(( // "~NodeManager() done\n")); // } const char* const NodeManager::s_defaultGroupPrefix = B_TRANSLATE("No name"); const char* const NodeManager::s_timeSourceGroup = B_TRANSLATE("Time sources"); const char* const NodeManager::s_audioInputGroup = B_TRANSLATE("System audio input"); const char* const NodeManager::s_videoInputGroup = B_TRANSLATE("System video input"); const char* const NodeManager::s_audioMixerGroup = B_TRANSLATE("System audio mixer"); const char* const NodeManager::s_videoOutputGroup = B_TRANSLATE("System video output"); NodeManager::NodeManager( bool useAddOnHost) : ObservableLooper("NodeManager"), roster(BMediaRoster::Roster()), m_audioInputNode(0), m_videoInputNode(0), m_audioMixerNode(0), m_audioOutputNode(0), m_videoOutputNode(0), m_nextConID(1), m_existingNodesInit(false), m_useAddOnHost(useAddOnHost) { D_METHOD(( "NodeManager()\n")); ASSERT(roster); // create refs for common nodes _initCommonNodes(); // start the looper Run(); // initialize connection to the media roster D_ROSTER(("# roster->StartWatching(%p)\n", this)); roster->StartWatching(BMessenger(this)); } // -------------------------------------------------------- // // *** operations // -------------------------------------------------------- // // * ACCESS // fetches NodeRef corresponding to a given ID; returns // B_BAD_VALUE if no matching entry was found (and writes // a 0 into the provided pointer.) status_t NodeManager::getNodeRef( media_node_id id, NodeRef** outRef) const { Autolock _l(this); D_METHOD(( "NodeManager::getNodeRef(%ld)\n", id)); node_ref_map::const_iterator it = m_nodeRefMap.find(id); if(it == m_nodeRefMap.end()) { *outRef = 0; return B_BAD_VALUE; } *outRef = (*it).second; return B_OK; } // [13aug99] // fetches Connection corresponding to a given source/destination // on a given node. Returns an invalid connection and B_BAD_VALUE // if no matching connection was found. status_t NodeManager::findConnection( media_node_id node, const media_source& source, Connection* outConnection) const { Autolock _l(this); D_METHOD(( "NodeManager::findConnection()\n")); ASSERT(source != media_source::null); con_map::const_iterator it = m_conSourceMap.lower_bound(node); con_map::const_iterator itEnd = m_conSourceMap.upper_bound(node); for(; it != itEnd; ++it) if((*it).second->source() == source) { // copy connection *outConnection = *((*it).second); return B_OK; } *outConnection = Connection(); return B_BAD_VALUE; } status_t NodeManager::findConnection( media_node_id node, const media_destination& destination, Connection* outConnection) const { Autolock _l(this); D_METHOD(( "NodeManager::findConnection()\n")); ASSERT(destination != media_destination::null); con_map::const_iterator it = m_conDestinationMap.lower_bound(node); con_map::const_iterator itEnd = m_conDestinationMap.upper_bound(node); for(; it != itEnd; ++it) if((*it).second->destination() == destination) { // copy connection *outConnection = *((*it).second); return B_OK; } *outConnection = Connection(); return B_BAD_VALUE; } // [e.moon 28sep99] // fetches a Connection matching the given source and destination // nodes. Returns an invalid connection and B_BAD_VALUE if // no matching connection was found status_t NodeManager::findConnection( media_node_id sourceNode, media_node_id destinationNode, Connection* outConnection) const { Autolock _l(this); D_METHOD(( "NodeManager::findConnection(source %ld, dest %ld)\n", sourceNode, destinationNode)); con_map::const_iterator it = m_conSourceMap.lower_bound(sourceNode); con_map::const_iterator itEnd = m_conSourceMap.upper_bound(sourceNode); for(; it != itEnd; ++it) { if((*it).second->destinationNode() == destinationNode) { *outConnection = *((*it).second); return B_OK; } } *outConnection = Connection(); return B_BAD_VALUE; } // [e.moon 28sep99] // tries to find a route from 'nodeA' to 'nodeB'; returns // true if one exists, false if not. class NodeManager::_find_route_state { public: set visited; }; bool NodeManager::findRoute( media_node_id nodeA, media_node_id nodeB) { Autolock _l(this); D_METHOD(( "NodeManager::findRoute(%ld, %ld)\n", nodeA, nodeB)); status_t err; NodeRef* ref; err = getNodeRef(nodeA, &ref); if(err < B_OK) { PRINT(( "!!! NodeManager::findRoute(%" B_PRId32 ", %" B_PRId32 "): no ref for node %" B_PRId32 "\n", nodeA, nodeB, nodeA)); return false; } _find_route_state st; return _find_route_recurse(ref, nodeB, &st); } // implementation of above bool NodeManager::_find_route_recurse( NodeRef* origin, media_node_id target, _find_route_state* state) { ASSERT(IsLocked()); ASSERT(origin); ASSERT(state); status_t err __attribute__((unused)); // node already visited? if(state->visited.find(origin->id()) != state->visited.end()) { return false; } // mark node visited state->visited.insert(origin->id()); vector connections; // walk input connections origin->getInputConnections(connections); for(uint32 n = 0; n < connections.size(); ++n) { if(!connections[n].isValid()) continue; // test against target if(connections[n].sourceNode() == target) return true; // SUCCESS // recurse NodeRef* ref; err = getNodeRef( connections[n].sourceNode(), &ref); ASSERT(err == B_OK); ASSERT(ref); if(_find_route_recurse( ref, target, state)) return true; // SUCCESS } // walk output connections connections.clear(); origin->getOutputConnections(connections); for(uint32 n = 0; n < connections.size(); ++n) { if(!connections[n].isValid()) continue; // test against target if(connections[n].destinationNode() == target) return true; // SUCCESS // recurse NodeRef* ref; err = getNodeRef( connections[n].destinationNode(), &ref); ASSERT(err == B_OK); ASSERT(ref); if(_find_route_recurse( ref, target, state)) return true; // SUCCESS } return false; // FAILED } // fetches Connection corresponding to a given source or // destination; Returns an invalid connection and B_BAD_VALUE if // none found. // * Note: this is the slowest possible way to look up a // connection. Since the low-level source/destination // structures don't include a node ID, and a destination // port can differ from its node's control port, a linear // search of all known connections is performed. Only // use these methods if you have no clue what node the // connection corresponds to. status_t NodeManager::findConnection( const media_source& source, Connection* outConnection) const { Autolock _l(this); D_METHOD(( "NodeManager::findConnection()\n")); ASSERT(source != media_source::null); for(con_map::const_iterator it = m_conSourceMap.begin(); it != m_conSourceMap.end(); ++it) { if((*it).second->source() == source) { // copy connection *outConnection = *((*it).second); return B_OK; } } *outConnection = Connection(); return B_BAD_VALUE; } status_t NodeManager::findConnection( const media_destination& destination, Connection* outConnection) const { Autolock _l(this); D_METHOD(( "NodeManager::findConnection()\n")); ASSERT(destination != media_destination::null); for(con_map::const_iterator it = m_conDestinationMap.begin(); it != m_conDestinationMap.end(); ++it) { if((*it).second->destination() == destination) { // copy connection *outConnection = *((*it).second); return B_OK; } } *outConnection = Connection(); return B_BAD_VALUE; } // fetch NodeRefs for system nodes (if a particular node doesn't // exist, these methods return 0) NodeRef* NodeManager::audioInputNode() const { Autolock _l(this); return m_audioInputNode; } NodeRef* NodeManager::videoInputNode() const { Autolock _l(this); return m_videoInputNode; } NodeRef* NodeManager::audioMixerNode() const { Autolock _l(this); return m_audioMixerNode; } NodeRef* NodeManager::audioOutputNode() const { Autolock _l(this); return m_audioOutputNode; } NodeRef* NodeManager::videoOutputNode() const { Autolock _l(this); return m_videoOutputNode; } // fetch groups by index // - you can write-lock the manager during sets of calls to these methods; // this ensures that the group set won't change. The methods do lock // the group internally, so locking isn't explicitly required. uint32 NodeManager::countGroups() const { Autolock _l(this); D_METHOD(( "NodeManager::countGroups()\n")); return m_nodeGroupSet.size(); } NodeGroup* NodeManager::groupAt( uint32 index) const { Autolock _l(this); D_METHOD(( "NodeManager::groupAt()\n")); return (index < m_nodeGroupSet.size()) ? m_nodeGroupSet[index] : 0; } // look up a group by unique ID; returns B_BAD_VALUE if no // matching group was found status_t NodeManager::findGroup(uint32 id, NodeGroup** outGroup) const { Autolock _l(this); D_METHOD(("NodeManager::findGroup(id)\n")); node_group_set::const_iterator it; for (it = m_nodeGroupSet.begin(); it != m_nodeGroupSet.end(); it++) { if ((*it)->id() == id) { *outGroup = *it; return B_OK; } } *outGroup = 0; return B_BAD_VALUE; } // look up a group by name; returns B_NAME_NOT_FOUND if // no group matching the name was found. status_t NodeManager::findGroup(const char* name, NodeGroup** outGroup) const { Autolock _l(this); D_METHOD(("NodeManager::findGroup(name)\n")); node_group_set::const_iterator it; for (it = m_nodeGroupSet.begin(); it != m_nodeGroupSet.end(); it++) { if (strcmp((*it)->name(), name) == 0) { *outGroup = *it; return B_OK; } } *outGroup = 0; return B_BAD_VALUE; } // merge the given source group to the given destination; // empties and releases the source group status_t NodeManager::mergeGroups( NodeGroup* sourceGroup, NodeGroup* destinationGroup) { Autolock _l(this); D_METHOD(( "NodeManager::mergeGroups(name)\n")); // [5feb00 c.lenz] already merged if(sourceGroup->id() == destinationGroup->id()) return B_OK; if(sourceGroup->isReleased() || destinationGroup->isReleased()) return B_NOT_ALLOWED; for(uint32 n = sourceGroup->countNodes(); n; --n) { NodeRef* node = sourceGroup->nodeAt(n-1); ASSERT(node); status_t err __attribute__((unused)) = sourceGroup->removeNode(n-1); ASSERT(err == B_OK); err = destinationGroup->addNode(node); ASSERT(err == B_OK); } // [7nov99 e.moon] delete the source group _removeGroup(sourceGroup); sourceGroup->release(); return B_OK; } // [e.moon 28sep99] // split group: given two nodes currently in the same group // that are not connected (directly OR indirectly), // this method removes outsideNode, and all nodes connected // to outsideNode, from the common group. These nodes are // then added to a new group, returned in 'outGroup'. The // new group has " split" appended to the end of the original // group's name. // // Returns B_NOT_ALLOWED if any of the above conditions aren't // met (ie. the nodes are in different groups or an indirect // route exists from one to the other), or B_OK if the group // was split successfully. class _changeNodeGroupFn { public: NodeGroup* newGroup; _changeNodeGroupFn( NodeGroup* _newGroup) : newGroup(_newGroup) { ASSERT(newGroup); } void operator()( NodeRef* node) { PRINT(( "_changeNodeGroupFn(): '%s'\n", node->name())); status_t err __attribute__((unused)); NodeGroup* oldGroup = node->group(); if(oldGroup) { err = oldGroup->removeNode(node); ASSERT(err == B_OK); } err = newGroup->addNode(node); ASSERT(err == B_OK); } }; status_t NodeManager::splitGroup( NodeRef* insideNode, NodeRef* outsideNode, NodeGroup** outGroup) { ASSERT(insideNode); ASSERT(outsideNode); Autolock _l(this); // ensure that no route exists from insideNode to outsideNode if(findRoute(insideNode->id(), outsideNode->id())) { PRINT(( "!!! NodeManager::splitGroup(): route exists from %" B_PRId32 " to %" B_PRId32 "\n", insideNode->id(), outsideNode->id())); return B_NOT_ALLOWED; } // make sure the nodes share a common group NodeGroup* oldGroup = insideNode->group(); if(!oldGroup) { PRINT(("!!! NodeManager::splitGroup(): invalid group\n")); return B_NOT_ALLOWED; } if(oldGroup != outsideNode->group()) { PRINT(( "!!! NodeManager::splitGroup(): mismatched groups for %" B_PRId32 " and %" B_PRId32 "\n", insideNode->id(), outsideNode->id())); return B_NOT_ALLOWED; } Autolock _l_old_group(oldGroup); // create the new group BString nameBuffer = oldGroup->name(); nameBuffer << " split"; NodeGroup* newGroup = createGroup( nameBuffer.String(), oldGroup->runMode()); *outGroup = newGroup; // move nodes connected to outsideNode from old to new group _changeNodeGroupFn fn(newGroup); fn(outsideNode); _for_each_state st; _do_for_each_connected( this, outsideNode, oldGroup, true, fn, &st); // [e.moon 1dec99] a single-node group takes that node's name if(newGroup->countNodes() == 1) newGroup->setName(newGroup->nodeAt(0)->name()); if(oldGroup->countNodes() == 1) oldGroup->setName(oldGroup->nodeAt(0)->name()); return B_OK; } // * INSTANTIATION & CONNECTION // Use these calls rather than the associated BMediaRoster() // methods to assure that the nodes and connections you set up // can be properly serialized & reconstituted. // basic BMediaRoster::InstantiateDormantNode() wrapper status_t NodeManager::instantiate( const dormant_node_info& info, NodeRef** outRef, bigtime_t timeout, uint32 nodeFlags) { Autolock _l(this); status_t err; D_METHOD(( "NodeManager::instantiate()\n")); // * instantiate media_node node; if(m_useAddOnHost) { err = AddOnHost::InstantiateDormantNode( info, &node, timeout); if(err < B_OK) { node = media_node::null; // attempt to relaunch BMessenger mess; err = AddOnHost::Launch(&mess); if(err < B_OK) { PRINT(( "!!! NodeManager::instantiate(): giving up on AddOnHost\n")); m_useAddOnHost = false; } else { err = AddOnHost::InstantiateDormantNode( info, &node, timeout); } } } if(!m_useAddOnHost || node == media_node::null) { D_ROSTER(( "# roster->InstantiateDormantNode()\n")); err = roster->InstantiateDormantNode(info, &node); } if(err < B_OK) { *outRef = 0; return err; } if(node == media_node::null) { // [e.moon 23oct99] +++++ // instantiating a soundcard input/output (emu10k or sonic_vibes) // produces a node ID of -1 (R4.5.2 x86) // PRINT(( "! InstantiateDormantNode(): invalid media node\n")); // bail out *outRef = 0; return B_BAD_INDEX; } // * create NodeRef NodeRef* ref = new NodeRef( node, this, nodeFlags, // | NodeRef::NO_ROSTER_WATCH, // +++++ e.moon 11oct99 NodeRef::_INTERNAL); ref->_setAddonHint(&info); _addRef(ref); // * return it *outRef = ref; return B_OK; } // SniffRef/Instantiate.../SetRefFor: a one-call interface // to create a node capable of playing a given media file. status_t NodeManager::instantiate( const entry_ref& file, uint64 requireNodeKinds, NodeRef** outRef, bigtime_t timeout, uint32 nodeFlags, bigtime_t* outDuration) { D_METHOD(( "NodeManager::instantiate(ref)\n")); // [no lock needed; calls the full form of instantiate()] status_t err; // * Find matching add-on dormant_node_info info; D_ROSTER(( "# roster->SniffRef()\n")); err = roster->SniffRef( file, requireNodeKinds, &info); if(err < B_OK) { *outRef = 0; return err; } // * Instantiate err = instantiate(info, outRef, timeout, nodeFlags); if(err < B_OK) return err; ASSERT(*outRef); // * Set file to play bigtime_t dur; D_ROSTER(("# roster->SetRefFor()\n")); err = roster->SetRefFor( (*outRef)->node(), file, false, &dur); if(err < B_OK) { PRINT(( "* SetRefFor() failed: %s\n", strerror(err))); } else if(outDuration) *outDuration = dur; // * update info [e.moon 29sep99] Autolock _l(*outRef); (*outRef)->_setAddonHint(&info, &file); return err; } // use this method to reference nodes internal to your // application. status_t NodeManager::reference( BMediaNode* node, NodeRef** outRef, uint32 nodeFlags) { Autolock _l(this); D_METHOD(( "NodeManager::reference()\n")); // should this node be marked _NO_RELEASE? NodeRef* ref = new NodeRef(node->Node(), this, nodeFlags, 0); _addRef(ref); *outRef = ref; return B_OK; } // the most flexible form of connect(): set the template // format as you would for BMediaRoster::Connect(). status_t NodeManager::connect( const media_output& output, const media_input& input, const media_format& templateFormat, Connection* outConnection /*=0*/) { Autolock _l(this); status_t err; D_METHOD(( "NodeManager::connect()\n")); // * Find (& create if necessary) NodeRefs NodeRef* outputRef; if(getNodeRef(output.node.node, &outputRef) < B_OK) outputRef = _addRefFor(output.node, 0); NodeRef* inputRef; if(getNodeRef(input.node.node, &inputRef) < B_OK) inputRef = _addRefFor(input.node, 0); // * Connect the nodes media_output finalOutput; media_input finalInput; media_format finalFormat = templateFormat; D_ROSTER(("# roster->Connect()\n")); err = roster->Connect( output.source, input.destination, &finalFormat, &finalOutput, &finalInput); if(err < B_OK) { if(outConnection) *outConnection = Connection(); connectionFailed(output, input, templateFormat, err); return err; } // * Create Connection instance; mark it INTERNAL // to automatically remove it upon shutdown. D_METHOD(( "! creating connection:\n" " source id: %" B_PRId32 "\n" " source port: %" B_PRId32 "\n" " dest id: %" B_PRId32 "\n" " dest port: %" B_PRId32 "\n", finalOutput.source.id, finalOutput.source.port, finalInput.destination.id, finalInput.destination.port)); uint32 cflags = Connection::INTERNAL; if(outputRef->m_info.node.kind & B_FILE_INTERFACE) { // 3aug99: // workaround for bug 19990802-12798: // connections involving a file-interface node aren't removed cflags |= Connection::LOCKED; } Connection* con = new Connection( m_nextConID++, output.node, finalOutput.source, finalOutput.name, input.node, finalInput.destination, finalInput.name, finalFormat, cflags); con->setOutputHint( output.name, output.format); con->setInputHint( input.name, input.format); con->setRequestedFormat( templateFormat); _addConnection(con); // [e.moon 10aug99] // fetch updated latencies; // [e.moon 28sep99] // scan all nodes connected directly OR indirectly to the // newly-connected nodes and update their latencies -- this includes // recalculating the node's 'producer delay' if in B_RECORDING mode. _updateLatenciesFrom(inputRef, true); // copy connection if(outConnection) { *outConnection = *con; } return B_OK; } // format-guessing form of connect(): tries to find // a common format between output & input before connection; // returns B_MEDIA_BAD_FORMAT if no common format appears // possible. // // NOTE: the specifics of the input and output formats are ignored; // this method only looks at the format type, and properly // handles wildcards at that level (B_MEDIA_NO_TYPE). status_t NodeManager::connect( const media_output& output, const media_input& input, Connection* outConnection /*=0*/) { D_METHOD(( "NodeManager::connect(guess)\n")); // [no lock needed; calls the full form of connect()] // defer to the pickier endpoint media_format f; if(output.format.type > B_MEDIA_UNKNOWN_TYPE) { f = output.format; if ((input.format.type > B_MEDIA_UNKNOWN_TYPE) && (f.type != input.format.type)) { connectionFailed(output, input, f, B_MEDIA_BAD_FORMAT); return B_MEDIA_BAD_FORMAT; } } else if(input.format.type > B_MEDIA_UNKNOWN_TYPE) { f = input.format; // output node doesn't care } else { // about as non-picky as two nodes can possibly be f.type = B_MEDIA_UNKNOWN_TYPE; } // +++++ ? revert to wildcard ? // let the nodes try to work out a common format from here return connect( output, input, f, outConnection); } // disconnects the connection represented by the provided // Connection object. if successful, returns B_OK. status_t NodeManager::disconnect( const Connection& connection) { Autolock _l(this); status_t err; D_METHOD(( "NodeManager::disconnect()\n")); // don't bother trying to disconnect an invalid connection if(!connection.isValid()) return B_NOT_ALLOWED; // make sure connection can be released if(connection.flags() & Connection::LOCKED) { PRINT(( "NodeManager::disconnect(): connection locked:\n" " %" B_PRId32 ":%s -> %" B_PRId32 ":%s\n", connection.sourceNode(), connection.outputName(), connection.destinationNode(), connection.inputName())); return B_NOT_ALLOWED; } D_METHOD(( "! breaking connection:\n" " source node: %" B_PRId32 "\n" " source id: %" B_PRId32 "\n" " source port: %" B_PRId32 "\n" " dest node: %" B_PRId32 "\n" " dest id: %" B_PRId32 "\n" " dest port: %" B_PRId32 "\n", connection.sourceNode(), connection.source().id, connection.source().port, connection.destinationNode(), connection.destination().id, connection.destination().port)); // do it D_ROSTER(("# roster->Disconnect()\n")); err = roster->Disconnect( connection.sourceNode(), connection.source(), connection.destinationNode(), connection.destination()); // mark disconnected if(err == B_OK) { con_map::iterator it = m_conSourceMap.lower_bound(connection.sourceNode()); con_map::iterator itEnd = m_conSourceMap.upper_bound(connection.sourceNode()); for(; it != itEnd; ++it) if((*it).second->id() == connection.id()) { (*it).second->m_disconnected = true; break; } ASSERT(it != itEnd); // [e.moon 28sep99] // fetch updated latencies; // scan all nodes connected directly OR indirectly to the // newly-connected nodes and update their latencies -- this includes // recalculating the node's 'producer delay' if in B_RECORDING mode. NodeRef* ref; if(getNodeRef(connection.sourceNode(), &ref) == B_OK) _updateLatenciesFrom(ref, true); if(getNodeRef(connection.destinationNode(), &ref) == B_OK) _updateLatenciesFrom(ref, true); } else { // +++++ if this failed, somebody somewhere is mighty confused PRINT(( "NodeManager::disconnect(): Disconnect() failed:\n %s\n", strerror(err))); } return err; } // * GROUP CREATION NodeGroup* NodeManager::createGroup( const char* name, BMediaNode::run_mode runMode) { Autolock _l(this); D_METHOD(( "NodeManager::createGroup()\n")); NodeGroup* g = new NodeGroup(name, this, runMode); _addGroup(g); return g; } // -------------------------------------------------------- // // *** node/connection iteration // *** MUST BE LOCKED for any of these calls // -------------------------------------------------------- // // usage: // For the first call, pass 'cookie' a pointer to a void* set to 0. // Returns B_BAD_INDEX when the set of nodes has been exhausted (and // re-zeroes the cookie, cleaning up any unused memory.) status_t NodeManager::getNextRef( NodeRef** ref, void** cookie) { ASSERT(IsLocked()); ASSERT(cookie); if(!*cookie) *cookie = new node_ref_map::iterator(m_nodeRefMap.begin()); node_ref_map::iterator* pit = (node_ref_map::iterator*)*cookie; // at end of set? if(*pit == m_nodeRefMap.end()) { delete pit; *cookie = 0; return B_BAD_INDEX; } // return next entry *ref = (*(*pit)).second; ++(*pit); return B_OK; } // +++++ reworked 13sep99: dtors wouldn't have been called with 'delete *cookie'! +++++ void NodeManager::disposeRefCookie( void** cookie) { if(!cookie) return; node_ref_map::iterator* it = reinterpret_cast(*cookie); ASSERT(it); if(it) delete it; } status_t NodeManager::getNextConnection( Connection* connection, void** cookie) { ASSERT(IsLocked()); ASSERT(cookie); if(!*cookie) *cookie = new con_map::iterator(m_conSourceMap.begin()); con_map::iterator* pit = (con_map::iterator*)*cookie; // at end of set? if(*pit == m_conSourceMap.end()) { delete pit; *cookie = 0; return B_BAD_INDEX; } // return next entry (ewww) *connection = *((*(*pit)).second); ++(*pit); return B_OK; } // +++++ reworked 13sep99: dtors wouldn't have been called with 'delete *cookie'! +++++ void NodeManager::disposeConnectionCookie( void** cookie) { if(!cookie) return; con_map::iterator* it = reinterpret_cast(*cookie); ASSERT(it); if(it) delete it; } // -------------------------------------------------------- // // *** BHandler impl // -------------------------------------------------------- // // +++++ support all Media Roster messages! +++++ // [e.moon 7nov99] implemented observer pattern for NodeGroup void NodeManager::MessageReceived(BMessage* message) { D_MESSAGE(( "NodeManager::MessageReceived(): %c%c%c%c\n", message->what >> 24, (message->what >> 16) & 0xff, (message->what >> 8) & 0xff, (message->what) & 0xff)); switch(message->what) { // *** Media Roster messages *** case B_MEDIA_NODE_CREATED: if(_handleNodesCreated(message) == B_OK) notify(message); break; case B_MEDIA_NODE_DELETED: _handleNodesDeleted(message); notify(message); break; case B_MEDIA_CONNECTION_MADE: _handleConnectionMade(message); notify(message); break; case B_MEDIA_CONNECTION_BROKEN: _handleConnectionBroken(message); // augments message! notify(message); break; case B_MEDIA_FORMAT_CHANGED: _handleFormatChanged(message); notify(message); break; default: _inherited::MessageReceived(message); break; } } // -------------------------------------------------------- // // *** IObservable hooks // -------------------------------------------------------- // void NodeManager::observerAdded( const BMessenger& observer) { BMessage m(M_OBSERVER_ADDED); m.AddMessenger("target", BMessenger(this)); observer.SendMessage(&m); } void NodeManager::observerRemoved( const BMessenger& observer) { BMessage m(M_OBSERVER_REMOVED); m.AddMessenger("target", BMessenger(this)); observer.SendMessage(&m); } void NodeManager::notifyRelease() { BMessage m(M_RELEASED); m.AddMessenger("target", BMessenger(this)); notify(&m); } void NodeManager::releaseComplete() { // tear down media roster connection D_ROSTER(("# roster->StopWatching()\n")); status_t err = roster->StopWatching( BMessenger(this)); if(err < B_OK) { PRINT(( "* roster->StopWatching() failed: %s\n", strerror(err))); } } // -------------------------------------------------------- // // *** ILockable impl. // -------------------------------------------------------- // bool NodeManager::lock( lock_t type, bigtime_t timeout) { D_LOCK(("*** NodeManager::lock(): %ld\n", find_thread(0))); status_t err = LockWithTimeout(timeout); D_LOCK(("*** NodeManager::lock() ACQUIRED: %ld\n", find_thread(0))); return err == B_OK; } bool NodeManager::unlock( lock_t type) { D_LOCK(("*** NodeManager::unlock(): %ld\n", find_thread(0))); Unlock(); D_LOCK(("*** NodeManager::unlock() RELEASED: %ld\n", find_thread(0))); return true; } bool NodeManager::isLocked( lock_t type) const { return IsLocked(); } // -------------------------------------------------------- // // *** internal operations (LOCK REQUIRED) // -------------------------------------------------------- // void NodeManager::_initCommonNodes() { ASSERT(IsLocked()); status_t err; media_node node; D_METHOD(( "NodeManager::_initCommonNodes()\n")); uint32 disableTransport = (NodeRef::NO_START_STOP | NodeRef::NO_SEEK | NodeRef::NO_PREROLL); // video input D_ROSTER(("# roster->GetVideoInput()\n")); err = roster->GetVideoInput(&node); if(err == B_OK) m_videoInputNode = _addRefFor( node, _userFlagsForKind(node.kind), _implFlagsForKind(node.kind)); // video output D_ROSTER(("# roster->GetVideoOutput()\n")); err = roster->GetVideoOutput(&node); if(err == B_OK) { if(m_videoInputNode && node.node == m_videoInputNode->id()) { // input and output nodes identical // [e.moon 20dec99] m_videoOutputNode = m_videoInputNode; } else { m_videoOutputNode = _addRefFor( node, _userFlagsForKind(node.kind) & ~NodeRef::NO_START_STOP, _implFlagsForKind(node.kind)); } } // audio mixer D_ROSTER(("# roster->GetAudioMixer()\n")); err = roster->GetAudioMixer(&node); if(err == B_OK) m_audioMixerNode = _addRefFor( node, _userFlagsForKind(node.kind) | disableTransport, _implFlagsForKind(node.kind)); // audio input D_ROSTER(("# roster->GetAudioInput()\n")); err = roster->GetAudioInput(&node); if(err == B_OK) m_audioInputNode = _addRefFor( node, _userFlagsForKind(node.kind), _implFlagsForKind(node.kind)); // audio output D_ROSTER(("# roster->GetAudioOutput()\n")); err = roster->GetAudioOutput(&node); if(err == B_OK) { if(m_audioInputNode && node.node == m_audioInputNode->id()) { // input and output nodes identical // [e.moon 20dec99] m_audioOutputNode = m_audioInputNode; // disable transport controls (the default audio output must always // be running, and can't be seeked.) m_audioOutputNode->setFlags( m_audioOutputNode->flags() | disableTransport); } else { m_audioOutputNode = _addRefFor( node, _userFlagsForKind(node.kind) | disableTransport, _implFlagsForKind(node.kind)); } } } void NodeManager::_addRef( NodeRef* ref) { ASSERT(ref); ASSERT(IsLocked()); D_METHOD(( "NodeManager::_addRef()\n")); // precondition: no NodeRef yet exists for this node // +++++ // [e.moon 21oct99] // sez this fails on startup w/ MediaKit 10.5 ASSERT( m_nodeRefMap.find(ref->id()) == m_nodeRefMap.end()); // add it // [e.moon 13oct99] PPC-friendly m_nodeRefMap.insert(node_ref_map::value_type(ref->id(), ref)); // [e.moon 8nov99] call hook nodeCreated(ref); } void NodeManager::_removeRef( media_node_id id) { ASSERT(IsLocked()); D_METHOD(( "NodeManager::_removeRef()\n")); node_ref_map::iterator it = m_nodeRefMap.find(id); // precondition: a NodeRef w/ matching ID is in the map ASSERT(it != m_nodeRefMap.end()); // [e.moon 8nov99] call hook nodeDeleted((*it).second); // remove it m_nodeRefMap.erase(it); } // create, add, return a NodeRef for the given external node; // must not already exist NodeRef* NodeManager::_addRefFor( const media_node& node, uint32 nodeFlags, uint32 nodeImplFlags) { ASSERT(IsLocked()); D_METHOD(( "NodeManager::_addRefFor()\n")); // precondition: no NodeRef yet exists for this node ASSERT( m_nodeRefMap.find(node.node) == m_nodeRefMap.end()); // // precondition: the node actually exists // live_node_info info; // D_ROSTER(("# roster->GetLiveNodeInfo()\n")); // ASSERT(roster->GetLiveNodeInfo(node, &info) == B_OK); // * create & add ref NodeRef* ref = new NodeRef(node, this, nodeFlags, nodeImplFlags); _addRef(ref); return ref; } void NodeManager::_addConnection( Connection* connection) { ASSERT(connection); ASSERT(IsLocked()); D_METHOD(( "NodeManager::_addConnection()\n")); // precondition: not already entered in either source or dest map // +++++ a more rigorous test would be a very good thing #ifdef DEBUG for(con_map::iterator it = m_conSourceMap.lower_bound(connection->sourceNode()); it != m_conSourceMap.upper_bound(connection->sourceNode()); ++it) { ASSERT((*it).second->id() != connection->id()); } for(con_map::iterator it = m_conDestinationMap.lower_bound(connection->destinationNode()); it != m_conDestinationMap.upper_bound(connection->destinationNode()); ++it) { ASSERT((*it).second->id() != connection->id()); } #endif // add to both maps // [e.moon 13oct99] PPC-friendly m_conSourceMap.insert( con_map::value_type( connection->sourceNode(), connection)); m_conDestinationMap.insert( con_map::value_type( connection->destinationNode(), connection)); // [e.moon 8nov99] call hook connectionMade(connection); } void NodeManager::_removeConnection( const Connection& connection) { ASSERT(IsLocked()); con_map::iterator itSource, itDestination; D_METHOD(( "NodeManager::_removeConnection()\n")); // [e.moon 8nov99] call hook connectionBroken(&connection); // precondition: one entry in both source & dest maps // +++++ a more rigorous test would be a very good thing for( itSource = m_conSourceMap.lower_bound(connection.sourceNode()); itSource != m_conSourceMap.upper_bound(connection.sourceNode()); ++itSource) if((*itSource).second->id() == connection.id()) break; ASSERT(itSource != m_conSourceMap.end()); for( itDestination = m_conDestinationMap.lower_bound(connection.destinationNode()); itDestination != m_conDestinationMap.upper_bound(connection.destinationNode()); ++itDestination) if((*itDestination).second->id() == connection.id()) break; ASSERT(itDestination != m_conDestinationMap.end()); // delete & remove from both maps delete (*itSource).second; m_conSourceMap.erase(itSource); m_conDestinationMap.erase(itDestination); } void NodeManager::_addGroup( NodeGroup* group) { ASSERT(group); ASSERT(IsLocked()); D_METHOD(( "NodeManager::_addGroup()\n")); // precondition: group not already in set ASSERT( find( m_nodeGroupSet.begin(), m_nodeGroupSet.end(), group) == m_nodeGroupSet.end()); // add m_nodeGroupSet.push_back(group); // // [e.moon 7nov99] observe // add_observer(this, group); } void NodeManager::_removeGroup( NodeGroup* group) { ASSERT(group); ASSERT(IsLocked()); D_METHOD(( "NodeManager::_removeGroup()\n")); node_group_set::iterator it = find( m_nodeGroupSet.begin(), m_nodeGroupSet.end(), group); // precondition: group in set if(it == m_nodeGroupSet.end()) { PRINT(( "* NodeManager::_removeGroup(%" B_PRId32 "): group not in set.\n", group->id())); return; } // remove it m_nodeGroupSet.erase(it); } // -------------------------------------------------------- // // *** Message Handlers *** // -------------------------------------------------------- // // now returns B_OK iff the message should be relayed to observers // [e.moon 11oct99] inline status_t NodeManager::_handleNodesCreated( BMessage* message) { ASSERT(IsLocked()); status_t err = B_OK; // fetch number of new nodes type_code type; int32 count; err = message->GetInfo("media_node_id", &type, &count); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesCreated(): GetInfo() failed:\n" " %s\n", strerror(err))); return err; } if(!count) { PRINT(( "* NodeManager::_handleNodesCreated(): no node IDs in message.\n")); return err; } D_METHOD(( "NodeManager::_handleNodesCreated(): %" B_PRId32 " nodes\n", count)); // * Create NodeRef instances for the listed nodes. // If this is the first message received from the Media Roster, // no connection info will be received for these nodes; store them // for now (indexed by service-thread port) and figure the connections // afterwards. // These nodes are mapped by port ID because that's the only criterion // available for matching a media_source to a node. typedef map port_ref_map; port_ref_map* initialNodes = m_existingNodesInit ? 0 : new port_ref_map; bool refsCreated = false; for(int32 n = 0; n < count; ++n) { // fetch ID of next node int32 id; err = message->FindInt32("media_node_id", n, &id); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesCreated(): FindInt32() failed:\n" " %s", strerror(err))); continue; } // look up the node media_node node; err = roster->GetNodeFor(id, &node); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesCreated(): roster->GetNodeFor(%" B_PRId32 ") failed:\n" " %s\n", id, strerror(err))); continue; } // look for an existing NodeRef; if not found, create one: NodeRef* ref = 0; if(getNodeRef(node.node, &ref) < B_OK) { // create one ref = _addRefFor( node, _userFlagsForKind(node.kind), // | NodeRef::NO_ROSTER_WATCH, // +++++ e.moon 11oct99 _implFlagsForKind(node.kind) | NodeRef::_CREATE_NOTIFIED); refsCreated = true; // // [e.moon 7nov99] call hook // nodeCreated(ref); } else { // PRINT(( // "* NodeManager::_handleNodesCreated():\n" // " found existing ref for '%s' (%ld)\n", // ref->name(), id)); // set _CREATE_NOTIFIED to prevent notification from being passed on // twice [e.moon 11oct99] if(!(ref->m_implFlags & NodeRef::_CREATE_NOTIFIED)) { ref->m_implFlags |= NodeRef::_CREATE_NOTIFIED; refsCreated = true; } // release the (duplicate) media_node reference err = roster->ReleaseNode(node); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesCreated(): roster->ReleaseNode(%" B_PRId32 ") failed:\n" " %s\n", id, strerror(err))); } } // add to the 'initial nodes' set if necessary // [e.moon 13oct99] PPC-friendly if(initialNodes) initialNodes->insert( port_ref_map::value_type( node.port, ref)); } if(initialNodes) { // populate current connections from each node in the set // PRINT(( // "* NodeManager::_handleNodesCreated(): POPULATING CONNECTIONS (%ld)\n", // initialNodes->size())); for(port_ref_map::const_iterator itDest = initialNodes->begin(); itDest != initialNodes->end(); ++itDest) { // walk each connected input for this node; find corresponding // output & fill in a new Connection instance. NodeRef* destRef = (*itDest).second; ASSERT(destRef); if(!(destRef->kind() & B_BUFFER_CONSUMER)) // no inputs continue; vector inputs; err = destRef->getConnectedInputs(inputs); // +++++ FAILED ON STARTUP [e.moon 28sep99]; haven't reproduced yet // [21oct99] failed again //ASSERT(err == B_OK); if(err < B_OK) { PRINT(( "!!! NodeManager::_handleNodesCreated():\n" " NodeRef('%s')::getConnectedInputs() failed:\n" " %s\n", destRef->name(), strerror(err))); continue; } // PRINT((" - %s: %ld inputs\n", destRef->name(), inputs.size())); for(vector::const_iterator itInput = inputs.begin(); itInput != inputs.end(); ++itInput) { // look for a matching source node by port ID: const media_input& input = *itInput; port_ref_map::const_iterator itSource = initialNodes->find( input.source.port); if(itSource == initialNodes->end()) { // source not found! PRINT(( "* NodeManager::_handleNodesCreated():\n" " Building initial Connection set: couldn't find source node\n" " connected to input '%s' of '%s' (source port %" B_PRId32 ").\n", input.name, destRef->name(), input.source.port)); continue; } // found it; fetch matching output NodeRef* sourceRef = (*itSource).second; ASSERT(sourceRef); media_output output; err = sourceRef->findOutput(input.source, &output); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesCreated():\n" " Building initial Connection set: couldn't find output\n" " in node '%s' connected to input '%s' of '%s'.\n", sourceRef->name(), input.name, destRef->name())); continue; } // sanity check // ASSERT(input.source == output.source); // ASSERT(input.destination == output.destination); // diagnostics [e.moon 11jan00] if(input.source != output.source || input.destination != output.destination) { PRINT(( "!!! NodeManager::_handleNodesCreated():\n" " input/output mismatch for connection\n" " '%s' (%s) -> '%s' (%s)\n" " input.source: port %" B_PRId32 ", ID %" B_PRId32 "\n" " output.source: port %" B_PRId32 ", ID %" B_PRId32 "\n" " input.destination: port %" B_PRId32 ", ID %" B_PRId32 "\n" " output.destination: port %" B_PRId32 ", ID %" B_PRId32 "\n\n", sourceRef->name(), output.name, destRef->name(), input.name, input.source.port, input.source.id, output.source.port, output.source.id, input.destination.port, input.destination.id, output.destination.port, output.destination.id)); continue; } // instantiate & add connection Connection* con = new Connection( m_nextConID++, output.node, output.source, output.name, input.node, input.destination, input.name, input.format, 0); _addConnection(con); // // [e.moon 7nov99] call hook // connectionMade(con); // PRINT(( // "* NodeManager::_handleNodesCreated(): Found initial connection:\n" // " %s:%s -> %s:%s\n", // sourceRef->name(), con->outputName(), // destRef->name(), con->inputName())); } // for(vector ... } // for(port_ref_map ... // mark the ordeal as over & done with m_existingNodesInit = true; // clean up delete initialNodes; } // don't relay message if no new create notifications were received [e.moon 11oct99] return refsCreated ? B_OK : B_ERROR; } inline void NodeManager::_handleNodesDeleted( BMessage* message) { ASSERT(IsLocked()); D_METHOD(( "NodeManager::_handleNodesDeleted()\n")); // walk the list of deleted nodes, removing & cleaning up refs // (and any straggler connections) type_code type; int32 count; status_t err = message->GetInfo("media_node_id", &type, &count); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesDeleted(): GetInfo() failed:\n" " %s\n", strerror(err))); return; } if(!count) return; for(int32 n = 0; n < count; n++) { int32 id; err = message->FindInt32("media_node_id", n, &id); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesDeleted(): FindInt32() failed\n" " %s\n", strerror(err))); continue; } // fetch ref NodeRef* ref; err = getNodeRef(id, &ref); if(err < B_OK) { PRINT(( "* NodeManager::_handleNodesDeleted(): getNodeRef(%" B_PRId32 ") failed\n" " %s\n", id, strerror(err))); continue; } // find & remove any 'stuck' connections; notify any observers // that the connections have been removed vector stuckConnections; ref->getInputConnections(stuckConnections); ref->getOutputConnections(stuckConnections); BMessage message(B_MEDIA_CONNECTION_BROKEN); for(uint32 n = 0; n < stuckConnections.size(); ++n) { Connection& c = stuckConnections[n]; // generate a complete B_MEDIA_CONNECTION_BROKEN message // +++++ the message format may be extended in the future -- ick message.AddData("source", B_RAW_TYPE, &c.source(), sizeof(media_source)); message.AddData("destination", B_RAW_TYPE, &c.destination(), sizeof(media_destination)); message.AddInt32(_connectionField, c.id()); message.AddInt32(_sourceNodeField, c.sourceNode()); message.AddInt32(_destNodeField, c.destinationNode()); _removeConnection(c); } // +++++ don't notify if no stuck connections were found notify(&message); // ungroup if necessary if(ref->m_group) { ASSERT(!ref->isReleased()); ref->m_group->removeNode(ref); } // [e.moon 23oct99] mark the node released! ref->m_nodeReleased = true; // // [e.moon 7nov99] call hook // nodeDeleted(ref); // release it ref->release(); } // for(int32 n ... } inline void NodeManager::_handleConnectionMade( BMessage* message) { ASSERT(IsLocked()); D_METHOD(( "NodeManager::_handleConnectionMade()\n")); status_t err; for(int32 n = 0;;++n) { media_input input; media_output output; const void* data; ssize_t dataSize; // fetch output err = message->FindData("output", B_RAW_TYPE, n, &data, &dataSize); if(err < B_OK) { if(!n) { PRINT(( "* NodeManager::_handleConnectionMade(): no entries in message.\n")); } break; } if(dataSize < ssize_t(sizeof(media_output))) { PRINT(( "* NodeManager::_handleConnectionMade(): not enough data for output.\n")); break; } output = *(media_output*)data; // fetch input err = message->FindData("input", B_RAW_TYPE, n, &data, &dataSize); if(err < B_OK) { if(!n) { PRINT(( "* NodeManager::_handleConnectionMade(): no complete entries in message.\n")); } break; } if(dataSize < ssize_t(sizeof(media_input))) { PRINT(( "* NodeManager::_handleConnectionMade(): not enough data for input.\n")); break; } input = *(media_input*)data; // look for existing Connection instance Connection found; err = findConnection( output.node.node, output.source, &found); if(err == B_OK) { PRINT(( " - existing connection for %s -> %s found\n", found.outputName(), found.inputName())); continue; } // instantiate & add Connection Connection* con = new Connection( m_nextConID++, output.node, output.source, output.name, input.node, input.destination, input.name, input.format, 0); _addConnection(con); } } // augments message with source and destination node ID's inline void NodeManager::_handleConnectionBroken( BMessage* message) { D_METHOD(( "NodeManager::_handleConnectionBroken()\n")); status_t err; // walk the listed connections for(int32 n=0;;n++) { media_source source; const void* data; ssize_t dataSize; // fetch source err = message->FindData("source", B_RAW_TYPE, n, &data, &dataSize); if(err < B_OK) { if(!n) { PRINT(( "* NodeManager::_handleConnectionBroken(): incomplete entry in message.\n")); } break; } if(dataSize < ssize_t(sizeof(media_source))) { PRINT(( "* NodeManager::_handleConnectionBroken(): not enough data for source.\n")); continue; } source = *(media_source*)data; // look up the connection +++++ SLOW +++++ Connection con; err = findConnection(source, &con); if(err < B_OK) { PRINT(( "* NodeManager::_handleConnectionBroken(): connection not found:\n" " %" B_PRId32 ":%" B_PRId32 "\n", source.port, source.id)); // add empty entry to message message->AddInt32(_connectionField, 0); message->AddInt32(_sourceNodeField, 0); message->AddInt32(_destNodeField, 0); continue; } // add entry to the message message->AddInt32(_connectionField, con.id()); message->AddInt32(_sourceNodeField, con.sourceNode()); message->AddInt32(_destNodeField, con.destinationNode()); // // [e.moon 7nov99] call hook // connectionBroken(&con); // home free; delete the connection _removeConnection(con); } // for(int32 n ... } inline void NodeManager::_handleFormatChanged(BMessage *message) { D_METHOD(( "NodeManager::_handleFormatChanged()\n")); status_t err; ssize_t dataSize; // fetch source media_source* source; err = message->FindData("be:source", B_RAW_TYPE, (const void**)&source, &dataSize); if(err < B_OK) { PRINT(( "* NodeManager::_handleFormatChanged(): incomplete entry in message.\n")); return; } // fetch destination media_destination* destination; err = message->FindData("be:destination", B_RAW_TYPE, (const void**)&destination, &dataSize); if(err < B_OK) { PRINT(( "* NodeManager::_handleFormatChanged(): incomplete entry in message.\n")); return; } // fetch format media_format* format; err = message->FindData("be:format", B_RAW_TYPE, (const void**)&format, &dataSize); if(err < B_OK) { PRINT(( "* NodeManager::_handleFormatChanged(): incomplete entry in message.\n")); return; } // find matching connection for(con_map::const_iterator it = m_conSourceMap.begin(); it != m_conSourceMap.end(); ++it) { if((*it).second->source() == *source) { if((*it).second->destination() != *destination) { // connection defunct return; } // found (*it).second->m_format = *format; // attach node IDs to message message->AddInt32(_connectionField, (*it).second->id()); message->AddInt32(_sourceNodeField, (*it).second->sourceNode()); message->AddInt32(_destNodeField, (*it).second->destinationNode()); break; } } } // return flags appropriate for an external // node with the given 'kind' inline uint32 NodeManager::_userFlagsForKind( uint32 kind) { uint32 f = 0; if( // kind & B_TIME_SOURCE || kind & B_PHYSICAL_OUTPUT // || kind & B_SYSTEM_MIXER [now in initCommonNodes() e.moon 17nov99] ) f |= (NodeRef::NO_START_STOP | NodeRef::NO_SEEK | NodeRef::NO_PREROLL); // // [28sep99 e.moon] physical inputs may not be stopped for now; at // // least one sound input node (for emu10k) stops working when requested // // to stop. // // +++++ should this logic be in initCommonNodes()? // if( // kind & B_PHYSICAL_INPUT) // f |= NodeRef::NO_STOP; return f; } inline uint32 NodeManager::_implFlagsForKind( uint32 kind) { return 0; } // [e.moon 28sep99] latency updating // These methods must set the recording-mode delay for // any B_RECORDING nodes they handle. // +++++ abstract to 'for each' and 'for each from' // methods (template or callback?) // refresh cached latency for every node in the given group // (or all nodes if no group given.) inline void NodeManager::_updateLatencies( NodeGroup* group) { ASSERT(IsLocked()); if(group) { ASSERT(group->isLocked()); } if(group) { for(NodeGroup::node_set::iterator it = group->m_nodes.begin(); it != group->m_nodes.end(); ++it) { (*it)->_updateLatency(); } } else { for(node_ref_map::iterator it = m_nodeRefMap.begin(); it != m_nodeRefMap.end(); ++it) { (*it).second->_updateLatency(); } } } // refresh cached latency for every node attached to // AND INCLUDING the given origin node. // if 'recurse' is true, affects indirectly attached // nodes as well. inline void NodeManager::_updateLatenciesFrom( NodeRef* origin, bool recurse) { ASSERT(IsLocked()); // PRINT(("### NodeManager::_updateLatenciesFrom()\n")); origin->lock(); origin->_updateLatency(); origin->unlock(); _lockAllGroups(); // [e.moon 13oct99] _for_each_state st; _do_for_each_connected( this, origin, 0, // all groups recurse, #if __GNUC__ <= 2 mem_fun(&NodeRef::_updateLatency), #else [](NodeRef* node) { return node->_updateLatency(); }, #endif &st); _unlockAllGroups(); // [e.moon 13oct99] } // a bit of unpleasantness [e.moon 13oct99] void NodeManager::_lockAllGroups() { ASSERT(IsLocked()); for(node_group_set::iterator it = m_nodeGroupSet.begin(); it != m_nodeGroupSet.end(); ++it) { (*it)->lock(); } } void NodeManager::_unlockAllGroups() { ASSERT(IsLocked()); for(node_group_set::iterator it = m_nodeGroupSet.begin(); it != m_nodeGroupSet.end(); ++it) { (*it)->unlock(); } } // END -- NodeManager.cpp --