diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp
index 1f705f9e60b156cfde44c4d1f750bad39c22f18b..efa0110f8b4ffd9a5efa1202b94f0c3276252d93 100644
--- a/indra/llwindow/llwindowsdl.cpp
+++ b/indra/llwindow/llwindowsdl.cpp
@@ -2543,6 +2543,7 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList()
 	// Use libfontconfig to find us a nice ordered list of fallback fonts
 	// specific to this system.
 	std::string final_fallback("/usr/share/fonts/truetype/kochi/kochi-gothic.ttf");
+	const int max_font_count_cutoff = 40; // fonts are expensive in the current system, don't enumerate an arbitrary number of them
 	// Our 'ideal' font properties which define the sorting results.
 	// slant=0 means Roman, index=0 means the first face in a font file
 	// (the one we actually use), weight=80 means medium weight,
@@ -2558,7 +2559,6 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList()
 	std::vector<std::string> rtns;
 	FcFontSet *fs = NULL;
 	FcPattern *sortpat = NULL;
-	int font_count = 0;
 
 	llinfos << "Getting system font list from FontConfig..." << llendl;
 
@@ -2602,12 +2602,13 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList()
 		FcPatternDestroy(sortpat);
 	}
 
+	int found_font_count = 0;
 	if (fs)
 	{
 		// Get the full pathnames to the fonts, where available,
 		// which is what we really want.
-		int i;
-		for (i=0; i<fs->nfont; ++i)
+		found_font_count = fs->nfont;
+		for (int i=0; i<fs->nfont; ++i)
 		{
 			FcChar8 *filename;
 			if (FcResultMatch == FcPatternGetString(fs->fonts[i],
@@ -2616,7 +2617,8 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList()
 			    && filename)
 			{
 				rtns.push_back(std::string((const char*)filename));
-				++font_count;
+				if (rtns.size() >= max_font_count_cutoff)
+					break; // hit limit
 			}
 		}
 		FcFontSetDestroy (fs);
@@ -2629,7 +2631,7 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList()
 	{
 		lldebugs << "  file: " << *it << llendl;
 	}
-	llinfos << "Using " << font_count << " system font(s)." << llendl;
+	llinfos << "Using " << rtns.size() << "/" << found_font_count << " system fonts." << llendl;
 
 	rtns.push_back(final_fallback);
 	return rtns;
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 9f474a39bc264341ef91f31fe6aec22718f7b720..73fb24e4eb68009708037f2a8653246a243b8e03 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10369,7 +10369,7 @@
       <key>Type</key>
       <string>Boolean</string>
       <key>Value</key>
-      <integer>1</integer>
+      <integer>0</integer>
     </map>
     <key>VivoxDebugLevel</key>
     <map>
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index e8fdccf30e0592ce98fa0bc40eb98b20e810294f..d8319f3cc325246a9c241f782925dac6d91aa19f 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -87,6 +87,12 @@
 static bool sConnectingToAgni = false;
 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;
+
+const F32 VOLUME_SCALE_VIVOX = 0.01f;
+
 const F32 SPEAKING_TIMEOUT = 1.f;
 
 const int VOICE_MAJOR_VERSION = 1;
@@ -1112,24 +1118,28 @@ class LLSpeakerVolumeStorage : public LLSingleton<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 (vivox) 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 (vivox) volume level for specified speaker and
-	 * transforms it into internal (viewer) level.
+	 * Gets stored volume level for specified speaker
 	 *
-	 * If specified user is not found -1 will be returned.
-	 * Internal level is calculated as: internal = 400 * external^2
-	 * Maps 0.0 to 1.0 to internal values 0-400
+	 * @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>;
@@ -1141,6 +1151,9 @@ class LLSpeakerVolumeStorage : public LLSingleton<LLSpeakerVolumeStorage>
 	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;
 };
@@ -1159,23 +1172,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 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 ret_val;
+
+	return volume_out;
 }
 
 void LLSpeakerVolumeStorage::load()
@@ -1183,6 +1258,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);
@@ -1194,7 +1271,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);
 	}
 }
 
@@ -1209,9 +1289,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;
@@ -2420,9 +2505,10 @@ void LLVoiceClient::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();
@@ -3417,38 +3503,26 @@ void LLVoiceClient::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++)
 		{
 			participantState *p = iter->second;
-			
+
 			if(p->mVolumeDirty)
 			{
 				// 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)
-					{
-						// scale from user volume in the range 0-400 (with 100 as "normal") to vivox volume in the range 0-100 (with 56 as "normal")
-						if(p->mUserVolume < 100)
-							volume = (p->mUserVolume * 56) / 100;
-						else
-							volume = (((p->mUserVolume - 100) * (100 - 56)) / 300) + 56;
-					}
-					else if(p->mVolume != -1)
-					{
-						// Use the previously reported internal volume (comes in with a ParticipantUpdatedEvent)
-						volume = p->mVolume;
-					}
-										
 
 					if(mute)
 					{
@@ -3456,10 +3530,16 @@ void LLVoiceClient::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;
 					
@@ -4600,16 +4680,13 @@ void LLVoiceClient::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)
+			// 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 = volume;
+				participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX;
 			}
 
-			
 			// *HACK: mantipov: added while working on EXT-3544
 			/*
 			Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE 
@@ -4993,7 +5070,7 @@ void LLVoiceClient::muteListChanged()
 			
 			// Check to see if this participant is on the mute list already
 			if(p->updateMuteState())
-				mAudioSession->mVolumeDirty = true;
+				mAudioSession->mMuteDirty = true;
 		}
 	}
 }
@@ -5016,10 +5093,10 @@ LLVoiceClient::participantState::participantState(const std::string &uri) :
 	 mIsModeratorMuted(false), 
 	 mLastSpokeTimestamp(0.f), 
 	 mPower(0.f), 
-	 mVolume(-1), 
-	 mOnMuteList(false), 
-	 mUserVolume(-1), 
-	 mVolumeDirty(false), 
+	 mVolume(VOLUME_DEFAULT),
+	 mOnMuteList(false),
+	 mVolumeSet(false),
+	 mVolumeDirty(false),
 	 mAvatarIDValid(false),
 	 mIsSelf(false)
 {
@@ -5068,7 +5145,7 @@ LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(con
 				result->mAvatarID = id;
 
 				if(result->updateMuteState())
-					mVolumeDirty = true;
+					mMuteDirty = true;
 			}
 			else
 			{
@@ -5080,8 +5157,7 @@ LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(con
 		
 		mParticipantsByUUID.insert(participantUUIDMap::value_type(&(result->mAvatarID), result));
 
-		result->mUserVolume = LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID);
-		if (result->mUserVolume != -1)
+		if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume))
 		{
 			result->mVolumeDirty = true;
 			mVolumeDirty = true;
@@ -6279,51 +6355,21 @@ BOOL LLVoiceClient::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 LLVoiceClient::getUserVolume(const LLUUID& id)
 {
-	F32 result = 0.0f;
+	// Minimum volume will be returned for users with voice disabled
+	F32 result = VOLUME_MIN;
 	
 	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 
-
-			// 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;
+		result = participant->mVolume;
 
-			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;
 }
 
@@ -6332,16 +6378,23 @@ void LLVoiceClient::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);
+			if (!is_approx_equal(volume, 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);
+			}
 
-			// 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;
+			participant->mVolume = llclamp(volume, VOLUME_MIN, VOLUME_MAX);
+			participant->mVolumeDirty = true;
+			mAudioSession->mVolumeDirty = true;
 		}
 	}
 }
@@ -6482,6 +6535,7 @@ LLVoiceClient::sessionState::sessionState() :
 	mVoiceEnabled(false),
 	mReconnect(false),
 	mVolumeDirty(false),
+	mMuteDirty(false),
 	mParticipantsChanged(false)
 {
 }
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index aaacab69e0c69e220d28775bda0b0c966a2c3980..a29c386182db988b5d96d56ced8b4b1a57a8f2c8 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -101,6 +101,10 @@ class LLVoiceClient: public LLSingleton<LLVoiceClient>
 		
 		static 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
 	
 		void getCaptureDevicesSendMessage();
@@ -269,7 +273,7 @@ static	void updatePosition(void);
 		public:
 			participantState(const std::string &uri);
 
-			bool updateMuteState();
+			bool updateMuteState();	// true if mute state has changed
 			bool isAvatar();
 
 			std::string mURI;
@@ -279,13 +283,13 @@ static	void updatePosition(void);
 			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;
@@ -349,6 +353,7 @@ static	void updatePosition(void);
 			// 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;