diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index e067754e3ea53408639ba125e0fb4f99c46f5689..70cff3644c30d39c69fd36f39f874b80ec932a09 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -43,6 +43,10 @@
 
 const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
 
+const F32 LLVoiceClient::VOLUME_MIN = 0.f;
+const F32 LLVoiceClient::VOLUME_DEFAULT = 0.5f;
+const F32 LLVoiceClient::VOLUME_MAX = 1.0f;
+
 std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus)
 {
 	std::string result = "UNKNOWN";
@@ -795,23 +799,85 @@ LLSpeakerVolumeStorage::~LLSpeakerVolumeStorage()
 
 void LLSpeakerVolumeStorage::storeSpeakerVolume(const LLUUID& speaker_id, F32 volume)
 {
-	mSpeakersData[speaker_id] = volume;
+	if ((volume >= LLVoiceClient::VOLUME_MIN) && (volume <= LLVoiceClient::VOLUME_MAX))
+	{
+		mSpeakersData[speaker_id] = volume;
+
+		// Enable this when debugging voice slider issues.  It's way to spammy even for debug-level logging.
+		// LL_DEBUGS("Voice") << "Stored volume = " << volume <<  " for " << id << LL_ENDL;
+	}
+	else
+	{
+		LL_WARNS("Voice") << "Attempted to store out of range volume " << volume << " for " << speaker_id << LL_ENDL;
+		llassert(0);
+	}
 }
 
-S32 LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id)
+bool LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id, F32& volume)
 {
-	// Return value of -1 indicates no level is stored for this speaker
-	S32 ret_val = -1;
 	speaker_data_map_t::const_iterator it = mSpeakersData.find(speaker_id);
 	
 	if (it != mSpeakersData.end())
 	{
-		F32 f_val = it->second;
-		// volume can amplify by as much as 4x!
-		S32 ivol = (S32)(400.f * f_val * f_val);
-		ret_val = llclamp(ivol, 0, 400);
+		volume = it->second;
+
+		// Enable this when debugging voice slider issues.  It's way to spammy even for debug-level logging.
+		// LL_DEBUGS("Voice") << "Retrieved stored volume = " << volume <<  " for " << id << LL_ENDL;
+
+		return true;
 	}
-	return ret_val;
+
+	return false;
+}
+
+void LLSpeakerVolumeStorage::removeSpeakerVolume(const LLUUID& speaker_id)
+{
+	mSpeakersData.erase(speaker_id);
+
+	// Enable this when debugging voice slider issues.  It's way to spammy even for debug-level logging.
+	// LL_DEBUGS("Voice") << "Removing stored volume for  " << id << LL_ENDL;
+}
+
+/* static */ F32 LLSpeakerVolumeStorage::transformFromLegacyVolume(F32 volume_in)
+{
+	// Convert to linear-logarithmic [0.0..1.0] with 0.5 = 0dB
+	// from legacy characteristic composed of two square-curves
+	// that intersect at volume_in = 0.5, volume_out = 0.56
+
+	F32 volume_out = 0.f;
+	volume_in = llclamp(volume_in, 0.f, 1.0f);
+
+	if (volume_in <= 0.5f)
+	{
+		volume_out = volume_in * volume_in * 4.f * 0.56f;
+	}
+	else
+	{
+		volume_out = (1.f - 0.56f) * (4.f * volume_in * volume_in - 1.f) / 3.f + 0.56f;
+	}
+
+	return volume_out;
+}
+
+/* static */ F32 LLSpeakerVolumeStorage::transformToLegacyVolume(F32 volume_in)
+{
+	// Convert from linear-logarithmic [0.0..1.0] with 0.5 = 0dB
+	// to legacy characteristic composed of two square-curves
+	// that intersect at volume_in = 0.56, volume_out = 0.5
+
+	F32 volume_out = 0.f;
+	volume_in = llclamp(volume_in, 0.f, 1.0f);
+
+	if (volume_in <= 0.56f)
+	{
+		volume_out = sqrt(volume_in / (4.f * 0.56f));
+	}
+	else
+	{
+		volume_out = sqrt((3.f * (volume_in - 0.56f) / (1.f - 0.56f) + 1.f) / 4.f);
+	}
+
+	return volume_out;
 }
 
 void LLSpeakerVolumeStorage::load()
@@ -819,6 +885,8 @@ void LLSpeakerVolumeStorage::load()
 	// load per-resident voice volume information
 	std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME);
 
+	LL_INFOS("Voice") << "Loading stored speaker volumes from: " << filename << LL_ENDL;
+
 	LLSD settings_llsd;
 	llifstream file;
 	file.open(filename);
@@ -830,7 +898,10 @@ void LLSpeakerVolumeStorage::load()
 	for (LLSD::map_const_iterator iter = settings_llsd.beginMap();
 		iter != settings_llsd.endMap(); ++iter)
 	{
-		mSpeakersData.insert(std::make_pair(LLUUID(iter->first), (F32)iter->second.asReal()));
+		// Maintain compatibility with 1.23 non-linear saved volume levels
+		F32 volume = transformFromLegacyVolume((F32)iter->second.asReal());
+
+		storeSpeakerVolume(LLUUID(iter->first), volume);
 	}
 }
 
@@ -845,9 +916,14 @@ void LLSpeakerVolumeStorage::save()
 		std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME);
 		LLSD settings_llsd;
 
+		LL_INFOS("Voice") << "Saving stored speaker volumes to: " << filename << LL_ENDL;
+
 		for(speaker_data_map_t::const_iterator iter = mSpeakersData.begin(); iter != mSpeakersData.end(); ++iter)
 		{
-			settings_llsd[iter->first.asString()] = iter->second;
+			// Maintain compatibility with 1.23 non-linear saved volume levels
+			F32 volume = transformToLegacyVolume(iter->second);
+
+			settings_llsd[iter->first.asString()] = volume;
 		}
 
 		llofstream file;
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index f1a7d3dbec2b22bd2d6fe9aa0a9af930ae5892ab..5d6bcf4a72dec08ece04114a0552d141d641d120 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -272,7 +272,11 @@ public:
 	
 	const LLVoiceVersionInfo getVersion();
 	
-static const F32 OVERDRIVEN_POWER_LEVEL;
+	static const F32 OVERDRIVEN_POWER_LEVEL;
+
+	static const F32 VOLUME_MIN;
+	static const F32 VOLUME_DEFAULT;
+	static const F32 VOLUME_MAX;
 
 	void updateSettings(); // call after loading settings and whenever they change
 
@@ -406,31 +410,34 @@ protected:
 /**
  * Speaker volume storage helper class
  **/
-
 class LLSpeakerVolumeStorage : public LLSingleton<LLSpeakerVolumeStorage>
 {
 	LOG_CLASS(LLSpeakerVolumeStorage);
 public:
 
 	/**
-	 * Sets internal voluem level for specified user.
+	 * Stores volume level for specified user.
 	 *
-	 * @param[in] speaker_id - LLUUID of user to store volume level for
-	 * @param[in] volume - external volume level to be stored for user.
+	 * @param[in] speaker_id - LLUUID of user to store volume level for.
+	 * @param[in] volume - volume level to be stored for user.
 	 */
 	void storeSpeakerVolume(const LLUUID& speaker_id, F32 volume);
 
 	/**
-	 * Gets stored external volume level for specified speaker.
+	 * Gets stored volume level for specified speaker
 	 *
-	 * If specified user is not found default level will be returned. It is equivalent of 
-	 * external level 0.5 from the 0.0..1.0 range.
-	 * Default external level is calculated as: internal = 400 * external^2
-	 * Maps 0.0 to 1.0 to internal values 0-400 with default 0.5 == 100
+	 * @param[in] speaker_id - LLUUID of user to retrieve volume level for.
+	 * @param[out] volume - set to stored volume if found, otherwise unmodified.
+	 * @return - true if a stored volume is found.
+	 */
+	bool getSpeakerVolume(const LLUUID& speaker_id, F32& volume);
+
+	/**
+	 * Removes stored volume level for specified user.
 	 *
-	 * @param[in] speaker_id - LLUUID of user to get his volume level
+	 * @param[in] speaker_id - LLUUID of user to remove.
 	 */
-	S32 getSpeakerVolume(const LLUUID& speaker_id);
+	void removeSpeakerVolume(const LLUUID& speaker_id);
 
 private:
 	friend class LLSingleton<LLSpeakerVolumeStorage>;
@@ -442,6 +449,9 @@ private:
 	void load();
 	void save();
 
+	static F32 LLSpeakerVolumeStorage::transformFromLegacyVolume(F32 volume_in);
+	static F32 LLSpeakerVolumeStorage::transformToLegacyVolume(F32 volume_in);
+
 	typedef std::map<LLUUID, F32> speaker_data_map_t;
 	speaker_data_map_t mSpeakersData;
 };
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index a7efc49c6767325eb3911bc36c61a187dd2d892c..6986ca0a90ebe3b740236f5beaa113f3c96ebfa2 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -78,6 +78,8 @@
 
 #define USE_SESSION_GROUPS 0
 
+const F32 VOLUME_SCALE_VIVOX = 0.01f;
+
 const F32 SPEAKING_TIMEOUT = 1.f;
 
 static const std::string VOICE_SERVER_TYPE = "Vivox";
@@ -1476,9 +1478,10 @@ void LLVivoxVoiceClient::stateMachine()
 					enforceTether();
 				}
 				
-				// Send an update if the ptt state has changed (which shouldn't be able to happen that often -- the user can only click so fast)
-				// or every 10hz, whichever is sooner.
-				if((mAudioSession && mAudioSession->mVolumeDirty) || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
+				// Send an update only if the ptt or mute state has changed (which shouldn't be able to happen that often
+				// -- the user can only click so fast) or every 10hz, whichever is sooner.
+				// Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged.
+				if((mAudioSession && mAudioSession->mMuteDirty) || mPTTDirty || mUpdateTimer.hasExpired())
 				{
 					mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
 					sendPositionalUpdate();
@@ -2475,11 +2478,12 @@ void LLVivoxVoiceClient::sendPositionalUpdate(void)
 		stream << "</Request>\n\n\n";
 	}	
 	
-	if(mAudioSession && mAudioSession->mVolumeDirty)
+	if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty))
 	{
 		participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
 
 		mAudioSession->mVolumeDirty = false;
+		mAudioSession->mMuteDirty = false;
 		
 		for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
 		{
@@ -2490,7 +2494,8 @@ void LLVivoxVoiceClient::sendPositionalUpdate(void)
 				// Can't set volume/mute for yourself
 				if(!p->mIsSelf)
 				{
-					int volume = 56; // nominal default value
+					// scale from the range 0.0-1.0 to vivox volume in the range 0-100
+					S32 volume = llround(p->mVolume / VOLUME_SCALE_VIVOX);
 					bool mute = p->mOnMuteList;
 					
 					if(p->mUserVolume != -1)
@@ -2514,10 +2519,15 @@ void LLVivoxVoiceClient::sendPositionalUpdate(void)
 						// If we want the user to be muted, set their volume to 0 as well.
 						// This isn't perfect, but it will at least reduce their volume to a minimum.
 						volume = 0;
+						// Mark the current volume level as set to prevent incoming events
+						// changing it to 0, so that we can return to it when unmuting.
+						p->mVolumeSet = true;
 					}
 					
 					if(volume == 0)
+					{
 						mute = true;
+					}
 
 					LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL;
 					
@@ -3671,15 +3681,12 @@ void LLVivoxVoiceClient::participantUpdatedEvent(
 				participant->mPower = 0.0f;
 			}
 
-			// *HACK: Minimal hack to fix EXT-6508, ignore the incoming volume if it is zero.
-			// This happens because we send volume zero to Vivox when someone is muted,
-			// Vivox then send it back to us, overwriting the previous volume.
-			// Remove this hack once volume refactoring from EXT-6031 is applied.
-			if (volume != 0)
-			  {
-			    participant->mVolume = volume;
-			  }
- 
+			// Ignore incoming volume level if it has been explicitly set, or there
+			//  is a volume or mute change pending.
+			if ( !participant->mVolumeSet && !participant->mVolumeDirty)
+			{
+				participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX;
+			}
 			
 			// *HACK: mantipov: added while working on EXT-3544                                                                                   
 			/*                                                                                                                                    
@@ -4089,7 +4096,7 @@ LLVivoxVoiceClient::participantState::participantState(const std::string &uri) :
 	 mIsModeratorMuted(false), 
 	 mLastSpokeTimestamp(0.f), 
 	 mPower(0.f), 
-	 mVolume(-1), 
+	 mVolume(LLVoiceClient::VOLUME_DEFAULT), 
 	 mOnMuteList(false), 
 	 mUserVolume(-1), 
 	 mVolumeDirty(false), 
@@ -4141,7 +4148,7 @@ LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::addParti
 				result->mAvatarID = id;
 
 				if(result->updateMuteState())
-					mVolumeDirty = true;
+					mMuteDirty = true;
 			}
 			else
 			{
@@ -4153,7 +4160,11 @@ LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::addParti
 		
 		mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result));
 
-		result->mUserVolume = LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID);
+		if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume))
+		{
+			result->mVolumeDirty = true;
+			mVolumeDirty = true;
+		}
 		
 		LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;
 	}
@@ -5351,51 +5362,21 @@ BOOL LLVivoxVoiceClient::getOnMuteList(const LLUUID& id)
 	return result;
 }
 
-// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100
-// internal = 400 * external^2
+// External accessors.
 F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id)
 {
-	F32 result = 0.0f;
+       // Minimum volume will be returned for users with voice disabled
+       F32 result = LLVoiceClient::VOLUME_MIN;
 	
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+        participantState *participant = findParticipantByID(id);
+        if(participant)
 	{
-		S32 ires = 100; // nominal default volume
-		
-		if(participant->mIsSelf)
-		{
-			// Always make it look like the user's own volume is set at the default.
-		}
-		else if(participant->mUserVolume != -1)
-		{
-			// Use the internal volume
-			ires = participant->mUserVolume;
-			
-			// Enable this when debugging voice slider issues.  It's way to spammy even for debug-level logging.
-//			LL_DEBUGS("Voice") << "mapping from mUserVolume " << ires << LL_ENDL;
-		}
-		else if(participant->mVolume != -1)
-		{
-			// Map backwards from vivox volume 
+		result = participant->mVolume;
 
-			// Enable this when debugging voice slider issues.  It's way to spammy even for debug-level logging.
-//			LL_DEBUGS("Voice") << "mapping from mVolume " << participant->mVolume << LL_ENDL;
-
-			if(participant->mVolume < 56)
-			{
-				ires = (participant->mVolume * 100) / 56;
-			}
-			else
-			{
-				ires = (((participant->mVolume - 56) * 300) / (100 - 56)) + 100;
-			}
-		}
-		result = sqrtf(((F32)ires) / 400.f);
+		// Enable this when debugging voice slider issues.  It's way to spammy even for debug-level logging.
+		// LL_DEBUGS("Voice") << "mVolume = " << result <<  " for " << id << LL_ENDL;
 	}
 
-	// Enable this when debugging voice slider issues.  It's way to spammy even for debug-level logging.
-//	LL_DEBUGS("Voice") << "returning " << result << LL_ENDL;
-
 	return result;
 }
 
@@ -5404,16 +5385,23 @@ void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
 	if(mAudioSession)
 	{
 		participantState *participant = findParticipantByID(id);
-		if (participant)
+		if (participant && !participant->mIsSelf)
 		{
-			// store this volume setting for future sessions
-			LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume);
-			// volume can amplify by as much as 4x!
-			S32 ivol = (S32)(400.f * volume * volume);
-			participant->mUserVolume = llclamp(ivol, 0, 400);
-			participant->mVolumeDirty = TRUE;
-			mAudioSession->mVolumeDirty = TRUE;
+			if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT))
+			{
+				// Store this volume setting for future sessions if it has been
+				// changed from the default
+				LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume);
+			}
+			else
+			{
+				// Remove stored volume setting if it is returned to the default
+				LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id);
+			}
 
+			participant->mVolume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX);
+			participant->mVolumeDirty = true;
+			mAudioSession->mVolumeDirty = true;
 		}
 	}
 }
diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h
index 10577254e891e5d8d49e909a3ee81492a7b3f699..e4e0b87ad04dae763260175cd6020e45e500776b 100644
--- a/indra/newview/llvoicevivox.h
+++ b/indra/newview/llvoicevivox.h
@@ -260,7 +260,7 @@ protected:
 	public:
 		participantState(const std::string &uri);
 		
-		bool updateMuteState();
+	        bool updateMuteState();	// true if mute state has changed
 		bool isAvatar();
 		
 		std::string mURI;
@@ -270,13 +270,14 @@ protected:
 		LLFrameTimer mSpeakingTimeout;
 		F32	mLastSpokeTimestamp;
 		F32 mPower;
-		int mVolume;
+		F32 mVolume;
 		std::string mGroupID;
 		int mUserVolume;
 		bool mPTT;
 		bool mIsSpeaking;
 		bool mIsModeratorMuted;
 		bool mOnMuteList;		// true if this avatar is on the user's mute list (and should be muted)
+	       bool mVolumeSet;		// true if incoming volume messages should not change the volume
 		bool mVolumeDirty;		// true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed)
 		bool mAvatarIDValid;
 		bool mIsSelf;
@@ -333,6 +334,7 @@ protected:
 		// Set to true when the mute state of someone in the participant list changes.
 		// The code will have to walk the list to find the changed participant(s).
 		bool		mVolumeDirty;
+	        bool		mMuteDirty;
 		
 		bool		mParticipantsChanged;
 		participantMap mParticipantsByURI;