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 #%ld 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 = %ld - Height = %ld - " 186 "Colorspace = %d\n", width, height, colorSpace); 187 188 fBuffers = new BBufferGroup(); 189 status = fBuffers->InitCheck(); 190 if (B_OK != status) { 191 ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n"); 192 return status; 193 } 194 195 // and attach the bitmaps to the buffer group 196 BRect bounds(0, 0, width - 1, height - 1); 197 for (uint32 i = 0; i < kBufferCount; i++) { 198 // figure out the bitmap creation flags 199 uint32 bitmapFlags = 0; 200 if (fTryOverlay) { 201 // try to use hardware overlay 202 bitmapFlags |= B_BITMAP_WILL_OVERLAY; 203 if (i == 0) 204 bitmapFlags |= B_BITMAP_RESERVE_OVERLAY_CHANNEL; 205 } else 206 bitmapFlags = B_BITMAP_IS_LOCKED; 207 208 fBitmap[i] = new BBitmap(bounds, bitmapFlags, colorSpace); 209 status = fBitmap[i]->InitCheck(); 210 if (status >= B_OK) { 211 buffer_clone_info info; 212 213 uint8* bits = (uint8*)fBitmap[i]->Bits(); 214 info.area = area_for(bits); 215 area_info bitmapAreaInfo; 216 status = get_area_info(info.area, &bitmapAreaInfo); 217 if (status != B_OK) { 218 fprintf(stderr, "VideoConsumer::CreateBuffers() - " 219 "get_area_info(): %s\n", strerror(status)); 220 return status; 221 } 222 223 //printf("area info for bitmap %ld (%p):\n", i, fBitmap[i]->Bits()); 224 //printf(" area: %ld\n", bitmapAreaInfo.area); 225 //printf(" size: %ld\n", bitmapAreaInfo.size); 226 //printf(" lock: %ld\n", bitmapAreaInfo.lock); 227 //printf(" protection: %ld\n", bitmapAreaInfo.protection); 228 //printf(" ram size: %ld\n", bitmapAreaInfo.ram_size); 229 //printf(" copy_count: %ld\n", bitmapAreaInfo.copy_count); 230 //printf(" out_count: %ld\n", bitmapAreaInfo.out_count); 231 //printf(" address: %p\n", bitmapAreaInfo.address); 232 233 info.offset = bits - (uint8*)bitmapAreaInfo.address; 234 info.size = (size_t)fBitmap[i]->BitsLength(); 235 info.flags = 0; 236 info.buffer = 0; 237 // the media buffer id 238 239 BBuffer* buffer = NULL; 240 if ((status = fBuffers->AddBuffer(info, &buffer)) != B_OK) { 241 ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER " 242 "TO GROUP (%" B_PRId32 "): %s\n", i, strerror(status)); 243 return status; 244 } else { 245 PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD " 246 "BUFFER TO GROUP\n"); 247 } 248 fBufferMap[i] = buffer; 249 } else { 250 ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING " 251 "BUFFER (Index %" B_PRId32 " Width %" B_PRId32 " Height %" 252 B_PRId32 " Colorspace %d: %s\n", i, width, height, colorSpace, 253 strerror(status)); 254 return status; 255 } 256 } 257 258 FUNCTION("VideoConsumer::CreateBuffers - EXIT\n"); 259 return status; 260 } 261 262 263 void 264 VideoConsumer::DeleteBuffers() 265 { 266 FUNCTION("VideoConsumer::DeleteBuffers\n"); 267 if (fBuffers) { 268 fTargetLock.Lock(); 269 if (fLastBufferIndex >= 0) { 270 if (fTarget) 271 fTarget->SetBitmap(NULL); 272 fLastBufferIndex = -1; 273 } 274 fTargetLock.Unlock(); 275 276 delete fBuffers; 277 fBuffers = NULL; 278 279 for (uint32 i = 0; i < kBufferCount; i++) { 280 snooze(20000); 281 delete fBitmap[i]; 282 fBitmap[i] = NULL; 283 } 284 } 285 FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n"); 286 } 287 288 289 void 290 VideoConsumer::SetTarget(VideoTarget* target) 291 { 292 fTargetLock.Lock(); 293 if (fTarget) 294 fTarget->SetBitmap(NULL); 295 fTarget = target; 296 if (fTarget && fLastBufferIndex >= 0) 297 fTarget->SetBitmap(fBitmap[fLastBufferIndex]); 298 fTargetLock.Unlock(); 299 } 300 301 302 void 303 VideoConsumer::SetTryOverlay(bool tryOverlay) 304 { 305 fTryOverlay = tryOverlay; 306 } 307 308 309 status_t 310 VideoConsumer::Connected(const media_source& producer, 311 const media_destination& where, const media_format& format, 312 media_input* outInput) 313 { 314 FUNCTION("VideoConsumer::Connected\n"); 315 316 fIn.source = producer; 317 fIn.format = format; 318 fIn.node = Node(); 319 sprintf(fIn.name, "Video Consumer"); 320 *outInput = fIn; 321 322 uint32 userData = 0; 323 int32 changeTag = 1; 324 status_t ret = CreateBuffers(format); 325 if (ret == B_OK) { 326 // TODO: With overlay bitmaps, there seems to be a problem with 327 // mapping the BBitmap areas into the BBuffers. Until that is fixed, 328 // don't enable a shared BBufferGroup. 329 if (!fTryOverlay) { 330 ret = SetOutputBuffersFor(producer, fIn.destination, 331 fBuffers, &userData, &changeTag, true); 332 if (ret != B_OK) 333 ERROR("SetOutputBuffersFor() failed: %s\n", strerror(ret)); 334 } 335 fIn.format.u.raw_video.display.bytes_per_row 336 = fBitmap[0]->BytesPerRow(); 337 } else { 338 ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n"); 339 return ret; 340 } 341 342 *outInput = fIn; 343 // bytes per row might have changed 344 fConnectionActive = true; 345 346 FUNCTION("VideoConsumer::Connected - EXIT\n"); 347 return B_OK; 348 } 349 350 351 void 352 VideoConsumer::Disconnected(const media_source& producer, 353 const media_destination& where) 354 { 355 FUNCTION("VideoConsumer::Disconnected\n"); 356 357 if (where != fIn.destination || producer != fIn.source) 358 return; 359 360 // reclaim our buffers 361 int32 changeTag = 0; 362 SetOutputBuffersFor(producer, fIn.destination, NULL, NULL, &changeTag, 363 false); 364 if (fOurBuffers) { 365 status_t reclaimError = fBuffers->ReclaimAllBuffers(); 366 if (reclaimError != B_OK) { 367 fprintf(stderr, "VideoConsumer::Disconnected() - Failed to " 368 "reclaim our buffers: %s\n", strerror(reclaimError)); 369 } 370 } 371 // disconnect the connection 372 fIn.source = media_source::null; 373 fConnectionActive = false; 374 375 // Unset the target's bitmap. Just to be safe -- since it is usually 376 // done when the stop event arrives, but someone may disonnect 377 // without stopping us before. 378 _UnsetTargetBuffer(); 379 } 380 381 382 status_t 383 VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format) 384 { 385 FUNCTION("VideoConsumer::AcceptFormat\n"); 386 387 if (dest != fIn.destination) { 388 ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n"); 389 return B_MEDIA_BAD_DESTINATION; 390 } 391 392 if (format->type == B_MEDIA_NO_TYPE) 393 format->type = B_MEDIA_RAW_VIDEO; 394 395 if (format->type != B_MEDIA_RAW_VIDEO) { 396 ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n"); 397 return B_MEDIA_BAD_FORMAT; 398 } 399 400 if (format->u.raw_video.display.format 401 != media_raw_video_format::wildcard.display.format) { 402 uint32 flags = 0; 403 bool supported = bitmaps_support_space( 404 format->u.raw_video.display.format, &flags); 405 #ifndef HAIKU_TARGET_PLATFORM_HAIKU 406 // GRRR! BeOS implementation claims not 407 // to support these formats, while they work just fine. 408 switch (format->u.raw_video.display.format) { 409 case B_YCbCr422: 410 case B_YCbCr411: 411 case B_YCbCr444: 412 case B_YCbCr420: 413 supported = true; 414 break; 415 default: 416 break; 417 } 418 #endif 419 if (!supported) { 420 // cannot create bitmaps with such a color space 421 ERROR("AcceptFormat - unsupported color space for BBitmaps " 422 "(%s)!\n", 423 color_space_to_string(format->u.raw_video.display.format)); 424 return B_MEDIA_BAD_FORMAT; 425 } 426 if (!fTryOverlay && (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) { 427 // BViews do not support drawing such a bitmap 428 ERROR("AcceptFormat - BViews cannot draw bitmaps in given " 429 "colorspace (%s)!\n", 430 color_space_to_string(format->u.raw_video.display.format)); 431 return B_MEDIA_BAD_FORMAT; 432 } 433 } 434 435 #ifdef TRACE_VIDEO_CONSUMER 436 char string[256]; 437 string[0] = 0; 438 string_for_format(*format, string, 256); 439 FUNCTION("VideoConsumer::AcceptFormat: %s\n", string); 440 #endif 441 442 return B_OK; 443 } 444 445 446 status_t 447 VideoConsumer::GetNextInput(int32* cookie, media_input* outInput) 448 { 449 FUNCTION("VideoConsumer::GetNextInput\n"); 450 451 // custom build a destination for this connection 452 // put connection number in id 453 454 if (*cookie < 1) { 455 fIn.node = Node(); 456 fIn.destination.id = *cookie; 457 sprintf(fIn.name, "Video Consumer"); 458 *outInput = fIn; 459 (*cookie)++; 460 return B_OK; 461 } else { 462 return B_MEDIA_BAD_DESTINATION; 463 } 464 } 465 466 467 void 468 VideoConsumer::DisposeInputCookie(int32 /*cookie*/) 469 { 470 } 471 472 473 status_t 474 VideoConsumer::GetLatencyFor(const media_destination& whom, 475 bigtime_t* _latency, media_node_id* _timeSource) 476 { 477 FUNCTION("VideoConsumer::GetLatencyFor\n"); 478 479 if (whom != fIn.destination) 480 return B_MEDIA_BAD_DESTINATION; 481 482 *_latency = fMyLatency; 483 *_timeSource = TimeSource()->ID(); 484 return B_OK; 485 } 486 487 488 status_t 489 VideoConsumer::FormatChanged(const media_source& producer, 490 const media_destination& consumer, int32 fromChangeCount, 491 const media_format& format) 492 { 493 FUNCTION("VideoConsumer::FormatChanged\n"); 494 495 if (consumer != fIn.destination) 496 return B_MEDIA_BAD_DESTINATION; 497 498 if (producer != fIn.source) 499 return B_MEDIA_BAD_SOURCE; 500 501 fIn.format = format; 502 503 return CreateBuffers(format); 504 } 505 506 507 void 508 VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness, 509 bool realTimeEvent) 510 { 511 LOOP("VideoConsumer::HandleEvent\n"); 512 513 switch (event->type) { 514 case BTimedEventQueue::B_START: 515 PROGRESS("VideoConsumer::HandleEvent - START\n"); 516 _SetPerformanceTimeBase(event->event_time); 517 break; 518 case BTimedEventQueue::B_WARP: 519 case BTimedEventQueue::B_SEEK: 520 PROGRESS("VideoConsumer::HandleEvent - WARP or SEEK\n"); 521 _SetPerformanceTimeBase(event->bigdata); 522 break; 523 524 case BTimedEventQueue::B_STOP: 525 PROGRESS("VideoConsumer::HandleEvent - STOP\n"); 526 EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER); 527 // unset the target's bitmap 528 _UnsetTargetBuffer(); 529 break; 530 531 case BTimedEventQueue::B_HANDLE_BUFFER: 532 LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n"); 533 _HandleBuffer(static_cast<BBuffer*>(event->pointer)); 534 break; 535 default: 536 ERROR("VideoConsumer::HandleEvent - BAD EVENT\n"); 537 break; 538 } 539 } 540 541 542 void 543 VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime) 544 { 545 fPerformanceTimeBase = performanceTime; 546 } 547 548 549 void 550 VideoConsumer::_HandleBuffer(BBuffer* buffer) 551 { 552 if (RunState() != B_STARTED || !fConnectionActive) { 553 TRACE("RunState() != B_STARTED\n"); 554 buffer->Recycle(); 555 return; 556 } 557 558 // See if this is one of our BBitmap buffers 559 uint32 index = 0; 560 fOurBuffers = true; 561 while (index < kBufferCount) { 562 if (buffer->ID() == fBufferMap[index]->ID()) 563 break; 564 else 565 index++; 566 } 567 if (index == kBufferCount) { 568 // Buffers belong to consumer 569 // NOTE: We maintain this in a member variable, since we still need 570 // to recycle this buffer later on, in case it was the last buffer 571 // received before shutting down. 572 fOurBuffers = false; 573 index = (fLastBufferIndex + 1) % kBufferCount; 574 } 575 576 bool recycle = true; 577 bigtime_t now = TimeSource()->Now(); 578 if (RunMode() == B_OFFLINE 579 || now < buffer->Header()->start_time + kMaxBufferLateness) { 580 // Only display the buffer if it's not too late, or if we are 581 // in B_OFFLINE run-mode. 582 if (!fOurBuffers) { 583 memcpy(fBitmap[index]->Bits(), buffer->Data(), 584 fBitmap[index]->BitsLength()); 585 } 586 bigtime_t tooEarly = buffer->Header()->start_time - now; 587 if (tooEarly > 3000) 588 snooze(tooEarly); 589 590 fTargetLock.Lock(); 591 if (fTarget) { 592 fTarget->SetBitmap(fBitmap[index]); 593 if (fOurBuffers) { 594 // recycle the previous but not the current buffer 595 if (fLastBufferIndex >= 0) 596 fBufferMap[fLastBufferIndex]->Recycle(); 597 recycle = false; 598 } 599 fLastBufferIndex = index; 600 } 601 fTargetLock.Unlock(); 602 } else { 603 // Drop the buffer if it's too late. 604 if (fManager->LockWithTimeout(10000) == B_OK) { 605 fManager->FrameDropped(); 606 fManager->Unlock(); 607 } 608 PROGRESS("VideoConsumer::HandleEvent - DROPPED FRAME\n" 609 " start_time: %lld, current: %lld, latency: %lld\n", 610 buffer->Header()->start_time, TimeSource()->Now(), 611 SchedulingLatency()); 612 } 613 if (recycle) 614 buffer->Recycle(); 615 } 616 617 618 void 619 VideoConsumer::_UnsetTargetBuffer() 620 { 621 fTargetLock.Lock(); 622 if (fLastBufferIndex >= 0) { 623 if (fTarget) 624 fTarget->SetBitmap(NULL); 625 if (fOurBuffers) 626 fBufferMap[fLastBufferIndex]->Recycle(); 627 fLastBufferIndex = -1; 628 } 629 fTargetLock.Unlock(); 630 } 631