Mercurial > hg > audiostuff
diff intercom/intercomd.cpp @ 2:13be24d74cd2
import intercom-0.4.1
author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
---|---|
date | Fri, 25 Jun 2010 09:57:52 +0200 |
parents | |
children | c6c5a16ce2f2 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/intercom/intercomd.cpp Fri Jun 25 09:57:52 2010 +0200 @@ -0,0 +1,1376 @@ +/* intercomd.cpp + * + * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). + * All Rights Reserved. + * Author: Andre Adrian + * + * Voice over IP Intercom with Telephone conference and Acoustic Echo + * Cancellation using unicast RTP messages (RFC3550, RFC3551) + * + * Attention. This source code is for Linux only! You need: + * same endian for CPU and soundcard for 16bit PCM audio sample + * Open Source Sound (OSS) support + * ALSA Sound support for hardware (2-channel) AEC + * + * Format Sourcecode: +indent -kr -i2 -nlp -ci2 -l72 -lc72 -nut intercomd.cpp + * + * global Replace (one replace per line only!) +sed -i "s/PIT_MIN/G729_PIT_MIN/" *.h *.cpp + * + */ + +const char VERSION[] = "0.4.1"; + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdarg.h> +#include <syslog.h> + +/* Error handling */ +#include <assert.h> +#include <errno.h> +extern int errno; + +/* low level io */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <sys/select.h> +#include <sys/time.h> + +/* Socket io */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +/* IETF iLBC codec */ +extern "C" { + #include "ilbc/iLBC_define.h" + #include "ilbc/iLBC_encode.h" + #include "ilbc/iLBC_decode.h" +} + +/* ITU-T G.711 codec */ +extern "C" { + #include "g711/g711.h" +} + +/* 3GPP GSM codec */ +extern "C" { + #include "gsm/private.h" + #include "gsm/gsm.h" + #include "gsm/rpeltp.h" +} + +/* ITU-T G.726 codec */ +extern "C" { + #include "g726/g726.h" + #include "g726/g726_rfc3551.h" +} + +/* Speex codec */ +extern "C" { + #include <speex/speex.h> + /* Note: if you use the RPM speex-devel on SuSE 9.1 change the + * header file above from <speex/speex.h> to <speex.h> */ +} + +#include "sinuston.h" + +typedef unsigned char Byte; +typedef short Word16; + +#ifdef EFRSTUB +/* EFR stubs - because of function name conflict in EFR and G.729 */ +class EFR { +public: + int EFR::initEncode(int dtx_mode_) { + return 0; + }; + int EFR::encode( /* (o) len in byte of encoded_data */ + Byte * encoded_data, /* (o) The encoded bytes */ + Word16 * data /* (i) The signal block to encode */ + ) { + return 0; + }; + int EFR::initDecode() { + return 0; + }; + int EFR::decode( /* (o) Number of decoded samples or 0 for error */ + Word16 * decoded_data, /* (o) Decoded signal block */ + Byte * encoded_data, /* (i) Encoded bytes */ + Word16 mode /* (i) 0=PL, 1=Normal */ + ) { + return 0; + }; +}; +#else + /* 3GPP GSM-EFR codec */ +#include "efr/typedef.h" +#include "efr/efr.h" +#endif + +#ifdef G729STUB +/* G.729 stubs - because of function name conflict in EFR and G.729 */ +class G729 { +public: + int G729::initEncode(int dummy) { + return 0; + }; + + int G729::encode( /* len in byte of encoded_data */ + Byte * encoded_data, /* (o) The encoded bytes */ + Word16 * data /* (i) The signal block to encode */ + ) { + return 0; + }; + int G729::initDecode() { + return 0; + }; + + int G729::decode( /* (o) Number of decoded samples */ + Word16 * decoded_data, /* (o) Decoded signal block */ + Byte * encoded_data, /* (i) Encoded bytes */ + Word16 mode /* (i) 0=PL, 1=Normal */ + ) { + return 0; + }; +}; +#else + /* ITU-T G.729 codec */ +#include "g729/typedef.h" +#include "g729/ld8k.h" +#include "g729/g729.h" +#endif + + +/* intercom */ +#include "rtp.h" +#include "udp.h" +#include "tcp.h" +#include "aec.h" +#include "oss.h" +#include "cirbuf.h" +#include "intercomd.h" + +/* Design Constants */ +#define PARTNERS 5 /* maximum telephony partners */ + +/* End of Design Constants */ +#define MAXPACKSIZE 160 /* maximum size of 20ms encoded audio */ +#define ILBC_MODE 20 +#define FRAMESIZE (20*8*WIDEB*2) /* compression frame size */ +#define MTU 1460 /* max. MTU size without Headers */ + +/* Globals */ +int wdisplay = NO; /* export aec.cpp */ + + +/* audio */ +static CIRBUF mic_cirbuf; +static CIRBUF spk_cirbuf; +static AEC aec; +static int channels = 1; +static int audio_wr_fd = -1; // Audio Output (speaker) +static int packetlc = NO; +static SINUSTON sinuston; // Erweiterung Christian Dorge +static int sinuson = 0; // Sinustonaktivierung + +/* network transmitting to partners */ +static in_addr_t to_ip[PARTNERS]; +static RTP to_rtp[PARTNERS]; +static UDP to_udp[PARTNERS]; +static iLBC_Enc_Inst_t enc_ilbc[PARTNERS]; +static G726_state enc_g726[PARTNERS]; +static EFR enc_efr[PARTNERS]; +static G729 enc_g729[PARTNERS]; +static gsm enc_gsm[PARTNERS]; +static SpeexBits enc_spxbits[PARTNERS]; +static void *enc_spx[PARTNERS]; +static CIRBUF conf_cirbuf[PARTNERS]; +static char tx_buf[PARTNERS][MTU]; +static char *tx_pbuf[PARTNERS]; +static int tx_frame[PARTNERS]; +static int to_partners = 0; +static int telephone_conference = NO; +static int tx_frames = 4; +static int tx_payloadt = PT_iLBC; /* default Codec */ +static int tx_packsize = NO_OF_BYTES_20MS; +static unsigned long to_ssrc = 0; + +/* network receiving from partners */ +static in_addr_t from_ip[PARTNERS]; +static unsigned long from_ssrc[PARTNERS]; +static int from_cnt[PARTNERS]; +static CIRBUF from_cirbuf[PARTNERS]; +static iLBC_Dec_Inst_t dec_ilbc[PARTNERS]; +static G726_state dec_g726[PARTNERS]; +static EFR dec_efr[PARTNERS]; +static G729 dec_g729[PARTNERS]; +static gsm dec_gsm[PARTNERS]; +static SpeexBits dec_spxbits[PARTNERS]; +static void *dec_spx[PARTNERS]; +static int from_partners = 0; +static int from_rx_pt[PARTNERS]; +static RTP from_rtp[PARTNERS]; + +/*----------------------------------------------------------------* +* iLBC Encoder interface function +*---------------------------------------------------------------*/ + +short encode( /* (o) Number of bytes encoded */ + iLBC_Enc_Inst_t * iLBCenc_inst, /* (i/o) Encoder instance */ + unsigned char *encoded_data, /* (o) The encoded bytes */ + short *data /* (i) The signal block to encode */ + ) +{ + float block[BLOCKL_MAX]; + int k; + + /* convert signal to float */ + + for (k = 0; k < iLBCenc_inst->blockl; k++) + block[k] = (float) data[k]; + + /* do the actual encoding */ + + iLBC_encode(encoded_data, block, iLBCenc_inst); + + return (iLBCenc_inst->no_of_bytes); +} + +short spx_encode( /* (o) Number of bytes encoded */ + SpeexBits *bits, + void *enc_state, + unsigned char *encoded_data, /* (o) The encoded bytes */ + short *data /* (i) The signal block to encode */ + ) +{ + float block[20*WIDEB*8]; + unsigned int k; + + /* convert signal to float */ + + for (k = 0; k < sizeof(block) / sizeof(float); k++) + block[k] = (float) data[k]; + + /* do the actual encoding */ + + speex_bits_reset(bits); + speex_encode(enc_state, block, bits); + int bytes = speex_bits_write(bits, (char *)encoded_data, 200); + + // RPM speex-devel 1.0.3 has different size then Speex 1.1.7 + if (bytes != SZ_SPX) { + static int errcnt = 0; + if (++errcnt > 0) { + errcnt = -250; + syslog(LOG_WARNING, "%s:%d: %s: Error bytes = %d, Update Speex lib to 1.1\n", \ + __FILE__, __LINE__, __PRETTY_FUNCTION__, bytes); \ + } + } + return SZ_SPX; +} + +void tx_buf_init(int i) +{ + tx_pbuf[i] = tx_buf[i]; + tx_frame[i] = 0; +} + +int encode_any(int i, Byte * encoded_data, short *buf) +{ + static int once = 0; + int len = 0; + switch (tx_payloadt) { + case PT_PCMA: + len = 160; + alaw_compress(len, buf, encoded_data); + break; + case PT_PCMU: + len = 160; + ulaw_compress(len, buf, encoded_data); + break; + case PT_GSM: + gsm_encode(enc_gsm[i], buf, encoded_data); + len = 33; + break; + case PT_EFR: + len = enc_efr[i].encode(encoded_data, buf); + break; + case PT_G729: + /* G.729 is a 10ms per frame codec */ + enc_g729[i].encode(encoded_data, buf); + len = 2 * enc_g729[i].encode(encoded_data + 10, buf + 80); + break; + case PT_iLBC: + len = encode(&enc_ilbc[i], encoded_data, buf); + break; + case PT_SPX: + len = spx_encode(&enc_spxbits[i], enc_spx[i], encoded_data, buf); + if (!once) { + printf("speex encode len = %d\n", len); + once = 1; + } + break; + case PT_G726: + len = g726_encode(&enc_g726[i], encoded_data, buf); + break; + } + return len; +} + +static int packet_loss = 0; + +int audio_read(int audio_rd_fd) +{ + /* fat software interrupt routine: read audio, send UDP packets */ + + short mic_buf[FRAGSIZE / 2]; + if (1 == channels) { + size_t len = read(audio_rd_fd, mic_buf, FRAGSIZE); + return_if(len != FRAGSIZE, ERROR); + + if (0 == to_partners) { + /* start assembling send packets only if we have a target */ + return OKAY; + } + + short spk_buf[FRAGSIZE / 2]; + spk_cirbuf.pop((char *) spk_buf, FRAGSIZE); + + /* Acoustic Echo Cancellation - using software buffers */ + int i; + for (i = 0; i < FRAGSIZE / 2; ++i) { + mic_buf[i] = aec.doAEC(mic_buf[i], spk_buf[i]); + } + } else { + short mic2_buf[FRAGSIZE]; + size_t len = read(audio_rd_fd, mic2_buf, 2 * FRAGSIZE); + return_if(len != 2 * FRAGSIZE, ERROR); + + if (0 == to_partners) { + /* start assembling send packets only if we have a target */ + return OKAY; + } + + /* Acoustic Echo Cancellation - using hardware audio mixer */ + int i; + for (i = 0; i < FRAGSIZE / 2; ++i) { + mic_buf[i] = aec.doAEC(mic2_buf[2 * i], mic2_buf[2 * i + 1]); + } + } + + int ret = mic_cirbuf.push((char *) mic_buf, FRAGSIZE); + if (ret < 0) { + syslog(LOG_WARNING, "mic_cirbuf.push overrun\n"); + } + + if (mic_cirbuf.getlen() >= FRAMESIZE) { + /* My RFC3551 interpretation: Only one RTP Header for packets + * with a number of frames */ + int i; + for (i = 0; i < PARTNERS; ++i) { + if (to_ip[i] && 0 == tx_frame[i]) { + to_rtp[i].reset_csrc(); + + /* add ssrc from partners in csrc field */ + if (telephone_conference) { + int j; + for (j = 0; j < PARTNERS; ++j) { + /* do not mix in origin */ + if (from_ssrc[j] && (to_ip[i] != from_ip[j])) { + to_rtp[i].add_csrc(from_ssrc[j]); + } + } + } + /* put RTP header */ + tx_pbuf[i] = RTP_network_copy(tx_pbuf[i], &to_rtp[i]); + } + } + + /* put payload (audio) */ + short micbuf[FRAMESIZE / 2]; + mic_cirbuf.pop((char *) micbuf, FRAMESIZE); + + if (telephone_conference) { + /* telephone conference mix - everybody from/to everybody else */ + short from_buf[PARTNERS][FRAMESIZE / sizeof(short)]; + short sum_buf[FRAMESIZE / sizeof(short)]; + int i, k; + unsigned int j; + + /* get audio from other partners */ + for (i = 0; i < PARTNERS; ++i) { + conf_cirbuf[i].pop((char *) from_buf[i], FRAMESIZE); + } + + for (i = 0; i < PARTNERS; ++i) { + if (to_ip[i]) { + for (j = 0; j < FRAMESIZE / sizeof(short); ++j) { + /* mix */ + long sum = micbuf[j]; + for (k = 0; k < PARTNERS; ++k) { + if (to_ip[i] != from_ip[k]) { /* do not mix in origin */ + sum += from_buf[k][j]; + } + } + /* clip */ + if (sum > 32767) { + sum_buf[j] = 32767; + } else if (sum < -32767) { + sum_buf[j] = -32767; + } else { + sum_buf[j] = sum; + } + } + /* do encoding (audio compression) */ + Byte encoded_data[MAXPACKSIZE]; + int len = encode_any(i, encoded_data, sum_buf); + + /* distribute to transmit buffers */ + memcpy(tx_pbuf[i], encoded_data, len); + tx_pbuf[i] += len; + assert(tx_pbuf[i] - tx_buf[i] <= MTU); + } + } + } else { + /* intercom conference mixing - central node from/to other nodes */ + /* do encoding (audio compression) */ + Byte encoded_data[MAXPACKSIZE]; + int len = encode_any(0, encoded_data, micbuf); + + /* distribute to transmit buffers */ + int i; + for (i = 0; i < PARTNERS; ++i) { + if (to_ip[i]) { + memcpy(tx_pbuf[i], encoded_data, len); + tx_pbuf[i] += len; + assert(tx_pbuf[i] - tx_buf[i] <= MTU); + } + } + } + + /* transmit data packet(s) */ + for (i = 0; i < PARTNERS; ++i) { + if (to_ip[i] && ++tx_frame[i] >= tx_frames) { + + if (random() >= packet_loss) { // simulate packet loss + to_udp[i].send(tx_buf[i], tx_pbuf[i] - tx_buf[i]); + } + + /* prepare next go */ + tx_buf_init(i); + to_rtp[i].next(tx_frames * 20 * 8); + } + } + } + return OKAY; +} + +void partner_timeout() +{ + /* Delete old from_ssrc[] entries - this is not very quick! */ + int i; + for (i = 0; i < PARTNERS; ++i) { + if (from_ssrc[i] && from_cnt[i] == 0) { + char s[20]; + print_gui("d %s\n", iptoa(s, from_ip[i])); + from_ssrc[i] = 0; + from_ip[i] = 0; + from_rx_pt[i] = -1; + from_rtp[i].reset_csrc(); + --from_partners; + if (0 == from_partners) { + /* no more demand for PCM out */ + close(audio_wr_fd); + audio_wr_fd = -1; + } + } + from_cnt[i] = 0; + } +} + + +int partner_lookup(unsigned long ssrc, in_addr_t ip) +{ + /* search */ + int i; + for (i = 0; i < PARTNERS; ++i) { + if (from_ssrc[i] == ssrc) { + ++from_cnt[i]; + return i; /* old entry */ + } + } + /* add new entry */ + for (i = 0; i < PARTNERS; ++i) { + if (0 == from_ssrc[i]) { + if (0 == from_partners) { + spk_cirbuf.init(); + } + from_ssrc[i] = ssrc; + from_ip[i] = ip; + from_cnt[i] = 1; + + initDecode(dec_ilbc + i, ILBC_MODE, 1); + enc_efr[i].initDecode(); + enc_g729[i].initDecode(); + rpeltp_delete(dec_gsm[i]); + dec_gsm[i] = rpeltp_init(); + speex_bits_destroy(dec_spxbits + i); + speex_decoder_destroy(dec_spx[i]); + speex_bits_init(dec_spxbits + i); + dec_spx[i] = speex_decoder_init(&speex_wb_mode); + int value = SPX_ENH; + speex_decoder_ctl(dec_spx[i], SPEEX_SET_ENH, &value); + + from_cirbuf[i].init(); + conf_cirbuf[i].init(); + ++from_partners; + char s[20]; + print_gui("r %s\n", iptoa(s, ip)); + + printf("from_ssrc[%d] = %08lx\n", i, from_ssrc[i]); + return i; + } + } + return ERROR; +} + +/*----------------------------------------------------------------* +* iLBC Decoder interface function +*---------------------------------------------------------------*/ + +short decode( /* (o) Number of decoded samples */ + iLBC_Dec_Inst_t * iLBCdec_inst, /* (i/o) Decoder instance */ + short *decoded_data, /* (o) Decoded signal block */ + unsigned char *encoded_data, /* (i) Encoded bytes */ + short mode /* (i) 0=PL, 1=Normal */ + ) +{ + int k; + float decblock[BLOCKL_MAX], dtmp; + + /* check if mode is valid */ + + if (mode < 0 || mode > 1) { + printf("\nERROR - Wrong mode - 0, 1 allowed\n"); + exit(3); + } + + /* do actual decoding of block */ + iLBC_decode(decblock, encoded_data, iLBCdec_inst, mode); + + /* convert to short */ + + for (k = 0; k < iLBCdec_inst->blockl; k++) { + dtmp = decblock[k]; + + if (dtmp < MIN_SAMPLE) + dtmp = MIN_SAMPLE; + else if (dtmp > MAX_SAMPLE) + dtmp = MAX_SAMPLE; + decoded_data[k] = (short) dtmp; + } + + return (iLBCdec_inst->blockl); +} + +short spx_decode( /* (o) Number of decoded samples */ + SpeexBits *bits, + void *dec_state, + short *decoded_data, /* (o) Decoded signal block */ + unsigned char *encoded_data, /* (i) Encoded bytes */ + short mode /* (i) 0=PL, 1=Normal */ + ) +{ + unsigned int k; + float decblock[20*WIDEB*8]; + + /* do actual decoding of block */ + if (bits) + speex_bits_read_from(bits, (char *)encoded_data, SZ_SPX); + speex_decode(dec_state, bits, decblock); + + /* convert to short */ + + for (k = 0; k < sizeof(decblock) / sizeof(float); k++) { + decoded_data[k] = (short)decblock[k]; + } + + return 20*WIDEB*8; +} + +void audio_write() +{ + int i; + unsigned int j; + short from_buf[PARTNERS][FRAGSIZE / sizeof(short)]; + short sum_buf[FRAGSIZE / sizeof(short)]; + int playout = 0; // is 1 if we have audio, not silence + + /* get audio */ + for (i = 0; i < PARTNERS; ++i) { + int ret = from_cirbuf[i].pop((char *) from_buf[i], FRAGSIZE); + if (OKAY == ret) playout = 1; + if (packetlc && ERROR == ret && from_ssrc[i] && audio_wr_fd >= 0) { + // printf("audio_write() PLC ch=%d ret=%d \n", i, ret); + + /* do Packet Loss Concealment */ + short decoded_data[FRAMESIZE / 2]; + int len; + switch (from_rx_pt[i]) { + case PT_iLBC: + len = decode(dec_ilbc + i, decoded_data, NULL, 0); + from_cirbuf[i].push((char *) decoded_data, 2 * len); + from_cirbuf[i].pop((char *) from_buf[i], FRAGSIZE); + playout = 1; + break; + case PT_SPX: + Byte spx_dummy[SZ_SPX]; + memset(spx_dummy, 0, SZ_SPX); + len = spx_decode(NULL, dec_spx[i], + decoded_data, spx_dummy, 1); + from_cirbuf[i].push((char *) decoded_data, 2 * len); + from_cirbuf[i].pop((char *) from_buf[i], FRAGSIZE); + playout = 1; + break; + case PT_EFR: + Byte efr_dummy[31]; + memset(efr_dummy, 0, 31); + efr_dummy[0] = 0xC0; + len = dec_efr[i].decode(decoded_data, efr_dummy, 0); + from_cirbuf[i].push((char *) decoded_data, 2 * len); + from_cirbuf[i].pop((char *) from_buf[i], FRAGSIZE); + playout = 1; + break; + case PT_G729: + Byte g729_dummy[20]; + memset(g729_dummy, 0, 20); + len = dec_g729[i].decode(decoded_data, g729_dummy, 0); + len += + dec_g729[i].decode(decoded_data + 80, g729_dummy + 10, 0); + from_cirbuf[i].push((char *) decoded_data, 2 * len); + from_cirbuf[i].pop((char *) from_buf[i], FRAGSIZE); + playout = 1; + break; + } + } + } + + // if (!playout) return; // knistern with AD1985 + + // audio mixing with sinuston + for (j = 0; j < FRAGSIZE / sizeof(short); ++j) { + /* mix */ + long sum = 0; + for (i = 0; i < PARTNERS; ++i) { + sum += from_buf[i][j]; + } + /* clip */ + if (sum > 32767) { + sum = 32767; + } else if (sum < -32767) { + sum = -32767; + } + if (sinuson && playout) { + // Sinuston dazumischen 11jul2005 adrian + sum = sinuston.mischen(sum); + } + sum_buf[j] = sum; + } + + if (1 == channels) { + if (from_partners > 0) { + /* save for 1-channel AEC */ + int ret = spk_cirbuf.push((char *) sum_buf, FRAGSIZE); + if (ret < 0) { + /* syslog(LOG_WARNING, "spk_cirbuf.push overrun\n"); */ + } + } + if (audio_wr_fd < 0) { + /* late audio write open */ + audio_wr_fd = audio_init("/dev/dsp", channels, O_WRONLY); + } + if (audio_wr_fd >= 0) { + write(audio_wr_fd, sum_buf, FRAGSIZE); + } + } else { + short sum2_buf[FRAGSIZE]; + int i; + for (i = 0; i < FRAGSIZE / 2; ++i) { + sum2_buf[2 * i] = 0; /* left channel nothing */ + sum2_buf[2 * i + 1] = sum_buf[i]; /* right channel spk */ + } + if (audio_wr_fd < 0) { + /* late audio write open */ + audio_wr_fd = audio_init("/dev/dsp", channels, O_WRONLY); + } + if (audio_wr_fd >= 0) { + write(audio_wr_fd, sum2_buf, 2 * FRAGSIZE); + } + } +} + +static unsigned short rtp_port = 5004; /* see RFC3551 */ + +int udp_read(int udp_fd) +{ + /* software interrupt routine */ + char buf[MTU]; + struct sockaddr_in from_sock; + socklen_t from_socklen = sizeof(sockaddr_in); + RTP rtp_in; + + int len = recvfrom(udp_fd, buf, MTU, 0, + (struct sockaddr *) &from_sock, &from_socklen); + + /* check Port number */ + in_addr_t from_ip = ntohl(from_sock.sin_addr.s_addr); + in_port_t from_port = ntohs(from_sock.sin_port); + return_if(from_port != rtp_port, ERROR); + + char *pbuf = RTP_host_copy(&rtp_in, buf); + len -= (pbuf - buf); + return_if(len < 0, ERROR); + int rc = rtp_in.check(); + return_if(rc, ERROR); + + int partner = partner_lookup(rtp_in.getssrc(), from_ip); + return_if(partner < 0, ERROR); + + /* Avoid Megaphone (station with itself) audio loop */ + if (to_ssrc == rtp_in.getssrc()) { + // return ERROR; + } + + /* Avoid telephone conference style audio loop (multipath) */ + int i; + for (i = 0; i < PARTNERS; ++i) { + /* drop packets with SSRC the same as an known CSRC */ + if (i != partner && from_rtp[i].find_csrc(rtp_in.getssrc())) { + return ERROR; + } + } + /* to avoid silence with audio loops: Only if above test passed + * copy rtp_in */ + from_rtp[partner] = rtp_in; /* die CSRC */ + + int rx_frames = 0; + int rx_pt = rtp_in.get_pt(); + /* detect number of frames and dynamic payload type from length */ + switch (len) { + case 1 * 20: + rx_frames = 1; + break; /* G729 */ + case 1 * 33: + rx_frames = 1; + break; /* GSM */ + case 1 * 31: + rx_frames = 1; + rx_pt = PT_EFR; + break; + case 1 * 38: + rx_frames = 1; + rx_pt = PT_iLBC; + break; + case 1 * SZ_SPX: + rx_frames = 1; + rx_pt = PT_SPX; + break; + case 1 * 80: /* or 4*20 */ + switch (rx_pt) { + case PT_G729: + rx_frames = 4; + break; + default: + rx_frames = 1; + rx_pt = PT_G726; + break; + } + break; + case 1 * 160: /* or 2*80 */ + switch (rx_pt) { + case PT_PCMA: + case PT_PCMU: + rx_frames = 1; /* G711 */ + break; + default: + rx_frames = 2; /* G726 32kbps */ + rx_pt = PT_G726; + break; + } + break; + + case 2 * 20: + rx_frames = 2; + break; + case 2 * 33: + rx_frames = 2; + break; + case 2 * 31: + rx_frames = 2; + rx_pt = PT_EFR; + break; + case 2 * 38: + rx_frames = 2; + rx_pt = PT_iLBC; + break; + case 2 * SZ_SPX: + rx_frames = 2; + rx_pt = PT_SPX; + break; + case 2 * 160: /* or 4*80 */ + switch (rx_pt) { + case PT_PCMA: + case PT_PCMU: + rx_frames = 2; /* G711 */ + break; + default: + rx_frames = 4; /* G726 32kbps */ + rx_pt = PT_G726; + break; + } + break; + + case 3 * 20: + rx_frames = 3; + break; + case 3 * 33: + rx_frames = 3; + break; + case 3 * 31: + rx_frames = 3; + rx_pt = PT_EFR; + break; + case 3 * 38: + rx_frames = 3; + rx_pt = PT_iLBC; + break; + case 3 * SZ_SPX: + rx_frames = 3; + rx_pt = PT_SPX; + break; + case 3 * 80: + rx_frames = 3; + rx_pt = PT_G726; + break; + case 3 * 160: + rx_frames = 3; + break; + + case 4 * 33: + rx_frames = 4; + break; + case 4 * 31: + rx_frames = 4; + rx_pt = PT_EFR; + break; + case 4 * 38: + rx_frames = 4; + rx_pt = PT_iLBC; + break; + case 4 * SZ_SPX: + rx_frames = 4; + rx_pt = PT_SPX; + break; + case 4 * 160: + rx_frames = 4; + break; + }; + return_if(0 == rx_frames, ERROR); + + from_rx_pt[partner] = rx_pt; + for (i = 0; i < rx_frames; ++i) { + /* do decoding (audio decompression) */ + short decoded_data[FRAMESIZE / 2]; + switch (rx_pt) { + case PT_PCMA: + len = 160; + alaw_expand(len, (Byte *) pbuf, decoded_data); + pbuf += 160; + break; + case PT_PCMU: + len = 160; + ulaw_expand(len, (Byte *) pbuf, decoded_data); + pbuf += 160; + break; + case PT_GSM: + len = 160; + gsm_decode(dec_gsm[partner], (Byte *) pbuf, decoded_data); + pbuf += 33; + break; + case PT_EFR: + len = dec_efr[partner].decode(decoded_data, (Byte *) pbuf, 1); + pbuf += 31; + break; + case PT_G729: + len = dec_g729[partner].decode(decoded_data, (Byte *) pbuf, 1); + len += + dec_g729[partner].decode(decoded_data + 80, + (Byte *) (pbuf + 10), 1); + pbuf += 20; + break; + case PT_iLBC: + len = decode(dec_ilbc + partner, decoded_data, (Byte *) pbuf, 1); + pbuf += 38; + break; + case PT_SPX: + len = spx_decode(dec_spxbits + partner, dec_spx[partner], + decoded_data, (Byte *) pbuf, 1); + pbuf += SZ_SPX; + break; + case PT_G726: + len = + g726_decode(dec_g726 + partner, decoded_data, (Byte *) pbuf, 1); + pbuf += 80; + break; + } + return_if(len != FRAMESIZE / 2, ERROR); + + int ret = from_cirbuf[partner].push((char *) decoded_data, 2 * len); + if (ret < 0) { + syslog(LOG_WARNING, "from_cirbuf[%d].push overrun=%d\n", partner, + ret / FRAGSIZE); + } + if (telephone_conference) { + ret = conf_cirbuf[partner].push((char *) decoded_data, 2 * len); + if (ret < 0) { + /* syslog(LOG_WARNING, "conf_cirbuf[%d].push overrun=%d\n", + partner, ret/FRAGSIZE); */ + } + } + } + return OKAY; +} + +void spx_encoder_init(int i) +{ + speex_bits_init(enc_spxbits + i); + enc_spx[i] = speex_encoder_init(&speex_wb_mode); + int value = SPX_QUALITY; + speex_encoder_ctl(enc_spx[i], SPEEX_SET_QUALITY, &value); + value = SPX_BITRATE; + speex_encoder_ctl(enc_spx[i], SPEEX_SET_BITRATE, &value); + value = SPX_COMPLEXITY; + speex_encoder_ctl(enc_spx[i], SPEEX_SET_COMPLEXITY, &value); +} + +// user command +void command(char *cmd, int udp_fd) +{ + /* delete special characters like \r, \n */ + unsigned int i; + for (i = 0; i < strlen(cmd); ++i) { + if (cmd[i] < ' ') { /* hack: assume ASCII coding */ + cmd[i] = 0; + break; + } + } + in_addr_t ip; + switch (cmd[0]) { + default: + printf("voipconf commands:\n" + "c IP-Adress - connect to IP-Adress\n" + "h IP-Adress - hang-up IP-Adress\n\n"); + fflush(stdout); + break; + case 'p': + /* do nothing */ + break; + case 'c': + ip = atoip(cmd + 2); + for (i = 0; i < PARTNERS; ++i) { + if (0 == to_ip[i]) { + if (0 == to_partners) { + mic_cirbuf.init(); + } + tx_buf_init(i); + to_ip[i] = ip; + to_rtp[i].init(tx_payloadt, to_ssrc); + to_udp[i].send_init(cmd + 2, rtp_port, udp_fd); + switch (tx_payloadt) { + case PT_EFR: + enc_efr[i].initEncode(0); + break; + case PT_G729: + enc_g729[i].initEncode(0); + break; + case PT_iLBC: + initEncode(enc_ilbc + i, ILBC_MODE); + break; + case PT_GSM: + rpeltp_delete(enc_gsm[i]); + enc_gsm[i] = rpeltp_init(); + break; + case PT_SPX: + speex_bits_destroy(enc_spxbits + i); + speex_encoder_destroy(enc_spx[i]); + spx_encoder_init(i); + break; + } + ++to_partners; + break; + } + } + break; + case 'h': + ip = atoip(cmd + 2); + for (i = 0; i < PARTNERS; ++i) { + if (ip == to_ip[i]) { + to_ip[i] = 0; + to_udp[i].send_close(); + --to_partners; + + print_gui("%s\n", cmd); /* Tcl/Tk needs \n */ + break; + } + } + break; + } + + /* syslog(LOG_WARNING, "cmd=%s to_partners=%d\n", cmd, to_partners); */ +} + +#define CMDLEN 80 + +// local signalling from intercom.tcl +int gui_read(int gui_fd, int udp_fd) +{ + char cmd[CMDLEN]; + + int len = read(gui_fd, cmd, CMDLEN); + + if (len <= 0) { + syslog(LOG_WARNING, "gui_read() close\n"); + int ret = shutdown(gui_fd, SHUT_RDWR); + assert_errno(ret >= 0); + + return -1; + } + + command(cmd, udp_fd); + + return gui_fd; +} + +static int gui_fd = -1; + +int print_gui(const char *fmt, ...) +{ +/* in fmt: Formatstring as printf */ +/* in ...: Parameter(s) as printf */ + + if (gui_fd >= 0) { + char s[MTU]; + va_list ap; + va_start(ap, fmt); + (void) vsnprintf(s, MTU, fmt, ap); + va_end(ap); + + int len = strlen(s); + + return write(gui_fd, s, len); + } else { + return ERROR; + } +} + +struct timeval difftimeval(struct timeval time1, struct timeval time2) +{ + struct timeval diff; + + diff.tv_usec = time1.tv_usec - time2.tv_usec; + if (diff.tv_usec < 0) { + diff.tv_usec += 1000000; + time2.tv_usec += 1; + } + diff.tv_sec = time1.tv_sec - time2.tv_sec; + + return diff; +} + +float dB2q(float dB) +{ + /* Dezibel to Ratio */ + return powf(10.0f, dB / 20.0f); +} +float q2dB(float q) +{ + /* Ratio to Dezibel */ + return 20.0f * log10f(q); +} + +/* program main loop. OS Event handler */ +int loop(int audio_rd_fd, int udp_fd, int gui_listen_fd) +{ + + struct timeval timeout; + fd_set read_fds; + int max_fd = 64; /* should be max(fd, ..) + 1 */ + static struct timeval last_partner_timeout; + static struct timeval last_getambient; + static struct timeval last_spkout; + static long remainder_spkout = 0; + struct timezone tz; + static int mic_int, spk_int, time_int; + + openlog(NULL, LOG_PERROR, LOG_WARNING); + + gettimeofday(&last_partner_timeout, &tz); + gettimeofday(&last_getambient, &tz); + gettimeofday(&last_spkout, &tz); + for (;;) { + timeout.tv_sec = 0; + timeout.tv_usec = FRAGTIME * 1000; + FD_ZERO(&read_fds); + FD_SET(audio_rd_fd, &read_fds); + FD_SET(udp_fd, &read_fds); + FD_SET(gui_listen_fd, &read_fds); + if (gui_fd >= 0) { + FD_SET(gui_fd, &read_fds); + } + // int ret = select(max_fd, &read_fds, NULL, NULL, &timeout); + int ret = select(max_fd, &read_fds, NULL, NULL, NULL); + assert_errno(ret >= 0); + + if (FD_ISSET(audio_rd_fd, &read_fds)) { + audio_read(audio_rd_fd); + ++mic_int; + } + if (FD_ISSET(udp_fd, &read_fds)) { + udp_read(udp_fd); + } + if (FD_ISSET(gui_listen_fd, &read_fds)) { + gui_fd = tcp_server_init2(gui_listen_fd); + } + if (gui_fd >= 0) { + if (FD_ISSET(gui_fd, &read_fds)) { + gui_fd = gui_read(gui_fd, udp_fd); + } + } +#if 0 + if ((mic_int % (5000 / FRAGTIME)) == 0) { + printf("int: mic %d spk %d time %d\n", mic_int, spk_int, + time_int); + } +#endif + + struct timeval now, diff; + if (packetlc) { + gettimeofday(&now, &tz); + diff = difftimeval(now, last_spkout); + if (diff.tv_usec + remainder_spkout >= FRAGTIME * 1000) { + ++time_int; + last_spkout = now; + /* Linux time resolution is more coarse (15ms) then mic + * time interval (4ms), therefore remainder calculation */ + remainder_spkout = + diff.tv_usec + remainder_spkout - FRAGTIME * 1000; + if (audio_wr_fd < 0) { + /* new output: fill up Open Sound System (OSS) buffer + * to compensate Operation System Jitter */ + // audio_write(); + } + audio_write(); + ++spk_int; + } + } else { + /* because of problems with Intel ICH5/Analog Devices AD1985 + * not together with audio_read() */ + audio_write(); + } + + gettimeofday(&now, &tz); + diff = difftimeval(now, last_partner_timeout); + if (diff.tv_usec >= 160000) { /* 2*PACKETDURATION in usec */ + last_partner_timeout = now; + partner_timeout(); + } +#if 0 + gettimeofday(&now, &tz); + diff = difftimeval(now, last_getambient); + if (diff.tv_sec >= 2) { + last_getambient = now; + // if (to_partners > 0) { + // float ambient = aec.getambient(); + // float ambientdB = q2dB(ambient / 32767.0f); + // syslog(LOG_WARNING, "Ambient = %2.0f dB\n", ambientdB); + float xfastdB = q2dB(aec.xfast / 32767.0f); + float dfastdB = q2dB(aec.dfast / 32767.0f); + float efastdB = q2dB(aec.efast / 32767.0f); + float xslowdB = q2dB(aec.xslow / 32767.0f); + float dslowdB = q2dB(aec.dslow / 32767.0f); + syslog(LOG_WARNING, "ratio= %2.0f / %2.0f / %2.0f / %2.0f e= %2.0f\n", + dfastdB, xfastdB, dslowdB, xslowdB, efastdB); + // } + } +#endif + } + return ERROR; +} + +int main(int argc, char *argv[]) +{ + int i; + float ambient = 0.0f; + float dB; + +#if WIDEB==1 + tx_payloadt = PT_iLBC; + tx_packsize = SZ_iLBC; + printf("Narrowband DFS VoIP Intercom %s\n", VERSION); +#else + tx_payloadt = PT_SPX; + tx_packsize = SZ_SPX; + printf("Wideband DFS VoIP Intercom %s\n", VERSION); +#endif + for (i = 1; i < argc && '-' == argv[i][0]; ++i) { + switch (argv[i][1]) { + case 'a': /* set Ambient (No Talking) Noise level */ + ambient = atof(argv[++i]); + break; + case 'b': /* No AES */ + dB = atof(argv[++i]); + if (dB < 0.0f) { + aec.setaes(dB2q(dB)); + } else { + aec.setaes(0.0f); // 0 is no AES + } + break; + case 'e': + sinuson = 1; /* Sinuston 2040Hz zumischen */ + break; + case 'k': /* packet loss concealment */ + packetlc = YES; + break; + case 'f': /* Frames */ + tx_frames = atoi(argv[++i]); + if (tx_frames < 1) + tx_frames = 1; + if (tx_frames > 4) + tx_frames = 4; + break; + case 'l': /* use hardware AEC and Line-in for microphone */ + channels = 2; + break; + case 'm': /* use hardware AEC and Mic-in for microphone */ + channels = 2; + break; + case 'p': /* RTP Portnumber */ + rtp_port = atoi(argv[++i]); + rtp_port &= 0xFFFE; /* RFC3551: RTP port has even port number */ + break; + case 's': /* simulated Packet loss */ + packet_loss = (RAND_MAX / 100) * atoi(argv[++i]); + break; + case 't': /* Telephone conference call (true conference) */ + telephone_conference = YES; + break; + case 'w': /* visualize vector w */ + aec.openwdisplay(); + break; +#if WIDEB==1 + case 'A': /* set G.711 A-law */ + tx_payloadt = PT_PCMA; + tx_packsize = 160; /* see RFC3551 */ + break; + case 'U': /* set G.711 u-law */ + tx_payloadt = PT_PCMU; + tx_packsize = 160; /* see RFC3551 */ + break; + case '6': /* set G.726 codec */ + tx_payloadt = PT_G726; + tx_packsize = 80; /* see RFC3551 */ + break; + case 'G': /* set GSM codec */ + tx_payloadt = PT_GSM; + tx_packsize = 33; /* see RFC3551 */ + break; + case 'E': /* set GSM-EFR codec */ + tx_payloadt = PT_EFR; + tx_packsize = 31; /* see RFC3551 */ + break; + case '9': /* set G.729 codec */ + tx_payloadt = PT_G729; + tx_packsize = 20; /* see RFC3551 */ + break; +#else + case 'S': /* set Speex codec */ + tx_payloadt = PT_SPX; + tx_packsize = SZ_SPX; /* 16.8kBit/s Wideband */ + break; +#endif + } + } + + /* do -a option even if codec options follows */ + if (ambient != 0.0f) { + aec.setambient(MAXPCM * dB2q(ambient)); + } + + /* init Sinuston fuer Transmitter */ + sinuston.frequenz(2040, WIDEB*8000, -24.0f); + + /* open Audio Receive */ + int audio_rd_fd = audio_init("/dev/dsp", channels, O_RDONLY); + assert(audio_rd_fd >= 0); + + /* open Network Receive */ + int udp_fd = UDP_recv_init(rtp_port); + assert(udp_fd >= 0); + + /* open Graphical User Interface as TCP server */ + int gui_listen_fd = tcp_server_init(4999); + + /* codec Initialization */ + int j; + for (j = 0; j < PARTNERS; ++j) { + /* iLBC */ + initEncode(enc_ilbc + j, ILBC_MODE); + initDecode(dec_ilbc + j, ILBC_MODE, 1); + + /* GSM-EFR */ + enc_efr[j].initEncode(0); + dec_efr[j].initDecode(); + + /* G.729 */ + enc_g729[j].initEncode(0); + dec_g729[j].initDecode(); + + /* GSM */ + enc_gsm[i] = rpeltp_init(); + dec_gsm[i] = rpeltp_init(); + + /* Speex */ + spx_encoder_init(j); + + speex_bits_init(dec_spxbits + j); + dec_spx[j] = speex_decoder_init(&speex_wb_mode); // Wideband + int value = SPX_ENH; + speex_decoder_ctl(dec_spx[j], SPEEX_SET_ENH, &value); + } + + /* open Network Transmit Partner (Connections) */ + to_ssrc = random32(tx_payloadt); + printf("to_ssrc = %08lx\n", to_ssrc); + for (; i < argc; ++i) { + if (0 == to_partners) { + mic_cirbuf.init(); + } + tx_buf_init(to_partners); + to_ip[to_partners] = atoip(argv[i]); + to_rtp[to_partners].init(tx_payloadt, to_ssrc); + to_udp[to_partners].send_init(argv[i], rtp_port, udp_fd); + ++to_partners; + } + + loop(audio_rd_fd, udp_fd, gui_listen_fd); + + return OKAY; +}