1 /* 2 * Copyright (c) 2000-2008, Ingo Weinhold <ingo_weinhold@gmx.de>, 3 * Copyright (c) 2000-2008, Stephan Aßmus <superstippi@gmx.de>, 4 * All Rights Reserved. Distributed under the terms of the MIT license. 5 */ 6 7 8 //! This class controls our media nodes and general playback 9 10 11 #include "NodeManager.h" 12 13 #include <stdio.h> 14 #include <string.h> 15 16 #include <MediaRoster.h> 17 #include <scheduler.h> 18 #include <TimeSource.h> 19 20 #include "AudioProducer.h" 21 #include "AudioSupplier.h" 22 #include "VideoConsumer.h" 23 #include "VideoProducer.h" 24 #include "VideoSupplier.h" 25 26 27 // debugging 28 //#define TRACE_NODE_MANAGER 29 #ifdef TRACE_NODE_MANAGER 30 # define TRACE(x...) printf(x) 31 # define ERROR(x...) fprintf(stderr, x) 32 #else 33 # define TRACE(x...) 34 # define ERROR(x...) fprintf(stderr, x) 35 #endif 36 37 #define print_error(str, status) printf(str ", error: %s\n", strerror(status)) 38 39 40 NodeManager::Connection::Connection() 41 : 42 connected(false) 43 { 44 } 45 46 47 // #pragma mark - 48 49 50 NodeManager::NodeManager() 51 : 52 PlaybackManager(), 53 fMediaRoster(NULL), 54 fAudioProducer(NULL), 55 fVideoConsumer(NULL), 56 fVideoProducer(NULL), 57 fTimeSource(media_node::null), 58 fAudioConnection(), 59 fVideoConnection(), 60 fPerformanceTimeBase(0), 61 fStatus(B_NO_INIT), 62 fVideoTarget(NULL), 63 fAudioSupplier(NULL), 64 fVideoSupplier(NULL), 65 fVideoBounds(0, 0, -1, -1), 66 fPeakListener(NULL) 67 { 68 } 69 70 71 NodeManager::~NodeManager() 72 { 73 _StopNodes(); 74 _TearDownNodes(); 75 } 76 77 78 status_t 79 NodeManager::Init(BRect videoBounds, float videoFrameRate, 80 color_space preferredVideoFormat, float audioFrameRate, 81 uint32 audioChannels, int32 loopingMode, bool loopingEnabled, 82 float speed, uint32 enabledNodes, bool useOverlays) 83 { 84 // init base class 85 PlaybackManager::Init(videoFrameRate, true, loopingMode, loopingEnabled, 86 speed); 87 88 // get some objects from a derived class 89 if (fVideoTarget == NULL) 90 fVideoTarget = CreateVideoTarget(); 91 92 if (fVideoSupplier == NULL) 93 fVideoSupplier = CreateVideoSupplier(); 94 95 if (fAudioSupplier == NULL) 96 fAudioSupplier = CreateAudioSupplier(); 97 98 return FormatChanged(videoBounds, videoFrameRate, preferredVideoFormat, 99 audioFrameRate, audioChannels, enabledNodes, useOverlays, true); 100 } 101 102 103 status_t 104 NodeManager::InitCheck() 105 { 106 return fStatus; 107 } 108 109 110 void 111 NodeManager::SetPlayMode(int32 mode, bool continuePlaying) 112 { 113 if (fVideoConsumer != NULL && fMediaRoster != NULL) { 114 BMediaNode::run_mode runMode = mode > 0 ? 115 BMediaNode::B_DROP_DATA : BMediaNode::B_OFFLINE; 116 status_t ret = fMediaRoster->SetRunModeNode(fVideoConnection.consumer, 117 runMode); 118 if (ret != B_OK) { 119 printf("NodeManager::SetPlayMode(%" B_PRId32 "), setting run mode " 120 "failed: %s\n", mode, strerror(ret)); 121 } 122 } 123 124 PlaybackManager::SetPlayMode(mode, continuePlaying); 125 } 126 127 128 status_t 129 NodeManager::CleanupNodes() 130 { 131 _StopNodes(); 132 return _TearDownNodes(false); 133 } 134 135 136 status_t 137 NodeManager::FormatChanged(BRect videoBounds, float videoFrameRate, 138 color_space preferredVideoFormat, float audioFrameRate, 139 uint32 audioChannels, uint32 enabledNodes, bool useOverlays, bool force) 140 { 141 TRACE("NodeManager::FormatChanged()\n"); 142 143 if (!force && videoBounds == VideoBounds() 144 && videoFrameRate == FramesPerSecond()) { 145 TRACE(" -> reusing existing nodes\n"); 146 // TODO: if enabledNodes would indicate that audio or video 147 // is no longer needed, or, worse yet, suddenly needed when 148 // it wasn't before, then we should not return here! 149 PlaybackManager::Init(videoFrameRate, false, LoopMode(), 150 IsLoopingEnabled(), Speed(), MODE_PLAYING_PAUSED_FORWARD, 151 CurrentFrame()); 152 return B_OK; 153 } 154 155 _StopNodes(); 156 _TearDownNodes(); 157 158 PlaybackManager::Init(videoFrameRate, true, LoopMode(), IsLoopingEnabled(), 159 Speed(), MODE_PLAYING_PAUSED_FORWARD, CurrentFrame()); 160 161 SetVideoBounds(videoBounds); 162 163 status_t ret = _SetUpNodes(preferredVideoFormat, enabledNodes, 164 useOverlays, audioFrameRate, audioChannels); 165 if (ret == B_OK) 166 _StartNodes(); 167 else 168 fprintf(stderr, "unable to setup nodes: %s\n", strerror(ret)); 169 170 return ret; 171 } 172 173 174 bigtime_t 175 NodeManager::RealTimeForTime(bigtime_t time) const 176 { 177 bigtime_t result = 0; 178 if (fVideoProducer) { 179 result = fVideoProducer->TimeSource()->RealTimeFor( 180 fPerformanceTimeBase + time, 0); 181 } else if (fAudioProducer) { 182 result = fAudioProducer->TimeSource()->RealTimeFor( 183 fPerformanceTimeBase + time, 0); 184 } 185 //printf("NodeManager::RealTimeForTime(%lld) -> %lld\n", time, result); 186 return result; 187 } 188 189 190 bigtime_t 191 NodeManager::TimeForRealTime(bigtime_t time) const 192 { 193 bigtime_t result = 0; 194 if (fVideoProducer) { 195 result = fVideoProducer->TimeSource()->PerformanceTimeFor(time) 196 - fPerformanceTimeBase; 197 } else if (fAudioProducer) { 198 result = fAudioProducer->TimeSource()->PerformanceTimeFor(time) 199 - fPerformanceTimeBase; 200 } 201 return result; 202 } 203 204 205 void 206 NodeManager::SetCurrentAudioTime(bigtime_t time) 207 { 208 //printf("NodeManager::SetCurrentAudioTime(%lld)\n", time); 209 PlaybackManager::SetCurrentAudioTime(time); 210 if (!fVideoProducer) { 211 // running without video, update video time as well 212 PlaybackManager::SetCurrentVideoTime(time); 213 } 214 } 215 216 217 void 218 NodeManager::SetVideoBounds(BRect bounds) 219 { 220 if (bounds != fVideoBounds) { 221 fVideoBounds = bounds; 222 NotifyVideoBoundsChanged(fVideoBounds); 223 } 224 } 225 226 227 BRect 228 NodeManager::VideoBounds() const 229 { 230 return fVideoBounds; 231 } 232 233 234 void 235 NodeManager::SetVideoTarget(VideoTarget* videoTarget) 236 { 237 if (videoTarget != fVideoTarget) { 238 fVideoTarget = videoTarget; 239 if (fVideoConsumer) 240 fVideoConsumer->SetTarget(fVideoTarget); 241 } 242 } 243 244 245 VideoTarget* 246 NodeManager::GetVideoTarget() const 247 { 248 return fVideoTarget; 249 } 250 251 252 void 253 NodeManager::SetVolume(float percent) 254 { 255 // TODO: would be nice to set the volume on the system mixer input of 256 // our audio node... 257 } 258 259 260 void 261 NodeManager::SetPeakListener(BHandler* handler) 262 { 263 fPeakListener = handler; 264 if (fAudioProducer) 265 fAudioProducer->SetPeakListener(fPeakListener); 266 } 267 268 269 // #pragma mark - 270 271 272 status_t 273 NodeManager::_SetUpNodes(color_space preferredVideoFormat, uint32 enabledNodes, 274 bool useOverlays, float audioFrameRate, uint32 audioChannels) 275 { 276 TRACE("NodeManager::_SetUpNodes()\n"); 277 278 // find the media roster 279 fStatus = B_OK; 280 fMediaRoster = BMediaRoster::Roster(&fStatus); 281 if (fStatus != B_OK) { 282 print_error("Can't find the media roster", fStatus); 283 fMediaRoster = NULL; 284 return fStatus; 285 } 286 287 // find the time source 288 fStatus = fMediaRoster->GetTimeSource(&fTimeSource); 289 if (fStatus != B_OK) { 290 print_error("Can't get a time source", fStatus); 291 return fStatus; 292 } 293 294 // setup the video nodes 295 if (enabledNodes != AUDIO_ONLY) { 296 fStatus = _SetUpVideoNodes(preferredVideoFormat, useOverlays); 297 if (fStatus != B_OK) { 298 print_error("Error setting up video nodes", fStatus); 299 return fStatus; 300 } 301 } else 302 printf("running without video node\n"); 303 304 // setup the audio nodes 305 if (enabledNodes != VIDEO_ONLY) { 306 fStatus = _SetUpAudioNodes(audioFrameRate, audioChannels); 307 if (fStatus != B_OK) { 308 print_error("Error setting up audio nodes", fStatus); 309 return fStatus; 310 } 311 fNoAudio = false; 312 } else { 313 fNoAudio = true; 314 printf("running without audio node\n"); 315 } 316 317 return fStatus; 318 } 319 320 321 status_t 322 NodeManager::_SetUpVideoNodes(color_space preferredVideoFormat, 323 bool useOverlays) 324 { 325 // create the video producer node 326 fVideoProducer = new VideoProducer(NULL, "MediaPlayer video out", 0, 327 this, fVideoSupplier); 328 329 // register the producer node 330 fStatus = fMediaRoster->RegisterNode(fVideoProducer); 331 if (fStatus != B_OK) { 332 print_error("Can't register the video producer", fStatus); 333 return fStatus; 334 } 335 336 // make sure the Media Roster knows that we're using the node 337 // fMediaRoster->GetNodeFor(fVideoProducer->Node().node, 338 // &fVideoConnection.producer); 339 fVideoConnection.producer = fVideoProducer->Node(); 340 341 // create the video consumer node 342 fVideoConsumer = new VideoConsumer("MediaPlayer video in", NULL, 0, this, 343 fVideoTarget); 344 345 // register the consumer node 346 fStatus = fMediaRoster->RegisterNode(fVideoConsumer); 347 if (fStatus != B_OK) { 348 print_error("Can't register the video consumer", fStatus); 349 return fStatus; 350 } 351 352 // make sure the Media Roster knows that we're using the node 353 // fMediaRoster->GetNodeFor(fVideoConsumer->Node().node, 354 // &fVideoConnection.consumer); 355 fVideoConnection.consumer = fVideoConsumer->Node(); 356 357 // find free producer output 358 media_input videoInput; 359 media_output videoOutput; 360 int32 count = 1; 361 fStatus = fMediaRoster->GetFreeOutputsFor(fVideoConnection.producer, 362 &videoOutput, 1, &count, B_MEDIA_RAW_VIDEO); 363 if (fStatus != B_OK || count < 1) { 364 fStatus = B_RESOURCE_UNAVAILABLE; 365 print_error("Can't find an available video stream", fStatus); 366 return fStatus; 367 } 368 369 // find free consumer input 370 count = 1; 371 fStatus = fMediaRoster->GetFreeInputsFor(fVideoConnection.consumer, 372 &videoInput, 1, &count, B_MEDIA_RAW_VIDEO); 373 if (fStatus != B_OK || count < 1) { 374 fStatus = B_RESOURCE_UNAVAILABLE; 375 print_error("Can't find an available connection to the video window", 376 fStatus); 377 return fStatus; 378 } 379 380 // connect the nodes 381 media_format format; 382 format.type = B_MEDIA_RAW_VIDEO; 383 media_raw_video_format videoFormat = { 384 FramesPerSecond(), 1, 0, 385 (uint32)fVideoBounds.IntegerWidth(), 386 B_VIDEO_TOP_LEFT_RIGHT, 1, 1, 387 { 388 preferredVideoFormat, 389 (uint32)(fVideoBounds.IntegerWidth() + 1), 390 (uint32)(fVideoBounds.IntegerHeight() + 1), 391 0, 0, 0 392 } 393 }; 394 format.u.raw_video = videoFormat; 395 396 // connect video producer to consumer (hopefully using overlays) 397 fVideoConsumer->SetTryOverlay(useOverlays); 398 fStatus = fMediaRoster->Connect(videoOutput.source, videoInput.destination, 399 &format, &videoOutput, &videoInput); 400 401 if (fStatus != B_OK) { 402 print_error("Can't connect the video source to the video window... " 403 "trying without overlays", fStatus); 404 405 uint32 flags = 0; 406 bool supported = bitmaps_support_space( 407 format.u.raw_video.display.format, &flags); 408 if (!supported || (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) { 409 // cannot create bitmaps with such a color space 410 // or BViews don't support drawing it, fallback to B_RGB32 411 format.u.raw_video.display.format = B_RGB32; 412 printf("NodeManager::_SetupVideoNodes() - falling back to " 413 "B_RGB32\n"); 414 } 415 416 fVideoConsumer->SetTryOverlay(false); 417 // connect video producer to consumer (not using overlays and using 418 // a colorspace that BViews support drawing) 419 fStatus = fMediaRoster->Connect(videoOutput.source, 420 videoInput.destination, &format, &videoOutput, &videoInput); 421 } 422 // bail if second attempt failed too 423 if (fStatus != B_OK) { 424 print_error("Can't connect the video source to the video window", 425 fStatus); 426 return fStatus; 427 } 428 429 // the inputs and outputs might have been reassigned during the 430 // nodes' negotiation of the Connect(). That's why we wait until 431 // after Connect() finishes to save their contents. 432 fVideoConnection.format = format; 433 fVideoConnection.source = videoOutput.source; 434 fVideoConnection.destination = videoInput.destination; 435 fVideoConnection.connected = true; 436 437 // set time sources 438 fStatus = fMediaRoster->SetTimeSourceFor(fVideoConnection.producer.node, 439 fTimeSource.node); 440 if (fStatus != B_OK) { 441 print_error("Can't set the timesource for the video source", fStatus); 442 return fStatus; 443 } 444 445 fStatus = fMediaRoster->SetTimeSourceFor(fVideoConsumer->ID(), 446 fTimeSource.node); 447 if (fStatus != B_OK) { 448 print_error("Can't set the timesource for the video window", fStatus); 449 return fStatus; 450 } 451 452 return fStatus; 453 } 454 455 456 status_t 457 NodeManager::_SetUpAudioNodes(float audioFrameRate, uint32 audioChannels) 458 { 459 fAudioProducer = new AudioProducer("MediaPlayer audio out", fAudioSupplier); 460 fAudioProducer->SetPeakListener(fPeakListener); 461 fStatus = fMediaRoster->RegisterNode(fAudioProducer); 462 if (fStatus != B_OK) { 463 print_error("unable to register audio producer node!\n", fStatus); 464 return fStatus; 465 } 466 // make sure the Media Roster knows that we're using the node 467 // fMediaRoster->GetNodeFor(fAudioProducer->Node().node, 468 // &fAudioConnection.producer); 469 fAudioConnection.producer = fAudioProducer->Node(); 470 471 // connect to the mixer 472 fStatus = fMediaRoster->GetAudioMixer(&fAudioConnection.consumer); 473 if (fStatus != B_OK) { 474 print_error("unable to get the system mixer", fStatus); 475 return fStatus; 476 } 477 478 fMediaRoster->SetTimeSourceFor(fAudioConnection.producer.node, 479 fTimeSource.node); 480 481 // got the nodes; now we find the endpoints of the connection 482 media_input mixerInput; 483 media_output soundOutput; 484 int32 count = 1; 485 fStatus = fMediaRoster->GetFreeOutputsFor(fAudioConnection.producer, 486 &soundOutput, 1, &count); 487 if (fStatus != B_OK) { 488 print_error("unable to get a free output from the producer node", 489 fStatus); 490 return fStatus; 491 } 492 count = 1; 493 fStatus = fMediaRoster->GetFreeInputsFor(fAudioConnection.consumer, 494 &mixerInput, 1, &count); 495 if (fStatus != B_OK) { 496 print_error("unable to get a free input to the mixer", fStatus); 497 return fStatus; 498 } 499 500 // got the endpoints; now we connect it! 501 media_format audioFormat; 502 audioFormat.type = B_MEDIA_RAW_AUDIO; 503 audioFormat.u.raw_audio = media_raw_audio_format::wildcard; 504 audioFormat.u.raw_audio.frame_rate = audioFrameRate; 505 audioFormat.u.raw_audio.channel_count = audioChannels; 506 fStatus = fMediaRoster->Connect(soundOutput.source, mixerInput.destination, 507 &audioFormat, &soundOutput, &mixerInput); 508 if (fStatus != B_OK) { 509 print_error("unable to connect audio nodes", fStatus); 510 return fStatus; 511 } 512 513 // the inputs and outputs might have been reassigned during the 514 // nodes' negotiation of the Connect(). That's why we wait until 515 // after Connect() finishes to save their contents. 516 fAudioConnection.format = audioFormat; 517 fAudioConnection.source = soundOutput.source; 518 fAudioConnection.destination = mixerInput.destination; 519 fAudioConnection.connected = true; 520 521 // Set an appropriate run mode for the producer 522 fMediaRoster->SetRunModeNode(fAudioConnection.producer, 523 BMediaNode::B_INCREASE_LATENCY); 524 525 return fStatus; 526 } 527 528 529 status_t 530 NodeManager::_TearDownNodes(bool disconnect) 531 { 532 TRACE("NodeManager::_TearDownNodes()\n"); 533 status_t err = B_OK; 534 fMediaRoster = BMediaRoster::Roster(&err); 535 if (err != B_OK) { 536 fprintf(stderr, "NodeManager::_TearDownNodes() - error getting media " 537 "roster: %s\n", strerror(err)); 538 fMediaRoster = NULL; 539 } 540 541 if (fVideoConsumer && fVideoProducer && fVideoConnection.connected) { 542 // disconnect 543 if (fMediaRoster) { 544 TRACE(" disconnecting video...\n"); 545 err = fMediaRoster->Disconnect(fVideoConnection.producer.node, 546 fVideoConnection.source, fVideoConnection.consumer.node, 547 fVideoConnection.destination); 548 if (err < B_OK) 549 print_error("unable to disconnect video nodes", err); 550 } else { 551 fprintf(stderr, "NodeManager::_TearDownNodes() - cannot " 552 "disconnect video nodes, no media server!\n"); 553 } 554 fVideoConnection.connected = false; 555 } 556 if (fVideoProducer) { 557 TRACE(" releasing video producer...\n"); 558 fVideoProducer->Release(); 559 fVideoProducer = NULL; 560 } 561 if (fVideoConsumer) { 562 TRACE(" releasing video consumer...\n"); 563 fVideoConsumer->Release(); 564 fVideoConsumer = NULL; 565 } 566 if (fAudioProducer) { 567 disconnect = fAudioConnection.connected; 568 // Ordinarily we'd stop *all* of the nodes in the chain at this point. 569 // However, one of the nodes is the System Mixer, and stopping the 570 // Mixer is a Bad Idea (tm). So, we just disconnect from it, and 571 // release our references to the nodes that we're using. We *are* 572 // supposed to do that even for global nodes like the Mixer. 573 if (fMediaRoster != NULL && disconnect) { 574 TRACE(" disconnecting audio...\n"); 575 err = fMediaRoster->Disconnect(fAudioConnection.producer.node, 576 fAudioConnection.source, fAudioConnection.consumer.node, 577 fAudioConnection.destination); 578 if (err < B_OK) { 579 print_error("unable to disconnect audio nodes", err); 580 disconnect = false; 581 } 582 } else { 583 fprintf(stderr, "NodeManager::_TearDownNodes() - cannot " 584 "disconnect audio nodes, no media server!\n"); 585 } 586 587 TRACE(" releasing audio producer...\n"); 588 fAudioProducer->Release(); 589 fAudioProducer = NULL; 590 fAudioConnection.connected = false; 591 592 if (fMediaRoster != NULL && disconnect) { 593 TRACE(" releasing audio consumer...\n"); 594 fMediaRoster->ReleaseNode(fAudioConnection.consumer); 595 } else { 596 fprintf(stderr, "NodeManager::_TearDownNodes() - cannot release " 597 "audio consumer (system mixer)!\n"); 598 } 599 } 600 601 TRACE("NodeManager::_TearDownNodes() done\n"); 602 return err; 603 } 604 605 606 status_t 607 NodeManager::_StartNodes() 608 { 609 status_t status = B_NO_INIT; 610 if (!fMediaRoster) 611 return status; 612 613 bigtime_t latency = 0; 614 bigtime_t initLatency = 0; 615 if (fVideoProducer && fVideoConsumer) { 616 // figure out what recording delay to use 617 status = fMediaRoster->GetLatencyFor(fVideoConnection.producer, 618 &latency); 619 if (status < B_OK) { 620 print_error("error getting latency for video producer", 621 status); 622 } else 623 TRACE("video latency: %lld\n", latency); 624 status = fMediaRoster->SetProducerRunModeDelay( 625 fVideoConnection.producer, latency); 626 if (status < B_OK) { 627 print_error("error settings run mode delay for video producer", 628 status); 629 } 630 631 // start the nodes 632 status = fMediaRoster->GetInitialLatencyFor( 633 fVideoConnection.producer, &initLatency); 634 if (status < B_OK) { 635 print_error("error getting initial latency for video producer", 636 status); 637 } 638 } 639 initLatency += estimate_max_scheduling_latency(); 640 641 if (fAudioProducer) { 642 // TODO: was this supposed to be added to initLatency?!? 643 bigtime_t audioLatency = 0; 644 status = fMediaRoster->GetLatencyFor(fAudioConnection.producer, 645 &audioLatency); 646 TRACE("audio latency: %lld\n", audioLatency); 647 } 648 649 BTimeSource* timeSource; 650 if (fVideoProducer) { 651 timeSource = fMediaRoster->MakeTimeSourceFor( 652 fVideoConnection.producer); 653 } else { 654 timeSource = fMediaRoster->MakeTimeSourceFor( 655 fAudioConnection.producer); 656 } 657 bool running = timeSource->IsRunning(); 658 659 // workaround for people without sound cards 660 // because the system time source won't be running 661 bigtime_t real = BTimeSource::RealTime(); 662 if (!running) { 663 status = fMediaRoster->StartTimeSource(fTimeSource, real); 664 if (status != B_OK) { 665 timeSource->Release(); 666 print_error("cannot start time source!", status); 667 return status; 668 } 669 status = fMediaRoster->SeekTimeSource(fTimeSource, 0, real); 670 if (status != B_OK) { 671 timeSource->Release(); 672 print_error("cannot seek time source!", status); 673 return status; 674 } 675 } 676 677 bigtime_t perf = timeSource->PerformanceTimeFor(real + latency 678 + initLatency); 679 680 timeSource->Release(); 681 682 // start the nodes 683 if (fVideoProducer && fVideoConsumer) { 684 status = fMediaRoster->StartNode(fVideoConnection.consumer, perf); 685 if (status != B_OK) { 686 print_error("Can't start the video consumer", status); 687 return status; 688 } 689 status = fMediaRoster->StartNode(fVideoConnection.producer, perf); 690 if (status != B_OK) { 691 print_error("Can't start the video producer", status); 692 return status; 693 } 694 } 695 696 if (fAudioProducer) { 697 status = fMediaRoster->StartNode(fAudioConnection.producer, perf); 698 if (status != B_OK) { 699 print_error("Can't start the audio producer", status); 700 return status; 701 } 702 } 703 704 fPerformanceTimeBase = perf; 705 706 return status; 707 } 708 709 710 void 711 NodeManager::_StopNodes() 712 { 713 TRACE("NodeManager::_StopNodes()\n"); 714 fMediaRoster = BMediaRoster::Roster(); 715 if (fMediaRoster != NULL) { 716 // begin mucking with the media roster 717 if (fVideoProducer != NULL) { 718 TRACE(" stopping video producer...\n"); 719 fMediaRoster->StopNode(fVideoConnection.producer, 0, true); 720 } 721 if (fAudioProducer != NULL) { 722 TRACE(" stopping audio producer...\n"); 723 fMediaRoster->StopNode(fAudioConnection.producer, 0, true); 724 // synchronous stop 725 } 726 if (fVideoConsumer != NULL) { 727 TRACE(" stopping video consumer...\n"); 728 fMediaRoster->StopNode(fVideoConnection.consumer, 0, true); 729 } 730 TRACE(" all nodes stopped\n"); 731 } 732 TRACE("NodeManager::_StopNodes() done\n"); 733 } 734