From 3403af6f5eba79740c98abb2678f9a66ef5a8190 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Fri, 23 May 2008 14:21:58 +0000 Subject: initial copy git-svn-id: http://svn.asterisk.org/svn/dahdi/tools/trunk@4335 a0bf4364-ded3-4de4-8d8a-66a801d63aff --- fxotune.c | 1110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1110 insertions(+) create mode 100644 fxotune.c (limited to 'fxotune.c') diff --git a/fxotune.c b/fxotune.c new file mode 100644 index 0000000..d7ee9ca --- /dev/null +++ b/fxotune.c @@ -0,0 +1,1110 @@ +/* + * This file and contents thereof are licensed under the terms and + * conditions of the GNU Public License version 2. For more information + * (including terms and conditions) see http://www.gnu.org/ + * + * fxotune.c -- A utility for tuning the various settings on the fxo + * modules for the TDM400 cards. + * + * by Matthew Fredrickson + * + * (C) 2004-2005 Digium, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STANDALONE_ZAPATA +#include "kernel/zaptel.h" +#else +#include +#endif +#include "kernel/wctdm.h" +#include "fxotune.h" + +#define TEST_DURATION 2000 +#define BUFFER_LENGTH (2 * TEST_DURATION) +#define SKIP_SAMPLES 800 + +static const float amplitude = 16384.0; + +static char *zappath = "/dev/zap"; +static char *configfile = "/etc/fxotune.conf"; + +static int audio_dump_fd = -1; + +static char *usage = +"Usage: fxotune [-v[vv] (-s | -i | -d )\n" +"\n" +" -s : set previously calibrated echo settings\n" +" -i : calibrate echo settings\n" +" options : [] [-t ]\n" +" [-b ][-e ]\n" +" [-n ][-l ][-m ]\n" +" -d : dump input and output waveforms to ./fxotune_dump.vals\n" +" options : [-b ][-w ]\n" +" [-n ][-l ][-m ]\n" +" -v : more output (-vv, -vvv also)\n" +" -o : Write the received raw 16-bit signed linear audio that is\n" +" used in processing to the file specified by \n" +" -c \n" +"\n" +" - type of calibration\n" +" (default 2, old method 1)\n" +" \n" +" - defines a range of devices to test\n" +" (default: 1-252)\n" +" - string to dial to clear the line\n" +" (default 5)\n" +" - seconds to wait for line to clear (default 0)\n" +" - seconds before line will no longer be clear\n" +" (default 18)\n" +" - the device to perform waveform dump on\n" +" (default 1)\n" +" - -1 for multitone waveform, or frequency of\n" +" single tone (default -1)\n" +" - 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 fxotune_read(int fd, void *buffer, int len) +{ + int res; + + res = read(fd, buffer, len); + + if ((res > 0) && (audio_dump_fd != -1)) { + 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; + + 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 */ + int x = ZT_ONHOOK; + + if (ioctl(info->device, ZT_HOOK, &x)) { + fprintf(stderr, "Unable to hang up fd %d\n", info->device); + return -1; + } + + sleep(2); + x = ZT_OFFHOOK; + if (ioctl(info->device, ZT_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 */ + + struct zt_dialoperation dop; + memset(&dop, 0, sizeof(dop)); + dop.op = ZT_DIAL_OP_REPLACE; + dop.dialstr[0] = 'T'; + zap_copy_string(dop.dialstr + 1, info->dialstr, sizeof(dop.dialstr)); + + + if (ioctl(info->device, ZT_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 index the current sample + * to begenerated. For a normal waveform you need to increment + * this every time you execute the function. + * + * @return 16bit slinear sample for the specified index + */ +static short inline gentone(int hz, int index) +{ + return amplitude * sin((index * 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 index the current sample + * to begenerated. For a normal waveform you need to increment + * this every time you execute the function. + * + * @return 16bit slinear sample for the specified index + */ +static short inline genwaveform(int index) +{ + int i = 0; + float response = (float)0; + for (i = 0; i < freqcount; i++){ + response += sin((index * 2.0 * M_PI * freqs[i])/8000); + } + + + return amplitude * response / freqcount; +} + + +/** + * Calculates the RMS of the waveform buffer of samples in 16bit 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) { + printf("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. + */ +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++) { + 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 whichzap, int freq, char *dialstr, int delayuntilsilence) +{ + int i = 0; + int res = 0, x = 0; + struct zt_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; + + outfile = fopen("fxotune_dump.vals", "w"); + if (!outfile) { + fprintf(stdout, "Cannot create fxotune_dump.vals\n"); + return -1; + } + + x = 1; + if (ioctl(whichzap, ZT_SETLINEAR, &x)) { + fprintf(stderr, "Unable to set channel to signed linear mode.\n"); + return -1; + } + + memset(&bi, 0, sizeof(bi)); + if (ioctl(whichzap, ZT_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 = ZT_POLICY_IMMEDIATE; + bi.rxbufpolicy = ZT_POLICY_IMMEDIATE; + if (ioctl(whichzap, ZT_SET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to set buffer information!\n"); + return -1; + } + + /* Fill the output buffers */ + int leadin = 50; + int trailout = 100; + 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 */ + struct silence_info sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.device = whichzap; + 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 = ZT_FLUSH_READ | ZT_FLUSH_WRITE | ZT_FLUSH_EVENT; + if (ioctl(whichzap, ZT_FLUSH, &x)) { + fprintf(stderr, "Unable to flush I/O: %s\n", strerror(errno)); + return -1; + } + + /* send data out on line */ + res = write(whichzap, 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(whichzap, inbuf, BUFFER_LENGTH); + if (res != BUFFER_LENGTH) { + int x; + + ioctl(whichzap, ZT_GETEVENT, &x); + goto retry; + } + + /* write content of output buffer to debug file */ + float power_result = power_of(inbuf, TEST_DURATION, 1); + float power_waveform = power_of(outbuf, TEST_DURATION, 1); + float 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; +} + + + +/** + * 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/public/documents/tpub_doc/anote/Wireline/Silicon_DAA/en/an84.pdf + * + */ +static int acim_tune2(int whichzap, 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 zt_bufferinfo bi; + + short inbuf[TEST_DURATION * 2]; + + if (debug && !debugoutfile) { + if (!(debugoutfile = fopen("fxotune.vals", "w"))) { + fprintf(stdout, "Cannot create fxotune.vals\n"); + return -1; + } + } + + /* Set echo settings */ + if (ioctl(whichzap, WCTDM_SET_ECHOTUNE, &echo_trys[0])) { + fprintf(stderr, "Unable to set impedance on fd %d\n", whichzap); + return -1; + } + + x = 1; + if (ioctl(whichzap, ZT_SETLINEAR, &x)) { + fprintf(stderr, "Unable to set channel to signed linear mode.\n"); + return -1; + } + + memset(&bi, 0, sizeof(bi)); + if (ioctl(whichzap, ZT_GET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to get buffer information!\n"); + return -1; + } + bi.numbufs = 2; + bi.bufsize = BUFFER_LENGTH; + bi.txbufpolicy = ZT_POLICY_IMMEDIATE; + bi.rxbufpolicy = ZT_POLICY_IMMEDIATE; + if (ioctl(whichzap, ZT_SET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to set buffer information!\n"); + return -1; + } + x = ZT_OFFHOOK; + if (ioctl(whichzap, ZT_HOOK, &x)) { + fprintf(stderr, "Cannot bring fd %d off hook", whichzap); + return -1; + } + + + /* Set up silence settings */ + struct silence_info sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.device = whichzap; + 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) */ + float waveform_power = calc_magnitude(outbuf, TEST_DURATION); + + + /* sweep through the various coefficient settings and see how our responses look */ + + int echo_trys_size = 72; + int trys = 0; + 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(whichzap, WCTDM_SET_ECHOTUNE, &echo_trys[trys])) { + fprintf(stderr, "Unable to set echo coefficients on fd %d\n", whichzap); + return -1; + } + + /* Flush buffers */ + x = ZT_FLUSH_READ | ZT_FLUSH_WRITE | ZT_FLUSH_EVENT; + if (ioctl(whichzap, ZT_FLUSH, &x)) { + fprintf(stderr, "Unable to flush I/O: %s\n", strerror(errno)); + return -1; + } + + /* send data out on line */ + res = write(whichzap, 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(whichzap, inbuf, BUFFER_LENGTH * 2); + if (res != BUFFER_LENGTH * 2) { + int x; + + ioctl(whichzap, ZT_GETEVENT, &x); + goto retry; + } + + float freq_result = calc_magnitude(inbuf, TEST_DURATION * 2); + float 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 (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)); + + 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 whichzap, 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 zt_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]; + + + if (debug) { + outfile = fopen("fxotune.vals", "w"); + if (!outfile) { + fprintf(stdout, "Cannot create fxotune.vals\n"); + return -1; + } + } + + /* Set up silence settings */ + struct silence_info sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.device = whichzap; + sinfo.dialstr = dialstr; + sinfo.initial_delay = delayuntilsilence; + sinfo.reset_after = silencegoodfor; + + /* Set echo settings */ + memset(&coefs, 0, sizeof(coefs)); + if (ioctl(whichzap, WCTDM_SET_ECHOTUNE, &coefs)) { + fprintf(stdout, "Skipping non-TDM / non-FXO\n"); + return -1; + } + + x = 1; + if (ioctl(whichzap, ZT_SETLINEAR, &x)) { + fprintf(stderr, "Unable to set channel to signed linear mode.\n"); + return -1; + } + + memset(&bi, 0, sizeof(bi)); + if (ioctl(whichzap, ZT_GET_BUFINFO, &bi)) { + fprintf(stderr, "Unable to get buffer information!\n"); + return -1; + } + bi.numbufs = 2; + bi.bufsize = BUFFER_LENGTH; + bi.txbufpolicy = ZT_POLICY_IMMEDIATE; + bi.rxbufpolicy = ZT_POLICY_IMMEDIATE; + if (ioctl(whichzap, ZT_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(whichzap, WCTDM_SET_ECHOTUNE, &coefs)) { + fprintf(stderr, "Unable to set impedance on fd %d\n", whichzap); + 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 = ZT_FLUSH_READ | ZT_FLUSH_WRITE | ZT_FLUSH_EVENT; + if (ioctl(whichzap, ZT_FLUSH, &x)) { + fprintf(stderr, "Unable to flush I/O: %s\n", strerror(errno)); + return -1; + } + + /* send data out on line */ + res = write(whichzap, 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(whichzap, inbuf, BUFFER_LENGTH); + if (res != BUFFER_LENGTH) { + int x; + + ioctl(whichzap, ZT_GETEVENT, &x); + 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; +} + +/** + * 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 + */ +static int do_set(char *configfilename) +{ + 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 completezappath[56] = ""; + int myzap,myacim,mycoef1,mycoef2,mycoef3,mycoef4,mycoef5,mycoef6,mycoef7,mycoef8; + + + res = fscanf(fp, "%d=%d,%d,%d,%d,%d,%d,%d,%d,%d",&myzap,&myacim,&mycoef1, + &mycoef2,&mycoef3,&mycoef4,&mycoef5,&mycoef6,&mycoef7, + &mycoef8); + + if (res == EOF) { + break; + } + + /* 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, myzap); + 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; + + snprintf(completezappath, sizeof(completezappath), "%s/%d", zappath, myzap); + fd = open(completezappath, O_RDWR); + + if (fd < 0) { + fprintf(stdout, "open error on %s: %s\n", completezappath, strerror(errno)); + return -1; + } + + if (ioctl(fd, WCTDM_SET_ECHOTUNE, &mycoefs)) { + fprintf(stdout, "%s: %s\n", completezappath, 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 zapdev[80] = ""; + + int zapmodule = startdev; + snprintf(zapdev, sizeof(zapdev), "%s/%d", zappath, zapmodule); + + fd = open(zapdev, O_RDWR); + if (fd < 0) { + fprintf(stdout, "%s absent: %s\n", zapdev, strerror(errno)); + return -1; + } + + fprintf(stdout, "Dumping module %s\n", zapdev); + 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, !0 otherwise + */ +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; + char zapdev[80] = ""; + 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++) { + snprintf(zapdev, sizeof(zapdev), "%s/%d", zappath, devno); + + fd = open(zapdev, O_RDWR); + if (fd < 0) { + fprintf(stdout, "%s absent: %s\n", zapdev, strerror(errno)); + continue; + } + + fprintf(stdout, "Tuning module %s\n", zapdev); + + 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 */ + 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 */ + int dodump = 0; /* -d */ + + int i = 0; + + 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; + } + int 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; + break; + case 'e': + stopdev = moreargs ? atoi(argv[++i]) : stopdev; + 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 '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, "\tdoset=%d\n", doset); + fprintf(stdout, "\tdocalibrate=%d\n", docalibrate); + fprintf(stdout, "\tdodump=%d\n", dodump); + fprintf(stdout, "\tstartdev=%d\n", startdev); + fprintf(stdout, "\tstopdev=%d\n", stopdev); + fprintf(stdout, "\tcalibtype=%d\n", calibtype); + fprintf(stdout, "\twaveformtype=%d\n", waveformtype); + fprintf(stdout, "\tdelaytosilence=%d\n", delaytosilence); + fprintf(stdout, "\tsilencegoodfor=%d\n", silencegoodfor); + fprintf(stdout, "\tdialstr=%s\n", dialstr); + fprintf(stdout, "\tdebug=%d\n", debug); + } + + if (docalibrate){ + res = do_calibrate(startdev, stopdev, calibtype, configfile, dialstr, delaytosilence, silencegoodfor); + if (!res) + return do_set(configfile); + else + return -1; + } + + if (doset) + return do_set(configfile); + + if (dodump){ + res = do_dump(startdev, dialstr, delaytosilence, silencegoodfor, waveformtype); + if (!res) + return 0; + else + return -1; + } + + fputs(usage, stdout); + return -1; +} -- cgit v1.2.3