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
40NodeManager::Connection::Connection()
41	:
42	connected(false)
43{
44}
45
46
47// #pragma mark -
48
49
50NodeManager::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
71NodeManager::~NodeManager()
72{
73	_StopNodes();
74	_TearDownNodes();
75}
76
77
78status_t
79NodeManager::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
103status_t
104NodeManager::InitCheck()
105{
106	return fStatus;
107}
108
109
110void
111NodeManager::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
128status_t
129NodeManager::CleanupNodes()
130{
131	_StopNodes();
132	return _TearDownNodes(false);
133}
134
135
136status_t
137NodeManager::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
174bigtime_t
175NodeManager::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
190bigtime_t
191NodeManager::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
205void
206NodeManager::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
217void
218NodeManager::SetVideoBounds(BRect bounds)
219{
220	if (bounds != fVideoBounds) {
221		fVideoBounds = bounds;
222		NotifyVideoBoundsChanged(fVideoBounds);
223	}
224}
225
226
227BRect
228NodeManager::VideoBounds() const
229{
230	return fVideoBounds;
231}
232
233
234void
235NodeManager::SetVideoTarget(VideoTarget* videoTarget)
236{
237	if (videoTarget != fVideoTarget) {
238		fVideoTarget = videoTarget;
239		if (fVideoConsumer)
240			fVideoConsumer->SetTarget(fVideoTarget);
241	}
242}
243
244
245VideoTarget*
246NodeManager::GetVideoTarget() const
247{
248	return fVideoTarget;
249}
250
251
252void
253NodeManager::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
260void
261NodeManager::SetPeakListener(BHandler* handler)
262{
263	fPeakListener = handler;
264	if (fAudioProducer)
265		fAudioProducer->SetPeakListener(fPeakListener);
266}
267
268
269// #pragma mark -
270
271
272status_t
273NodeManager::_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
321status_t
322NodeManager::_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
456status_t
457NodeManager::_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
529status_t
530NodeManager::_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
606status_t
607NodeManager::_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: %Ld\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: %Ld\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
710void
711NodeManager::_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