/* intercom.c -- A real-time audio communication utility Copyright (C) 2001-2003 Shane Wegner This file is part of Intercom. Intercom is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. Intercom 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 version 2 of the GNU General Public License along with Intercom; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA To contact the author, please send email to shane@cm.nu. */ /* $Id: intercom.c,v 1.83 2003/02/13 20:22:49 shane Exp $ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "unistd.h" #ifdef HAVE_STDINT_H #include #else #include #endif #ifdef HAVE_GETOPT_H #include #else #include "subst/getopt.h" #endif #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_SYS_SOUNDCARD_H) #include #elif defined(HAVE_MACHINE_SOUNDCARD_H) #include #else #include #endif #include #include #ifdef HAVE_NETINET_IN_SYSTM_H #include #endif #ifdef HAVE_NETINET_IP_H #include #endif #include #include #ifdef USE_GETTEXT #ifdef HAVE_LOCALE_H #include #endif #include #define _(String) gettext(String) #define gettext_noop(String) (String) #define N_(String) gettext_noop(String) #else #define _(String) (String) #define N_(String) (String) #endif #include "intercom.h" #include "protocol.h" #include "iobuff.h" #include "console.h" #include "pkthandler.h" #include "userdefs.h" #include "codecs.h" #include "hooks.h" #ifdef USE_CRYPTO #include "crypto.h" #endif /* Global variables */ int done = 0; #ifdef USE_CRYPTO #define CALL_INIT_CRYPTO .cryptinfo = {CALL_CRYPT_NONE, }, #else #define CALL_INIT_CRYPTO #endif #define CALL_INIT_STATE (struct call_info){ \ .state = CALL_STATE_IDLE, .csock = -1, .dsock = -1, .loopback = 0, \ .rdsp = -1, .pdsp = -1, \ .snd_sequence = 0, .rcv_sequence = 0, \ .ogain = NULL, .igain = NULL, \ CALL_INIT_CRYPTO \ .connstats = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} } struct call_info call = CALL_INIT_STATE; struct compression_format compression_table[]={ {AUDIO_COMPRESSION_NONE, "none", "None"}, #ifdef HAVE_LIBGSM {AUDIO_COMPRESSION_GSM, "gsm", "GSM 6.10"}, #endif {AUDIO_COMPRESSION_G711U, "g.711-ulaw", "CCITT G.711 U-LAW"}, {AUDIO_COMPRESSION_G711A, "g.711-alaw", "CCITT G.711 A-LAW"}, #ifdef HAVE_G72X {AUDIO_COMPRESSION_G721, "g.721", "CCITT G.721"}, {AUDIO_COMPRESSION_G723_24, "g.723-24", "CCITT G.723 (24 kbit/s)"}, {AUDIO_COMPRESSION_G723_40, "g.723-40", "CCITT G.723 (40 kbit/s)"}, #endif {0, NULL, NULL}}; /* Local variables */ static fd_set f_rset, f_wset; static char audiopkt[4096]; #ifdef USE_CRYPTO static char cr_audiopkt[4096]; #endif static int isplaying = 1, dropskip = 0; static int listener; /* Config variables */ static float silence_time; static int silence_thresh, min_buffsize, max_buffsize; void refresh_userdefs(void) { double g; /* handle gain changes */ g = opt_get_float("input_gain"); if (g != 0.0 && (call.igain == NULL || call.igain->gain_db != g)) { if (call.igain != NULL) gain_free(call.igain); call.igain = gain_init(g); } else if (g == 0.0 && call.igain != NULL) { gain_free(call.igain); call.igain = NULL; } g = opt_get_float("output_gain"); if (g != 0.0 && (call.ogain == NULL || call.ogain->gain_db != g)) { if (call.ogain != NULL) gain_free(call.ogain); call.ogain = gain_init(g); } else if (g == 0.0 && call.ogain != NULL) { gain_free(call.ogain); call.ogain = NULL; } silence_time = opt_get_float("silence_time"); silence_thresh = opt_get_int("silence_thresh"); min_buffsize = opt_get_int("udp_minsize"); max_buffsize = opt_get_int("udp_maxsize"); } void sighandler(int signum) { switch(signum) { case SIGHUP: hk_trigger(hk_sighup); break; case SIGINT: case SIGTERM: done = 1; break; } signal(signum, sighandler); } static char **ic_environ = NULL; static int ic_envlen = 0; void icsetenv(const char *var, const char *value) { int i, exists = 0; char rv[strlen(var) + 4]; strcpy(rv, "ic_"); strcpy(&rv[3], var); for (i = 0; i < ic_envlen; i++) if (!strcmp(rv, ic_environ[i])) exists = 1; if (!exists) { ic_environ = realloc(ic_environ, sizeof(char **) * (ic_envlen + 1)); ic_environ[ic_envlen++] = strdup(rv); } setenv(rv, value, 1); } int do_connect(const char *hostname, int port, struct sockaddr_in *r_address) { int sock; struct in_addr addr; struct sockaddr_in address; struct hostent *host = gethostbyname(hostname); if (host == NULL) { herror("gethostbyname"); return -1; } memcpy(&addr, host->h_addr_list[0], sizeof(addr)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = addr.s_addr; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { perror("socket"); return -1; } if (connect(sock, (struct sockaddr *)&address, sizeof(address)) == -1) { perror("connect"); close(sock); return -1; } /* if (fcntl(sock, F_SETFL, O_RDWR | O_NONBLOCK) == -1) { perror("fcntl"); close(sock); return -1; } */ if (r_address != NULL) memcpy(r_address, &address, sizeof(address)); return sock; } /* do_connect */ static int do_listen(int port) { int sock; struct in_addr addr; struct sockaddr_in address; struct hostent *host = gethostbyname("0.0.0.0"); memcpy(&addr, host->h_addr_list[0], sizeof(addr)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = addr.s_addr; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { perror("socket"); return -1; } if (bind(sock, (struct sockaddr *)&address, sizeof(address)) == -1) { perror("bind"); close(sock); return -1; } if (fcntl(sock, F_SETFL, O_RDWR | O_NONBLOCK) == -1) { perror("fcntl"); close(sock); return -1; } if (listen(sock, 1) == -1) { perror("listen"); close(sock); return -1; } return sock; } /* do_listen */ int dsp_setup(const char *device, int mode, struct audio_params *params) { int fd, r, caps; if ((fd = open(device, (mode|O_NONBLOCK))) == -1) { perror("open"); return -1; } if (ioctl(fd, SNDCTL_DSP_GETCAPS, &caps) == -1) { perror("sndctl_dsp_getcaps failed:"); goto err; } if ((mode & O_RDWR) == O_RDWR) { if ((caps & DSP_CAP_DUPLEX) != DSP_CAP_DUPLEX) { fputs(_("Error: missing duplex capability\n"), stderr); goto err; } if (ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0) == -1) perror("SNDCTL_DSP_SETDUPLEX"); } if (ioctl(fd, SNDCTL_DSP_RESET, 0) == -1) { perror("sound ioctl(SNDCTL_DSP_RESET)"); goto err; } /* r = APF_NETWORK; if (ioctl(fd, SNDCTL_DSP_PROFILE, &r) == -1) perror("sound ioctl(SNDCTL_DSP_PROFILE)"); */ r = (0x02 << 16) + logb(params->rate * (params->bitwidth / 8) / 20); if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &r) == -1) perror("sound ioctl(SNDCTL_DSP_SETFRAGMENT)"); r = AFMT_S16_LE; if (ioctl(fd, SNDCTL_DSP_SETFMT, &r) == -1) { perror("sound ioctl(SNDCTL_DSP_SETFMT)"); goto err; } r = 0; if (ioctl(fd, SNDCTL_DSP_STEREO, &r) == -1) { perror("sound ioctl(SNDCTL_DSP_CHANNELS)"); goto err; } r = params->rate; if (ioctl(fd, SNDCTL_DSP_SPEED, &r) == -1) { perror("sound ioctl(SNDCTL_DSP_SPEED)"); goto err; } if ((caps & DSP_CAP_TRIGGER) == DSP_CAP_TRIGGER) { if ((mode & O_RDWR) == O_RDWR) r = (PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT); else if ((mode & O_WRONLY) == O_WRONLY) r = PCM_ENABLE_OUTPUT; else r = PCM_ENABLE_INPUT; if (ioctl(fd, SNDCTL_DSP_SETTRIGGER, &r) == -1) perror("SNDCTL_DSP_SETTRIGGER failed:"); } return fd; err: close(fd); return -1; } /* dsp_setup */ #if (DEBUG >= 2) void hexdump(const char *buff, size_t n) { size_t i; for (i = 0; i < n; i++) printf(" %02x", (unsigned int)buff[i] & 0xff); } static void pkthexdump(const char *buff, size_t n) { fputs(_("debug: Packet hex dump:"), stdout); hexdump(buff, n); putchar('\n'); } #endif void queuepacket(struct iobuff *iob, uint16_t type, char *pkt, size_t pktlen) { char buff[sizeof(struct pkt_header) + pktlen]; struct pkt_header *ph = (struct pkt_header *)buff; ph->p_type = type; ph->p_len = sizeof(ph) + pktlen; memcpy(&buff[sizeof(*ph)], pkt, pktlen); call.connstats.cpkts_sent++; call.connstats.cbytes_sent += ph->p_len; if (opt_get_int("verbose")) { #if (DEBUG >= 1) printf(_("debug: Queueing packet type %u of %u bytes.\n"), type, ph->p_len); #if (DEBUG >= 2) pkthexdump(buff, ph->p_len); #endif #endif } iob_queue(iob, buff, ph->p_len); } void closecall(void) { int i; if (TEST_BIT(call.state, CALL_STATE_CONNECTED)) { if (call.csock != -1) { shutdown(call.csock, 2); close(call.csock); } if (call.dsock != -1) { shutdown(call.dsock, 2); close(call.dsock); } iob_flush(&call.csrq); iob_flush(&call.cssq); iob_flush(&call.rdspq); iob_flush(&call.rdspq); if (call.rdsp != -1) { close(call.rdsp); free(call.rdsp_buff); if (call.pdsp == call.rdsp) call.pdsp = -1; } if (call.pdsp != -1) { close(call.pdsp); free(call.pdsp_buff); } /* Free the environment */ for (i = 0; i < ic_envlen; i++) { unsetenv(ic_environ[i]); free(ic_environ[i]); } free(ic_environ); ic_environ = NULL; ic_envlen = 0; /* And repopulate with userdefs. TODO: optimize this out. */ for (i = 0; useropts[i].key != NULL; i++) if (strcmp(useropts[i].key, "crypt_passphrase")) icsetenv(useropts[i].key, useropts[i].value); memcpy(&call, &CALL_INIT_STATE, sizeof(call)); iob_init(&call.cssq); iob_setminbytes(&call.cssq, IOB_PAGESIZE); iob_init(&call.csrq); iob_setminbytes(&call.csrq, IOB_PAGESIZE); iob_init(&call.rdspq); iob_init(&call.pdspq); codecs_init(); } } int setup_udpsock(struct sockaddr_in *addr) { unsigned char tos; int l = sizeof(*addr); int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (s == -1) { perror("socket"); return -1; } addr->sin_family = AF_INET; /* On Linux at least, this gives us a random port. */ /* TODO: verify portability */ addr->sin_port = 0; addr->sin_addr.s_addr = INADDR_ANY; if (bind(s, (struct sockaddr *)addr, sizeof(*addr)) == -1) { perror("bind"); close(s); return -1; } getsockname(s, (struct sockaddr *)addr, &l); if (fcntl(s, F_SETFL, O_RDWR | O_NONBLOCK) == -1) { perror("fcntl"); close(s); return -1; } #ifdef HAVE_NETINET_IP_H if (opt_get_int("iptos_lowdelay") > 0) { tos = IPTOS_LOWDELAY; if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, 1) == -1) if (opt_get_int("verbose")) perror("setsockopt failed for IP_LOWDELAY"); } #endif return s; } void send_errorpkt(struct iobuff *iob, uint16_t errorcode) { static const struct pkt_error err_list[]= { {PKT_ERROR_PKTUNKNOWN, N_("Unknown packet type")}, {PKT_ERROR_PKTORDER, N_("Packet ordering error")}, {PKT_ERROR_AUDIOFMT, N_("Audio format error")}, {PKT_ERROR_PROTO_VERSION, N_("Protocol version mismatch")}, {PKT_ERROR_NOCRYPT, N_("Encryption format not supported")}, }; static size_t err_list_nr = sizeof(err_list) / sizeof(struct pkt_error); size_t i; struct pkt_error errorpkt; errorpkt.errorcode = errorcode; *errorpkt.errormsg = 0; memset(errorpkt.errormsg, 0, sizeof(errorpkt.errormsg)); for (i = 0; i < err_list_nr; i++) if (err_list[i].errorcode == errorcode) { strncpy(errorpkt.errormsg, err_list[i].errormsg, sizeof(errorpkt.errormsg)); break; } queuepacket(iob, PKT_TYPE_ERROR, (char *)&errorpkt, sizeof(errorpkt)); } int int_compression(const char *s) { size_t i; for (i = 0; compression_table[i].name != NULL; i++) if (!strcasecmp(s, compression_table[i].name)) return compression_table[i].fmt; return -1; } char *str_compression(int v) { size_t i; for (i = 0; compression_table[i].name != NULL; i++) if (v == compression_table[i].fmt) return compression_table[i].name; return NULL; } /* Strip non-printable characters from s, modifying it in the process */ char *to_printable(char *s) { unsigned char *p; for (p = s; *p; p++) { if (*p < 32 || *p > 127) *p = '.'; } return s; } static void handle_packet(char *pktbuff) { struct pkt_header *ph = (struct pkt_header *)pktbuff; char *pktdata = &pktbuff[sizeof(ph)]; if (opt_get_int("verbose")) { printf(_("Received control packet of type %u\n"), ph->p_type); #if (DEBUG >= 2) pkthexdump(pktbuff, ph->p_len); #endif } switch(ph->p_type) { case PKT_TYPE_HELLO: handle_pkt_hello((struct pkt_hello *)pktdata); break; case PKT_TYPE_CALL: handle_pkt_call((struct pkt_call *)pktdata); break; case PKT_TYPE_CALLRESPONSE: handle_pkt_callresponse((struct pkt_callresponse *)pktdata); break; case PKT_TYPE_AUDIOSTART: handle_pkt_audiostart((struct pkt_audiostart *)pktdata); break; case PKT_TYPE_ERROR: { struct pkt_error *err = (struct pkt_error *)pktdata; err->errormsg[sizeof(err->errormsg) - 1] = 0; to_printable(err->errormsg); printf("Error from remote host: '%s'\n", err->errormsg); } break; case PKT_TYPE_USRTEXT: handle_pkt_usrtext(pktdata, ph->p_len - sizeof(struct pkt_header)); break; default: fprintf(stderr, _("Received unknown packet type %u.\n"), ph->p_type); send_errorpkt(&call.cssq, PKT_ERROR_PKTUNKNOWN); }; } static void get_csockdata(void) { char buff[1024]; int nr; int nopacket = 0; size_t qsz; nr = read(call.csock, buff, 1024); if (!nr) { printf(_("Connection to %s closed by remote host.\n"), call.r_hostent->h_name); if (TEST_BIT(call.state, CALL_STATE_INCALL)) hk_trigger(hk_hangup_rmt); closecall(); return; } if (nr == -1) return; #if (DEBUG >= 1) if (opt_get_int("verbose")) printf(_("Received %u bytes from control socket.\n"), nr); #endif iob_queue(&call.csrq, buff, nr); while(!nopacket) { qsz = iob_buffsize(&call.csrq); if (qsz >= sizeof(struct pkt_header)) { struct pkt_header ph; /* Peak at the header */ iob_readkeep(&call.csrq, (char *)&ph, sizeof(ph)); if (qsz >= ph.p_len) { char pktbuff[ph.p_len]; iob_read(&call.csrq, pktbuff, ph.p_len); call.connstats.cpkts_recv++; call.connstats.cbytes_recv += ph.p_len; handle_packet(pktbuff); } else nopacket = 1; } else nopacket = 1; } } static void get_dsockdata(void) { struct pkt_udpaudio *ph = (struct pkt_udpaudio *)audiopkt; int nr; size_t qsz; #ifdef USE_CRYPTO size_t os; #endif if ((nr = recv(call.dsock, audiopkt, 4096, 0)) <= 0) return; call.connstats.apkts_recv++; call.connstats.abytes_recv += nr; if (ph->sequence <= call.rcv_sequence) { call.connstats.apkts_order++; return; } call.connstats.apkts_lost += ((ph->sequence - call.rcv_sequence) - 1); call.rcv_sequence = ph->sequence; if (call.loopback) send(call.dsock, audiopkt, nr, 0); if (call.pdsp == -1) return; #ifdef USE_CRYPTO if (call.cryptinfo.type != CALL_CRYPT_NONE) { cr_decrypt(&audiopkt[sizeof(struct pkt_udpaudio)], nr - sizeof(struct pkt_udpaudio), cr_audiopkt, &os); if (codec_decode(call.rcv_aparams.compression, cr_audiopkt, ph->datalen, &call.pdspq) == -1) { closecall(); return; } } else #endif if (codec_decode(call.rcv_aparams.compression, &audiopkt[sizeof(struct pkt_udpaudio)], nr - sizeof(struct pkt_udpaudio), &call.pdspq) == -1) { closecall(); return; } qsz = iob_buffsize(&call.pdspq); if ((!isplaying && qsz >= (call.pdsp_blksize * 4)) || (FD_ISSET(call.pdsp, &f_wset) && qsz >= (call.pdsp_blksize * 2))) { int nw, i, reps; if (!dropskip && qsz > call.pdsp_blksize * 10) dropskip = 8; if (dropskip) { iob_delete(&call.pdspq, call.pdsp_blksize / 8); dropskip--; } if (dropskip == 1 && qsz > call.pdsp_blksize * 5) dropskip = 8; reps = (isplaying) ? 1 : 2; for (i = 0; i < reps; i++) { iob_readkeep(&call.pdspq, call.pdsp_buff, call.pdsp_blksize); if (call.ogain != NULL) gain_apply(call.ogain, (short *)call.pdsp_buff, call.pdsp_blksize / 2); nw = write(call.pdsp, call.pdsp_buff, call.pdsp_blksize); if (nw > 0) { iob_delete(&call.pdspq, nw); isplaying = 1; } if (nw != call.pdsp_blksize) break; } /* end for */ FD_CLR(call.pdsp, &f_wset); } /* ENDIF ISSET pdsp */ } static void handle_connection(int listener) { struct sockaddr_in r_address; struct pkt_hello hellopkt; int sock2; socklen_t l = sizeof(r_address); if ((sock2 = accept(listener, (struct sockaddr *)&r_address, &l)) == -1) { fprintf(stderr, "Accept failed: %s\n", strerror(errno)); return; } if (opt_get_int("verbose")) printf(_("Got connection from: %s.\n"), inet_ntoa(r_address.sin_addr)); if (TEST_BIT(call.state, CALL_STATE_CONNECTED)) { shutdown(sock2, 2); close(sock2); return; } fcntl(sock2, F_SETFL, O_RDWR | O_NONBLOCK); call.csock = sock2; memcpy(&call.r_address, &r_address, sizeof(r_address)); icsetenv("remote_hostip", inet_ntoa(r_address.sin_addr)); if (opt_get_int("reverse_lookup")) { if ((call.r_hostent = gethostbyaddr((char *)&r_address.sin_addr, sizeof(r_address.sin_addr), AF_INET)) == NULL) call.r_hostent = gethostbyname(inet_ntoa(r_address.sin_addr)); icsetenv("remote_hostname", call.r_hostent->h_name); } else call.r_hostent = gethostbyname(inet_ntoa(r_address.sin_addr)); hellopkt.proto_version = INTERCOM_PROTOCOL_VERSION; strcpy(hellopkt.username, opt_get_str("username")); strcpy(hellopkt.clientname, "Intercom " VERSION); queuepacket(&call.cssq, PKT_TYPE_HELLO, (char *)&hellopkt, sizeof(hellopkt)); SET_BIT(call.state, CALL_STATE_CONNECTED); } static void get_rdspdata(void) { static float silent_tm = 0.0; int nr; size_t i; int only_silence = 1; struct pkt_udpaudio *ph = (struct pkt_udpaudio *)audiopkt; size_t bs; #ifdef USE_CRYPTO size_t os; #endif if ((nr = read(call.rdsp, call.rdsp_buff, call.rdsp_blksize)) <= 0) return; if (call.igain != NULL) gain_apply(call.igain, (short *)call.rdsp_buff, call.rdsp_blksize / 2); for (i = 0; i < (call.rdsp_blksize / 2); i++) { if ((int)abs(((short *)call.rdsp_buff)[i]) > silence_thresh) { only_silence = 0; silent_tm = 0.0; break; } } if (only_silence) { silent_tm += (float)call.rdsp_blksize / (call.snd_aparams.rate * (call.snd_aparams.bitwidth / 8)); } if (silent_tm < silence_time) if (codec_encode(call.snd_aparams.compression, call.rdsp_buff, nr, &call.rdspq) == -1) { closecall(); return; } #ifdef USE_CRYPTO if (call.cryptinfo.type != CALL_CRYPT_NONE) ph = (struct pkt_udpaudio *)cr_audiopkt; #endif while((bs = iob_buffsize(&call.rdspq)) >= min_buffsize && bs >= call.snd_datasize) { if (bs > max_buffsize) bs = max_buffsize; if ((nr = bs % call.snd_datasize)) bs -= nr; iob_read(&call.rdspq, &audiopkt[sizeof(struct pkt_udpaudio)], bs); ph->sequence = ++call.snd_sequence; ph->datalen = bs; #ifdef USE_CRYPTO if (call.cryptinfo.type != CALL_CRYPT_NONE) { cr_encrypt(&audiopkt[sizeof(struct pkt_udpaudio)], bs, &cr_audiopkt[sizeof(struct pkt_udpaudio)], &os); ph->datalen = os; send(call.dsock, cr_audiopkt, os + sizeof(struct pkt_udpaudio), 0); } else #endif send(call.dsock, audiopkt, bs + sizeof(struct pkt_udpaudio), 0); call.connstats.apkts_sent++; call.connstats.abytes_sent += (bs + sizeof(struct pkt_udpaudio)); } } static void mainloop(void) { while(!done) { FD_ZERO(&f_rset); FD_ZERO(&f_wset); FD_SET(0, &f_rset); if (listener != -1) FD_SET(listener, &f_rset); if (call.csock != -1) { FD_SET(call.csock, &f_rset); if (iob_buffsize(&call.cssq)) FD_SET(call.csock, &f_wset); } if (TEST_BIT(call.state, CALL_STATE_AUDIORCV)) { if (isplaying && call.pdsp != -1) FD_SET(call.pdsp, &f_wset); FD_SET(call.dsock, &f_rset); } if (TEST_BIT(call.state, CALL_STATE_AUDIOSND) && call.rdsp != -1) FD_SET(call.rdsp, &f_rset); if (select(16, &f_rset, &f_wset, NULL, NULL) == -1) continue; if (FD_ISSET(0, &f_rset)) console_processkey(); else if (listener != -1 && FD_ISSET(listener, &f_rset)) handle_connection(listener); if (call.csock != -1) { if (FD_ISSET(call.csock, &f_rset)) get_csockdata(); if (iob_buffsize(&call.cssq) && FD_ISSET(call.csock, &f_wset)) { size_t buffsize = iob_buffsize(&call.cssq); char buff[buffsize]; int nw; iob_readkeep(&call.cssq, buff, buffsize); nw = write(call.csock, buff, buffsize); #if (DEBUG >= 1) if (opt_get_int("verbose")) printf(_("Wrote %d of %d bytes to control socket.\n"), nw, buffsize); #endif if (nw > 0) iob_delete(&call.cssq, nw); } } /* endif csock != -1 */ if (TEST_BIT(call.state, CALL_STATE_AUDIORCV)) { if (FD_ISSET(call.dsock, &f_rset)) get_dsockdata(); else if (call.pdsp != -1 && FD_ISSET(call.pdsp, &f_wset)) { size_t qsz = iob_buffsize(&call.pdspq); if (!dropskip && qsz > call.pdsp_blksize * 10) dropskip = 8; if (dropskip) { iob_delete(&call.pdspq, call.pdsp_blksize / 8); dropskip--; } if (dropskip == 1 && qsz > call.pdsp_blksize * 5) dropskip = 8; if (qsz >= call.pdsp_blksize) { iob_read(&call.pdspq, call.pdsp_buff, call.pdsp_blksize); if (call.ogain != NULL) gain_apply(call.ogain, (short *)call.pdsp_buff, call.pdsp_blksize / 2); write(call.pdsp, call.pdsp_buff, call.pdsp_blksize); } else { ioctl(call.pdsp, SNDCTL_DSP_POST, 0); isplaying = 0; } } /* endif FD_ISSET call.pdsp */ } /* endif call_state_audiorcv */ if (TEST_BIT(call.state, CALL_STATE_AUDIOSND) && call.rdsp != -1 && FD_ISSET(call.rdsp, &f_rset)) get_rdspdata(); } /* while done */ } int main(int ARGC, char **ARGV) { const struct option long_options[] = { {"compression", required_argument, NULL, 'c'}, {"pdevice", required_argument, NULL, 'd'}, {"rdevice", required_argument, NULL, 'D'}, {"rate", required_argument, NULL, 'r'}, {"port", required_argument, NULL, 'p'}, {"silent", no_argument, NULL, 's'}, {"verbose", no_argument, NULL, 'V'}, {"version", no_argument, NULL, 'v'}, {0, 0, 0, 0} }; char *home, *rcfiles; int r; #ifdef USE_GETTEXT #ifdef HAVE_SETLOCALE setlocale(LC_ALL, ""); #endif bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif printf("Intercom version %s, Copyright (C) 2001-2003 Shane Wegner\n" "Intercom is free software, covered by the GNU General Public License, and you\n" "are welcome to change it and/or distribute copies of it under certain\n" "conditions. Type \"copyright\" to see the license details.\n\n", VERSION); if ((home = getenv("HOME")) == NULL) { fputs(_("HOME environment variable not set.\n"), stderr); return 1; } signal(SIGHUP, sighandler); signal(SIGINT, sighandler); signal(SIGTERM, sighandler); opt_init(); refresh_userdefs(); asprintf(&rcfiles, "read --silent " SYSCONFDIR "/intercomrc %s/.intercomrc", home); processline(0, rcfiles); free(rcfiles); optind = 0; while ((r = getopt_long(ARGC, ARGV, "c:d:D:gh:r:p:sVv", long_options, (int *)0)) != EOF) switch (r) { case 'c': if (int_compression(optarg) != -1) opt_set_str("snd_compression", optarg); else fprintf(stderr, _("Compression format %s is invalid or not supported.\n"), optarg); break; case 'd': opt_set_str("snd_play_device", optarg); break; case 'D': opt_set_str("snd_capture_device", optarg); break; case 'r': opt_set_int("snd_rate", atoi(optarg)); break; case 'p': opt_set_int("port", atoi(optarg)); break; case 's': opt_set_str("snd_capture_device", ""); break; case 'v': return 0; case 'V': opt_set_int("verbose", 1); break; default: return 1; } /* switch */ iob_init(&call.cssq); iob_setminbytes(&call.cssq, IOB_PAGESIZE); iob_init(&call.csrq); iob_setminbytes(&call.csrq, IOB_PAGESIZE); iob_init(&call.rdspq); iob_init(&call.pdspq); codecs_init(); if ((listener = do_listen(opt_get_int("port"))) == -1) return 1; console_init(); if ((ARGC - optind) == 1) { char *call_arg[2]; call_arg[0] = "call"; call_arg[1] = ARGV[optind]; cmd_call(2, call_arg); } else if ((ARGC - optind) > 1) { fprintf(stderr, "%s: too many command-line options.\n", ARGV[0]); done = 1; } mainloop(); console_done(); closecall(); if (listener != -1) { shutdown(listener, 2); close(listener); } return 0; }