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 -- %lx\n", event->type));
223			break;
224	}
225}
226
227void
228VideoProducer::CleanUpEvent(const media_timed_event *event)
229{
230	BMediaEventLooper::CleanUpEvent(event);
231}
232
233bigtime_t
234VideoProducer::OfflineTime()
235{
236	return BMediaEventLooper::OfflineTime();
237}
238
239void
240VideoProducer::ControlLoop()
241{
242	BMediaEventLooper::ControlLoop();
243}
244
245status_t
246VideoProducer::DeleteHook(BMediaNode * node)
247{
248	return BMediaEventLooper::DeleteHook(node);
249}
250
251/* BBufferProducer */
252
253status_t
254VideoProducer::FormatSuggestionRequested(
255		media_type type, int32 quality, media_format *format)
256{
257	if (type != B_MEDIA_ENCODED_VIDEO)
258		return B_MEDIA_BAD_FORMAT;
259
260	TOUCH(quality);
261
262	if (fOutput.format.u.raw_video.display.line_width == 0)
263		fOutput.format.u.raw_video.display.line_width = 320;
264	if (fOutput.format.u.raw_video.display.line_count == 0)
265		fOutput.format.u.raw_video.display.line_count = 240;
266	if (fOutput.format.u.raw_video.field_rate == 0)
267		fOutput.format.u.raw_video.field_rate = 29.97f;
268
269	*format = fOutput.format;
270	return B_OK;
271}
272
273status_t
274VideoProducer::FormatProposal(const media_source &output, media_format *format)
275{
276	status_t err;
277
278	if (!format)
279		return B_BAD_VALUE;
280
281	if (output != fOutput.source)
282		return B_MEDIA_BAD_SOURCE;
283
284	err = format_is_compatible(*format, fOutput.format) ?
285			B_OK : B_MEDIA_BAD_FORMAT;
286	*format = fOutput.format;
287
288	return err;
289}
290
291status_t
292VideoProducer::FormatChangeRequested(const media_source &source,
293		const media_destination &destination, media_format *io_format,
294		int32 *_deprecated_)
295{
296	TOUCH(destination); TOUCH(io_format); TOUCH(_deprecated_);
297	if (source != fOutput.source)
298		return B_MEDIA_BAD_SOURCE;
299
300	return B_ERROR;
301}
302
303status_t
304VideoProducer::GetNextOutput(int32 *cookie, media_output *out_output)
305{
306	if (!out_output)
307		return B_BAD_VALUE;
308
309	if ((*cookie) != 0)
310		return B_BAD_INDEX;
311
312	*out_output = fOutput;
313	(*cookie)++;
314	return B_OK;
315}
316
317status_t
318VideoProducer::DisposeOutputCookie(int32 cookie)
319{
320	TOUCH(cookie);
321
322	return B_OK;
323}
324
325status_t
326VideoProducer::SetBufferGroup(const media_source &for_source,
327		BBufferGroup *group)
328{
329	TOUCH(for_source); TOUCH(group);
330
331	return B_ERROR;
332}
333
334status_t
335VideoProducer::VideoClippingChanged(const media_source &for_source,
336		int16 num_shorts, int16 *clip_data,
337		const media_video_display_info &display, int32 *_deprecated_)
338{
339	TOUCH(for_source); TOUCH(num_shorts); TOUCH(clip_data);
340	TOUCH(display); TOUCH(_deprecated_);
341
342	return B_ERROR;
343}
344
345status_t
346VideoProducer::GetLatency(bigtime_t *out_latency)
347{
348	*out_latency = EventLatency() + SchedulingLatency();
349	return B_OK;
350}
351
352status_t
353VideoProducer::PrepareToConnect(const media_source &source,
354		const media_destination &destination, media_format *format,
355		media_source *out_source, char *out_name)
356{
357	PRINTF(1, ("PrepareToConnect() %ldx%ld\n", \
358			format->u.raw_video.display.line_width, \
359			format->u.raw_video.display.line_count));
360
361	if (fConnected) {
362		PRINTF(0, ("PrepareToConnect: Already connected\n"));
363		return EALREADY;
364	}
365
366	if (source != fOutput.source)
367		return B_MEDIA_BAD_SOURCE;
368
369	if (fOutput.destination != media_destination::null)
370		return B_MEDIA_ALREADY_CONNECTED;
371
372	/* The format parameter comes in with the suggested format, and may be
373	 * specialized as desired by the node */
374	if (!format_is_compatible(*format, fOutput.format)) {
375		*format = fOutput.format;
376		return B_MEDIA_BAD_FORMAT;
377	}
378
379	if (format->u.raw_video.display.line_width == 0)
380		format->u.raw_video.display.line_width = 320;
381	if (format->u.raw_video.display.line_count == 0)
382		format->u.raw_video.display.line_count = 240;
383	if (format->u.raw_video.field_rate == 0)
384		format->u.raw_video.field_rate = 29.97f;
385
386	*out_source = fOutput.source;
387	strcpy(out_name, fOutput.name);
388
389	fOutput.destination = destination;
390
391	return B_OK;
392}
393
394void
395VideoProducer::Connect(status_t error, const media_source &source,
396		const media_destination &destination, const media_format &format,
397		char *io_name)
398{
399	PRINTF(1, ("Connect() %ldx%ld\n", \
400			format.u.raw_video.display.line_width, \
401			format.u.raw_video.display.line_count));
402
403	if (fConnected) {
404		PRINTF(0, ("Connect: Already connected\n"));
405		return;
406	}
407
408	if (	(source != fOutput.source) || (error < B_OK) ||
409			!const_cast<media_format *>(&format)->Matches(&fOutput.format)) {
410		PRINTF(1, ("Connect: Connect error\n"));
411		return;
412	}
413
414	fOutput.destination = destination;
415	strcpy(io_name, fOutput.name);
416
417	if (fOutput.format.u.raw_video.field_rate != 0.0f) {
418		fPerformanceTimeBase = fPerformanceTimeBase +
419				(bigtime_t)
420					((fFrame - fFrameBase) *
421					(1000000 / fOutput.format.u.raw_video.field_rate));
422		fFrameBase = fFrame;
423	}
424
425	fConnectedFormat = format.u.raw_video;
426
427	/* get the latency */
428	bigtime_t latency = 0;
429	media_node_id tsID = 0;
430	FindLatencyFor(fOutput.destination, &latency, &tsID);
431	#define NODE_LATENCY 1000
432	SetEventLatency(latency + NODE_LATENCY);
433
434	uint32 *buffer, *p, f = 3;
435	p = buffer = (uint32 *)malloc(4 * fConnectedFormat.display.line_count *
436			fConnectedFormat.display.line_width);
437	if (!buffer) {
438		PRINTF(0, ("Connect: Out of memory\n"));
439		return;
440	}
441	bigtime_t now = system_time();
442	for (int y = 0; y < (int)fConnectedFormat.display.line_count; y++)
443		for (int x = 0; x < (int)fConnectedFormat.display.line_width; x++)
444			*(p++) = ((((x+y)^0^x)+f) & 0xff) * (0x01010101 & fColor);
445	fProcessingLatency = system_time() - now;
446	free(buffer);
447
448	/* Create the buffer group */
449	fBufferGroup = new BBufferGroup(4 * fConnectedFormat.display.line_width *
450			fConnectedFormat.display.line_count, 8);
451	if (fBufferGroup->InitCheck() < B_OK) {
452		delete fBufferGroup;
453		fBufferGroup = NULL;
454		return;
455	}
456
457	fConnected = true;
458	fEnabled = true;
459
460	/* Tell frame generation thread to recalculate delay value */
461	release_sem(fFrameSync);
462}
463
464void
465VideoProducer::Disconnect(const media_source &source,
466		const media_destination &destination)
467{
468	PRINTF(1, ("Disconnect()\n"));
469
470	if (!fConnected) {
471		PRINTF(0, ("Disconnect: Not connected\n"));
472		return;
473	}
474
475	if ((source != fOutput.source) || (destination != fOutput.destination)) {
476		PRINTF(0, ("Disconnect: Bad source and/or destination\n"));
477		return;
478	}
479
480	fEnabled = false;
481	fOutput.destination = media_destination::null;
482
483	fLock.Lock();
484		delete fBufferGroup;
485		fBufferGroup = NULL;
486	fLock.Unlock();
487
488	fConnected = false;
489}
490
491void
492VideoProducer::LateNoticeReceived(const media_source &source,
493		bigtime_t how_much, bigtime_t performance_time)
494{
495	TOUCH(source); TOUCH(how_much); TOUCH(performance_time);
496}
497
498void
499VideoProducer::EnableOutput(const media_source &source, bool enabled,
500		int32 *_deprecated_)
501{
502	TOUCH(_deprecated_);
503
504	if (source != fOutput.source)
505		return;
506
507	fEnabled = enabled;
508}
509
510status_t
511VideoProducer::SetPlayRate(int32 numer, int32 denom)
512{
513	TOUCH(numer); TOUCH(denom);
514
515	return B_ERROR;
516}
517
518void
519VideoProducer::AdditionalBufferRequested(const media_source &source,
520		media_buffer_id prev_buffer, bigtime_t prev_time,
521		const media_seek_tag *prev_tag)
522{
523	TOUCH(source); TOUCH(prev_buffer); TOUCH(prev_time); TOUCH(prev_tag);
524}
525
526void
527VideoProducer::LatencyChanged(const media_source &source,
528		const media_destination &destination, bigtime_t new_latency,
529		uint32 flags)
530{
531	TOUCH(source); TOUCH(destination); TOUCH(new_latency); TOUCH(flags);
532}
533
534/* BControllable */
535
536status_t
537VideoProducer::GetParameterValue(
538	int32 id, bigtime_t *last_change, void *value, size_t *size)
539{
540	if (id != P_COLOR)
541		return B_BAD_VALUE;
542
543	*last_change = fLastColorChange;
544	*size = sizeof(uint32);
545	*((uint32 *)value) = fColor;
546
547	return B_OK;
548}
549
550void
551VideoProducer::SetParameterValue(
552	int32 id, bigtime_t when, const void *value, size_t size)
553{
554	if ((id != P_COLOR) || !value || (size != sizeof(uint32)))
555		return;
556
557	if (*(uint32 *)value == fColor)
558		return;
559
560	fColor = *(uint32 *)value;
561	fLastColorChange = when;
562
563	BroadcastNewParameterValue(
564			fLastColorChange, P_COLOR, &fColor, sizeof(fColor));
565}
566
567status_t
568VideoProducer::StartControlPanel(BMessenger *out_messenger)
569{
570	return BControllable::StartControlPanel(out_messenger);
571}
572
573/* VideoProducer */
574
575void
576VideoProducer::HandleStart(bigtime_t performance_time)
577{
578	/* Start producing frames, even if the output hasn't been connected yet. */
579
580	PRINTF(1, ("HandleStart(%Ld)\n", performance_time));
581
582	if (fRunning) {
583		PRINTF(-1, ("HandleStart: Node already started\n"));
584		return;
585	}
586
587	fFrame = 0;
588	fFrameBase = 0;
589	fPerformanceTimeBase = performance_time;
590
591	fFrameSync = create_sem(0, "frame synchronization");
592	if (fFrameSync < B_OK)
593		goto err1;
594
595	fThread = spawn_thread(_frame_generator_, "frame generator",
596			B_NORMAL_PRIORITY, this);
597	if (fThread < B_OK)
598		goto err2;
599
600	resume_thread(fThread);
601
602	fRunning = true;
603	return;
604
605err2:
606	delete_sem(fFrameSync);
607err1:
608	return;
609}
610
611void
612VideoProducer::HandleStop(void)
613{
614	PRINTF(1, ("HandleStop()\n"));
615
616	if (!fRunning) {
617		PRINTF(-1, ("HandleStop: Node isn't running\n"));
618		return;
619	}
620
621	delete_sem(fFrameSync);
622	wait_for_thread(fThread, &fThread);
623
624	fRunning = false;
625}
626
627void
628VideoProducer::HandleTimeWarp(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
637void
638VideoProducer::HandleSeek(bigtime_t performance_time)
639{
640	fPerformanceTimeBase = performance_time;
641	fFrameBase = fFrame;
642
643	/* Tell frame generation thread to recalculate delay value */
644	release_sem(fFrameSync);
645}
646
647/* The following functions form the thread that generates frames. You should
648 * replace this with the code that interfaces to your hardware. */
649int32
650VideoProducer::FrameGenerator()
651{
652	bigtime_t wait_until = system_time();
653
654	while (1) {
655		status_t err = acquire_sem_etc(fFrameSync, 1, B_ABSOLUTE_TIMEOUT,
656				wait_until);
657
658		/* The only acceptable responses are B_OK and B_TIMED_OUT. Everything
659		 * else means the thread should quit. Deleting the semaphore, as in
660		 * VideoProducer::HandleStop(), will trigger this behavior. */
661		if ((err != B_OK) && (err != B_TIMED_OUT))
662			break;
663
664		fFrame++;
665
666		/* Recalculate the time until the thread should wake up to begin
667		 * processing the next frame. Subtract fProcessingLatency so that
668		 * the frame is sent in time. */
669		wait_until = TimeSource()->RealTimeFor(fPerformanceTimeBase +
670				(bigtime_t)
671						((fFrame - fFrameBase) *
672						(1000000 / fConnectedFormat.field_rate)), 0) -
673				fProcessingLatency;
674
675		/* Drop frame if it's at least a frame late */
676		if (wait_until < system_time())
677			continue;
678
679		/* If the semaphore was acquired successfully, it means something
680		 * changed the timing information (see VideoProducer::Connect()) and
681		 * so the thread should go back to sleep until the newly-calculated
682		 * wait_until time. */
683		if (err == B_OK)
684			continue;
685
686		/* Send buffers only if the node is running and the output has been
687		 * enabled */
688		if (!fRunning || !fEnabled)
689			continue;
690
691		BAutolock _(fLock);
692
693		/* Fetch a buffer from the buffer group */
694		BBuffer *buffer = fBufferGroup->RequestBuffer(
695						4 * fConnectedFormat.display.line_width *
696						fConnectedFormat.display.line_count, 0LL);
697		if (!buffer)
698			continue;
699
700		/* Fill out the details about this buffer. */
701		media_header *h = buffer->Header();
702		h->type = B_MEDIA_RAW_VIDEO;
703		h->time_source = TimeSource()->ID();
704		h->size_used = 4 * fConnectedFormat.display.line_width *
705						fConnectedFormat.display.line_count;
706		/* For a buffer originating from a device, you might want to calculate
707		 * this based on the PerformanceTimeFor the time your buffer arrived at
708		 * the hardware (plus any applicable adjustments). */
709		h->start_time = fPerformanceTimeBase +
710						(bigtime_t)
711							((fFrame - fFrameBase) *
712							(1000000 / fConnectedFormat.field_rate));
713		h->file_pos = 0;
714		h->orig_size = 0;
715		h->data_offset = 0;
716		h->u.raw_video.field_gamma = 1.0;
717		h->u.raw_video.field_sequence = fFrame;
718		h->u.raw_video.field_number = 0;
719		h->u.raw_video.pulldown_number = 0;
720		h->u.raw_video.first_active_line = 1;
721		h->u.raw_video.line_count = fConnectedFormat.display.line_count;
722
723		if (fColor == 0xff000000) {
724			// display a gray block that moves
725			uint32 *p = (uint32 *)buffer->Data();
726			for (int y = 0; y < (int)fConnectedFormat.display.line_count; y++)
727				for (int x = 0; x < (int)fConnectedFormat.display.line_width; x++) {
728					if (x > (fFrame & 0xff) && x < (fFrame & 0xff) + 60 && y > 90 && y < 150) {
729						*(p++) = 0xff777777;
730					} else {
731						*(p++) = 0x00000000;
732					}
733				}
734		} else {
735
736			/* Fill in a pattern */
737			uint32 *p = (uint32 *)buffer->Data();
738			for (int y = 0; y < (int)fConnectedFormat.display.line_count; y++)
739				for (int x = 0; x < (int)fConnectedFormat.display.line_width; x++)
740					*(p++) = ((((x+y)^0^x)+fFrame) & 0xff) * (0x01010101 & fColor);
741		}
742
743		/* Send the buffer on down to the consumer */
744		if (SendBuffer(buffer, fOutput.source, fOutput.destination) < B_OK) {
745			PRINTF(-1, ("FrameGenerator: Error sending buffer\n"));
746			/* If there is a problem sending the buffer, return it to its
747			 * buffer group. */
748			buffer->Recycle();
749		}
750	}
751
752	return B_OK;
753}
754
755int32
756VideoProducer::_frame_generator_(void *data)
757{
758	return ((VideoProducer *)data)->FrameGenerator();
759}
760