2010-08-18 21:49:24 +08:00
/*
* fxotune . c - - A utility for tuning the various settings on the fxo
* modules for the TDM400 cards .
*
* by Matthew Fredrickson < creslin @ digium . com >
*
* ( C ) 2004 - 2008 Digium , Inc .
*/
/*
* See http : //www.asterisk.org for more information about
* the Asterisk project . Please do not directly contact
* any of the maintainers of this project for assistance ;
* the project provides a web site , mailing lists and IRC
* channels for your use .
*
* This program is free software , distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation . See the LICENSE file included with
* this program for more details .
*/
# include <stdio.h>
# include <stdlib.h>
# include <errno.h>
# include <string.h>
# include <sys/ioctl.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
# include <fcntl.h>
# include <math.h>
# include <sys/time.h>
# include <dahdi/user.h>
# include <dahdi/wctdm_user.h>
# include "dahdi_tools_version.h"
# include "fxotune.h"
# define TEST_DURATION 2000
# define BUFFER_LENGTH (2 * TEST_DURATION)
# define SKIP_SAMPLES 800
# define SINE_SAMPLES 8000
static float sintable [ SINE_SAMPLES ] ;
static const float amplitude = 16384.0 ;
static char * configfile = " /etc/fxotune.conf " ;
static int audio_dump_fd = - 1 ;
static int printbest = 0 ;
# define MAX_RESULTS (5)
struct result_catalog {
int idx ;
float echo ;
float freqres ;
struct wctdm_echo_coefs settings ;
} ;
struct {
struct result_catalog results [ MAX_RESULTS ] ;
int numactive ;
} topresults ;
static char * usage =
" Usage: fxotune [-v[vv] (-s | -i <options> | -d <options>) \n "
" \n "
" -s : set previously calibrated echo settings \n "
" -i : calibrate echo settings \n "
" options : [<dialstring>] [-t <calibtype>] \n "
" [-b <startdev>][-e <stopdev>] \n "
" [-n <dialstring>][-l <delaytosilence>][-m <silencegoodfor>] \n "
" -d : dump input and output waveforms to ./fxotune_dump.vals \n "
" options : [-b <device>][-w <waveform>] \n "
" [-n <dialstring>][-l <delaytosilence>][-m <silencegoodfor>] \n "
" -v : more output (-vv, -vvv also) \n "
" -p : print the 5 best candidates for acim and coefficients settings \n "
" -x : Perform sin/cos functions using table lookup \n "
" -o <path> : Write the received raw 16-bit signed linear audio that is \n "
" used in processing to the file specified by <path> \n "
" -c <config_file> \n "
" \n "
" <calibtype> - type of calibration \n "
" (default 2, old method 1) \n "
" <startdev> \n "
" <stopdev> - defines a range of devices to test \n "
" (default: 1-252) \n "
" <dialstring> - string to dial to clear the line \n "
" (default 5) \n "
" <delaytosilence> - seconds to wait for line to clear (default 0) \n "
" <silencegoodfor> - seconds before line will no longer be clear \n "
" (default 18) \n "
" <device> - the device to perform waveform dump on \n "
" (default 1) \n "
" <waveform> - -1 for multitone waveform, or frequency of \n "
" single tone (default -1) \n "
" <config_file> - Alternative file to set from / calibrate to. \n "
" (Default: /etc/fxotune.conf) \n "
;
# define OUT_OF_BOUNDS(x) ((x) < 0 || (x) > 255)
struct silence_info {
char * dialstr ;
/** fd of device we are working with */
int device ;
/** seconds we should wait after dialing the dialstring before we know for sure we'll have silence */
int initial_delay ;
/** seconds after which a reset should occur */
int reset_after ;
/** time of last reset */
struct timeval last_reset ;
} ;
static short outbuf [ TEST_DURATION ] ;
static int debug = 0 ;
static FILE * debugoutfile = NULL ;
static int use_table = 0 ;
static int fxotune_read ( int fd , void * buffer , int len )
{
int res ;
res = read ( fd , buffer , len ) ;
if ( ( res > 0 ) & & ( audio_dump_fd ! = - 1 ) ) {
res = write ( audio_dump_fd , buffer , len ) ;
}
return res ;
}
/**
* Makes sure that the line is clear .
* Right now , we do this by relying on the user to specify how long after dialing the
* dialstring we can rely on the line being silent ( before the telco complains about
* the user not hitting the next digit ) .
*
* A more robust way to do this would be to actually measure the sound levels on the line ,
* but that ' s a lot more complicated , and this should work .
*
* @ return 0 if succesful ( no errors ) , 1 if unsuccesful
*/
static int ensure_silence ( struct silence_info * info )
{
struct timeval tv ;
long int elapsedms ;
int x = DAHDI_ONHOOK ;
struct dahdi_dialoperation dop ;
gettimeofday ( & tv , NULL ) ;
if ( info - > last_reset . tv_sec = = 0 ) {
/* this is the first request, we will force it to run */
elapsedms = - 1 ;
} else {
/* this is not the first request, we will compute elapsed time */
elapsedms = ( ( tv . tv_sec - info - > last_reset . tv_sec ) * 1000L + ( tv . tv_usec - info - > last_reset . tv_usec ) / 1000L ) ;
}
if ( debug > 4 ) {
fprintf ( stdout , " Reset line request received - elapsed ms = %li / reset after = %ld \n " , elapsedms , info - > reset_after * 1000L ) ;
}
if ( elapsedms > 0 & & elapsedms < info - > reset_after * 1000L )
return 0 ;
if ( debug > 1 ) {
fprintf ( stdout , " Resetting line \n " ) ;
}
/* do a line reset */
/* prepare line for silence */
/* Do line hookstate reset */
if ( ioctl ( info - > device , DAHDI_HOOK , & x ) ) {
fprintf ( stderr , " Unable to hang up fd %d \n " , info - > device ) ;
return - 1 ;
}
sleep ( 2 ) ;
x = DAHDI_OFFHOOK ;
if ( ioctl ( info - > device , DAHDI_HOOK , & x ) ) {
fprintf ( stderr , " Cannot bring fd %d off hook \n " , info - > device ) ;
return - 1 ;
}
sleep ( 2 ) ; /* Added to ensure that dial can actually takes place */
memset ( & dop , 0 , sizeof ( dop ) ) ;
dop . op = DAHDI_DIAL_OP_REPLACE ;
dop . dialstr [ 0 ] = ' T ' ;
dahdi_copy_string ( dop . dialstr + 1 , info - > dialstr , sizeof ( dop . dialstr ) ) ;
if ( ioctl ( info - > device , DAHDI_DIAL , & dop ) ) {
fprintf ( stderr , " Unable to dial! \n " ) ;
return - 1 ;
}
sleep ( 1 ) ;
sleep ( info - > initial_delay ) ;
gettimeofday ( & info - > last_reset , NULL ) ;
return 0 ;
}
/**
* Generates a tone of specified frequency .
*
* @ param hz the frequency of the tone to be generated
* @ param idx the current sample
* to begenerated . For a normal waveform you need to increment
* this every time you execute the function .
*
* @ return 16 bit slinear sample for the specified index
*/
static short inline gentone ( int hz , int idx )
{
return amplitude * sin ( ( idx * 2.0 * M_PI * hz ) / 8000 ) ;
}
/* Using DTMF tones for now since they provide good mid band testing
* while not being harmonics of each other */
static int freqs [ ] = { 697 , 770 , 941 , 1209 , 1336 , 1633 } ;
static int freqcount = 6 ;
/**
* Generates a waveform of several frequencies .
*
* @ param idx the current sample
* to begenerated . For a normal waveform you need to increment
* this every time you execute the function .
*
* @ return 16 bit slinear sample for the specified index
*/
static short inline genwaveform ( int idx )
{
int i = 0 ;
float response = ( float ) 0 ;
for ( i = 0 ; i < freqcount ; i + + ) {
response + = sin ( ( idx * 2.0 * M_PI * freqs [ i ] ) / 8000 ) ;
}
return amplitude * response / freqcount ;
}
/**
* Calculates the RMS of the waveform buffer of samples in 16 bit slinear format .
* prebuf the buffer of either shorts or floats
* bufsize the number of elements in the prebuf buffer ( not the number of bytes ! )
* short_format 1 if prebuf points to an array of shorts , 0 if it points to an array of floats
*
* Formula for RMS ( http : //en.wikipedia.org/wiki/Root_mean_square):
*
* Xrms = sqrt ( 1 / N Sum ( x1 ^ 2 , x2 ^ 2 , . . . , xn ^ 2 ) )
*
* Note : this isn ' t really a power calculation - but it gives a good measure of the level of the response
*
* @ param prebuf the buffer containing the values to compute
* @ param bufsize the size of the buffer
* @ param short_format 1 if prebuf contains short values , 0 if it contains float values
*/
static float power_of ( void * prebuf , int bufsize , int short_format )
{
float sum_of_squares = 0 ;
int numsamples = 0 ;
float finalanswer = 0 ;
short * sbuf = ( short * ) prebuf ;
float * fbuf = ( float * ) prebuf ;
int i = 0 ;
if ( short_format ) {
/* idiot proof checks */
if ( bufsize < = 0 )
return - 1 ;
numsamples = bufsize ; /* Got rid of divide by 2 - the bufsize parameter should give the number of samples (that's what it does for the float computation, and it should do it here as well) */
for ( i = 0 ; i < numsamples ; i + + ) {
sum_of_squares + = ( ( float ) sbuf [ i ] * ( float ) sbuf [ i ] ) ;
}
} else {
/* Version for float inputs */
for ( i = 0 ; i < bufsize ; i + + ) {
sum_of_squares + = ( fbuf [ i ] * fbuf [ i ] ) ;
}
}
finalanswer = sum_of_squares / ( float ) bufsize ; /* need to divide by the number of elements in the sample for RMS calc */
if ( finalanswer < 0 ) {
fprintf ( stderr , " Error: Final answer negative number %f \n " , finalanswer ) ;
return - 3 ;
}
return sqrtf ( finalanswer ) ;
}
/*
* In an effort to eliminate as much as possible the effect of outside noise , we use principles
* from the Fourier Transform to attempt to calculate the return loss of our signal for each setting .
*
* To begin , we send our output signal out on the line . We then receive back the reflected
* response . In the Fourier Transform , each evenly distributed frequency within the window
* is correlated ( multiplied against , then the resulting samples are added together ) with
* the real ( cos ) and imaginary ( sin ) portions of that frequency base to detect that frequency .
*
* Instead of doing a complete Fourier Transform , we solve the transform for only our signal
* by multiplying the received signal by the real and imaginary portions of our reference
* signal . This then gives us the real and imaginary values that we can use to calculate
* the return loss of the sinusoids that we sent out on the line . This is done by finding
* the magnitude ( think polar form ) of the vector resulting from the real and imaginary
* portions calculated above .
*
* This essentially filters out any other noise which maybe present on the line which is outside
* the frequencies used in our test multi - tone .
*/
void init_sinetable ( void )
{
int i ;
if ( debug ) {
fprintf ( stdout , " Using sine tables with %d samples \n " , SINE_SAMPLES ) ;
}
for ( i = 0 ; i < SINE_SAMPLES ; i + + ) {
sintable [ i ] = sin ( ( ( float ) i * 2.0 * M_PI ) / ( float ) ( SINE_SAMPLES ) ) ;
}
}
/* Sine and cosine table lookup to use periodicity of the calculations being done */
float sin_tbl ( int arg , int num_per_period )
{
arg = arg % num_per_period ;
arg = ( arg * SINE_SAMPLES ) / num_per_period ;
return sintable [ arg ] ;
}
float cos_tbl ( int arg , int num_per_period )
{
arg = arg % num_per_period ;
arg = ( arg * SINE_SAMPLES ) / num_per_period ;
arg = ( arg + SINE_SAMPLES / 4 ) % SINE_SAMPLES ; /* Pi/2 adjustment */
return sintable [ arg ] ;
}
static float db_loss ( float measured , float reference )
{
return 20 * ( logf ( measured / reference ) / logf ( 10 ) ) ;
}
static void one_point_dft ( const short * inbuf , int len , int frequency , float * real , float * imaginary )
{
float myreal = 0 , myimag = 0 ;
int i ;
for ( i = 0 ; i < len ; i + + ) {
if ( use_table ) {
myreal + = ( float ) inbuf [ i ] * cos_tbl ( i * frequency , 8000 ) ;
myimag + = ( float ) inbuf [ i ] * sin_tbl ( i * frequency , 8000 ) ;
} else {
myreal + = ( float ) inbuf [ i ] * cos ( ( i * 2.0 * M_PI * frequency ) / 8000 ) ;
myimag + = ( float ) inbuf [ i ] * sin ( ( i * 2.0 * M_PI * frequency ) / 8000 ) ;
}
}
myimag * = - 1 ;
* real = myreal / ( float ) len ;
* imaginary = myimag / ( float ) len ;
}
static float calc_magnitude ( short * inbuf , int insamps )
{
float real , imaginary , magnitude ;
float totalmagnitude = 0 ;
int i ;
for ( i = 0 ; i < freqcount ; i + + ) {
one_point_dft ( inbuf , insamps , freqs [ i ] , & real , & imaginary ) ;
magnitude = sqrtf ( ( real * real ) + ( imaginary * imaginary ) ) ;
totalmagnitude + = magnitude ;
}
return totalmagnitude ;
}
/**
* dumps input and output buffer contents for the echo test - used to see exactly what ' s going on
*/
static int maptone ( int whichdahdi , int freq , char * dialstr , int delayuntilsilence )
{
int i = 0 ;
int res = 0 , x = 0 ;
struct dahdi_bufferinfo bi ;
short inbuf [ TEST_DURATION ] ; /* changed from BUFFER_LENGTH - this buffer is for short values, so it should be allocated using the length of the test */
FILE * outfile = NULL ;
int leadin = 50 ;
int trailout = 100 ;
struct silence_info sinfo ;
float power_result ;
float power_waveform ;
float echo ;
outfile = fopen ( " fxotune_dump.vals " , " w " ) ;
if ( ! outfile ) {
fprintf ( stdout , " Cannot create fxotune_dump.vals \n " ) ;
return - 1 ;
}
x = 1 ;
if ( ioctl ( whichdahdi , DAHDI_SETLINEAR , & x ) ) {
fprintf ( stderr , " Unable to set channel to signed linear mode. \n " ) ;
return - 1 ;
}
memset ( & bi , 0 , sizeof ( bi ) ) ;
if ( ioctl ( whichdahdi , DAHDI_GET_BUFINFO , & bi ) ) {
fprintf ( stderr , " Unable to get buffer information! \n " ) ;
return - 1 ;
}
bi . numbufs = 2 ;
bi . bufsize = TEST_DURATION ; /* KD - changed from BUFFER_LENGTH; */
bi . txbufpolicy = DAHDI_POLICY_IMMEDIATE ;
bi . rxbufpolicy = DAHDI_POLICY_IMMEDIATE ;
if ( ioctl ( whichdahdi , DAHDI_SET_BUFINFO , & bi ) ) {
fprintf ( stderr , " Unable to set buffer information! \n " ) ;
return - 1 ;
}
/* Fill the output buffers */
for ( i = 0 ; i < leadin ; i + + )
outbuf [ i ] = 0 ;
for ( ; i < TEST_DURATION - trailout ; i + + ) {
outbuf [ i ] = freq > 0 ? gentone ( freq , i ) : genwaveform ( i ) ; /* if frequency is negative, use a multi-part waveform instead of a single frequency */
}
for ( ; i < TEST_DURATION ; i + + )
outbuf [ i ] = 0 ;
/* Make sure the line is clear */
memset ( & sinfo , 0 , sizeof ( sinfo ) ) ;
sinfo . device = whichdahdi ;
sinfo . dialstr = dialstr ;
sinfo . initial_delay = delayuntilsilence ;
sinfo . reset_after = 4 ; /* doesn't matter - we are only running one test */
if ( ensure_silence ( & sinfo ) ) {
fprintf ( stderr , " Unable to get a clear outside line \n " ) ;
return - 1 ;
}
/* Flush buffers */
x = DAHDI_FLUSH_READ | DAHDI_FLUSH_WRITE | DAHDI_FLUSH_EVENT ;
if ( ioctl ( whichdahdi , DAHDI_FLUSH , & x ) ) {
fprintf ( stderr , " Unable to flush I/O: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
/* send data out on line */
res = write ( whichdahdi , outbuf , BUFFER_LENGTH ) ; /* we are sending a TEST_DURATION length array of shorts (which are 2 bytes each) */
if ( res ! = BUFFER_LENGTH ) {
fprintf ( stderr , " Could not write all data to line \n " ) ;
return - 1 ;
}
retry :
/* read return response */
res = fxotune_read ( whichdahdi , inbuf , BUFFER_LENGTH ) ;
if ( res ! = BUFFER_LENGTH ) {
int dummy ;
ioctl ( whichdahdi , DAHDI_GETEVENT , & dummy ) ;
goto retry ;
}
/* write content of output buffer to debug file */
power_result = power_of ( inbuf , TEST_DURATION , 1 ) ;
power_waveform = power_of ( outbuf , TEST_DURATION , 1 ) ;
echo = power_result / power_waveform ;
fprintf ( outfile , " Buffers, freq=%d, outpower=%0.0f, echo=%0.4f \n " , freq , power_result , echo ) ;
fprintf ( outfile , " Sample, Input (received from the line), Output (sent to the line) \n " ) ;
for ( i = 0 ; i < TEST_DURATION ; i + + ) {
fprintf ( outfile , " %d, %d, %d \n " ,
i ,
inbuf [ i ] ,
outbuf [ i ]
) ;
}
fclose ( outfile ) ;
fprintf ( stdout , " echo ratio = %0.4f (%0.1f / %0.1f) \n " , echo , power_result , power_waveform ) ;
return 0 ;
}
/**
* Initialize the data store for storing off best calculated results
*/
static void init_topresults ( void )
{
topresults . numactive = 0 ;
}
/**
* If this is a best result candidate , store in the top results data store
* This is dependent on being the lowest echo value
*
* @ param tbleoffset - The offset into the echo_trys table used
* @ param setting - Pointer to the settings used to achieve the fgiven value
* @ param echo - The calculated echo return value ( in dB )
* @ param echo - The calculated magnitude of the response
*/
static void set_topresults ( int tbloffset , struct wctdm_echo_coefs * setting , float echo , float freqres )
{
int place ;
int idx ;
for ( place = 0 ; place < MAX_RESULTS & & place < topresults . numactive ; place + + ) {
if ( echo < topresults . results [ place ] . echo ) {
break ;
}
}
if ( place < MAX_RESULTS ) {
/* move results to the bottom */
for ( idx = topresults . numactive - 2 ; idx > = place ; idx - - ) {
topresults . results [ idx + 1 ] = topresults . results [ idx ] ;
}
topresults . results [ place ] . idx = tbloffset ;
topresults . results [ place ] . settings = * setting ;
topresults . results [ place ] . echo = echo ;
topresults . results [ place ] . freqres = freqres ;
if ( MAX_RESULTS > topresults . numactive ) {
topresults . numactive + + ;
}
}
}
/**
* Prints the top results stored to stdout
*
* @ param header - Text that goes in the header of the response
*/
static void print_topresults ( char * header )
{
int item ;
fprintf ( stdout , " Top %d results for %s \n " , topresults . numactive , header ) ;
for ( item = 0 ; item < topresults . numactive ; item + + ) {
fprintf ( stdout , " Res #%d: index=%d, %3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d: magnitude = %0.0f, echo = %0.4f dB \n " ,
item + 1 , topresults . results [ item ] . idx , topresults . results [ item ] . settings . acim ,
topresults . results [ item ] . settings . coef1 , topresults . results [ item ] . settings . coef2 ,
topresults . results [ item ] . settings . coef3 , topresults . results [ item ] . settings . coef4 ,
topresults . results [ item ] . settings . coef5 , topresults . results [ item ] . settings . coef6 ,
topresults . results [ item ] . settings . coef7 , topresults . results [ item ] . settings . coef8 ,
topresults . results [ item ] . freqres , topresults . results [ item ] . echo ) ;
}
}
/**
* Perform calibration type 2 on the specified device
*
* Determine optimum echo coefficients for the specified device
*
* New tuning strategy . If we have a number that we can dial that will result in silence from the
* switch , the tune will be * much * faster ( we don ' t have to keep hanging up and dialing a digit , etc . . . )
* The downside is that the user needs to actually find a ' no tone ' phone number at their CO ' s switch - but for
* really fixing echo problems , this is what it takes .
*
* Also , for the purposes of optimizing settings , if we pick a single frequency and test with that ,
* we can try a whole bunch of impedence / echo coefficients . This should give better results than trying
* a bunch of frequencies , and we can always do a a frequency sweep to pick between the best 3 or 4
* impedence / coefficients configurations .
*
* Note : It may be possible to take this even further and do some pertubation analysis on the echo coefficients
* themselves ( maybe use the 72 entry sweep to find some settings that are close to working well , then
* deviate the coefficients a bit to see if we can improve things ) . A better way to do this would be to
* use the optimization strategy from silabs . For reference , here is an application note that describes
* the echo coefficients ( and acim values ) :
*
* http : //www.silabs.com/Support%20Documents/TechnicalDocs/an84.pdf
*
* See Table 13 in this document for a breakdown of acim values by region .
*
* http : //www.silabs.com/Support%20Documents/TechnicalDocs/si3050-18-19.pdf
*
*/
static int acim_tune2 ( int whichdahdi , int freq , char * dialstr , int delayuntilsilence , int silencegoodfor , struct wctdm_echo_coefs * coefs_out )
{
int i = 0 ;
int res = 0 , x = 0 ;
int lowesttry = - 1 ;
float lowesttryresult = 999999999999.0 ;
float lowestecho = 999999999999.0 ;
struct dahdi_bufferinfo bi ;
short inbuf [ TEST_DURATION * 2 ] ;
struct silence_info sinfo ;
int echo_trys_size = 72 ;
int trys = 0 ;
float waveform_power ;
float freq_result ;
float echo ;
init_topresults ( ) ;
if ( debug & & ! debugoutfile ) {
if ( ! ( debugoutfile = fopen ( " fxotune.vals " , " w " ) ) ) {
fprintf ( stdout , " Cannot create fxotune.vals \n " ) ;
return - 1 ;
}
}
/* Set echo settings */
if ( ioctl ( whichdahdi , WCTDM_SET_ECHOTUNE , & echo_trys [ 0 ] ) ) {
fprintf ( stderr , " Unable to set impedance on fd %d \n " , whichdahdi ) ;
return - 1 ;
}
x = 1 ;
if ( ioctl ( whichdahdi , DAHDI_SETLINEAR , & x ) ) {
fprintf ( stderr , " Unable to set channel to signed linear mode. \n " ) ;
return - 1 ;
}
memset ( & bi , 0 , sizeof ( bi ) ) ;
if ( ioctl ( whichdahdi , DAHDI_GET_BUFINFO , & bi ) ) {
fprintf ( stderr , " Unable to get buffer information! \n " ) ;
return - 1 ;
}
bi . numbufs = 2 ;
bi . bufsize = BUFFER_LENGTH ;
bi . txbufpolicy = DAHDI_POLICY_IMMEDIATE ;
bi . rxbufpolicy = DAHDI_POLICY_IMMEDIATE ;
if ( ioctl ( whichdahdi , DAHDI_SET_BUFINFO , & bi ) ) {
fprintf ( stderr , " Unable to set buffer information! \n " ) ;
return - 1 ;
}
x = DAHDI_OFFHOOK ;
if ( ioctl ( whichdahdi , DAHDI_HOOK , & x ) ) {
fprintf ( stderr , " Cannot bring fd %d off hook " , whichdahdi ) ;
return - 1 ;
}
/* Set up silence settings */
memset ( & sinfo , 0 , sizeof ( sinfo ) ) ;
sinfo . device = whichdahdi ;
sinfo . dialstr = dialstr ;
sinfo . initial_delay = delayuntilsilence ;
sinfo . reset_after = silencegoodfor ;
/* Fill the output buffers */
for ( i = 0 ; i < TEST_DURATION ; i + + )
outbuf [ i ] = freq > 0 ? gentone ( freq , i ) : genwaveform ( i ) ; /* if freq is negative, use a multi-frequency waveform */
/* compute power of input (so we can later compute echo levels relative to input) */
waveform_power = calc_magnitude ( outbuf , TEST_DURATION ) ;
/* sweep through the various coefficient settings and see how our responses look */
for ( trys = 0 ; trys < echo_trys_size ; trys + + ) {
/* ensure silence on the line */
if ( ensure_silence ( & sinfo ) ) {
fprintf ( stderr , " Unable to get a clear outside line \n " ) ;
return - 1 ;
}
if ( ioctl ( whichdahdi , WCTDM_SET_ECHOTUNE , & echo_trys [ trys ] ) ) {
fprintf ( stderr , " Unable to set echo coefficients on fd %d \n " , whichdahdi ) ;
return - 1 ;
}
/* Flush buffers */
x = DAHDI_FLUSH_READ | DAHDI_FLUSH_WRITE | DAHDI_FLUSH_EVENT ;
if ( ioctl ( whichdahdi , DAHDI_FLUSH , & x ) ) {
fprintf ( stderr , " Unable to flush I/O: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
/* send data out on line */
res = write ( whichdahdi , outbuf , BUFFER_LENGTH ) ;
if ( res ! = BUFFER_LENGTH ) {
fprintf ( stderr , " Could not write all data to line \n " ) ;
return - 1 ;
}
retry :
/* read return response */
res = fxotune_read ( whichdahdi , inbuf , BUFFER_LENGTH * 2 ) ;
if ( res ! = BUFFER_LENGTH * 2 ) {
int dummy ;
ioctl ( whichdahdi , DAHDI_GETEVENT , & dummy ) ;
goto retry ;
}
freq_result = calc_magnitude ( inbuf , TEST_DURATION * 2 ) ;
echo = db_loss ( freq_result , waveform_power ) ;
#if 0
if ( debug > 0 )
fprintf ( stdout , " %3d,%d,%d,%d,%d,%d,%d,%d,%d: magnitude = %0.0f, echo = %0.4f dB \n " ,
echo_trys [ trys ] . acim , echo_trys [ trys ] . coef1 , echo_trys [ trys ] . coef2 ,
echo_trys [ trys ] . coef3 , echo_trys [ trys ] . coef4 , echo_trys [ trys ] . coef5 ,
echo_trys [ trys ] . coef6 , echo_trys [ trys ] . coef7 , echo_trys [ trys ] . coef8 ,
freq_result , echo ) ;
# endif
if ( freq_result < lowesttryresult ) {
lowesttry = trys ;
lowesttryresult = freq_result ;
lowestecho = echo ;
}
if ( debug ) {
char result [ 256 ] ;
snprintf ( result , sizeof ( result ) , " %3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%f,%f " ,
echo_trys [ trys ] . acim ,
echo_trys [ trys ] . coef1 ,
echo_trys [ trys ] . coef2 ,
echo_trys [ trys ] . coef3 ,
echo_trys [ trys ] . coef4 ,
echo_trys [ trys ] . coef5 ,
echo_trys [ trys ] . coef6 ,
echo_trys [ trys ] . coef7 ,
echo_trys [ trys ] . coef8 ,
freq_result ,
echo
) ;
fprintf ( debugoutfile , " %s \n " , result ) ;
fprintf ( stdout , " %3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d,%3d: magnitude = %0.0f, echo = %0.4f dB \n " ,
echo_trys [ trys ] . acim , echo_trys [ trys ] . coef1 , echo_trys [ trys ] . coef2 ,
echo_trys [ trys ] . coef3 , echo_trys [ trys ] . coef4 , echo_trys [ trys ] . coef5 ,
echo_trys [ trys ] . coef6 , echo_trys [ trys ] . coef7 , echo_trys [ trys ] . coef8 ,
freq_result , echo ) ;
}
if ( printbest ) {
set_topresults ( trys , & echo_trys [ trys ] , echo , freq_result ) ;
}
}
if ( debug > 0 )
fprintf ( stdout , " Config with lowest response = %d, magnitude = %0.0f, echo = %0.4f dB \n " , lowesttry , lowesttryresult , lowestecho ) ;
memcpy ( coefs_out , & echo_trys [ lowesttry ] , sizeof ( struct wctdm_echo_coefs ) ) ;
if ( printbest ) {
print_topresults ( " Acim2_tune Test " ) ;
}
return 0 ;
}
/**
* Perform calibration type 1 on the specified device . Only tunes the line impedance . Look for best response range
*/
static int acim_tune ( int whichdahdi , char * dialstr , int delayuntilsilence , int silencegoodfor , struct wctdm_echo_coefs * coefs_out )
{
int i = 0 , freq = 0 , acim = 0 ;
int res = 0 , x = 0 ;
struct dahdi_bufferinfo bi ;
struct wctdm_echo_coefs coefs ;
short inbuf [ TEST_DURATION ] ; /* changed from BUFFER_LENGTH - this buffer is for short values, so it should be allocated using the length of the test */
int lowest = 0 ;
FILE * outfile = NULL ;
float acim_results [ 16 ] ;
struct silence_info sinfo ;
if ( debug ) {
outfile = fopen ( " fxotune.vals " , " w " ) ;
if ( ! outfile ) {
fprintf ( stdout , " Cannot create fxotune.vals \n " ) ;
return - 1 ;
}
}
/* Set up silence settings */
memset ( & sinfo , 0 , sizeof ( sinfo ) ) ;
sinfo . device = whichdahdi ;
sinfo . dialstr = dialstr ;
sinfo . initial_delay = delayuntilsilence ;
sinfo . reset_after = silencegoodfor ;
/* Set echo settings */
memset ( & coefs , 0 , sizeof ( coefs ) ) ;
if ( ioctl ( whichdahdi , WCTDM_SET_ECHOTUNE , & coefs ) ) {
fprintf ( stdout , " Skipping non-TDM / non-FXO \n " ) ;
return - 1 ;
}
x = 1 ;
if ( ioctl ( whichdahdi , DAHDI_SETLINEAR , & x ) ) {
fprintf ( stderr , " Unable to set channel to signed linear mode. \n " ) ;
return - 1 ;
}
memset ( & bi , 0 , sizeof ( bi ) ) ;
if ( ioctl ( whichdahdi , DAHDI_GET_BUFINFO , & bi ) ) {
fprintf ( stderr , " Unable to get buffer information! \n " ) ;
return - 1 ;
}
bi . numbufs = 2 ;
bi . bufsize = BUFFER_LENGTH ;
bi . txbufpolicy = DAHDI_POLICY_IMMEDIATE ;
bi . rxbufpolicy = DAHDI_POLICY_IMMEDIATE ;
if ( ioctl ( whichdahdi , DAHDI_SET_BUFINFO , & bi ) ) {
fprintf ( stderr , " Unable to set buffer information! \n " ) ;
return - 1 ;
}
for ( acim = 0 ; acim < 16 ; acim + + ) {
float freq_results [ 15 ] ;
coefs . acim = acim ;
if ( ioctl ( whichdahdi , WCTDM_SET_ECHOTUNE , & coefs ) ) {
fprintf ( stderr , " Unable to set impedance on fd %d \n " , whichdahdi ) ;
return - 1 ;
}
for ( freq = 200 ; freq < = 3000 ; freq + = 200 ) {
/* Fill the output buffers */
for ( i = 0 ; i < TEST_DURATION ; i + + )
outbuf [ i ] = gentone ( freq , i ) ;
/* Make sure line is ready for next test iteration */
if ( ensure_silence ( & sinfo ) ) {
fprintf ( stderr , " Unable to get a clear line \n " ) ;
return - 1 ;
}
/* Flush buffers */
x = DAHDI_FLUSH_READ | DAHDI_FLUSH_WRITE | DAHDI_FLUSH_EVENT ;
if ( ioctl ( whichdahdi , DAHDI_FLUSH , & x ) ) {
fprintf ( stderr , " Unable to flush I/O: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
/* send data out on line */
res = write ( whichdahdi , outbuf , BUFFER_LENGTH ) ;
if ( res ! = BUFFER_LENGTH ) {
fprintf ( stderr , " Could not write all data to line \n " ) ;
return - 1 ;
}
/* read return response */
retry :
/* read return response */
res = fxotune_read ( whichdahdi , inbuf , BUFFER_LENGTH ) ;
if ( res ! = BUFFER_LENGTH ) {
int dummy ;
ioctl ( whichdahdi , DAHDI_GETEVENT , & dummy ) ;
goto retry ;
}
/* calculate power of response */
freq_results [ ( freq / 200 ) - 1 ] = power_of ( inbuf + SKIP_SAMPLES , TEST_DURATION - SKIP_SAMPLES , 1 ) ; /* changed from inbuf+SKIP_BYTES, BUFFER_LENGTH-SKIP_BYTES, 1 */
if ( debug ) fprintf ( outfile , " %d,%d,%f \n " , acim , freq , freq_results [ ( freq / 200 ) - 1 ] ) ;
}
acim_results [ acim ] = power_of ( freq_results , 15 , 0 ) ;
}
if ( debug ) {
for ( i = 0 ; i < 16 ; i + + )
fprintf ( outfile , " acim_results[%d] = %f \n " , i , acim_results [ i ] ) ;
}
/* Find out what the "best" impedance is for the line */
lowest = 0 ;
for ( i = 0 ; i < 16 ; i + + ) {
if ( acim_results [ i ] < acim_results [ lowest ] ) {
lowest = i ;
}
}
coefs_out - > acim = lowest ;
coefs_out - > coef1 = 0 ;
coefs_out - > coef2 = 0 ;
coefs_out - > coef3 = 0 ;
coefs_out - > coef4 = 0 ;
coefs_out - > coef5 = 0 ;
coefs_out - > coef6 = 0 ;
coefs_out - > coef7 = 0 ;
coefs_out - > coef8 = 0 ;
return 0 ;
}
2013-05-09 23:34:20 +08:00
static int channel_is_fxo ( int channo )
{
int res = 0 ;
int fd ;
const char * CTL_DEV = " /dev/dahdi/ctl " ;
struct dahdi_params params ;
fd = open ( CTL_DEV , O_RDWR , 0600 ) ;
if ( - 1 = = fd ) {
fprintf ( stderr , " Failed to open %s: %s \n " ,
CTL_DEV , strerror ( errno ) ) ;
return - 1 ;
}
params . channo = channo ;
if ( ioctl ( fd , DAHDI_GET_PARAMS , & params ) ) {
fprintf ( stderr ,
" %d is not a valid channel number. \n " , channo ) ;
res = - 1 ;
} else if ( 0 = = ( __DAHDI_SIG_FXS & params . sigcap ) ) {
fprintf ( stderr ,
" Channel %d is not an FXO port. \n " , channo ) ;
res = - 1 ;
} else if ( 0 = = params . sigtype ) {
fprintf ( stderr ,
" Cannot run on unconfigured channel %d. Please run dahdi_cfg to configure channels before running fxotune. \n " ,
channo ) ;
res = - 1 ;
}
close ( fd ) ;
return res ;
}
static int channel_open ( int channo )
{
int fd ;
const char * DEVICE = " /dev/dahdi/channel " ;
if ( channo > 0 ) {
if ( channel_is_fxo ( channo ) )
return - 1 ;
fd = open ( DEVICE , O_RDWR , 0600 ) ;
if ( fd < 0 ) {
perror ( DEVICE ) ;
return - 1 ;
}
if ( ioctl ( fd , DAHDI_SPECIFY , & channo ) < 0 ) {
perror ( " DADHI_SPECIFY ioctl failed " ) ;
close ( fd ) ;
fd = - 1 ;
}
} else {
fprintf ( stderr ,
" Specified channel is not a valid channel number " ) ;
fd = - 1 ;
}
return fd ;
}
2010-08-18 21:49:24 +08:00
/**
* Reads echo register settings from the configuration file and pushes them into
* the appropriate devices
*
* @ param configfilename the path of the file that the calibration results should be written to
*
* @ return 0 if successful , ! 0 otherwise
*/
2013-10-21 18:54:58 +08:00
static int do_set ( char * configfilename , int dev_range , int startdev , int stopdev )
2010-08-18 21:49:24 +08:00
{
FILE * fp = NULL ;
int res = 0 ;
int fd = 0 ;
fp = fopen ( configfile , " r " ) ;
if ( ! fp ) {
fprintf ( stdout , " Cannot open %s! \n " , configfile ) ;
return - 1 ;
}
while ( res ! = EOF ) {
struct wctdm_echo_coefs mycoefs ;
char completedahdipath [ 56 ] = " " ;
int mydahdi , myacim , mycoef1 , mycoef2 , mycoef3 , mycoef4 , mycoef5 , mycoef6 , mycoef7 , mycoef8 ;
res = fscanf ( fp , " %d=%d,%d,%d,%d,%d,%d,%d,%d,%d " , & mydahdi , & myacim , & mycoef1 ,
& mycoef2 , & mycoef3 , & mycoef4 , & mycoef5 , & mycoef6 , & mycoef7 ,
& mycoef8 ) ;
if ( res = = EOF ) {
break ;
}
2013-10-21 18:54:58 +08:00
if ( dev_range & & ( mydahdi < startdev | | mydahdi > stopdev ) )
continue ;
2010-08-18 21:49:24 +08:00
/* Check to be sure conversion is done correctly */
if ( OUT_OF_BOUNDS ( myacim ) | | OUT_OF_BOUNDS ( mycoef1 ) | |
OUT_OF_BOUNDS ( mycoef2 ) | | OUT_OF_BOUNDS ( mycoef3 ) | |
OUT_OF_BOUNDS ( mycoef4 ) | | OUT_OF_BOUNDS ( mycoef5 ) | |
OUT_OF_BOUNDS ( mycoef6 ) | | OUT_OF_BOUNDS ( mycoef7 ) | | OUT_OF_BOUNDS ( mycoef8 ) ) {
fprintf ( stdout , " Bounds check error on inputs from %s:%d \n " , configfile , mydahdi ) ;
return - 1 ;
}
mycoefs . acim = myacim ;
mycoefs . coef1 = mycoef1 ;
mycoefs . coef2 = mycoef2 ;
mycoefs . coef3 = mycoef3 ;
mycoefs . coef4 = mycoef4 ;
mycoefs . coef5 = mycoef5 ;
mycoefs . coef6 = mycoef6 ;
mycoefs . coef7 = mycoef7 ;
mycoefs . coef8 = mycoef8 ;
2013-10-21 18:54:58 +08:00
if ( debug > = 2 )
printf ( " fxotune: set channel %d \n " , mydahdi ) ;
2013-05-09 23:34:20 +08:00
fd = channel_open ( mydahdi ) ;
2010-08-18 21:49:24 +08:00
if ( fd < 0 ) {
return - 1 ;
}
if ( ioctl ( fd , WCTDM_SET_ECHOTUNE , & mycoefs ) ) {
fprintf ( stdout , " %s: %s \n " , completedahdipath , strerror ( errno ) ) ;
return - 1 ;
}
close ( fd ) ;
}
fclose ( fp ) ;
if ( debug )
fprintf ( stdout , " fxotune: successfully set echo coeffecients on FXO modules \n " ) ;
return 0 ;
}
/**
* Output waveform information from a single test
*
* Clears the line , then sends a single waveform ( multi - tone , or single tone ) , and listens
* for the response on the line . Output is written to fxotune_dump . vals
*
* @ param startdev the device to test
* @ param dialstr the string that should be dialed to clear the dialtone from the line
* @ param delayuntilsilence the number of seconds to wait after dialing dialstr before starting the test
* @ param silencegoodfor the number of seconds that the test can run before having to reset the line again
* ( this is basically the amount of time it takes before the ' if you ' d like to make a call . . . ' message
* kicks in after you dial dialstr . This test is so short that the value is pretty much ignored .
* @ param waveformtype the type of waveform to use - - 1 = multi - tone waveform , otherwise the specified value
* is used as the frequency of a single tone . A value of 0 will output silence .
*/
static int do_dump ( int startdev , char * dialstr , int delayuntilsilence , int silencegoodfor , int waveformtype )
{
int res = 0 ;
int fd ;
char dahdidev [ 80 ] = " " ;
int dahdimodule = startdev ;
2013-05-09 23:34:20 +08:00
fd = channel_open ( dahdimodule ) ;
2010-08-18 21:49:24 +08:00
if ( fd < 0 ) {
return - 1 ;
}
fprintf ( stdout , " Dumping module %s \n " , dahdidev ) ;
res = maptone ( fd , waveformtype , dialstr , delayuntilsilence ) ;
close ( fd ) ;
if ( res ) {
fprintf ( stdout , " Failure! \n " ) ;
return res ;
} else {
fprintf ( stdout , " Done! \n " ) ;
return 0 ;
}
}
/**
* Performs calibration on all specified devices
*
* @ param startdev the first device to check
* @ param enddev the last device to check
* @ param calibtype the type of calibration to perform . 1 = old style ( loops through individual frequencies
* doesn ' t optimize echo coefficients . 2 = new style ( uses multi - tone and optimizes echo coefficients
* and acim setting )
* @ param configfilename the path of the file that the calibration results should be written to
* @ param dialstr the string that should be dialed to clear the dialtone from the line
* @ param delayuntilsilence the number of seconds to wait after dialing dialstr before starting the test
* @ param silencegoodfor the number of seconds that the test can run before having to reset the line again
* ( this is basically the amount of time it takes before the ' if you ' d like to make a call . . . ' message
* kicks in after you dial dialstr
*
* @ return 0 if successful , - 1 for serious error such as device not available , > 0 indicates the number of channels
*/
static int do_calibrate ( int startdev , int enddev , int calibtype , char * configfilename , char * dialstr , int delayuntilsilence , int silencegoodfor )
{
int problems = 0 ;
int res = 0 ;
int configfd , fd ;
int devno = 0 ;
struct wctdm_echo_coefs coefs ;
configfd = open ( configfile , O_CREAT | O_TRUNC | O_WRONLY , 0666 ) ;
if ( configfd < 0 ) {
fprintf ( stderr , " Cannot generate config file %s: open: %s \n " , configfile , strerror ( errno ) ) ;
return - 1 ;
}
for ( devno = startdev ; devno < = enddev ; devno + + ) {
2013-05-09 23:34:20 +08:00
fd = channel_open ( devno ) ;
2010-08-18 21:49:24 +08:00
if ( fd < 0 ) {
continue ;
}
2013-05-09 23:34:20 +08:00
fprintf ( stdout , " Tuning module %d \n " , devno ) ;
2010-08-18 21:49:24 +08:00
if ( 1 = = calibtype )
res = acim_tune ( fd , dialstr , delayuntilsilence , silencegoodfor , & coefs ) ;
else
res = acim_tune2 ( fd , - 1 , dialstr , delayuntilsilence , silencegoodfor , & coefs ) ;
close ( fd ) ;
if ( res ) {
fprintf ( stdout , " Failure! \n " ) ;
problems + + ;
} else {
fprintf ( stdout , " Done! \n " ) ;
}
if ( res = = 0 ) {
/* Do output to file */
int len = 0 ;
static char output [ 255 ] = " " ;
snprintf ( output , sizeof ( output ) , " %d=%d,%d,%d,%d,%d,%d,%d,%d,%d \n " ,
devno ,
coefs . acim ,
coefs . coef1 ,
coefs . coef2 ,
coefs . coef3 ,
coefs . coef4 ,
coefs . coef5 ,
coefs . coef6 ,
coefs . coef7 ,
coefs . coef8
) ;
if ( debug )
fprintf ( stdout , " Found best echo coefficients: %s \n " , output ) ;
len = strlen ( output ) ;
res = write ( configfd , output , strlen ( output ) ) ;
if ( res ! = len ) {
fprintf ( stdout , " Unable to write line \" %s \" to file. \n " , output ) ;
return - 1 ;
}
}
}
close ( configfd ) ;
if ( problems )
fprintf ( stdout , " Unable to tune %d devices, even though those devices are present \n " , problems ) ;
return problems ;
}
int main ( int argc , char * * argv )
{
int startdev = 1 ; /* -b */
int stopdev = 252 ; /* -e */
2013-10-21 18:54:58 +08:00
int dev_range = 0 ; /* false */
2010-08-18 21:49:24 +08:00
int calibtype = 2 ; /* -t */
int waveformtype = - 1 ; /* -w multi-tone by default. If > 0, single tone of specified frequency */
int delaytosilence = 0 ; /* -l */
int silencegoodfor = 18 ; /* -m */
char * dialstr = " 5 " ; /* -n */
int res = 0 ;
int doset = 0 ; /* -s */
int docalibrate = 0 ; /* -i <dialstr> */
int dodump = 0 ; /* -d */
int i = 0 ;
int moreargs ;
for ( i = 1 ; i < argc ; i + + ) {
if ( ! ( argv [ i ] [ 0 ] = = ' - ' | | argv [ i ] [ 0 ] = = ' / ' ) | | ( strlen ( argv [ i ] ) < = 1 ) ) {
fprintf ( stdout , " Unknown option : %s \n " , argv [ i ] ) ;
/* Show usage */
fputs ( usage , stdout ) ;
return - 1 ;
}
moreargs = ( i < argc - 1 ) ;
switch ( argv [ i ] [ 1 ] ) {
case ' s ' :
doset = 1 ;
continue ;
case ' i ' :
docalibrate = 1 ;
if ( moreargs ) { /* we need to check for a value after 'i' for backwards compatability with command line options of old fxotune */
if ( argv [ i + 1 ] [ 0 ] ! = ' - ' & & argv [ i + 1 ] [ 0 ] ! = ' / ' )
dialstr = argv [ + + i ] ;
}
continue ;
case ' c ' :
configfile = moreargs ? argv [ + + i ] : configfile ;
continue ;
case ' d ' :
dodump = 1 ;
continue ;
case ' b ' :
startdev = moreargs ? atoi ( argv [ + + i ] ) : startdev ;
2013-10-21 18:54:58 +08:00
dev_range = 1 ;
2010-08-18 21:49:24 +08:00
break ;
case ' e ' :
stopdev = moreargs ? atoi ( argv [ + + i ] ) : stopdev ;
2013-10-21 18:54:58 +08:00
dev_range = 1 ;
2010-08-18 21:49:24 +08:00
break ;
case ' t ' :
calibtype = moreargs ? atoi ( argv [ + + i ] ) : calibtype ;
break ;
case ' w ' :
waveformtype = moreargs ? atoi ( argv [ + + i ] ) : waveformtype ;
break ;
case ' l ' :
delaytosilence = moreargs ? atoi ( argv [ + + i ] ) : delaytosilence ;
break ;
case ' m ' :
silencegoodfor = moreargs ? atoi ( argv [ + + i ] ) : silencegoodfor ;
break ;
case ' n ' :
dialstr = moreargs ? argv [ + + i ] : dialstr ;
break ;
case ' p ' :
printbest + + ;
break ;
case ' x ' :
use_table = 1 ;
break ;
case ' v ' :
debug = strlen ( argv [ i ] ) - 1 ;
break ;
case ' o ' :
if ( moreargs ) {
audio_dump_fd = open ( argv [ + + i ] , O_WRONLY | O_CREAT | O_TRUNC , 0666 ) ;
if ( audio_dump_fd = = - 1 ) {
fprintf ( stdout , " Unable to open file %s: %s \n " , argv [ i ] , strerror ( errno ) ) ;
return - 1 ;
}
break ;
} else {
fprintf ( stdout , " No path supplied to -o option! \n " ) ;
return - 1 ;
}
default :
fprintf ( stdout , " Unknown option : %s \n " , argv [ i ] ) ;
/* Show usage */
fputs ( usage , stdout ) ;
return - 1 ;
}
}
if ( debug > 3 ) {
fprintf ( stdout , " Running with parameters: \n " ) ;
fprintf ( stdout , " \t doset=%d \n " , doset ) ;
fprintf ( stdout , " \t docalibrate=%d \n " , docalibrate ) ;
fprintf ( stdout , " \t dodump=%d \n " , dodump ) ;
fprintf ( stdout , " \t print best settings=%d \n " , printbest ) ;
fprintf ( stdout , " \t startdev=%d \n " , startdev ) ;
fprintf ( stdout , " \t stopdev=%d \n " , stopdev ) ;
fprintf ( stdout , " \t calibtype=%d \n " , calibtype ) ;
fprintf ( stdout , " \t waveformtype=%d \n " , waveformtype ) ;
fprintf ( stdout , " \t delaytosilence=%d \n " , delaytosilence ) ;
fprintf ( stdout , " \t silencegoodfor=%d \n " , silencegoodfor ) ;
fprintf ( stdout , " \t dialstr=%s \n " , dialstr ) ;
fprintf ( stdout , " \t debug=%d \n " , debug ) ;
}
if ( use_table ) {
init_sinetable ( ) ;
}
if ( docalibrate ) {
res = do_calibrate ( startdev , stopdev , calibtype , configfile , dialstr , delaytosilence , silencegoodfor ) ;
if ( ! res )
2013-10-21 18:54:58 +08:00
return do_set ( configfile , dev_range , startdev , stopdev ) ;
2010-08-18 21:49:24 +08:00
else
return - 1 ;
}
if ( doset )
2013-10-21 18:54:58 +08:00
return do_set ( configfile , dev_range , startdev , stopdev ) ;
2010-08-18 21:49:24 +08:00
if ( dodump ) {
res = do_dump ( startdev , dialstr , delaytosilence , silencegoodfor , waveformtype ) ;
if ( ! res )
return 0 ;
else
return - 1 ;
}
fputs ( usage , stdout ) ;
return - 1 ;
}