From a461c286fb7d9aae69594f2d89b4f0a853ef57e2 Mon Sep 17 00:00:00 2001 From: qwell Date: Tue, 24 Apr 2007 18:54:06 +0000 Subject: merge (manually) ztmonitor pre-echocan debugging git-svn-id: http://svn.digium.com/svn/zaptel/trunk@2436 5390a7c7-147a-4af0-8ec9-7488f05a26cb --- jpah.h | 104 +++++++++++++++++++++++++ zaptel.c | 126 ++++++++++++++++++++++++++++++- zaptel.h | 4 + zconfig.h | 7 ++ ztmonitor.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++++------------ 5 files changed, 438 insertions(+), 49 deletions(-) create mode 100644 jpah.h diff --git a/jpah.h b/jpah.h new file mode 100644 index 0000000..f72c5fa --- /dev/null +++ b/jpah.h @@ -0,0 +1,104 @@ +/* + * ECHO_CAN_JP1 + * + * by Jason Parker + * + * Based upon mg2ec.h - sort of. + * This "echo can" will completely hose your audio. + * Don't use it unless you're absolutely sure you know what you're doing. + * + * Copyright (C) 2007, Digium, Inc. + * + * This program is free software and may be used and + * distributed according to the terms of the GNU + * General Public License, incorporated herein by + * reference. + * + */ + +#ifndef _JP_ECHO_H +#define _JP_ECHO_H + +#ifdef __KERNEL__ +#include +#include +#define MALLOC(a) kmalloc((a), GFP_KERNEL) +#define FREE(a) kfree(a) +#else +#include +#include +#include +#include +#define MALLOC(a) malloc(a) +#define FREE(a) free(a) +#endif + +/* Echo canceller definition */ +struct echo_can_state { + /* an arbitrary ID for this echo can - this really should be settable from the calling channel... */ + int id; + + /* absolute time - aka. sample number index - essentially the number of samples since this can was init'ed */ + int i_d; +}; + +static void echo_can_init(void) +{ + printk("Zaptel Audio Hoser: JP1\n"); +} + +static void echo_can_identify(char *buf, size_t len) +{ + strncpy(buf, "JP1", len); +} + +static void echo_can_shutdown(void) +{ +} + +static inline void init_cc(struct echo_can_state *ec) +{ + void *ptr = ec; + unsigned long tmp; + /* Double-word align past end of state */ + ptr += sizeof(struct echo_can_state); + tmp = (unsigned long)ptr; + tmp += 3; + tmp &= ~3L; + ptr = (void *)tmp; +} + +static inline void echo_can_free(struct echo_can_state *ec) +{ + FREE(ec); +} + +static inline short echo_can_update(struct echo_can_state *ec, short iref, short isig) +{ + static int blah = 0; + + if (blah < 2) { + blah++; + return 0; + } else { + blah = (blah + 1) % 3; + return isig; + } +} + +static inline struct echo_can_state *echo_can_create(int len, int adaption_mode) +{ + struct echo_can_state *ec; + ec = (struct echo_can_state *)MALLOC(sizeof(struct echo_can_state) + 4); /* align */ + if (ec) { + memset(ec, 0, sizeof(struct echo_can_state) + 4); /* align */ + init_cc(ec); + } + return ec; +} + +static inline int echo_can_traintap(struct echo_can_state *ec, int pos, short val) +{ + return 0; +} +#endif diff --git a/zaptel.c b/zaptel.c index d47bcb0..63d31de 100644 --- a/zaptel.c +++ b/zaptel.c @@ -115,6 +115,8 @@ #elif defined(ECHO_CAN_MG2) #define ZAPTEL_ECHO_CANCELLER "MG2" #include "mg2ec.h" +#elif defined(ECHO_CAN_JP1) +#include "jpah.h" #else #define ZAPTEL_ECHO_CANCELLER "MARK3" #include "mec3.h" @@ -991,6 +993,7 @@ static void close_channel(struct zt_chan *chan) void *rxgain = NULL; struct echo_can_state *ec = NULL; int oldconf; + short *readchunkpreec; #ifdef CONFIG_ZAPATA_PPP struct ppp_channel *ppp; #endif @@ -1005,6 +1008,8 @@ static void close_channel(struct zt_chan *chan) #endif ec = chan->ec; chan->ec = NULL; + readchunkpreec = chan->readchunkpreec; + chan->readchunkpreec = NULL; chan->curtone = NULL; chan->curzone = NULL; chan->cadencepos = 0; @@ -1065,6 +1070,7 @@ static void close_channel(struct zt_chan *chan) kfree(rxgain); if (ec) echo_can_free(ec); + kfree(readchunkpreec); #ifdef CONFIG_ZAPATA_PPP if (ppp) { @@ -1722,6 +1728,9 @@ static void zt_chan_unreg(struct zt_chan *chan) ((chans[x]->confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR || (chans[x]->confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORTX || (chans[x]->confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORBOTH || + (chans[x]->confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR_RX_PREECHO || + (chans[x]->confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR_TX_PREECHO || + (chans[x]->confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORBOTH_PREECHO || (chans[x]->confmode & ZT_CONF_MODE_MASK) == ZT_CONF_DIGITALMON)) { /* Take them out of conference with us */ /* release conference resource if any */ @@ -3927,7 +3936,10 @@ static int zt_chanandpseudo_ioctl(struct inode *inode, struct file *file, unsign if (!(chans[i]->flags & ZT_FLAG_AUDIO)) return (-EINVAL); if ((stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR || (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORTX || - (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORBOTH) { + (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORBOTH || + (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR_RX_PREECHO || + (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR_TX_PREECHO || + (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORBOTH_PREECHO) { /* Monitor mode -- it's a channel */ if ((stack.conf.confno < 0) || (stack.conf.confno >= ZT_MAX_CHANNELS) || !chans[stack.conf.confno]) return(-EINVAL); } else { @@ -3980,6 +3992,16 @@ static int zt_chanandpseudo_ioctl(struct inode *inode, struct file *file, unsign /* Get alias */ chans[i]->_confn = zt_get_conf_alias(stack.conf.confno); } + + if ((stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR_RX_PREECHO || + (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITOR_TX_PREECHO || + (stack.conf.confmode & ZT_CONF_MODE_MASK) == ZT_CONF_MONITORBOTH_PREECHO) + chans[stack.conf.confno]->readchunkpreec = kmalloc(sizeof(*chans[stack.conf.confno]->readchunkpreec) * ZT_CHUNKSIZE, GFP_KERNEL); + else { + kfree(chans[stack.conf.confno]->readchunkpreec); + chans[stack.conf.confno]->readchunkpreec = NULL; + } + spin_unlock_irqrestore(&chan->lock, flags); spin_unlock_irqrestore(&bigzaplock, flagso); if (copy_to_user((struct zt_confinfo *) data,&stack.conf,sizeof(stack.conf))) @@ -5000,6 +5022,51 @@ static inline void __zt_process_getaudio_chunk(struct zt_chan *ss, unsigned char for (x=0;xflags & ZT_FLAG_PSEUDO) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(getlin, chans[ms->confna]->flags & ZT_FLAG_PSEUDO ? + chans[ms->confna]->readchunkpreec : chans[ms->confna]->putlin); + for (x = 0; x < ZT_CHUNKSIZE; x++) + txb[x] = ZT_LIN2X(getlin[x], ms); + + break; + case ZT_CONF_MONITOR_TX_PREECHO: /* Monitor a channel's tx mode */ + /* if a pseudo-channel, ignore */ + if (ms->flags & ZT_FLAG_PSEUDO) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(getlin, chans[ms->confna]->flags & ZT_FLAG_PSEUDO ? + chans[ms->confna]->putlin : chans[ms->confna]->readchunkpreec); + for (x = 0; x < ZT_CHUNKSIZE; x++) + txb[x] = ZT_LIN2X(getlin[x], ms); + + break; + case ZT_CONF_MONITORBOTH_PREECHO: /* monitor a channel's rx and tx mode */ + /* if a pseudo-channel, ignore */ + if (ms->flags & ZT_FLAG_PSEUDO) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + ACSS(getlin, chans[ms->confna]->putlin); + ACSS(getlin, chans[ms->confna]->readchunkpreec); + + for (x = 0; x < ZT_CHUNKSIZE; x++) + txb[x] = ZT_LIN2X(getlin[x], ms); + + break; case ZT_CONF_REALANDPSEUDO: /* This strange mode takes the transmit buffer and puts it on the conference, minus its last sample, @@ -5688,7 +5755,16 @@ static inline void __zt_ec_chunk(struct zt_chan *ss, unsigned char *rxchunk, con short rxlin, txlin; int x; unsigned long flags; + spin_lock_irqsave(&ss->lock, flags); + + if (ss->readchunkpreec) { + /* Save a copy of the audio before the echo can has its way with it */ + for (x = 0; x < ZT_CHUNKSIZE; x++) + /* We only ever really need to deal with signed linear - let's just convert it now */ + ss->readchunkpreec[x] = ZT_XLAW(rxchunk[x], ss); + } + /* Perform echo cancellation on a chunk if necessary */ if (ss->ec) { #if defined(CONFIG_ZAPTEL_MMX) || defined(ECHO_CAN_FP) @@ -5912,6 +5988,54 @@ static inline void __zt_process_putaudio_chunk(struct zt_chan *ss, unsigned char for(x=0;xflags & ZT_FLAG_PSEUDO)) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(putlin, chans[ms->confna]->flags & ZT_FLAG_PSEUDO ? + chans[ms->confna]->getlin : chans[ms->confna]->readchunkpreec); + for (x = 0; x < ZT_CHUNKSIZE; x++) + rxb[x] = ZT_LIN2X(putlin[x], ms); + + break; + case ZT_CONF_MONITOR_TX_PREECHO: /* Monitor a channel's tx mode */ + /* if not a pseudo-channel, ignore */ + if (!(ms->flags & ZT_FLAG_PSEUDO)) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(putlin, chans[ms->confna]->flags & ZT_FLAG_PSEUDO ? + chans[ms->confna]->readchunkpreec : chans[ms->confna]->getlin); + for (x = 0; x < ZT_CHUNKSIZE; x++) + rxb[x] = ZT_LIN2X(putlin[x], ms); + + break; + case ZT_CONF_MONITORBOTH_PREECHO: /* Monitor a channel's tx and rx mode */ + /* if not a pseudo-channel, ignore */ + if (!(ms->flags & ZT_FLAG_PSEUDO)) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Note: Technically, saturation should be done at + the end of the whole addition, but for performance + reasons, we don't do that. Besides, it only matters + when you're so loud you're clipping anyway */ + ACSS(putlin, chans[ms->confna]->getlin); + ACSS(putlin, chans[ms->confna]->readchunkpreec); + for (x = 0; x < ZT_CHUNKSIZE; x++) + rxb[x] = ZT_LIN2X(putlin[x], ms); + + break; case ZT_CONF_REALANDPSEUDO: /* do normal conf mode processing */ if (ms->confmode & ZT_CONF_TALKER) { diff --git a/zaptel.h b/zaptel.h index 0f862e9..1d64092 100644 --- a/zaptel.h +++ b/zaptel.h @@ -975,6 +975,9 @@ struct zt_tone_def { /* Structure for zone programming */ #define ZT_CONF_CONFANNMON 7 /* conference announce/monitor mode */ #define ZT_CONF_REALANDPSEUDO 8 /* real and pseudo port both on conf */ #define ZT_CONF_DIGITALMON 9 /* Do not decode or interpret */ +#define ZT_CONF_MONITOR_RX_PREECHO 10 /* monitor mode (rx of other chan) - before echo can is done */ +#define ZT_CONF_MONITOR_TX_PREECHO 11 /* monitor mode (tx of other chan) - before echo can is done */ +#define ZT_CONF_MONITORBOTH_PREECHO 12 /* monitor mode (rx & tx of other chan) - before echo can is done */ #define ZT_CONF_FLAG_MASK 0xff00 /* mask for flags */ #define ZT_CONF_LISTENER 0x100 /* is a listener on the conference */ #define ZT_CONF_TALKER 0x200 /* is a talker on the conference */ @@ -1120,6 +1123,7 @@ struct zt_chan { u_char swritechunk[ZT_MAX_CHUNKSIZE]; /* Buffer to be written */ u_char *readchunk; /* Actual place to read from */ u_char sreadchunk[ZT_MAX_CHUNKSIZE]; /* Preallocated static area */ + short *readchunkpreec; /* Pointer to tx and rx gain tables */ u_char *rxgain; diff --git a/zconfig.h b/zconfig.h index cbfec91..d40d5e0 100644 --- a/zconfig.h +++ b/zconfig.h @@ -65,6 +65,13 @@ /* This is the new latest and greatest */ #define ECHO_CAN_MG2 +/* + * This is only technically an "echo canceller"... + * It purposely drops 2 out of 3 samples and sounds horrible. + * You really only want this for testing "echo cancelled" audio. + */ +/* #define ECHO_CAN_JP1 */ + /* * Uncomment for aggressive residual echo suppression under * MARK2, KB1, and MG2 echo canceler diff --git a/ztmonitor.c b/ztmonitor.c index 43333e3..26b6ccb 100644 --- a/ztmonitor.c +++ b/ztmonitor.c @@ -51,11 +51,11 @@ #define FRAG_SIZE 8 -/* Put the ofh (output file handle) outside +/* Put the ofh (output file handles) outside * the main loop in case we ever add a signal - * hanlder. + * handler. */ -static FILE* ofh = 0; +static FILE* ofh[4] = {0, 0, 0, 0}; static int stereo = 0; static int verbose = 0; @@ -255,62 +255,148 @@ void visualize(short *tx, short *rx, int cnt) int main(int argc, char *argv[]) { - int afd = -1, pfd, pfd2 = -1; + int afd = -1; + int pfd[4] = {-1, -1, -1, -1}; short buf[8192]; short buf2[16384]; char output_file[255]; int res, res2; int visual = 0; - int x,i; + int multichannel = 0; + int ossoutput = 0; + int preecho = 0; + int savefile = 0; + int x, i; struct zt_confinfo zc; if ((argc < 2) || (atoi(argv[1]) < 1)) { - fprintf(stderr, "Usage: ztmonitor [-v[v]] [-f FILE]\n"); + fprintf(stderr, "Usage: ztmonitor [-v[v]] [-m] [-o] [-p] [-f FILE | -r FILE1 -t FILE2] [-F FILE | -R FILE1 -T FILE2]\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -v: Visual mode. Implies -m.\n"); + fprintf(stderr, " -vv: Visual/Verbose mode. Implies -m.\n"); + fprintf(stderr, " -m: Separate rx/tx streams.\n"); + fprintf(stderr, " -o: Output audio via OSS. Note: Only 'normal' combined rx/tx streams are output via OSS.\n"); + fprintf(stderr, " -p: Get a pre-echocanceled stream.\n"); + fprintf(stderr, " -f FILE: Save combined rx/tx stream to FILE. Cannot be used with -m.\n"); + fprintf(stderr, " -r FILE: Save rx stream to FILE. Implies -m.\n"); + fprintf(stderr, " -t FILE: Save tx stream to FILE. Implies -m.\n"); + fprintf(stderr, " -F FILE: Save combined pre-echocanceled rx/tx stream to FILE. Cannot be used with -m. Implies -p.\n"); + fprintf(stderr, " -R FILE: Save pre-echocanceled rx stream to FILE. Implies -m and -p.\n"); + fprintf(stderr, " -T FILE: Save pre-echocanceled tx stream to FILE. Implies -m and -p.\n"); + fprintf(stderr, "Examples:\n"); + fprintf(stderr, "Save a stream to a file\n"); + fprintf(stderr, " ztmonitor 1 -f stream.raw\n"); + fprintf(stderr, "Visualize an rx/tx stream and save them to separate files.\n"); + fprintf(stderr, " ztmonitor 1 -v -r streamrx.raw -t streamtx.raw\n"); + fprintf(stderr, "Play a combined rx/tx stream via OSS and save it to a file\n"); + fprintf(stderr, " ztmonitor 1 -o -f stream.raw\n"); + fprintf(stderr, "Play a combined rx/tx stream via OSS and save them to separate files\n"); + fprintf(stderr, " ztmonitor 1 -m -o -r streamrx.raw -t streamtx.raw\n"); + fprintf(stderr, "Save a combined normal rx/tx stream and a combined 'preecho' rx/tx stream to files\n"); + fprintf(stderr, " ztmonitor 1 -m -p -f stream.raw -F streampreecho.raw\n"); + fprintf(stderr, "Save a normal rx/tx stream and a 'preecho' rx/tx stream to separate files\n"); + fprintf(stderr, " ztmonitor 1 -m -p -r streamrx.raw -t streamtx.raw -R streampreechorx.raw -T streampreechotx.raw\n"); exit(1); } for (i = 2; i < argc; ++i) { if (!strcmp(argv[i], "-v")) { - if (visual) - verbose = 1; + if (visual) + verbose = 1; visual = 1; + multichannel = 1; } else if (!strcmp(argv[i], "-vv")) { visual = 1; verbose = 1; - } else if (!strcmp(argv[i], "-f") && (i+1) < argc) { - ++i; /*we care about hte file name */ + multichannel = 1; + } else if ((!strcmp(argv[i], "-f") || !strcmp(argv[i], "-r") || !strcmp(argv[i], "-t") + || !strcmp(argv[i], "-F") || !strcmp(argv[i], "-R") || !strcmp(argv[i], "-T")) + && (i+1) < argc) { + /* Set which file descriptor to use */ + if (!strcmp(argv[i], "-f")) { + savefile = 1; + x = 0; + } else if (!strcmp(argv[i], "-r")) { + savefile = 1; + multichannel = 1; + x = 0; + } else if (!strcmp(argv[i], "-t")) { + savefile = 1; + multichannel = 1; + x = 1; + } else if (!strcmp(argv[i], "-F")) { + savefile = 1; + preecho = 1; + x = 2; + } else if (!strcmp(argv[i], "-R")) { + savefile = 1; + multichannel = 1; + preecho = 1; + x = 2; + } else if (!strcmp(argv[i], "-T")) { + savefile = 1; + multichannel = 1; + preecho = 1; + x = 3; + } else + x = 0; + + ++i; /* we care about the file name */ if (strlen(argv[i]) < 255 ) { strcpy(output_file, argv[i]); fprintf(stderr, "Output to %s\n", output_file); - if ((ofh = fopen(output_file, "w"))<0) { + if ((ofh[x] = fopen(output_file, "w"))<0) { fprintf(stderr, "Could not open %s for writing: %s\n", output_file, strerror(errno)); - exit(0); + exit(1); } - fprintf(stderr, "Run e.g., 'sox -r 8000 -s -w -c 1 file.raw file.wav' to convert.\n"); + fprintf(stderr, "Run e.g., 'sox -r 8000 -s -w -c 1 %s file.wav' to convert.\n", output_file); } else { fprintf(stderr, "File Name %s too long\n",argv[i+1]); } + } else if (!strcmp(argv[i], "-m")) { + multichannel = 1; + } else if (!strcmp(argv[i], "-o")) { + ossoutput = 1; + } else if (!strcmp(argv[i], "-p")) { + preecho = 1; } } - if (!visual) { - /* Open audio */ - if ((afd = audio_open()) < 0) { - printf("Cannot open audio ...\n"); - if (!ofh) exit(0); + + if (ossoutput) { + if (multichannel) { + printf("Multi-channel audio is enabled. OSS output will be disabled.\n"); + ossoutput = 0; + } else { + /* Open audio */ + if ((afd = audio_open()) < 0) { + printf("Cannot open audio ...\n"); + ossoutput = 0; + } } } + if (!ossoutput && !multichannel && !savefile) { + fprintf(stderr, "Nothing to do with the stream(s) ...\n"); + exit(1); + } + /* Open Pseudo device */ - if ((pfd = pseudo_open()) < 0) + if ((pfd[0] = pseudo_open()) < 0) exit(1); - if (visual && ((pfd2 = pseudo_open()) < 0)) + 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 */ - memset(&zc, 0, sizeof(zc)); - zc.chan = 0; - zc.confno = atoi(argv[1]); - if (visual) { + if (multichannel) { + memset(&zc, 0, sizeof(zc)); + zc.chan = 0; + zc.confno = atoi(argv[1]); /* Two pseudo's, one for tx, one for rx */ zc.confmode = ZT_CONF_MONITORTX; - if (ioctl(pfd, ZT_SETCONF, &zc) < 0) { + if (ioctl(pfd[0], ZT_SETCONF, &zc) < 0) { fprintf(stderr, "Unable to monitor: %s\n", strerror(errno)); exit(1); } @@ -318,16 +404,48 @@ int main(int argc, char *argv[]) zc.chan = 0; zc.confno = atoi(argv[1]); zc.confmode = ZT_CONF_MONITOR; - if (ioctl(pfd2, ZT_SETCONF, &zc) < 0) { + 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 = atoi(argv[1]); + /* 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 = atoi(argv[1]); + 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 = atoi(argv[1]); zc.confmode = ZT_CONF_MONITORBOTH; - if (ioctl(pfd, ZT_SETCONF, &zc) < 0) { + 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 = atoi(argv[1]); + 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"); @@ -338,31 +456,63 @@ int main(int argc, char *argv[]) } /* Now, copy from pseudo to audio */ for (;;) { - res = read(pfd, buf, sizeof(buf)); - if (res < 1) + res = read(pfd[0], buf, sizeof(buf)); + if (res < 1) break; - if (visual) { - res2 = read(pfd2, buf2, res); - if (res2 < 1) + if (ofh[0]) + fwrite(buf, 1, res, ofh[0]); + + if (multichannel) { + res2 = read(pfd[1], buf2, res); + if (res2 < 1) break; - if (res == res2) - visualize((short *)buf, (short *)buf2, res/2); - else - printf("Huh? res = %d, res2 = %d?\n", res, res2); - - } else { - if (ofh) - fwrite(buf, 1, res, ofh); - if (afd) { - if (stereo) { - for (x=0;x