xref: /haiku/src/kits/media/TimeSource.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
1 /*
2  * Copyright 2002-2024, Haiku. All rights reserved.
3  * This file may be used under the terms of the MIT License.
4  *
5  * Authors:
6  *    Dario Casalinuovo
7  *    Marcus Overhagen
8  */
9 
10 
11 #include <TimeSource.h>
12 
13 #include <Autolock.h>
14 
15 #include <string.h>
16 
17 #include "MediaDebug.h"
18 #include "DataExchange.h"
19 #include "ServerInterface.h"
20 #include "TimeSourceObject.h"
21 #include "TMap.h"
22 
23 #define DEBUG_TIMESOURCE 0
24 
25 #if DEBUG_TIMESOURCE
26 	#define TRACE_TIMESOURCE printf
27 #else
28 	#define TRACE_TIMESOURCE if (1) {} else printf
29 #endif
30 
31 namespace BPrivate { namespace media {
32 
33 #define _atomic_read(p) 	atomic_or((p), 0)
34 
35 // must be multiple of page size
36 #define TS_AREA_SIZE		B_PAGE_SIZE
37 // must be power of two
38 #define TS_INDEX_COUNT		128
39 
40 // sizeof(TimeSourceTransmit) must be <= TS_AREA_SIZE
41 struct TimeSourceTransmit
42 {
43 	int32 readindex;
44 	int32 writeindex;
45 	int32 isrunning;
46 	bigtime_t realtime[TS_INDEX_COUNT];
47 	bigtime_t perftime[TS_INDEX_COUNT];
48 	float drift[TS_INDEX_COUNT];
49 };
50 
51 #define MAX_SLAVE_NODES 300
52 
53 
54 class SlaveNodes : public BLocker
55 {
56 public:
57 								SlaveNodes();
58 								~SlaveNodes();
59 
60 	int32						CountSlaves() const;
61 	bool						GetNextSlave(port_id** id);
62 	void						Rewind();
63 
64 	bool						InsertSlave(const media_node& node);
65 	bool						RemoveSlave(const media_node& node);
66 private:
67 	Map<media_node_id, port_id>	fSlaveList;
68 };
69 
70 
71 SlaveNodes::SlaveNodes()
72 	:
73 	BLocker("BTimeSource slavenodes")
74 {
75 }
76 
77 
78 SlaveNodes::~SlaveNodes()
79 {
80 	fSlaveList.MakeEmpty();
81 }
82 
83 
84 int32
85 SlaveNodes::CountSlaves() const
86 {
87 	return fSlaveList.CountItems();
88 }
89 
90 
91 bool
92 SlaveNodes::GetNextSlave(port_id** id)
93 {
94 	return fSlaveList.GetNext(id);
95 }
96 
97 
98 void
99 SlaveNodes::Rewind()
100 {
101 	fSlaveList.Rewind();
102 }
103 
104 
105 bool
106 SlaveNodes::InsertSlave(const media_node& node)
107 {
108 	return fSlaveList.Insert(node.node, node.port);
109 }
110 
111 
112 bool
113 SlaveNodes::RemoveSlave(const media_node& node)
114 {
115 	return fSlaveList.Remove(node.node);
116 }
117 
118 
119 } } // namespace BPrivate::media
120 
121 
122 /*************************************************************
123  * protected BTimeSource
124  *************************************************************/
125 
126 BTimeSource::~BTimeSource()
127 {
128 	CALLED();
129 	if (fArea > 0)
130 		delete_area(fArea);
131 	delete fSlaveNodes;
132 }
133 
134 /*************************************************************
135  * public BTimeSource
136  *************************************************************/
137 
138 status_t
139 BTimeSource::SnoozeUntil(bigtime_t performance_time,
140 	bigtime_t with_latency, bool retry_signals)
141 {
142 	CALLED();
143 	bigtime_t time;
144 	status_t err;
145 	do {
146 		time = RealTimeFor(performance_time, with_latency);
147 		err = snooze_until(time, B_SYSTEM_TIMEBASE);
148 	} while (err == B_INTERRUPTED && retry_signals);
149 	return err;
150 }
151 
152 
153 bigtime_t
154 BTimeSource::Now()
155 {
156 	PRINT(8, "CALLED BTimeSource::Now()\n");
157 	return PerformanceTimeFor(RealTime());
158 }
159 
160 
161 bigtime_t
162 BTimeSource::PerformanceTimeFor(bigtime_t real_time)
163 {
164 	PRINT(8, "CALLED BTimeSource::PerformanceTimeFor()\n");
165 	bigtime_t last_perf_time;
166 	bigtime_t last_real_time;
167 	float last_drift;
168 
169 	if (GetTime(&last_perf_time, &last_real_time, &last_drift) != B_OK)
170 		debugger("BTimeSource::PerformanceTimeFor: GetTime failed");
171 
172 	bigtime_t real_time_difference = real_time - last_real_time;
173 	if (real_time_difference >= (1 << FLT_MANT_DIG)) {
174 		// The difference is too large fit in a float.
175 		if (last_drift == 1.0f)
176 			return last_perf_time + real_time_difference;
177 
178 		debugger("BTimeSource::PerformanceTimeFor: real time too large");
179 	}
180 
181 	return last_perf_time + (bigtime_t)(real_time_difference * last_drift);
182 }
183 
184 
185 bigtime_t
186 BTimeSource::RealTimeFor(bigtime_t performance_time,
187 	bigtime_t with_latency)
188 {
189 	PRINT(8, "CALLED BTimeSource::RealTimeFor()\n");
190 
191 	if (fIsRealtime)
192 		return performance_time - with_latency;
193 
194 	bigtime_t last_perf_time;
195 	bigtime_t last_real_time;
196 	float last_drift;
197 
198 	if (GetTime(&last_perf_time, &last_real_time, &last_drift) != B_OK)
199 		debugger("BTimeSource::RealTimeFor: GetTime failed");
200 
201 	bigtime_t perf_time_difference = performance_time - last_perf_time;
202 	if (perf_time_difference >= (1 << FLT_MANT_DIG)) {
203 		// The difference is too large to fit in a float.
204 		if (last_drift == 1.0f)
205 			return last_real_time - with_latency + perf_time_difference;
206 
207 		debugger("BTimeSource::PerformanceTimeFor: performance time too large");
208 	}
209 
210 	return last_real_time - with_latency
211 		+ (bigtime_t)(perf_time_difference / last_drift);
212 }
213 
214 
215 bool
216 BTimeSource::IsRunning()
217 {
218 	PRINT(8, "CALLED BTimeSource::IsRunning()\n");
219 
220 	bool isrunning;
221 
222 	// The system time source is always running
223 	if (fIsRealtime)
224 		isrunning = true;
225 	else
226 		isrunning = fBuf ? atomic_add(&fBuf->isrunning, 0) : fStarted;
227 
228 	TRACE_TIMESOURCE("BTimeSource::IsRunning() node %" B_PRId32 ", port %"
229 		B_PRId32 ", %s\n", fNodeID, fControlPort, isrunning ? "yes" : "no");
230 	return isrunning;
231 }
232 
233 
234 status_t
235 BTimeSource::GetTime(bigtime_t* performance_time,
236 	bigtime_t* real_time, float* drift)
237 {
238 	PRINT(8, "CALLED BTimeSource::GetTime()\n");
239 
240 	if (fIsRealtime) {
241 		*performance_time = *real_time = system_time();
242 		*drift = 1.0f;
243 		return B_OK;
244 	}
245 
246 	if (fBuf == NULL)
247 		debugger("BTimeSource::GetTime: fBuf == NULL");
248 
249 	int32 index = _atomic_read(&fBuf->readindex);
250 	index &= (TS_INDEX_COUNT - 1);
251 	*real_time = fBuf->realtime[index];
252 	*performance_time = fBuf->perftime[index];
253 	*drift = fBuf->drift[index];
254 
255 	TRACE_TIMESOURCE("BTimeSource::GetTime     timesource %" B_PRId32
256 		", perf %16" B_PRId64 ", real %16" B_PRId64 ", drift %2.2f\n", ID(),
257 		*performance_time, *real_time, *drift);
258 	return B_OK;
259 }
260 
261 
262 bigtime_t
263 BTimeSource::RealTime()
264 {
265 	PRINT(8, "CALLED BTimeSource::RealTime()\n");
266 	return system_time();
267 }
268 
269 
270 status_t
271 BTimeSource::GetStartLatency(bigtime_t* out_latency)
272 {
273 	CALLED();
274 	*out_latency = 0;
275 	return B_OK;
276 }
277 
278 /*************************************************************
279  * protected BTimeSource
280  *************************************************************/
281 
282 
283 BTimeSource::BTimeSource()
284 	:
285 	BMediaNode("This one is never called"),
286 	fStarted(false),
287 	fArea(-1),
288 	fBuf(NULL),
289 	fSlaveNodes(new BPrivate::media::SlaveNodes),
290 	fIsRealtime(false)
291 {
292 	CALLED();
293 	AddNodeKind(B_TIME_SOURCE);
294 	// This constructor is only called by real time sources that inherit
295 	// BTimeSource. We create the communication area in FinishCreate(),
296 	// since we don't have a correct ID() until this node is registered.
297 }
298 
299 
300 status_t
301 BTimeSource::HandleMessage(int32 message, const void* rawdata,
302 	size_t size)
303 {
304 	PRINT(4, "BTimeSource::HandleMessage %#" B_PRIx32 ", node %" B_PRId32 "\n",
305 		message, fNodeID);
306 	status_t rv;
307 	switch (message) {
308 		case TIMESOURCE_OP:
309 		{
310 			const time_source_op_info* data
311 				= static_cast<const time_source_op_info*>(rawdata);
312 
313 			status_t result;
314 			result = TimeSourceOp(*data, NULL);
315 			if (result != B_OK) {
316 				ERROR("BTimeSource::HandleMessage: TimeSourceOp failed\n");
317 			}
318 
319 			switch (data->op) {
320 				case B_TIMESOURCE_START:
321 					DirectStart(data->real_time);
322 					break;
323 				case B_TIMESOURCE_STOP:
324 					DirectStop(data->real_time, false);
325 					break;
326 				case B_TIMESOURCE_STOP_IMMEDIATELY:
327 					DirectStop(data->real_time, true);
328 					break;
329 				case B_TIMESOURCE_SEEK:
330 					DirectSeek(data->performance_time, data->real_time);
331 					break;
332 			}
333 			return B_OK;
334 		}
335 
336 		case TIMESOURCE_ADD_SLAVE_NODE:
337 		{
338 			const timesource_add_slave_node_command* data
339 				= static_cast<const timesource_add_slave_node_command*>(rawdata);
340 			DirectAddMe(data->node);
341 			return B_OK;
342 		}
343 
344 		case TIMESOURCE_REMOVE_SLAVE_NODE:
345 		{
346 			const timesource_remove_slave_node_command* data
347 				= static_cast<const timesource_remove_slave_node_command*>(rawdata);
348 			DirectRemoveMe(data->node);
349 			return B_OK;
350 		}
351 
352 		case TIMESOURCE_GET_START_LATENCY:
353 		{
354 			const timesource_get_start_latency_request* request
355 				= static_cast<const timesource_get_start_latency_request*>(rawdata);
356 			timesource_get_start_latency_reply reply;
357 			rv = GetStartLatency(&reply.start_latency);
358 			request->SendReply(rv, &reply, sizeof(reply));
359 			return B_OK;
360 		}
361 	}
362 	return B_ERROR;
363 }
364 
365 
366 void
367 BTimeSource::PublishTime(bigtime_t performance_time,
368 	bigtime_t real_time, float drift)
369 {
370 	TRACE_TIMESOURCE("BTimeSource::PublishTime timesource %" B_PRId32
371 		", perf %16" B_PRId64 ", real %16" B_PRId64 ", drift %2.2f\n", ID(),
372 		performance_time, real_time, drift);
373 	if (fBuf == NULL) {
374 		ERROR("BTimeSource::PublishTime timesource %" B_PRId32
375 			", fBuf = NULL\n", ID());
376 		fStarted = true;
377 		return;
378 	}
379 
380 	if (drift <= 0.0f)
381 		debugger("BTimeSource::PublishTime: drift must be > 0");
382 
383 	int32 index = atomic_add(&fBuf->writeindex, 1);
384 	index &= (TS_INDEX_COUNT - 1);
385 	fBuf->realtime[index] = real_time;
386 	fBuf->perftime[index] = performance_time;
387 	fBuf->drift[index] = drift;
388 	atomic_add(&fBuf->readindex, 1);
389 }
390 
391 
392 void
393 BTimeSource::BroadcastTimeWarp(bigtime_t at_real_time,
394 	bigtime_t new_performance_time)
395 {
396 	CALLED();
397 	ASSERT(fSlaveNodes != NULL);
398 
399 	// calls BMediaNode::TimeWarp() of all slaved nodes
400 
401 	TRACE("BTimeSource::BroadcastTimeWarp: at_real_time %" B_PRId64
402 		", new_performance_time %" B_PRId64 "\n", at_real_time,
403 		new_performance_time);
404 
405 	BAutolock lock(fSlaveNodes);
406 
407 	port_id* port = NULL;
408 	while (fSlaveNodes->GetNextSlave(&port) == true) {
409 		node_time_warp_command cmd;
410 		cmd.at_real_time = at_real_time;
411 		cmd.to_performance_time = new_performance_time;
412 		SendToPort(*port, NODE_TIME_WARP,
413 			&cmd, sizeof(cmd));
414 	}
415 	fSlaveNodes->Rewind();
416 }
417 
418 
419 void
420 BTimeSource::SendRunMode(run_mode mode)
421 {
422 	CALLED();
423 	ASSERT(fSlaveNodes != NULL);
424 
425 	// send the run mode change to all slaved nodes
426 
427 	BAutolock lock(fSlaveNodes);
428 
429 	port_id* port = NULL;
430 	while (fSlaveNodes->GetNextSlave(&port) == true) {
431 		node_set_run_mode_command cmd;
432 		cmd.mode = mode;
433 		SendToPort(*port, NODE_SET_RUN_MODE,
434 			&cmd, sizeof(cmd));
435 	}
436 	fSlaveNodes->Rewind();
437 }
438 
439 
440 void
441 BTimeSource::SetRunMode(run_mode mode)
442 {
443 	CALLED();
444 	BMediaNode::SetRunMode(mode);
445 	SendRunMode(mode);
446 }
447 /*************************************************************
448  * private BTimeSource
449  *************************************************************/
450 
451 /*
452 //unimplemented
453 BTimeSource::BTimeSource(const BTimeSource &clone)
454 BTimeSource &BTimeSource::operator=(const BTimeSource &clone)
455 */
456 
457 status_t BTimeSource::_Reserved_TimeSource_0(void *) { return B_ERROR; }
458 status_t BTimeSource::_Reserved_TimeSource_1(void *) { return B_ERROR; }
459 status_t BTimeSource::_Reserved_TimeSource_2(void *) { return B_ERROR; }
460 status_t BTimeSource::_Reserved_TimeSource_3(void *) { return B_ERROR; }
461 status_t BTimeSource::_Reserved_TimeSource_4(void *) { return B_ERROR; }
462 status_t BTimeSource::_Reserved_TimeSource_5(void *) { return B_ERROR; }
463 
464 /* explicit */
465 BTimeSource::BTimeSource(media_node_id id)
466 	:
467 	BMediaNode("This one is never called"),
468 	fStarted(false),
469 	fArea(-1),
470 	fBuf(NULL),
471 	fSlaveNodes(NULL),
472 	fIsRealtime(false)
473 {
474 	CALLED();
475 	AddNodeKind(B_TIME_SOURCE);
476 	ASSERT(id > 0);
477 
478 	// This constructor is only called by the derived
479 	// BPrivate::media::TimeSourceObject objects
480 	// We create a clone of the communication area.
481 	char name[32];
482 	sprintf(name, "__timesource_buf_%" B_PRId32, id);
483 	area_id area = find_area(name);
484 	if (area <= 0) {
485 		ERROR("BTimeSource::BTimeSource couldn't find area, node %" B_PRId32
486 			"\n", id);
487 		return;
488 	}
489 	sprintf(name, "__cloned_timesource_buf_%" B_PRId32, id);
490 
491 	void** buf = reinterpret_cast<void**>
492 		(const_cast<BPrivate::media::TimeSourceTransmit**>(&fBuf));
493 
494 	fArea = clone_area(name, buf, B_ANY_ADDRESS,
495 		B_READ_AREA | B_WRITE_AREA, area);
496 
497 	if (fArea <= 0) {
498 		ERROR("BTimeSource::BTimeSource couldn't clone area, node %" B_PRId32
499 			"\n", id);
500 		return;
501 	}
502 }
503 
504 
505 void
506 BTimeSource::FinishCreate()
507 {
508 	CALLED();
509 
510 	char name[32];
511 	sprintf(name, "__timesource_buf_%" B_PRId32, ID());
512 
513 	void** buf = reinterpret_cast<void**>
514 		(const_cast<BPrivate::media::TimeSourceTransmit**>(&fBuf));
515 
516 	fArea = create_area(name, buf, B_ANY_ADDRESS, TS_AREA_SIZE,
517 		B_FULL_LOCK, B_READ_AREA | B_WRITE_AREA | B_CLONEABLE_AREA);
518 
519 	if (fArea <= 0) {
520 		ERROR("BTimeSource::BTimeSource couldn't create area, node %" B_PRId32
521 			"\n", ID());
522 		fBuf = NULL;
523 		return;
524 	}
525 	fBuf->readindex = 0;
526 	fBuf->writeindex = 1;
527 	fBuf->realtime[0] = 0;
528 	fBuf->perftime[0] = 0;
529 	fBuf->drift[0] = 1.0f;
530 	fBuf->isrunning = fStarted;
531 }
532 
533 
534 status_t
535 BTimeSource::RemoveMe(BMediaNode* node)
536 {
537 	CALLED();
538 	if (fKinds & NODE_KIND_SHADOW_TIMESOURCE) {
539 		timesource_remove_slave_node_command cmd;
540 		cmd.node = node->Node();
541 		SendToPort(fControlPort, TIMESOURCE_REMOVE_SLAVE_NODE,
542 			&cmd, sizeof(cmd));
543 	} else {
544 		DirectRemoveMe(node->Node());
545 	}
546 	return B_OK;
547 }
548 
549 
550 status_t
551 BTimeSource::AddMe(BMediaNode* node)
552 {
553 	CALLED();
554 	if (fKinds & NODE_KIND_SHADOW_TIMESOURCE) {
555 		timesource_add_slave_node_command cmd;
556 		cmd.node = node->Node();
557 		SendToPort(fControlPort, TIMESOURCE_ADD_SLAVE_NODE, &cmd, sizeof(cmd));
558 	} else {
559 		DirectAddMe(node->Node());
560 	}
561 	return B_OK;
562 }
563 
564 
565 void
566 BTimeSource::DirectAddMe(const media_node& node)
567 {
568 	CALLED();
569 	ASSERT(fSlaveNodes != NULL);
570 	BAutolock lock(fSlaveNodes);
571 
572 	if (fSlaveNodes->CountSlaves() == MAX_SLAVE_NODES) {
573 		ERROR("BTimeSource::DirectAddMe reached maximum number of slaves\n");
574 		return;
575 	}
576 	if (fNodeID == node.node) {
577 		ERROR("BTimeSource::DirectAddMe should not add itself to slave nodes\n");
578 		return;
579 	}
580 
581 	if (fSlaveNodes->InsertSlave(node) != true) {
582 		ERROR("BTimeSource::DirectAddMe failed\n");
583 		return;
584 	}
585 
586 	if (fSlaveNodes->CountSlaves() == 1) {
587 		// start the time source
588 		time_source_op_info msg;
589 		msg.op = B_TIMESOURCE_START;
590 		msg.real_time = RealTime();
591 
592 		TRACE_TIMESOURCE("starting time source %" B_PRId32 "\n", ID());
593 
594 		write_port(fControlPort, TIMESOURCE_OP, &msg, sizeof(msg));
595 	}
596  }
597 
598 
599 void
600 BTimeSource::DirectRemoveMe(const media_node& node)
601 {
602 	CALLED();
603 	ASSERT(fSlaveNodes != NULL);
604 	BAutolock lock(fSlaveNodes);
605 
606 	if (fSlaveNodes->CountSlaves() == 0) {
607 		ERROR("BTimeSource::DirectRemoveMe no slots used\n");
608 		return;
609 	}
610 
611 	if (fSlaveNodes->RemoveSlave(node) != true) {
612 		ERROR("BTimeSource::DirectRemoveMe failed\n");
613 		return;
614 	}
615 
616 	if (fSlaveNodes->CountSlaves() == 0) {
617 		// stop the time source
618 		time_source_op_info msg;
619 		msg.op = B_TIMESOURCE_STOP_IMMEDIATELY;
620 		msg.real_time = RealTime();
621 
622 		TRACE_TIMESOURCE("stopping time source %" B_PRId32 "\n", ID());
623 
624 		write_port(fControlPort, TIMESOURCE_OP, &msg, sizeof(msg));
625 	}
626 }
627 
628 
629 void
630 BTimeSource::DirectStart(bigtime_t at)
631 {
632 	CALLED();
633 	if (fBuf)
634 		atomic_or(&fBuf->isrunning, 1);
635 	else
636 		fStarted = true;
637 }
638 
639 
640 void
641 BTimeSource::DirectStop(bigtime_t at, bool immediate)
642 {
643 	CALLED();
644 	if (fBuf)
645 		atomic_and(&fBuf->isrunning, 0);
646 	else
647 		fStarted = false;
648 }
649 
650 
651 void
652 BTimeSource::DirectSeek(bigtime_t to, bigtime_t at)
653 {
654 	UNIMPLEMENTED();
655 }
656 
657 
658 void
659 BTimeSource::DirectSetRunMode(run_mode mode)
660 {
661 	UNIMPLEMENTED();
662 }
663