1/*
2 * Copyright (c) 2002, 2003 Marcus Overhagen <Marcus@Overhagen.de>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files or portions
6 * thereof (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so, subject
10 * to the following conditions:
11 *
12 *  * Redistributions of source code must retain the above copyright notice,
13 *    this list of conditions and the following disclaimer.
14 *
15 *  * Redistributions in binary form must reproduce the above copyright notice
16 *    in the  binary, as well as this list of conditions and the following
17 *    disclaimer in the documentation and/or other materials provided with
18 *    the distribution.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 *
28 */
29
30#include <MediaEventLooper.h>
31#include <TimeSource.h>
32#include <scheduler.h>
33#include <Buffer.h>
34#include <ServerInterface.h>
35#include "debug.h"
36
37/*************************************************************
38 * protected BMediaEventLooper
39 *************************************************************/
40
41/* virtual */
42BMediaEventLooper::~BMediaEventLooper()
43{
44	CALLED();
45
46	// don't call Quit(); here, except if the user was stupid
47	if (fControlThread != -1) {
48		printf("You MUST call BMediaEventLooper::Quit() in your destructor!\n");
49		Quit();
50	}
51}
52
53/* explicit */
54BMediaEventLooper::BMediaEventLooper(uint32 apiVersion) :
55	BMediaNode("called by BMediaEventLooper"),
56	fControlThread(-1),
57	fCurrentPriority(B_URGENT_PRIORITY),
58	fSetPriority(B_URGENT_PRIORITY),
59	fRunState(B_UNREGISTERED),
60	fEventLatency(0),
61	fSchedulingLatency(0),
62	fBufferDuration(0),
63	fOfflineTime(0),
64	fApiVersion(apiVersion)
65{
66	CALLED();
67	fEventQueue.SetCleanupHook(BMediaEventLooper::_CleanUpEntry, this);
68	fRealTimeQueue.SetCleanupHook(BMediaEventLooper::_CleanUpEntry, this);
69}
70
71/* virtual */ void
72BMediaEventLooper::NodeRegistered()
73{
74	CALLED();
75	// Calling Run() should be done by the derived class,
76	// at least that's how it is documented by the BeBook.
77	// It appears that BeOS R5 called it here. Calling Run()
78	// twice doesn't hurt, and some nodes need it to be called here.
79	Run();
80}
81
82
83/* virtual */ void
84BMediaEventLooper::Start(bigtime_t performance_time)
85{
86	CALLED();
87	// This hook function is called when a node is started
88	// by a call to the BMediaRoster. The specified
89	// performanceTime, the time at which the node
90	// should start running, may be in the future.
91	fEventQueue.AddEvent(media_timed_event(performance_time, BTimedEventQueue::B_START));
92}
93
94
95/* virtual */ void
96BMediaEventLooper::Stop(bigtime_t performance_time,
97						bool immediate)
98{
99	CALLED();
100	// This hook function is called when a node is stopped
101	// by a call to the BMediaRoster. The specified performanceTime,
102	// the time at which the node should stop, may be in the future.
103	// If immediate is true, your node should ignore the performanceTime
104	// value and synchronously stop performance. When Stop() returns,
105	// you're promising not to write into any BBuffers you may have
106	// received from your downstream consumers, and you promise not
107	// to send any more buffers until Start() is called again.
108
109	if (immediate) {
110		// always be sure to add to the front of the queue so we can make sure it is
111		// handled before any buffers are sent!
112		performance_time = 0;
113	}
114	fEventQueue.AddEvent(media_timed_event(performance_time, BTimedEventQueue::B_STOP));
115}
116
117
118/* virtual */ void
119BMediaEventLooper::Seek(bigtime_t media_time,
120						bigtime_t performance_time)
121{
122	CALLED();
123	// This hook function is called when a node is asked to seek to
124	// the specified mediaTime by a call to the BMediaRoster.
125	// The specified performanceTime, the time at which the node
126	// should begin the seek operation, may be in the future.
127	fEventQueue.AddEvent(media_timed_event(performance_time, BTimedEventQueue::B_SEEK, NULL,
128		BTimedEventQueue::B_NO_CLEANUP, 0, media_time, NULL));
129}
130
131
132/* virtual */ void
133BMediaEventLooper::TimeWarp(bigtime_t at_real_time,
134							bigtime_t to_performance_time)
135{
136	CALLED();
137	// This hook function is called when the time source to which the
138	// node is slaved is repositioned (via a seek operation) such that
139	// there will be a sudden jump in the performance time progression
140	// as seen by the node. The to_performance_time argument indicates
141	// the new performance time; the change should occur at the real
142	// time specified by the at_real_time argument.
143
144	// place in the realtime queue
145	fRealTimeQueue.AddEvent(media_timed_event(at_real_time,	BTimedEventQueue::B_WARP,
146		NULL, BTimedEventQueue::B_NO_CLEANUP, 0, to_performance_time, NULL));
147
148	// BeBook: Your implementation of TimeWarp() should call through to BMediaNode::TimeWarp()
149	// BeBook: as well as all other inherited forms of TimeWarp()
150	// XXX should we do this here?
151	BMediaNode::TimeWarp(at_real_time, to_performance_time);
152}
153
154
155/* virtual */ status_t
156BMediaEventLooper::AddTimer(bigtime_t at_performance_time,
157							int32 cookie)
158{
159	CALLED();
160	// XXX what do we need to do here?
161	return BMediaNode::AddTimer(at_performance_time,cookie);
162}
163
164
165/* virtual */ void
166BMediaEventLooper::SetRunMode(run_mode mode)
167{
168	CALLED();
169	// The SetRunMode() hook function is called when someone requests that your node's run mode be changed.
170
171	// bump or reduce priority when switching from/to offline run mode
172	int32 priority;
173	priority = (mode == B_OFFLINE) ? min_c(B_NORMAL_PRIORITY, fSetPriority) : fSetPriority;
174	if (priority != fCurrentPriority) {
175		fCurrentPriority = priority;
176		if (fControlThread > 0) {
177			set_thread_priority(fControlThread, fCurrentPriority);
178			fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
179			printf("BMediaEventLooper: SchedulingLatency is %" B_PRId64 "\n",
180				fSchedulingLatency);
181		}
182	}
183
184	BMediaNode::SetRunMode(mode);
185}
186
187
188/* virtual */ void
189BMediaEventLooper::CleanUpEvent(const media_timed_event *event)
190{
191	CALLED();
192	// Implement this function to clean up after custom events you've created
193	// and added to your queue. It's called when a custom event is removed from
194	// the queue, to let you handle any special tidying-up that the event might require.
195}
196
197
198/* virtual */ bigtime_t
199BMediaEventLooper::OfflineTime()
200{
201	CALLED();
202	return fOfflineTime;
203}
204
205
206/* virtual */ void
207BMediaEventLooper::ControlLoop()
208{
209	CALLED();
210
211	bool is_realtime = false;
212	status_t err;
213	bigtime_t latency;
214	bigtime_t waituntil;
215	bigtime_t lateness;
216	for (;;) {
217		// while there are no events or it is not time for the earliest event,
218		// process messages using WaitForMessages. Whenever this funtion times out,
219		// we need to handle the next event
220		for (;;) {
221			if (RunState() == B_QUITTING)
222				return;
223			// BMediaEventLooper compensates your performance time by adding the event latency
224			// (see SetEventLatency()) and the scheduling latency (or, for real-time events,
225			// only the scheduling latency).
226
227			latency = fEventLatency + fSchedulingLatency;
228			waituntil = B_INFINITE_TIMEOUT;
229			if (fEventQueue.HasEvents()) {
230				const media_timed_event *firstEvent = fEventQueue.FirstEvent();
231				waituntil = TimeSource()->RealTimeFor(firstEvent->event_time, latency);
232				is_realtime = false;
233				lateness = firstEvent->queued_time - waituntil;
234				if (lateness > 0) {
235//					if (lateness > 1000)
236//						printf("node %02ld handling %12Ld at %12Ld -- %Ld late,  queued at %Ld now %12Ld \n",
237//							ID(), fEventQueue.FirstEventTime(), TimeSource()->Now(), lateness,
238//							firstEvent->queued_time, TimeSource()->RealTime());
239					is_realtime = false;
240					break;
241				}
242//				printf("node %02ld waiting for %12Ld that will happen at %12Ld\n", ID(), fEventQueue.FirstEventTime(), waituntil);
243			}
244			if (fRealTimeQueue.HasEvents()) {
245				const media_timed_event *firstEvent = fRealTimeQueue.FirstEvent();
246				bigtime_t temp;
247				temp = firstEvent->event_time - fSchedulingLatency;
248				lateness =  firstEvent->queued_time - temp;
249				if (lateness > 0) {
250					is_realtime = true;
251					break;
252				}
253				if (temp < waituntil) {
254					waituntil = temp;
255					is_realtime = true;
256				}
257			}
258			lateness = 0;	// remove any extraneous value if we get this far
259			err = WaitForMessage(waituntil);
260			if (err == B_TIMED_OUT)
261				break;
262		}
263		/// we have timed out - so handle the next event
264		media_timed_event event;
265		if (is_realtime)
266			err = fRealTimeQueue.RemoveFirstEvent(&event);
267		else
268			err = fEventQueue.RemoveFirstEvent(&event);
269
270//		printf("node %02ld handling  %12Ld  at %12Ld\n", ID(), event.event_time, TimeSource()->Now());
271
272		if (err == B_OK) DispatchEvent(&event, lateness, is_realtime);
273	}
274}
275
276
277thread_id
278BMediaEventLooper::ControlThread()
279{
280	CALLED();
281	return fControlThread;
282}
283
284/*************************************************************
285 * protected BMediaEventLooper
286 *************************************************************/
287
288
289BTimedEventQueue *
290BMediaEventLooper::EventQueue()
291{
292	CALLED();
293	return &fEventQueue;
294}
295
296
297BTimedEventQueue *
298BMediaEventLooper::RealTimeQueue()
299{
300	CALLED();
301	return &fRealTimeQueue;
302}
303
304
305int32
306BMediaEventLooper::Priority() const
307{
308	CALLED();
309	return fCurrentPriority;
310}
311
312
313int32
314BMediaEventLooper::RunState() const
315{
316	PRINT(6, "CALLED BMediaEventLooper::RunState()\n");
317	return fRunState;
318}
319
320
321bigtime_t
322BMediaEventLooper::EventLatency() const
323{
324	CALLED();
325	return fEventLatency;
326}
327
328
329bigtime_t
330BMediaEventLooper::BufferDuration() const
331{
332	CALLED();
333	return fBufferDuration;
334}
335
336
337bigtime_t
338BMediaEventLooper::SchedulingLatency() const
339{
340	CALLED();
341	return fSchedulingLatency;
342}
343
344
345status_t
346BMediaEventLooper::SetPriority(int32 priority)
347{
348	CALLED();
349
350	// clamp to a valid value
351	if (priority < 5)
352		priority = 5;
353
354	if (priority > 120)
355		priority = 120;
356
357	fSetPriority = priority;
358	fCurrentPriority = (RunMode() == B_OFFLINE) ? min_c(B_NORMAL_PRIORITY, fSetPriority) : fSetPriority;
359
360	if (fControlThread > 0) {
361		set_thread_priority(fControlThread, fCurrentPriority);
362		fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
363		printf("BMediaEventLooper: SchedulingLatency is %" B_PRId64 "\n",
364			fSchedulingLatency);
365	}
366
367	return B_OK;
368}
369
370
371void
372BMediaEventLooper::SetRunState(run_state state)
373{
374	CALLED();
375
376	// don't allow run state changes while quitting,
377	// also needed for correct terminating of the ControlLoop()
378	if (fRunState == B_QUITTING && state != B_TERMINATED)
379		return;
380
381	fRunState = state;
382}
383
384
385void
386BMediaEventLooper::SetEventLatency(bigtime_t latency)
387{
388	CALLED();
389	// clamp to a valid value
390	if (latency < 0)
391		latency = 0;
392
393	fEventLatency = latency;
394	write_port_etc(ControlPort(), GENERAL_PURPOSE_WAKEUP, 0, 0, B_TIMEOUT, 0);
395}
396
397
398void
399BMediaEventLooper::SetBufferDuration(bigtime_t duration)
400{
401	CALLED();
402	fBufferDuration = duration;
403}
404
405
406void
407BMediaEventLooper::SetOfflineTime(bigtime_t offTime)
408{
409	CALLED();
410	fOfflineTime = offTime;
411}
412
413
414void
415BMediaEventLooper::Run()
416{
417	CALLED();
418
419	if (fControlThread != -1)
420		return; // thread already running
421
422	// until now, the run state is B_UNREGISTERED, but we need to start in B_STOPPED state.
423	SetRunState(B_STOPPED);
424
425	char threadName[32];
426	sprintf(threadName, "%.20s control", Name());
427	fControlThread = spawn_thread(_ControlThreadStart, threadName, fCurrentPriority, this);
428	resume_thread(fControlThread);
429
430	// get latency information
431	fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
432	printf("BMediaEventLooper: SchedulingLatency is %" B_PRId64 "\n",
433		fSchedulingLatency);
434}
435
436
437void
438BMediaEventLooper::Quit()
439{
440	CALLED();
441
442	if (fRunState == B_TERMINATED)
443		return;
444
445	SetRunState(B_QUITTING);
446	close_port(ControlPort());
447	if (fControlThread != -1) {
448		status_t err;
449		wait_for_thread(fControlThread, &err);
450		fControlThread = -1;
451	}
452	SetRunState(B_TERMINATED);
453}
454
455
456void
457BMediaEventLooper::DispatchEvent(const media_timed_event *event,
458								 bigtime_t lateness,
459								 bool realTimeEvent)
460{
461	PRINT(6, "CALLED BMediaEventLooper::DispatchEvent()\n");
462
463	HandleEvent(event, lateness, realTimeEvent);
464
465	switch (event->type) {
466		case BTimedEventQueue::B_START:
467			SetRunState(B_STARTED);
468			break;
469
470		case BTimedEventQueue::B_STOP:
471			SetRunState(B_STOPPED);
472			break;
473
474		case BTimedEventQueue::B_SEEK:
475			/* nothing */
476			break;
477
478		case BTimedEventQueue::B_WARP:
479			/* nothing */
480			break;
481
482		default:
483			break;
484	}
485
486	_DispatchCleanUp(event);
487}
488
489/*************************************************************
490 * private BMediaEventLooper
491 *************************************************************/
492
493
494/* static */ int32
495BMediaEventLooper::_ControlThreadStart(void *arg)
496{
497	CALLED();
498	((BMediaEventLooper *)arg)->SetRunState(B_STOPPED);
499	((BMediaEventLooper *)arg)->ControlLoop();
500	((BMediaEventLooper *)arg)->SetRunState(B_QUITTING);
501	return 0;
502}
503
504
505/* static */ void
506BMediaEventLooper::_CleanUpEntry(const media_timed_event *event,
507								 void *context)
508{
509	PRINT(6, "CALLED BMediaEventLooper::_CleanUpEntry()\n");
510	((BMediaEventLooper *)context)->_DispatchCleanUp(event);
511}
512
513
514void
515BMediaEventLooper::_DispatchCleanUp(const media_timed_event *event)
516{
517	PRINT(6, "CALLED BMediaEventLooper::_DispatchCleanUp()\n");
518
519	// this function to clean up after custom events you've created
520	if (event->cleanup >= BTimedEventQueue::B_USER_CLEANUP)
521		CleanUpEvent(event);
522}
523
524/*
525// unimplemented
526BMediaEventLooper::BMediaEventLooper(const BMediaEventLooper &)
527BMediaEventLooper &BMediaEventLooper::operator=(const BMediaEventLooper &)
528*/
529
530/*************************************************************
531 * protected BMediaEventLooper
532 *************************************************************/
533
534
535status_t
536BMediaEventLooper::DeleteHook(BMediaNode *node)
537{
538	CALLED();
539	// this is the DeleteHook that gets called by the media server
540	// before the media node is deleted
541	Quit();
542	return BMediaNode::DeleteHook(node);
543}
544
545/*************************************************************
546 * private BMediaEventLooper
547 *************************************************************/
548
549status_t BMediaEventLooper::_Reserved_BMediaEventLooper_0(int32 arg,...) { return B_ERROR; }
550status_t BMediaEventLooper::_Reserved_BMediaEventLooper_1(int32 arg,...) { return B_ERROR; }
551status_t BMediaEventLooper::_Reserved_BMediaEventLooper_2(int32 arg,...) { return B_ERROR; }
552status_t BMediaEventLooper::_Reserved_BMediaEventLooper_3(int32 arg,...) { return B_ERROR; }
553status_t BMediaEventLooper::_Reserved_BMediaEventLooper_4(int32 arg,...) { return B_ERROR; }
554status_t BMediaEventLooper::_Reserved_BMediaEventLooper_5(int32 arg,...) { return B_ERROR; }
555status_t BMediaEventLooper::_Reserved_BMediaEventLooper_6(int32 arg,...) { return B_ERROR; }
556status_t BMediaEventLooper::_Reserved_BMediaEventLooper_7(int32 arg,...) { return B_ERROR; }
557status_t BMediaEventLooper::_Reserved_BMediaEventLooper_8(int32 arg,...) { return B_ERROR; }
558status_t BMediaEventLooper::_Reserved_BMediaEventLooper_9(int32 arg,...) { return B_ERROR; }
559status_t BMediaEventLooper::_Reserved_BMediaEventLooper_10(int32 arg,...) { return B_ERROR; }
560status_t BMediaEventLooper::_Reserved_BMediaEventLooper_11(int32 arg,...) { return B_ERROR; }
561status_t BMediaEventLooper::_Reserved_BMediaEventLooper_12(int32 arg,...) { return B_ERROR; }
562status_t BMediaEventLooper::_Reserved_BMediaEventLooper_13(int32 arg,...) { return B_ERROR; }
563status_t BMediaEventLooper::_Reserved_BMediaEventLooper_14(int32 arg,...) { return B_ERROR; }
564status_t BMediaEventLooper::_Reserved_BMediaEventLooper_15(int32 arg,...) { return B_ERROR; }
565status_t BMediaEventLooper::_Reserved_BMediaEventLooper_16(int32 arg,...) { return B_ERROR; }
566status_t BMediaEventLooper::_Reserved_BMediaEventLooper_17(int32 arg,...) { return B_ERROR; }
567status_t BMediaEventLooper::_Reserved_BMediaEventLooper_18(int32 arg,...) { return B_ERROR; }
568status_t BMediaEventLooper::_Reserved_BMediaEventLooper_19(int32 arg,...) { return B_ERROR; }
569status_t BMediaEventLooper::_Reserved_BMediaEventLooper_20(int32 arg,...) { return B_ERROR; }
570status_t BMediaEventLooper::_Reserved_BMediaEventLooper_21(int32 arg,...) { return B_ERROR; }
571status_t BMediaEventLooper::_Reserved_BMediaEventLooper_22(int32 arg,...) { return B_ERROR; }
572status_t BMediaEventLooper::_Reserved_BMediaEventLooper_23(int32 arg,...) { return B_ERROR; }
573
574