1 // NodeGroup.h (Cortex/NodeManager) 2 // 3 // * PURPOSE 4 // Represents a logical group of media kit nodes. 5 // Provides group-level operations (transport control 6 // and serialization, for example.) 7 // 8 // * NODE SYNC/LOOPING +++++ 9 // 10 // 22jul99 11 // ---------------------------- 12 // +++++ 13 // - cycling support needs to be thoroughly defined; ie. what 14 // can it do, and what can it NOT do. 15 // 16 // For example, a change in node latency will likely confuse 17 // the cycling mechanism. Is there any possible way to avoid 18 // this without explicit help from the node? The roster sends 19 // no notification, and even if it did the message might not 20 // arrive in time. Polling is possible, but ...ick. 21 // 22 // [this is assuming the 'just-in-time' cycling mechanism: 23 // seeks are queued as late as possible; if the latency increases 24 // significantly, the seek will be queued TOO late and the 25 // node will get out of sync.] 26 // 27 // ---------------------------- 28 // 14jul99 29 // 30 // How about handling addition of a node to a group while it's 31 // playing? The "effects insert" scenario: 32 // 33 // 1) you have a producer node, followed by (0..*) filters in 34 // series, followed by a consumer 35 // 2) the transport is started 36 // 3) you wish to connect a new filter in the chain with minimal 37 // interruption -- preferably a pause in output, resuming 38 // with media time & perf. time still in sync. 39 // 40 // Process: 41 // - instantiate the filter & do any setup needed 42 // - [tmStart = current media time; tpStart = current perf. time] 43 // - stop the transport; break the connection at the insert 44 // point and connect the new filter 45 // - calculate the new latency 46 // - cue a seek for all nodes: 47 // to tmStart+latency+pad, 48 // at tpStart+latency+pad - 1 49 // - cue a start for all nodes: 50 // at tpStart+latency+pad 51 // 52 // (pad is the estimated amount of time taken to stop, break 53 // & make connections, etc. It can probably be determined in 54 // a test loop.) 55 // 56 // With the current NodeManager grouping behavior, this operation 57 // would split the NodeGroup, then (provided the filter insertion 58 // works) join it again. This is such a common procedure, though, 59 // that an 'insert' operation at the NodeManager level would be 60 // pretty damn handy. So would a 'remove insert' operation (given 61 // a node with a single input and output, connect its input's source 62 // to its output's destination, with the original format.) 63 // 64 // * HISTORY 65 // e.moon 29sep99 Made thread control variables 'volatile'. 66 // e.moon 6jul99 Begun 67 68 #ifndef __NodeGroup_H__ 69 #define __NodeGroup_H__ 70 71 #include "ObservableHandler.h" 72 #include "observe.h" 73 #include "ILockable.h" 74 75 // +++++ [e.moon 3dec99] need to include these for calcLatencyFn impl 76 // +++++ YUCK 77 #include "NodeRef.h" 78 #include <MediaRoster.h> 79 80 #include <vector> 81 82 #include <Locker.h> 83 #include <MediaNode.h> 84 #include <String.h> 85 86 class BTimeSource; 87 88 #include "cortex_defs.h" 89 90 #if CORTEX_XML 91 #include "IPersistent.h" 92 #endif 93 94 __BEGIN_CORTEX_NAMESPACE 95 96 class NodeManager; 97 class NodeRef; 98 99 class GroupCycleThread; 100 101 class NodeGroup : 102 public ObservableHandler, 103 public ILockable { 104 105 typedef ObservableHandler _inherited; 106 107 friend NodeManager; 108 friend NodeRef; 109 110 public: // *** messages 111 enum message_t { 112 // groupID: int32 113 // target: BMessenger 114 M_OBSERVER_ADDED =NodeGroup_message_base, 115 M_OBSERVER_REMOVED, 116 M_RELEASED, 117 118 // groupID: int32 119 // nodeID: int32 120 M_NODE_ADDED, 121 M_NODE_REMOVED, 122 123 // groupID: int32 124 // transportState: int32 125 // ++++++ include the following? 126 // runMode: int32 127 // [mediaStart: bigtime_t] only sent if changed 128 // [mediaEnd: bigtime_t] only sent if changed 129 M_TRANSPORT_STATE_CHANGED, 130 131 // groupID: int32 132 // timeSourceID: int32 +++++ should be a node? 133 M_TIME_SOURCE_CHANGED, 134 135 // groupID: int32 136 // runMode: int32 137 M_RUN_MODE_CHANGED, 138 139 // Set a new time source: 140 // timeSourceNode: media_node 141 M_SET_TIME_SOURCE, 142 143 // Set a run mode 144 // runMode: int32 (must be a valid run mode -- not 0!) 145 M_SET_RUN_MODE, 146 147 // Set new start/end position: 148 // position: bigtime_t (int64) 149 M_SET_START_POSITION, //K 150 M_SET_END_POSITION, //L 151 152 // Transport controls: 153 M_PREROLL, 154 M_START, 155 M_STOP, 156 M_ROLL // [e.moon 11oct99] 157 }; 158 159 public: // *** types 160 161 // transport state 162 enum transport_state_t { 163 TRANSPORT_INVALID, 164 TRANSPORT_STOPPED, 165 TRANSPORT_STARTING, 166 TRANSPORT_RUNNING, 167 TRANSPORT_ROLLING, // [e.moon 11oct99] 168 TRANSPORT_STOPPING 169 }; 170 171 // [em 1feb00] flags 172 enum flag_t { 173 // no new nodes may be added 174 GROUP_LOCKED = 1 175 }; 176 177 public: // *** ctor/dtor 178 179 // free the group, including all nodes within it 180 // (this call will result in the eventual deletion of the object.) 181 // returns B_OK on success; B_NOT_ALLOWED if release() has 182 // already been called; other error codes if the Media Roster 183 // call fails. 184 185 status_t release(); 186 187 // call release() rather than deleting NodeGroup objects 188 virtual ~NodeGroup(); 189 190 public: // *** const accessors 191 // [e.moon 13oct99] moved method definition here to keep inline 192 // in the face of a balky PPC compiler 193 inline uint32 id() const { return m_id; } 194 195 public: // *** content accessors 196 197 // name access 198 const char* name() const; 199 status_t setName(const char* name); 200 201 // node access 202 // - you can write-lock the group during sets of calls to these methods; 203 // this ensures that the node set won't change. The methods do lock 204 // the group internally, so locking isn't explicitly required. 205 uint32 countNodes() const; 206 NodeRef* nodeAt( 207 uint32 index) const; 208 209 // add/remove nodes: 210 // - you may only add a node with no current group. 211 // - nodes added during playback will be started; 212 // nodes removed during playback will be stopped (unless 213 // the NO_START_STOP transport restriction flag is set 214 // for a given node.) 215 216 status_t addNode( 217 NodeRef* node); 218 219 status_t removeNode( 220 NodeRef* node); 221 222 status_t removeNode( 223 uint32 index); 224 225 // group flag access 226 227 uint32 groupFlags() const; 228 229 status_t setGroupFlags( 230 uint32 flags); 231 232 // returns true if one or more nodes in the group have cycling 233 // enabled, and the start- and end-positions are valid 234 bool canCycle() const; 235 236 public: // *** TRANSPORT POSITIONING 237 238 // Fetch the current transport state 239 240 transport_state_t transportState() const; 241 242 // Set the starting media time: 243 // This is the point at which playback will begin in any media 244 // files/documents being played by the nodes in this group. 245 // When cycle mode is enabled, this is the point to which each 246 // node will be seek'd at the end of each cycle (loop). 247 // 248 // The starting time can't be changed in the B_OFFLINE run mode 249 // (this call will return an error.) 250 251 status_t setStartPosition( 252 bigtime_t start); //nyi 253 254 // Fetch the starting position: 255 256 bigtime_t startPosition() const; //nyi 257 258 // Set the ending media time: 259 // This is the point at which playback will end relative to 260 // media documents begin played by the nodes in this group; 261 // in cycle mode, this specifies the loop point. If the 262 // ending time is less than or equal to the starting time, 263 // the transport will continue until stopped manually. 264 // If the end position is changed while the transport is playing, 265 // it must take effect retroactively (if it's before the current 266 // position and looping is enabled, all nodes must 'warp' to 267 // the proper post-loop position.) +++++ echk! 268 // 269 // The ending time can't be changed if run mode is B_OFFLINE and 270 // the transport is running (this call will return an error.) 271 272 status_t setEndPosition( 273 bigtime_t end); //nyi 274 275 // Fetch the end position: 276 // Note that if the end position is less than or equal to the start 277 // position, it's ignored. 278 279 bigtime_t endPosition() const; //nyi 280 281 public: // *** TRANSPORT OPERATIONS 282 283 // Preroll the group: 284 // Seeks, then prerolls, each node in the group (honoring the 285 // NO_SEEK and NO_PREROLL flags.) This ensures that the group 286 // can start as quickly as possible. 287 // 288 // Returns B_NOT_ALLOWED if the transport is running. 289 290 status_t preroll(); 291 292 // Start all nodes in the group: 293 // Nodes with the NO_START_STOP flag aren't molested. 294 295 status_t start(); 296 297 // Stop all nodes in the group: 298 // Nodes with the NO_START_STOP flag aren't molested. 299 300 status_t stop(); 301 302 // Roll all nodes in the group: 303 // Queues a start and stop atomically (via BMediaRoster::RollNode()). 304 // Returns B_NOT_ALLOWED if endPosition <= startPosition. 305 306 status_t roll(); 307 308 public: // *** TIME SOURCE & RUN-MODE OPERATIONS 309 310 // getTimeSource(): 311 // returns B_ERROR if no time source has been set; otherwise, 312 // returns the node ID of the current time source for all 313 // nodes in the group. 314 // 315 // setTimeSource(): 316 // Calls SetTimeSourceFor() on every node in the group. 317 // The group must be stopped; B_NOT_ALLOWED will be returned 318 // if the state is TRANSPORT_RUNNING or TRANSPORT_ROLLING. 319 320 status_t getTimeSource( 321 media_node* outTimeSource) const; 322 323 status_t setTimeSource( 324 const media_node& timeSource); 325 326 // run mode access: 327 // Sets the default run mode for the group. This will be 328 // applied to every node with a wildcard (0) run mode. 329 // 330 // Special case: if the run mode is B_OFFLINE, it will be 331 // applied to all nodes in the group. 332 333 status_t setRunMode( 334 BMediaNode::run_mode mode); //nyi 335 336 BMediaNode::run_mode runMode() const; //nyi 337 338 public: // *** BHandler 339 virtual void MessageReceived( 340 BMessage* message); 341 342 #if CORTEX_XML 343 public: // *** IPersistent 344 // +++++ 345 346 // Default constructor 347 NodeGroup(); 348 349 #endif /*CORTEX_XML*/ 350 351 public: // *** IObservable: [19aug99] 352 virtual void observerAdded( 353 const BMessenger& observer); 354 355 virtual void observerRemoved( 356 const BMessenger& observer); 357 358 virtual void notifyRelease(); 359 360 virtual void releaseComplete(); 361 362 public: // *** ILockable: [21jul99] 363 // Each NodeGroup has a semaphore (BLocker). 364 // Only WRITE locking is allowed! 365 366 bool lock( 367 lock_t type=WRITE, 368 bigtime_t timeout=B_INFINITE_TIMEOUT); 369 bool unlock( 370 lock_t type=WRITE); 371 bool isLocked( 372 lock_t type=WRITE) const; 373 374 protected: // *** ctor (accessible to NodeManager) 375 NodeGroup( 376 const char* name, 377 NodeManager* manager, 378 BMediaNode::run_mode runMode=BMediaNode::B_INCREASE_LATENCY); 379 380 protected: // *** internal operations 381 382 static uint32 NextID(); 383 384 protected: // *** ref->group communication (LOCK REQUIRED) 385 386 // When a NodeRef's cycle state (ie. looping or not looping) 387 // changes, it must pass that information on via this method. 388 // +++++ group cycle thread 389 void _refCycleChanged( 390 NodeRef* ref); 391 392 // when a cycling node's latency changes, call this method. 393 // +++++ shouldn't there be a general latency-change hook? 394 void _refLatencyChanged( 395 NodeRef* ref); 396 397 // when a NodeRef receives notification that it has been stopped, 398 // but is labeled as still running, it must call this method. 399 // [e.moon 11oct99: roll/B_OFFLINE support] 400 void _refStopped( 401 NodeRef* ref); 402 403 private: // *** transport helpers (LOCK REQUIRED) 404 405 // Preroll all nodes in the group; this is the implementation 406 // of preroll(). 407 // *** this method should not be called from the transport thread 408 // (since preroll operations can block for a relatively long time.) 409 410 status_t _preroll(); 411 412 // Start all nodes in the group; this is the implementation of 413 // start(). 414 // 415 // (this may be called from the transport thread or from 416 // an API-implementation method.) 417 418 status_t _start(); 419 420 // Stop all nodes in the group; this is the implementation of 421 // stop(). Fails if the run mode is B_OFFLINE; use _roll() instead 422 // in that case. 423 // 424 // (this may be called from the transport thread or from 425 // an API-implementation method.) 426 427 status_t _stop(); 428 429 // Roll all nodes in the group; this is the implementation of 430 // roll(). 431 // 432 // (this may be called from the transport thread or from 433 // an API-implementation method.) 434 435 status_t _roll(); //nyi [11oct99 e.moon] 436 437 // State transition; notify listeners 438 inline void _changeState( 439 transport_state_t to); 440 441 // Enforce a state transition, and notify listeners 442 inline void _changeState( 443 transport_state_t from, 444 transport_state_t to); 445 446 447 private: // *** transport thread guts 448 // void _initPort(); 449 // void _initThread(); 450 // 451 // static status_t _TransportThread(void* user); 452 // void _transportThread(); 453 454 // functor: calculates latency of each node it's handed, caching 455 // the largest one found; includes initial latency if nodes report it. 456 class calcLatencyFn { public: 457 bigtime_t& maxLatency; 458 calcLatencyFn(bigtime_t& _m) : maxLatency(_m) {} 459 void operator()(NodeRef* r) { 460 ASSERT(r); 461 if(!(r->node().kind & B_BUFFER_PRODUCER)) { 462 // node can't incur latency 463 return; 464 } 465 466 bigtime_t latency; 467 status_t err = 468 BMediaRoster::Roster()->GetLatencyFor( 469 r->node(), 470 &latency); 471 if(err < B_OK) { 472 PRINT(( 473 "* calcLatencyFn: GetLatencyFor() failed: %s\n", 474 strerror(err))); 475 return; 476 } 477 bigtime_t add; 478 err = BMediaRoster::Roster()->GetInitialLatencyFor( 479 r->node(), 480 &add); 481 if(err < B_OK) { 482 PRINT(( 483 "* calcLatencyFn: GetInitialLatencyFor() failed: %s\n", 484 strerror(err))); 485 } 486 else 487 latency += add; 488 if(latency > maxLatency) 489 maxLatency = latency; 490 } 491 }; 492 493 friend class calcLatencyFn; 494 495 protected: // *** cycle thread & helpers (LOCK REQUIRED) 496 497 // set up the cycle thread (including its kernel port) 498 status_t _initCycleThread(); 499 500 // shut down the cycle thread/port 501 status_t _destroyCycleThread(); 502 503 // 1) do the current positions specify a valid cycle region? 504 // 2) are any nodes in the group cycle-enabled? 505 bool _cycleValid(); 506 507 // initialize the next cycle 508 void _cycleInit( 509 bigtime_t startTime); 510 511 // add a ref to the cycle set (in proper order, based on latency) 512 void _cycleAddRef( 513 NodeRef* ref); 514 515 // remove a ref from the cycle set 516 void _cycleRemoveRef( 517 NodeRef* ref); 518 519 // fetches the next cycle boundary (performance time of next loop) 520 bigtime_t _cycleBoundary() const; 521 522 // cycle thread impl. 523 static status_t _CycleThread(void* user); 524 void _cycleThread(); 525 526 // cycle service: seek all nodes & initiate next cycle 527 void _handleCycleService(); 528 529 private: // *** members 530 531 // lock 532 mutable BLocker m_lock; 533 534 // parent object 535 NodeManager* m_manager; 536 537 // unique group ID (non-0) 538 const uint32 m_id; 539 static uint32 s_nextID; 540 541 // group name 542 BString m_name; 543 544 // group contents 545 typedef vector<NodeRef*> node_set; 546 node_set m_nodes; 547 548 // flags & state 549 uint32 m_flags; 550 transport_state_t m_transportState; 551 552 // default run mode applied to all nodes with a wildcard (0) 553 // run mode. 554 BMediaNode::run_mode m_runMode; 555 556 // current time source 557 media_node m_timeSource; 558 BTimeSource* m_timeSourceObj; 559 560 // slated to die? 561 bool m_released; 562 563 // --------------------------- 564 // 10aug99 565 // cycle thread implementation 566 // --------------------------- 567 568 enum cycle_thread_msg_t { 569 _CYCLE_STOP, 570 _CYCLE_END_CHANGED, 571 _CYCLE_LATENCY_CHANGED 572 }; 573 574 thread_id m_cycleThread; 575 port_id m_cyclePort; 576 bool m_cycleThreadDone; 577 578 // when did the current cycle begin? 579 bigtime_t m_cycleStart; 580 581 // performance time at which the current cycle settings will 582 // be applied (ie. the first seek should be queued) 583 bigtime_t m_cycleDeadline; 584 585 // performance time at which the next cycle begins 586 bigtime_t m_cycleBoundary; 587 588 // the set of nodes currently in cycle mode 589 // ordered by latency (largest first) 590 node_set m_cycleNodes; 591 592 // count of nodes that have completed this cycle 593 // (once complete, deadline and boundary are reset, and any 594 // deferred start/end positions are applied.) 595 uint32 m_cycleNodesComplete; 596 597 // max latency of any cycling node 598 bigtime_t m_cycleMaxLatency; 599 600 // the minimum allowed loop time 601 static const bigtime_t s_minCyclePeriod = 1000LL; 602 603 // the amount of time to allow for Media Roster calls 604 // (StartNode, SeekNode, etc) to be handled 605 static const bigtime_t s_rosterLatency = 1000LL; // +++++ probably high 606 607 // position state 608 volatile bigtime_t m_startPosition; 609 volatile bigtime_t m_endPosition; 610 611 // changed positions deferred in cycle mode 612 volatile bool m_newStart; 613 volatile bigtime_t m_newStartPosition; 614 volatile bool m_newEnd; 615 volatile bigtime_t m_newEndPosition; 616 }; 617 618 619 __END_CORTEX_NAMESPACE 620 #endif /*__NodeGroup_H__*/ 621