vfs: check userland buffers before reading them.
[haiku.git] / src / apps / mediaplayer / media_node_framework / video / VideoProducer.cpp
blob774dfc3ed26d550e445f20305e8daac9419f4562
1 /* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved.
2 * Distributed under the terms of the Be Sample Code license.
4 * Copyright (c) 2000-2008, Ingo Weinhold <ingo_weinhold@gmx.de>,
5 * Copyright (c) 2000-2008, Stephan Aßmus <superstippi@gmx.de>,
6 * All Rights Reserved. Distributed under the terms of the MIT license.
7 */
10 #include "VideoProducer.h"
12 #include <stdio.h>
13 #include <string.h>
15 #include <Autolock.h>
16 #include <Buffer.h>
17 #include <BufferGroup.h>
18 #include <TimeSource.h>
20 #include "NodeManager.h"
21 #include "VideoSupplier.h"
24 // debugging
25 //#define TRACE_VIDEO_PRODUCER
26 #ifdef TRACE_VIDEO_PRODUCER
27 # define TRACE(x...) printf("VideoProducer::"); printf(x)
28 # define FUNCTION(x...) TRACE(x)
29 # define ERROR(x...) fprintf(stderr, "VideoProducer::"); fprintf(stderr, x)
30 #else
31 # define TRACE(x...)
32 # define FUNCTION(x...)
33 # define ERROR(x...) fprintf(stderr, "VideoProducer::"); fprintf(stderr, x)
34 #endif
37 #define BUFFER_COUNT 3
39 #define TOUCH(x) ((void)(x))
42 VideoProducer::VideoProducer(BMediaAddOn* addon, const char* name,
43 int32 internalId, NodeManager* manager, VideoSupplier* supplier)
44 : BMediaNode(name),
45 BMediaEventLooper(),
46 BBufferProducer(B_MEDIA_RAW_VIDEO),
47 fInitStatus(B_NO_INIT),
48 fInternalID(internalId),
49 fAddOn(addon),
50 fBufferGroup(NULL),
51 fUsedBufferGroup(NULL),
52 fThread(-1),
53 fFrameSync(-1),
54 fFrame(0),
55 fFrameBase(0),
56 fPerformanceTimeBase(0),
57 fBufferLatency(0),
58 fRunning(false),
59 fConnected(false),
60 fEnabled(false),
61 fManager(manager),
62 fSupplier(supplier)
64 fOutput.destination = media_destination::null;
65 fInitStatus = B_OK;
69 VideoProducer::~VideoProducer()
71 if (fInitStatus == B_OK) {
72 // Clean up after ourselves, in case the application didn't make us
73 // do so.
74 if (fConnected)
75 Disconnect(fOutput.source, fOutput.destination);
76 if (fRunning)
77 _HandleStop();
79 Quit();
83 BMediaAddOn*
84 VideoProducer::AddOn(int32* _internalId) const
86 if (_internalId)
87 *_internalId = fInternalID;
88 return fAddOn;
92 status_t
93 VideoProducer::HandleMessage(int32 message, const void* data, size_t size)
95 return B_ERROR;
99 void
100 VideoProducer::SetTimeSource(BTimeSource* timeSource)
102 // Tell frame generation thread to recalculate delay value
103 release_sem(fFrameSync);
107 void
108 VideoProducer::NodeRegistered()
110 if (fInitStatus != B_OK) {
111 ReportError(B_NODE_IN_DISTRESS);
112 return;
115 fOutput.node = Node();
116 fOutput.source.port = ControlPort();
117 fOutput.source.id = 0;
118 fOutput.destination = media_destination::null;
119 strcpy(fOutput.name, Name());
121 // fill with wild cards at this point in time
122 fOutput.format.type = B_MEDIA_RAW_VIDEO;
123 fOutput.format.u.raw_video = media_raw_video_format::wildcard;
124 fOutput.format.u.raw_video.interlace = 1;
125 fOutput.format.u.raw_video.display.format = B_NO_COLOR_SPACE;
126 fOutput.format.u.raw_video.display.bytes_per_row = 0;
127 fOutput.format.u.raw_video.display.line_width = 0;
128 fOutput.format.u.raw_video.display.line_count = 0;
130 // start the BMediaEventLooper control loop running
131 Run();
135 void
136 VideoProducer::Start(bigtime_t performanceTime)
138 // notify the manager in case we were started from the outside world
139 // fManager->StartPlaying();
141 BMediaEventLooper::Start(performanceTime);
145 void
146 VideoProducer::Stop(bigtime_t performanceTime, bool immediate)
148 // notify the manager in case we were stopped from the outside world
149 // fManager->StopPlaying();
151 BMediaEventLooper::Stop(performanceTime, immediate);
155 void
156 VideoProducer::Seek(bigtime_t media_time, bigtime_t performanceTime)
158 BMediaEventLooper::Seek(media_time, performanceTime);
162 void
163 VideoProducer::HandleEvent(const media_timed_event* event,
164 bigtime_t lateness, bool realTimeEvent)
166 TOUCH(lateness); TOUCH(realTimeEvent);
168 switch (event->type) {
169 case BTimedEventQueue::B_START:
170 _HandleStart(event->event_time);
171 break;
172 case BTimedEventQueue::B_STOP:
174 EventQueue()->FlushEvents(event->event_time,
175 BTimedEventQueue::B_ALWAYS,
176 true, BTimedEventQueue::B_HANDLE_BUFFER);
177 _HandleStop();
178 break;
180 case BTimedEventQueue::B_WARP:
181 _HandleTimeWarp(event->bigdata);
182 break;
183 case BTimedEventQueue::B_SEEK:
184 _HandleSeek(event->bigdata);
185 break;
186 case BTimedEventQueue::B_HANDLE_BUFFER:
187 case BTimedEventQueue::B_DATA_STATUS:
188 case BTimedEventQueue::B_PARAMETER:
189 default:
190 TRACE("HandleEvent: Unhandled event -- %lx\n", event->type);
191 break;
196 status_t
197 VideoProducer::DeleteHook(BMediaNode* node)
199 return BMediaEventLooper::DeleteHook(node);
203 status_t
204 VideoProducer::FormatSuggestionRequested(media_type type, int32 quality,
205 media_format* _format)
207 FUNCTION("FormatSuggestionRequested\n");
209 if (type != B_MEDIA_ENCODED_VIDEO)
210 return B_MEDIA_BAD_FORMAT;
212 TOUCH(quality);
214 *_format = fOutput.format;
215 return B_OK;
219 status_t
220 VideoProducer::FormatProposal(const media_source& output, media_format* format)
222 #ifdef TRACE_VIDEO_PRODUCER
223 char string[256];
224 string_for_format(*format, string, 256);
225 FUNCTION("FormatProposal(%s)\n", string);
226 #endif
228 if (!format)
229 return B_BAD_VALUE;
231 if (output != fOutput.source)
232 return B_MEDIA_BAD_SOURCE;
234 status_t ret = format_is_compatible(*format, fOutput.format) ?
235 B_OK : B_MEDIA_BAD_FORMAT;
236 if (ret != B_OK) {
237 ERROR("FormatProposal() error: %s\n", strerror(ret));
238 char string[512];
239 string_for_format(*format, string, sizeof(string));
240 ERROR(" requested: %s\n", string);
241 string_for_format(fOutput.format, string, sizeof(string));
242 ERROR(" output: %s\n", string);
245 // change any wild cards to specific values
247 return ret;
252 status_t
253 VideoProducer::FormatChangeRequested(const media_source& source,
254 const media_destination& destination, media_format* ioFormat,
255 int32 *_deprecated_)
257 TOUCH(destination); TOUCH(ioFormat); TOUCH(_deprecated_);
259 if (source != fOutput.source)
260 return B_MEDIA_BAD_SOURCE;
262 return B_ERROR;
266 status_t
267 VideoProducer::GetNextOutput(int32* cookie, media_output* outOutput)
269 if (!outOutput)
270 return B_BAD_VALUE;
272 if ((*cookie) != 0)
273 return B_BAD_INDEX;
275 *outOutput = fOutput;
276 (*cookie)++;
278 return B_OK;
282 status_t
283 VideoProducer::DisposeOutputCookie(int32 cookie)
285 TOUCH(cookie);
287 return B_OK;
291 status_t
292 VideoProducer::SetBufferGroup(const media_source& forSource,
293 BBufferGroup *group)
295 if (forSource != fOutput.source)
296 return B_MEDIA_BAD_SOURCE;
298 TRACE("VideoProducer::SetBufferGroup() - using buffer group of "
299 "consumer.\n");
300 fUsedBufferGroup = group;
302 return B_OK;
306 status_t
307 VideoProducer::VideoClippingChanged(const media_source& forSource,
308 int16 numShorts, int16* clipData, const media_video_display_info& display,
309 int32* _deprecated_)
311 TOUCH(forSource); TOUCH(numShorts); TOUCH(clipData);
312 TOUCH(display); TOUCH(_deprecated_);
314 return B_ERROR;
318 status_t
319 VideoProducer::GetLatency(bigtime_t* _latency)
321 if (!_latency)
322 return B_BAD_VALUE;
324 *_latency = EventLatency() + SchedulingLatency();
326 return B_OK;
330 status_t
331 VideoProducer::PrepareToConnect(const media_source& source,
332 const media_destination& destination, media_format* format,
333 media_source* outSource, char* outName)
335 FUNCTION("PrepareToConnect() %ldx%ld\n",
336 format->u.raw_video.display.line_width,
337 format->u.raw_video.display.line_count);
339 if (fConnected) {
340 ERROR("PrepareToConnect() - already connected!\n");
341 return B_MEDIA_ALREADY_CONNECTED;
344 if (source != fOutput.source)
345 return B_MEDIA_BAD_SOURCE;
347 if (fOutput.destination != media_destination::null) {
348 ERROR("PrepareToConnect() - destination != null.\n");
349 return B_MEDIA_ALREADY_CONNECTED;
352 // The format parameter comes in with the suggested format, and may be
353 // specialized as desired by the node
354 if (!format_is_compatible(*format, fOutput.format)) {
355 ERROR("PrepareToConnect() - incompatible format.\n");
356 *format = fOutput.format;
357 return B_MEDIA_BAD_FORMAT;
360 if (format->u.raw_video.display.line_width == 0)
361 format->u.raw_video.display.line_width = 384;
362 if (format->u.raw_video.display.line_count == 0)
363 format->u.raw_video.display.line_count = 288;
364 if (format->u.raw_video.field_rate == 0)
365 format->u.raw_video.field_rate = 25.0;
366 if (format->u.raw_video.display.bytes_per_row == 0)
367 format->u.raw_video.display.bytes_per_row = format->u.raw_video.display.line_width * 4;
369 *outSource = fOutput.source;
370 strcpy(outName, fOutput.name);
372 return B_OK;
376 void
377 VideoProducer::Connect(status_t error, const media_source& source,
378 const media_destination& destination, const media_format& format,
379 char* _name)
381 FUNCTION("Connect() %ldx%ld\n",
382 format.u.raw_video.display.line_width,
383 format.u.raw_video.display.line_count);
385 if (fConnected) {
386 ERROR("Connect() - already connected.\n");
387 return;
390 if (source != fOutput.source) {
391 ERROR("Connect() - wrong source.\n");
392 return;
394 if (error != B_OK) {
395 ERROR("Connect() - consumer error: %s\n", strerror(error));
396 return;
398 if (!const_cast<media_format*>(&format)->Matches(&fOutput.format)) {
399 ERROR("Connect() - format mismatch.\n");
400 return;
403 fOutput.destination = destination;
404 strcpy(_name, fOutput.name);
405 fConnectedFormat = format.u.raw_video;
406 fBufferDuration = 20000;
408 if (fConnectedFormat.field_rate != 0.0f) {
409 fPerformanceTimeBase = fPerformanceTimeBase
410 + (bigtime_t)((fFrame - fFrameBase)
411 * 1000000LL / fConnectedFormat.field_rate);
412 fFrameBase = fFrame;
413 fBufferDuration = bigtime_t(1000000LL / fConnectedFormat.field_rate);
416 if (fConnectedFormat.display.bytes_per_row == 0) {
417 ERROR("Connect() - connected format still has BPR wildcard!\n");
418 fConnectedFormat.display.bytes_per_row
419 = 4 * fConnectedFormat.display.line_width;
422 // Create the buffer group
423 if (fUsedBufferGroup == NULL) {
424 fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row
425 * fConnectedFormat.display.line_count, BUFFER_COUNT);
426 status_t err = fBufferGroup->InitCheck();
427 if (err < B_OK) {
428 delete fBufferGroup;
429 fBufferGroup = NULL;
430 ERROR("Connect() - buffer group error: %s\n", strerror(err));
431 return;
433 fUsedBufferGroup = fBufferGroup;
436 // get the latency
437 fBufferLatency = (BUFFER_COUNT - 1) * fBufferDuration;
439 int32 bufferCount;
440 if (fUsedBufferGroup->CountBuffers(&bufferCount) == B_OK) {
441 // recompute the latency
442 fBufferLatency = (bufferCount - 1) * fBufferDuration;
445 bigtime_t latency = 0;
446 media_node_id tsID = 0;
447 FindLatencyFor(fOutput.destination, &latency, &tsID);
448 SetEventLatency(latency + fBufferLatency);
450 fConnected = true;
451 fEnabled = true;
453 // Tell frame generation thread to recalculate delay value
454 release_sem(fFrameSync);
458 void
459 VideoProducer::Disconnect(const media_source& source,
460 const media_destination& destination)
462 FUNCTION("Disconnect()\n");
464 if (!fConnected) {
465 ERROR("Disconnect() - Not connected\n");
466 return;
469 if ((source != fOutput.source) || (destination != fOutput.destination)) {
470 ERROR("Disconnect() - Bad source and/or destination\n");
471 return;
474 fEnabled = false;
475 fOutput.destination = media_destination::null;
477 if (fLock.Lock()) {
478 // Always delete the buffer group, even if it is not ours.
479 // (See BeBook::SetBufferGroup()).
480 delete fUsedBufferGroup;
481 if (fBufferGroup != fUsedBufferGroup)
482 delete fBufferGroup;
483 fUsedBufferGroup = NULL;
484 fBufferGroup = NULL;
485 fLock.Unlock();
488 fConnected = false;
489 TRACE("Disconnect() done\n");
493 void
494 VideoProducer::LateNoticeReceived(const media_source &source,
495 bigtime_t how_much, bigtime_t performanceTime)
497 TOUCH(source); TOUCH(how_much); TOUCH(performanceTime);
498 TRACE("Late!!!\n");
502 void
503 VideoProducer::EnableOutput(const media_source& source, bool enabled,
504 int32* _deprecated_)
506 TOUCH(_deprecated_);
508 if (source != fOutput.source)
509 return;
511 fEnabled = enabled;
515 status_t
516 VideoProducer::SetPlayRate(int32 numer, int32 denom)
518 TOUCH(numer); TOUCH(denom);
520 return B_ERROR;
524 void
525 VideoProducer::AdditionalBufferRequested(const media_source& source,
526 media_buffer_id prevBuffer, bigtime_t prevTime,
527 const media_seek_tag* prevTag)
529 TOUCH(source); TOUCH(prevBuffer); TOUCH(prevTime); TOUCH(prevTag);
533 void
534 VideoProducer::LatencyChanged(const media_source& source,
535 const media_destination& destination,
536 bigtime_t newLatency, uint32 flags)
538 TOUCH(source); TOUCH(destination); TOUCH(newLatency); TOUCH(flags);
539 TRACE("Latency changed!\n");
543 // #pragma mark -
546 void
547 VideoProducer::_HandleStart(bigtime_t performanceTime)
549 // Start producing frames, even if the output hasn't been connected yet.
550 TRACE("_HandleStart(%Ld)\n", performanceTime);
552 if (fRunning) {
553 TRACE("_HandleStart: Node already started\n");
554 return;
557 fFrame = 0;
558 fFrameBase = 0;
559 fPerformanceTimeBase = performanceTime;
561 fFrameSync = create_sem(0, "frame synchronization");
562 if (fFrameSync < B_OK)
563 return;
565 fThread = spawn_thread(_FrameGeneratorThreadEntry, "frame generator",
566 B_NORMAL_PRIORITY, this);
567 if (fThread < B_OK) {
568 delete_sem(fFrameSync);
569 return;
572 resume_thread(fThread);
573 fRunning = true;
574 return;
578 void
579 VideoProducer::_HandleStop()
581 TRACE("_HandleStop()\n");
583 if (!fRunning) {
584 TRACE("_HandleStop: Node isn't running\n");
585 return;
588 delete_sem(fFrameSync);
589 wait_for_thread(fThread, &fThread);
591 fRunning = false;
595 void
596 VideoProducer::_HandleTimeWarp(bigtime_t performanceTime)
598 fPerformanceTimeBase = performanceTime;
599 fFrameBase = fFrame;
601 // Tell frame generation thread to recalculate delay value
602 release_sem(fFrameSync);
606 void
607 VideoProducer::_HandleSeek(bigtime_t performanceTime)
609 fPerformanceTimeBase = performanceTime;
610 fFrameBase = fFrame;
612 // Tell frame generation thread to recalculate delay value
613 release_sem(fFrameSync);
617 int32
618 VideoProducer::_FrameGeneratorThreadEntry(void* data)
620 return ((VideoProducer*)data)->_FrameGeneratorThread();
624 int32
625 VideoProducer::_FrameGeneratorThread()
627 bool forceSendingBuffer = true;
628 int32 droppedFrames = 0;
629 const int32 kMaxDroppedFrames = 15;
630 bool running = true;
631 while (running) {
632 TRACE("_FrameGeneratorThread: loop: %Ld\n", fFrame);
633 // lock the node manager
634 status_t err = fManager->LockWithTimeout(10000);
635 bool ignoreEvent = false;
636 // Data to be retrieved from the node manager.
637 bigtime_t performanceTime = 0;
638 bigtime_t nextPerformanceTime = 0;
639 bigtime_t waitUntil = 0;
640 bigtime_t nextWaitUntil = 0;
641 int32 playingDirection = 0;
642 int64 playlistFrame = 0;
643 switch (err) {
644 case B_OK: {
645 TRACE("_FrameGeneratorThread: node manager successfully "
646 "locked\n");
647 if (droppedFrames > 0)
648 fManager->FrameDropped();
649 // get the times for the current and the next frame
650 performanceTime = fManager->TimeForFrame(fFrame);
651 nextPerformanceTime = fManager->TimeForFrame(fFrame + 1);
652 waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
653 + performanceTime, fBufferLatency);
654 nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
655 + nextPerformanceTime, fBufferLatency);
656 // get playing direction and playlist frame for the current
657 // frame
658 bool newPlayingState;
659 playlistFrame = fManager->PlaylistFrameAtFrame(fFrame,
660 playingDirection, newPlayingState);
661 TRACE("_FrameGeneratorThread: performance time: %Ld, "
662 "playlist frame: %lld\n", performanceTime, playlistFrame);
663 forceSendingBuffer |= newPlayingState;
664 fManager->SetCurrentVideoTime(nextPerformanceTime);
665 fManager->Unlock();
666 break;
668 case B_TIMED_OUT:
669 TRACE("_FrameGeneratorThread: Couldn't lock the node "
670 "manager.\n");
671 ignoreEvent = true;
672 waitUntil = system_time() - 1;
673 break;
674 default:
675 ERROR("_FrameGeneratorThread: Couldn't lock the node manager. "
676 "Terminating video producer frame generator thread.\n");
677 TRACE("_FrameGeneratorThread: frame generator thread done.\n");
678 // do not access any member variables, since this could
679 // also mean the Node has been deleted
680 return B_OK;
683 TRACE("_FrameGeneratorThread: waiting (%Ld)...\n", waitUntil);
684 // wait until...
685 err = acquire_sem_etc(fFrameSync, 1, B_ABSOLUTE_TIMEOUT, waitUntil);
686 // The only acceptable responses are B_OK and B_TIMED_OUT. Everything
687 // else means the thread should quit. Deleting the semaphore, as in
688 // VideoProducer::_HandleStop(), will trigger this behavior.
689 switch (err) {
690 case B_OK:
691 TRACE("_FrameGeneratorThread: going back to sleep.\n");
692 break;
693 case B_TIMED_OUT:
694 TRACE("_FrameGeneratorThread: timed out => event\n");
695 // Catch the cases in which the node manager could not be
696 // locked and we therefore have no valid data to work with,
697 // or the producer is not running or enabled.
698 if (ignoreEvent || !fRunning || !fEnabled) {
699 TRACE("_FrameGeneratorThread: ignore event\n");
700 // nothing to do
701 } else if (!forceSendingBuffer
702 && nextWaitUntil < system_time() - fBufferLatency
703 && droppedFrames < kMaxDroppedFrames) {
704 // Drop frame if it's at least a frame late.
705 if (playingDirection > 0) {
706 printf("VideoProducer: dropped frame (%" B_PRId64
707 ") (perf. time %" B_PRIdBIGTIME ")\n", fFrame,
708 performanceTime);
710 // next frame
711 droppedFrames++;
712 fFrame++;
713 } else if (playingDirection != 0 || forceSendingBuffer) {
714 // Send buffers only, if playing, the node is running and
715 // the output has been enabled
716 TRACE("_FrameGeneratorThread: produce frame\n");
717 BAutolock _(fLock);
718 // Fetch a buffer from the buffer group
719 fUsedBufferGroup->WaitForBuffers();
720 BBuffer* buffer = fUsedBufferGroup->RequestBuffer(
721 fConnectedFormat.display.bytes_per_row
722 * fConnectedFormat.display.line_count, 0LL);
723 if (buffer == NULL) {
724 // Wait until a buffer becomes available again
725 ERROR("_FrameGeneratorThread: no buffer!\n");
726 break;
728 // Fill out the details about this buffer.
729 media_header* h = buffer->Header();
730 h->type = B_MEDIA_RAW_VIDEO;
731 h->time_source = TimeSource()->ID();
732 h->size_used = fConnectedFormat.display.bytes_per_row
733 * fConnectedFormat.display.line_count;
734 // For a buffer originating from a device, you might
735 // want to calculate this based on the
736 // PerformanceTimeFor the time your buffer arrived at
737 // the hardware (plus any applicable adjustments).
738 h->start_time = fPerformanceTimeBase + performanceTime;
739 h->file_pos = 0;
740 h->orig_size = 0;
741 h->data_offset = 0;
742 h->u.raw_video.field_gamma = 1.0;
743 h->u.raw_video.field_sequence = fFrame;
744 h->u.raw_video.field_number = 0;
745 h->u.raw_video.pulldown_number = 0;
746 h->u.raw_video.first_active_line = 1;
747 h->u.raw_video.line_count
748 = fConnectedFormat.display.line_count;
749 // Fill in a frame
750 TRACE("_FrameGeneratorThread: frame: %Ld, "
751 "playlistFrame: %Ld\n", fFrame, playlistFrame);
752 bool wasCached = false;
753 err = fSupplier->FillBuffer(playlistFrame,
754 buffer->Data(), fConnectedFormat, forceSendingBuffer,
755 wasCached);
756 if (err == B_TIMED_OUT) {
757 // Don't send the buffer if there was insufficient
758 // time for rendering, this will leave the last
759 // valid frame on screen until we catch up, instead
760 // of going black.
761 wasCached = true;
762 err = B_OK;
764 // clean the buffer if something went wrong
765 if (err != B_OK) {
766 // TODO: should use "back value" according
767 // to color space!
768 memset(buffer->Data(), 0, h->size_used);
769 err = B_OK;
771 // Send the buffer on down to the consumer
772 if (wasCached || (err = SendBuffer(buffer, fOutput.source,
773 fOutput.destination) != B_OK)) {
774 // If there is a problem sending the buffer,
775 // or if we don't send the buffer because its
776 // contents are the same as the last one,
777 // return it to its buffer group.
778 buffer->Recycle();
779 // we tell the supplier to delete
780 // its caches if there was a problem sending
781 // the buffer
782 if (err != B_OK) {
783 ERROR("_FrameGeneratorThread: Error "
784 "sending buffer\n");
785 fSupplier->DeleteCaches();
788 // Only if everything went fine we clear the flag
789 // that forces us to send a buffer even if not
790 // playing.
791 if (err == B_OK)
792 forceSendingBuffer = false;
793 // next frame
794 fFrame++;
795 droppedFrames = 0;
796 } else {
797 TRACE("_FrameGeneratorThread: not playing\n");
798 // next frame
799 fFrame++;
801 break;
802 default:
803 TRACE("_FrameGeneratorThread: Couldn't acquire semaphore. "
804 "Error: %s\n", strerror(err));
805 running = false;
806 break;
809 TRACE("_FrameGeneratorThread: frame generator thread done.\n");
810 return B_OK;