add new channel option (via ast_channel_setoption()) to let channel drivers adjust...
authorKevin P. Fleming <kpfleming@digium.com>
Wed, 7 Sep 2005 01:30:01 +0000 (01:30 +0000)
committerKevin P. Fleming <kpfleming@digium.com>
Wed, 7 Sep 2005 01:30:01 +0000 (01:30 +0000)
modify app_chanspy to use new gain option
reformat app_chanspy to match coding guidelines
add user-controlled volume adjustment to app_meetme (issue #4170, heavily modified to actually work on Zap channels)

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@6519 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_chanspy.c
apps/app_meetme.c
channels/chan_iax2.c
channels/chan_zap.c
include/asterisk/frame.h

index 5bc8c79..93c8987 100755 (executable)
@@ -39,12 +39,10 @@ AST_MUTEX_DEFINE_STATIC(modlock);
 #define AST_NAME_STRLEN 256
 #define ALL_DONE(u, ret) LOCAL_USER_REMOVE(u); return ret;
 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
-#define minmax(x,y) x ? (x > y) ? y : ((x < (y * -1)) ? (y * -1) : x) : 0
 
-
-static char *synopsis = "Tap into any type of asterisk channel and listen to audio";
-static char *app = "ChanSpy";
-static char *desc = "   Chanspy([<scanspec>][|<options>])\n\n"
+static const char *synopsis = "Tap into any type of asterisk channel and listen to audio";
+static const char *app = "ChanSpy";
+static const char *desc = "   Chanspy([<scanspec>][|<options>])\n\n"
 "Valid Options:\n"
 " - q: quiet, don't announce channels beep, etc.\n"
 " - b: bridged, only spy on channels involved in a bridged call.\n"
@@ -237,104 +235,102 @@ static int spy_queue_ready(struct ast_channel_spy *spy)
 }
 #endif
 
-
 static int spy_generate(struct ast_channel *chan, void *data, int len, int samples) 
 {
 
-               struct chanspy_translation_helper *csth = data;
-               struct ast_frame frame, *f;
-               int len0 = 0, len1 = 0, samp0 = 0, samp1 = 0, x, vf, maxsamp;
-               short buf0[1280], buf1[1280], buf[1280];
+       struct chanspy_translation_helper *csth = data;
+       struct ast_frame frame, *f;
+       int len0 = 0, len1 = 0, samp0 = 0, samp1 = 0, x, vf, maxsamp;
+       short buf0[1280], buf1[1280], buf[1280];
                
-               if (csth->spy.status == CHANSPY_DONE) {
-            return -1;
+       if (csth->spy.status == CHANSPY_DONE) {
+               return -1;
         }
 
-               ast_mutex_lock(&csth->spy.lock);
-               while((f = csth->spy.queue[0])) {
-                       csth->spy.queue[0] = f->next;
-                       ast_slinfactory_feed(&csth->slinfactory[0], f);
-                       ast_frfree(f);
-               }
-               ast_mutex_unlock(&csth->spy.lock);
-               ast_mutex_lock(&csth->spy.lock);
-               while((f = csth->spy.queue[1])) {
-                       csth->spy.queue[1] = f->next;
-                       ast_slinfactory_feed(&csth->slinfactory[1], f);
-                       ast_frfree(f);
-               }
-               ast_mutex_unlock(&csth->spy.lock);
+       ast_mutex_lock(&csth->spy.lock);
+       while((f = csth->spy.queue[0])) {
+               csth->spy.queue[0] = f->next;
+               ast_slinfactory_feed(&csth->slinfactory[0], f);
+               ast_frfree(f);
+       }
+       ast_mutex_unlock(&csth->spy.lock);
+       ast_mutex_lock(&csth->spy.lock);
+       while((f = csth->spy.queue[1])) {
+               csth->spy.queue[1] = f->next;
+               ast_slinfactory_feed(&csth->slinfactory[1], f);
+               ast_frfree(f);
+       }
+       ast_mutex_unlock(&csth->spy.lock);
                
-               if (csth->slinfactory[0].size < len || csth->slinfactory[1].size < len) {
-                       return 0;
-               }
+       if (csth->slinfactory[0].size < len || csth->slinfactory[1].size < len) {
+               return 0;
+       }
                
-               if ((len0 = ast_slinfactory_read(&csth->slinfactory[0], buf0, len))) {
-                       samp0 = len0 / 2;
-               } 
-               if((len1 = ast_slinfactory_read(&csth->slinfactory[1], buf1, len))) {
-                       samp1 = len1 / 2;
-               }
+       if ((len0 = ast_slinfactory_read(&csth->slinfactory[0], buf0, len))) {
+               samp0 = len0 / 2;
+       } 
+       if ((len1 = ast_slinfactory_read(&csth->slinfactory[1], buf1, len))) {
+               samp1 = len1 / 2;
+       }
 
-               maxsamp = (samp0 > samp1) ? samp0 : samp1;
-               vf = get_volfactor(csth->volfactor);
-        vf = minmax(vf, 16);
+       maxsamp = (samp0 > samp1) ? samp0 : samp1;
+       vf = get_volfactor(csth->volfactor);
                
-               for(x=0; x < maxsamp; x++) {
-                       if (vf < 0) {
-                               if (samp0) {
-                                       buf0[x] /= abs(vf);
-                               }
-                               if (samp1) {
-                                       buf1[x] /= abs(vf);
-                               }
-                       } else if (vf > 0) {
-                               if (samp0) {
-                                       buf0[x] *= vf;
-                               }
-                               if (samp1) {
-                                       buf1[x] *= vf;
-                               }
+       for(x=0; x < maxsamp; x++) {
+               if (vf < 0) {
+                       if (samp0) {
+                               buf0[x] /= abs(vf);
                        }
-                       if (samp0 && samp1) {
-                               if (x < samp0 && x < samp1) {
-                                       buf[x] = buf0[x] + buf1[x];
-                               } else if (x < samp0) {
-                                       buf[x] = buf0[x];
-                               } else if (x < samp1) {
-                                       buf[x] = buf1[x];
-                               }
+                       if (samp1) {
+                               buf1[x] /= abs(vf);
+                       }
+               } else if (vf > 0) {
+                       if (samp0) {
+                               buf0[x] *= vf;
+                       }
+                       if (samp1) {
+                               buf1[x] *= vf;
+                       }
+               }
+               if (samp0 && samp1) {
+                       if (x < samp0 && x < samp1) {
+                               buf[x] = buf0[x] + buf1[x];
                        } else if (x < samp0) {
                                buf[x] = buf0[x];
                        } else if (x < samp1) {
                                buf[x] = buf1[x];
                        }
+               } else if (x < samp0) {
+                       buf[x] = buf0[x];
+               } else if (x < samp1) {
+                       buf[x] = buf1[x];
                }
+       }
                
-               memset(&frame, 0, sizeof(frame));
-               frame.frametype = AST_FRAME_VOICE;
-               frame.subclass = AST_FORMAT_SLINEAR;
-               frame.data = buf;
-               frame.samples = x;
-               frame.datalen = x * 2;
-
-               if (ast_write(chan, &frame)) {
-                       csth->spy.status = CHANSPY_DONE;
-                       return -1;
-               }
+       memset(&frame, 0, sizeof(frame));
+       frame.frametype = AST_FRAME_VOICE;
+       frame.subclass = AST_FORMAT_SLINEAR;
+       frame.data = buf;
+       frame.samples = x;
+       frame.datalen = x * 2;
+
+       if (ast_write(chan, &frame)) {
+               csth->spy.status = CHANSPY_DONE;
+               return -1;
+       }
 
-               if (csth->fd) {
-                       write(csth->fd, buf1, len1);
-               }
+       if (csth->fd) {
+               write(csth->fd, buf1, len1);
+       }
 
-               return 0;
+       return 0;
 }
 
 
 static struct ast_generator spygen = {
-    alloc: spy_alloc, 
-    release: spy_release, 
-    generate: spy_generate, 
+       alloc: spy_alloc, 
+       release: spy_release, 
+       generate: spy_generate, 
 };
 
 static void start_spying(struct ast_channel *chan, struct ast_channel *spychan, struct ast_channel_spy *spy) 
@@ -398,6 +394,34 @@ static void stop_spying(struct ast_channel *chan, struct ast_channel_spy *spy)
 
 }
 
+/* Map 'volume' levels from -4 through +4 into
+   decibel (dB) settings for channel drivers
+*/
+static signed char volfactor_map[] = {
+       -24,
+       -18,
+       -12,
+       -6,
+       0,
+       6,
+       12,
+       18,
+       24,
+};
+
+/* attempt to set the desired gain adjustment via the channel driver;
+   if successful, clear it out of the csth structure so the
+   generator will not attempt to do the adjustment itself
+*/
+static void set_volume(struct ast_channel *chan, struct chanspy_translation_helper *csth)
+{
+       signed char volume_adjust = volfactor_map[csth->volfactor + 4];
+
+       if (!ast_channel_setoption(chan, AST_OPTION_TXGAIN, &volume_adjust, sizeof(volume_adjust), 0)) {
+               csth->volfactor = 0;
+       }
+}
+
 static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int *volfactor, int fd) 
 {
        struct chanspy_translation_helper csth;
@@ -416,6 +440,7 @@ static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int
                csth.spy.status = CHANSPY_RUNNING;
                ast_mutex_init(&csth.spy.lock);
                csth.volfactor = *volfactor;
+               set_volume(chan, &csth);
                
                if (fd) {
                        csth.fd = fd;
@@ -423,12 +448,12 @@ static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int
                start_spying(spyee, chan, &csth.spy);
                ast_activate_generator(chan, &spygen, &csth);
 
-               while(csth.spy.status == CHANSPY_RUNNING && 
-                         chan && !ast_check_hangup(chan) && 
-                         spyee && 
-                         !ast_check_hangup(spyee) 
-                         && running == 1 && 
-                         (res = ast_waitfor(chan, -1) > -1)) {
+               while (csth.spy.status == CHANSPY_RUNNING &&
+                      chan && !ast_check_hangup(chan) &&
+                      spyee &&
+                      !ast_check_hangup(spyee) &&
+                      running == 1 &&
+                      (res = ast_waitfor(chan, -1) > -1)) {
                        if ((f = ast_read(chan))) {
                                res = 0;
                                if (f->frametype == AST_FRAME_DTMF) {
@@ -456,14 +481,15 @@ static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int
                                        running = x ? atoi(inp) : -1;
                                        break;
                                } else {
-                                       csth.volfactor++;
-                                       if (csth.volfactor > 4) {
-                                               csth.volfactor = -4;
+                                       (*volfactor)++;
+                                       if (*volfactor > 4) {
+                                               *volfactor = -4;
                                        }
                                        if (option_verbose > 2) {
-                                               ast_verbose(VERBOSE_PREFIX_3"Setting spy volume on %s to %d\n", chan->name, csth.volfactor);
+                                               ast_verbose(VERBOSE_PREFIX_3 "Setting spy volume on %s to %d\n", chan->name, *volfactor);
                                        }
-                                       *volfactor = csth.volfactor;
+                                       csth.volfactor = *volfactor;
+                                       set_volume(chan, &csth);
                                }
                        } else if (res >= 48 && res <= 57) {
                                inp[x++] = res;
@@ -483,8 +509,6 @@ static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int
        return running;
 }
 
-
-
 static int chanspy_exec(struct ast_channel *chan, void *data)
 {
        struct localuser *u;
@@ -560,12 +584,13 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
                silent = ast_test_flag(&flags, OPTION_QUIET);
                bronly = ast_test_flag(&flags, OPTION_BRIDGED);
                if (ast_test_flag(&flags, OPTION_VOLUME) && opts[1]) {
-                       if (sscanf(opts[0], "%d", &volfactor) != 1)
-                               ast_log(LOG_NOTICE, "volfactor must be a number between -4 and 4\n");
-                       else {
-                               volfactor = minmax(volfactor, 4);
+                       int vol;
+
+                       if ((sscanf(opts[0], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
+                               ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
+                       else
+                               volfactor = vol;
                        }
-               }
        }
 
        if (recbase) {
@@ -614,9 +639,9 @@ static int chanspy_exec(struct ast_channel *chan, void *data)
                                }
                                
                                if (igrp && (!spec || ((strlen(spec) < strlen(peer->name) &&
-                                                                          !strncasecmp(peer->name, spec, strlen(spec)))))) {
+                                                       !strncasecmp(peer->name, spec, strlen(spec)))))) {
                                        if (peer && (!bronly || ast_bridged_channel(peer)) &&
-                                               !ast_check_hangup(peer) && !ast_test_flag(peer, AST_FLAG_SPYING)) {
+                                           !ast_check_hangup(peer) && !ast_test_flag(peer, AST_FLAG_SPYING)) {
                                                int x = 0;
 
                                                strncpy(peer_name, peer->name, AST_NAME_STRLEN);
@@ -694,7 +719,7 @@ int load_module(void)
 
 char *description(void)
 {
-       return synopsis;
+       return (char *) synopsis;
 }
 
 int usecount(void)
index 5589f29..0599e5e 100755 (executable)
@@ -136,16 +136,19 @@ static struct ast_conference {
 } *confs;
 
 struct ast_conf_user {
-       int user_no;                 /* User Number */
-       struct ast_conf_user *prevuser;  /* Pointer to the previous user */
-       struct ast_conf_user *nextuser;  /* Pointer to the next user */
-       int userflags;                   /* Flags as set in the conference */
-       int adminflags;                  /* Flags set by the Admin */
-       struct ast_channel *chan;        /* Connected channel */
-       int talking;                     /* Is user talking */
-       char usrvalue[50];               /* Custom User Value */
-       char namerecloc[AST_MAX_EXTENSION]; /* Name Recorded file Location */
-       time_t jointime;                 /* Time the user joined the conference */
+       int user_no;                            /* User Number */
+       struct ast_conf_user *prevuser;         /* Pointer to the previous user */
+       struct ast_conf_user *nextuser;         /* Pointer to the next user */
+       int userflags;                          /* Flags as set in the conference */
+       int adminflags;                         /* Flags set by the Admin */
+       struct ast_channel *chan;               /* Connected channel */
+       int talking;                            /* Is user talking */
+       int zapchannel;                         /* Is a Zaptel channel */
+       char usrvalue[50];                      /* Custom User Value */
+       char namerecloc[AST_MAX_EXTENSION];     /* Name Recorded file Location */
+       time_t jointime;                        /* Time the user joined the conference */
+       int desired_volume;                     /* Desired volume adjustment */
+       int actual_volume;                      /* Actual volume adjustment (for channels that can't adjust) */
 };
 
 #define ADMINFLAG_MUTED (1 << 1)       /* User is muted */
@@ -153,6 +156,11 @@ struct ast_conf_user {
 #define MEETME_DELAYDETECTTALK                 300
 #define MEETME_DELAYDETECTENDTALK      1000
 
+enum volume_action {
+       VOL_UP,
+       VOL_DOWN,
+};
+
 AST_MUTEX_DEFINE_STATIC(conflock);
 
 static int admin_exec(struct ast_channel *chan, void *data);
@@ -251,6 +259,94 @@ static int careful_write(int fd, unsigned char *data, int len)
        return 0;
 }
 
+/* Map 'volume' levels from -5 through +5 into
+   decibel (dB) settings for channel drivers
+   Note: these are not a straight linear-to-dB
+   conversion... the numbers have been modified
+   to give the user a better level of adjustability
+*/
+static signed char gain_map[] = {
+       -15,
+       -13,
+       -10,
+       -6,
+       0,
+       0,
+       0,
+       6,
+       10,
+       13,
+       15,
+};
+
+static int set_volume(struct ast_conf_user *user, int volume)
+{
+       signed char gain_adjust;
+
+       /* attempt to make the adjustment in the channel driver;
+          if successful, don't adjust in the frame reading routine
+       */
+       gain_adjust = gain_map[volume + 5];
+       return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
+}
+
+static void tweak_volume(struct ast_conf_user *user, enum volume_action action)
+{
+       switch (action) {
+       case VOL_UP:
+               switch (user->desired_volume) {
+               case 5:
+                       break;
+               case 0:
+                       user->desired_volume = 2;
+                       break;
+               case -2:
+                       user->desired_volume = 0;
+                       break;
+               default:
+                       user->desired_volume++;
+                       break;
+               }
+               break;
+       case VOL_DOWN:
+               switch (user->desired_volume) {
+               case -5:
+                       break;
+               case 2:
+                       user->desired_volume = 0;
+                       break;
+               case 0:
+                       user->desired_volume = -2;
+                       break;
+               default:
+                       user->desired_volume--;
+                       break;
+               }
+       }
+       /* attempt to make the adjustment in the channel driver;
+          if successful, don't adjust in the frame reading routine
+       */
+       if (!set_volume(user, user->desired_volume))
+               user->actual_volume = 0;
+       else
+               user->actual_volume = user->desired_volume;
+}
+
+static void adjust_volume(struct ast_frame *f, int vol)
+{
+       int count;
+       short *fdata = f->data;
+
+       for (count = 0; count < f->datalen; count++) {
+               if (vol > 0) {
+                       fdata[count] *= abs(vol);
+               } else if (vol < 0) {
+                       fdata[count] /= abs(vol);
+               }
+       }
+}
+
+
 static void conf_play(struct ast_channel *chan, struct ast_conference *conf, int sound)
 {
        unsigned char *data;
@@ -789,6 +885,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c
        }
        ast_indicate(chan, -1);
        retryzap = strcasecmp(chan->type, "Zap");
+       user->zapchannel = retryzap;
 zapretry:
        origfd = chan->fds[0];
        if (retryzap) {
@@ -903,7 +1000,7 @@ zapretry:
                if (!agifile)
                        agifile = agifiledefault;
 
-               if (!strcasecmp(chan->type,"Zap")) {
+               if (user->zapchannel) {
                        /*  Set CONFMUTE mode on Zap channel to mute DTMF tones */
                        x = 1;
                        ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
@@ -916,13 +1013,13 @@ zapretry:
                        ast_log(LOG_WARNING, "Could not find application (agi)\n");
                        ret = -2;
                }
-               if (!strcasecmp(chan->type,"Zap")) {
+               if (user->zapchannel) {
                        /*  Remove CONFMUTE mode on Zap channel */
                        x = 0;
                        ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
                }
        } else {
-               if (!strcasecmp(chan->type,"Zap") && (confflags & CONFFLAG_STARMENU)) {
+               if (user->zapchannel && (confflags & CONFFLAG_STARMENU)) {
                        /*  Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
                        x = 1;
                        ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
@@ -932,8 +1029,19 @@ zapretry:
                        res = -1;
                }
                for(;;) {
+                       int menu_was_active = 0;
+
                        outfd = -1;
                        ms = -1;
+                       
+                       /* if we have just exited from the menu, and the user had a channel-driver
+                          volume adjustment, restore it
+                       */
+                       if (!menu_active && menu_was_active && user->desired_volume && !user->actual_volume)
+                               set_volume(user, user->desired_volume);
+
+                       menu_was_active = menu_active;
+
                        currentmarked = conf->markedusers;
                        if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_MARKEDUSER) && (confflags & CONFFLAG_WAITMARKED) && lastmarked == 0) {
                                if (currentmarked == 1 && conf->users > 1) {
@@ -1079,6 +1187,9 @@ zapretry:
                                if (!f)
                                        break;
                                if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
+                                       if (user->actual_volume) {
+                                               adjust_volume(f, user->actual_volume);
+                                       }
                                        if (confflags &  CONFFLAG_MONITORTALKER) {
                                                int totalsilence;
                                                if (user->talking == -1)
@@ -1129,6 +1240,13 @@ zapretry:
                                                ast_mutex_unlock(&conflock);
                                                goto outrun;
                                        }
+
+                                       /* if we are entering the menu, and the user has a channel-driver
+                                          volume adjustment, clear it
+                                       */
+                                       if (!menu_active && user->desired_volume && !user->actual_volume)
+                                               set_volume(user, 0);
+
                                        if (musiconhold) {
                                                ast_moh_stop(chan);
                                        }
@@ -1189,6 +1307,19 @@ zapretry:
                                                                                usr->adminflags |= ADMINFLAG_KICKME;
                                                                        ast_stopstream(chan);
                                                                        break;  
+
+                                                               case '9':
+                                                                       tweak_volume(user, VOL_UP);
+                                                                       break;
+
+                                                               case '8':
+                                                                       menu_active = 0;
+                                                                       break;
+
+                                                               case '7':
+                                                                       tweak_volume(user, VOL_DOWN);
+                                                                       break;
+
                                                                default:
                                                                        menu_active = 0;
                                                                        /* Play an error message! */
@@ -1232,6 +1363,18 @@ zapretry:
                                                                                        ast_waitstream(chan, "");
                                                                        }
                                                                        break;
+                                                       case '9':
+                                                               tweak_volume(user, VOL_UP);
+                                                               break;
+
+                                                       case '8':
+                                                               menu_active = 0;
+                                                               break;
+
+                                                       case '7':
+                                                               tweak_volume(user, VOL_DOWN);
+                                                               break;
+
                                                                default:
                                                                        menu_active = 0;
                                                                        /* Play an error message! */
index b05544f..9c1c5f4 100755 (executable)
@@ -2992,18 +2992,29 @@ static int iax2_setoption(struct ast_channel *c, int option, void *data, int dat
 {
        struct ast_option_header *h;
        int res;
-       h = malloc(datalen + sizeof(struct ast_option_header));
-       if (h) {
-               h->flag = AST_OPTION_FLAG_REQUEST;
-               h->option = htons(option);
-               memcpy(h->data, data, datalen);
-               res = send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_CONTROL,
-                       AST_CONTROL_OPTION, 0, (unsigned char *)h, datalen + sizeof(struct ast_option_header), -1);
-               free(h);
-               return res;
-       } else 
-               ast_log(LOG_WARNING, "Out of memory\n");
-       return -1;
+
+       switch (option) {
+       case AST_OPTION_TXGAIN:
+       case AST_OPTION_RXGAIN:
+               /* these two cannot be sent, because they require a result */
+               errno = ENOSYS;
+               return -1;
+       default:
+               h = malloc(datalen + sizeof(*h));
+               if (h) {
+                       h->flag = AST_OPTION_FLAG_REQUEST;
+                       h->option = htons(option);
+                       memcpy(h->data, data, datalen);
+                       res = send_command_locked(PTR_TO_CALLNO(c->tech_pvt), AST_FRAME_CONTROL,
+                                                 AST_CONTROL_OPTION, 0, (unsigned char *) h,
+                                                 datalen + sizeof(*h), -1);
+                       free(h);
+                       return res;
+               } else {
+                       ast_log(LOG_WARNING, "Out of memory\n");
+                       return -1;
+               }
+       }
 }
 
 static struct ast_frame *iax2_read(struct ast_channel *c) 
index 6bf8f63..e6e31d2 100755 (executable)
@@ -1409,50 +1409,105 @@ static void zt_disable_ec(struct zt_pvt *p)
        p->echocanon = 0;
 }
 
-int set_actual_gain(int fd, int chan, float rxgain, float txgain, int law)
+static void fill_txgain(struct zt_gains *g, float gain, int law)
 {
-       struct  zt_gains g;
-       float ltxgain;
-       float lrxgain;
-       int j,k;
-       g.chan = chan;
-       if ((rxgain != 0.0)  || (txgain != 0.0)) {
-               /* caluculate linear value of tx gain */
-               ltxgain = pow(10.0,txgain / 20.0);
-               /* caluculate linear value of rx gain */
-               lrxgain = pow(10.0,rxgain / 20.0);
-               if (law == ZT_LAW_ALAW) {
-                       for (j=0;j<256;j++) {
-                               k = (int)(((float)AST_ALAW(j)) * lrxgain);
-                               if (k > 32767) k = 32767;
-                               if (k < -32767) k = -32767;
-                               g.rxgain[j] = AST_LIN2A(k);
-                               k = (int)(((float)AST_ALAW(j)) * ltxgain);
-                               if (k > 32767) k = 32767;
-                               if (k < -32767) k = -32767;
-                               g.txgain[j] = AST_LIN2A(k);
+       int j;
+       short k;
+       float linear_gain = pow(10.0, gain / 20.0);
+
+       switch (law) {
+       case ZT_LAW_ALAW:
+               for (j = 0; j < (sizeof(g->txgain) / sizeof(g->txgain[0])); j++) {
+                       if (gain) {
+                               k = (short) (((float) AST_ALAW(j)) * linear_gain);
+                               g->txgain[j] = AST_LIN2A(k);
+                       } else {
+                               g->txgain[j] = j;
                        }
-               } else {
-                       for (j=0;j<256;j++) {
-                               k = (int)(((float)AST_MULAW(j)) * lrxgain);
-                               if (k > 32767) k = 32767;
-                               if (k < -32767) k = -32767;
-                               g.rxgain[j] = AST_LIN2MU(k);
-                               k = (int)(((float)AST_MULAW(j)) * ltxgain);
-                               if (k > 32767) k = 32767;
-                               if (k < -32767) k = -32767;
-                               g.txgain[j] = AST_LIN2MU(k);
+               }
+               break;
+       case ZT_LAW_MULAW:
+               for (j = 0; j < (sizeof(g->txgain) / sizeof(g->txgain[0])); j++) {
+                       if (gain) {
+                               k = (short) (((float) AST_MULAW(j)) * linear_gain);
+                               g->txgain[j] = AST_LIN2MU(k);
+                       } else {
+                               g->txgain[j] = j;
                        }
                }
-       } else {
-               for (j=0;j<256;j++) {
-                       g.rxgain[j] = j;
-                       g.txgain[j] = j;
+               break;
+       }
+}
+
+static void fill_rxgain(struct zt_gains *g, float gain, int law)
+{
+       int j;
+       short k;
+       float linear_gain = pow(10.0, gain / 20.0);
+
+       switch (law) {
+       case ZT_LAW_ALAW:
+               for (j = 0; j < (sizeof(g->rxgain) / sizeof(g->rxgain[0])); j++) {
+                       if (gain) {
+                               k = (short) (((float) AST_ALAW(j)) * linear_gain);
+                               g->rxgain[j] = AST_LIN2A(k);
+                       } else {
+                               g->rxgain[j] = j;
+                       }
                }
+               break;
+       case ZT_LAW_MULAW:
+               for (j = 0; j < (sizeof(g->rxgain) / sizeof(g->rxgain[0])); j++) {
+                       if (gain) {
+                               k = (short) (((float) AST_MULAW(j)) * linear_gain);
+                               g->rxgain[j] = AST_LIN2MU(k);
+                       } else {
+                               g->rxgain[j] = j;
+                       }
+               }
+               break;
        }
-               
-         /* set 'em */
-       return(ioctl(fd,ZT_SETGAINS,&g));
+}
+
+int set_actual_txgain(int fd, int chan, float gain, int law)
+{
+       struct zt_gains g;
+       int res;
+
+       memset(&g, 0, sizeof(g));
+       g.chan = chan;
+       res = ioctl(fd, ZT_GETGAINS, &g);
+       if (res) {
+               ast_log(LOG_DEBUG, "Failed to read gains: %s\n", strerror(errno));
+               return res;
+       }
+
+       fill_txgain(&g, gain, law);
+
+       return ioctl(fd, ZT_SETGAINS, &g);
+}
+
+int set_actual_rxgain(int fd, int chan, float gain, int law)
+{
+       struct zt_gains g;
+       int res;
+
+       memset(&g, 0, sizeof(g));
+       g.chan = chan;
+       res = ioctl(fd, ZT_GETGAINS, &g);
+       if (res) {
+               ast_log(LOG_DEBUG, "Failed to read gains: %s\n", strerror(errno));
+               return res;
+       }
+
+       fill_rxgain(&g, gain, law);
+
+       return ioctl(fd, ZT_SETGAINS, &g);
+}
+
+int set_actual_gain(int fd, int chan, float rxgain, float txgain, int law)
+{
+       return set_actual_txgain(fd, chan, txgain, law) | set_actual_rxgain(fd, chan, rxgain, law);
 }
 
 static inline int zt_set_hook(int fd, int hs)
@@ -2570,65 +2625,78 @@ static int zt_answer(struct ast_channel *ast)
 
 static int zt_setoption(struct ast_channel *chan, int option, void *data, int datalen)
 {
-char   *cp;
-int    x;
-
+       char *cp;
+       signed char *scp;
+       int x;
+       int index;
        struct zt_pvt *p = chan->tech_pvt;
 
-       
-       if ((option != AST_OPTION_TONE_VERIFY) && (option != AST_OPTION_AUDIO_MODE) &&
-               (option != AST_OPTION_TDD) && (option != AST_OPTION_RELAXDTMF))
-          {
-               errno = ENOSYS;
-               return -1;
-          }
-       cp = (char *)data;
-       if ((!cp) || (datalen < 1))
-          {
+       /* all supported options require data */
+       if (!data || (datalen < 1)) {
                errno = EINVAL;
                return -1;
-          }
+       }
+
        switch(option) {
-           case AST_OPTION_TONE_VERIFY:
+       case AST_OPTION_TXGAIN:
+               scp = (signed char *) data;
+               index = zt_get_index(chan, p, 0);
+               if (index < 0) {
+                       ast_log(LOG_WARNING, "No index in TXGAIN?\n");
+                       return -1;
+               }
+               ast_log(LOG_DEBUG, "Setting actual tx gain on %s to %f\n", chan->name, p->txgain + (float) *scp);
+               return set_actual_txgain(p->subs[index].zfd, 0, p->txgain + (float) *scp, p->law);
+       case AST_OPTION_RXGAIN:
+               scp = (signed char *) data;
+               index = zt_get_index(chan, p, 0);
+               if (index < 0) {
+                       ast_log(LOG_WARNING, "No index in RXGAIN?\n");
+                       return -1;
+               }
+               ast_log(LOG_DEBUG, "Setting actual rx gain on %s to %f\n", chan->name, p->rxgain + (float) *scp);
+               return set_actual_rxgain(p->subs[index].zfd, 0, p->rxgain + (float) *scp, p->law);
+       case AST_OPTION_TONE_VERIFY:
                if (!p->dsp)
                        break;
-               switch(*cp) {
-                   case 1:
-                               ast_log(LOG_DEBUG, "Set option TONE VERIFY, mode: MUTECONF(1) on %s\n",chan->name);
-                               ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MUTECONF | p->dtmfrelax);  /* set mute mode if desired */
+               cp = (char *) data;
+               switch (*cp) {
+               case 1:
+                       ast_log(LOG_DEBUG, "Set option TONE VERIFY, mode: MUTECONF(1) on %s\n",chan->name);
+                       ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MUTECONF | p->dtmfrelax);  /* set mute mode if desired */
                        break;
-                   case 2:
-                               ast_log(LOG_DEBUG, "Set option TONE VERIFY, mode: MUTECONF/MAX(2) on %s\n",chan->name);
-                               ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX | p->dtmfrelax);  /* set mute mode if desired */
+               case 2:
+                       ast_log(LOG_DEBUG, "Set option TONE VERIFY, mode: MUTECONF/MAX(2) on %s\n",chan->name);
+                       ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX | p->dtmfrelax);  /* set mute mode if desired */
                        break;
-                   default:
-                               ast_log(LOG_DEBUG, "Set option TONE VERIFY, mode: OFF(0) on %s\n",chan->name);
-                               ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_DTMF | p->dtmfrelax);  /* set mute mode if desired */
+               default:
+                       ast_log(LOG_DEBUG, "Set option TONE VERIFY, mode: OFF(0) on %s\n",chan->name);
+                       ast_dsp_digitmode(p->dsp,DSP_DIGITMODE_DTMF | p->dtmfrelax);  /* set mute mode if desired */
                        break;
                }
                break;
-           case AST_OPTION_TDD:  /* turn on or off TDD */
+       case AST_OPTION_TDD:
+               /* turn on or off TDD */
+               cp = (char *) data;
+               p->mate = 0;
                if (!*cp) { /* turn it off */
                        ast_log(LOG_DEBUG, "Set option TDD MODE, value: OFF(0) on %s\n",chan->name);
                        if (p->tdd) tdd_free(p->tdd);
                        p->tdd = 0;
-                       p->mate = 0;
                        break;
                }
-               if (*cp == 2)
-                       ast_log(LOG_DEBUG, "Set option TDD MODE, value: MATE(2) on %s\n",chan->name);
-               else ast_log(LOG_DEBUG, "Set option TDD MODE, value: ON(1) on %s\n",chan->name);
-               p->mate = 0;
+               ast_log(LOG_DEBUG, "Set option TDD MODE, value: %s(%d) on %s\n",
+                       (*cp == 2) ? "MATE" : "ON", (int) *cp, chan->name);
                zt_disable_ec(p);
                /* otherwise, turn it on */
                if (!p->didtdd) { /* if havent done it yet */
                        unsigned char mybuf[41000],*buf;
                        int size,res,fd,len;
-                       int index;
                        struct pollfd fds[1];
+
                        buf = mybuf;
-                       memset(buf,0x7f,sizeof(mybuf)); /* set to silence */
-                       ast_tdd_gen_ecdisa(buf + 16000,16000);  /* put in tone */
+                       memset(buf, 0x7f, sizeof(mybuf)); /* set to silence */
+                       ast_tdd_gen_ecdisa(buf + 16000, 16000);  /* put in tone */
                        len = 40000;
                        index = zt_get_index(chan, p, 0);
                        if (index < 0) {
@@ -2649,7 +2717,7 @@ int       x;
                                        ast_log(LOG_DEBUG, "poll (for write) ret. 0 on channel %d\n", p->channel);
                                        continue;
                                }
-                                 /* if got exception */
+                               /* if got exception */
                                if (fds[0].revents & POLLPRI) return -1;
                                if (!(fds[0].revents & POLLOUT)) {
                                        ast_log(LOG_DEBUG, "write fd not ready on channel %d\n", p->channel);
@@ -2671,36 +2739,27 @@ int     x;
                        p->tdd = 0;
                        p->mate = 1;
                        break;
-                       }               
+               }               
                if (!p->tdd) { /* if we dont have one yet */
                        p->tdd = tdd_new(); /* allocate one */
                }               
                break;
-           case AST_OPTION_RELAXDTMF:  /* Relax DTMF decoding (or not) */
+       case AST_OPTION_RELAXDTMF:  /* Relax DTMF decoding (or not) */
                if (!p->dsp)
                        break;
-               if (!*cp)
-               {               
-                       ast_log(LOG_DEBUG, "Set option RELAX DTMF, value: OFF(0) on %s\n",chan->name);
-                       x = 0;
-               }
-               else
-               {               
-                       ast_log(LOG_DEBUG, "Set option RELAX DTMF, value: ON(1) on %s\n",chan->name);
-                       x = 1;
-               }
-               ast_dsp_digitmode(p->dsp,x ? DSP_DIGITMODE_RELAXDTMF : DSP_DIGITMODE_DTMF | p->dtmfrelax);
+               cp = (char *) data;
+               ast_log(LOG_DEBUG, "Set option RELAX DTMF, value: %s(%d) on %s\n",
+                       *cp ? "ON" : "OFF", (int) *cp, chan->name);
+               ast_dsp_digitmode(p->dsp, ((*cp) ? DSP_DIGITMODE_RELAXDTMF : DSP_DIGITMODE_DTMF) | p->dtmfrelax);
                break;
-           case AST_OPTION_AUDIO_MODE:  /* Set AUDIO mode (or not) */
-               if (!*cp)
-               {               
-                       ast_log(LOG_DEBUG, "Set option AUDIO MODE, value: OFF(0) on %s\n",chan->name);
+       case AST_OPTION_AUDIO_MODE:  /* Set AUDIO mode (or not) */
+               cp = (char *) data;
+               if (!*cp) {             
+                       ast_log(LOG_DEBUG, "Set option AUDIO MODE, value: OFF(0) on %s\n", chan->name);
                        x = 0;
                        zt_disable_ec(p);
-               }
-               else
-               {               
-                       ast_log(LOG_DEBUG, "Set option AUDIO MODE, value: ON(1) on %s\n",chan->name);
+               } else {                
+                       ast_log(LOG_DEBUG, "Set option AUDIO MODE, value: ON(1) on %s\n", chan->name);
                        x = 1;
                }
                if (ioctl(p->subs[SUB_REAL].zfd, ZT_AUDIOMODE, &x) == -1)
@@ -2708,6 +2767,7 @@ int       x;
                break;
        }
        errno = 0;
+
        return 0;
 }
 
index ae42b6c..e39974d 100755 (executable)
@@ -227,6 +227,22 @@ struct ast_frame_chain {
 /* Set (or clear) Audio (Not-Clear) Mode */
 #define        AST_OPTION_AUDIO_MODE           4
 
+/* Set channel transmit gain */
+/* Option data is a single signed char
+   representing number of decibels (dB)
+   to set gain to (on top of any gain
+   specified in channel driver)
+*/
+#define AST_OPTION_TXGAIN              5
+
+/* Set channel receive gain */
+/* Option data is a single signed char
+   representing number of decibels (dB)
+   to set gain to (on top of any gain
+   specified in channel driver)
+*/
+#define AST_OPTION_RXGAIN              6
+
 struct ast_option_header {
        /* Always keep in network byte order */
 #if __BYTE_ORDER == __BIG_ENDIAN