root/branches/ppalmers-streaming/src/libstreaming/util/IsoHandlerManager.cpp

Revision 722, 21.3 kB (checked in by ppalmers, 13 years ago)

more rewrite of streaming

Line 
1 /*
2  * Copyright (C) 2005-2007 by Pieter Palmers
3  *
4  * This file is part of FFADO
5  * FFADO = Free Firewire (pro-)audio drivers for linux
6  *
7  * FFADO is based upon FreeBoB.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License version 2.1, as published by the Free Software Foundation;
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  * MA 02110-1301 USA
22  */
23
24 #include "IsoHandlerManager.h"
25 #include "IsoHandler.h"
26 #include "../generic/IsoStream.h"
27
28 #include "libutil/PosixThread.h"
29
30 #include <assert.h>
31
32 #define MINIMUM_INTERRUPTS_PER_PERIOD  4U
33 #define PACKETS_PER_INTERRUPT          4U
34
35 namespace Streaming
36 {
37
38 IMPL_DEBUG_MODULE( IsoHandlerManager, IsoHandlerManager, DEBUG_LEVEL_NORMAL );
39
40 IsoHandlerManager::IsoHandlerManager() :
41    m_State(E_Created),
42    m_poll_timeout(100), m_poll_fds(0), m_poll_nfds(0),
43    m_realtime(false), m_priority(0), m_xmit_nb_periods( 1 )
44 {
45
46 }
47
48 IsoHandlerManager::IsoHandlerManager(bool run_rt, unsigned int rt_prio) :
49    m_State(E_Created),
50    m_poll_timeout(1), m_poll_fds(0), m_poll_nfds(0),
51    m_realtime(run_rt), m_priority(rt_prio), m_xmit_nb_periods( 1 )
52 {
53
54 }
55
56 IsoHandlerManager::~IsoHandlerManager()
57 {
58
59 }
60
61 bool IsoHandlerManager::init()
62 {
63     // the tread that performs the actual packet transfer
64     // needs high priority
65     unsigned int prio=m_priority+6;
66
67     if (prio>98) prio=98;
68
69     m_isoManagerThread=new Util::PosixThread(
70         this,
71         m_realtime, prio,
72         PTHREAD_CANCEL_DEFERRED);
73
74     if(!m_isoManagerThread) {
75         debugFatal("Could not create iso manager thread\n");
76         return false;
77     }
78
79     // propagate the debug level
80 //     m_isoManagerThread->setVerboseLevel(getDebugLevel());
81
82     return true;
83 }
84
85 bool IsoHandlerManager::Init()
86 {
87     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
88     pthread_mutex_init(&m_debug_lock, NULL);
89
90     return true;
91 }
92
93 /**
94  * the IsoHandlerManager thread execute function iterates the handlers.
95  *
96  * This means that once the thread is running, streams are
97  * transmitted and received (if present on the bus). Make sure
98  * that the clients are registered & ready before starting the
99  * thread!
100  *
101  * The register and unregister functions are thread unsafe, so
102  * should not be used when the thread is running.
103  *
104  * @return false if the handlers could not be iterated.
105  */
106 bool IsoHandlerManager::Execute()
107 {
108 //     updateCycleTimers();
109
110     pthread_mutex_lock(&m_debug_lock);
111
112     if(!iterate()) {
113         debugFatal("Could not iterate the isoManager\n");
114         pthread_mutex_unlock(&m_debug_lock);
115         return false;
116     }
117
118     pthread_mutex_unlock(&m_debug_lock);
119
120     return true;
121 }
122
123 /**
124  * Poll the handlers managed by this manager, and iterate them
125  * when ready
126  *
127  * @return true when successful
128  */
129 bool IsoHandlerManager::iterate()
130 {
131     int err;
132     int i=0;
133     debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "enter...\n");
134
135     err = poll (m_poll_fds, m_poll_nfds, m_poll_timeout);
136
137     if (err == -1) {
138         if (errno == EINTR) {
139             return true;
140         }
141         debugFatal("poll error: %s\n", strerror (errno));
142         return false;
143     }
144
145     for (i = 0; i < m_poll_nfds; i++) {
146         if (m_poll_fds[i].revents & POLLERR) {
147             debugWarning("error on fd for %d\n",i);
148         }
149
150         if (m_poll_fds[i].revents & POLLHUP) {
151             debugWarning("hangup on fd for %d\n",i);
152         }
153
154         if(m_poll_fds[i].revents & (POLLIN)) {
155             IsoHandler *s=m_IsoHandlers.at(i);
156             assert(s);
157
158             s->iterate();
159         }
160     }
161
162     return true;
163
164 }
165
166 bool IsoHandlerManager::registerHandler(IsoHandler *handler)
167 {
168     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
169     assert(handler);
170
171     m_IsoHandlers.push_back(handler);
172
173     handler->setVerboseLevel(getDebugLevel());
174
175     // rebuild the fd map for poll()'ing.
176     return rebuildFdMap();
177
178 }
179
180 bool IsoHandlerManager::unregisterHandler(IsoHandler *handler)
181 {
182     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
183     assert(handler);
184
185     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
186       it != m_IsoHandlers.end();
187       ++it )
188     {
189         if ( *it == handler ) {
190             // erase the iso handler from the list
191             m_IsoHandlers.erase(it);
192             // rebuild the fd map for poll()'ing.
193             return rebuildFdMap();
194         }
195     }
196     debugFatal("Could not find handler (%p)\n", handler);
197
198     return false; //not found
199
200 }
201
202 bool IsoHandlerManager::rebuildFdMap() {
203     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
204     int i=0;
205
206     m_poll_nfds=0;
207     if(m_poll_fds) free(m_poll_fds);
208
209     // count the number of handlers
210     m_poll_nfds=m_IsoHandlers.size();
211
212     // allocate the fd array
213     m_poll_fds   = (struct pollfd *) calloc (m_poll_nfds, sizeof (struct pollfd));
214     if(!m_poll_fds) {
215         debugFatal("Could not allocate memory for poll FD array\n");
216         return false;
217     }
218
219     // fill the fd map
220     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
221       it != m_IsoHandlers.end();
222       ++it )
223     {
224         m_poll_fds[i].fd=(*it)->getFileDescriptor();
225         m_poll_fds[i].events = POLLIN;
226         i++;
227     }
228
229     return true;
230 }
231
232 void IsoHandlerManager::disablePolling(IsoStream *stream) {
233     int i=0;
234
235     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "Disable polling on stream %p\n",stream);
236
237     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
238         it != m_IsoHandlers.end();
239         ++it )
240     {
241         if ((*it)->isStreamRegistered(stream)) {
242             m_poll_fds[i].events = 0;
243             m_poll_fds[i].revents = 0;
244             debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "polling disabled\n");
245         }
246
247         i++;
248     }
249 }
250
251 void IsoHandlerManager::enablePolling(IsoStream *stream) {
252     int i=0;
253
254     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "Enable polling on stream %p\n",stream);
255
256     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
257         it != m_IsoHandlers.end();
258         ++it )
259     {
260         if ((*it)->isStreamRegistered(stream)) {
261             m_poll_fds[i].events = POLLIN;
262             m_poll_fds[i].revents = 0;
263             debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "polling enabled\n");
264         }
265
266         i++;
267     }
268 }
269
270
271 /**
272  * Registers an IsoStream with the IsoHandlerManager.
273  *
274  * If nescessary, an IsoHandler is created to handle this stream.
275  * Once an IsoStream is registered to the handler, it will be included
276  * in the ISO streaming cycle (i.e. receive/transmit of it will occur).
277  *
278  * @param stream the stream to register
279  * @return true if registration succeeds
280  *
281  * \todo : currently there is a one-to-one mapping
282  *        between streams and handlers, this is not ok for
283  *        multichannel receive
284  */
285 bool IsoHandlerManager::registerStream(IsoStream *stream)
286 {
287     debugOutput( DEBUG_LEVEL_VERBOSE, "Registering stream %p\n",stream);
288     assert(stream);
289
290     // make sure the stream isn't already attached to a handler
291     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
292       it != m_IsoHandlers.end();
293       ++it )
294     {
295         if((*it)->isStreamRegistered(stream)) {
296             debugWarning( "stream already registered!\n");
297             (*it)->unregisterStream(stream);
298
299         }
300     }
301
302     // clean up all handlers that aren't used
303     pruneHandlers();
304
305     // allocate a handler for this stream
306     if (stream->getStreamType()==IsoStream::eST_Receive) {
307         // setup the optimal parameters for the raw1394 ISO buffering
308         unsigned int packets_per_period=stream->getPacketsPerPeriod();
309
310 #if 1
311         // hardware interrupts occur when one DMA block is full, and the size of one DMA
312         // block = PAGE_SIZE. Setting the max_packet_size makes sure that the HW irq
313         // occurs at a period boundary (optimal CPU use)
314
315         // NOTE: try and use MINIMUM_INTERRUPTS_PER_PERIOD hardware interrupts
316         //       per period for better latency.
317         unsigned int max_packet_size=(MINIMUM_INTERRUPTS_PER_PERIOD * getpagesize()) / packets_per_period;
318         if (max_packet_size < stream->getMaxPacketSize()) {
319             max_packet_size=stream->getMaxPacketSize();
320         }
321
322         // Ensure we don't request a packet size bigger than the
323         // kernel-enforced maximum which is currently 1 page.
324         if (max_packet_size > (unsigned int)getpagesize())
325                     max_packet_size = getpagesize();
326
327         unsigned int irq_interval=packets_per_period / MINIMUM_INTERRUPTS_PER_PERIOD;
328         if(irq_interval <= 0) irq_interval=1;
329         // FIXME: test
330         irq_interval=1;
331         #warning Using fixed irq_interval
332
333 #else
334         // hardware interrupts occur when one DMA block is full, and the size of one DMA
335         // block = PAGE_SIZE. Setting the max_packet_size enables control over the IRQ
336         // frequency, as the controller uses max_packet_size, and not the effective size
337         // when writing to the DMA buffer.
338
339         // configure it such that we have an irq for every PACKETS_PER_INTERRUPT packets
340         unsigned int irq_interval=PACKETS_PER_INTERRUPT;
341
342         // unless the period size doesn't allow this
343         if ((packets_per_period/MINIMUM_INTERRUPTS_PER_PERIOD) < irq_interval) {
344             irq_interval=1;
345         }
346
347         // FIXME: test
348         irq_interval=1;
349 #warning Using fixed irq_interval
350
351         unsigned int max_packet_size=getpagesize() / irq_interval;
352
353         if (max_packet_size < stream->getMaxPacketSize()) {
354             max_packet_size=stream->getMaxPacketSize();
355         }
356
357         // Ensure we don't request a packet size bigger than the
358         // kernel-enforced maximum which is currently 1 page.
359         if (max_packet_size > (unsigned int)getpagesize())
360                     max_packet_size = getpagesize();
361
362 #endif
363         /* the receive buffer size doesn't matter for the latency,
364            but it has a minimal value in order for libraw to operate correctly (300) */
365         int buffers=400;
366
367         // create the actual handler
368         IsoRecvHandler *h = new IsoRecvHandler(stream->getPort(), buffers,
369                                                max_packet_size, irq_interval);
370
371         debugOutput( DEBUG_LEVEL_VERBOSE, " registering IsoRecvHandler\n");
372
373         if(!h) {
374             debugFatal("Could not create IsoRecvHandler\n");
375             return false;
376         }
377
378         h->setVerboseLevel(getDebugLevel());
379
380         // init the handler
381         if(!h->init()) {
382             debugFatal("Could not initialize receive handler\n");
383             return false;
384         }
385
386         // register the stream with the handler
387         if(!h->registerStream(stream)) {
388             debugFatal("Could not register receive stream with handler\n");
389             return false;
390         }
391
392         // register the handler with the manager
393         if(!registerHandler(h)) {
394             debugFatal("Could not register receive handler with manager\n");
395             return false;
396         }
397         debugOutput( DEBUG_LEVEL_VERBOSE, " registered stream (%p) with handler (%p)\n",stream,h);
398     }
399
400     if (stream->getStreamType()==IsoStream::eST_Transmit) {
401         // setup the optimal parameters for the raw1394 ISO buffering
402         unsigned int packets_per_period = stream->getPacketsPerPeriod();
403
404 #if 1
405         // hardware interrupts occur when one DMA block is full, and the size of one DMA
406         // block = PAGE_SIZE. Setting the max_packet_size makes sure that the HW irq
407         // occurs at a period boundary (optimal CPU use)
408         // NOTE: try and use MINIMUM_INTERRUPTS_PER_PERIOD interrupts per period
409         //       for better latency.
410         unsigned int max_packet_size=MINIMUM_INTERRUPTS_PER_PERIOD * getpagesize() / packets_per_period;
411         if (max_packet_size < stream->getMaxPacketSize()) {
412             max_packet_size = stream->getMaxPacketSize();
413         }
414
415         // Ensure we don't request a packet size bigger than the
416         // kernel-enforced maximum which is currently 1 page.
417         if (max_packet_size > (unsigned int)getpagesize())
418                     max_packet_size = getpagesize();
419
420          unsigned int irq_interval = packets_per_period / MINIMUM_INTERRUPTS_PER_PERIOD;
421          if(irq_interval <= 0) irq_interval=1;
422 #else
423         // hardware interrupts occur when one DMA block is full, and the size of one DMA
424         // block = PAGE_SIZE. Setting the max_packet_size enables control over the IRQ
425         // frequency, as the controller uses max_packet_size, and not the effective size
426         // when writing to the DMA buffer.
427
428         // configure it such that we have an irq for every PACKETS_PER_INTERRUPT packets
429         unsigned int irq_interval = PACKETS_PER_INTERRUPT;
430
431         // unless the period size doesn't allow this
432         if ((packets_per_period/MINIMUM_INTERRUPTS_PER_PERIOD) < irq_interval) {
433             irq_interval = 1;
434         }
435
436         // FIXME: test
437         irq_interval = 1;
438 #warning Using fixed irq_interval
439
440         unsigned int max_packet_size = getpagesize() / irq_interval;
441
442         if (max_packet_size < stream->getMaxPacketSize()) {
443             max_packet_size = stream->getMaxPacketSize();
444         }
445
446         // Ensure we don't request a packet size bigger than the
447         // kernel-enforced maximum which is currently 1 page.
448         if (max_packet_size > (unsigned int)getpagesize())
449                     max_packet_size = getpagesize();
450 #endif
451         // the transmit buffer size should be as low as possible for latency.
452         // note however that the raw1394 subsystem tries to keep this buffer
453         // full, so we have to make sure that we have enough events in our
454         // event buffers
455
456         // FIXME: latency spoiler
457         // every irq_interval packets an interrupt will occur. that is when
458         // buffers get transfered, meaning that we should have at least some
459         // margin here
460 //         int buffers=irq_interval * 2;
461
462         // we should queue up as much as possible
463         int buffers = packets_per_period * m_xmit_nb_periods;
464
465         // create the actual handler
466         IsoXmitHandler *h = new IsoXmitHandler(stream->getPort(), buffers,
467                                                max_packet_size, irq_interval);
468
469         debugOutput( DEBUG_LEVEL_VERBOSE, " registering IsoXmitHandler\n");
470
471         if(!h) {
472             debugFatal("Could not create IsoXmitHandler\n");
473             return false;
474         }
475
476         h->setVerboseLevel(getDebugLevel());
477
478         // init the handler
479         if(!h->init()) {
480             debugFatal("Could not initialize transmit handler\n");
481             return false;
482         }
483
484         // register the stream with the handler
485         if(!h->registerStream(stream)) {
486             debugFatal("Could not register transmit stream with handler\n");
487             return false;
488         }
489
490         // register the handler with the manager
491         if(!registerHandler(h)) {
492             debugFatal("Could not register transmit handler with manager\n");
493             return false;
494         }
495         debugOutput( DEBUG_LEVEL_VERBOSE, " registered stream (%p) with handler (%p)\n",stream,h);
496     }
497
498     m_IsoStreams.push_back(stream);
499     debugOutput( DEBUG_LEVEL_VERBOSE, " %d streams, %d handlers registered\n",
500                                       m_IsoStreams.size(), m_IsoHandlers.size());
501
502     return true;
503 }
504
505 bool IsoHandlerManager::unregisterStream(IsoStream *stream)
506 {
507     debugOutput( DEBUG_LEVEL_VERBOSE, "Unregistering stream %p\n",stream);
508     assert(stream);
509
510     // make sure the stream isn't attached to a handler anymore
511     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
512       it != m_IsoHandlers.end();
513       ++it )
514     {
515         if((*it)->isStreamRegistered(stream)) {
516             if(!(*it)->unregisterStream(stream)) {
517                 debugOutput( DEBUG_LEVEL_VERBOSE, " could not unregister stream (%p) from handler (%p)...\n",stream,*it);
518                 return false;
519             }
520
521             debugOutput( DEBUG_LEVEL_VERBOSE, " unregistered stream (%p) from handler (%p)...\n",stream,*it);
522         }
523     }
524
525     // clean up all handlers that aren't used
526     pruneHandlers();
527
528     // remove the stream from the registered streams list
529     for ( IsoStreamVectorIterator it = m_IsoStreams.begin();
530       it != m_IsoStreams.end();
531       ++it )
532     {
533         if ( *it == stream ) {
534             m_IsoStreams.erase(it);
535
536             debugOutput( DEBUG_LEVEL_VERBOSE, " deleted stream (%p) from list...\n", *it);
537             return true;
538         }
539     }
540
541     return false; //not found
542
543 }
544
545 void IsoHandlerManager::pruneHandlers() {
546     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
547     IsoHandlerVector toUnregister;
548
549     // find all handlers that are not in use
550     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
551           it != m_IsoHandlers.end();
552           ++it )
553     {
554         if(!((*it)->inUse())) {
555             debugOutput( DEBUG_LEVEL_VERBOSE, " handler (%p) not in use\n",*it);
556             toUnregister.push_back(*it);
557         }
558     }
559     // delete them
560     for ( IsoHandlerVectorIterator it = toUnregister.begin();
561           it != toUnregister.end();
562           ++it )
563     {
564         unregisterHandler(*it);
565         debugOutput( DEBUG_LEVEL_VERBOSE, " deleting handler (%p)\n",*it);
566
567         // Now the handler's been unregistered it won't be reused
568         // again.  Therefore it really needs to be formally deleted
569         // to free up the raw1394 handle.  Otherwise things fall
570         // apart after several xrun recoveries as the system runs
571         // out of resources to support all the disused but still
572         // allocated raw1394 handles.  At least this is the current
573         // theory as to why we end up with "memory allocation"
574         // failures after several Xrun recoveries.
575         delete *it;
576     }
577
578 }
579
580
581 bool IsoHandlerManager::prepare()
582 {
583     bool retval=true;
584
585     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
586
587     // check state
588     if(m_State != E_Created) {
589         debugError("Incorrect state, expected E_Created, got %d\n",(int)m_State);
590         return false;
591     }
592
593     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
594           it != m_IsoHandlers.end();
595           ++it )
596     {
597         if(!(*it)->prepare()) {
598             debugFatal("Could not prepare handlers\n");
599             retval=false;
600         }
601     }
602
603     if (retval) {
604         m_State=E_Prepared;
605     } else {
606         m_State=E_Error;
607     }
608
609     return retval;
610 }
611
612 bool IsoHandlerManager::startHandlers() {
613     return startHandlers(-1);
614 }
615
616 bool IsoHandlerManager::startHandlers(int cycle) {
617     bool retval=true;
618
619     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
620
621     // check state
622     if(m_State != E_Prepared) {
623         debugError("Incorrect state, expected E_Prepared, got %d\n",(int)m_State);
624         return false;
625     }
626
627     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
628         it != m_IsoHandlers.end();
629         ++it )
630     {
631         debugOutput( DEBUG_LEVEL_VERBOSE, " starting handler (%p)\n",*it);
632         if(!(*it)->start(cycle)) {
633             debugOutput( DEBUG_LEVEL_VERBOSE, " could not start handler (%p)\n",*it);
634             retval=false;
635         }
636     }
637
638     debugOutput( DEBUG_LEVEL_VERBOSE, "Starting ISO iterator thread...\n");
639
640     // note: libraw1394 doesn't like it if you poll() and/or iterate() before
641     //       starting the streams.
642     // start the iso runner thread
643     m_isoManagerThread->Start();
644
645     if (retval) {
646         m_State=E_Running;
647     } else {
648         m_State=E_Error;
649     }
650
651     return retval;
652 }
653
654 bool IsoHandlerManager::stopHandlers() {
655     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
656
657     // check state
658     if(m_State != E_Running) {
659         debugError("Incorrect state, expected E_Running, got %d\n",(int)m_State);
660         return false;
661     }
662
663     bool retval=true;
664
665     debugOutput( DEBUG_LEVEL_VERBOSE, "Stopping ISO iterator thread...\n");
666     m_isoManagerThread->Stop();
667
668     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
669         it != m_IsoHandlers.end();
670         ++it )
671     {
672         debugOutput( DEBUG_LEVEL_VERBOSE, "Stopping handler (%p)\n",*it);
673         if(!(*it)->stop()){
674             debugOutput( DEBUG_LEVEL_VERBOSE, " could not stop handler (%p)\n",*it);
675             retval=false;
676         }
677     }
678
679     if (retval) {
680         m_State=E_Prepared;
681     } else {
682         m_State=E_Error;
683     }
684
685     return retval;
686 }
687
688 bool IsoHandlerManager::reset() {
689     debugOutput( DEBUG_LEVEL_VERBOSE, "enter...\n");
690
691     // check state
692     if(m_State == E_Error) {
693         debugFatal("Resetting from error condition not yet supported...\n");
694         return false;
695     }
696
697     // if not in an error condition, reset means stop the handlers
698     return stopHandlers();
699 }
700
701
702 void IsoHandlerManager::setVerboseLevel(int i) {
703     setDebugLevel(i);
704
705     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
706           it != m_IsoHandlers.end();
707           ++it )
708     {
709         (*it)->setVerboseLevel(i);
710     }
711 }
712
713 void IsoHandlerManager::dumpInfo() {
714     int i=0;
715
716     debugOutputShort( DEBUG_LEVEL_NORMAL, "Dumping IsoHandlerManager Stream handler information...\n");
717     debugOutputShort( DEBUG_LEVEL_NORMAL, " State: %d\n",(int)m_State);
718
719     for ( IsoHandlerVectorIterator it = m_IsoHandlers.begin();
720           it != m_IsoHandlers.end();
721           ++it )
722     {
723         debugOutputShort( DEBUG_LEVEL_NORMAL, " IsoHandler %d (%p)\n",i++,*it);
724
725         (*it)->dumpInfo();
726     }
727
728 }
729
730 } // end of namespace Streaming
731
Note: See TracBrowser for help on using the browser.