com.alicizax.unity.framework/Runtime/Audio/AudioService.cs
陈思海 46194ddee8 重构音频模块 1. 高频、大量音频反复调用时,单帧 CPU 开销与 GC 最优 2. AudioClip / AudioSource 的加载、缓存淘汰、卸载形成完整闭环,避免线性遍历 3. AudioSource 对象池 + 播放请求 struct 全部池化覆盖所有分配点 4. 支持3D环境音并具备距离衰减、遮挡等空间属性 5. 新增音频类型(BGM/SFX/Voice/Ambient) 6. 可调式监控Debug信息 及时跟踪音频缓存 处理 句柄状态
重构音频模块
1. 高频、大量音频反复调用时,单帧 CPU 开销与 GC 最优
2. AudioClip / AudioSource 的加载、缓存淘汰、卸载形成完整闭环,避免线性遍历
3. AudioSource 对象池 + 播放请求 struct 全部池化覆盖所有分配点
4. 支持3D环境音并具备距离衰减、遮挡等空间属性
5. 新增音频类型(BGM/SFX/Voice/Ambient)
6. 可调式监控Debug信息 及时跟踪音频缓存 处理 句柄状态
2026-04-23 17:21:36 +08:00

1248 lines
39 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using AlicizaX.ObjectPool;
using AlicizaX.Resource.Runtime;
using UnityEngine;
using UnityEngine.Audio;
using YooAsset;
namespace AlicizaX.Audio.Runtime
{
internal sealed class AudioService : ServiceBase, IAudioService, IAudioDebugService, IServiceTickable
{
private const string SourcePoolName = "Audio Source Pool";
private const int DefaultCacheCapacity = 128;
private const float DefaultClipTtl = 30f;
private const int HandleIndexBits = 20;
private const ulong HandleIndexMask = (1UL << HandleIndexBits) - 1UL;
private static readonly string[] VolumeParameterNames =
{
"SoundVolume",
"UISoundVolume",
"MusicVolume",
"VoiceVolume",
"AmbientVolume"
};
private readonly AudioCategory[] _categories = new AudioCategory[(int)AudioType.Max];
private readonly float[] _categoryVolumes = new float[(int)AudioType.Max];
private readonly bool[] _categoryEnables = new bool[(int)AudioType.Max];
private readonly Dictionary<string, AudioClipCacheEntry> _clipCache = new Dictionary<string, AudioClipCacheEntry>(DefaultCacheCapacity, StringComparer.Ordinal);
private readonly AudioSourceObject[][] _sourceObjects = new AudioSourceObject[(int)AudioType.Max][];
private IResourceService _resourceService;
private IObjectPool<AudioSourceObject> _sourcePool;
private AudioMixer _audioMixer;
private Transform _instanceRoot;
private AudioGroupConfig[] _configs;
private AudioAgent[] _handleAgents = Array.Empty<AudioAgent>();
private uint[] _handleGenerations = Array.Empty<uint>();
private AudioListener _listenerCache;
private AudioClipCacheEntry _lruHead;
private AudioClipCacheEntry _lruTail;
private AudioClipCacheEntry _allHead;
private AudioClipCacheEntry _allTail;
private int _clipCacheCapacity = DefaultCacheCapacity;
private float _clipTtl = DefaultClipTtl;
private float _volume = 1f;
private bool _enable = true;
private bool _unityAudioDisabled;
private bool _initialized;
private bool _ownsInstanceRoot;
public AudioMixer AudioMixer => _audioMixer;
public Transform InstanceRoot => _instanceRoot;
public Transform ListenerTransform => _listenerCache != null && _listenerCache.enabled && _listenerCache.gameObject.activeInHierarchy
? _listenerCache.transform
: null;
int IAudioDebugService.CategoryCount => _categories.Length;
int IAudioDebugService.ClipCacheCount => _clipCache.Count;
int IAudioDebugService.ClipCacheCapacity => _clipCacheCapacity;
int IAudioDebugService.HandleCapacity => _handleAgents.Length;
bool IAudioDebugService.Initialized => _initialized;
bool IAudioDebugService.UnityAudioDisabled => _unityAudioDisabled;
AudioClipCacheEntry IAudioDebugService.FirstClipCacheEntry => _allHead;
public int Priority => 0;
public float Volume
{
get => _unityAudioDisabled ? 0f : _volume;
set
{
if (_unityAudioDisabled)
{
return;
}
_volume = Mathf.Clamp01(value);
AudioListener.volume = _enable ? _volume : 0f;
}
}
public bool Enable
{
get => !_unityAudioDisabled && _enable;
set
{
if (_unityAudioDisabled)
{
return;
}
_enable = value;
AudioListener.volume = _enable ? _volume : 0f;
}
}
internal float MusicVolume
{
get => GetCategoryVolume(AudioType.Music);
set => SetCategoryVolume(AudioType.Music, value);
}
internal float SoundVolume
{
get => GetCategoryVolume(AudioType.Sound);
set => SetCategoryVolume(AudioType.Sound, value);
}
internal float UISoundVolume
{
get => GetCategoryVolume(AudioType.UISound);
set => SetCategoryVolume(AudioType.UISound, value);
}
internal float VoiceVolume
{
get => GetCategoryVolume(AudioType.Voice);
set => SetCategoryVolume(AudioType.Voice, value);
}
internal float AmbientVolume
{
get => GetCategoryVolume(AudioType.Ambient);
set => SetCategoryVolume(AudioType.Ambient, value);
}
internal bool MusicEnable
{
get => GetCategoryEnable(AudioType.Music);
set => SetCategoryEnable(AudioType.Music, value);
}
internal bool SoundEnable
{
get => GetCategoryEnable(AudioType.Sound);
set => SetCategoryEnable(AudioType.Sound, value);
}
internal bool UISoundEnable
{
get => GetCategoryEnable(AudioType.UISound);
set => SetCategoryEnable(AudioType.UISound, value);
}
internal bool VoiceEnable
{
get => GetCategoryEnable(AudioType.Voice);
set => SetCategoryEnable(AudioType.Voice, value);
}
internal bool AmbientEnable
{
get => GetCategoryEnable(AudioType.Ambient);
set => SetCategoryEnable(AudioType.Ambient, value);
}
protected override void OnInitialize() { }
protected override void OnDestroyService()
{
Shutdown(true);
}
public void Initialize(AudioGroupConfig[] audioGroupConfigs, Transform instanceRoot = null, AudioMixer audioMixer = null)
{
Shutdown(false);
_resourceService = AppServices.Require<IResourceService>();
IObjectPoolService objectPoolService = AppServices.Require<IObjectPoolService>();
_sourcePool = objectPoolService.HasObjectPool<AudioSourceObject>(SourcePoolName)
? objectPoolService.GetObjectPool<AudioSourceObject>(SourcePoolName)
: objectPoolService.CreatePool<AudioSourceObject>(new ObjectPoolCreateOptions(SourcePoolName, false, 10f, int.MaxValue, float.MaxValue, 10));
if (audioGroupConfigs == null || audioGroupConfigs.Length == 0)
{
throw new GameFrameworkException("AudioGroupConfig[] is invalid.");
}
_configs = audioGroupConfigs;
if (instanceRoot != null)
{
_instanceRoot = instanceRoot;
_ownsInstanceRoot = false;
}
else if (_instanceRoot == null)
{
_instanceRoot = new GameObject("[AudioService Instances]").transform;
_ownsInstanceRoot = true;
}
_instanceRoot.localScale = Vector3.one;
if (_ownsInstanceRoot)
{
UnityEngine.Object.DontDestroyOnLoad(_instanceRoot.gameObject);
}
_unityAudioDisabled = IsUnityAudioDisabled();
if (_unityAudioDisabled)
{
_initialized = true;
return;
}
_audioMixer = audioMixer != null ? audioMixer : Resources.Load<AudioMixer>("AudioMixer");
if (_audioMixer == null)
{
throw new GameFrameworkException("AudioMixer is invalid.");
}
int totalAgentCount = 0;
for (int i = 0; i < (int)AudioType.Max; i++)
{
AudioGroupConfig config = FindConfig((AudioType)i);
totalAgentCount += config != null ? config.AgentHelperCount : 1;
}
if ((ulong)totalAgentCount > HandleIndexMask)
{
throw new GameFrameworkException("Audio agent count exceeds handle capacity.");
}
_handleAgents = new AudioAgent[totalAgentCount];
_handleGenerations = new uint[totalAgentCount];
MemoryPool.Add<AudioPlayRequest>(totalAgentCount);
MemoryPool.Add<AudioLoadRequest>(totalAgentCount);
MemoryPool.Add<AudioClipCacheEntry>(_clipCacheCapacity);
int globalIndexOffset = 0;
for (int i = 0; i < (int)AudioType.Max; i++)
{
AudioGroupConfig config = FindConfig((AudioType)i);
if (config == null)
{
config = CreateRuntimeDefaultConfig((AudioType)i);
}
_categoryVolumes[i] = Mathf.Clamp(config.Volume, 0.0001f, 1f);
_categoryEnables[i] = !config.Mute;
_sourceObjects[i] = new AudioSourceObject[config.AgentHelperCount];
_categories[i] = new AudioCategory(this, _audioMixer, config, globalIndexOffset);
globalIndexOffset += config.AgentHelperCount;
ApplyMixerVolume(config, _categoryVolumes[i], _categoryEnables[i]);
}
_initialized = true;
}
public void Restart()
{
Initialize(_configs, _instanceRoot, _audioMixer);
}
public ulong Play(AudioType type, string path, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true)
{
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.Set2D(type, path, loop, volume, async, cacheClip);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong Play(AudioType type, AudioClip clip, bool loop = false, float volume = 1f)
{
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.Set2D(type, clip, loop, volume);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong Play3D(AudioType type, string path, in Vector3 position, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true)
{
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.Set3D(type, path, position, loop, volume, async, cacheClip);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong Play3D(AudioType type, string path, in Vector3 position, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true)
{
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.Set3D(type, path, position, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume, async, cacheClip);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong Play3D(AudioType type, AudioClip clip, in Vector3 position, bool loop = false, float volume = 1f)
{
if (clip == null)
{
return 0UL;
}
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.Set3D(type, clip, position, loop, volume);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong Play3D(AudioType type, AudioClip clip, in Vector3 position, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f)
{
if (clip == null)
{
return 0UL;
}
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.Set3D(type, clip, position, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong PlayFollow(AudioType type, string path, Transform target, in Vector3 localOffset, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true)
{
if (target == null)
{
return 0UL;
}
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.SetFollow(type, path, target, localOffset, loop, volume, async, cacheClip);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong PlayFollow(AudioType type, AudioClip clip, Transform target, in Vector3 localOffset, bool loop = false, float volume = 1f)
{
if (target == null || clip == null)
{
return 0UL;
}
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.SetFollow(type, clip, target, localOffset, loop, volume);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong PlayFollow(AudioType type, AudioClip clip, Transform target, in Vector3 localOffset, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f)
{
if (target == null || clip == null)
{
return 0UL;
}
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.SetFollow(type, clip, target, localOffset, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
public ulong PlayFollow(AudioType type, string path, Transform target, in Vector3 localOffset, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true)
{
if (target == null)
{
return 0UL;
}
AudioPlayRequest request = MemoryPool.Acquire<AudioPlayRequest>();
request.SetFollow(type, path, target, localOffset, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume, async, cacheClip);
ulong handle = Play(request);
MemoryPool.Release(request);
return handle;
}
internal ulong Play(AudioPlayRequest request)
{
if (!_initialized || _unityAudioDisabled || request == null)
{
return 0UL;
}
int index = (int)request.Type;
if ((uint)index >= (uint)_categories.Length)
{
return 0UL;
}
AudioCategory category = _categories[index];
return category != null ? category.Play(request) : 0UL;
}
public bool Stop(ulong handle, bool fadeout = false)
{
AudioAgent agent = ResolveHandle(handle);
if (agent == null)
{
return false;
}
agent.Stop(fadeout);
return true;
}
public bool IsPlaying(ulong handle)
{
AudioAgent agent = ResolveHandle(handle);
return agent != null && agent.IsPlayingState;
}
public void Stop(AudioType type, bool fadeout)
{
int index = (int)type;
if ((uint)index < (uint)_categories.Length && _categories[index] != null)
{
_categories[index].Stop(fadeout);
}
}
public void StopAll(bool fadeout)
{
for (int i = 0; i < _categories.Length; i++)
{
AudioCategory category = _categories[i];
if (category != null)
{
category.Stop(fadeout);
}
}
}
public void Pause(ulong handle)
{
AudioAgent agent = ResolveHandle(handle);
if (agent != null)
{
agent.Pause();
}
}
public void Resume(ulong handle)
{
AudioAgent agent = ResolveHandle(handle);
if (agent != null)
{
agent.Resume();
}
}
public void Preload(IList<string> paths, bool pin = true)
{
if (paths == null || _unityAudioDisabled)
{
return;
}
for (int i = 0; i < paths.Count; i++)
{
string path = paths[i];
if (string.IsNullOrEmpty(path))
{
continue;
}
AudioClipCacheEntry entry = GetOrCreateClipEntry(path, pin);
entry.Pinned = entry.Pinned || pin;
entry.CacheAfterUse = true;
TouchClip(entry);
if (entry.Handle == null)
{
BeginLoad(entry, true);
}
}
}
public void Unload(IList<string> paths)
{
if (paths == null)
{
return;
}
for (int i = 0; i < paths.Count; i++)
{
string path = paths[i];
if (string.IsNullOrEmpty(path) || !_clipCache.TryGetValue(path, out AudioClipCacheEntry entry))
{
continue;
}
entry.Pinned = false;
if (entry.RefCount <= 0 && !entry.Loading && entry.PendingHead == null)
{
RemoveClipEntry(entry);
}
}
}
public void ClearCache()
{
AudioClipCacheEntry entry = _allHead;
while (entry != null)
{
AudioClipCacheEntry next = entry.AllNext;
if (entry.RefCount <= 0 && !entry.Loading && entry.PendingHead == null)
{
RemoveClipEntry(entry);
}
entry = next;
}
}
void IServiceTickable.Tick(float deltaTime)
{
if (!_initialized || _unityAudioDisabled)
{
return;
}
for (int i = 0; i < _categories.Length; i++)
{
AudioCategory category = _categories[i];
if (category != null)
{
category.Update(deltaTime);
}
}
TrimClipCache();
}
internal AudioSourceObject AcquireSourceObject(AudioCategory category, int index)
{
string name = BuildSourceName(category.TypeIndex, index);
AudioSourceObject sourceObject = _sourcePool.Spawn(name);
if (sourceObject == null)
{
sourceObject = CreateSourceObject(category, index, name);
_sourcePool.Register(sourceObject, true);
}
_sourceObjects[category.TypeIndex][index] = sourceObject;
return sourceObject;
}
internal void ReleaseSourceObject(int typeIndex, int index)
{
AudioSourceObject[] sourceObjects = _sourceObjects[typeIndex];
if (sourceObjects == null)
{
return;
}
AudioSourceObject sourceObject = sourceObjects[index];
if (sourceObject == null)
{
return;
}
sourceObjects[index] = null;
if (sourceObject.Source != null)
{
sourceObject.Source.transform.SetParent(_instanceRoot, false);
}
_sourcePool.Unspawn(sourceObject);
}
internal ulong AllocateHandle(AudioAgent agent)
{
int index = agent.GlobalIndex;
if ((uint)index >= (uint)_handleAgents.Length)
{
return 0UL;
}
uint generation = _handleGenerations[index] + 1U;
if (generation == 0)
{
generation = 1U;
}
_handleGenerations[index] = generation;
_handleAgents[index] = agent;
return ((ulong)generation << HandleIndexBits) | (uint)(index + 1);
}
internal void ReleaseHandle(ulong handle, AudioAgent agent)
{
int index = (int)((handle & HandleIndexMask) - 1UL);
if ((uint)index < (uint)_handleAgents.Length && ReferenceEquals(_handleAgents[index], agent))
{
_handleAgents[index] = null;
}
}
private AudioAgent ResolveHandle(ulong handle)
{
if (handle == 0)
{
return null;
}
int index = (int)((handle & HandleIndexMask) - 1UL);
uint generation = (uint)(handle >> HandleIndexBits);
if ((uint)index >= (uint)_handleAgents.Length || _handleGenerations[index] != generation)
{
return null;
}
return _handleAgents[index];
}
internal AudioClipCacheEntry RequestClip(string address, bool async, bool cacheClip, AudioAgent agent, int generation)
{
AudioClipCacheEntry entry = GetOrCreateClipEntry(address, cacheClip);
entry.CacheAfterUse = entry.CacheAfterUse || cacheClip;
TouchClip(entry);
if (entry.IsLoaded)
{
return entry;
}
AudioLoadRequest request = MemoryPool.Acquire<AudioLoadRequest>();
request.Agent = agent;
request.Generation = generation;
entry.AddPending(request);
if (entry.Handle == null)
{
BeginLoad(entry, async);
}
return null;
}
internal void RetainClip(AudioClipCacheEntry entry)
{
entry.RefCount++;
RemoveFromLru(entry);
TouchClip(entry);
}
internal void ReleaseClip(AudioClipCacheEntry entry)
{
if (entry.RefCount > 0)
{
entry.RefCount--;
}
TouchClip(entry);
if (entry.RefCount <= 0)
{
if (entry.CacheAfterUse)
{
AddToLruTail(entry);
}
else
{
RemoveClipEntry(entry);
}
}
}
internal void OnClipLoadCompleted(AudioClipCacheEntry entry, AssetHandle handle)
{
if (entry == null || !_clipCache.TryGetValue(entry.Address, out AudioClipCacheEntry mapped) || !ReferenceEquals(mapped, entry))
{
if (handle is { IsValid: true })
{
handle.Dispose();
}
return;
}
entry.Loading = false;
if (handle != null)
{
handle.Completed -= entry.CompletedCallback;
}
bool success = handle is { IsValid: true } && handle.AssetObject is AudioClip;
if (success)
{
entry.Handle = handle;
entry.Clip = (AudioClip)handle.AssetObject;
TouchClip(entry);
}
AudioLoadRequest request = entry.PendingHead;
entry.PendingHead = null;
entry.PendingTail = null;
while (request != null)
{
AudioLoadRequest next = request.Next;
if (success)
{
request.Agent?.OnClipReady(entry, request.Generation);
}
else
{
request.Agent?.OnClipLoadFailed(request.Generation);
}
MemoryPool.Release(request);
request = next;
}
if (!success)
{
RemoveClipEntry(entry);
if (handle is { IsValid: true })
{
handle.Dispose();
}
}
else if (entry.RefCount <= 0 && entry.CacheAfterUse)
{
AddToLruTail(entry);
}
else if (success && entry.RefCount <= 0)
{
RemoveClipEntry(entry);
}
}
public float GetCategoryVolume(AudioType type)
{
if (_unityAudioDisabled)
{
return 0f;
}
int index = (int)type;
return (uint)index < (uint)_categoryVolumes.Length ? _categoryVolumes[index] : 0f;
}
public void SetCategoryVolume(AudioType type, float value)
{
if (_unityAudioDisabled)
{
return;
}
int index = (int)type;
if ((uint)index >= (uint)_categoryVolumes.Length)
{
return;
}
float volume = Mathf.Clamp(value, 0.0001f, 1f);
_categoryVolumes[index] = volume;
AudioGroupConfig config = GetConfig(type);
ApplyMixerVolume(config, volume, _categoryEnables[index]);
}
public bool GetCategoryEnable(AudioType type)
{
if (_unityAudioDisabled)
{
return false;
}
int index = (int)type;
return (uint)index < (uint)_categoryEnables.Length && _categoryEnables[index];
}
public void SetCategoryEnable(AudioType type, bool value)
{
if (_unityAudioDisabled)
{
return;
}
int index = (int)type;
if ((uint)index >= (uint)_categoryEnables.Length)
{
return;
}
_categoryEnables[index] = value;
AudioCategory category = _categories[index];
if (category != null)
{
category.Enabled = value;
}
AudioGroupConfig config = GetConfig(type);
ApplyMixerVolume(config, _categoryVolumes[index], value);
}
public void RegisterListener(AudioListener listener)
{
if (listener == null)
{
return;
}
_listenerCache = listener;
}
public void UnregisterListener(AudioListener listener)
{
if (listener == null)
{
return;
}
if (ReferenceEquals(_listenerCache, listener))
{
_listenerCache = null;
}
}
void IAudioDebugService.FillServiceDebugInfo(AudioServiceDebugInfo info)
{
if (info == null)
{
return;
}
info.Initialized = _initialized;
info.UnityAudioDisabled = _unityAudioDisabled;
info.Enable = Enable;
info.Volume = Volume;
info.CategoryCount = _categories.Length;
info.ActiveAgentCount = CountActiveAgents();
info.HandleCapacity = _handleAgents.Length;
info.ClipCacheCount = _clipCache.Count;
info.ClipCacheCapacity = _clipCacheCapacity;
info.Listener = _listenerCache;
info.InstanceRoot = _instanceRoot;
}
bool IAudioDebugService.FillCategoryDebugInfo(int typeIndex, AudioCategoryDebugInfo info)
{
if (info == null || (uint)typeIndex >= (uint)_categories.Length)
{
return false;
}
AudioCategory category = _categories[typeIndex];
if (category == null)
{
info.Clear();
info.Type = (AudioType)typeIndex;
return false;
}
float volume = (uint)typeIndex < (uint)_categoryVolumes.Length ? _categoryVolumes[typeIndex] : 0f;
category.FillDebugInfo(volume, info);
return true;
}
bool IAudioDebugService.FillAgentDebugInfo(int typeIndex, int agentIndex, AudioAgentDebugInfo info)
{
if (info == null || (uint)typeIndex >= (uint)_categories.Length)
{
return false;
}
AudioCategory category = _categories[typeIndex];
if (category == null || !category.TryGetAgent(agentIndex, out AudioAgent agent) || agent == null)
{
info.Clear();
return false;
}
agent.FillDebugInfo(info);
return true;
}
bool IAudioDebugService.FillClipCacheDebugInfo(AudioClipCacheEntry entry, AudioClipCacheDebugInfo info)
{
if (entry == null || info == null)
{
return false;
}
entry.FillDebugInfo(info);
return true;
}
private void ApplyMixerVolume(AudioGroupConfig config, float volume, bool enabled)
{
if (_audioMixer == null || config == null)
{
return;
}
string parameter = string.IsNullOrEmpty(config.ExposedVolumeParameter)
? VolumeParameterNames[(int)config.AudioType]
: config.ExposedVolumeParameter;
_audioMixer.SetFloat(parameter, enabled ? Mathf.Log10(volume) * 20f : -80f);
}
private AudioClipCacheEntry GetOrCreateClipEntry(string address, bool pinned)
{
if (_clipCache.TryGetValue(address, out AudioClipCacheEntry entry))
{
return entry;
}
entry = MemoryPool.Acquire<AudioClipCacheEntry>();
entry.Initialize(this, address, pinned);
_clipCache.Add(address, entry);
AddToAllList(entry);
if (pinned)
{
AddToLruTail(entry);
}
return entry;
}
private void BeginLoad(AudioClipCacheEntry entry, bool async)
{
entry.Loading = async;
if (async)
{
entry.Handle = _resourceService.LoadAssetAsyncHandle<AudioClip>(entry.Address);
entry.Handle.Completed += entry.CompletedCallback;
return;
}
AssetHandle handle = _resourceService.LoadAssetSyncHandle<AudioClip>(entry.Address);
entry.Handle = handle;
OnClipLoadCompleted(entry, handle);
}
private void TouchClip(AudioClipCacheEntry entry)
{
entry.LastUseTime = Time.realtimeSinceStartup;
if (entry.RefCount <= 0 && entry.IsLoaded)
{
MoveLruToTail(entry);
}
}
private void TrimClipCache()
{
float now = Time.realtimeSinceStartup;
while (_clipCache.Count > _clipCacheCapacity && _lruHead != null)
{
AudioClipCacheEntry entry = _lruHead;
if (!CanEvict(entry, now, false))
{
break;
}
RemoveClipEntry(entry);
}
AudioClipCacheEntry current = _lruHead;
while (current != null)
{
AudioClipCacheEntry next = current.LruNext;
if (!CanEvict(current, now, true))
{
break;
}
RemoveClipEntry(current);
current = next;
}
}
private bool CanEvict(AudioClipCacheEntry entry, float now, bool requireExpired)
{
if (entry == null || entry.RefCount > 0 || entry.Pinned || entry.Loading)
{
return false;
}
if (!requireExpired)
{
return true;
}
return now - entry.LastUseTime >= _clipTtl;
}
private void RemoveClipEntry(AudioClipCacheEntry entry)
{
if (entry == null)
{
return;
}
_clipCache.Remove(entry.Address);
RemoveFromLru(entry);
RemoveFromAllList(entry);
MemoryPool.Release(entry);
}
private void AddToLruTail(AudioClipCacheEntry entry)
{
if (entry.InLru || entry.RefCount > 0 || entry.Loading || !entry.CacheAfterUse)
{
return;
}
entry.InLru = true;
entry.LruPrev = _lruTail;
entry.LruNext = null;
if (_lruTail != null)
{
_lruTail.LruNext = entry;
}
else
{
_lruHead = entry;
}
_lruTail = entry;
}
private void RemoveFromLru(AudioClipCacheEntry entry)
{
if (!entry.InLru)
{
return;
}
AudioClipCacheEntry prev = entry.LruPrev;
AudioClipCacheEntry next = entry.LruNext;
if (prev != null)
{
prev.LruNext = next;
}
else
{
_lruHead = next;
}
if (next != null)
{
next.LruPrev = prev;
}
else
{
_lruTail = prev;
}
entry.LruPrev = null;
entry.LruNext = null;
entry.InLru = false;
}
private void MoveLruToTail(AudioClipCacheEntry entry)
{
if (!entry.InLru || ReferenceEquals(_lruTail, entry))
{
return;
}
RemoveFromLru(entry);
AddToLruTail(entry);
}
private void AddToAllList(AudioClipCacheEntry entry)
{
entry.AllPrev = _allTail;
entry.AllNext = null;
if (_allTail != null)
{
_allTail.AllNext = entry;
}
else
{
_allHead = entry;
}
_allTail = entry;
}
private void RemoveFromAllList(AudioClipCacheEntry entry)
{
AudioClipCacheEntry prev = entry.AllPrev;
AudioClipCacheEntry next = entry.AllNext;
if (prev != null)
{
prev.AllNext = next;
}
else
{
_allHead = next;
}
if (next != null)
{
next.AllPrev = prev;
}
else
{
_allTail = prev;
}
entry.AllPrev = null;
entry.AllNext = null;
}
private AudioSourceObject CreateSourceObject(AudioCategory category, int index, string name)
{
GameObject host = new GameObject(name);
host.transform.SetParent(category.InstanceRoot, false);
AudioSource source = host.AddComponent<AudioSource>();
source.playOnAwake = false;
source.outputAudioMixerGroup = category.MixerGroup;
source.rolloffMode = category.Config.RolloffMode;
source.minDistance = category.Config.MinDistance;
source.maxDistance = category.Config.MaxDistance;
AudioLowPassFilter lowPassFilter = host.AddComponent<AudioLowPassFilter>();
lowPassFilter.enabled = false;
host.SetActive(true);
return AudioSourceObject.Create(name, source, lowPassFilter);
}
private static string BuildSourceName(int typeIndex, int index)
{
return "AudioSource_" + typeIndex + "_" + index;
}
private AudioGroupConfig GetConfig(AudioType type)
{
AudioCategory category = _categories[(int)type];
if (category != null)
{
return category.Config;
}
return FindConfig(type);
}
private AudioGroupConfig FindConfig(AudioType type)
{
if (_configs == null)
{
return null;
}
for (int i = 0; i < _configs.Length; i++)
{
AudioGroupConfig config = _configs[i];
if (config != null && config.AudioType == type)
{
return config;
}
}
return null;
}
private int CountActiveAgents()
{
int count = 0;
for (int i = 0; i < _categories.Length; i++)
{
AudioCategory category = _categories[i];
if (category != null)
{
count += category.ActiveCount;
}
}
return count;
}
private static AudioGroupConfig CreateRuntimeDefaultConfig(AudioType type)
{
AudioGroupConfig config = new AudioGroupConfig();
config.SetDefaults(type, type.ToString(), VolumeParameterNames[(int)type], 8, type == AudioType.Music || type == AudioType.UISound ? 0f : 1f, type == AudioType.Voice || type == AudioType.Ambient);
return config;
}
private void Shutdown(bool destroyRoot)
{
StopAll(false);
for (int i = 0; i < _categories.Length; i++)
{
AudioCategory category = _categories[i];
if (category != null)
{
category.Shutdown();
_categories[i] = null;
}
}
ClearCache();
if (_sourcePool != null)
{
_sourcePool.ReleaseAllUnused();
}
Array.Clear(_handleAgents, 0, _handleAgents.Length);
Array.Clear(_handleGenerations, 0, _handleGenerations.Length);
_handleAgents = Array.Empty<AudioAgent>();
_handleGenerations = Array.Empty<uint>();
_resourceService = null;
_sourcePool = null;
_audioMixer = null;
_listenerCache = null;
_initialized = false;
for (int i = 0; i < _sourceObjects.Length; i++)
{
_sourceObjects[i] = null;
}
if (destroyRoot)
{
DestroyOwnedRoot();
}
}
private void DestroyOwnedRoot()
{
if (_ownsInstanceRoot && _instanceRoot != null)
{
UnityEngine.Object.Destroy(_instanceRoot.gameObject);
}
_instanceRoot = null;
_ownsInstanceRoot = false;
}
private static bool IsUnityAudioDisabled()
{
#if UNITY_EDITOR
try
{
TypeInfo typeInfo = typeof(AudioSettings).GetTypeInfo();
PropertyInfo propertyInfo = typeInfo.GetDeclaredProperty("unityAudioDisabled");
return propertyInfo != null && (bool)propertyInfo.GetValue(null);
}
catch (Exception exception)
{
Log.Error(exception.ToString());
}
#endif
return false;
}
}
}