xref: /haiku/src/kits/media/MediaEventLooper.cpp (revision 138a802617a45b58aa3f778b6f061b68d6341c48)
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 "debug.h"
37 
38 /*************************************************************
39  * protected BMediaEventLooper
40  *************************************************************/
41 
42 /* virtual */
43 BMediaEventLooper::~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 */
55 BMediaEventLooper::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
73 BMediaEventLooper::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
85 BMediaEventLooper::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
97 BMediaEventLooper::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
120 BMediaEventLooper::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
134 BMediaEventLooper::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
157 BMediaEventLooper::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
171 BMediaEventLooper::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
194 BMediaEventLooper::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
204 BMediaEventLooper::OfflineTime()
205 {
206 	CALLED();
207 	return fOfflineTime;
208 }
209 
210 
211 /* virtual */ void
212 BMediaEventLooper::ControlLoop()
213 {
214 	CALLED();
215 
216 	status_t err;
217 	bigtime_t waitUntil = B_INFINITE_TIMEOUT;
218 	bool hasRealtime = false;
219 	bool hasEvent = false;
220 	bool hasBooted = false;
221 
222 	// While there are no events or it is not time for the earliest event,
223 	// process messages using WaitForMessages. Whenever this funtion times out,
224 	// we need to handle the next event
225 
226 	fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
227 	while (true) {
228 		if (RunState() == B_QUITTING)
229 			return;
230 
231 		err = WaitForMessage(waitUntil);
232 		if (err == B_TIMED_OUT
233 				|| err == B_WOULD_BLOCK) {
234 			// NOTE: The reference for doing the lateness calculus this way can
235 			// be found in the BeBook article "A BMediaEventLooper Example".
236 			// The value which we are going to calculate, is referred there as
237 			// 'lateness'.
238 			media_timed_event event;
239 			if (hasEvent)
240 				err = fEventQueue.RemoveFirstEvent(&event);
241 			else if (hasRealtime)
242 				err = fRealTimeQueue.RemoveFirstEvent(&event);
243 
244 			if (err == B_OK) {
245 				// The general idea of lateness is to allow
246 				// the client code to detect when the buffer
247 				// is handled late or early. What we add is
248 				// that the code log the time at which the
249 				// current event is added to the queue. This
250 				// allow us to detect cyclic/stagnant latency
251 				// in the meantime, so that the client can
252 				// notify to the producer only the portion
253 				// that might be attributable.
254 				bigtime_t lateness = 0;
255 				if (waitUntil > 0) {
256 					lateness = waitUntil - TimeSource()->RealTime();
257 					if (lateness > 0) {
258 						bigtime_t enqueueLatency = event.enqueue_time - waitUntil;
259 						if (enqueueLatency > 0)
260 							lateness += enqueueLatency;
261 					}
262 				}
263 				DispatchEvent(&event, -lateness, hasRealtime);
264 			}
265 		} else if (err != B_OK)
266 			return;
267 
268 		// BMediaEventLooper compensates your performance time by adding
269 		// the event latency (see SetEventLatency()) and the scheduling
270 		// latency (or, for real-time events, only the scheduling latency).
271 
272 		hasRealtime = fRealTimeQueue.HasEvents();
273 		hasEvent = fEventQueue.HasEvents();
274 
275 		if (hasEvent) {
276 			waitUntil = TimeSource()->RealTimeFor(
277 				fEventQueue.FirstEventTime(),
278 				fEventLatency + fSchedulingLatency);
279 
280 			// The first event we handle will have
281 			// a negative startup wait. In this case
282 			// we just check the port and let the
283 			// first event to be executed just now.
284 			if (!hasBooted && waitUntil < 0) {
285 				waitUntil = 0;
286 				hasBooted = true;
287 			}
288 		} else if (!hasRealtime) {
289 			waitUntil = B_INFINITE_TIMEOUT;
290 			continue;
291 		}
292 
293 		if (hasRealtime) {
294 			bigtime_t realtimeWait = fRealTimeQueue.FirstEventTime()
295 				- fSchedulingLatency;
296 
297 			if (!hasEvent || realtimeWait <= waitUntil) {
298 				waitUntil = realtimeWait;
299 				hasEvent = false;
300 			} else
301 				hasRealtime = false;
302 		}
303 	}
304 }
305 
306 
307 thread_id
308 BMediaEventLooper::ControlThread()
309 {
310 	CALLED();
311 	return fControlThread;
312 }
313 
314 /*************************************************************
315  * protected BMediaEventLooper
316  *************************************************************/
317 
318 
319 BTimedEventQueue *
320 BMediaEventLooper::EventQueue()
321 {
322 	CALLED();
323 	return &fEventQueue;
324 }
325 
326 
327 BTimedEventQueue *
328 BMediaEventLooper::RealTimeQueue()
329 {
330 	CALLED();
331 	return &fRealTimeQueue;
332 }
333 
334 
335 int32
336 BMediaEventLooper::Priority() const
337 {
338 	CALLED();
339 	return fCurrentPriority;
340 }
341 
342 
343 int32
344 BMediaEventLooper::RunState() const
345 {
346 	PRINT(6, "CALLED BMediaEventLooper::RunState()\n");
347 	return fRunState;
348 }
349 
350 
351 bigtime_t
352 BMediaEventLooper::EventLatency() const
353 {
354 	CALLED();
355 	return fEventLatency;
356 }
357 
358 
359 bigtime_t
360 BMediaEventLooper::BufferDuration() const
361 {
362 	CALLED();
363 	return fBufferDuration;
364 }
365 
366 
367 bigtime_t
368 BMediaEventLooper::SchedulingLatency() const
369 {
370 	CALLED();
371 	return fSchedulingLatency;
372 }
373 
374 
375 status_t
376 BMediaEventLooper::SetPriority(int32 priority)
377 {
378 	CALLED();
379 
380 	// clamp to a valid value
381 	if (priority < 5)
382 		priority = 5;
383 
384 	if (priority > 120)
385 		priority = 120;
386 
387 	fSetPriority = priority;
388 	fCurrentPriority = (RunMode() == B_OFFLINE) ? min_c(B_NORMAL_PRIORITY, fSetPriority) : fSetPriority;
389 
390 	if (fControlThread > 0) {
391 		set_thread_priority(fControlThread, fCurrentPriority);
392 		fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
393 		printf("BMediaEventLooper: SchedulingLatency is %" B_PRId64 "\n",
394 			fSchedulingLatency);
395 	}
396 
397 	return B_OK;
398 }
399 
400 
401 void
402 BMediaEventLooper::SetRunState(run_state state)
403 {
404 	CALLED();
405 
406 	// don't allow run state changes while quitting,
407 	// also needed for correct terminating of the ControlLoop()
408 	if (fRunState == B_QUITTING && state != B_TERMINATED)
409 		return;
410 
411 	fRunState = state;
412 }
413 
414 
415 void
416 BMediaEventLooper::SetEventLatency(bigtime_t latency)
417 {
418 	CALLED();
419 	// clamp to a valid value
420 	if (latency < 0)
421 		latency = 0;
422 
423 	fEventLatency = latency;
424 	write_port_etc(ControlPort(), GENERAL_PURPOSE_WAKEUP, 0, 0, B_TIMEOUT, 0);
425 }
426 
427 
428 void
429 BMediaEventLooper::SetBufferDuration(bigtime_t duration)
430 {
431 	CALLED();
432 	fBufferDuration = duration;
433 }
434 
435 
436 void
437 BMediaEventLooper::SetOfflineTime(bigtime_t offTime)
438 {
439 	CALLED();
440 	fOfflineTime = offTime;
441 }
442 
443 
444 void
445 BMediaEventLooper::Run()
446 {
447 	CALLED();
448 
449 	if (fControlThread != -1)
450 		return; // thread already running
451 
452 	// until now, the run state is B_UNREGISTERED, but we need to start in B_STOPPED state.
453 	SetRunState(B_STOPPED);
454 
455 	char threadName[32];
456 	sprintf(threadName, "%.20s control", Name());
457 	fControlThread = spawn_thread(_ControlThreadStart, threadName, fCurrentPriority, this);
458 	resume_thread(fControlThread);
459 
460 	// get latency information
461 	fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
462 }
463 
464 
465 void
466 BMediaEventLooper::Quit()
467 {
468 	CALLED();
469 
470 	if (fRunState == B_TERMINATED)
471 		return;
472 
473 	SetRunState(B_QUITTING);
474 	close_port(ControlPort());
475 	if (fControlThread != -1) {
476 		status_t err;
477 		wait_for_thread(fControlThread, &err);
478 		fControlThread = -1;
479 	}
480 	SetRunState(B_TERMINATED);
481 }
482 
483 
484 void
485 BMediaEventLooper::DispatchEvent(const media_timed_event *event,
486 								 bigtime_t lateness,
487 								 bool realTimeEvent)
488 {
489 	PRINT(6, "CALLED BMediaEventLooper::DispatchEvent()\n");
490 
491 	HandleEvent(event, lateness, realTimeEvent);
492 
493 	switch (event->type) {
494 		case BTimedEventQueue::B_START:
495 			SetRunState(B_STARTED);
496 			break;
497 
498 		case BTimedEventQueue::B_STOP:
499 			SetRunState(B_STOPPED);
500 			break;
501 
502 		case BTimedEventQueue::B_SEEK:
503 			/* nothing */
504 			break;
505 
506 		case BTimedEventQueue::B_WARP:
507 			/* nothing */
508 			break;
509 
510 		case BTimedEventQueue::B_TIMER:
511 			TimerExpired(event->event_time, event->data);
512 			break;
513 
514 		default:
515 			break;
516 	}
517 
518 	_DispatchCleanUp(event);
519 }
520 
521 /*************************************************************
522  * private BMediaEventLooper
523  *************************************************************/
524 
525 
526 /* static */ int32
527 BMediaEventLooper::_ControlThreadStart(void *arg)
528 {
529 	CALLED();
530 	((BMediaEventLooper *)arg)->SetRunState(B_STOPPED);
531 	((BMediaEventLooper *)arg)->ControlLoop();
532 	((BMediaEventLooper *)arg)->SetRunState(B_QUITTING);
533 	return 0;
534 }
535 
536 
537 /* static */ void
538 BMediaEventLooper::_CleanUpEntry(const media_timed_event *event,
539 								 void *context)
540 {
541 	PRINT(6, "CALLED BMediaEventLooper::_CleanUpEntry()\n");
542 	((BMediaEventLooper *)context)->_DispatchCleanUp(event);
543 }
544 
545 
546 void
547 BMediaEventLooper::_DispatchCleanUp(const media_timed_event *event)
548 {
549 	PRINT(6, "CALLED BMediaEventLooper::_DispatchCleanUp()\n");
550 
551 	// this function to clean up after custom events you've created
552 	if (event->cleanup >= BTimedEventQueue::B_USER_CLEANUP)
553 		CleanUpEvent(event);
554 }
555 
556 /*
557 // unimplemented
558 BMediaEventLooper::BMediaEventLooper(const BMediaEventLooper &)
559 BMediaEventLooper &BMediaEventLooper::operator=(const BMediaEventLooper &)
560 */
561 
562 /*************************************************************
563  * protected BMediaEventLooper
564  *************************************************************/
565 
566 
567 status_t
568 BMediaEventLooper::DeleteHook(BMediaNode *node)
569 {
570 	CALLED();
571 	// this is the DeleteHook that gets called by the media server
572 	// before the media node is deleted
573 	Quit();
574 	return BMediaNode::DeleteHook(node);
575 }
576 
577 /*************************************************************
578  * private BMediaEventLooper
579  *************************************************************/
580 
581 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_0(int32 arg,...) { return B_ERROR; }
582 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_1(int32 arg,...) { return B_ERROR; }
583 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_2(int32 arg,...) { return B_ERROR; }
584 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_3(int32 arg,...) { return B_ERROR; }
585 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_4(int32 arg,...) { return B_ERROR; }
586 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_5(int32 arg,...) { return B_ERROR; }
587 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_6(int32 arg,...) { return B_ERROR; }
588 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_7(int32 arg,...) { return B_ERROR; }
589 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_8(int32 arg,...) { return B_ERROR; }
590 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_9(int32 arg,...) { return B_ERROR; }
591 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_10(int32 arg,...) { return B_ERROR; }
592 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_11(int32 arg,...) { return B_ERROR; }
593 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_12(int32 arg,...) { return B_ERROR; }
594 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_13(int32 arg,...) { return B_ERROR; }
595 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_14(int32 arg,...) { return B_ERROR; }
596 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_15(int32 arg,...) { return B_ERROR; }
597 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_16(int32 arg,...) { return B_ERROR; }
598 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_17(int32 arg,...) { return B_ERROR; }
599 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_18(int32 arg,...) { return B_ERROR; }
600 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_19(int32 arg,...) { return B_ERROR; }
601 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_20(int32 arg,...) { return B_ERROR; }
602 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_21(int32 arg,...) { return B_ERROR; }
603 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_22(int32 arg,...) { return B_ERROR; }
604 status_t BMediaEventLooper::_Reserved_BMediaEventLooper_23(int32 arg,...) { return B_ERROR; }
605 
606