xref: /haiku/src/add-ons/media/media-add-ons/video_producer_demo/Producer.cpp (revision 71452e98334eaac603bf542d159e24788a46bebb)
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 
37 VideoProducer::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 
65 VideoProducer::~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 
79 port_id
80 VideoProducer::ControlPort() const
81 {
82 	return BMediaNode::ControlPort();
83 }
84 
85 BMediaAddOn *
86 VideoProducer::AddOn(int32 *internal_id) const
87 {
88 	if (internal_id)
89 		*internal_id = fInternalID;
90 	return fAddOn;
91 }
92 
93 status_t
94 VideoProducer::HandleMessage(int32 message, const void *data, size_t size)
95 {
96 	return B_ERROR;
97 }
98 
99 void
100 VideoProducer::Preroll()
101 {
102 	/* This hook may be called before the node is started to give the hardware
103 	 * a chance to start. */
104 }
105 
106 void
107 VideoProducer::SetTimeSource(BTimeSource *time_source)
108 {
109 	/* Tell frame generation thread to recalculate delay value */
110 	release_sem(fFrameSync);
111 }
112 
113 status_t
114 VideoProducer::RequestCompleted(const media_request_info &info)
115 {
116 	return BMediaNode::RequestCompleted(info);
117 }
118 
119 /* BMediaEventLooper */
120 
121 void
122 VideoProducer::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 
162 void
163 VideoProducer::Start(bigtime_t performance_time)
164 {
165 	BMediaEventLooper::Start(performance_time);
166 }
167 
168 void
169 VideoProducer::Stop(bigtime_t performance_time, bool immediate)
170 {
171 	BMediaEventLooper::Stop(performance_time, immediate);
172 }
173 
174 void
175 VideoProducer::Seek(bigtime_t media_time, bigtime_t performance_time)
176 {
177 	BMediaEventLooper::Seek(media_time, performance_time);
178 }
179 
180 void
181 VideoProducer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time)
182 {
183 	BMediaEventLooper::TimeWarp(at_real_time, to_performance_time);
184 }
185 
186 status_t
187 VideoProducer::AddTimer(bigtime_t at_performance_time, int32 cookie)
188 {
189 	return BMediaEventLooper::AddTimer(at_performance_time, cookie);
190 }
191 
192 void
193 VideoProducer::SetRunMode(run_mode mode)
194 {
195 	BMediaEventLooper::SetRunMode(mode);
196 }
197 
198 void
199 VideoProducer::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 
227 void
228 VideoProducer::CleanUpEvent(const media_timed_event *event)
229 {
230 	BMediaEventLooper::CleanUpEvent(event);
231 }
232 
233 bigtime_t
234 VideoProducer::OfflineTime()
235 {
236 	return BMediaEventLooper::OfflineTime();
237 }
238 
239 void
240 VideoProducer::ControlLoop()
241 {
242 	BMediaEventLooper::ControlLoop();
243 }
244 
245 status_t
246 VideoProducer::DeleteHook(BMediaNode * node)
247 {
248 	return BMediaEventLooper::DeleteHook(node);
249 }
250 
251 /* BBufferProducer */
252 
253 status_t
254 VideoProducer::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 
273 status_t
274 VideoProducer::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 
291 status_t
292 VideoProducer::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 
303 status_t
304 VideoProducer::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 
317 status_t
318 VideoProducer::DisposeOutputCookie(int32 cookie)
319 {
320 	TOUCH(cookie);
321 
322 	return B_OK;
323 }
324 
325 status_t
326 VideoProducer::SetBufferGroup(const media_source &for_source,
327 		BBufferGroup *group)
328 {
329 	TOUCH(for_source); TOUCH(group);
330 
331 	return B_ERROR;
332 }
333 
334 status_t
335 VideoProducer::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 
345 status_t
346 VideoProducer::GetLatency(bigtime_t *out_latency)
347 {
348 	*out_latency = EventLatency() + SchedulingLatency();
349 	return B_OK;
350 }
351 
352 status_t
353 VideoProducer::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 
394 void
395 VideoProducer::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 
464 void
465 VideoProducer::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 
491 void
492 VideoProducer::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 
498 void
499 VideoProducer::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 
510 status_t
511 VideoProducer::SetPlayRate(int32 numer, int32 denom)
512 {
513 	TOUCH(numer); TOUCH(denom);
514 
515 	return B_ERROR;
516 }
517 
518 void
519 VideoProducer::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 
526 void
527 VideoProducer::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 
536 status_t
537 VideoProducer::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 
550 void
551 VideoProducer::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 
567 status_t
568 VideoProducer::StartControlPanel(BMessenger *out_messenger)
569 {
570 	return BControllable::StartControlPanel(out_messenger);
571 }
572 
573 /* VideoProducer */
574 
575 void
576 VideoProducer::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 
605 err2:
606 	delete_sem(fFrameSync);
607 err1:
608 	return;
609 }
610 
611 void
612 VideoProducer::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 
627 void
628 VideoProducer::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 
637 void
638 VideoProducer::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. */
649 int32
650 VideoProducer::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 
755 int32
756 VideoProducer::_frame_generator_(void *data)
757 {
758 	return ((VideoProducer *)data)->FrameGenerator();
759 }
760