summaryrefslogtreecommitdiff
path: root/fxotune.c
diff options
context:
space:
mode:
authorKevin P. Fleming <kpfleming@digium.com>2008-05-23 14:21:58 +0000
committerKevin P. Fleming <kpfleming@digium.com>2008-05-23 14:21:58 +0000
commit3403af6f5eba79740c98abb2678f9a66ef5a8190 (patch)
tree93415a9598e5363284832757de0596988a421aa6 /fxotune.c
parentdb9cffbffeedcde4ee52027160b96a2548262c5d (diff)
initial copy
git-svn-id: http://svn.asterisk.org/svn/dahdi/tools/trunk@4335 a0bf4364-ded3-4de4-8d8a-66a801d63aff
Diffstat (limited to 'fxotune.c')
-rw-r--r--fxotune.c1110
1 files changed, 1110 insertions, 0 deletions
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 <creslin@digium.com>
+ *
+ * (C) 2004-2005 Digium, Inc.
+ */
+
+#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>
+
+#ifdef STANDALONE_ZAPATA
+#include "kernel/zaptel.h"
+#else
+#include <zaptel/zaptel.h>
+#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 <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"
+" -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 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 <dialstr> */
+ 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;
+}