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