/* * Monitor a Zaptel Channel * * Written by Mark Spencer * Based on previous works, designs, and architectures conceived and * written by Jim Dixon . * * Copyright (C) 2001 Jim Dixon / Zapata Telephony. * Copyright (C) 2001 Linux Support Services, Inc. * * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under thet erms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Primary Author: Mark Spencer * */ #include #include #include #include #include #include #include #include #include #include #ifdef STANDALONE_ZAPATA #include "zaptel.h" #include "tonezone.h" #else #include #include #endif #include #define BUFFERS 4 #define FRAG_SIZE 8 #define PROG "ztmonitor" /* Put the ofh (output file handles) outside * the main loop in case we ever add a signal * handler. */ enum ofh_type { ofh_def = 0, ofh_tx, ofh_pre, ofh_pretx, ofh_last }; static FILE* ofh[4] = {0, 0, 0, 0}; static int stereo = 0; static int verbose = 0; int audio_open(void) { int fd; int speed = 8000; int fmt = AFMT_S16_LE; int fragsize = (BUFFERS << 16) | (FRAG_SIZE); struct audio_buf_info ispace, ospace; fd = open("/dev/dsp", O_WRONLY); if (fd < 0) { fprintf(stderr, "Unable to open /dev/dsp: %s\n", strerror(errno)); return -1; } /* Step 1: Signed linear */ if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0) { fprintf(stderr, "ioctl(SETFMT) failed: %s\n", strerror(errno)); close(fd); return -1; } /* Step 2: Make non-stereo */ if (ioctl(fd, SNDCTL_DSP_STEREO, &stereo) < 0) { fprintf(stderr, "ioctl(STEREO) failed: %s\n", strerror(errno)); close(fd); return -1; } if (stereo != 0) { fprintf(stderr, "Can't turn stereo off :(\n"); } /* Step 3: Make 8000 Hz */ if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) { fprintf(stderr, "ioctl(SPEED) failed: %s\n", strerror(errno)); close(fd); return -1; } if (speed != 8000) fprintf(stderr, "Warning: Requested 8000 Hz, got %d\n", speed); if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fragsize)) { fprintf(stderr, "Sound card won't let me set fragment size to 10 64-byte buffers (%x)\n" "so sound may be choppy: %s.\n", fragsize, strerror(errno)); } bzero(&ispace, sizeof(ispace)); bzero(&ospace, sizeof(ospace)); if (ioctl(fd, SNDCTL_DSP_GETISPACE, &ispace)) { /* They don't support block size stuff, so just return but notify the user */ fprintf(stderr, "Sound card won't let me know the input buffering...\n"); } if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &ospace)) { /* They don't support block size stuff, so just return but notify the user */ fprintf(stderr, "Sound card won't let me know the output buffering...\n"); } fprintf(stderr, "New input space: %d of %d %d byte fragments (%d bytes left)\n", ispace.fragments, ispace.fragstotal, ispace.fragsize, ispace.bytes); fprintf(stderr, "New output space: %d of %d %d byte fragments (%d bytes left)\n", ospace.fragments, ospace.fragstotal, ospace.fragsize, ospace.bytes); return fd; } int pseudo_open(void) { int fd; int x = 1; fd = open("/dev/zap/pseudo", O_RDWR); if (fd < 0) { fprintf(stderr, "Unable to open pseudo channel: %s\n", strerror(errno)); return -1; } if (ioctl(fd, ZT_SETLINEAR, &x)) { fprintf(stderr, "Unable to set linear mode: %s\n", strerror(errno)); close(fd); return -1; } x = 240; if (ioctl(fd, ZT_SET_BLOCKSIZE, &x)) { fprintf(stderr, "unable to set sane block size: %s\n", strerror(errno)); close(fd); return -1; } return fd; } #define barlen 35 #define baroptimal 3250 //define barlevel 200 #define barlevel ((baroptimal/barlen)*2) #define maxlevel (barlen*barlevel) void draw_barheader() { char bar[barlen+5]; memset(bar, '-', sizeof(bar)); memset(bar, '<', 1); memset(bar+barlen+2, '>', 1); memset(bar+barlen+3, '\0', 1); strncpy(bar+(barlen/2), "(RX)", 4); printf("%s", bar); strncpy(bar+(barlen/2), "(TX)", 4); printf(" %s\n", bar); } void draw_bar(int avg, int max) { char bar[barlen+5]; memset(bar, ' ', sizeof(bar)); max /= barlevel; avg /= barlevel; if (avg > barlen) avg = barlen; if (max > barlen) max = barlen; if (avg > 0) memset(bar, '#', avg); if (max > 0) memset(bar + max, '*', 1); bar[barlen+1] = '\0'; printf("%s", bar); fflush(stdout); } void visualize(short *tx, short *rx, int cnt) { int x; float txavg = 0; float rxavg = 0; static int txmax = 0; static int rxmax = 0; static int sametxmax = 0; static int samerxmax = 0; static int txbest = 0; static int rxbest = 0; float ms; static struct timeval last; struct timeval tv; gettimeofday(&tv, NULL); ms = (tv.tv_sec - last.tv_sec) * 1000.0 + (tv.tv_usec - last.tv_usec) / 1000.0; for (x=0;x txbest) txbest = txavg; if (rxavg > rxbest) rxbest = rxavg; /* Update no more than 10 times a second */ if (ms < 100) return; /* Save as max levels, if greater */ if (txbest > txmax) { txmax = txbest; sametxmax = 0; } if (rxbest > rxmax) { rxmax = rxbest; samerxmax = 0; } memcpy(&last, &tv, sizeof(last)); /* Clear screen */ printf("\r "); draw_bar(rxbest, rxmax); printf(" "); draw_bar(txbest, txmax); if (verbose) printf(" Rx: %5d (%5d) Tx: %5d (%5d)", rxbest, rxmax, txbest, txmax); txbest = 0; rxbest = 0; /* If we have had the same max hits for x times, clear the values */ sametxmax++; samerxmax++; if (sametxmax > 6) { txmax = 0; sametxmax = 0; } if (samerxmax > 6) { rxmax = 0; samerxmax = 0; } } /** Print usage message */ static void usage() { fprintf(stderr, "" PROG ": monitor a zaptel channel.\n" "Usage:\n" " " PROG " [options] \n" "Options:\n" " -v: Visual mode. Implies -m.\n" " -vv: Visual/Verbose mode. Implies -m.\n" " -m: Separate rx/tx streams.\n" " -o: Output audio via OSS. Note: Only 'normal'\n" " combined rx/tx streams are output via OSS.\n" " -p: Get a pre-echocanceled stream.\n" " -f FILE: Save combined rx/tx stream to FILE. \n" " Cannot be used with -m.\n" " -r FILE: Save rx stream to FILE. Implies -m.\n" " -t FILE: Save tx stream to FILE. Implies -m.\n" " -F FILE: Save combined pre-echocanceled rx/tx\n" " stream to FILE. Cannot be used with -m.\n" " Implies -p.\n" " -R FILE: Save pre-echocanceled rx stream to FILE. \n" " Implies -m and -p.\n" " -T FILE: Save pre-echocanceled tx stream to FILE. \n" " Implies -m and -p.\n" "" "At least one of -v, -o, -f -F, -r, -R, -t, -T must be\n" "used to set the output.\n" "Examples:\n" "Save a stream to a file:\n" " " PROG " 1 -f stream.raw\n" "Visualize an rx/tx stream and save them to separate files:\n" " " PROG " 1 -v -r streamrx.raw -t streamtx.raw\n" "Play a combined rx/tx stream via OSS and save it to a file\n" " " PROG " 1 -o -f stream.raw\n" "Save a combined normal rx/tx stream and a combined 'preecho' rx/tx stream to files\n" " " PROG " 1 -p -f stream.raw -F streampreecho.raw\n" "Save a normal rx/tx stream and a 'preecho' rx/tx stream to separate files\n" " " PROG " 1 -m -p -r streamrx.raw -t streamtx.raw -R streampreechorx.raw -T streampreechotx.raw\n" ""); } int main(int argc, char *argv[]) { int afd = -1; int pfd[4] = {-1, -1, -1, -1}; short buf[8192]; short buf2[16384]; char *output_file = NULL; int res, res2; int visual = 0; int multichannel = 0; int ossoutput = 0; int preecho = 0; //int savefile = 0; replaced with: (output_file == NULL) int x; struct zt_confinfo zc; int opt; int opt_visual = 0; enum ofh_type ofh_type = ofh_def; int chan_num = -1; while((opt = getopt(argc, argv, "hvmopf:r:t:F:R:T:")) != -1) { switch(opt) { case 'h': usage(); exit(0); break; case '?': usage(); exit(1); break; case 'v': opt_visual++; break; case 'm': multichannel=1; break; case 'o': ossoutput=1; break; case 'p': preecho=1; break; case 'f': output_file = optarg; break; case 'r': output_file = optarg; multichannel = 1; break; case 't': output_file = optarg; multichannel = 1; ofh_type = ofh_tx; break; case 'F': output_file = optarg; ofh_type = ofh_pre; preecho = 1; break; case 'R': output_file = optarg; multichannel = 1; ofh_type = ofh_pre; preecho = 1; break; case 'T': output_file = optarg; multichannel = 1; ofh_type = ofh_pretx; preecho = 1; break; } } /* XXX: have a single "verbose" variable? or separate those * two options? */ if (opt_visual >= 1) visual = 1; if (opt_visual >= 2) verbose = 1; if (output_file && ((ofh[ofh_type] = fopen(output_file, "w"))<0)) { fprintf(stderr, PROG ": Could not open %s for writing: %s\n", output_file, strerror(errno)); exit(1); } if (output_file) printf("Started recording. To stop: cress ctrl-C. " "Recording a raw slinear file, to get a wav file:\n" " sox -r 8000 -s -w -c %d %s %s.wav\n", (multichannel)? 2:1, output_file, output_file); /* XXX: if POSIXLY_CORRECT is set, getopt will stop parsing at the * first non-option argument. In the case of ztmonitor, the * first argument used to be a non-option (the channel), hence * we rely on a posix-incompatibility. * Workaround: in the presence of POSIXLY_CORRECT, use: * ztmonitor [options] */ if (argv[optind]) chan_num =atoi(argv[optind]); if (! chan_num < 1) { fprintf(stderr,"Error: missing channel number\n"); usage(); exit(1); } if (ossoutput) { if (multichannel) { fprintf(stderr, "Multi-channel audio is enabled. OSS output will be disabled.\n"); ossoutput = 0; } else { /* Open audio */ if ((afd = audio_open()) < 0) { fprintf(stderr, "Cannot open audio ...\n"); ossoutput = 0; } } } if (!ossoutput || !multichannel || (output_file != NULL)) { fprintf(stderr, "No output defined. Aborting.\n"); usage(); exit(1); } /* Open Pseudo device */ if ((pfd[0] = pseudo_open()) < 0) exit(1); if (multichannel && ((pfd[1] = pseudo_open()) < 0)) exit(1); if (preecho) { if ((pfd[2] = pseudo_open()) < 0) exit(1); if (multichannel && ((pfd[3] = pseudo_open()) < 0)) exit(1); } /* Conference them */ if (multichannel) { memset(&zc, 0, sizeof(zc)); zc.chan = 0; zc.confno = chan_num; /* Two pseudo's, one for tx, one for rx */ zc.confmode = ZT_CONF_MONITORTX; if (ioctl(pfd[0], ZT_SETCONF, &zc) < 0) { fprintf(stderr, "Unable to monitor: %s\n", strerror(errno)); exit(1); } memset(&zc, 0, sizeof(zc)); zc.chan = 0; zc.confno = chan_num; zc.confmode = ZT_CONF_MONITOR; if (ioctl(pfd[1], ZT_SETCONF, &zc) < 0) { fprintf(stderr, "Unable to monitor: %s\n", strerror(errno)); exit(1); } if (preecho) { memset(&zc, 0, sizeof(zc)); zc.chan = 0; zc.confno = chan_num; /* Two pseudo's, one for tx, one for rx */ zc.confmode = ZT_CONF_MONITOR_TX_PREECHO; if (ioctl(pfd[2], ZT_SETCONF, &zc) < 0) { fprintf(stderr, "Unable to monitor: %s\n", strerror(errno)); exit(1); } memset(&zc, 0, sizeof(zc)); zc.chan = 0; zc.confno = chan_num; zc.confmode = ZT_CONF_MONITOR_RX_PREECHO; if (ioctl(pfd[3], ZT_SETCONF, &zc) < 0) { fprintf(stderr, "Unable to monitor: %s\n", strerror(errno)); exit(1); } } } else { memset(&zc, 0, sizeof(zc)); zc.chan = 0; zc.confno = chan_num; zc.confmode = ZT_CONF_MONITORBOTH; if (ioctl(pfd[0], ZT_SETCONF, &zc) < 0) { fprintf(stderr, "Unable to monitor: %s\n", strerror(errno)); exit(1); } if (preecho) { memset(&zc, 0, sizeof(zc)); zc.chan = 0; zc.confno = chan_num; zc.confmode = ZT_CONF_MONITORBOTH_PREECHO; if (ioctl(pfd[2], ZT_SETCONF, &zc) < 0) { fprintf(stderr, "Unable to monitor: %s\n", strerror(errno)); exit(1); } } } if (visual) { printf("\nVisual Audio Levels.\n"); printf("--------------------\n"); printf(" Use zapata.conf file to adjust the gains if needed.\n\n"); printf("( # = Audio Level * = Max Audio Hit )\n"); draw_barheader(); } /* Now, copy from pseudo to audio */ for (;;) { res = read(pfd[0], buf, sizeof(buf)); if (res < 1) break; if (ofh[ofh_def]) fwrite(buf, 1, res, ofh[ofh_def]); if (multichannel) { res2 = read(pfd[1], buf2, res); if (res2 < 1) break; if (ofh[ofh_tx]) fwrite(buf2, 1, res2, ofh[ofh_tx]); if (visual) { if (res == res2) visualize((short *)buf, (short *)buf2, res/2); else printf("Huh? res = %d, res2 = %d?\n", res, res2); } } if (preecho) { res = read(pfd[2], buf, sizeof(buf)); if (res < 1) break; if (ofh[ofh_pre]) fwrite(buf, 1, res, ofh[ofh_pre]); if (multichannel) { res2 = read(pfd[3], buf2, res); if (res2 < 1) break; if (ofh[ofh_pretx]) fwrite(buf2, 1, res, ofh[ofh_pretx]); /* XXX How are we going to visualize the preecho set of streams? if (visual) { if (res == res2) visualize((short *)buf, (short *)buf2, res/2); else printf("Huh? res = %d, res2 = %d?\n", res, res2); } */ } } if (ossoutput && afd) { if (stereo) { for (x=0;x