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 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 81 VideoConsumer::~VideoConsumer() 82 { 83 Quit(); 84 DeleteBuffers(); 85 } 86 87 88 BMediaAddOn* 89 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 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 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 137 VideoConsumer::HandleMessage(int32 message, const void* data, size_t size) 138 { 139 return B_OK; 140 } 141 142 143 void 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 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 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 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 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 304 VideoConsumer::SetTryOverlay(bool tryOverlay) 305 { 306 fTryOverlay = tryOverlay; 307 } 308 309 310 status_t 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 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 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 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 469 VideoConsumer::DisposeInputCookie(int32 /*cookie*/) 470 { 471 } 472 473 474 status_t 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 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 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 544 VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime) 545 { 546 fPerformanceTimeBase = performanceTime; 547 } 548 549 550 void 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 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