Files
sbox-public/engine/Sandbox.Engine/Systems/Audio/SoundHandle.Source.cs
Lorenz Junglas 9d072e6d89 Audio Optimizations (#3847)
- Auto-cleanup idle voice handles: Voice sound handles are now killed when player hasn't been talking for a while
- Audio Occlusion refactor: Moved occlusion calculation to a dedicated SoundOcclusionSystem GameObjectSystem for better organization
  - This now performs really well, so we could look into improving our occlusion calculation: proper damping when sound is transferred via wall, path tracing for occlusion etc. (will open a separate issue)
- Fix mixer early return bug: Fixed issue where mixer could return early, potentially skipping sounds
- Voice Component lipsync toggle: Added option to enable/disable lipsync processing on VoiceComponent
- Cheaper HRTF for voice audio: Disabled expensive bilinear interpolation for voice and certain other sounds in Steam Audio
- Editor MixerDetail perf: Skip frame updates on MixerDetail widget when not visible
- Reduced allocations in audio systems: Optimized away List and LINQ allocations in SoundOcclusionSystem, Listener, and SoundHandle.Source
- MP3 decoder buffer optimization: Improved buffer handling in native CAudioMixerWaveMP3 to reduce overhead

Depending on scenario can reduces audio frame time by up to 30%.
This should also drastically improve performance for games that use VOIP.
2026-01-20 18:48:09 +01:00

102 lines
2.0 KiB
C#

using Sandbox.Audio;
namespace Sandbox;
partial class SoundHandle
{
private SteamAudioSource _audioSource;
private Dictionary<Listener, SteamAudioSource> _audioSources;
internal SteamAudioSource GetSource( Listener listener )
{
// Find listener source
if ( _audioSources?.TryGetValue( listener, out var source ) == true )
{
return source;
}
// Local audio source
return _audioSource;
}
private void UpdateSources()
{
// Only need a single audio source if listen local
if ( ListenLocal )
{
_audioSource ??= new SteamAudioSource();
_audioSource.UpdateFrom( this );
// Dispose listener sources if there's any
if ( _audioSources is not null )
{
foreach ( var source in _audioSources.Values )
{
source.Dispose();
}
_audioSources.Clear();
_audioSources = default;
}
return;
}
// Dispose local audio source if we're not listen local
if ( _audioSource is not null )
{
_audioSource?.Dispose();
_audioSource = null;
}
// Remove stale sources - only if we have any
if ( _audioSources is { Count: > 0 } )
{
foreach ( var removed in Listener.RemovedList )
{
if ( _audioSources.Remove( removed, out var source ) )
{
source.Dispose();
}
}
}
// Find listeners of this scene and update sources
var scene = Scene;
foreach ( var listener in Listener.ActiveList )
{
if ( listener.Scene != scene ) continue;
_audioSources ??= new();
if ( !_audioSources.TryGetValue( listener, out var source ) )
{
source = new SteamAudioSource();
_audioSources[listener] = source;
}
source.UpdateFrom( this, scene?.PhysicsWorld, listener.Position );
}
}
private void DisposeSources()
{
// Dispose local source
MainThread.QueueDispose( _audioSource );
_audioSource = default;
// Dispose listener sources if there's any
if ( _audioSources is not null )
{
foreach ( var source in _audioSources.Values )
{
MainThread.QueueDispose( source );
}
_audioSources.Clear();
_audioSources = default;
}
}
}