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.
10 #include "VideoProducer.h"
17 #include <BufferGroup.h>
18 #include <TimeSource.h>
20 #include "NodeManager.h"
21 #include "VideoSupplier.h"
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)
32 # define FUNCTION(x...)
33 # define ERROR(x...) fprintf(stderr, "VideoProducer::"); fprintf(stderr, x)
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
)
46 BBufferProducer(B_MEDIA_RAW_VIDEO
),
47 fInitStatus(B_NO_INIT
),
48 fInternalID(internalId
),
51 fUsedBufferGroup(NULL
),
56 fPerformanceTimeBase(0),
64 fOutput
.destination
= media_destination::null
;
69 VideoProducer::~VideoProducer()
71 if (fInitStatus
== B_OK
) {
72 // Clean up after ourselves, in case the application didn't make us
75 Disconnect(fOutput
.source
, fOutput
.destination
);
84 VideoProducer::AddOn(int32
* _internalId
) const
87 *_internalId
= fInternalID
;
93 VideoProducer::HandleMessage(int32 message
, const void* data
, size_t size
)
100 VideoProducer::SetTimeSource(BTimeSource
* timeSource
)
102 // Tell frame generation thread to recalculate delay value
103 release_sem(fFrameSync
);
108 VideoProducer::NodeRegistered()
110 if (fInitStatus
!= B_OK
) {
111 ReportError(B_NODE_IN_DISTRESS
);
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
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
);
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
);
156 VideoProducer::Seek(bigtime_t media_time
, bigtime_t performanceTime
)
158 BMediaEventLooper::Seek(media_time
, performanceTime
);
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
);
172 case BTimedEventQueue::B_STOP
:
174 EventQueue()->FlushEvents(event
->event_time
,
175 BTimedEventQueue::B_ALWAYS
,
176 true, BTimedEventQueue::B_HANDLE_BUFFER
);
180 case BTimedEventQueue::B_WARP
:
181 _HandleTimeWarp(event
->bigdata
);
183 case BTimedEventQueue::B_SEEK
:
184 _HandleSeek(event
->bigdata
);
186 case BTimedEventQueue::B_HANDLE_BUFFER
:
187 case BTimedEventQueue::B_DATA_STATUS
:
188 case BTimedEventQueue::B_PARAMETER
:
190 TRACE("HandleEvent: Unhandled event -- %lx\n", event
->type
);
197 VideoProducer::DeleteHook(BMediaNode
* node
)
199 return BMediaEventLooper::DeleteHook(node
);
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
;
214 *_format
= fOutput
.format
;
220 VideoProducer::FormatProposal(const media_source
& output
, media_format
* format
)
222 #ifdef TRACE_VIDEO_PRODUCER
224 string_for_format(*format
, string
, 256);
225 FUNCTION("FormatProposal(%s)\n", string
);
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
;
237 ERROR("FormatProposal() error: %s\n", strerror(ret
));
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
253 VideoProducer::FormatChangeRequested(const media_source
& source
,
254 const media_destination
& destination
, media_format
* ioFormat
,
257 TOUCH(destination
); TOUCH(ioFormat
); TOUCH(_deprecated_
);
259 if (source
!= fOutput
.source
)
260 return B_MEDIA_BAD_SOURCE
;
267 VideoProducer::GetNextOutput(int32
* cookie
, media_output
* outOutput
)
275 *outOutput
= fOutput
;
283 VideoProducer::DisposeOutputCookie(int32 cookie
)
292 VideoProducer::SetBufferGroup(const media_source
& forSource
,
295 if (forSource
!= fOutput
.source
)
296 return B_MEDIA_BAD_SOURCE
;
298 TRACE("VideoProducer::SetBufferGroup() - using buffer group of "
300 fUsedBufferGroup
= group
;
307 VideoProducer::VideoClippingChanged(const media_source
& forSource
,
308 int16 numShorts
, int16
* clipData
, const media_video_display_info
& display
,
311 TOUCH(forSource
); TOUCH(numShorts
); TOUCH(clipData
);
312 TOUCH(display
); TOUCH(_deprecated_
);
319 VideoProducer::GetLatency(bigtime_t
* _latency
)
324 *_latency
= EventLatency() + SchedulingLatency();
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
);
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
);
377 VideoProducer::Connect(status_t error
, const media_source
& source
,
378 const media_destination
& destination
, const media_format
& format
,
381 FUNCTION("Connect() %ldx%ld\n",
382 format
.u
.raw_video
.display
.line_width
,
383 format
.u
.raw_video
.display
.line_count
);
386 ERROR("Connect() - already connected.\n");
390 if (source
!= fOutput
.source
) {
391 ERROR("Connect() - wrong source.\n");
395 ERROR("Connect() - consumer error: %s\n", strerror(error
));
398 if (!const_cast<media_format
*>(&format
)->Matches(&fOutput
.format
)) {
399 ERROR("Connect() - format mismatch.\n");
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
);
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();
430 ERROR("Connect() - buffer group error: %s\n", strerror(err
));
433 fUsedBufferGroup
= fBufferGroup
;
437 fBufferLatency
= (BUFFER_COUNT
- 1) * fBufferDuration
;
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
);
453 // Tell frame generation thread to recalculate delay value
454 release_sem(fFrameSync
);
459 VideoProducer::Disconnect(const media_source
& source
,
460 const media_destination
& destination
)
462 FUNCTION("Disconnect()\n");
465 ERROR("Disconnect() - Not connected\n");
469 if ((source
!= fOutput
.source
) || (destination
!= fOutput
.destination
)) {
470 ERROR("Disconnect() - Bad source and/or destination\n");
475 fOutput
.destination
= media_destination::null
;
478 // Always delete the buffer group, even if it is not ours.
479 // (See BeBook::SetBufferGroup()).
480 delete fUsedBufferGroup
;
481 if (fBufferGroup
!= fUsedBufferGroup
)
483 fUsedBufferGroup
= NULL
;
489 TRACE("Disconnect() done\n");
494 VideoProducer::LateNoticeReceived(const media_source
&source
,
495 bigtime_t how_much
, bigtime_t performanceTime
)
497 TOUCH(source
); TOUCH(how_much
); TOUCH(performanceTime
);
503 VideoProducer::EnableOutput(const media_source
& source
, bool enabled
,
508 if (source
!= fOutput
.source
)
516 VideoProducer::SetPlayRate(int32 numer
, int32 denom
)
518 TOUCH(numer
); TOUCH(denom
);
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
);
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");
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
);
553 TRACE("_HandleStart: Node already started\n");
559 fPerformanceTimeBase
= performanceTime
;
561 fFrameSync
= create_sem(0, "frame synchronization");
562 if (fFrameSync
< B_OK
)
565 fThread
= spawn_thread(_FrameGeneratorThreadEntry
, "frame generator",
566 B_NORMAL_PRIORITY
, this);
567 if (fThread
< B_OK
) {
568 delete_sem(fFrameSync
);
572 resume_thread(fThread
);
579 VideoProducer::_HandleStop()
581 TRACE("_HandleStop()\n");
584 TRACE("_HandleStop: Node isn't running\n");
588 delete_sem(fFrameSync
);
589 wait_for_thread(fThread
, &fThread
);
596 VideoProducer::_HandleTimeWarp(bigtime_t performanceTime
)
598 fPerformanceTimeBase
= performanceTime
;
601 // Tell frame generation thread to recalculate delay value
602 release_sem(fFrameSync
);
607 VideoProducer::_HandleSeek(bigtime_t performanceTime
)
609 fPerformanceTimeBase
= performanceTime
;
612 // Tell frame generation thread to recalculate delay value
613 release_sem(fFrameSync
);
618 VideoProducer::_FrameGeneratorThreadEntry(void* data
)
620 return ((VideoProducer
*)data
)->_FrameGeneratorThread();
625 VideoProducer::_FrameGeneratorThread()
627 bool forceSendingBuffer
= true;
628 int32 droppedFrames
= 0;
629 const int32 kMaxDroppedFrames
= 15;
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;
645 TRACE("_FrameGeneratorThread: node manager successfully "
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
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
);
669 TRACE("_FrameGeneratorThread: Couldn't lock the node "
672 waitUntil
= system_time() - 1;
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
683 TRACE("_FrameGeneratorThread: waiting (%Ld)...\n", waitUntil
);
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.
691 TRACE("_FrameGeneratorThread: going back to sleep.\n");
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");
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
,
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");
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");
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
;
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
;
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
,
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
764 // clean the buffer if something went wrong
766 // TODO: should use "back value" according
768 memset(buffer
->Data(), 0, h
->size_used
);
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.
779 // we tell the supplier to delete
780 // its caches if there was a problem sending
783 ERROR("_FrameGeneratorThread: Error "
785 fSupplier
->DeleteCaches();
788 // Only if everything went fine we clear the flag
789 // that forces us to send a buffer even if not
792 forceSendingBuffer
= false;
797 TRACE("_FrameGeneratorThread: not playing\n");
803 TRACE("_FrameGeneratorThread: Couldn't acquire semaphore. "
804 "Error: %s\n", strerror(err
));
809 TRACE("_FrameGeneratorThread: frame generator thread done.\n");