1 /* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved.
2 * Distributed under the terms of the Be Sample Code license.
3 *
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 */
8 #include "VideoConsumer.h"
9
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
14
15 #include <Buffer.h>
16 #include <BufferGroup.h>
17 #include <NodeInfo.h>
18 #include <Bitmap.h>
19 #include <View.h>
20 #include <scheduler.h>
21 #include <TimeSource.h>
22 #include <MediaRoster.h>
23
24 #include "ColorSpaceToString.h"
25 #include "NodeManager.h"
26 #include "VideoTarget.h"
27
28
29 // debugging
30 //#define TRACE_VIDEO_CONSUMER
31 #ifdef TRACE_VIDEO_CONSUMER
32 # define TRACE(x...) printf(x)
33 # define PROGRESS(x...) printf(x)
34 # define FUNCTION(x...) printf(x)
35 # define LOOP(x...) printf(x)
36 # define ERROR(x...) fprintf(stderr, x)
37 #else
38 # define TRACE(x...)
39 # define PROGRESS(x...)
40 # define FUNCTION(x...)
41 # define LOOP(x...)
42 # define ERROR(x...) fprintf(stderr, x)
43 #endif
44
45 #define M1 ((double)1000000.0)
46 static const bigtime_t kMaxBufferLateness = 20000LL;
47
48
VideoConsumer(const char * name,BMediaAddOn * addon,const uint32 internal_id,NodeManager * manager,VideoTarget * target)49 VideoConsumer::VideoConsumer(const char* name, BMediaAddOn* addon,
50 const uint32 internal_id, NodeManager* manager,
51 VideoTarget* target)
52 : BMediaNode(name),
53 BMediaEventLooper(),
54 BBufferConsumer(B_MEDIA_RAW_VIDEO),
55 fInternalID(internal_id),
56 fAddOn(addon),
57 fConnectionActive(false),
58 fMyLatency(3000),
59 fOurBuffers(false),
60 fBuffers(NULL),
61 fManager(manager),
62 fTargetLock(),
63 fTarget(target),
64 fLastBufferIndex(-1),
65 fTryOverlay(true)
66 {
67 FUNCTION("VideoConsumer::VideoConsumer\n");
68
69 AddNodeKind(B_PHYSICAL_OUTPUT);
70 SetEventLatency(0);
71
72 for (uint32 i = 0; i < kBufferCount; i++) {
73 fBitmap[i] = NULL;
74 fBufferMap[i] = NULL;
75 }
76
77 SetPriority(B_DISPLAY_PRIORITY);
78 }
79
80
~VideoConsumer()81 VideoConsumer::~VideoConsumer()
82 {
83 Quit();
84 DeleteBuffers();
85 }
86
87
88 BMediaAddOn*
AddOn(int32 * cookie) const89 VideoConsumer::AddOn(int32* cookie) const
90 {
91 FUNCTION("VideoConsumer::AddOn\n");
92 // do the right thing if we're ever used with an add-on
93 *cookie = fInternalID;
94 return fAddOn;
95 }
96
97
98 void
NodeRegistered()99 VideoConsumer::NodeRegistered()
100 {
101 FUNCTION("VideoConsumer::NodeRegistered\n");
102 fIn.destination.port = ControlPort();
103 fIn.destination.id = 0;
104 fIn.source = media_source::null;
105 fIn.format.type = B_MEDIA_RAW_VIDEO;
106 // wild cards yet
107 fIn.format.u.raw_video = media_raw_video_format::wildcard;
108 fIn.format.u.raw_video.interlace = 1;
109 fIn.format.u.raw_video.display.format = B_NO_COLOR_SPACE;
110 fIn.format.u.raw_video.display.bytes_per_row = 0;
111 fIn.format.u.raw_video.display.line_width = 0;
112 fIn.format.u.raw_video.display.line_count = 0;
113
114 Run();
115 }
116
117
118 status_t
RequestCompleted(const media_request_info & info)119 VideoConsumer::RequestCompleted(const media_request_info& info)
120 {
121 FUNCTION("VideoConsumer::RequestCompleted\n");
122 switch(info.what) {
123 case media_request_info::B_SET_OUTPUT_BUFFERS_FOR:
124 if (info.status != B_OK)
125 ERROR("VideoConsumer::RequestCompleted: Not using our "
126 "buffers!\n");
127 break;
128
129 default:
130 break;
131 }
132 return B_OK;
133 }
134
135
136 status_t
HandleMessage(int32 message,const void * data,size_t size)137 VideoConsumer::HandleMessage(int32 message, const void* data, size_t size)
138 {
139 return B_OK;
140 }
141
142
143 void
BufferReceived(BBuffer * buffer)144 VideoConsumer::BufferReceived(BBuffer* buffer)
145 {
146 LOOP("VideoConsumer::Buffer #%" B_PRId32 " received\n", buffer->ID());
147
148 if (RunState() == B_STOPPED) {
149 buffer->Recycle();
150 return;
151 }
152
153 media_timed_event event(buffer->Header()->start_time,
154 BTimedEventQueue::B_HANDLE_BUFFER, buffer,
155 BTimedEventQueue::B_RECYCLE_BUFFER);
156 EventQueue()->AddEvent(event);
157 }
158
159
160 void
ProducerDataStatus(const media_destination & forWhom,int32 status,bigtime_t atMediaTime)161 VideoConsumer::ProducerDataStatus(const media_destination& forWhom,
162 int32 status, bigtime_t atMediaTime)
163 {
164 FUNCTION("VideoConsumer::ProducerDataStatus\n");
165
166 if (forWhom != fIn.destination)
167 return;
168 }
169
170
171 status_t
CreateBuffers(const media_format & format)172 VideoConsumer::CreateBuffers(const media_format& format)
173 {
174 FUNCTION("VideoConsumer::CreateBuffers\n");
175
176 // delete any old buffers
177 DeleteBuffers();
178
179 status_t status = B_OK;
180
181 // create a buffer group
182 uint32 width = format.u.raw_video.display.line_width;
183 uint32 height = format.u.raw_video.display.line_count;
184 color_space colorSpace = format.u.raw_video.display.format;
185 PROGRESS("VideoConsumer::CreateBuffers - Width = %" B_PRIu32 " - "
186 "Height = %" B_PRIu32 " - Colorspace = %d\n",
187 width, height, colorSpace);
188
189 fBuffers = new BBufferGroup();
190 status = fBuffers->InitCheck();
191 if (B_OK != status) {
192 ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n");
193 return status;
194 }
195
196 // and attach the bitmaps to the buffer group
197 BRect bounds(0, 0, width - 1, height - 1);
198 for (uint32 i = 0; i < kBufferCount; i++) {
199 // figure out the bitmap creation flags
200 uint32 bitmapFlags = 0;
201 if (fTryOverlay) {
202 // try to use hardware overlay
203 bitmapFlags |= B_BITMAP_WILL_OVERLAY;
204 if (i == 0)
205 bitmapFlags |= B_BITMAP_RESERVE_OVERLAY_CHANNEL;
206 } else
207 bitmapFlags = B_BITMAP_IS_LOCKED;
208
209 fBitmap[i] = new BBitmap(bounds, bitmapFlags, colorSpace);
210 status = fBitmap[i]->InitCheck();
211 if (status >= B_OK) {
212 buffer_clone_info info;
213
214 uint8* bits = (uint8*)fBitmap[i]->Bits();
215 info.area = area_for(bits);
216 area_info bitmapAreaInfo;
217 status = get_area_info(info.area, &bitmapAreaInfo);
218 if (status != B_OK) {
219 fprintf(stderr, "VideoConsumer::CreateBuffers() - "
220 "get_area_info(): %s\n", strerror(status));
221 return status;
222 }
223
224 //printf("area info for bitmap %ld (%p):\n", i, fBitmap[i]->Bits());
225 //printf(" area: %ld\n", bitmapAreaInfo.area);
226 //printf(" size: %ld\n", bitmapAreaInfo.size);
227 //printf(" lock: %ld\n", bitmapAreaInfo.lock);
228 //printf(" protection: %ld\n", bitmapAreaInfo.protection);
229 //printf(" ram size: %ld\n", bitmapAreaInfo.ram_size);
230 //printf(" copy_count: %ld\n", bitmapAreaInfo.copy_count);
231 //printf(" out_count: %ld\n", bitmapAreaInfo.out_count);
232 //printf(" address: %p\n", bitmapAreaInfo.address);
233
234 info.offset = bits - (uint8*)bitmapAreaInfo.address;
235 info.size = (size_t)fBitmap[i]->BitsLength();
236 info.flags = 0;
237 info.buffer = 0;
238 // the media buffer id
239
240 BBuffer* buffer = NULL;
241 if ((status = fBuffers->AddBuffer(info, &buffer)) != B_OK) {
242 ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER "
243 "TO GROUP (%" B_PRId32 "): %s\n", i, strerror(status));
244 return status;
245 } else {
246 PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD "
247 "BUFFER TO GROUP\n");
248 }
249 fBufferMap[i] = buffer;
250 } else {
251 ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING "
252 "BUFFER (Index %" B_PRId32 " Width %" B_PRId32 " Height %"
253 B_PRId32 " Colorspace %d: %s\n", i, width, height, colorSpace,
254 strerror(status));
255 return status;
256 }
257 }
258
259 FUNCTION("VideoConsumer::CreateBuffers - EXIT\n");
260 return status;
261 }
262
263
264 void
DeleteBuffers()265 VideoConsumer::DeleteBuffers()
266 {
267 FUNCTION("VideoConsumer::DeleteBuffers\n");
268 if (fBuffers) {
269 fTargetLock.Lock();
270 if (fLastBufferIndex >= 0) {
271 if (fTarget)
272 fTarget->SetBitmap(NULL);
273 fLastBufferIndex = -1;
274 }
275 fTargetLock.Unlock();
276
277 delete fBuffers;
278 fBuffers = NULL;
279
280 for (uint32 i = 0; i < kBufferCount; i++) {
281 snooze(20000);
282 delete fBitmap[i];
283 fBitmap[i] = NULL;
284 }
285 }
286 FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n");
287 }
288
289
290 void
SetTarget(VideoTarget * target)291 VideoConsumer::SetTarget(VideoTarget* target)
292 {
293 fTargetLock.Lock();
294 if (fTarget)
295 fTarget->SetBitmap(NULL);
296 fTarget = target;
297 if (fTarget && fLastBufferIndex >= 0)
298 fTarget->SetBitmap(fBitmap[fLastBufferIndex]);
299 fTargetLock.Unlock();
300 }
301
302
303 void
SetTryOverlay(bool tryOverlay)304 VideoConsumer::SetTryOverlay(bool tryOverlay)
305 {
306 fTryOverlay = tryOverlay;
307 }
308
309
310 status_t
Connected(const media_source & producer,const media_destination & where,const media_format & format,media_input * outInput)311 VideoConsumer::Connected(const media_source& producer,
312 const media_destination& where, const media_format& format,
313 media_input* outInput)
314 {
315 FUNCTION("VideoConsumer::Connected\n");
316
317 fIn.source = producer;
318 fIn.format = format;
319 fIn.node = Node();
320 sprintf(fIn.name, "Video Consumer");
321 *outInput = fIn;
322
323 uint32 userData = 0;
324 int32 changeTag = 1;
325 status_t ret = CreateBuffers(format);
326 if (ret == B_OK) {
327 // TODO: With overlay bitmaps, there seems to be a problem with
328 // mapping the BBitmap areas into the BBuffers. Until that is fixed,
329 // don't enable a shared BBufferGroup.
330 if (!fTryOverlay) {
331 ret = SetOutputBuffersFor(producer, fIn.destination,
332 fBuffers, &userData, &changeTag, true);
333 if (ret != B_OK)
334 ERROR("SetOutputBuffersFor() failed: %s\n", strerror(ret));
335 }
336 fIn.format.u.raw_video.display.bytes_per_row
337 = fBitmap[0]->BytesPerRow();
338 } else {
339 ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n");
340 return ret;
341 }
342
343 *outInput = fIn;
344 // bytes per row might have changed
345 fConnectionActive = true;
346
347 FUNCTION("VideoConsumer::Connected - EXIT\n");
348 return B_OK;
349 }
350
351
352 void
Disconnected(const media_source & producer,const media_destination & where)353 VideoConsumer::Disconnected(const media_source& producer,
354 const media_destination& where)
355 {
356 FUNCTION("VideoConsumer::Disconnected\n");
357
358 if (where != fIn.destination || producer != fIn.source)
359 return;
360
361 // reclaim our buffers
362 int32 changeTag = 0;
363 SetOutputBuffersFor(producer, fIn.destination, NULL, NULL, &changeTag,
364 false);
365 if (fOurBuffers) {
366 status_t reclaimError = fBuffers->ReclaimAllBuffers();
367 if (reclaimError != B_OK) {
368 fprintf(stderr, "VideoConsumer::Disconnected() - Failed to "
369 "reclaim our buffers: %s\n", strerror(reclaimError));
370 }
371 }
372 // disconnect the connection
373 fIn.source = media_source::null;
374 fConnectionActive = false;
375
376 // Unset the target's bitmap. Just to be safe -- since it is usually
377 // done when the stop event arrives, but someone may disonnect
378 // without stopping us before.
379 _UnsetTargetBuffer();
380 }
381
382
383 status_t
AcceptFormat(const media_destination & dest,media_format * format)384 VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format)
385 {
386 FUNCTION("VideoConsumer::AcceptFormat\n");
387
388 if (dest != fIn.destination) {
389 ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n");
390 return B_MEDIA_BAD_DESTINATION;
391 }
392
393 if (format->type == B_MEDIA_NO_TYPE)
394 format->type = B_MEDIA_RAW_VIDEO;
395
396 if (format->type != B_MEDIA_RAW_VIDEO) {
397 ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n");
398 return B_MEDIA_BAD_FORMAT;
399 }
400
401 if (format->u.raw_video.display.format
402 != media_raw_video_format::wildcard.display.format) {
403 uint32 flags = 0;
404 bool supported = bitmaps_support_space(
405 format->u.raw_video.display.format, &flags);
406 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
407 // GRRR! BeOS implementation claims not
408 // to support these formats, while they work just fine.
409 switch (format->u.raw_video.display.format) {
410 case B_YCbCr422:
411 case B_YCbCr411:
412 case B_YCbCr444:
413 case B_YCbCr420:
414 supported = true;
415 break;
416 default:
417 break;
418 }
419 #endif
420 if (!supported) {
421 // cannot create bitmaps with such a color space
422 ERROR("AcceptFormat - unsupported color space for BBitmaps "
423 "(%s)!\n",
424 color_space_to_string(format->u.raw_video.display.format));
425 return B_MEDIA_BAD_FORMAT;
426 }
427 if (!fTryOverlay && (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) {
428 // BViews do not support drawing such a bitmap
429 ERROR("AcceptFormat - BViews cannot draw bitmaps in given "
430 "colorspace (%s)!\n",
431 color_space_to_string(format->u.raw_video.display.format));
432 return B_MEDIA_BAD_FORMAT;
433 }
434 }
435
436 #ifdef TRACE_VIDEO_CONSUMER
437 char string[256];
438 string[0] = 0;
439 string_for_format(*format, string, 256);
440 FUNCTION("VideoConsumer::AcceptFormat: %s\n", string);
441 #endif
442
443 return B_OK;
444 }
445
446
447 status_t
GetNextInput(int32 * cookie,media_input * outInput)448 VideoConsumer::GetNextInput(int32* cookie, media_input* outInput)
449 {
450 FUNCTION("VideoConsumer::GetNextInput\n");
451
452 // custom build a destination for this connection
453 // put connection number in id
454
455 if (*cookie < 1) {
456 fIn.node = Node();
457 fIn.destination.id = *cookie;
458 sprintf(fIn.name, "Video Consumer");
459 *outInput = fIn;
460 (*cookie)++;
461 return B_OK;
462 } else {
463 return B_MEDIA_BAD_DESTINATION;
464 }
465 }
466
467
468 void
DisposeInputCookie(int32)469 VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
470 {
471 }
472
473
474 status_t
GetLatencyFor(const media_destination & whom,bigtime_t * _latency,media_node_id * _timeSource)475 VideoConsumer::GetLatencyFor(const media_destination& whom,
476 bigtime_t* _latency, media_node_id* _timeSource)
477 {
478 FUNCTION("VideoConsumer::GetLatencyFor\n");
479
480 if (whom != fIn.destination)
481 return B_MEDIA_BAD_DESTINATION;
482
483 *_latency = fMyLatency;
484 *_timeSource = TimeSource()->ID();
485 return B_OK;
486 }
487
488
489 status_t
FormatChanged(const media_source & producer,const media_destination & consumer,int32 fromChangeCount,const media_format & format)490 VideoConsumer::FormatChanged(const media_source& producer,
491 const media_destination& consumer, int32 fromChangeCount,
492 const media_format& format)
493 {
494 FUNCTION("VideoConsumer::FormatChanged\n");
495
496 if (consumer != fIn.destination)
497 return B_MEDIA_BAD_DESTINATION;
498
499 if (producer != fIn.source)
500 return B_MEDIA_BAD_SOURCE;
501
502 fIn.format = format;
503
504 return CreateBuffers(format);
505 }
506
507
508 void
HandleEvent(const media_timed_event * event,bigtime_t lateness,bool realTimeEvent)509 VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness,
510 bool realTimeEvent)
511 {
512 LOOP("VideoConsumer::HandleEvent\n");
513
514 switch (event->type) {
515 case BTimedEventQueue::B_START:
516 PROGRESS("VideoConsumer::HandleEvent - START\n");
517 _SetPerformanceTimeBase(event->event_time);
518 break;
519 case BTimedEventQueue::B_WARP:
520 case BTimedEventQueue::B_SEEK:
521 PROGRESS("VideoConsumer::HandleEvent - WARP or SEEK\n");
522 _SetPerformanceTimeBase(event->bigdata);
523 break;
524
525 case BTimedEventQueue::B_STOP:
526 PROGRESS("VideoConsumer::HandleEvent - STOP\n");
527 EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
528 // unset the target's bitmap
529 _UnsetTargetBuffer();
530 break;
531
532 case BTimedEventQueue::B_HANDLE_BUFFER:
533 LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n");
534 _HandleBuffer(static_cast<BBuffer*>(event->pointer));
535 break;
536 default:
537 ERROR("VideoConsumer::HandleEvent - BAD EVENT\n");
538 break;
539 }
540 }
541
542
543 void
_SetPerformanceTimeBase(bigtime_t performanceTime)544 VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime)
545 {
546 fPerformanceTimeBase = performanceTime;
547 }
548
549
550 void
_HandleBuffer(BBuffer * buffer)551 VideoConsumer::_HandleBuffer(BBuffer* buffer)
552 {
553 if (RunState() != B_STARTED || !fConnectionActive) {
554 TRACE("RunState() != B_STARTED\n");
555 buffer->Recycle();
556 return;
557 }
558
559 // See if this is one of our BBitmap buffers
560 uint32 index = 0;
561 fOurBuffers = true;
562 while (index < kBufferCount) {
563 if (buffer->ID() == fBufferMap[index]->ID())
564 break;
565 else
566 index++;
567 }
568 if (index == kBufferCount) {
569 // Buffers belong to consumer
570 // NOTE: We maintain this in a member variable, since we still need
571 // to recycle this buffer later on, in case it was the last buffer
572 // received before shutting down.
573 fOurBuffers = false;
574 index = (fLastBufferIndex + 1) % kBufferCount;
575 }
576
577 bool recycle = true;
578 bigtime_t now = TimeSource()->Now();
579 if (RunMode() == B_OFFLINE
580 || now < buffer->Header()->start_time + kMaxBufferLateness) {
581 // Only display the buffer if it's not too late, or if we are
582 // in B_OFFLINE run-mode.
583 if (!fOurBuffers) {
584 memcpy(fBitmap[index]->Bits(), buffer->Data(),
585 fBitmap[index]->BitsLength());
586 }
587 bigtime_t tooEarly = buffer->Header()->start_time - now;
588 if (tooEarly > 3000)
589 snooze(tooEarly);
590
591 fTargetLock.Lock();
592 if (fTarget) {
593 fTarget->SetBitmap(fBitmap[index]);
594 if (fOurBuffers) {
595 // recycle the previous but not the current buffer
596 if (fLastBufferIndex >= 0)
597 fBufferMap[fLastBufferIndex]->Recycle();
598 recycle = false;
599 }
600 fLastBufferIndex = index;
601 }
602 fTargetLock.Unlock();
603 } else {
604 // Drop the buffer if it's too late.
605 if (fManager->LockWithTimeout(10000) == B_OK) {
606 fManager->FrameDropped();
607 fManager->Unlock();
608 }
609 PROGRESS("VideoConsumer::HandleEvent - DROPPED FRAME\n"
610 " start_time: %" B_PRIdBIGTIME ", current: %" B_PRIdBIGTIME ", "
611 "latency: %" B_PRIdBIGTIME "\n", buffer->Header()->start_time,
612 TimeSource()->Now(), SchedulingLatency());
613 }
614 if (recycle)
615 buffer->Recycle();
616 }
617
618
619 void
_UnsetTargetBuffer()620 VideoConsumer::_UnsetTargetBuffer()
621 {
622 fTargetLock.Lock();
623 if (fLastBufferIndex >= 0) {
624 if (fTarget)
625 fTarget->SetBitmap(NULL);
626 if (fOurBuffers)
627 fBufferMap[fLastBufferIndex]->Recycle();
628 fLastBufferIndex = -1;
629 }
630 fTargetLock.Unlock();
631 }
632