]> git.sesse.net Git - vlc/blob - src/video_parser/vpar_synchro.c
* Fixed a few warnings with gcc 3.0.
[vlc] / src / video_parser / vpar_synchro.c
1 /*****************************************************************************
2  * vpar_synchro.c : frame dropping routines
3  *****************************************************************************
4  * Copyright (C) 1999, 2000 VideoLAN
5  * $Id: vpar_synchro.c,v 1.89 2001/05/06 04:32:03 sam Exp $
6  *
7  * Authors: Christophe Massiot <massiot@via.ecp.fr>
8  *          Samuel Hocevar <sam@via.ecp.fr>
9  *          Jean-Marc Dressler <polux@via.ecp.fr>
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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
24  *****************************************************************************/
25
26 /*
27  * DISCUSSION : How to Write an efficient Frame-Dropping Algorithm
28  * ==========
29  *
30  * This implementation is based on mathematical and statistical
31  * developments. Older implementations used an enslavement, considering
32  * that if we're late when reading an I picture, we will decode one frame
33  * less. It had a tendancy to derive, and wasn't responsive enough, which
34  * would have caused trouble with the stream control stuff.
35  *
36  * 1. Structure of a picture stream
37  *    =============================
38  * Between 2 I's, we have for instance :
39  *    I   B   P   B   P   B   P   B   P   B   P   B   I
40  *    t0  t1  t2  t3  t4  t5  t6  t7  t8  t9  t10 t11 t12
41  * Please bear in mind that B's and IP's will be inverted when displaying
42  * (decoding order != presentation order). Thus, t1 < t0.
43  *
44  * 2. Definitions
45  *    ===========
46  * t[0..12]     : Presentation timestamps of pictures 0..12.
47  * t            : Current timestamp, at the moment of the decoding.
48  * T            : Picture period, T = 1/frame_rate.
49  * tau[I,P,B]   : Mean time to decode an [I,P,B] picture.
50  * tauYUV       : Mean time to render a picture (given by the video_output).
51  * tau´[I,P,B] = 2 * tau[I,P,B] + tauYUV
52  *              : Mean time + typical difference (estimated to tau/2, that
53  *                needs to be confirmed) + render time.
54  * DELTA        : A given error margin.
55  *
56  * 3. General considerations
57  *    ======================
58  * We define three types of machines :
59  *      14T > tauI : machines capable of decoding all I pictures
60  *      2T > tauP  : machines capable of decoding all P pictures
61  *      T > tauB   : machines capable of decoding all B pictures
62  *
63  * 4. Decoding of an I picture
64  *    ========================
65  * On fast machines, we decode all I's.
66  * Otherwise :
67  * We can decode an I picture if we simply have enough time to decode it 
68  * before displaying :
69  *      t0 - t > tau´I + DELTA
70  *
71  * 5. Decoding of a P picture
72  *    =======================
73  * On fast machines, we decode all P's.
74  * Otherwise :
75  * First criterion : have time to decode it.
76  *      t2 - t > tau´P + DELTA
77  *
78  * Second criterion : it shouldn't prevent us from displaying the forthcoming
79  * I picture, which is more important.
80  *      t12 - t > tau´P + tau´I + DELTA
81  *
82  * 6. Decoding of a B picture
83  *    =======================
84  * On fast machines, we decode all B's. Otherwise :
85  *      t1 - t > tau´B + DELTA
86  * Since the next displayed I or P is already decoded, we don't have to
87  * worry about it.
88  *
89  * I hope you will have a pleasant flight and do not forget your life
90  * jacket.
91  *                                                  --Meuuh (2000-12-29)
92  */
93
94 /*****************************************************************************
95  * Preamble
96  *****************************************************************************/
97 #include "defs.h"
98
99 #include <string.h>                                    /* memcpy(), memset() */
100
101 #include "config.h"
102 #include "common.h"
103 #include "threads.h"
104 #include "mtime.h"
105
106 #include "intf_msg.h"
107
108 #include "stream_control.h"
109 #include "input_ext-dec.h"
110
111 #include "video.h"
112 #include "video_output.h"
113
114 #include "video_decoder.h"
115 #include "vdec_motion.h"
116
117 #include "vpar_blocks.h"
118 #include "vpar_headers.h"
119 #include "vpar_synchro.h"
120 #include "video_parser.h"
121
122 #include "main.h"
123
124 /*
125  * Local prototypes
126  */
127 static int  SynchroType( void );
128
129 /* Error margins */
130 #define DELTA                   (int)(0.040*CLOCK_FREQ)
131
132 #define DEFAULT_NB_P            5
133 #define DEFAULT_NB_B            1
134
135 /*****************************************************************************
136  * vpar_SynchroInit : You know what ?
137  *****************************************************************************/
138 void vpar_SynchroInit( vpar_thread_t * p_vpar )
139 {
140     p_vpar->synchro.i_type = SynchroType();
141     p_vpar->synchro.i_start = p_vpar->synchro.i_end = 0;
142     vlc_mutex_init( &p_vpar->synchro.fifo_lock );
143
144     /* We use a fake stream pattern, which is often right. */
145     p_vpar->synchro.i_n_p = p_vpar->synchro.i_eta_p = DEFAULT_NB_P;
146     p_vpar->synchro.i_n_b = p_vpar->synchro.i_eta_b = DEFAULT_NB_B;
147     memset( p_vpar->synchro.p_tau, 0, 4 * sizeof(mtime_t) );
148     memset( p_vpar->synchro.pi_meaningful, 0, 4 * sizeof(unsigned int) );
149     p_vpar->synchro.b_dropped_last = 0;
150     p_vpar->synchro.current_pts = mdate() + DEFAULT_PTS_DELAY;
151     p_vpar->synchro.backward_pts = 0;
152     p_vpar->synchro.i_current_period = p_vpar->synchro.i_backward_period = 0;
153 #ifdef STATS
154     p_vpar->synchro.i_trashed_pic = p_vpar->synchro.i_not_chosen_pic = 
155         p_vpar->synchro.i_pic = 0;
156 #endif
157 }
158
159 /*****************************************************************************
160  * vpar_SynchroChoose : Decide whether we will decode a picture or not
161  *****************************************************************************/
162 boolean_t vpar_SynchroChoose( vpar_thread_t * p_vpar, int i_coding_type,
163                               int i_structure )
164 {
165     /* For clarity reasons, we separated the special synchros code from the
166      * mathematical synchro */
167
168     if( p_vpar->synchro.i_type != VPAR_SYNCHRO_DEFAULT )
169     {
170         switch( i_coding_type )
171         {
172         case I_CODING_TYPE:
173             /* I, IP, IP+, IPB */
174             if( p_vpar->synchro.i_type == VPAR_SYNCHRO_Iplus )
175             {
176                 p_vpar->synchro.b_dropped_last = 1;
177             }
178             return( 1 );
179
180         case P_CODING_TYPE:
181             if( p_vpar->synchro.i_type == VPAR_SYNCHRO_I ) /* I */
182             {
183                 return( 0 );
184             }
185
186             if( p_vpar->synchro.i_type == VPAR_SYNCHRO_Iplus ) /* I+ */
187             {
188                 if( p_vpar->synchro.b_dropped_last )
189                 {
190                     p_vpar->synchro.b_dropped_last = 0;
191                     return( 1 );
192                 }
193                 else
194                 {
195                     return( 0 );
196                 }
197             }
198
199             return( 1 ); /* IP, IP+, IPB */
200
201         case B_CODING_TYPE:
202             if( p_vpar->synchro.i_type <= VPAR_SYNCHRO_IP ) /* I, IP */
203             {
204                 return( 0 );
205             }
206             else if( p_vpar->synchro.i_type == VPAR_SYNCHRO_IPB ) /* IPB */
207             {
208                 return( 1 );
209             }
210
211             p_vpar->synchro.b_dropped_last ^= 1; /* IP+ */
212             return( !p_vpar->synchro.b_dropped_last );
213         }
214         return( 0 ); /* never reached but gcc yells at me */
215     }
216     else
217     {
218 #define TAU_PRIME( coding_type )    (p_vpar->synchro.p_tau[(coding_type)] \
219                                  + (p_vpar->synchro.p_tau[(coding_type)] >> 1) \
220                                             + tau_yuv)
221 #define S                           p_vpar->synchro
222         /* VPAR_SYNCHRO_DEFAULT */
223         mtime_t         now, period, tau_yuv;
224         mtime_t         pts = 0;
225         boolean_t       b_decode = 0;
226 #ifdef TRACE_VPAR
227         char            p_date[MSTRTIME_MAX_SIZE];
228 #endif
229
230         now = mdate();
231         period = 1000000 * 1001 / p_vpar->sequence.i_frame_rate
232                     * p_vpar->sequence.i_current_rate / DEFAULT_RATE;
233
234         vlc_mutex_lock( &p_vpar->p_vout->change_lock );
235         tau_yuv = p_vpar->p_vout->render_time;
236         vlc_mutex_unlock( &p_vpar->p_vout->change_lock );
237 #ifdef VDEC_SMP
238         vlc_mutex_lock( &p_vpar->synchro.fifo_lock );
239 #endif
240
241         switch( i_coding_type )
242         {
243         case I_CODING_TYPE:
244             if( S.backward_pts )
245             {
246                 pts = S.backward_pts;
247             }
248             else
249             {
250                 /* displaying order : B B P B B I
251                  *                      ^       ^
252                  *                      |       +- current picture
253                  *                      +- current PTS
254                  */
255                 pts = S.current_pts + period * (S.i_n_b + 2);
256             }
257
258             if( (1 + S.i_n_p * (S.i_n_b + 1)) * period >
259                     S.p_tau[I_CODING_TYPE] )
260             {
261                 b_decode = 1;
262             }
263             else
264             {
265                 b_decode = (pts - now) > (TAU_PRIME(I_CODING_TYPE) + DELTA);
266             }
267             if( !b_decode )
268                 intf_WarnMsg( 3, "vpar synchro warning: trashing I (%lld)",
269                              pts - now);
270             break;
271
272         case P_CODING_TYPE:
273             if( S.backward_pts )
274             {
275                 pts = S.backward_pts;
276             }
277             else
278             {
279                 pts = S.current_pts + period * (S.i_n_b + 1);
280             }
281
282             if( (1 + S.i_n_p * (S.i_n_b + 1)) * period >
283                     S.p_tau[I_CODING_TYPE] )
284             {
285                 if( (S.i_n_b + 1) * period > S.p_tau[P_CODING_TYPE] )
286                 {
287                     /* Security in case we're _really_ late */
288                     b_decode = (pts - now > 0);
289                 }
290                 else
291                 {
292                     b_decode = (pts - now) > (TAU_PRIME(P_CODING_TYPE) + DELTA);
293                     /* next I */
294                     b_decode &= (pts - now
295                                   + period
296                               * ( (S.i_n_p - S.i_eta_p) * (1 + S.i_n_b) - 1 ))
297                                 > (TAU_PRIME(P_CODING_TYPE)
298                                     + TAU_PRIME(I_CODING_TYPE) + DELTA);
299                 }
300             }
301             else
302             {
303                 b_decode = 0;
304             }
305             break;
306
307         case B_CODING_TYPE:
308             pts = S.current_pts;
309
310             if( (S.i_n_b + 1) * period > S.p_tau[P_CODING_TYPE] )
311             {
312                 b_decode = (pts - now) > (TAU_PRIME(B_CODING_TYPE) + DELTA);
313             }
314             else
315             {
316                 b_decode = 0;
317             }
318         }
319
320 #ifdef VDEC_SMP
321         vlc_mutex_unlock( &p_vpar->synchro.fifo_lock );
322 #endif
323 #ifdef TRACE_VPAR
324         intf_DbgMsg("vpar synchro debug: %s picture scheduled for %s, %s (%lld)",
325                     i_coding_type == B_CODING_TYPE ? "B" :
326                     (i_coding_type == P_CODING_TYPE ? "P" : "I"),
327                     mstrtime(p_date, pts), b_decode ? "decoding" : "trashed",
328                     S.p_tau[i_coding_type]);
329 #endif
330 #ifdef STATS
331         if( !b_decode )
332         {
333             S.i_not_chosen_pic++;
334         }
335 #endif
336         return( b_decode );
337 #undef S
338 #undef TAU_PRIME
339     }
340 }
341
342 /*****************************************************************************
343  * vpar_SynchroTrash : Update counters when we trash a picture
344  *****************************************************************************/
345 void vpar_SynchroTrash( vpar_thread_t * p_vpar, int i_coding_type,
346                         int i_structure )
347 {
348 #ifdef STATS
349     p_vpar->synchro.i_trashed_pic++;
350 #endif
351 }
352
353 /*****************************************************************************
354  * vpar_SynchroDecode : Update timers when we decide to decode a picture
355  *****************************************************************************/
356 void vpar_SynchroDecode( vpar_thread_t * p_vpar, int i_coding_type,
357                          int i_structure )
358 {
359 #ifdef VDEC_SMP
360     vlc_mutex_lock( &p_vpar->synchro.fifo_lock );
361 #endif
362
363     if( ((p_vpar->synchro.i_end + 1 - p_vpar->synchro.i_start)
364             % MAX_DECODING_PIC) )
365     {
366         p_vpar->synchro.p_date_fifo[p_vpar->synchro.i_end] = mdate();
367         p_vpar->synchro.pi_coding_types[p_vpar->synchro.i_end] = i_coding_type;
368
369         FIFO_INCREMENT( i_end );
370     }
371     else
372     {
373         /* FIFO full, panic() */
374         intf_ErrMsg("vpar error: synchro fifo full, estimations will be biased (%d:%d)",
375                     p_vpar->synchro.i_start, p_vpar->synchro.i_end);
376     }
377 #ifdef VDEC_SMP
378     vlc_mutex_unlock( &p_vpar->synchro.fifo_lock );
379 #endif
380 }
381
382 /*****************************************************************************
383  * vpar_SynchroEnd : Called when the image is totally decoded
384  *****************************************************************************/
385 void vpar_SynchroEnd( vpar_thread_t * p_vpar, int i_garbage )
386 {
387     mtime_t     tau;
388     int         i_coding_type;
389
390 #ifdef VDEC_SMP
391     vlc_mutex_lock( &p_vpar->synchro.fifo_lock );
392 #endif
393
394     i_coding_type = p_vpar->synchro.pi_coding_types[p_vpar->synchro.i_start];
395
396     if( !i_garbage )
397     {
398         tau = mdate() - p_vpar->synchro.p_date_fifo[p_vpar->synchro.i_start];
399
400         /* If duration too high, something happened (pause ?), so don't
401          * take it into account. */
402         if( tau < 3 * p_vpar->synchro.p_tau[i_coding_type]
403              || !p_vpar->synchro.pi_meaningful[i_coding_type] )
404         {
405             /* Mean with average tau, to ensure stability. */
406             p_vpar->synchro.p_tau[i_coding_type] =
407                 (p_vpar->synchro.pi_meaningful[i_coding_type]
408                  * p_vpar->synchro.p_tau[i_coding_type] + tau)
409                 / (p_vpar->synchro.pi_meaningful[i_coding_type] + 1);
410             if( p_vpar->synchro.pi_meaningful[i_coding_type] < MAX_PIC_AVERAGE )
411             {
412                 p_vpar->synchro.pi_meaningful[i_coding_type]++;
413             }
414         }
415
416 #ifdef TRACE_VPAR
417         intf_DbgMsg("vpar synchro debug: finished decoding %s (%lld)",
418                     i_coding_type == B_CODING_TYPE ? "B" :
419                     (i_coding_type == P_CODING_TYPE ? "P" : "I"), tau);
420 #endif
421     }
422     else
423     {
424         intf_DbgMsg("vpar synchro debug: aborting %s",
425                     i_coding_type == B_CODING_TYPE ? "B" :
426                     (i_coding_type == P_CODING_TYPE ? "P" : "I"));
427     }
428
429     FIFO_INCREMENT( i_start );
430
431 #ifdef VDEC_SMP
432     vlc_mutex_unlock( &p_vpar->synchro.fifo_lock );
433 #endif
434 }
435
436 /*****************************************************************************
437  * vpar_SynchroDate : When an image has been decoded, ask for its date
438  *****************************************************************************/
439 mtime_t vpar_SynchroDate( vpar_thread_t * p_vpar )
440 {
441     /* No need to lock, since PTS are only used by the video parser. */
442     return( p_vpar->synchro.current_pts );
443 }
444
445 /*****************************************************************************
446  * vpar_SynchroNewPicture: Update stream structure and PTS
447  *****************************************************************************/
448 void vpar_SynchroNewPicture( vpar_thread_t * p_vpar, int i_coding_type,
449                              int i_repeat_field )
450 {
451     mtime_t         period = 1000000 * 1001 / p_vpar->sequence.i_frame_rate
452                               * p_vpar->sequence.i_current_rate / DEFAULT_RATE;
453 #if 0
454     mtime_t         now = mdate(); 
455 #endif
456
457     switch( i_coding_type )
458     {
459     case I_CODING_TYPE:
460         if( p_vpar->synchro.i_eta_p
461                 && p_vpar->synchro.i_eta_p != p_vpar->synchro.i_n_p )
462         {
463             intf_WarnMsg( 1, "Stream periodicity changed from P[%d] to P[%d]",
464                           p_vpar->synchro.i_n_p, p_vpar->synchro.i_eta_p );
465             p_vpar->synchro.i_n_p = p_vpar->synchro.i_eta_p;
466         }
467         p_vpar->synchro.i_eta_p = p_vpar->synchro.i_eta_b = 0;
468 #ifdef STATS
469         if( p_vpar->synchro.i_type == VPAR_SYNCHRO_DEFAULT )
470         {
471             intf_Msg( "vpar synchro stats: I(%lld) P(%lld)[%d] B(%lld)[%d] YUV(%lld) : trashed %d:%d/%d",
472                   p_vpar->synchro.p_tau[I_CODING_TYPE],
473                   p_vpar->synchro.p_tau[P_CODING_TYPE],
474                   p_vpar->synchro.i_n_p,
475                   p_vpar->synchro.p_tau[B_CODING_TYPE],
476                   p_vpar->synchro.i_n_b,
477                   p_vpar->p_vout->render_time,
478                   p_vpar->synchro.i_not_chosen_pic,
479                   p_vpar->synchro.i_trashed_pic -
480                   p_vpar->synchro.i_not_chosen_pic,
481                   p_vpar->synchro.i_pic );
482             p_vpar->synchro.i_trashed_pic = p_vpar->synchro.i_not_chosen_pic
483                 = p_vpar->synchro.i_pic = 0;
484         }
485 #endif
486         break;
487     case P_CODING_TYPE:
488         p_vpar->synchro.i_eta_p++;
489         if( p_vpar->synchro.i_eta_b
490                 && p_vpar->synchro.i_eta_b != p_vpar->synchro.i_n_b )
491         {
492             intf_WarnMsg( 1, "Stream periodicity changed from B[%d] to B[%d]",
493                           p_vpar->synchro.i_n_b, p_vpar->synchro.i_eta_b );
494             p_vpar->synchro.i_n_b = p_vpar->synchro.i_eta_b;
495         }
496         p_vpar->synchro.i_eta_b = 0;
497         break;
498     case B_CODING_TYPE:
499         p_vpar->synchro.i_eta_b++;
500         break;
501     }
502
503     p_vpar->synchro.current_pts += p_vpar->synchro.i_current_period
504                                         * (period >> 1);
505  
506 #define PTS_THRESHOLD   (period >> 2)
507     if( i_coding_type == B_CODING_TYPE )
508     {
509         /* A video frame can be displayed 1, 2 or 3 times, according to
510          * repeat_first_field, top_field_first, progressive_sequence and
511          * progressive_frame. */
512         p_vpar->synchro.i_current_period = i_repeat_field;
513
514         if( p_vpar->sequence.next_pts )
515         {
516             if( p_vpar->sequence.next_pts - p_vpar->synchro.current_pts
517                     > PTS_THRESHOLD
518                  || p_vpar->synchro.current_pts - p_vpar->sequence.next_pts
519                     > PTS_THRESHOLD )
520             {
521                 intf_WarnMsg( 2,
522                         "vpar synchro warning: pts != current_date (%lld)",
523                         p_vpar->synchro.current_pts
524                             - p_vpar->sequence.next_pts );
525             }
526             p_vpar->synchro.current_pts = p_vpar->sequence.next_pts;
527             p_vpar->sequence.next_pts = 0;
528         }
529     }
530     else
531     {
532         p_vpar->synchro.i_current_period = p_vpar->synchro.i_backward_period;
533         p_vpar->synchro.i_backward_period = i_repeat_field;
534
535         if( p_vpar->synchro.backward_pts )
536         {
537             if( p_vpar->sequence.next_dts && 
538                 (p_vpar->sequence.next_dts - p_vpar->synchro.backward_pts
539                     > PTS_THRESHOLD
540               || p_vpar->synchro.backward_pts - p_vpar->sequence.next_dts
541                     > PTS_THRESHOLD) )
542             {
543                 intf_WarnMsg( 2,
544                         "vpar synchro warning: backward_pts != dts (%lld)",
545                         p_vpar->sequence.next_dts
546                             - p_vpar->synchro.backward_pts );
547             }
548             if( p_vpar->synchro.backward_pts - p_vpar->synchro.current_pts
549                     > PTS_THRESHOLD
550                  || p_vpar->synchro.current_pts - p_vpar->synchro.backward_pts
551                     > PTS_THRESHOLD )
552             {
553                 intf_WarnMsg( 2,
554                    "vpar synchro warning: backward_pts != current_pts (%lld)",
555                    p_vpar->synchro.current_pts - p_vpar->synchro.backward_pts );
556             }
557             p_vpar->synchro.current_pts = p_vpar->synchro.backward_pts;
558             p_vpar->synchro.backward_pts = 0;
559         }
560         else if( p_vpar->sequence.next_dts )
561         {
562             if( p_vpar->sequence.next_dts - p_vpar->synchro.current_pts
563                     > PTS_THRESHOLD
564                  || p_vpar->synchro.current_pts - p_vpar->sequence.next_dts
565                     > PTS_THRESHOLD )
566             {
567                 intf_WarnMsg( 2,
568                         "vpar synchro warning: dts != current_pts (%lld)",
569                         p_vpar->synchro.current_pts
570                             - p_vpar->sequence.next_dts );
571             }
572             /* By definition of a DTS. */
573             p_vpar->synchro.current_pts = p_vpar->sequence.next_dts;
574             p_vpar->sequence.next_dts = 0;
575         }
576
577         if( p_vpar->sequence.next_pts )
578         {
579             /* Store the PTS for the next time we have to date an I picture. */
580             p_vpar->synchro.backward_pts = p_vpar->sequence.next_pts;
581             p_vpar->sequence.next_pts = 0;
582         }
583     }
584 #undef PTS_THRESHOLD
585
586 #if 0
587     /* Removed for incompatibility with slow motion */
588     if( p_vpar->synchro.current_pts + DEFAULT_PTS_DELAY < now )
589     {
590         /* We cannot be _that_ late, something must have happened, reinit
591          * the dates. */
592         intf_WarnMsg( 2, "PTS << now (%lld), resetting",
593                       now - p_vpar->synchro.current_pts - DEFAULT_PTS_DELAY );
594         p_vpar->synchro.current_pts = now + DEFAULT_PTS_DELAY;
595     }
596     if( p_vpar->synchro.backward_pts
597          && p_vpar->synchro.backward_pts + DEFAULT_PTS_DELAY < now )
598     {
599         /* The same. */
600         p_vpar->synchro.backward_pts = 0;
601     }
602 #endif
603
604 #ifdef STATS
605     p_vpar->synchro.i_pic++;
606 #endif
607 }
608
609 /*****************************************************************************
610  * SynchroType: Get the user's synchro type
611  *****************************************************************************
612  * This function is called at initialization.
613  *****************************************************************************/
614 static int SynchroType( void )
615 {
616     char * psz_synchro = main_GetPszVariable( VPAR_SYNCHRO_VAR, NULL );
617
618     if( psz_synchro == NULL )
619     {
620         return VPAR_SYNCHRO_DEFAULT;
621     }
622
623     switch( *psz_synchro++ )
624     {
625       case 'i':
626       case 'I':
627         switch( *psz_synchro++ )
628         {
629           case '\0':
630             return VPAR_SYNCHRO_I;
631
632           case '+':
633             if( *psz_synchro ) return 0;
634             return VPAR_SYNCHRO_Iplus;
635
636           case 'p':
637           case 'P':
638             switch( *psz_synchro++ )
639             {
640               case '\0':
641                 return VPAR_SYNCHRO_IP;
642
643               case '+':
644                 if( *psz_synchro ) return 0;
645                 return VPAR_SYNCHRO_IPplus;
646
647               case 'b':
648               case 'B':
649                 if( *psz_synchro ) return 0;
650                 return VPAR_SYNCHRO_IPB;
651
652               default:
653                 return VPAR_SYNCHRO_DEFAULT;
654                 
655             }
656
657           default:
658             return VPAR_SYNCHRO_DEFAULT;
659         }
660     }
661
662     return VPAR_SYNCHRO_DEFAULT;
663 }
664