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