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