xref: /haiku/src/add-ons/media/media-add-ons/video_producer_demo/Producer.cpp (revision 9ecf9d1c1d4888d341a6eac72112c72d1ae3a4cb)
1 /*
2 	Copyright 1999, Be Incorporated.   All Rights Reserved.
3 	This file may be used under the terms of the Be Sample Code License.
4 */
5 #include <fcntl.h>
6 #include <malloc.h>
7 #include <math.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <sys/uio.h>
11 #include <unistd.h>
12 
13 #include <Buffer.h>
14 #include <BufferGroup.h>
15 #include <ParameterWeb.h>
16 #include <TimeSource.h>
17 
18 #include <Autolock.h>
19 #include <Debug.h>
20 
21 #define TOUCH(x) ((void)(x))
22 
23 #define PRINTF(a,b) \
24 		do { \
25 			if (a < 2) { \
26 				printf("VideoProducer::"); \
27 				printf b; \
28 			} \
29 		} while (0)
30 
31 #include "Producer.h"
32 
33 #define FIELD_RATE 30.f
34 
35 VideoProducer::VideoProducer(
36 		BMediaAddOn *addon, const char *name, int32 internal_id)
37   :	BMediaNode(name),
38 	BMediaEventLooper(),
39 	BBufferProducer(B_MEDIA_RAW_VIDEO),
40 	BControllable()
41 {
42 	fInitStatus = B_NO_INIT;
43 
44 	fInternalID = internal_id;
45 	fAddOn = addon;
46 
47 	fBufferGroup = NULL;
48 
49 	fThread = -1;
50 	fFrameSync = -1;
51 	fProcessingLatency = 0LL;
52 
53 	fRunning = false;
54 	fConnected = false;
55 	fEnabled = false;
56 
57 	fOutput.destination = media_destination::null;
58 
59 	fInitStatus = B_OK;
60 	return;
61 }
62 
63 VideoProducer::~VideoProducer()
64 {
65 	if (fInitStatus == B_OK) {
66 		/* Clean up after ourselves, in case the application didn't make us
67 		 * do so. */
68 		if (fConnected)
69 			Disconnect(fOutput.source, fOutput.destination);
70 		if (fRunning)
71 			HandleStop();
72 	}
73 }
74 
75 /* BMediaNode */
76 
77 port_id
78 VideoProducer::ControlPort() const
79 {
80 	return BMediaNode::ControlPort();
81 }
82 
83 BMediaAddOn *
84 VideoProducer::AddOn(int32 *internal_id) const
85 {
86 	if (internal_id)
87 		*internal_id = fInternalID;
88 	return fAddOn;
89 }
90 
91 status_t
92 VideoProducer::HandleMessage(int32 message, const void *data, size_t size)
93 {
94 	return B_ERROR;
95 }
96 
97 void
98 VideoProducer::Preroll()
99 {
100 	/* This hook may be called before the node is started to give the hardware
101 	 * a chance to start. */
102 }
103 
104 void
105 VideoProducer::SetTimeSource(BTimeSource *time_source)
106 {
107 	/* Tell frame generation thread to recalculate delay value */
108 	release_sem(fFrameSync);
109 }
110 
111 status_t
112 VideoProducer::RequestCompleted(const media_request_info &info)
113 {
114 	return BMediaNode::RequestCompleted(info);
115 }
116 
117 /* BMediaEventLooper */
118 
119 void
120 VideoProducer::NodeRegistered()
121 {
122 	if (fInitStatus != B_OK) {
123 		ReportError(B_NODE_IN_DISTRESS);
124 		return;
125 	}
126 
127 	/* Set up the parameter web */
128 	BParameterWeb *web = new BParameterWeb();
129 	BParameterGroup *main = web->MakeGroup(Name());
130 	BDiscreteParameter *state = main->MakeDiscreteParameter(
131 			P_COLOR, B_MEDIA_RAW_VIDEO, "Color", "Color");
132 	state->AddItem(B_HOST_TO_LENDIAN_INT32(0x00ff0000), "Red");
133 	state->AddItem(B_HOST_TO_LENDIAN_INT32(0x0000ff00), "Green");
134 	state->AddItem(B_HOST_TO_LENDIAN_INT32(0x000000ff), "Blue");
135 
136 	fColor = B_HOST_TO_LENDIAN_INT32(0x00ff0000);
137 	fLastColorChange = system_time();
138 
139 	/* After this call, the BControllable owns the BParameterWeb object and
140 	 * will delete it for you */
141 	SetParameterWeb(web);
142 
143 	fOutput.node = Node();
144 	fOutput.source.port = ControlPort();
145 	fOutput.source.id = 0;
146 	fOutput.destination = media_destination::null;
147 	strcpy(fOutput.name, Name());
148 
149 	/* Tailor these for the output of your device */
150 	fOutput.format.type = B_MEDIA_RAW_VIDEO;
151 	fOutput.format.u.raw_video = media_raw_video_format::wildcard;
152 	fOutput.format.u.raw_video.interlace = 1;
153 	fOutput.format.u.raw_video.display.format = B_RGB32;
154 
155 	/* Start the BMediaEventLooper control loop running */
156 	Run();
157 }
158 
159 void
160 VideoProducer::Start(bigtime_t performance_time)
161 {
162 	BMediaEventLooper::Start(performance_time);
163 }
164 
165 void
166 VideoProducer::Stop(bigtime_t performance_time, bool immediate)
167 {
168 	BMediaEventLooper::Stop(performance_time, immediate);
169 }
170 
171 void
172 VideoProducer::Seek(bigtime_t media_time, bigtime_t performance_time)
173 {
174 	BMediaEventLooper::Seek(media_time, performance_time);
175 }
176 
177 void
178 VideoProducer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time)
179 {
180 	BMediaEventLooper::TimeWarp(at_real_time, to_performance_time);
181 }
182 
183 status_t
184 VideoProducer::AddTimer(bigtime_t at_performance_time, int32 cookie)
185 {
186 	return BMediaEventLooper::AddTimer(at_performance_time, cookie);
187 }
188 
189 void
190 VideoProducer::SetRunMode(run_mode mode)
191 {
192 	BMediaEventLooper::SetRunMode(mode);
193 }
194 
195 void
196 VideoProducer::HandleEvent(const media_timed_event *event,
197 		bigtime_t lateness, bool realTimeEvent)
198 {
199 	TOUCH(lateness); TOUCH(realTimeEvent);
200 
201 	switch(event->type)
202 	{
203 		case BTimedEventQueue::B_START:
204 			HandleStart(event->event_time);
205 			break;
206 		case BTimedEventQueue::B_STOP:
207 			HandleStop();
208 			break;
209 		case BTimedEventQueue::B_WARP:
210 			HandleTimeWarp(event->bigdata);
211 			break;
212 		case BTimedEventQueue::B_SEEK:
213 			HandleSeek(event->bigdata);
214 			break;
215 		case BTimedEventQueue::B_HANDLE_BUFFER:
216 		case BTimedEventQueue::B_DATA_STATUS:
217 		case BTimedEventQueue::B_PARAMETER:
218 		default:
219 			PRINTF(-1, ("HandleEvent: Unhandled event -- %lx\n", event->type));
220 			break;
221 	}
222 }
223 
224 void
225 VideoProducer::CleanUpEvent(const media_timed_event *event)
226 {
227 	BMediaEventLooper::CleanUpEvent(event);
228 }
229 
230 bigtime_t
231 VideoProducer::OfflineTime()
232 {
233 	return BMediaEventLooper::OfflineTime();
234 }
235 
236 void
237 VideoProducer::ControlLoop()
238 {
239 	BMediaEventLooper::ControlLoop();
240 }
241 
242 status_t
243 VideoProducer::DeleteHook(BMediaNode * node)
244 {
245 	return BMediaEventLooper::DeleteHook(node);
246 }
247 
248 /* BBufferProducer */
249 
250 status_t
251 VideoProducer::FormatSuggestionRequested(
252 		media_type type, int32 quality, media_format *format)
253 {
254 	if (type != B_MEDIA_ENCODED_VIDEO)
255 		return B_MEDIA_BAD_FORMAT;
256 
257 	TOUCH(quality);
258 
259 	*format = fOutput.format;
260 	return B_OK;
261 }
262 
263 status_t
264 VideoProducer::FormatProposal(const media_source &output, media_format *format)
265 {
266 	status_t err;
267 
268 	if (!format)
269 		return B_BAD_VALUE;
270 
271 	if (output != fOutput.source)
272 		return B_MEDIA_BAD_SOURCE;
273 
274 	err = format_is_compatible(*format, fOutput.format) ?
275 			B_OK : B_MEDIA_BAD_FORMAT;
276 	*format = fOutput.format;
277 	return err;
278 
279 }
280 
281 status_t
282 VideoProducer::FormatChangeRequested(const media_source &source,
283 		const media_destination &destination, media_format *io_format,
284 		int32 *_deprecated_)
285 {
286 	TOUCH(destination); TOUCH(io_format); TOUCH(_deprecated_);
287 	if (source != fOutput.source)
288 		return B_MEDIA_BAD_SOURCE;
289 
290 	return B_ERROR;
291 }
292 
293 status_t
294 VideoProducer::GetNextOutput(int32 *cookie, media_output *out_output)
295 {
296 	if (!out_output)
297 		return B_BAD_VALUE;
298 
299 	if ((*cookie) != 0)
300 		return B_BAD_INDEX;
301 
302 	*out_output = fOutput;
303 	(*cookie)++;
304 	return B_OK;
305 }
306 
307 status_t
308 VideoProducer::DisposeOutputCookie(int32 cookie)
309 {
310 	TOUCH(cookie);
311 
312 	return B_OK;
313 }
314 
315 status_t
316 VideoProducer::SetBufferGroup(const media_source &for_source,
317 		BBufferGroup *group)
318 {
319 	TOUCH(for_source); TOUCH(group);
320 
321 	return B_ERROR;
322 }
323 
324 status_t
325 VideoProducer::VideoClippingChanged(const media_source &for_source,
326 		int16 num_shorts, int16 *clip_data,
327 		const media_video_display_info &display, int32 *_deprecated_)
328 {
329 	TOUCH(for_source); TOUCH(num_shorts); TOUCH(clip_data);
330 	TOUCH(display); TOUCH(_deprecated_);
331 
332 	return B_ERROR;
333 }
334 
335 status_t
336 VideoProducer::GetLatency(bigtime_t *out_latency)
337 {
338 	*out_latency = EventLatency() + SchedulingLatency();
339 	return B_OK;
340 }
341 
342 status_t
343 VideoProducer::PrepareToConnect(const media_source &source,
344 		const media_destination &destination, media_format *format,
345 		media_source *out_source, char *out_name)
346 {
347 	PRINTF(1, ("PrepareToConnect() %ldx%ld\n", \
348 			format->u.raw_video.display.line_width, \
349 			format->u.raw_video.display.line_count));
350 
351 	if (fConnected) {
352 		PRINTF(0, ("PrepareToConnect: Already connected\n"));
353 		return EALREADY;
354 	}
355 
356 	if (source != fOutput.source)
357 		return B_MEDIA_BAD_SOURCE;
358 
359 	if (fOutput.destination != media_destination::null)
360 		return B_MEDIA_ALREADY_CONNECTED;
361 
362 	/* The format parameter comes in with the suggested format, and may be
363 	 * specialized as desired by the node */
364 	if (!format_is_compatible(*format, fOutput.format)) {
365 		*format = fOutput.format;
366 		return B_MEDIA_BAD_FORMAT;
367 	}
368 
369 	if (format->u.raw_video.display.line_width == 0)
370 		format->u.raw_video.display.line_width = 320;
371 	if (format->u.raw_video.display.line_count == 0)
372 		format->u.raw_video.display.line_count = 240;
373 	if (format->u.raw_video.field_rate == 0)
374 		format->u.raw_video.field_rate = 29.97f;
375 
376 	*out_source = fOutput.source;
377 	strcpy(out_name, fOutput.name);
378 
379 	fOutput.destination = destination;
380 
381 	return B_OK;
382 }
383 
384 void
385 VideoProducer::Connect(status_t error, const media_source &source,
386 		const media_destination &destination, const media_format &format,
387 		char *io_name)
388 {
389 	PRINTF(1, ("Connect() %ldx%ld\n", \
390 			format.u.raw_video.display.line_width, \
391 			format.u.raw_video.display.line_count));
392 
393 	if (fConnected) {
394 		PRINTF(0, ("Connect: Already connected\n"));
395 		return;
396 	}
397 
398 	if (	(source != fOutput.source) || (error < B_OK) ||
399 			!const_cast<media_format *>(&format)->Matches(&fOutput.format)) {
400 		PRINTF(1, ("Connect: Connect error\n"));
401 		return;
402 	}
403 
404 	fOutput.destination = destination;
405 	strcpy(io_name, fOutput.name);
406 
407 	if (fOutput.format.u.raw_video.field_rate != 0.0f) {
408 		fPerformanceTimeBase = fPerformanceTimeBase +
409 				(bigtime_t)
410 					((fFrame - fFrameBase) *
411 					(1000000 / fOutput.format.u.raw_video.field_rate));
412 		fFrameBase = fFrame;
413 	}
414 
415 	fConnectedFormat = format.u.raw_video;
416 
417 	/* get the latency */
418 	bigtime_t latency = 0;
419 	media_node_id tsID = 0;
420 	FindLatencyFor(fOutput.destination, &latency, &tsID);
421 	#define NODE_LATENCY 1000
422 	SetEventLatency(latency + NODE_LATENCY);
423 
424 	uint32 *buffer, *p, f = 3;
425 	p = buffer = (uint32 *)malloc(4 * fConnectedFormat.display.line_count *
426 			fConnectedFormat.display.line_width);
427 	if (!buffer) {
428 		PRINTF(0, ("Connect: Out of memory\n"));
429 		return;
430 	}
431 	bigtime_t now = system_time();
432 	for (int y = 0; y < (int)fConnectedFormat.display.line_count; y++)
433 		for (int x = 0; x < (int)fConnectedFormat.display.line_width; x++)
434 			*(p++) = ((((x+y)^0^x)+f) & 0xff) * (0x01010101 & fColor);
435 	fProcessingLatency = system_time() - now;
436 	free(buffer);
437 
438 	/* Create the buffer group */
439 	fBufferGroup = new BBufferGroup(4 * fConnectedFormat.display.line_width *
440 			fConnectedFormat.display.line_count, 8);
441 	if (fBufferGroup->InitCheck() < B_OK) {
442 		delete fBufferGroup;
443 		fBufferGroup = NULL;
444 		return;
445 	}
446 
447 	fConnected = true;
448 	fEnabled = true;
449 
450 	/* Tell frame generation thread to recalculate delay value */
451 	release_sem(fFrameSync);
452 }
453 
454 void
455 VideoProducer::Disconnect(const media_source &source,
456 		const media_destination &destination)
457 {
458 	PRINTF(1, ("Disconnect()\n"));
459 
460 	if (!fConnected) {
461 		PRINTF(0, ("Disconnect: Not connected\n"));
462 		return;
463 	}
464 
465 	if ((source != fOutput.source) || (destination != fOutput.destination)) {
466 		PRINTF(0, ("Disconnect: Bad source and/or destination\n"));
467 		return;
468 	}
469 
470 	fEnabled = false;
471 	fOutput.destination = media_destination::null;
472 
473 	fLock.Lock();
474 		delete fBufferGroup;
475 		fBufferGroup = NULL;
476 	fLock.Unlock();
477 
478 	fConnected = false;
479 }
480 
481 void
482 VideoProducer::LateNoticeReceived(const media_source &source,
483 		bigtime_t how_much, bigtime_t performance_time)
484 {
485 	TOUCH(source); TOUCH(how_much); TOUCH(performance_time);
486 }
487 
488 void
489 VideoProducer::EnableOutput(const media_source &source, bool enabled,
490 		int32 *_deprecated_)
491 {
492 	TOUCH(_deprecated_);
493 
494 	if (source != fOutput.source)
495 		return;
496 
497 	fEnabled = enabled;
498 }
499 
500 status_t
501 VideoProducer::SetPlayRate(int32 numer, int32 denom)
502 {
503 	TOUCH(numer); TOUCH(denom);
504 
505 	return B_ERROR;
506 }
507 
508 void
509 VideoProducer::AdditionalBufferRequested(const media_source &source,
510 		media_buffer_id prev_buffer, bigtime_t prev_time,
511 		const media_seek_tag *prev_tag)
512 {
513 	TOUCH(source); TOUCH(prev_buffer); TOUCH(prev_time); TOUCH(prev_tag);
514 }
515 
516 void
517 VideoProducer::LatencyChanged(const media_source &source,
518 		const media_destination &destination, bigtime_t new_latency,
519 		uint32 flags)
520 {
521 	TOUCH(source); TOUCH(destination); TOUCH(new_latency); TOUCH(flags);
522 }
523 
524 /* BControllable */
525 
526 status_t
527 VideoProducer::GetParameterValue(
528 	int32 id, bigtime_t *last_change, void *value, size_t *size)
529 {
530 	if (id != P_COLOR)
531 		return B_BAD_VALUE;
532 
533 	*last_change = fLastColorChange;
534 	*size = sizeof(uint32);
535 	*((uint32 *)value) = fColor;
536 
537 	return B_OK;
538 }
539 
540 void
541 VideoProducer::SetParameterValue(
542 	int32 id, bigtime_t when, const void *value, size_t size)
543 {
544 	if ((id != P_COLOR) || !value || (size != sizeof(uint32)))
545 		return;
546 
547 	if (*(uint32 *)value == fColor)
548 		return;
549 
550 	fColor = *(uint32 *)value;
551 	fLastColorChange = when;
552 
553 	BroadcastNewParameterValue(
554 			fLastColorChange, P_COLOR, &fColor, sizeof(fColor));
555 }
556 
557 status_t
558 VideoProducer::StartControlPanel(BMessenger *out_messenger)
559 {
560 	return BControllable::StartControlPanel(out_messenger);
561 }
562 
563 /* VideoProducer */
564 
565 void
566 VideoProducer::HandleStart(bigtime_t performance_time)
567 {
568 	/* Start producing frames, even if the output hasn't been connected yet. */
569 
570 	PRINTF(1, ("HandleStart(%Ld)\n", performance_time));
571 
572 	if (fRunning) {
573 		PRINTF(-1, ("HandleStart: Node already started\n"));
574 		return;
575 	}
576 
577 	fFrame = 0;
578 	fFrameBase = 0;
579 	fPerformanceTimeBase = performance_time;
580 
581 	fFrameSync = create_sem(0, "frame synchronization");
582 	if (fFrameSync < B_OK)
583 		goto err1;
584 
585 	fThread = spawn_thread(_frame_generator_, "frame generator",
586 			B_NORMAL_PRIORITY, this);
587 	if (fThread < B_OK)
588 		goto err2;
589 
590 	resume_thread(fThread);
591 
592 	fRunning = true;
593 	return;
594 
595 err2:
596 	delete_sem(fFrameSync);
597 err1:
598 	return;
599 }
600 
601 void
602 VideoProducer::HandleStop(void)
603 {
604 	PRINTF(1, ("HandleStop()\n"));
605 
606 	if (!fRunning) {
607 		PRINTF(-1, ("HandleStop: Node isn't running\n"));
608 		return;
609 	}
610 
611 	delete_sem(fFrameSync);
612 	wait_for_thread(fThread, &fThread);
613 
614 	fRunning = false;
615 }
616 
617 void
618 VideoProducer::HandleTimeWarp(bigtime_t performance_time)
619 {
620 	fPerformanceTimeBase = performance_time;
621 	fFrameBase = fFrame;
622 
623 	/* Tell frame generation thread to recalculate delay value */
624 	release_sem(fFrameSync);
625 }
626 
627 void
628 VideoProducer::HandleSeek(bigtime_t performance_time)
629 {
630 	fPerformanceTimeBase = performance_time;
631 	fFrameBase = fFrame;
632 
633 	/* Tell frame generation thread to recalculate delay value */
634 	release_sem(fFrameSync);
635 }
636 
637 /* The following functions form the thread that generates frames. You should
638  * replace this with the code that interfaces to your hardware. */
639 int32
640 VideoProducer::FrameGenerator()
641 {
642 	bigtime_t wait_until = system_time();
643 
644 	while (1) {
645 		status_t err = acquire_sem_etc(fFrameSync, 1, B_ABSOLUTE_TIMEOUT,
646 				wait_until);
647 
648 		/* The only acceptable responses are B_OK and B_TIMED_OUT. Everything
649 		 * else means the thread should quit. Deleting the semaphore, as in
650 		 * VideoProducer::HandleStop(), will trigger this behavior. */
651 		if ((err != B_OK) && (err != B_TIMED_OUT))
652 			break;
653 
654 		fFrame++;
655 
656 		/* Recalculate the time until the thread should wake up to begin
657 		 * processing the next frame. Subtract fProcessingLatency so that
658 		 * the frame is sent in time. */
659 		wait_until = TimeSource()->RealTimeFor(fPerformanceTimeBase +
660 				(bigtime_t)
661 						((fFrame - fFrameBase) *
662 						(1000000 / fConnectedFormat.field_rate)), 0) -
663 				fProcessingLatency;
664 
665 		/* Drop frame if it's at least a frame late */
666 		if (wait_until < system_time())
667 			continue;
668 
669 		/* If the semaphore was acquired successfully, it means something
670 		 * changed the timing information (see VideoProducer::Connect()) and
671 		 * so the thread should go back to sleep until the newly-calculated
672 		 * wait_until time. */
673 		if (err == B_OK)
674 			continue;
675 
676 		/* Send buffers only if the node is running and the output has been
677 		 * enabled */
678 		if (!fRunning || !fEnabled)
679 			continue;
680 
681 		BAutolock _(fLock);
682 
683 		/* Fetch a buffer from the buffer group */
684 		BBuffer *buffer = fBufferGroup->RequestBuffer(
685 						4 * fConnectedFormat.display.line_width *
686 						fConnectedFormat.display.line_count, 0LL);
687 		if (!buffer)
688 			continue;
689 
690 		/* Fill out the details about this buffer. */
691 		media_header *h = buffer->Header();
692 		h->type = B_MEDIA_RAW_VIDEO;
693 		h->time_source = TimeSource()->ID();
694 		h->size_used = 4 * fConnectedFormat.display.line_width *
695 						fConnectedFormat.display.line_count;
696 		/* For a buffer originating from a device, you might want to calculate
697 		 * this based on the PerformanceTimeFor the time your buffer arrived at
698 		 * the hardware (plus any applicable adjustments). */
699 		h->start_time = fPerformanceTimeBase +
700 						(bigtime_t)
701 							((fFrame - fFrameBase) *
702 							(1000000 / fConnectedFormat.field_rate));
703 		h->file_pos = 0;
704 		h->orig_size = 0;
705 		h->data_offset = 0;
706 		h->u.raw_video.field_gamma = 1.0;
707 		h->u.raw_video.field_sequence = fFrame;
708 		h->u.raw_video.field_number = 0;
709 		h->u.raw_video.pulldown_number = 0;
710 		h->u.raw_video.first_active_line = 1;
711 		h->u.raw_video.line_count = fConnectedFormat.display.line_count;
712 
713 		/* Fill in a pattern */
714 		uint32 *p = (uint32 *)buffer->Data();
715 		for (int y = 0; y < (int)fConnectedFormat.display.line_count; y++)
716 			for (int x = 0; x < (int)fConnectedFormat.display.line_width; x++)
717 				*(p++) = ((((x+y)^0^x)+fFrame) & 0xff) * (0x01010101 & fColor);
718 
719 		/* Send the buffer on down to the consumer */
720 		if (SendBuffer(buffer, fOutput.destination) < B_OK) {
721 			PRINTF(-1, ("FrameGenerator: Error sending buffer\n"));
722 			/* If there is a problem sending the buffer, return it to its
723 			 * buffer group. */
724 			buffer->Recycle();
725 		}
726 	}
727 
728 	return B_OK;
729 }
730 
731 int32
732 VideoProducer::_frame_generator_(void *data)
733 {
734 	return ((VideoProducer *)data)->FrameGenerator();
735 }
736