1 // Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. 2 // SMS 3 // VideoConsumer.cpp 4 5 6 #include "FtpClient.h" 7 #include "VideoConsumer.h" 8 9 #include <fcntl.h> 10 #include <stdio.h> 11 #include <unistd.h> 12 #include <string.h> 13 14 #include <Application.h> 15 #include <Buffer.h> 16 #include <BufferGroup.h> 17 #include <MediaRoster.h> 18 #include <NodeInfo.h> 19 #include <scheduler.h> 20 #include <StringView.h> 21 #include <TimeSource.h> 22 #include <View.h> 23 24 25 #define M1 ((double)1000000.0) 26 #define JITTER 20000 27 28 #define FUNCTION printf 29 #define ERROR printf 30 #define PROGRESS printf 31 #define LOOP printf 32 33 static status_t SetFileType(BFile* file, int32 translator, uint32 type); 34 35 const media_raw_video_format vid_format = {29.97, 1, 0, 239, B_VIDEO_TOP_LEFT_RIGHT, 36 1, 1, {B_RGB16, 320, 240, 320 * 4, 0, 0}}; 37 38 39 VideoConsumer::VideoConsumer(const char* name, BView* view, BStringView* statusLine, 40 BMediaAddOn* addon, const uint32 internalId) 41 : BMediaNode(name), BMediaEventLooper(), BBufferConsumer(B_MEDIA_RAW_VIDEO), 42 fStatusLine(statusLine), 43 fInternalID(internalId), 44 fAddOn(addon), 45 fConnectionActive(false), 46 fMyLatency(20000), 47 fWindow(NULL), 48 fView(view), 49 fOurBuffers(false), 50 fBuffers(NULL), 51 fTimeToFtp(false), 52 fFtpComplete(true), 53 fRate(1000000), 54 fImageFormat(0), 55 fTranslator(0), 56 fPassiveFtp(true) 57 { 58 FUNCTION("VideoConsumer::VideoConsumer\n"); 59 60 AddNodeKind(B_PHYSICAL_OUTPUT); 61 SetEventLatency(0); 62 fWindow = fView->Window(); 63 64 for (uint32 j = 0; j < 3; j++) { 65 fBitmap[j] = NULL; 66 fBufferMap[j] = 0; 67 } 68 69 strcpy(fFileNameText, ""); 70 strcpy(fServerText, ""); 71 strcpy(fLoginText, ""); 72 strcpy(fPasswordText, ""); 73 strcpy(fDirectoryText, ""); 74 75 SetPriority(B_DISPLAY_PRIORITY); 76 } 77 78 79 VideoConsumer::~VideoConsumer() 80 { 81 FUNCTION("VideoConsumer::~VideoConsumer\n"); 82 83 Quit(); 84 85 if (fWindow) { 86 printf("Locking the window\n"); 87 if (fWindow->Lock()) { 88 printf("Closing the window\n"); 89 fWindow->Close(); 90 fWindow = 0; 91 } 92 } 93 94 // clean up ftp thread 95 // wait up to 30 seconds if ftp is in progress 96 int32 count = 0; 97 while (!fFtpComplete && count < 30) { 98 snooze(1000000); 99 count++; 100 } 101 102 if (count == 30) 103 kill_thread(fFtpThread); 104 105 DeleteBuffers(); 106 107 } 108 109 /******************************** 110 From BMediaNode 111 ********************************/ 112 113 114 BMediaAddOn* 115 VideoConsumer::AddOn(long* cookie) const 116 { 117 FUNCTION("VideoConsumer::AddOn\n"); 118 // do the right thing if we're ever used with an add-on 119 *cookie = fInternalID; 120 return fAddOn; 121 } 122 123 124 // This implementation is required to get around a bug in 125 // the ppc compiler. 126 127 void 128 VideoConsumer::Start(bigtime_t performanceTime) 129 { 130 BMediaEventLooper::Start(performanceTime); 131 } 132 133 134 void 135 VideoConsumer::Stop(bigtime_t performanceTime, bool immediate) 136 { 137 BMediaEventLooper::Stop(performanceTime, immediate); 138 } 139 140 141 void 142 VideoConsumer::Seek(bigtime_t mediaTime, bigtime_t performanceTime) 143 { 144 BMediaEventLooper::Seek(mediaTime, performanceTime); 145 } 146 147 148 void 149 VideoConsumer::TimeWarp(bigtime_t atRealTime, bigtime_t toPerformanceTime) 150 { 151 BMediaEventLooper::TimeWarp(atRealTime, toPerformanceTime); 152 } 153 154 155 status_t 156 VideoConsumer::DeleteHook(BMediaNode* node) 157 { 158 return BMediaEventLooper::DeleteHook(node); 159 } 160 161 162 void 163 VideoConsumer::NodeRegistered() 164 { 165 FUNCTION("VideoConsumer::NodeRegistered\n"); 166 fIn.destination.port = ControlPort(); 167 fIn.destination.id = 0; 168 fIn.source = media_source::null; 169 fIn.format.type = B_MEDIA_RAW_VIDEO; 170 fIn.format.u.raw_video = vid_format; 171 172 Run(); 173 } 174 175 176 status_t 177 VideoConsumer::RequestCompleted(const media_request_info& info) 178 { 179 FUNCTION("VideoConsumer::RequestCompleted\n"); 180 switch (info.what) { 181 case media_request_info::B_SET_OUTPUT_BUFFERS_FOR: 182 if (info.status != B_OK) 183 ERROR("VideoConsumer::RequestCompleted: Not using our buffers!\n"); 184 break; 185 186 default: 187 ERROR("VideoConsumer::RequestCompleted: Invalid argument\n"); 188 break; 189 } 190 return B_OK; 191 } 192 193 194 status_t 195 VideoConsumer::HandleMessage(int32 message, const void* data, size_t size) 196 { 197 //FUNCTION("VideoConsumer::HandleMessage\n"); 198 ftp_msg_info* info = (ftp_msg_info*)data; 199 status_t status = B_OK; 200 201 switch (message) { 202 case FTP_INFO: 203 PROGRESS("VideoConsumer::HandleMessage - FTP_INFO message\n"); 204 fRate = info->rate; 205 fImageFormat = info->imageFormat; 206 fTranslator = info->translator; 207 fPassiveFtp = info->passiveFtp; 208 strcpy(fFileNameText, info->fileNameText); 209 strcpy(fServerText, info->serverText); 210 strcpy(fLoginText, info->loginText); 211 strcpy(fPasswordText, info->passwordText); 212 strcpy(fDirectoryText, info->directoryText); 213 // remove old user events 214 EventQueue()->FlushEvents(TimeSource()->Now(), BTimedEventQueue::B_ALWAYS, 215 true, BTimedEventQueue::B_USER_EVENT); 216 if (fRate != B_INFINITE_TIMEOUT) { 217 // if rate is not "Never," push an event 218 // to restart captures 5 seconds from now 219 media_timed_event event(TimeSource()->Now() + 5000000, 220 BTimedEventQueue::B_USER_EVENT); 221 EventQueue()->AddEvent(event); 222 } 223 break; 224 } 225 226 return status; 227 } 228 229 230 void 231 VideoConsumer::BufferReceived(BBuffer* buffer) 232 { 233 LOOP("VideoConsumer::Buffer #%ld received\n", buffer->ID()); 234 235 if (RunState() == B_STOPPED) { 236 buffer->Recycle(); 237 return; 238 } 239 240 media_timed_event event(buffer->Header()->start_time, BTimedEventQueue::B_HANDLE_BUFFER, 241 buffer, BTimedEventQueue::B_RECYCLE_BUFFER); 242 EventQueue()->AddEvent(event); 243 } 244 245 246 void 247 VideoConsumer::ProducerDataStatus(const media_destination& forWhom, int32 status, 248 bigtime_t atMediaTime) 249 { 250 FUNCTION("VideoConsumer::ProducerDataStatus\n"); 251 252 if (forWhom != fIn.destination) 253 return; 254 } 255 256 257 status_t 258 VideoConsumer::CreateBuffers(const media_format& withFormat) 259 { 260 FUNCTION("VideoConsumer::CreateBuffers\n"); 261 262 DeleteBuffers(); 263 // delete any old buffers 264 265 status_t status = B_OK; 266 267 // create a buffer group 268 uint32 xSize = withFormat.u.raw_video.display.line_width; 269 uint32 ySize = withFormat.u.raw_video.display.line_count; 270 color_space colorspace = withFormat.u.raw_video.display.format; 271 PROGRESS("VideoConsumer::CreateBuffers - Colorspace = %d\n", colorspace); 272 273 fBuffers = new BBufferGroup(); 274 status = fBuffers->InitCheck(); 275 if (status != B_OK) { 276 ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n"); 277 return status; 278 } 279 // and attach the bitmaps to the buffer group 280 for (uint32 j = 0; j < 3; j++) { 281 fBitmap[j] = new BBitmap(BRect(0, 0, (xSize - 1), (ySize - 1)), colorspace, 282 false, true); 283 if (fBitmap[j]->IsValid()) { 284 buffer_clone_info info; 285 if ((info.area = area_for(fBitmap[j]->Bits())) == B_ERROR) 286 ERROR("VideoConsumer::CreateBuffers - ERROR IN AREA_FOR\n"); 287 info.offset = 0; 288 info.size = (size_t)fBitmap[j]->BitsLength(); 289 info.flags = j; 290 info.buffer = 0; 291 292 if ((status = fBuffers->AddBuffer(info)) != B_OK) { 293 ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER TO GROUP\n"); 294 return status; 295 } 296 else 297 PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD BUFFER TO GROUP\n"); 298 } else { 299 ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING BUFFER: %08lx\n", 300 status); 301 return B_ERROR; 302 } 303 } 304 305 BBuffer** buffList = new BBuffer * [3]; 306 for (int j = 0; j < 3; j++) 307 buffList[j] = 0; 308 309 if ((status = fBuffers->GetBufferList(3, buffList)) == B_OK) 310 for (int j = 0; j < 3; j++) 311 if (buffList[j] != NULL) { 312 fBufferMap[j] = (uint32)buffList[j]; 313 PROGRESS(" j = %d buffer = %08lx\n", j, fBufferMap[j]); 314 } else { 315 ERROR("VideoConsumer::CreateBuffers ERROR MAPPING RING BUFFER\n"); 316 return B_ERROR; 317 } 318 else 319 ERROR("VideoConsumer::CreateBuffers ERROR IN GET BUFFER LIST\n"); 320 321 FUNCTION("VideoConsumer::CreateBuffers - EXIT\n"); 322 return status; 323 } 324 325 326 void 327 VideoConsumer::DeleteBuffers() 328 { 329 FUNCTION("VideoConsumer::DeleteBuffers\n"); 330 331 if (fBuffers) { 332 delete fBuffers; 333 fBuffers = NULL; 334 335 for (uint32 j = 0; j < 3; j++) 336 if (fBitmap[j]->IsValid()) { 337 delete fBitmap[j]; 338 fBitmap[j] = NULL; 339 } 340 } 341 FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n"); 342 } 343 344 345 status_t 346 VideoConsumer::Connected(const media_source& producer, const media_destination& where, 347 const media_format& withFormat, media_input* outInput) 348 { 349 FUNCTION("VideoConsumer::Connected\n"); 350 351 fIn.source = producer; 352 fIn.format = withFormat; 353 fIn.node = Node(); 354 sprintf(fIn.name, "Video Consumer"); 355 *outInput = fIn; 356 357 uint32 userData = 0; 358 int32 changeTag = 1; 359 if (CreateBuffers(withFormat) == B_OK) 360 BBufferConsumer::SetOutputBuffersFor(producer, fDestination, 361 fBuffers, (void *)&userData, &changeTag, true); 362 else { 363 ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n"); 364 return B_ERROR; 365 } 366 367 fFtpBitmap = new BBitmap(BRect(0, 0, 320 - 1, 240 - 1), B_RGB32, false, false); 368 fConnectionActive = true; 369 370 FUNCTION("VideoConsumer::Connected - EXIT\n"); 371 return B_OK; 372 } 373 374 375 void 376 VideoConsumer::Disconnected(const media_source& producer, const media_destination& where) 377 { 378 FUNCTION("VideoConsumer::Disconnected\n"); 379 380 if (where == fIn.destination && producer == fIn.source) { 381 // disconnect the connection 382 fIn.source = media_source::null; 383 delete fFtpBitmap; 384 fConnectionActive = false; 385 } 386 387 } 388 389 390 status_t 391 VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format) 392 { 393 FUNCTION("VideoConsumer::AcceptFormat\n"); 394 395 if (dest != fIn.destination) { 396 ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n"); 397 return B_MEDIA_BAD_DESTINATION; 398 } 399 400 if (format->type == B_MEDIA_NO_TYPE) 401 format->type = B_MEDIA_RAW_VIDEO; 402 403 if (format->type != B_MEDIA_RAW_VIDEO) { 404 ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n"); 405 return B_MEDIA_BAD_FORMAT; 406 } 407 408 if (format->u.raw_video.display.format != B_RGB32 409 && format->u.raw_video.display.format != B_RGB16 410 && format->u.raw_video.display.format != B_RGB15 411 && format->u.raw_video.display.format != B_GRAY8 412 && 413 format->u.raw_video.display.format != media_raw_video_format::wildcard.display.format) { 414 ERROR("AcceptFormat - not a format we know about!\n"); 415 return B_MEDIA_BAD_FORMAT; 416 } 417 418 if (format->u.raw_video.display.format == media_raw_video_format::wildcard.display.format) { 419 format->u.raw_video.display.format = B_RGB16; 420 } 421 422 char formatString[256]; 423 string_for_format(*format, formatString, 256); 424 FUNCTION("VideoConsumer::AcceptFormat: %s\n", formatString); 425 426 return B_OK; 427 } 428 429 430 status_t 431 VideoConsumer::GetNextInput(int32* cookie, media_input* outInput) 432 { 433 FUNCTION("VideoConsumer::GetNextInput\n"); 434 435 // custom build a destination for this connection 436 // put connection number in id 437 438 if (*cookie < 1) { 439 fIn.node = Node(); 440 fIn.destination.id = *cookie; 441 sprintf(fIn.name, "Video Consumer"); 442 *outInput = fIn; 443 (*cookie)++; 444 return B_OK; 445 } else { 446 ERROR("VideoConsumer::GetNextInput - - BAD INDEX\n"); 447 return B_MEDIA_BAD_DESTINATION; 448 } 449 } 450 451 452 void 453 VideoConsumer::DisposeInputCookie(int32 /*cookie*/) 454 { 455 } 456 457 458 status_t 459 VideoConsumer::GetLatencyFor(const media_destination& forWhom, bigtime_t* outLatency, 460 media_node_id* out_timesource) 461 { 462 FUNCTION("VideoConsumer::GetLatencyFor\n"); 463 464 if (forWhom != fIn.destination) 465 return B_MEDIA_BAD_DESTINATION; 466 467 *outLatency = fMyLatency; 468 *out_timesource = TimeSource()->ID(); 469 return B_OK; 470 } 471 472 473 status_t 474 VideoConsumer::FormatChanged(const media_source& producer, const media_destination& consumer, 475 int32 fromChangeCount, const media_format& format) 476 { 477 FUNCTION("VideoConsumer::FormatChanged\n"); 478 479 if (consumer != fIn.destination) 480 return B_MEDIA_BAD_DESTINATION; 481 482 if (producer != fIn.source) 483 return B_MEDIA_BAD_SOURCE; 484 485 fIn.format = format; 486 487 return CreateBuffers(format); 488 } 489 490 491 void 492 VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness, 493 bool realTimeEvent) 494 { 495 LOOP("VideoConsumer::HandleEvent\n"); 496 497 BBuffer* buffer; 498 499 switch (event->type) { 500 case BTimedEventQueue::B_START: 501 PROGRESS("VideoConsumer::HandleEvent - START\n"); 502 break; 503 504 case BTimedEventQueue::B_STOP: 505 PROGRESS("VideoConsumer::HandleEvent - STOP\n"); 506 EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, 507 true, BTimedEventQueue::B_HANDLE_BUFFER); 508 break; 509 510 case BTimedEventQueue::B_USER_EVENT: 511 PROGRESS("VideoConsumer::HandleEvent - USER EVENT\n"); 512 if (RunState() == B_STARTED) { 513 fTimeToFtp = true; 514 PROGRESS("Pushing user event for %.4f, time now %.4f\n", 515 (event->event_time + fRate) / M1, event->event_time/M1); 516 media_timed_event newEvent(event->event_time + fRate, 517 BTimedEventQueue::B_USER_EVENT); 518 EventQueue()->AddEvent(newEvent); 519 } 520 break; 521 522 case BTimedEventQueue::B_HANDLE_BUFFER: 523 { 524 LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n"); 525 buffer = (BBuffer *)event->pointer; 526 if (RunState() == B_STARTED && fConnectionActive) { 527 // see if this is one of our buffers 528 uint32 index = 0; 529 fOurBuffers = true; 530 while (index < 3) 531 if ((uint32)buffer == fBufferMap[index]) 532 break; 533 else 534 index++; 535 536 if (index == 3) { 537 // no, buffers belong to consumer 538 fOurBuffers = false; 539 index = 0; 540 } 541 542 if (fFtpComplete && fTimeToFtp) { 543 PROGRESS("VidConsumer::HandleEvent - SPAWNING FTP THREAD\n"); 544 fTimeToFtp = false; 545 fFtpComplete = false; 546 memcpy(fFtpBitmap->Bits(), buffer->Data(), fFtpBitmap->BitsLength()); 547 fFtpThread = spawn_thread(FtpRun, "Video Window Ftp", B_NORMAL_PRIORITY, this); 548 resume_thread(fFtpThread); 549 } 550 551 if ((RunMode() == B_OFFLINE) 552 || ((TimeSource()->Now() > (buffer->Header()->start_time - JITTER)) 553 && (TimeSource()->Now() < (buffer->Header()->start_time + JITTER)))) { 554 if (!fOurBuffers) 555 // not our buffers, so we need to copy 556 memcpy(fBitmap[index]->Bits(), buffer->Data(), fBitmap[index]->BitsLength()); 557 558 if (fWindow->Lock()) { 559 uint32 flags; 560 if ((fBitmap[index]->ColorSpace() == B_GRAY8) && 561 !bitmaps_support_space(fBitmap[index]->ColorSpace(), &flags)) { 562 // handle mapping of GRAY8 until app server knows how 563 uint32* start = (uint32*)fBitmap[index]->Bits(); 564 int32 size = fBitmap[index]->BitsLength(); 565 uint32* end = start + size / 4; 566 for (uint32* p = start; p < end; p++) 567 *p = (*p >> 3) & 0x1f1f1f1f; 568 } 569 570 fView->DrawBitmap(fBitmap[index]); 571 fWindow->Unlock(); 572 } 573 } 574 else 575 PROGRESS("VidConsumer::HandleEvent - DROPPED FRAME\n"); 576 buffer->Recycle(); 577 } 578 else 579 buffer->Recycle(); 580 break; 581 } 582 583 default: 584 ERROR("VideoConsumer::HandleEvent - BAD EVENT\n"); 585 break; 586 } 587 } 588 589 590 status_t 591 VideoConsumer::FtpRun(void* data) 592 { 593 FUNCTION("VideoConsumer::FtpRun\n"); 594 595 ((VideoConsumer *)data)->FtpThread(); 596 597 return 0; 598 } 599 600 601 void 602 VideoConsumer::FtpThread() 603 { 604 FUNCTION("VideoConsumer::FtpThread\n"); 605 606 if (LocalSave(fFileNameText, fFtpBitmap) == B_OK) 607 FtpSave(fFileNameText); 608 609 #if 0 610 // save a small version, too 611 BBitmap* b = new BBitmap(BRect(0,0,159,119), B_RGB32, true, false); 612 BView* v = new BView(BRect(0,0,159,119), "SmallView 1", 0, B_WILL_DRAW); 613 b->AddChild(v); 614 615 b->Lock(); 616 v->DrawBitmap(fFtpBitmap, v->Frame()); 617 v->Sync(); 618 b->Unlock(); 619 620 if (LocalSave("small.jpg", b) == B_OK) 621 FtpSave("small.jpg"); 622 623 delete b; 624 #endif 625 626 fFtpComplete = true; 627 } 628 629 630 void 631 VideoConsumer::UpdateFtpStatus(char* status) 632 { 633 printf("FTP STATUS: %s\n",status); 634 if (fView->Window()->Lock()) { 635 fStatusLine->SetText(status); 636 fView->Window()->Unlock(); 637 } 638 } 639 640 641 status_t 642 VideoConsumer::LocalSave(char* filename, BBitmap* bitmap) 643 { 644 BFile* output; 645 646 UpdateFtpStatus("Capturing Image ..."); 647 648 /* save a local copy of the image in the requested format */ 649 output = new BFile(); 650 if (output->SetTo(filename, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE) == B_NO_ERROR) { 651 BBitmapStream input(bitmap); 652 status_t err = BTranslatorRoster::Default()->Translate(&input, NULL, NULL, 653 output, fImageFormat); 654 if (err == B_OK) { 655 err = SetFileType(output, fTranslator, fImageFormat); 656 if (err != B_OK) 657 UpdateFtpStatus("Error setting type of output file"); 658 } 659 else 660 UpdateFtpStatus("Error writing output file"); 661 662 input.DetachBitmap(&bitmap); 663 output->Unset(); 664 delete output; 665 return B_OK; 666 } else { 667 UpdateFtpStatus("Error creating output file"); 668 return B_ERROR; 669 } 670 } 671 672 673 status_t 674 VideoConsumer::FtpSave(char* filename) 675 { 676 FtpClient ftp; 677 678 ftp.SetPassive(fPassiveFtp); 679 // ftp the local file to our web site 680 681 UpdateFtpStatus("Logging in ..."); 682 if (ftp.Connect((string)fServerText, (string)fLoginText, (string)fPasswordText)) { 683 // connect to server 684 UpdateFtpStatus("Connected ..."); 685 686 if (ftp.ChangeDir((string)fDirectoryText)) { 687 // cd to the desired directory 688 UpdateFtpStatus("Transmitting ..."); 689 690 if (ftp.PutFile((string)filename, (string)"temp")) { 691 // send the file to the server 692 UpdateFtpStatus("Renaming ..."); 693 694 if (ftp.MoveFile((string)"temp", (string)filename)) { 695 // change to the desired name 696 uint32 time = real_time_clock(); 697 char s[80]; 698 strcpy(s, "Last Capture: "); 699 strcat(s, ctime((const long*)&time)); 700 s[strlen(s) - 1] = 0; 701 UpdateFtpStatus(s); 702 return B_OK; 703 } 704 else 705 UpdateFtpStatus("Rename failed"); 706 } 707 else 708 UpdateFtpStatus("File transmission failed"); 709 } 710 else 711 UpdateFtpStatus("Couldn't find requested directory on server"); 712 } 713 else 714 UpdateFtpStatus("Server login failed"); 715 716 return B_ERROR; 717 } 718 719 720 status_t 721 SetFileType(BFile* file, int32 translator, uint32 type) 722 { 723 translation_format* formats; 724 int32 count; 725 726 status_t err = BTranslatorRoster::Default()->GetOutputFormats(translator, 727 (const translation_format **)&formats, &count); 728 if (err < B_OK) 729 return err; 730 731 const char* mime = NULL; 732 for (int ix = 0; ix < count; ix++) { 733 if (formats[ix].type == type) { 734 mime = formats[ix].MIME; 735 break; 736 } 737 } 738 739 if (mime == NULL) { 740 /* this should not happen, but being defensive might be prudent */ 741 return B_ERROR; 742 } 743 744 /* use BNodeInfo to set the file type */ 745 BNodeInfo ninfo(file); 746 return ninfo.SetType(mime); 747 } 748