root/branches/ppalmers-streaming/src/libutil/TimestampedBuffer.cpp

Revision 719, 39.8 kB (checked in by ppalmers, 14 years ago)

backup commit

Line 
1 /* $Id$ */
2
3 /*
4  *   FFADO Streaming API
5  *   FFADO = Firewire (pro-)audio for linux
6  *
7  *   http://ffado.sf.net
8  *
9  *   Copyright (C) 2005,2006,2007 Pieter Palmers <pieterpalmers@users.sourceforge.net>
10  *
11  *   This program is free software {} you can redistribute it and/or modify
12  *   it under the terms of the GNU General Public License as published by
13  *   the Free Software Foundation {} either version 2 of the License, or
14  *   (at your option) any later version.
15  *
16  *   This program is distributed in the hope that it will be useful,
17  *   but WITHOUT ANY WARRANTY {} without even the implied warranty of
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *   GNU General Public License for more details.
20  *
21  *   You should have received a copy of the GNU General Public License
22  *   along with this program {} if not, write to the Free Software
23  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24  *
25  *
26  *
27  */
28
29 #include "libutil/Atomic.h"
30 #include "libstreaming/util/cycletimer.h"
31
32 #include "TimestampedBuffer.h"
33 #include "assert.h"
34
35 // FIXME: note that it will probably be better to use a DLL bandwidth that is
36 //        dependant on the sample rate
37
38
39 // #define DLL_BANDWIDTH (4800/48000.0)
40 #define DLL_BANDWIDTH (0.01)
41 #define DLL_PI        (3.141592653589793238)
42 #define DLL_SQRT2     (1.414213562373095049)
43 #define DLL_OMEGA     (2.0*DLL_PI*DLL_BANDWIDTH)
44 #define DLL_COEFF_B   (DLL_SQRT2 * DLL_OMEGA)
45 #define DLL_COEFF_C   (DLL_OMEGA * DLL_OMEGA)
46
47 #define ENTER_CRITICAL_SECTION { \
48     pthread_mutex_lock(&m_framecounter_lock); \
49     }
50 #define EXIT_CRITICAL_SECTION { \
51     pthread_mutex_unlock(&m_framecounter_lock); \
52     }
53
54 namespace Util {
55
56 IMPL_DEBUG_MODULE( TimestampedBuffer, TimestampedBuffer, DEBUG_LEVEL_VERBOSE );
57
58 TimestampedBuffer::TimestampedBuffer(TimestampedBufferClient *c)
59     : m_event_buffer(NULL), m_cluster_buffer(NULL),
60       m_event_size(0), m_events_per_frame(0), m_buffer_size(0),
61       m_bytes_per_frame(0), m_bytes_per_buffer(0),
62       m_enabled( false ), m_transparent ( true ),
63       m_wrap_at(0xFFFFFFFFFFFFFFFFLLU),
64       m_Client(c), m_framecounter(0),
65       m_tick_offset(0.0),
66       m_buffer_tail_timestamp(0.0),
67       m_buffer_next_tail_timestamp(0.0),
68       m_dll_e2(0.0), m_dll_b(DLL_COEFF_B), m_dll_c(DLL_COEFF_C),
69       m_nominal_rate(0.0), m_update_period(0)
70 {
71     pthread_mutex_init(&m_framecounter_lock, NULL);
72
73 }
74
75 TimestampedBuffer::~TimestampedBuffer() {
76     ffado_ringbuffer_free(m_event_buffer);
77     free(m_cluster_buffer);
78 }
79
80 /**
81  * \brief Set the nominal rate in frames/timeunit
82  *
83  * Sets the nominal rate in frames per time unit. This rate is used
84  * to initialize the DLL that will extract the effective rate based
85  * upon the timestamps it gets fed.
86  *
87  * @param r rate
88  * @return true if successful
89  */
90 bool TimestampedBuffer::setNominalRate(float r) {
91     m_nominal_rate=r;
92     debugOutput(DEBUG_LEVEL_VERBOSE," nominal rate=%e set to %e\n",
93                                     m_nominal_rate, r);
94     return true;
95 }
96
97 /**
98  * \brief Set the nominal update period (in frames)
99  *
100  * Sets the nominal update period. This period is the number of frames
101  * between two timestamp updates (hence buffer writes)
102  *
103  * @param n period in frames
104  * @return true if successful
105  */
106 bool TimestampedBuffer::setUpdatePeriod(unsigned int n) {
107     m_update_period=n;
108     return true;
109 }
110
111 /**
112  * \brief set the value at which timestamps should wrap around
113  * @param w value to wrap at
114  * @return true if successful
115  */
116 bool TimestampedBuffer::setWrapValue(ffado_timestamp_t w) {
117     m_wrap_at=w;
118     return true;
119 }
120 #include <math.h>
121
122 /**
123  * \brief return the effective rate
124  *
125  * Returns the effective rate calculated by the DLL.
126  *
127  * @return rate (in timeunits/frame)
128  */
129 float TimestampedBuffer::getRate() {
130     ffado_timestamp_t diff;
131    
132     ENTER_CRITICAL_SECTION;
133     diff=m_buffer_next_tail_timestamp - m_buffer_tail_timestamp;
134     EXIT_CRITICAL_SECTION;
135    
136     debugOutput(DEBUG_LEVEL_VERY_VERBOSE,"getRate: %f/%f=%f\n",
137         (float)(diff),
138         (float)m_update_period,
139         ((float)(diff))/((float) m_update_period));
140    
141     // the maximal difference we can allow (64secs)
142     const ffado_timestamp_t max=m_wrap_at/((ffado_timestamp_t)2);
143
144     if(diff > max) {
145         diff -= m_wrap_at;
146     } else if (diff < -max) {
147         diff += m_wrap_at;
148     }
149
150     float rate=((float)diff)/((float) m_update_period);
151     if (rate<0.0) debugError("rate < 0! (%f)\n",rate);
152     if (fabsf(m_nominal_rate - rate)>(m_nominal_rate*0.1)) {
153         debugWarning("(%p) rate (%10.5f) more that 10%% off nominal (rate=%10.5f, diff="TIMESTAMP_FORMAT_SPEC", update_period=%d)\n",
154                      this, rate,m_nominal_rate,diff, m_update_period);
155
156         return m_nominal_rate;
157     } else {
158         return rate;
159     }
160 }
161
162 /**
163  * \brief Sets the size of the events
164  * @param s event size in bytes
165  * @return true if successful
166  */
167 bool TimestampedBuffer::setEventSize(unsigned int s) {
168     m_event_size=s;
169
170     m_bytes_per_frame=m_event_size*m_events_per_frame;
171     m_bytes_per_buffer=m_bytes_per_frame*m_buffer_size;
172
173     return true;
174 }
175
176 /**
177  * \brief Sets the number of events per frame
178  * @param n number of events per frame
179  * @return true if successful
180  */
181 bool TimestampedBuffer::setEventsPerFrame(unsigned int n) {
182     m_events_per_frame=n;
183
184     m_bytes_per_frame=m_event_size*m_events_per_frame;
185     m_bytes_per_buffer=m_bytes_per_frame*m_buffer_size;
186
187     return true;
188 }
189 /**
190  * \brief Sets the buffer size in frames
191  * @param n number frames
192  * @return true if successful
193  */
194 bool TimestampedBuffer::setBufferSize(unsigned int n) {
195     m_buffer_size=n;
196
197     m_bytes_per_frame=m_event_size*m_events_per_frame;
198     m_bytes_per_buffer=m_bytes_per_frame*m_buffer_size;
199
200     return true;
201 }
202
203 /**
204  * Sets the buffer offset in ticks.
205  *
206  * A positive value means that the buffer is 'delayed' for nticks ticks.
207  *
208  * @note These offsets are only used when reading timestamps. Any function
209  *       that returns a timestamp will incorporate this offset.
210  * @param nframes the number of ticks (positive = delay buffer)
211  * @return true if successful
212  */
213 bool TimestampedBuffer::setTickOffset(ffado_timestamp_t nticks) {
214     debugOutput(DEBUG_LEVEL_VERBOSE,"Setting ticks offset to "TIMESTAMP_FORMAT_SPEC"\n",nticks);
215
216     // JMW: I think we need to update the internal DLL state to take account
217     // of the new offset.  Doing so certainly makes for a smoother MOTU
218     // startup.
219     ENTER_CRITICAL_SECTION;
220     m_buffer_tail_timestamp = m_buffer_tail_timestamp - m_tick_offset + nticks;
221     m_buffer_next_tail_timestamp = (ffado_timestamp_t)((double)m_buffer_tail_timestamp + m_dll_e2);
222     m_tick_offset=nticks;
223     EXIT_CRITICAL_SECTION;
224
225     return true;
226 }
227
228 /**
229  * \brief Returns the current fill of the buffer
230  *
231  * This returns the buffer fill of the internal ringbuffer. This
232  * can only be used as an indication because it's state is not
233  * guaranteed to be consistent at all times due to threading issues.
234  *
235  * In order to get the number of frames in the buffer, use the
236  * getBufferHeadTimestamp, getBufferTailTimestamp
237  * functions
238  *
239  * @return the internal buffer fill in frames
240  */
241 unsigned int TimestampedBuffer::getBufferFill() {
242     return ffado_ringbuffer_read_space(m_event_buffer)/(m_bytes_per_frame);
243 }
244
245 /**
246  * \brief Initializes the TimestampedBuffer
247  *
248  * Initializes the TimestampedBuffer, should be called before anything else
249  * is done.
250  *
251  * @return true if successful
252  */
253 bool TimestampedBuffer::init() {
254     return true;
255 }
256
257 /**
258  * \brief Resets the TimestampedBuffer
259  *
260  * Resets the TimestampedBuffer, clearing the buffers and counters.
261  * (not true yet: Also resets the DLL to the nominal values.)
262  *
263  * \note when this is called, you should make sure that the buffer
264  *       tail timestamp gets set before continuing
265  *
266  * @return true if successful
267  */
268 bool TimestampedBuffer::reset() {
269     ffado_ringbuffer_reset(m_event_buffer);
270
271     resetFrameCounter();
272
273     return true;
274 }
275
276 /**
277  * \brief Prepares the TimestampedBuffer
278  *
279  * Prepare the TimestampedBuffer. This allocates all internal buffers and
280  * initializes all data structures.
281  *
282  * This should be called after parameters such as buffer size, event size etc.. are set,
283  * and before any read/write operations are performed.
284  *
285  * @return true if successful
286  */
287 bool TimestampedBuffer::prepare() {
288     debugOutput(DEBUG_LEVEL_VERBOSE,"Preparing buffer (%p)\n",this);
289     debugOutput(DEBUG_LEVEL_VERBOSE," Size=%u events, events/frame=%u, event size=%ubytes\n",
290                                         m_buffer_size,m_events_per_frame,m_event_size);
291
292     debugOutput(DEBUG_LEVEL_VERBOSE," update period %u\n",
293                                     m_update_period);
294     debugOutput(DEBUG_LEVEL_VERBOSE," nominal rate=%f\n",
295                                     m_nominal_rate);
296
297     debugOutput(DEBUG_LEVEL_VERBOSE," wrapping at "TIMESTAMP_FORMAT_SPEC"\n",m_wrap_at);
298
299     assert(m_buffer_size);
300     assert(m_events_per_frame);
301     assert(m_event_size);
302
303     assert(m_nominal_rate != 0.0L);
304     assert(m_update_period != 0);
305
306     if( !(m_event_buffer=ffado_ringbuffer_create(
307             (m_events_per_frame * m_buffer_size) * m_event_size))) {
308         debugFatal("Could not allocate memory event ringbuffer\n");
309         return false;
310     }
311
312     // allocate the temporary cluster buffer
313     if( !(m_cluster_buffer=(char *)calloc(m_events_per_frame,m_event_size))) {
314             debugFatal("Could not allocate temporary cluster buffer\n");
315         ffado_ringbuffer_free(m_event_buffer);
316         return false;
317     }
318
319     // init the DLL
320     m_dll_e2=m_nominal_rate * (float)m_update_period;
321
322     m_dll_b=((float)(DLL_COEFF_B));
323     m_dll_c=((float)(DLL_COEFF_C));
324    
325     // this will init the internal timestamps to a sensible value
326     setBufferTailTimestamp(m_buffer_tail_timestamp);
327    
328     return true;
329 }
330
331 /**
332  * @brief Insert a dummy frame to the head buffer
333  *
334  * Writes one frame of dummy data to the head of the buffer.
335  * This is to assist the phase sync of several buffers.
336  *
337  * Note: currently the dummy data is added to the tail of the
338  *       buffer, but without updating the timestamp.
339  *
340  * @return true if successful
341  */
342 bool TimestampedBuffer::writeDummyFrame() {
343
344     unsigned int write_size=m_event_size*m_events_per_frame;
345    
346     char dummy[write_size]; // one frame of garbage
347     memset(dummy,0,write_size);
348
349     // add the data payload to the ringbuffer
350     if (ffado_ringbuffer_write(m_event_buffer,dummy,write_size) < write_size)
351     {
352 //         debugWarning("writeFrames buffer overrun\n");
353         return false;
354     }
355
356 //     incrementFrameCounter(nframes,ts);
357    
358     // increment without updating the DLL
359     ENTER_CRITICAL_SECTION;
360     m_framecounter++;
361     EXIT_CRITICAL_SECTION;
362    
363     return true;
364 }
365
366 /**
367  * @brief Write frames to the buffer
368  *
369  * Copies \ref nframes of frames from the buffer pointed to by \ref data to the
370  * internal ringbuffer. The time of the last frame in the buffer is set to \ref ts.
371  *
372  * @param nframes number of frames to copy
373  * @param data pointer to the frame buffer
374  * @param ts timestamp of the last frame copied
375  * @return true if successful
376  */
377 bool TimestampedBuffer::writeFrames(unsigned int nframes, char *data, ffado_timestamp_t ts) {
378
379     unsigned int write_size=nframes*m_event_size*m_events_per_frame;
380
381     if (m_transparent) {
382         // while disabled, we don't update the DLL, nor do we write frames
383         // we just set the correct timestamp for the frames
384         setBufferTailTimestamp(ts);
385     } else {
386         // add the data payload to the ringbuffer
387         size_t written = ffado_ringbuffer_write(m_event_buffer, data, write_size);
388         if (written < write_size)
389         {
390             debugWarning("ringbuffer full, %u, %u\n", write_size, written);
391             return false;
392         }
393         incrementFrameCounter(nframes,ts);
394     }
395     return true;
396 }
397
398 /**
399  * @brief Drop frames from the head of the buffer
400  *
401  * drops \ref nframes of frames from the head of internal buffer
402  *
403  * @param nframes number of frames to drop
404  * @return true if successful
405  */
406 bool TimestampedBuffer::dropFrames(unsigned int nframes) {
407
408     unsigned int read_size=nframes*m_event_size*m_events_per_frame;
409
410     ffado_ringbuffer_read_advance(m_event_buffer, read_size);
411     decrementFrameCounter(nframes);
412
413     return true;
414 }
415
416 /**
417  * @brief Read frames from the buffer
418  *
419  * Copies \ref nframes of frames from the internal buffer to the data buffer pointed
420  * to by \ref data.
421  *
422  * @param nframes number of frames to copy
423  * @param data pointer to the frame buffer
424  * @return true if successful
425  */
426 bool TimestampedBuffer::readFrames(unsigned int nframes, char *data) {
427
428     unsigned int read_size=nframes*m_event_size*m_events_per_frame;
429
430     if (m_transparent) {
431         return true; // FIXME: the data still doesn't make sense!
432     } else {
433         // get the data payload to the ringbuffer
434         if ((ffado_ringbuffer_read(m_event_buffer,data,read_size)) < read_size)
435         {
436             debugWarning("readFrames buffer underrun\n");
437             return false;
438         }
439         decrementFrameCounter(nframes);
440     }
441     return true;
442 }
443
444 /**
445  * @brief Performs block processing write of frames
446  *
447  * This function allows for zero-copy writing into the ringbuffer.
448  * It calls the client's processWriteBlock function to write frames
449  * into the internal buffer's data area, in a thread safe fashion.
450  *
451  * It also updates the timestamp.
452  *
453  * @param nbframes number of frames to process
454  * @param ts timestamp of the last frame written to the buffer
455  * @return true if successful
456  */
457 bool TimestampedBuffer::blockProcessWriteFrames(unsigned int nbframes, ffado_timestamp_t ts) {
458
459     debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "Transferring period...\n");
460     int xrun;
461     unsigned int offset=0;
462
463     ffado_ringbuffer_data_t vec[2];
464     // we received one period of frames
465     // this is period_size*dimension of events
466     unsigned int events2write=nbframes*m_events_per_frame;
467     unsigned int bytes2write=events2write*m_event_size;
468
469     /* write events2write bytes to the ringbuffer
470     *  first see if it can be done in one read.
471     *  if so, ok.
472     *  otherwise write up to a multiple of clusters directly to the buffer
473     *  then do the buffer wrap around using ringbuffer_write
474     *  then write the remaining data directly to the buffer in a third pass
475     *  Make sure that we cannot end up on a non-cluster aligned position!
476     */
477     unsigned int cluster_size=m_events_per_frame*m_event_size;
478
479     while(bytes2write>0) {
480         int byteswritten=0;
481
482         unsigned int frameswritten=(nbframes*cluster_size-bytes2write)/cluster_size;
483         offset=frameswritten;
484
485         ffado_ringbuffer_get_write_vector(m_event_buffer, vec);
486
487         if(vec[0].len==0) { // this indicates a full event buffer
488             debugError("Event buffer overrun in buffer %p, fill: %u, bytes2write: %u \n",
489                        this, ffado_ringbuffer_read_space(m_event_buffer), bytes2write);
490             debugShowBackLog();
491             return false;
492         }
493
494         /* if we don't take care we will get stuck in an infinite loop
495         * because we align to a cluster boundary later
496         * the remaining nb of bytes in one write operation can be
497         * smaller than one cluster
498         * this can happen because the ringbuffer size is always a power of 2
499         */
500         if(vec[0].len<cluster_size) {
501
502             // encode to the temporary buffer
503             xrun = m_Client->processWriteBlock(m_cluster_buffer, 1, offset);
504
505             if(xrun<0) {
506                 // xrun detected
507                 debugError("Frame buffer underrun in buffer %p\n",this);
508                 return false;
509             }
510
511             // use the ringbuffer function to write one cluster
512             // the write function handles the wrap around.
513             ffado_ringbuffer_write(m_event_buffer,
514                          m_cluster_buffer,
515                          cluster_size);
516
517             // we advanced one cluster_size
518             bytes2write-=cluster_size;
519
520         } else { //
521
522             if(bytes2write>vec[0].len) {
523                 // align to a cluster boundary
524                 byteswritten=vec[0].len-(vec[0].len%cluster_size);
525             } else {
526                 byteswritten=bytes2write;
527             }
528
529             xrun = m_Client->processWriteBlock(vec[0].buf,
530                          byteswritten/cluster_size,
531                          offset);
532
533             if(xrun<0) {
534                     // xrun detected
535                 debugError("Frame buffer underrun in buffer %p\n",this);
536                 return false; // FIXME: return false ?
537             }
538
539             ffado_ringbuffer_write_advance(m_event_buffer, byteswritten);
540             bytes2write -= byteswritten;
541         }
542
543         // the bytes2write should always be cluster aligned
544         assert(bytes2write%cluster_size==0);
545
546     }
547
548     incrementFrameCounter(nbframes,ts);
549
550     return true;
551
552 }
553
554 /**
555  * @brief Performs block processing read of frames
556  *
557  * This function allows for zero-copy reading from the ringbuffer.
558  * It calls the client's processReadBlock function to read frames
559  * directly from the internal buffer's data area, in a thread safe
560  * fashion.
561  *
562  * @param nbframes number of frames to process
563  * @return true if successful
564  */
565 bool TimestampedBuffer::blockProcessReadFrames(unsigned int nbframes) {
566
567     debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "Reading %u from buffer (%p)...\n", nbframes, this);
568
569     int xrun;
570     unsigned int offset=0;
571
572     ffado_ringbuffer_data_t vec[2];
573     // we received one period of frames on each connection
574     // this is period_size*dimension of events
575
576     unsigned int events2read=nbframes*m_events_per_frame;
577     unsigned int bytes2read=events2read*m_event_size;
578     /* read events2read bytes from the ringbuffer
579     *  first see if it can be done in one read.
580     *  if so, ok.
581     *  otherwise read up to a multiple of clusters directly from the buffer
582     *  then do the buffer wrap around using ringbuffer_read
583     *  then read the remaining data directly from the buffer in a third pass
584     *  Make sure that we cannot end up on a non-cluster aligned position!
585     */
586     unsigned int cluster_size=m_events_per_frame*m_event_size;
587
588     while(bytes2read>0) {
589         unsigned int framesread=(nbframes*cluster_size-bytes2read)/cluster_size;
590         offset=framesread;
591
592         int bytesread=0;
593
594         ffado_ringbuffer_get_read_vector(m_event_buffer, vec);
595
596         if(vec[0].len==0) { // this indicates an empty event buffer
597             debugError("Event buffer underrun in buffer %p\n",this);
598             return false;
599         }
600
601         /* if we don't take care we will get stuck in an infinite loop
602         * because we align to a cluster boundary later
603         * the remaining nb of bytes in one read operation can be smaller than one cluster
604         * this can happen because the ringbuffer size is always a power of 2
605                 */
606         if(vec[0].len<cluster_size) {
607             // use the ringbuffer function to read one cluster
608             // the read function handles wrap around
609             ffado_ringbuffer_read(m_event_buffer,m_cluster_buffer,cluster_size);
610
611             assert(m_Client);
612             xrun = m_Client->processReadBlock(m_cluster_buffer, 1, offset);
613
614             if(xrun<0) {
615                 // xrun detected
616                 debugError("Frame buffer overrun in buffer %p\n",this);
617                     return false;
618             }
619
620             // we advanced one cluster_size
621             bytes2read-=cluster_size;
622
623         } else { //
624
625             if(bytes2read>vec[0].len) {
626                 // align to a cluster boundary
627                 bytesread=vec[0].len-(vec[0].len%cluster_size);
628             } else {
629                 bytesread=bytes2read;
630             }
631
632             assert(m_Client);
633             xrun = m_Client->processReadBlock(vec[0].buf, bytesread/cluster_size, offset);
634
635             if(xrun<0) {
636                 // xrun detected
637                 debugError("Frame buffer overrun in buffer %p\n",this);
638                 return false;
639             }
640
641             ffado_ringbuffer_read_advance(m_event_buffer, bytesread);
642             bytes2read -= bytesread;
643         }
644
645         // the bytes2read should always be cluster aligned
646         assert(bytes2read%cluster_size==0);
647     }
648
649     decrementFrameCounter(nbframes);
650
651     return true;
652 }
653
654 /**
655  * @brief Sets the buffer tail timestamp.
656  *
657  * Set the buffer tail timestamp to \ref new_timestamp. This will recalculate
658  * the internal state such that the buffer's timeframe starts at
659  * \ref new_timestamp.
660  *
661  * This is thread safe.
662  *
663  * @note considers offsets
664  *
665  * @param new_timestamp
666  */
667 void TimestampedBuffer::setBufferTailTimestamp(ffado_timestamp_t new_timestamp) {
668
669     // add the offsets
670     ffado_timestamp_t ts=new_timestamp;
671     ts += m_tick_offset;
672
673     if (ts >= m_wrap_at) {
674         ts -= m_wrap_at;
675     } else if (ts < 0) {
676         ts += m_wrap_at;
677     }
678
679 #ifdef DEBUG
680     if (new_timestamp >= m_wrap_at) {
681         debugWarning("timestamp not wrapped: "TIMESTAMP_FORMAT_SPEC"\n",new_timestamp);
682     }
683     if ((ts >= m_wrap_at) || (ts < 0 )) {
684         debugWarning("ts not wrapped correctly: "TIMESTAMP_FORMAT_SPEC"\n",ts);
685     }
686 #endif
687
688     ENTER_CRITICAL_SECTION;
689
690     m_buffer_tail_timestamp = ts;
691
692     m_dll_e2=m_update_period * (double)m_nominal_rate;
693     m_buffer_next_tail_timestamp = (ffado_timestamp_t)((double)m_buffer_tail_timestamp + m_dll_e2);
694
695     EXIT_CRITICAL_SECTION;
696
697     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "for (%p) to "
698                                           TIMESTAMP_FORMAT_SPEC" => "TIMESTAMP_FORMAT_SPEC", NTS="
699                                           TIMESTAMP_FORMAT_SPEC", DLL2=%f, RATE=%f\n",
700                 this, new_timestamp, ts, m_buffer_next_tail_timestamp, m_dll_e2, getRate());
701
702 }
703
704 /**
705  * @brief Sets the buffer head timestamp.
706  *
707  * Set the buffer tail timestamp such that the buffer head timestamp becomes
708  * \ref new_timestamp. This does not consider offsets, because it's use is to
709  * make sure the following is true after setBufferHeadTimestamp(x):
710  *   x == getBufferHeadTimestamp()
711  *
712  * This is thread safe.
713  *
714  * @param new_timestamp
715  */
716 void TimestampedBuffer::setBufferHeadTimestamp(ffado_timestamp_t new_timestamp) {
717
718 #ifdef DEBUG
719     if (new_timestamp >= m_wrap_at) {
720         debugWarning("timestamp not wrapped: "TIMESTAMP_FORMAT_SPEC"\n",new_timestamp);
721     }
722 #endif
723
724     ffado_timestamp_t ts=new_timestamp;
725
726     ENTER_CRITICAL_SECTION;
727
728     // add the time
729     ts += (ffado_timestamp_t)(m_nominal_rate * (float)m_framecounter);
730
731     if (ts >= m_wrap_at) {
732         ts -= m_wrap_at;
733     } else if (ts < 0) {
734         ts += m_wrap_at;
735     }
736
737     m_buffer_tail_timestamp = ts;
738
739     m_dll_e2=m_update_period * (double)m_nominal_rate;
740     m_buffer_next_tail_timestamp = (ffado_timestamp_t)((double)m_buffer_tail_timestamp + m_dll_e2);
741
742     EXIT_CRITICAL_SECTION;
743
744     debugOutput(DEBUG_LEVEL_VERBOSE, "for (%p) to "TIMESTAMP_FORMAT_SPEC" => "
745                                           TIMESTAMP_FORMAT_SPEC", NTS="TIMESTAMP_FORMAT_SPEC", DLL2=%f, RATE=%f\n",
746                 this, new_timestamp, ts, m_buffer_next_tail_timestamp, m_dll_e2, getRate());
747
748 }
749
750 /**
751  * @brief Synchronize the buffer head to a specified timestamp
752  *
753  * Try to synchronize the buffer head to a specific timestamp. This
754  * can mean adding or removing samples to/from the buffer such that
755  * the buffer head aligns with the specified timestamp. The alignment
756  * is within ts +/- Tsample/2
757  *
758  * @param target the timestamp to align to
759  * @return true if alignment succeeded, false if not
760  */
761 bool
762 TimestampedBuffer::syncBufferHeadToTimestamp(ffado_timestamp_t target)
763 {
764     uint64_t ts_head;
765     uint64_t ts_target=(uint64_t)target;
766     signed int fc;
767     int32_t lag_ticks;
768     float lag_frames;
769
770     ffado_timestamp_t ts_head_tmp;
771     getBufferHeadTimestamp(&ts_head_tmp, &fc);
772     ts_head=(uint64_t)ts_head_tmp;
773     // if target > ts_head then the wanted buffer head timestamp
774     // is later than the actual. This means that we (might) have to drop
775     // some frames.
776     lag_ticks=diffTicks(ts_target, ts_head);
777     float rate=getRate();
778    
779     assert(rate!=0.0);
780
781     lag_frames=(((float)lag_ticks)/rate);
782    
783     debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): HEAD=%llu, TS=%llu, diff=%ld = %10.5f frames (rate=%10.5f)\n",
784                                       this, ts_head, ts_target, lag_ticks, lag_frames, rate);
785
786     if (lag_frames>=1.0) {
787         // the buffer head is too early
788         // ditch frames until the buffer head is on time
789         char dummy[getBytesPerFrame()]; // one frame of garbage
790         int frames_to_ditch=(int)roundf(lag_frames);
791         debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): ditching %d frames (@ ts=%lld)\n",this,frames_to_ditch,ts_target);
792        
793         while (frames_to_ditch--) {
794             readFrames(1, dummy);
795         }
796        
797     } else if (lag_frames<=-1.0) {
798         // the buffer head is too late
799         // add some padding frames
800         int frames_to_add=(int)roundf(lag_frames);
801         debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): adding %d frames (@ ts=%lld)\n",this,-frames_to_add,ts_target);
802        
803         while (frames_to_add++) {
804              writeDummyFrame();
805         }
806     }
807     getBufferHeadTimestamp(&ts_head_tmp, &fc);
808     ts_head=(uint64_t)ts_head_tmp;
809     debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): new HEAD=%llu, fc=%d, target=%llu, new diff=%lld\n",
810                                       this, ts_head, fc, ts_target, diffTicks(ts_target, ts_head));
811     // FIXME: of course this doesn't always succeed
812     return true;
813 }
814
815 /**
816  * @brief Synchronize the buffer tail to a specified timestamp
817  *
818  * Try to synchronize the buffer tail to a specific timestamp. This
819  * can mean adding or removing samples to/from the buffer such that
820  * the buffer tail aligns with the specified timestamp. The alignment
821  * is within ts +/- Tsample/2
822  *
823  * @param target the timestamp to align to
824  * @return true if alignment succeeded, false if not
825  */
826 bool
827 TimestampedBuffer::syncBufferTailToTimestamp(ffado_timestamp_t target)
828 {
829     uint64_t ts_tail;
830     uint64_t ts_target=(uint64_t)target;
831     signed int fc;
832     int32_t lag_ticks;
833     float lag_frames;
834
835     debugWarning("Untested\n");
836    
837     ffado_timestamp_t ts_tail_tmp;
838     getBufferTailTimestamp(&ts_tail_tmp, &fc);
839     ts_tail=(uint64_t)ts_tail_tmp;
840     // if target < ts_tail then the wanted buffer head timestamp
841     // is later than the actual. This means that we (might) have to drop
842     // some frames.
843     lag_ticks=diffTicks(ts_tail, ts_target);
844     float rate=getRate();
845    
846     assert(rate!=0.0);
847
848     lag_frames=(((float)lag_ticks)/rate);
849    
850     debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): HEAD=%llu, TS=%llu, diff=%ld = %10.5f frames (rate=%10.5f)\n",
851                                       this, ts_tail, ts_target, lag_ticks, lag_frames, rate);
852
853     if (lag_frames>=1.0) {
854         // the buffer head is too early
855         // ditch frames until the buffer head is on time
856         char dummy[getBytesPerFrame()]; // one frame of garbage
857         int frames_to_ditch=(int)roundf(lag_frames);
858         debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): ditching %d frames (@ ts=%lld)\n",this,frames_to_ditch,ts_target);
859        
860         while (frames_to_ditch--) {
861             readFrames(1, dummy);
862         }
863        
864     } else if (lag_frames<=-1.0) {
865         // the buffer head is too late
866         // add some padding frames
867         int frames_to_add=(int)roundf(lag_frames);
868         debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): adding %d frames (@ ts=%lld)\n",this,-frames_to_add,ts_target);
869        
870         while (frames_to_add++) {
871              writeDummyFrame();
872         }
873     }
874     getBufferHeadTimestamp(&ts_tail_tmp, &fc);
875     ts_tail=(uint64_t)ts_tail_tmp;
876     debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): new HEAD=%llu, fc=%d, target=%llu, new diff=%lld\n",
877                                       this, ts_tail, fc, ts_target, diffTicks(ts_target, ts_tail));
878     // FIXME: of course this doesn't always succeed
879     return true;
880 }
881
882 /**
883  * @brief correct lag
884  *
885  * Try to synchronize the buffer tail to a specific timestamp. This
886  * can mean adding or removing samples to/from the buffer such that
887  * the buffer tail aligns with the specified timestamp. The alignment
888  * is within ts +/- Tsample/2
889  *
890  * @param target the timestamp to align to
891  * @return true if alignment succeeded, false if not
892  */
893 bool
894 TimestampedBuffer::syncCorrectLag(int64_t lag_ticks)
895 {
896     float lag_frames;
897     float rate=getRate();
898     assert(rate!=0.0);
899
900     lag_frames=(((float)lag_ticks)/rate);
901     if (lag_frames >= 1.0) {
902         // the buffer head is too late
903         // add some padding frames
904         int frames_to_add=(int)roundf(lag_frames);
905         debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): adding %d frames\n",this,frames_to_add);
906
907         while (frames_to_add++) {
908              writeDummyFrame();
909         }
910     } else if (lag_frames <= -1.0) {
911         // the buffer head is too early
912         // ditch frames until the buffer head is on time
913         char dummy[getBytesPerFrame()]; // one frame of garbage
914         int frames_to_ditch=(int)roundf(lag_frames);
915         debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): ditching %d frames\n",this,-frames_to_ditch);
916
917         while (frames_to_ditch--) {
918             readFrames(1, dummy);
919         }
920     }
921     return true;
922 }
923
924 /**
925  * \brief return the timestamp of the first frame in the buffer
926  *
927  * This function returns the timestamp of the very first sample in
928  * the StreamProcessor's buffer. It also returns the framecounter value
929  * for which this timestamp is valid.
930  *
931  * @param ts address to store the timestamp in
932  * @param fc address to store the associated framecounter in
933  */
934 void TimestampedBuffer::getBufferHeadTimestamp(ffado_timestamp_t *ts, signed int *fc) {
935     // NOTE: this is still ok with threads, because we use *fc to compute
936     //       the timestamp
937     *fc = m_framecounter;
938     *ts=getTimestampFromTail(*fc);
939 }
940
941 /**
942  * \brief return the timestamp of the last frame in the buffer
943  *
944  * This function returns the timestamp of the last frame in
945  * the StreamProcessor's buffer. It also returns the framecounter
946  * value for which this timestamp is valid.
947  *
948  * @param ts address to store the timestamp in
949  * @param fc address to store the associated framecounter in
950  */
951 void TimestampedBuffer::getBufferTailTimestamp(ffado_timestamp_t *ts, signed int *fc) {
952     ENTER_CRITICAL_SECTION;
953     *fc = m_framecounter;
954     *ts = m_buffer_tail_timestamp;
955     EXIT_CRITICAL_SECTION;
956 }
957
958 /**
959  * @brief Get timestamp for a specific position from the buffer tail
960  *
961  * Returns the timestamp for a position that is nframes earlier than the
962  * buffer tail
963  *
964  * @param nframes number of frames
965  * @return timestamp value
966  */
967 ffado_timestamp_t TimestampedBuffer::getTimestampFromTail(int nframes)
968 {
969     // ts(x) = m_buffer_tail_timestamp -
970     //         (m_buffer_next_tail_timestamp - m_buffer_tail_timestamp)/(samples_between_updates)*(x)
971     ffado_timestamp_t diff;
972     float rate;
973     ffado_timestamp_t timestamp;
974    
975     ENTER_CRITICAL_SECTION;
976
977     diff=m_buffer_next_tail_timestamp - m_buffer_tail_timestamp;
978     timestamp=m_buffer_tail_timestamp;
979    
980     EXIT_CRITICAL_SECTION;
981    
982     if (diff < 0) diff += m_wrap_at;
983     rate=(float)diff / (float)m_update_period;
984
985     timestamp-=(ffado_timestamp_t)((nframes) * rate);
986
987     if(timestamp >= m_wrap_at) {
988         timestamp -= m_wrap_at;
989     } else if(timestamp < 0) {
990         timestamp += m_wrap_at;
991     }
992
993     return timestamp;
994 }
995
996 /**
997  * @brief Get timestamp for a specific position from the buffer head
998  *
999  * Returns the timestamp for a position that is nframes later than the
1000  * buffer head
1001  *
1002  * @param nframes number of frames
1003  * @return timestamp value
1004  */
1005 ffado_timestamp_t TimestampedBuffer::getTimestampFromHead(int nframes)
1006 {
1007     return getTimestampFromTail(m_framecounter-nframes);
1008 }
1009
1010 /**
1011  * Resets the frame counter, in a atomic way. This
1012  * is thread safe.
1013  */
1014 void TimestampedBuffer::resetFrameCounter() {
1015     ENTER_CRITICAL_SECTION;
1016     m_framecounter = 0;
1017     EXIT_CRITICAL_SECTION;
1018 }
1019
1020 /**
1021  * Decrements the frame counter in a thread safe way.
1022  *
1023  * @param nbframes number of frames to decrement
1024  */
1025 void TimestampedBuffer::decrementFrameCounter(int nbframes) {
1026     ENTER_CRITICAL_SECTION;
1027     m_framecounter -= nbframes;
1028     EXIT_CRITICAL_SECTION;
1029 }
1030
1031 /**
1032  * Increments the frame counter in a thread safe way.
1033  * Also updates the timestamp.
1034  *
1035  * @note the offsets defined by setTicksOffset and setFrameOffset
1036  *       are added here.
1037  *
1038  * @param nbframes the number of frames to add
1039  * @param new_timestamp the new timestamp
1040  */
1041 void TimestampedBuffer::incrementFrameCounter(int nbframes, ffado_timestamp_t new_timestamp) {
1042
1043     // add the offsets
1044     ffado_timestamp_t diff;
1045    
1046     ENTER_CRITICAL_SECTION;
1047     diff=m_buffer_next_tail_timestamp - m_buffer_tail_timestamp;
1048     EXIT_CRITICAL_SECTION;
1049
1050     if (diff < 0) diff += m_wrap_at;
1051
1052 #ifdef DEBUG
1053     float rate=(float)diff / (float)m_update_period;
1054 #endif
1055
1056     ffado_timestamp_t ts=new_timestamp;
1057     ts += m_tick_offset;
1058
1059     if (ts >= m_wrap_at) {
1060         ts -= m_wrap_at;
1061     } else if (ts < 0) {
1062         ts += m_wrap_at;
1063     }
1064
1065 #ifdef DEBUG
1066     if (new_timestamp >= m_wrap_at) {
1067         debugWarning("timestamp not wrapped: "TIMESTAMP_FORMAT_SPEC"\n", new_timestamp);
1068     }
1069     if ((ts >= m_wrap_at) || (ts < 0 )) {
1070         debugWarning("ts not wrapped correctly: "TIMESTAMP_FORMAT_SPEC"\n",ts);
1071     }
1072 #endif
1073 // FIXME: JMW: at some points during startup the timestamp doesn't change.
1074 // This still needs to be verified in more detail. 
1075 // if (ts>m_buffer_tail_timestamp-1 && ts<m_buffer_tail_timestamp+1) {
1076 //   ENTER_CRITICAL_SECTION;
1077 //   m_framecounter += nbframes;
1078 //   EXIT_CRITICAL_SECTION;
1079 //   return;
1080 // }
1081     ffado_timestamp_t pred_buffer_next_tail_timestamp;
1082     if(nbframes == m_update_period) {
1083         pred_buffer_next_tail_timestamp = m_buffer_next_tail_timestamp;
1084     } else {
1085         debugOutput( DEBUG_LEVEL_VERBOSE,
1086                      "Number of frames (%u) != update period (%u)\n",
1087                      nbframes, m_update_period );
1088         // calculate the predicted timestamp for nframes (instead of m_update_period)
1089         // after the previous update.
1090         float rel_step = ((float)nbframes)/((float)m_update_period);
1091         ENTER_CRITICAL_SECTION; // FIXME: do we need these?
1092         ffado_timestamp_t corrected_step = (m_buffer_next_tail_timestamp - m_buffer_tail_timestamp) * rel_step;
1093         pred_buffer_next_tail_timestamp = m_buffer_tail_timestamp + corrected_step;
1094         EXIT_CRITICAL_SECTION;
1095        
1096         debugOutput( DEBUG_LEVEL_VERBOSE,
1097                      "Updated ("TIMESTAMP_FORMAT_SPEC","TIMESTAMP_FORMAT_SPEC") to ("TIMESTAMP_FORMAT_SPEC","TIMESTAMP_FORMAT_SPEC")\n",
1098                      m_buffer_tail_timestamp, m_buffer_next_tail_timestamp,
1099                      m_buffer_tail_timestamp, pred_buffer_next_tail_timestamp);
1100        
1101     }
1102    
1103     // the difference between the given TS and the one predicted for this time instant
1104     // this is the error for the DLL
1105     diff = ts - pred_buffer_next_tail_timestamp;
1106
1107     // check whether the update is within the allowed bounds
1108     const float max_deviation = (50.0/100.0); // maximal relative difference considered normal
1109     ffado_timestamp_t one_update_step = nbframes * getRate();
1110     ffado_timestamp_t max_abs_diff = one_update_step * (1.0 + max_deviation);
1111    
1112     if (diff > max_abs_diff) {
1113         debugWarning("(%p) difference rather large (+): diff="TIMESTAMP_FORMAT_SPEC", max="TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC"\n",
1114             this, diff, max_abs_diff, ts, pred_buffer_next_tail_timestamp);
1115 //         debugShowBackLogLines(40);
1116     } else if (diff < -max_abs_diff) {
1117         debugWarning("(%p) difference rather large (-): diff="TIMESTAMP_FORMAT_SPEC", max="TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC"\n",
1118             this, diff, -max_abs_diff, ts, pred_buffer_next_tail_timestamp);
1119 //         debugShowBackLogLines(40);
1120     }
1121
1122     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "(%p): diff="TIMESTAMP_FORMAT_SPEC" ",
1123                 this, diff);
1124
1125     double err=diff;
1126
1127     debugOutputShort(DEBUG_LEVEL_VERY_VERBOSE, "diff2="TIMESTAMP_FORMAT_SPEC" err=%f\n",
1128                     diff, err);
1129     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "B: FC=%10u, TS="TIMESTAMP_FORMAT_SPEC", NTS="TIMESTAMP_FORMAT_SPEC"\n",
1130                     m_framecounter, m_buffer_tail_timestamp, pred_buffer_next_tail_timestamp);
1131
1132     ENTER_CRITICAL_SECTION;
1133     m_framecounter += nbframes;
1134
1135     m_buffer_tail_timestamp = pred_buffer_next_tail_timestamp;
1136     m_buffer_next_tail_timestamp = pred_buffer_next_tail_timestamp + (ffado_timestamp_t)(m_dll_b * err + m_dll_e2);
1137 //    m_buffer_tail_timestamp=ts;
1138 //    m_buffer_next_tail_timestamp += (ffado_timestamp_t)(m_dll_b * err + m_dll_e2);
1139    
1140     m_dll_e2 += m_dll_c*err;
1141
1142     EXIT_CRITICAL_SECTION;
1143
1144     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "tail for (%p) to "
1145                                           TIMESTAMP_FORMAT_SPEC" => "TIMESTAMP_FORMAT_SPEC", NTS="
1146                                           TIMESTAMP_FORMAT_SPEC", DLL2=%f, RATE=%f\n",
1147                 this, new_timestamp, ts, m_buffer_next_tail_timestamp, m_dll_e2, getRate());
1148
1149     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "U: FC=%10u, TS="TIMESTAMP_FORMAT_SPEC", NTS="TIMESTAMP_FORMAT_SPEC"\n",
1150                     m_framecounter, m_buffer_tail_timestamp, m_buffer_next_tail_timestamp);
1151
1152     ENTER_CRITICAL_SECTION;
1153     if (m_buffer_next_tail_timestamp >= m_wrap_at) {
1154         debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "Unwrapping next tail timestamp: "TIMESTAMP_FORMAT_SPEC"",
1155                 m_buffer_next_tail_timestamp);
1156
1157         m_buffer_next_tail_timestamp -= m_wrap_at;
1158
1159         debugOutputShort(DEBUG_LEVEL_VERY_VERBOSE, " => "TIMESTAMP_FORMAT_SPEC"\n",
1160                 m_buffer_next_tail_timestamp);
1161
1162     }
1163     EXIT_CRITICAL_SECTION;
1164
1165     debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "A: TS="TIMESTAMP_FORMAT_SPEC", NTS="TIMESTAMP_FORMAT_SPEC", DLLe2=%f, RATE=%f\n",
1166                 m_buffer_tail_timestamp, m_buffer_next_tail_timestamp, m_dll_e2, rate);
1167
1168
1169     if(m_buffer_tail_timestamp>=m_wrap_at) {
1170         debugError("Wrapping failed for m_buffer_tail_timestamp! "TIMESTAMP_FORMAT_SPEC"\n",m_buffer_tail_timestamp);
1171         debugOutput(DEBUG_LEVEL_VERY_VERBOSE, " IN="TIMESTAMP_FORMAT_SPEC", TS="TIMESTAMP_FORMAT_SPEC", NTS="TIMESTAMP_FORMAT_SPEC"\n",
1172                     ts, m_buffer_tail_timestamp, m_buffer_next_tail_timestamp);
1173
1174     }
1175     if(m_buffer_next_tail_timestamp>=m_wrap_at) {
1176         debugError("Wrapping failed for m_buffer_next_tail_timestamp! "TIMESTAMP_FORMAT_SPEC"\n",m_buffer_next_tail_timestamp);
1177         debugOutput(DEBUG_LEVEL_VERY_VERBOSE, " IN="TIMESTAMP_FORMAT_SPEC", TS="TIMESTAMP_FORMAT_SPEC", NTS="TIMESTAMP_FORMAT_SPEC"\n",
1178                     ts, m_buffer_tail_timestamp, m_buffer_next_tail_timestamp);
1179     }
1180    
1181     if(m_buffer_tail_timestamp==m_buffer_next_tail_timestamp) {
1182         debugError("Current and next timestamps are equal: "TIMESTAMP_FORMAT_SPEC" "TIMESTAMP_FORMAT_SPEC"\n",
1183                    m_buffer_tail_timestamp,m_buffer_next_tail_timestamp);
1184    
1185     }
1186
1187     // this DLL allows the calculation of any sample timestamp relative to the buffer tail,
1188     // to the next period and beyond (through extrapolation)
1189     //
1190     // ts(x) = m_buffer_tail_timestamp +
1191     //         (m_buffer_next_tail_timestamp - m_buffer_tail_timestamp)/(samples_between_updates)*x
1192
1193 }
1194
1195 /**
1196  * @brief Print status info.
1197  */
1198 void TimestampedBuffer::dumpInfo() {
1199
1200     ffado_timestamp_t ts_head;
1201     signed int fc;
1202     getBufferHeadTimestamp(&ts_head,&fc);
1203
1204 #ifdef DEBUG
1205     ffado_timestamp_t diff=(ffado_timestamp_t)ts_head - (ffado_timestamp_t)m_buffer_tail_timestamp;
1206 #endif
1207
1208     debugOutputShort( DEBUG_LEVEL_NORMAL, "  TimestampedBuffer (%p) info:\n",this);
1209     debugOutputShort( DEBUG_LEVEL_NORMAL, "  Frame counter         : %d\n", m_framecounter);
1210     debugOutputShort( DEBUG_LEVEL_NORMAL, "  Buffer head timestamp : "TIMESTAMP_FORMAT_SPEC"\n",ts_head);
1211     debugOutputShort( DEBUG_LEVEL_NORMAL, "  Buffer tail timestamp : "TIMESTAMP_FORMAT_SPEC"\n",m_buffer_tail_timestamp);
1212     debugOutputShort( DEBUG_LEVEL_NORMAL, "  Next tail timestamp   : "TIMESTAMP_FORMAT_SPEC"\n",m_buffer_next_tail_timestamp);
1213     debugOutputShort( DEBUG_LEVEL_NORMAL, "  Head - Tail           : "TIMESTAMP_FORMAT_SPEC"\n",diff);
1214     debugOutputShort( DEBUG_LEVEL_NORMAL, "  rate                  : %f (%f)\n",m_dll_e2,m_dll_e2/m_update_period);
1215 }
1216
1217 } // end of namespace Util
Note: See TracBrowser for help on using the browser.