diff --git a/apps/app_chanspy.c b/apps/app_chanspy.c
index 5bc8c79d723346ad9f2b4c324f2d840a6a7f5646..93c8987411eeb06e91d66cec921604abd9cfd7e1 100755
--- a/apps/app_chanspy.c
+++ b/apps/app_chanspy.c
@@ -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)
diff --git a/apps/app_meetme.c b/apps/app_meetme.c
index 5589f2967434961511cbaf68e9632a252bd211fc..0599e5ebf3921968c346ffdd45f11bda5c14c1c9 100755
--- a/apps/app_meetme.c
+++ b/apps/app_meetme.c
@@ -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! */
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index b05544f24b6fa402be3872b322cf912c3ca604da..9c1c5f489baa9809bc13327d353f5c7d237eff79 100755
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -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) 
diff --git a/channels/chan_zap.c b/channels/chan_zap.c
index 6bf8f6398fe744a3f5e1b0a592606c2a49526c2c..e6e31d2a972b3d97969524485f4806cc593956fa 100755
--- a/channels/chan_zap.c
+++ b/channels/chan_zap.c
@@ -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;
 }
 
diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h
index ae42b6cae6a9452c96ab1863dd80f34816f70a5a..e39974d36343de7cd563f4d84332f5d3de4359e6 100755
--- a/include/asterisk/frame.h
+++ b/include/asterisk/frame.h
@@ -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