com.alicizax.unity.framework/Runtime/Audio/AudioCategory.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

335 lines
9.4 KiB
C#

using UnityEngine;
using UnityEngine.Audio;
namespace AlicizaX.Audio.Runtime
{
internal sealed class AudioCategory
{
private readonly AudioService _service;
private readonly AudioAgent[] _agents;
private readonly AudioAgent[] _activeAgents;
private readonly AudioAgent[] _playHeap;
private readonly int[] _freeStack;
private int _activeCount;
private int _heapCount;
private int _freeCount;
private bool _enabled;
internal AudioType Type { get; }
internal int TypeIndex { get; }
internal Transform InstanceRoot { get; private set; }
internal AudioMixerGroup MixerGroup { get; }
internal AudioGroupConfig Config { get; }
internal int Capacity => _agents.Length;
internal int ActiveCount => _activeCount;
internal int FreeCount => _freeCount;
internal int HeapCount => _heapCount;
internal bool Enabled
{
get => _enabled;
set
{
if (_enabled == value)
{
return;
}
_enabled = value;
if (!_enabled)
{
Stop(false);
}
}
}
internal AudioCategory(AudioService service, AudioMixer audioMixer, AudioGroupConfig config, int globalIndexOffset)
{
_service = service;
Config = config;
Type = config.AudioType;
TypeIndex = (int)config.AudioType;
_enabled = !config.Mute;
MixerGroup = ResolveMixerGroup(audioMixer, config);
InstanceRoot = new GameObject("Audio Category - " + Type).transform;
InstanceRoot.SetParent(service.InstanceRoot, false);
int capacity = config.AgentHelperCount;
_agents = new AudioAgent[capacity];
_activeAgents = new AudioAgent[capacity];
_playHeap = new AudioAgent[capacity];
_freeStack = new int[capacity];
_freeCount = capacity;
for (int i = 0; i < capacity; i++)
{
AudioSourceObject sourceObject = service.AcquireSourceObject(this, i);
AudioAgent agent = new AudioAgent();
agent.Initialize(service, this, i, globalIndexOffset + i, sourceObject);
_agents[i] = agent;
_freeStack[i] = capacity - 1 - i;
}
}
internal ulong Play(AudioPlayRequest request)
{
if (!_enabled)
{
return 0UL;
}
AudioAgent agent = AcquireAgent();
return agent != null ? agent.Play(request) : 0UL;
}
internal void Stop(bool fadeout)
{
for (int i = _activeCount - 1; i >= 0; i--)
{
AudioAgent agent = _activeAgents[i];
if (agent != null)
{
agent.Stop(fadeout);
}
}
}
internal void Update(float deltaTime)
{
int i = 0;
while (i < _activeCount)
{
AudioAgent agent = _activeAgents[i];
agent.Update(deltaTime);
if (i < _activeCount && _activeAgents[i] == agent)
{
i++;
}
}
}
internal bool TryGetAgent(int index, out AudioAgent agent)
{
if ((uint)index >= (uint)_agents.Length)
{
agent = null;
return false;
}
agent = _agents[index];
return agent != null;
}
internal void FillDebugInfo(float volume, AudioCategoryDebugInfo info)
{
if (info == null)
{
return;
}
info.Type = Type;
info.Enabled = _enabled;
info.Volume = volume;
info.Capacity = _agents.Length;
info.ActiveCount = _activeCount;
info.FreeCount = _freeCount;
info.HeapCount = _heapCount;
}
internal void MarkOccupied(AudioAgent agent)
{
if (agent.ActiveIndex < 0)
{
agent.ActiveIndex = _activeCount;
_activeAgents[_activeCount++] = agent;
}
if (agent.HeapIndex < 0)
{
int heapIndex = _heapCount++;
_playHeap[heapIndex] = agent;
agent.HeapIndex = heapIndex;
SiftHeapUp(heapIndex);
}
}
internal void MarkFree(AudioAgent agent)
{
RemoveActive(agent);
RemoveHeap(agent);
if (_freeCount < _freeStack.Length)
{
_freeStack[_freeCount++] = agent.Index;
}
}
internal void Shutdown()
{
Stop(false);
for (int i = 0; i < _agents.Length; i++)
{
AudioAgent agent = _agents[i];
if (agent != null)
{
agent.Shutdown();
_service.ReleaseSourceObject(TypeIndex, i);
_agents[i] = null;
}
}
_activeCount = 0;
_heapCount = 0;
_freeCount = 0;
if (InstanceRoot != null)
{
Object.Destroy(InstanceRoot.gameObject);
InstanceRoot = null;
}
}
private AudioAgent AcquireAgent()
{
if (_freeCount > 0)
{
int index = _freeStack[--_freeCount];
return _agents[index];
}
if (_heapCount <= 0)
{
return null;
}
AudioAgent oldest = _playHeap[0];
RemoveActive(oldest);
RemoveHeapAt(0);
return oldest;
}
private void RemoveActive(AudioAgent agent)
{
int index = agent.ActiveIndex;
if (index < 0)
{
return;
}
int lastIndex = --_activeCount;
AudioAgent last = _activeAgents[lastIndex];
_activeAgents[lastIndex] = null;
if (index != lastIndex)
{
_activeAgents[index] = last;
last.ActiveIndex = index;
}
agent.ActiveIndex = -1;
}
private void RemoveHeap(AudioAgent agent)
{
int index = agent.HeapIndex;
if (index >= 0)
{
RemoveHeapAt(index);
}
}
private void RemoveHeapAt(int index)
{
int lastIndex = --_heapCount;
AudioAgent removed = _playHeap[index];
AudioAgent last = _playHeap[lastIndex];
_playHeap[lastIndex] = null;
removed.HeapIndex = -1;
if (index == lastIndex)
{
return;
}
_playHeap[index] = last;
last.HeapIndex = index;
int parent = (index - 1) >> 1;
if (index > 0 && IsOlder(last, _playHeap[parent]))
{
SiftHeapUp(index);
}
else
{
SiftHeapDown(index);
}
}
private void SiftHeapUp(int index)
{
AudioAgent item = _playHeap[index];
while (index > 0)
{
int parent = (index - 1) >> 1;
AudioAgent parentAgent = _playHeap[parent];
if (!IsOlder(item, parentAgent))
{
break;
}
_playHeap[index] = parentAgent;
parentAgent.HeapIndex = index;
index = parent;
}
_playHeap[index] = item;
item.HeapIndex = index;
}
private void SiftHeapDown(int index)
{
AudioAgent item = _playHeap[index];
int half = _heapCount >> 1;
while (index < half)
{
int child = (index << 1) + 1;
int right = child + 1;
AudioAgent childAgent = _playHeap[child];
if (right < _heapCount && IsOlder(_playHeap[right], childAgent))
{
child = right;
childAgent = _playHeap[child];
}
if (!IsOlder(childAgent, item))
{
break;
}
_playHeap[index] = childAgent;
childAgent.HeapIndex = index;
index = child;
}
_playHeap[index] = item;
item.HeapIndex = index;
}
private static bool IsOlder(AudioAgent left, AudioAgent right)
{
return left.StartedAt < right.StartedAt;
}
private static AudioMixerGroup ResolveMixerGroup(AudioMixer audioMixer, AudioGroupConfig config)
{
AudioMixerGroup[] groups = audioMixer.FindMatchingGroups("Master/" + config.AudioType);
if (groups != null && groups.Length > 0)
{
return groups[0];
}
groups = audioMixer.FindMatchingGroups("Master");
return groups != null && groups.Length > 0 ? groups[0] : null;
}
}
}