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