--- chan_oss.c 2005-12-03 21:35:20.000000000 +0100 +++ chan_oss_new.c 2005-12-03 21:55:55.000000000 +0100 @@ -18,6 +18,8 @@ * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ + + /* Remember to also set OPTIONS += -DRADIO_RELAX in Makefile! */ /*! \file * @@ -39,7 +41,6 @@ #include #include - #ifdef __linux #include #elif defined(__FreeBSD__) @@ -66,6 +67,7 @@ #include "asterisk/utils.h" #include "asterisk/causes.h" #include "asterisk/endian.h" +#include "asterisk/dsp.h" /* ringtones we use */ #include "busy.h" @@ -183,9 +185,8 @@ */ #define TEXT_SIZE 256 -#if 0 #define TRYOPEN 1 /* try to open on startup */ -#endif + #define O_CLOSE 0x444 /* special 'close' mode for device */ /* Which device to use */ #if defined( __OpenBSD__ ) || defined( __NetBSD__ ) @@ -201,12 +202,14 @@ #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif +// number of frames to use for hook detection +#define HD_FRAME_INTEGRATION 3 static int usecnt; AST_MUTEX_DEFINE_STATIC(usecnt_lock); static char *config = "oss.conf"; /* default config file */ - +static int AST_CONTROL_NOSOUND=99; static int oss_debug; /* @@ -297,6 +300,16 @@ char oss_read_buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET]; int readpos; /* read position above */ struct ast_frame read_f; /* returned by oss_read */ + struct ast_dsp *dsp; /* DTMF detection */ + /* Parameters for off hook detection. */ + int hd_disp_interval; + int hl_threshold; // high level noise threshold + int hl_ring_threshold; // high level noise threshold during ring + int hl_seqmin; // 'low pass' for high level threshold + int ll_threshold; // low level noise threshold + int ll_seqmin; // 'low pass' for low level threshold + int vol_divide; /* Volume divide to make the input volume of this driver right (to avoid + acoustical echos). */ }; static struct chan_oss_pvt oss_default = { @@ -311,7 +324,15 @@ .ext = "s", .ctx = "default", .readpos = AST_FRIENDLY_OFFSET, /* start here on reads */ - .lastopen = { 0, 0 }, + .lastopen = { 0, 0 }, + /* Parameters for off hook detection. */ + .hd_disp_interval=-1, + .hl_threshold=-1, + .hl_ring_threshold=110, + .hl_seqmin=4, + .ll_threshold=40, + .ll_seqmin=4, + .vol_divide=3, }; static char *oss_active; /* the active device */ @@ -346,6 +367,110 @@ .fixup = oss_fixup, }; +// calc standard deviation of signal +static double hd_calc_stddev(short *buf, int len) { + int x; + // calculate mean + int meani=0; + double mean; + double dev=0.0; + + for (x=0; x < len; x++) + meani+=buf[x]; + mean=meani/len; + + for (x=0; x < len; x++) + dev+=(buf[x]-mean)*(buf[x]-mean); + + dev/=len; + return sqrt(dev); +} + +// amplitude histogram off/on-hook detection +/*! \parameter len frame length in SAMPLES */ +static void amp_hist(short *frame, int len,struct chan_oss_pvt *o) { + static short pbuf[FRAME_SIZE*HD_FRAME_INTEGRATION]; + static int pbp; // pbuf pos (next write location) + int fpos=0; // frame position + do { + // concatenate data together to get full frames + int cpylen=len; + if (cpylen>FRAME_SIZE*HD_FRAME_INTEGRATION-pbp) + cpylen=FRAME_SIZE*HD_FRAME_INTEGRATION-pbp; + + memcpy(pbuf+pbp, frame+fpos, cpylen*2); + pbp+=cpylen; fpos+=cpylen; + + if (pbp==FRAME_SIZE*HD_FRAME_INTEGRATION) { + static FILE *f=NULL; + if ((!f) && (oss_debug & 0x4)) f=fopen("/tmp/dump.raw", "wb"); + + double stddev=hd_calc_stddev(pbuf, FRAME_SIZE*HD_FRAME_INTEGRATION); + if ((f) && (oss_debug & 0x4)) fwrite(pbuf, 1, FRAME_SIZE*HD_FRAME_INTEGRATION*2, f); + + /* For finding the sweet spot here, i.e. the correct + threshold values etc., a switchable debug output is + really neccessary. */ + static int disp_count; + disp_count++; + if ((oss_debug & 0x4) && o->hd_disp_interval > 0 && (!(disp_count%o->hd_disp_interval))) + ast_verbose("stddev (for %d samples): %f\n", FRAME_SIZE*HD_FRAME_INTEGRATION, stddev); + + static int hd_state=0; /* hangup state. FIXME: Maybe + this can be combined with + hookstate? */ + + static int lo_seqcnt; // needs to be some value for real offline detecton + if (stddevll_threshold) + lo_seqcnt++; + else lo_seqcnt=0; + + if ((lo_seqcnt>o->ll_seqmin) && (hd_state==1)) { + ast_verbose("stddev (for %d samples): %f\n", FRAME_SIZE*HD_FRAME_INTEGRATION, stddev); + ast_verbose("oss: HANGUP\n"); + if (o->owner) ast_verbose("oss: current asterisk channel state: %d\n", o->owner->_state); + hd_state=0; + // do hang up work + o->hookstate=0; + o->cursound=-1; + o->nosound=1; + + if (o->owner) { + ast_verbose("OSS: HANGUP ---> SEND HANGUP\n"); + ast_queue_hangup(o->owner); + } + } + static int hi_seqcnt; // needs to be some value for real online detecton + if (stddev< o->hl_threshold || + ((o->cursound==3) && stddevhl_ring_threshold)) // during ring it needs more! + hi_seqcnt=0; + else hi_seqcnt++; + + if ((hi_seqcnt>o->hl_seqmin) && (hd_state==0)) { + ast_verbose("stddev (for %d samples): %f\n", FRAME_SIZE*HD_FRAME_INTEGRATION, stddev); + if (o->owner) ast_verbose("oss: current asterisk channel state: %d\n", o->owner->_state); + ast_verbose("oss: OFFHOOK\n"); + hd_state=1; + + // do off hook work + if (!o->owner) { + ast_verbose("OSS: HOOK ---> AST_OFFHOOK\n"); + o->owner=oss_new(o,NULL, NULL,AST_STATE_UP); + } + if (o->owner) { + ast_verbose("OSS: HOOK -> HOOKSTATE=1\n"); + ast_setstate(o->owner, AST_STATE_UP); + o->hookstate=1; + o->cursound = -1; + o->nosound=0; + } + o->hookstate=1; + } + pbp=0; + } + } while(fpossndcmd[0], &rfds); maxfd = o->sndcmd[0]; /* pipe from the main process */ - if (o->cursound > -1 && o->sounddev < 0) - setformat(o, O_RDWR); /* need the channel, try to reopen */ - else if (o->cursound == -1 && o->owner == NULL) - setformat(o, O_CLOSE); /* can close */ - if (o->sounddev > -1) { - if (!o->owner) { /* no one owns the audio, so we must drain it */ + if (o->cursound > -1 && o->sounddev < 0) + setformat(o, O_RDWR); /* need the channel, try to reopen */ + else if (o->cursound == -1 && o->owner == NULL && o->hl_threshold==-1) + setformat(o, O_CLOSE); /* can close */ + if (o->sounddev > -1) { + if (!o->owner) { /* no one owns the audio, so we must drain it */ FD_SET(o->sounddev, &rfds); + FD_SET(o->sounddev, &wfds); /* to get real data, some soundcards require writing data simultaneously. */ maxfd = MAX(o->sounddev, maxfd); } if (o->cursound > -1) { @@ -542,20 +666,28 @@ int i, what = -1; read(o->sndcmd[0], &what, sizeof(what)); - for (i = 0; sounds[i].ind != -1; i++) { - if (sounds[i].ind == what) { - o->cursound = i; - o->sampsent = 0; - o->nosound = 1; /* block audio from pbx */ - break; + if (what==AST_CONTROL_NOSOUND){ + o->cursound = -1; + o->sampsent = 0; + } else { + for (i = 0; sounds[i].ind != -1; i++) { + if (sounds[i].ind == what) { + o->cursound = i; + o->sampsent = 0; + o->nosound = 1; /* block audio from pbx */ + break; + } } + if (sounds[i].ind == -1) + ast_log(LOG_WARNING, "invalid sound index: %d\n", what); } - if (sounds[i].ind == -1) - ast_log(LOG_WARNING, "invalid sound index: %d\n", what); } if (o->sounddev > -1) { - if (FD_ISSET(o->sounddev, &rfds)) /* read and ignore errors */ + if (FD_ISSET(o->sounddev, &rfds)) { /* read and ignore errors */ read(o->sounddev, ign, sizeof(ign)); + if (o->hl_threshold!=-1) + amp_hist((short*)ign,FRAME_SIZE,o); + } if (FD_ISSET(o->sounddev, &wfds)) send_sound(o); } @@ -735,8 +867,8 @@ { struct chan_oss_pvt *o = c->tech_pvt; - o->cursound = -1; - o->nosound = 0; + o->cursound = -1; + o->nosound = 1; c->tech_pvt = NULL; o->owner = NULL; ast_verbose( " << Hangup on console >> \n"); @@ -752,8 +884,10 @@ /* Make congestion noise */ ring(o, AST_CONTROL_CONGESTION); } - } - return 0; + } else { + ring(o, AST_CONTROL_NOSOUND); + } + return 0; } /* used for data coming from the network */ @@ -828,6 +962,19 @@ f->datalen = FRAME_SIZE * 2; f->data = o->oss_read_buf + AST_FRIENDLY_OFFSET; f->offset = AST_FRIENDLY_OFFSET; + if (o->hl_threshold!=-1) { + amp_hist((short*)(f->data), FRAME_SIZE,o); + /* process with dsp for DTMF */ + if (o->dsp) { + struct ast_frame* f2 = ast_dsp_process(c,o->dsp,f); + //FIXME: Check f2==&f ? + // ast_verbose("Checked for DTMF data.\n"); + if (f2 && (f2->frametype == AST_FRAME_DTMF)) { + ast_verbose("Detected inband DTMF digit: %c on OSS\n", f2->subclass); + return f2; + } + } + } return f; } @@ -915,6 +1062,19 @@ /* XXX what about usecnt ? */ } } + if (o->hl_threshold!=-1){ + /* Let AST DSP detect DTMF */ + if (o->dsp) { + ast_verbose("Already have a dsp for OSS?\n"); + } else { + o->dsp=ast_dsp_new(); + if (o->dsp) { + ast_verbose("Detecting DTMF inband with sw DSP on OSS\n"); + ast_dsp_set_features(o->dsp, DSP_FEATURE_DTMF_DETECT); + ast_dsp_digitmode(o->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF); + } + } + } return c; } @@ -1331,6 +1491,13 @@ M_STR("extension", o->ext) M_F("mixer", store_mixer(o, v->value)) M_F("callerid", store_callerid(o, v->value)) + M_UINT("hd_disp_interval",o->hd_disp_interval) + M_UINT("hd_hl_threshold",o->hl_threshold) + M_UINT("hd_hl_ring_threshold",o->hl_ring_threshold) + M_UINT("hd_hl_seqmin",o->hl_seqmin) + M_UINT("hd_ll_threshold",o->ll_threshold) + M_UINT("hd_ll_seqmin",o->ll_seqmin) + M_UINT("vol_divide",o->vol_divide) M_END(;); } if (ast_strlen_zero(o->device)) @@ -1348,6 +1515,7 @@ openit: #if TRYOPEN + sleep(3); /* let ast_tvdiff_ms(ast_tvnow(), o->lastopen) return a value > 1000 */ if (setformat(o, O_RDWR) < 0) { /* open device */ if (option_verbose > 0) { ast_verbose(VERBOSE_PREFIX_2 "Device %s not detected\n", ctg);