1999-12-05 02:03:40 +00:00
/*
* Asterisk - - A telephony toolkit for Linux .
*
* Translate via the use of pseudo channels
*
2004-12-06 21:53:57 +00:00
* Copyright ( C ) 1999 - 2004 , Digium , Inc .
1999-12-05 02:03:40 +00:00
*
2004-12-06 21:53:57 +00:00
* Mark Spencer < markster @ digium . com >
1999-12-05 02:03:40 +00:00
*
* This program is free software , distributed under the terms of
* the GNU General Public License
*/
2002-05-18 02:35:06 +00:00
# include <asterisk/lock.h>
1999-12-05 02:03:40 +00:00
# include <asterisk/channel.h>
# include <asterisk/channel_pvt.h>
# include <asterisk/logger.h>
# include <asterisk/translate.h>
# include <asterisk/options.h>
2000-01-05 20:03:55 +00:00
# include <asterisk/frame.h>
# include <asterisk/sched.h>
# include <asterisk/cli.h>
2002-05-18 02:35:06 +00:00
# include <asterisk/term.h>
1999-12-05 02:03:40 +00:00
# include <sys/socket.h>
# include <sys/time.h>
# include <unistd.h>
# include <stdlib.h>
# include <string.h>
# include <stdio.h>
2004-05-17 22:59:27 +00:00
# define MAX_RECALC 200 /* max sample recalc */
2000-01-05 20:03:55 +00:00
/* This could all be done more efficiently *IF* we chained packets together
by default , but it would also complicate virtually every application . */
2004-06-09 01:45:08 +00:00
AST_MUTEX_DEFINE_STATIC ( list_lock ) ;
1999-12-05 02:03:40 +00:00
static struct ast_translator * list = NULL ;
struct ast_translator_dir {
struct ast_translator * step ; /* Next step translator */
int cost ; /* Complete cost to destination */
} ;
2000-01-05 20:03:55 +00:00
struct ast_frame_delivery {
struct ast_frame * f ;
struct ast_channel * chan ;
int fd ;
struct translator_pvt * owner ;
struct ast_frame_delivery * prev ;
struct ast_frame_delivery * next ;
} ;
1999-12-05 02:03:40 +00:00
static struct ast_translator_dir tr_matrix [ MAX_FORMAT ] [ MAX_FORMAT ] ;
struct ast_trans_pvt {
struct ast_translator * step ;
struct ast_translator_pvt * state ;
struct ast_trans_pvt * next ;
2004-04-05 19:45:53 +00:00
struct timeval nextin ;
struct timeval nextout ;
1999-12-05 02:03:40 +00:00
} ;
static int powerof ( int d )
{
int x ;
for ( x = 0 ; x < 32 ; x + + )
if ( ( 1 < < x ) & d )
return x ;
ast_log ( LOG_WARNING , " Powerof %d: No power?? \n " , d ) ;
return - 1 ;
}
void ast_translator_free_path ( struct ast_trans_pvt * p )
{
2004-03-20 10:55:57 +00:00
struct ast_trans_pvt * pl , * pn ;
pn = p ;
while ( pn ) {
pl = pn ;
pn = pn - > next ;
1999-12-05 02:03:40 +00:00
if ( pl - > state & & pl - > step - > destroy )
pl - > step - > destroy ( pl - > state ) ;
free ( pl ) ;
}
}
2001-03-22 04:20:13 +00:00
struct ast_trans_pvt * ast_translator_build_path ( int dest , int source )
1999-12-05 02:03:40 +00:00
{
struct ast_trans_pvt * tmpr = NULL , * tmp = NULL ;
/* One of the hardest parts: Build a set of translators based upon
the given source and destination formats */
source = powerof ( source ) ;
dest = powerof ( dest ) ;
while ( source ! = dest ) {
if ( tr_matrix [ source ] [ dest ] . step ) {
if ( tmp ) {
tmp - > next = malloc ( sizeof ( struct ast_trans_pvt ) ) ;
tmp = tmp - > next ;
} else
tmp = malloc ( sizeof ( struct ast_trans_pvt ) ) ;
if ( tmp ) {
tmp - > next = NULL ;
2004-04-05 19:45:53 +00:00
tmp - > nextin . tv_sec = 0 ;
tmp - > nextin . tv_usec = 0 ;
tmp - > nextout . tv_sec = 0 ;
tmp - > nextout . tv_usec = 0 ;
1999-12-05 02:03:40 +00:00
tmp - > step = tr_matrix [ source ] [ dest ] . step ;
2004-08-01 14:19:04 +00:00
tmp - > state = tmp - > step - > newpvt ( ) ;
1999-12-05 02:03:40 +00:00
if ( ! tmp - > state ) {
2002-05-18 02:35:06 +00:00
ast_log ( LOG_WARNING , " Failed to build translator step from %d to %d \n " , source , dest ) ;
1999-12-05 02:03:40 +00:00
free ( tmp ) ;
tmp = NULL ;
2001-05-29 12:55:04 +00:00
return NULL ;
1999-12-05 02:03:40 +00:00
}
/* Set the root, if it doesn't exist yet... */
if ( ! tmpr )
tmpr = tmp ;
/* Keep going if this isn't the final destination */
source = tmp - > step - > dstfmt ;
} else {
/* XXX This could leak XXX */
ast_log ( LOG_WARNING , " Out of memory \n " ) ;
return NULL ;
}
2000-01-05 20:03:55 +00:00
} else {
/* We shouldn't have allocated any memory */
2003-08-16 05:10:35 +00:00
ast_log ( LOG_WARNING , " No translator path from %s to %s \n " ,
ast_getformatname ( source ) , ast_getformatname ( dest ) ) ;
2000-01-05 20:03:55 +00:00
return NULL ;
1999-12-05 02:03:40 +00:00
}
}
return tmpr ;
}
2001-03-22 04:20:13 +00:00
struct ast_frame * ast_translate ( struct ast_trans_pvt * path , struct ast_frame * f , int consume )
1999-12-05 02:03:40 +00:00
{
struct ast_trans_pvt * p ;
struct ast_frame * out ;
2004-04-20 15:28:58 +00:00
struct timeval delivery ;
1999-12-05 02:03:40 +00:00
p = path ;
/* Feed the first frame into the first translator */
p - > step - > framein ( p - > state , f ) ;
2004-04-05 20:47:44 +00:00
if ( f - > delivery . tv_sec | | f - > delivery . tv_usec ) {
if ( path - > nextin . tv_sec | | path - > nextin . tv_usec ) {
/* Make sure this is in line with what we were expecting */
if ( ( path - > nextin . tv_sec ! = f - > delivery . tv_sec ) | |
( path - > nextin . tv_usec ! = f - > delivery . tv_usec ) ) {
/* The time has changed between what we expected and this
most recent time on the new packet . Adjust our output
time appropriately */
long sdiff ;
long udiff ;
sdiff = f - > delivery . tv_sec - path - > nextin . tv_sec ;
udiff = f - > delivery . tv_usec - path - > nextin . tv_usec ;
path - > nextin . tv_sec = f - > delivery . tv_sec ;
path - > nextin . tv_usec = f - > delivery . tv_usec ;
path - > nextout . tv_sec + = sdiff ;
path - > nextout . tv_usec + = udiff ;
if ( path - > nextout . tv_usec < 0 ) {
path - > nextout . tv_usec + = 1000000 ;
path - > nextout . tv_sec - - ;
} else if ( path - > nextout . tv_usec > = 1000000 ) {
path - > nextout . tv_usec - = 1000000 ;
path - > nextout . tv_sec + + ;
}
}
} else {
/* This is our first pass. Make sure the timing looks good */
2004-04-05 19:45:53 +00:00
path - > nextin . tv_sec = f - > delivery . tv_sec ;
path - > nextin . tv_usec = f - > delivery . tv_usec ;
2004-04-05 20:47:44 +00:00
path - > nextout . tv_sec = f - > delivery . tv_sec ;
path - > nextout . tv_usec = f - > delivery . tv_usec ;
}
/* Predict next incoming sample */
path - > nextin . tv_sec + = ( f - > samples / 8000 ) ;
path - > nextin . tv_usec + = ( ( f - > samples % 8000 ) * 125 ) ;
if ( path - > nextin . tv_usec > = 1000000 ) {
path - > nextin . tv_usec - = 1000000 ;
path - > nextin . tv_sec + + ;
2004-04-05 19:45:53 +00:00
}
}
2004-04-20 15:28:58 +00:00
delivery . tv_sec = f - > delivery . tv_sec ;
delivery . tv_usec = f - > delivery . tv_usec ;
2001-03-22 04:20:13 +00:00
if ( consume )
ast_frfree ( f ) ;
1999-12-05 02:03:40 +00:00
while ( p ) {
2001-03-22 04:20:13 +00:00
out = p - > step - > frameout ( p - > state ) ;
/* If we get nothing out, return NULL */
if ( ! out )
return NULL ;
/* If there is a next state, feed it in there. If not,
return this frame */
if ( p - > next )
p - > next - > step - > framein ( p - > next - > state , out ) ;
2004-03-19 15:03:43 +00:00
else {
2004-04-20 15:28:58 +00:00
if ( delivery . tv_sec | | delivery . tv_usec ) {
2004-04-05 20:47:44 +00:00
/* Use next predicted outgoing timestamp */
out - > delivery . tv_sec = path - > nextout . tv_sec ;
out - > delivery . tv_usec = path - > nextout . tv_usec ;
/* Predict next outgoing timestamp from samples in this
frame . */
path - > nextout . tv_sec + = ( out - > samples / 8000 ) ;
path - > nextout . tv_usec + = ( ( out - > samples % 8000 ) * 125 ) ;
if ( path - > nextout . tv_usec > = 1000000 ) {
path - > nextout . tv_sec + + ;
path - > nextout . tv_usec - = 1000000 ;
}
} else {
out - > delivery . tv_sec = 0 ;
out - > delivery . tv_usec = 0 ;
2004-04-05 19:45:53 +00:00
}
2001-03-22 04:20:13 +00:00
return out ;
2004-03-19 15:03:43 +00:00
}
1999-12-05 02:03:40 +00:00
p = p - > next ;
}
2001-03-22 04:20:13 +00:00
ast_log ( LOG_WARNING , " I should never get here... \n " ) ;
1999-12-05 02:03:40 +00:00
return NULL ;
}
2004-05-17 22:59:27 +00:00
static void calc_cost ( struct ast_translator * t , int samples )
{
int sofar = 0 ;
struct ast_translator_pvt * pvt ;
struct ast_frame * f , * out ;
struct timeval start , finish ;
int cost ;
if ( ! samples )
samples = 1 ;
/* If they don't make samples, give them a terrible score */
if ( ! t - > sample ) {
ast_log ( LOG_WARNING , " Translator '%s' does not produce sample frames. \n " , t - > name ) ;
t - > cost = 99999 ;
return ;
}
2004-08-01 14:19:04 +00:00
pvt = t - > newpvt ( ) ;
2004-05-17 22:59:27 +00:00
if ( ! pvt ) {
ast_log ( LOG_WARNING , " Translator '%s' appears to be broken and will probably fail. \n " , t - > name ) ;
t - > cost = 99999 ;
return ;
}
gettimeofday ( & start , NULL ) ;
/* Call the encoder until we've processed one second of time */
while ( sofar < samples * 8000 ) {
f = t - > sample ( ) ;
if ( ! f ) {
ast_log ( LOG_WARNING , " Translator '%s' failed to produce a sample frame. \n " , t - > name ) ;
t - > destroy ( pvt ) ;
t - > cost = 99999 ;
return ;
}
t - > framein ( pvt , f ) ;
ast_frfree ( f ) ;
while ( ( out = t - > frameout ( pvt ) ) ) {
sofar + = out - > samples ;
ast_frfree ( out ) ;
}
}
gettimeofday ( & finish , NULL ) ;
t - > destroy ( pvt ) ;
cost = ( finish . tv_sec - start . tv_sec ) * 1000 + ( finish . tv_usec - start . tv_usec ) / 1000 ;
t - > cost = cost / samples ;
if ( ! t - > cost )
t - > cost = 1 ;
}
static void rebuild_matrix ( int samples )
1999-12-05 02:03:40 +00:00
{
struct ast_translator * t ;
int changed ;
int x , y , z ;
if ( option_debug )
ast_log ( LOG_DEBUG , " Reseting translation matrix \n " ) ;
/* Use the list of translators to build a translation matrix */
bzero ( tr_matrix , sizeof ( tr_matrix ) ) ;
t = list ;
while ( t ) {
2004-05-17 22:59:27 +00:00
if ( samples )
calc_cost ( t , samples ) ;
1999-12-05 02:03:40 +00:00
if ( ! tr_matrix [ t - > srcfmt ] [ t - > dstfmt ] . step | |
tr_matrix [ t - > srcfmt ] [ t - > dstfmt ] . cost > t - > cost ) {
tr_matrix [ t - > srcfmt ] [ t - > dstfmt ] . step = t ;
tr_matrix [ t - > srcfmt ] [ t - > dstfmt ] . cost = t - > cost ;
}
t = t - > next ;
}
do {
changed = 0 ;
/* Don't you just love O(N^3) operations? */
for ( x = 0 ; x < MAX_FORMAT ; x + + ) /* For each source format */
for ( y = 0 ; y < MAX_FORMAT ; y + + ) /* And each destination format */
if ( x ! = y ) /* Except ourselves, of course */
for ( z = 0 ; z < MAX_FORMAT ; z + + ) /* And each format it might convert to */
if ( ( x ! = z ) & & ( y ! = z ) ) /* Don't ever convert back to us */
if ( tr_matrix [ x ] [ y ] . step & & /* We can convert from x to y */
tr_matrix [ y ] [ z ] . step & & /* And from y to z and... */
( ! tr_matrix [ x ] [ z ] . step | | /* Either there isn't an x->z conversion */
( tr_matrix [ x ] [ y ] . cost +
tr_matrix [ y ] [ z ] . cost < /* Or we're cheaper than the existing */
tr_matrix [ x ] [ z ] . cost ) /* solution */
) ) {
/* We can get from x to z via y with a cost that
is the sum of the transition from x to y and
from y to z */
tr_matrix [ x ] [ z ] . step = tr_matrix [ x ] [ y ] . step ;
tr_matrix [ x ] [ z ] . cost = tr_matrix [ x ] [ y ] . cost +
tr_matrix [ y ] [ z ] . cost ;
if ( option_debug )
2003-08-16 05:10:35 +00:00
ast_log ( LOG_DEBUG , " Discovered %d cost path from %s to %s, via %d \n " , tr_matrix [ x ] [ z ] . cost , ast_getformatname ( x ) , ast_getformatname ( z ) , y ) ;
1999-12-05 02:03:40 +00:00
changed + + ;
}
} while ( changed ) ;
}
2004-05-17 22:59:27 +00:00
1999-12-05 02:03:40 +00:00
2000-01-05 20:03:55 +00:00
static int show_translation ( int fd , int argc , char * argv [ ] )
{
2003-08-16 05:10:35 +00:00
# define SHOW_TRANS 11
2004-06-22 20:11:15 +00:00
int x , y , z ;
2000-01-05 20:03:55 +00:00
char line [ 80 ] ;
2004-05-17 22:59:27 +00:00
if ( argc > 4 )
2000-01-05 20:03:55 +00:00
return RESULT_SHOWUSAGE ;
2004-05-17 22:59:27 +00:00
2004-06-22 20:11:15 +00:00
if ( argv [ 2 ] & & ! strcasecmp ( argv [ 2 ] , " recalc " ) ) {
z = argv [ 3 ] ? atoi ( argv [ 3 ] ) : 1 ;
2004-05-17 22:59:27 +00:00
2004-06-22 20:11:15 +00:00
if ( z < = 0 ) {
ast_cli ( fd , " C'mon let's be serious here... defaulting to 1. \n " ) ;
z = 1 ;
}
2004-05-17 22:59:27 +00:00
2004-06-22 20:11:15 +00:00
if ( z > MAX_RECALC ) {
ast_cli ( fd , " Maximum limit of recalc exceeded by %d, truncating value to %d \n " , z - MAX_RECALC , MAX_RECALC ) ;
z = MAX_RECALC ;
}
ast_cli ( fd , " Recalculating Codec Translation (number of sample seconds: %d) \n \n " , z ) ;
rebuild_matrix ( z ) ;
2004-05-17 22:59:27 +00:00
}
2003-08-16 05:10:35 +00:00
ast_cli ( fd , " Translation times between formats (in milliseconds) \n " ) ;
ast_cli ( fd , " Source Format (Rows) Destination Format(Columns) \n \n " ) ;
2003-08-13 15:25:16 +00:00
ast_mutex_lock ( & list_lock ) ;
2003-08-16 05:10:35 +00:00
for ( x = - 1 ; x < SHOW_TRANS ; x + + ) {
2004-07-14 07:44:19 +00:00
/* next 2 lines run faster than using strcpy() */
line [ 0 ] = ' ' ;
line [ 1 ] = ' \0 ' ;
2003-08-16 05:10:35 +00:00
for ( y = - 1 ; y < SHOW_TRANS ; y + + ) {
2004-02-25 04:24:51 +00:00
if ( x > = 0 & & y > = 0 & & tr_matrix [ x ] [ y ] . step )
2004-02-26 01:12:38 +00:00
snprintf ( line + strlen ( line ) , sizeof ( line ) - strlen ( line ) , " %5d " , tr_matrix [ x ] [ y ] . cost > = 99999 ? tr_matrix [ x ] [ y ] . cost - 99999 : tr_matrix [ x ] [ y ] . cost ) ;
2000-01-05 20:03:55 +00:00
else
2004-02-25 04:24:51 +00:00
if ( ( ( x = = - 1 & & y > = 0 ) | | ( y = = - 1 & & x > = 0 ) ) ) {
2003-08-16 05:10:35 +00:00
snprintf ( line + strlen ( line ) , sizeof ( line ) - strlen ( line ) ,
2004-02-25 04:24:51 +00:00
" %5s " , ast_getformatname ( 1 < < ( x + y + 1 ) ) ) ;
2004-02-26 01:12:38 +00:00
} else if ( x ! = - 1 & & y ! = - 1 ) {
2004-02-25 04:24:51 +00:00
snprintf ( line + strlen ( line ) , sizeof ( line ) - strlen ( line ) , " - " ) ;
2004-02-26 01:12:38 +00:00
} else {
2004-02-25 04:24:51 +00:00
snprintf ( line + strlen ( line ) , sizeof ( line ) - strlen ( line ) , " " ) ;
2003-08-16 05:10:35 +00:00
}
2000-01-05 20:03:55 +00:00
}
snprintf ( line + strlen ( line ) , sizeof ( line ) - strlen ( line ) , " \n " ) ;
ast_cli ( fd , line ) ;
}
2003-08-13 15:25:16 +00:00
ast_mutex_unlock ( & list_lock ) ;
2000-01-05 20:03:55 +00:00
return RESULT_SUCCESS ;
}
static int added_cli = 0 ;
static char show_trans_usage [ ] =
2004-05-17 22:59:27 +00:00
" Usage: show translation [recalc] [<recalc seconds>] \n "
2000-01-05 20:03:55 +00:00
" Displays known codec translators and the cost associated \n "
2004-05-17 22:59:27 +00:00
" with each conversion. if the arguement 'recalc' is supplied along \n "
" with optional number of seconds to test a new test will be performed \n "
" as the chart is being displayed. \n " ;
2000-01-05 20:03:55 +00:00
static struct ast_cli_entry show_trans =
{ { " show " , " translation " , NULL } , show_translation , " Display translation matrix " , show_trans_usage } ;
1999-12-05 02:03:40 +00:00
int ast_register_translator ( struct ast_translator * t )
{
2002-05-18 02:35:06 +00:00
char tmp [ 80 ] ;
1999-12-05 02:03:40 +00:00
t - > srcfmt = powerof ( t - > srcfmt ) ;
t - > dstfmt = powerof ( t - > dstfmt ) ;
if ( ( t - > srcfmt > = MAX_FORMAT ) | | ( t - > dstfmt > = MAX_FORMAT ) ) {
2003-08-16 05:10:35 +00:00
ast_log ( LOG_WARNING , " Format %s is larger than MAX_FORMAT \n " , ast_getformatname ( t - > srcfmt ) ) ;
1999-12-05 02:03:40 +00:00
return - 1 ;
}
2004-05-17 22:59:27 +00:00
calc_cost ( t , 1 ) ;
1999-12-05 02:03:40 +00:00
if ( option_verbose > 1 )
2003-08-16 15:47:53 +00:00
ast_verbose ( VERBOSE_PREFIX_2 " Registered translator '%s' from format %s to %s, cost %d \n " , term_color ( tmp , t - > name , COLOR_MAGENTA , COLOR_BLACK , sizeof ( tmp ) ) , ast_getformatname ( 1 < < t - > srcfmt ) , ast_getformatname ( 1 < < t - > dstfmt ) , t - > cost ) ;
2003-08-13 15:25:16 +00:00
ast_mutex_lock ( & list_lock ) ;
2000-01-05 20:03:55 +00:00
if ( ! added_cli ) {
ast_cli_register ( & show_trans ) ;
added_cli + + ;
}
1999-12-05 02:03:40 +00:00
t - > next = list ;
list = t ;
2004-05-17 22:59:27 +00:00
rebuild_matrix ( 0 ) ;
2003-08-13 15:25:16 +00:00
ast_mutex_unlock ( & list_lock ) ;
1999-12-05 02:03:40 +00:00
return 0 ;
}
int ast_unregister_translator ( struct ast_translator * t )
{
2004-04-21 04:26:38 +00:00
char tmp [ 80 ] ;
1999-12-05 02:03:40 +00:00
struct ast_translator * u , * ul = NULL ;
2003-08-13 15:25:16 +00:00
ast_mutex_lock ( & list_lock ) ;
1999-12-05 02:03:40 +00:00
u = list ;
while ( u ) {
if ( u = = t ) {
if ( ul )
ul - > next = u - > next ;
else
list = u - > next ;
2004-04-21 04:26:38 +00:00
if ( option_verbose > 1 )
ast_verbose ( VERBOSE_PREFIX_2 " Unregistered translator '%s' from format %s to %s \n " , term_color ( tmp , t - > name , COLOR_MAGENTA , COLOR_BLACK , sizeof ( tmp ) ) , ast_getformatname ( 1 < < t - > srcfmt ) , ast_getformatname ( 1 < < t - > dstfmt ) ) ;
1999-12-05 02:03:40 +00:00
break ;
}
2000-01-05 20:03:55 +00:00
ul = u ;
1999-12-05 02:03:40 +00:00
u = u - > next ;
}
2004-05-17 22:59:27 +00:00
rebuild_matrix ( 0 ) ;
2003-08-13 15:25:16 +00:00
ast_mutex_unlock ( & list_lock ) ;
1999-12-05 02:03:40 +00:00
return ( u ? 0 : - 1 ) ;
}
2001-03-22 04:20:13 +00:00
int ast_translator_best_choice ( int * dst , int * srcs )
1999-12-05 02:03:40 +00:00
{
/* Calculate our best source format, given costs, and a desired destination */
2000-01-05 20:03:55 +00:00
int x , y ;
1999-12-05 02:03:40 +00:00
int best = - 1 ;
2001-03-22 04:20:13 +00:00
int bestdst = 0 ;
2000-01-05 20:03:55 +00:00
int cur = 1 ;
1999-12-05 02:03:40 +00:00
int besttime = 999999999 ;
2004-12-06 21:53:57 +00:00
if ( ( * dst ) & ( * srcs ) ) {
/* We have a format in common */
for ( y = 0 ; y < MAX_FORMAT ; y + + ) {
if ( ( cur & * dst ) & & ( cur & * srcs ) ) {
/* This is a common format to both. Pick it if we don't have one already */
besttime = 0 ;
bestdst = cur ;
best = cur ;
1999-12-05 02:03:40 +00:00
}
2004-12-06 21:53:57 +00:00
cur = cur < < 1 ;
}
} else {
/* We will need to translate */
ast_mutex_lock ( & list_lock ) ;
for ( y = 0 ; y < MAX_FORMAT ; y + + ) {
if ( cur & * dst )
for ( x = 0 ; x < MAX_FORMAT ; x + + ) {
if ( tr_matrix [ x ] [ y ] . step & & /* There's a step */
( tr_matrix [ x ] [ y ] . cost < besttime ) & & /* We're better than what exists now */
( * srcs & ( 1 < < x ) ) ) /* x is a valid source format */
{
best = 1 < < x ;
bestdst = cur ;
besttime = tr_matrix [ x ] [ y ] . cost ;
}
}
cur = cur < < 1 ;
}
ast_mutex_unlock ( & list_lock ) ;
1999-12-05 02:03:40 +00:00
}
2001-03-22 04:20:13 +00:00
if ( best > - 1 ) {
* srcs = best ;
* dst = bestdst ;
best = 0 ;
}
1999-12-05 02:03:40 +00:00
return best ;
}