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