Unable to set Android audio to 0 with SoundMix

The basic issue we are facing is that we have certain background music set as a member of the SoundClass “GameMusic”, and then we have a mute function for just these sounds by implementing a “MusicOff” and “MusicOn” SoundMix where the volume multiplier of “GameMusic” is set to either 0.0 or 1.0. When the “MusicOn” mix is applied the music plays normally, on PC when MusicOff is pushed and MusicOn is Popped the music is muted as expected. However, on Android the music can still be heard very faintly if you turn the volume up very high.

While debugging this issue I came across something in \Engine\Source\Runtime\Android\AndroidAudio\Private\AndroidAudioSource.cpp, at about line 391. The Volume is converted to Millibels for use in Android’s OpenSL Audio Effects. However it appears that the minimum is set to -3000 instead of the -9000 that appears in the Android Documentation. Indeed, changing this value to -9000 appears to resolve the problem we are facing.

I am not sure if this is an actual bug or not, as I am not well-versed in the fine details of Android audio. Perhaps there is a better solution to our issue that does not involve recompiling the engine, but I would appreciate some feedback from Epic on this.

Thank you.

Hey Thrakhath,

Thank you for taking the time to post this to the AnswerHub. I am glad you guys have a workaround, but your question is going to be assigned to another member of our team who has a bit more knowledge on this subject matter. Until he responds feel free to add additional comments or edit this post to help us find a solution to your issue.

Cheers,

This is due to an unfortunate android bug with how volumes were applied to playing voices in the android audio implementation. The bug arose out of a misunderstanding of how to properly convert linear volume to dB volume.

Ais the currently released android code that causes the problem (and that you found):

	// Convert volume to millibels.
	SLmillibel MaxMillibel = 0;
	SLmillibel MinMillibel = -3000;
	(*SL_VolumeInterface)->GetMaxVolumeLevel( SL_VolumeInterface, &MaxMillibel );
	SLmillibel VolumeMillibel = (Volume * (MaxMillibel - MinMillibel)) + MinMillibel;
	VolumeMillibel = FMath::Clamp(VolumeMillibel, MinMillibel, MaxMillibel);
	
	SLresult result = (*SL_VolumeInterface)->SetVolumeLevel(SL_VolumeInterface, VolumeMillibel);
	check(SL_RESULT_SUCCESS == result);

This algorithm takes the linear volume and scales it between a min and max decibel (-30 dB and 0) and is definitely not
how to do it.

Not only is it just wrong (the output volumes are not equivalent to the input volumes) but the min volume value (-30 dB)
will still be audible (as you discovered) if you crank your output volume. -30 dB is equivalent to a linear volume/magnitude of 0.032.

It is also not correct to just change the min volume in that clamp to -90 dB since (as mentioned above) it will result in incorrect volumes. For
example, if the input linear volume was 0.5, scaling this between -90 dB and 0 dB would be -45 dB! When in reality a 0.5 linear
volume is actually -6 dB and (as we all know) totally audible.

I recently checked in a fix to this, which properly converts linear volume to dB volume using the correct equation for
converting between linear and dB volumes:

    dBVolume = 20 * Log10(Linear)

And of course, going in reverse, solving for linear volume (if you want to confirm a given dB value results in the expected linear value):

Linear = pow(dBVolume / 20, 10)

Unfortunately, a linear volume of 0.0 will result in a hugely negative number (technically negative infinity) so we still
need to clamp it – but instead of clamping at an audible -30 dB, I am clamping it at an absolutely inaudible -120 dB.

		static const int64 MinVolumeMillibel = -12000;
		if (Volume > 0.0f)
		{
			// Convert volume to millibels.
			SLmillibel MaxMillibel = 0;
			(*SL_VolumeInterface)->GetMaxVolumeLevel(SL_VolumeInterface, &MaxMillibel);
			SLmillibel VolumeMillibel = (SLmillibel)FMath::Clamp<int64>((int64)(2000.0f * log10f(Volume)), MinVolumeMillibel, (int64)MaxMillibel);
			SLresult result = (*SL_VolumeInterface)->SetVolumeLevel(SL_VolumeInterface, VolumeMillibel);
			check(SL_RESULT_SUCCESS == result);
		}
		else
		{
			SLresult result = (*SL_VolumeInterface)->SetVolumeLevel(SL_VolumeInterface, MinVolumeMillibel);
			check(SL_RESULT_SUCCESS == result);
		}

This checked into mainline on github here:

https://github.com/EpicGames/UnrealEngine/commit/418b93bbb4c3d13e0b9e9e926117d739ab721ff3

Thanks so much for the quick reply and explaination. This patch seems to solve the issue completely.