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