RAGE 音频引擎:audioengine / audiohardware / soundtypes / synth
本页说明 RAGE 底层音频运行时:一个由 metadata 驱动的“声音对象层次”(audSound 树)如何在专用音频线程上被更新,如何通过无锁的多缓冲参数和命令缓冲把结果交给混音线程,再由软件混音器(submix 图 + DSP 效果)生成 48 kHz 浮点输出并交给各平台的输出驱动。GTA V 游戏层(audNorthAudioEngine 及各类 audEntity 派生)全部建立在这一层之上,见 game-audio.md;音乐、电台与语音见 music-radio-speech.md。
背景与相关页面#
- 线程/任务系统总体背景见 ../01-runtime/threading-and-render-thread.md;
gameSkeleton注册见 ../01-runtime/game-skeleton-and-main-loop.md。 - 音频 metadata 自有一套二进制格式(与 parser/PSO 无关),parser 体系见 ../02-rage-core/parser-and-metadata.md;wave bank 通过
pgStreamer读取,见 ../02-rage-core/paging-and-resources.md 与 ../02-rage-core/file-system-and-rpf.md。 - 术语:
- sound(
audSound):由 metadata 对象实例化出来的运行时节点,构成一棵层次树;叶子audEnvironmentSound才持有“虚拟 voice”。 - voice:混音器层面的一个播放通道。虚拟 voice 数量远多于物理 voice,引擎每帧按优先级把物理 voice 分配给最重要的虚拟 voice(virtualisation)。
- PCM source:在混音线程上生成 PCM 帧的对象(波形播放器、颗粒播放器、合成器、外部流等)。
- RAVE:音频设计工具,
__BANK构建下可通过audRemoteControl实时下发 metadata。
- sound(
相关源码文件#
| 文件 | 作用 |
|---|---|
rage/base/src/audioengine/engine.{h,cpp} | audEngine:引擎初始化、音频线程、AudioFrame() |
rage/base/src/audioengine/controller.{h,cpp}、entity.{h,cpp} | audController(每线程一个)与 audEntity(声音拥有者基类) |
rage/base/src/audioengine/soundmanager*.cpp、soundpool.{h,cpp}、soundfactory.{h,cpp} | 音频线程上的 sound 生命周期、bucket 池、按 ClassId 实例化 |
rage/base/src/audioengine/requestedsettings.{h,cpp}、enginedefs.h | 游戏线程→音频线程的多缓冲参数 |
rage/base/src/audioengine/metadatamanager.{h,cpp} | 所有音频 metadata(.dat)的加载与查找 |
rage/base/src/audioengine/category*.{h,cpp}、curve*.{h,cpp}、dynamicmixmgr.{h,cpp}、dynamixdefs.h、curvedefs.h | 类别、曲线、mix group |
rage/base/src/audioengine/environment*.{h,cpp} | 听者、声像/衰减计算、submix 路由(environment_game.cpp 是 GTA 定制部分) |
rage/base/src/audiosoundtypes/* | 36 种 sound 类型与生成的 sounddefs.h |
rage/base/src/audiohardware/driver.{h,cpp}、device*.{h,cpp} | audDriver 与软件混音器 audMixerDevice 及平台输出驱动 |
rage/base/src/audiohardware/voicemgr.{h,cpp}、virtualisation.{h,cpp} | 物理/虚拟 voice 管理 |
rage/base/src/audiohardware/waveslot.{h,cpp}、streamingwaveslot.{h,cpp}、decodemgr.cpp、decoder_*.cpp | wave bank 内存槽、流式加载、解码器 |
rage/base/src/audiohardware/submix.{h,cpp}、dspeffect.h、rage/base/src/audioeffecttypes/* | submix 与 DSP 效果 |
rage/base/src/audiosynth/* | 模块化合成器(编辑器)与 synthCore 字节码运行时 |
rage/base/src/audiodata/container.{h,cpp} | .awc 容器(ADAT)格式 |
rage/suite/src/audmusic/*、rage/suite/src/vieweraudio/* | 用户音乐(Xenon/PS3)抽象、查看器用简易音频管理器 |
1. 分层总览#
图中证据:GetSoundInstance 调用 SOUNDFACTORY.GetInstance 与选桶 src/dev_ng/rage/base/src/audioengine/controller.cpp:1171-1227;CommitGameSettings 推进写索引 src/dev_ng/rage/base/src/audioengine/soundmanager.cpp:294-298;AudioFrame 依次调用 m_SoundManager.Update、audDriver::Update、用户回调并提交命令缓冲 src/dev_ng/rage/base/src/audioengine/engine.cpp:591-619;audDriver::Update 调 m_VoiceManager.Update 与 audWaveSlot::UpdateSlots src/dev_ng/rage/base/src/audiohardware/driver.cpp:344-357;MixBuffers 执行命令、生成 PCM、混 voice 与 submix src/dev_ng/rage/base/src/audiohardware/device.cpp:169-264;输出设备的选择见第 11 节。
2. 初始化顺序#
入口是自由函数 InitializeAudio(controller, physAlloc, virtAlloc, configPath, ...):先 g_AudioEngine.Init,再初始化调用线程的 audController 并立即 PreUpdate/Update 一次使其激活 src/dev_ng/rage/base/src/audioengine/engine.cpp:154-171。GTA 中由 audNorthAudioEngine::InitClass 调用(见 game-audio.md)。
audEngine::Init 处理路径参数,-cheapaudioeffects 决定是否用廉价效果,随后静态初始化 audEntity、audPcmSource、audController,最后进入 PartialInit()(Reset() 也会再次调用它)src/dev_ng/rage/base/src/audioengine/engine.cpp:189-257。PartialInit 的顺序如下:
| 步骤 | 调用 | 证据 |
|---|---|---|
| 1 | audEngineUtil::InitClass;__BANK 下初始化 audRemoteControl(连接 RAVE)和 audRemoteDebug,连不上只告警 | engine.cpp:263-280 |
| 2 | audConfig::InitClass("<relPath><config>/audioconfig.dat"),再 audDriver::GetConfig().Init()、m_Config.Init() | engine.cpp:282-293 |
| 3 | 非 __FINAL 且驱动配置禁用处理时强制 -noaudio 与 -cheapaudioeffects | engine.cpp:295-301 |
| 4 | m_CurveRepository.Init → m_CategoryManager.Init → (除非 -nodynamicmixer)m_DynamicMixMgr.Init | engine.cpp:305-324 |
| 5 | audSoundManager::InitClass(分配 sound 池)→ m_SoundManager.Init(加载 Sounds metadata) | engine.cpp:326-338 |
| 6 | synthSynthesizer::InitClass(日志称 “AMP runtime”) | engine.cpp:340-345 |
| 7 | audDriver::InitClass(创建混音器、wave slot、voice 管理器);为调用线程建立名为 "GameThread" 的命令缓冲(默认 32 KB,PC 乘系数);m_Environment.Init() | engine.cpp:347-367 |
| 8 | audEnvironmentSound::InitClass;创建音频线程;audController::FlagControllersActive(true) | engine.cpp:369-385 |
audDriver::InitClass 内部:解析 -audiooutputchannels(缺省 kMaxOutputChannels = 6),InitMixer() 创建平台混音设备,成功后 audWaveSlot::InitClass(...) 与 m_VoiceManager.Init();混音器初始化失败直接 Quitf(ERR_AUD_HARDWARE, ...) src/dev_ng/rage/base/src/audiohardware/driver.cpp:168-245。
3. 线程模型与帧内时序#
3.1 参与的线程#
| 线程 | 创建位置 | 主要工作 |
|---|---|---|
游戏主线程(命令缓冲名 "GameThread") | PartialInit 中 InitClientThread("GameThread", ...) | 通过 audController/audEntity 创建 sound、写 requested settings、CommitGameSettings |
RAGE 音频引擎线程 "[RAGE Audio] - Engine Thread"(命令缓冲名 "AudioThread") | audEngine::InitAudioThread:优先级 PRIO_ABOVE_NORMAL,CPU 1(Durango 为 5),栈 18432(Orbis 36864) | AudioFrame():sound 层次更新、虚拟化、wave slot、调用游戏注册的回调 |
混音线程 "[RAGE Audio] Mix thread"(各平台不同) | 各 audMixerDevice*::StartMixing/InitHardware | MixBuffers():执行命令包、生成 PCM、混音、输出 |
| 游戏侧附加客户端线程 | 例如 GTA 的 "NorthAudioUpdateThread" | 见 game-audio.md |
证据:线程参数 src/dev_ng/rage/base/src/audioengine/engine.cpp:90-105、engine.cpp:687-706;音频线程入口先 m_SoundManager.InitCommandBuffers()(建立 "AudioThread" 命令缓冲,默认 64 KB)再循环调用 AudioFrame(),每帧持有 sm_EngineUpdateLock engine.cpp:485-509、src/dev_ng/rage/base/src/audioengine/soundmanager.cpp:238-243。若音频线程 10 秒(1000×10 ms)内没有进入运行状态,InitAudioThread 以 ERR_AUD_INIT_1 退出。
3.2 audEngine::AudioFrame() 一帧#
- 若
m_TriggeredUpdates(PS3 下-triggeredaudio且运行 Tuner 时启用)则先等m_UpdateSemaengine.cpp:173-182、engine.cpp:515-519。 - 进入混音器的 replay 切换锁;若混音器处于回放捕获且逐帧渲染,则手动
TriggerUpdate()+WaitOnMixBuffers()驱动混音;随后WaitOnThreadCommandBufferProcessing()——阻塞直到混音线程消费完本线程即将写入的那块命令缓冲engine.cpp:521-543。 - 用混音器帧数换算引擎时间(见 3.5),计算
m_TimeDeltaInSeconds;__STATS且-audiodesigner下帧间隔 ≥25 ms 告警engine.cpp:547-577。 - 处理
StartVirtualisingEverything():把物理 voice 数设为 0(保存旧值),停止时恢复engine.cpp:579-589。 m_SoundManager.Update(m_TimeInMs)→audDriver::Update(m_TimeInMs)→(*m_pfUpdateAudioThread)()(游戏注册的回调,GTA 为audNorthAudioEngine::UpdateAudioThread)→m_Ambisonics.Update()→__BANK下 RAVE 远程控制/调试更新engine.cpp:591-617。FlagThreadCommandBufferReadyToProcess(m_MixerTimeFrames):把本帧写满的命令缓冲交给混音线程,并打上时间戳engine.cpp:619。- 帧率限制:目标帧时长
g_TargetAudioFrameTime = 15 ms(暂停时 10 ms)。Windows PC 非工具构建下,非暂停时还会等待GRCDEVICE帧计数变化(最多到剩余预算),即音频线程向渲染帧对齐;其他平台简单sysIpcSleep(15 - 已用)engine.cpp:631-669。
3.3 混音器命令缓冲协议#
所有对混音器所属对象(voice、submix、PCM source)的修改都不直接调用,而是编码成 audCommandPacketHeader(4 字节头)写入当前线程的命令缓冲。audMixerDevice::audCommandSystemIds 定义三个目标子系统 COMMAND_VOICE / COMMAND_SUBMIX / COMMAND_PCMSOURCE src/dev_ng/rage/base/src/audiohardware/device.h:302-307;objectId 位宽必须能容纳 kMaxPcmSources,头大小编译期断言为 4 src/dev_ng/rage/base/src/audiohardware/device.cpp:636-641。
- 每个客户端线程有一个
audMixerClientThreadState,内含kAudioNumberOfCommandBuffers块缓冲(PC 为 3,其余平台 2),WriteIndex初始为 1src/dev_ng/rage/base/src/audiohardware/device.h:39-72。线程通过 TLS 风格的g_MixThreadClientId找到自己的状态;同一线程重复注册会断言device.cpp:853-883。 - 提交:
FlagThreadCommandBufferReadyToProcess(timestamp)设置时间戳与单调递增的提交序号,sys_lwsync()后置 ready,并把WriteIndex轮转到下一块device.cpp:894-911。 - 背压:
WaitOnThreadCommandBufferProcessing()等待下一块缓冲的ProcessedSema;-noaudiohardware时不等device.cpp:913-921。 - 消费:混音线程在
MixBuffers()开头持 Big Mixer Lock 调ExecuteCommandBuffers()。对每个客户端先按提交序号找到最旧的缓冲,按序处理;只有当m_MixTimerFrames >= timestamp + CommandDelayFrames(3)时才执行,即命令至少延迟 3 个混音帧(约 16 ms)生效,以吸收线程抖动;跳号会断言 “command buffer has been skipped”;处理完Reset()并 signalProcessedSemadevice.cpp:945-1031。分派按systemId进入m_Submixes[]/m_Voices[]/PCM source 池;对未初始化 PCM source 的命令会断言device.cpp:1033-1060。
这表明命令缓冲同时承担了“跨线程原子批量提交”和“时间对齐”两种职责:同一游戏帧写入的所有命令在同一混音帧整体生效。
3.4 requested settings:游戏线程 → 音频线程的多缓冲#
游戏代码对 sound 的 SetRequestedVolume/Pitch/Position/... 并不直接改 sound,而是写入其 audRequestedSettings。tRequestedSettings 包含 Volume、PostSubmixVolumeAttenuation、源效果干湿、Pitch、Doppler、VolumeCurveScale、LPF/HPF、SpeakerMask、EnvironmentalLoudness、ListenerMask、Pan,以及 IsSetToPlay/IsSetToStop 位 src/dev_ng/rage/base/src/audioengine/requestedsettings.h:71-99。每个对象持有 m_Settings[g_audRequestedSettingsBuffers] 和 m_SettingsPosition[...] requestedsettings.h:493-495。
- 缓冲数:PC/Orbis/Durango 为 4,其余为 3
src/dev_ng/rage/base/src/audioengine/enginedefs.h:44-49。头文件注释仍写“cached three times”,与 NG 平台的 4 不一致;以代码为准。brief 中所说的“双缓冲”实际是 3/4 槽环形缓冲。 - 写:Set* 写
m_Settings[GetWriteIndex()]src/dev_ng/rage/base/src/audioengine/requestedsettings.cpp:260-283。 - 每帧
audController::Update→audSound::Update(timeInMs, indices)(先更新环境度量并回调实体UpdateSound,最后提交)→audRequestedSettings::Commitsrc/dev_ng/rage/base/src/audiosoundtypes/sound.cpp:1405-1451:若nextWriteIndex == readIndex(音频线程落后),游戏线程最多自旋等待 1 秒;仍不行就打印 “Audio Thread is running too slowly, or is blocked” 并永久设置sm_IgnoreBlockedAudioThread(此后变化不再保证原子);否则用位置差计算速度(以 1/30 s 为单位缩放),并把当前槽复制到下一槽作为下一帧的起点requestedsettings.cpp:140-215。 - 帧末
audEngine::CommitGameSettings→audEnvironment::CommitListenerSettings(听者矩阵同样多缓冲)+audSoundManager::CommitGameSettings:原子递增m_GameUpdateIndex并audRequestedSettings::IncrementWriteIndex()engine.cpp:708-718、soundmanager.cpp:294-298、requestedsettings.h:435-445。 - 读:音频线程在
audSoundManager::Update开头读取m_GameUpdateIndex,把听者索引与 requested settings 读索引都设为(writeIndex - 1) mod Nsoundmanager.cpp:539-544、requestedsettings.h:457-472、src/dev_ng/rage/base/src/audioengine/environment.h:558-565。
因此一个游戏帧内对所有 sound 的修改作为一个整体在下一音频帧可见,且读写槽从不重叠(UpdateListenerAudioThreadIndex 断言两索引不同)。
3.5 时间基准#
引擎时间不取系统时钟,而由混音器已产出的帧数推导:kMixerNativeSampleRate = 48000、kMixBufNumSamples = 256(每混音帧 5.333 ms)src/dev_ng/rage/base/src/audiohardware/mixer.h:14-18。AudioFrame 用定点乘法把帧数换算为毫秒(注释以 128 样本为例,代码对 256 用不同移位)engine.cpp:551-567。audSoundManager 再维护 g_NumAudioTimers = 7 个可独立暂停、可缩放(SetTimeScale)的计时器;每帧按 timeElapsed * timeScale 推进未暂停的计时器 enginedefs.h:52、soundmanager.cpp:546-575。PC 上若时间倒退会把 m_LastUpdateTime 夹住,源码注释解释为 PC 高精度计时器漂移问题 soundmanager.cpp:547-560。
4. 声音对象模型:audController / audEntity / audSound#
4.1 audController#
头文件说明:每个能创建 sound 的线程有且仅有一个 audController,audEntity 向本线程的 controller 注册并通过它请求 sound;controller 管理 sound 的游戏侧生命周期 src/dev_ng/rage/base/src/audioengine/controller.h:67-74。容量:MAX_AUDIO_ENTITIES = 2048、MAX_CONTROLLER_SOUNDS = 2048、MAX_ENVIRONMENT_GROUPS = 512 controller.h:19-21。实体与 sound 引用都放在定长数组中,以 u16 索引串成链表,用空闲栈实现 O(1) 注册/注销 controller.h:355-392。
| 方法 | 行为 | 证据 |
|---|---|---|
PreUpdate(t) | 按全局 FlagControllersActive 切换激活状态;对每个已注册实体调用 PreUpdateService(t);引擎暂停(timer 0)时只更新 IsUnpausable() 的实体 | controller.cpp:428-497 |
Update(t) | 回收无实体且无引用的 environment group;调用 m_EnvironmentGroupManager->UpdateEnvironmentGroups;对每个实体的每个 sound 调 sound->Update(t, reqSetIndices)(即 requested settings 提交) | controller.cpp:499-600 |
PostUpdate() | 对实体调用 PostUpdate() | controller.cpp:602-650 |
UpdateEntityRef | 孤儿处理:WAITING_TO_BE_DELETED 的 sound 解除实体引用(必要时把实体持有的 audSound* 置空)并 SetIsReferencedByGame(false);无实体、无 tracker 且不允许孤儿的顶层 sound 被 Stop() | controller.cpp:652-804 |
GetSoundInstance | 音频禁用或 controller 非激活返回 NULL;sound 引用槽满时告警并打印列表;未指定 bucket 时选最空的 bucket;把 init 参数拆成 internal/scratch/parent 三组交给 SOUNDFACTORY.GetInstance | controller.cpp:1139-1254 |
__ASSERT 下可以挂 audControllerThreadVerifier,在每次创建 sound 时校验调用线程 controller.cpp:1143-1148(GTA 用它禁止主线程在 North 更新期间创建 sound)。
4.2 audEntity#
audEntity : datBase 是所有声音拥有者的基类,虚函数 PreUpdateService、PostUpdate、UpdateSound(sound, reqSets, t)(为设置了 UpdateEntity 的 sound 提供每帧回调);创建 API 有三种语义 src/dev_ng/rage/base/src/audioengine/entity.h:43-117:
| API | 语义 |
|---|---|
CreateSound_LocalReference | 返回临时指针,不由实体长期持有 |
CreateSound_PersistentReference / CreateAndPlaySound_Persistent | 实体持有 audSound**,sound 被回收时由 controller 置空 |
CreateAndPlaySound | fire-and-forget |
实体还拥有共享的“实体变量块”:最多 g_MaxAudEntityVariableBlocks = 256 个块 enginedefs.h:27、entity.h:338-343;以及批量 sound 请求、已删除实体/tracker 列表等静态机制(ProcessBatchedSoundRequests、ReportTrackerDeletion 等,entity.h:278-334)。
4.3 sound 生命周期状态机#
状态枚举 AUD_SOUND_DORMANT / PREPARING / PLAYING / WAITING_TO_BE_DELETED src/dev_ng/rage/base/src/audiosoundtypes/sound.h:217-226。转换逻辑全部在音频线程的 audSoundManager::ProcessHierarchy:先读取 requested settings 中的 IsSetToPlay/IsSetToStop,再按状态分支;PREPARING 超时(以系统时间而非暂停时间计)或失败时销毁;PLAYING 时若被 kill 则 _ManagedAudioKill,否则 _ManagedAudioUpdate;WTBD 且游戏不再引用时 delete;计时器暂停期间只允许继续准备 src/dev_ng/rage/base/src/audioengine/soundmanager_update.cpp:21-191。游戏侧对应 API 为 Prepare(waveSlot, allowLoad, priority)、Play()、PrepareAndPlay(...)、Stop()、StopAndForget() sound.h:643-691。每个 sound 最多 kMaxChildren = 8 个子节点 sound.h:285。
4.4 sound 池与 bucket#
audSoundPool 保证每棵 sound 层次完全位于同一个 bucket中 src/dev_ng/rage/base/src/audioengine/soundpool.h:149-152。bucket 结构内含:虚拟 voice 分配位图与 audVoiceSettings 数组(每桶 g_audMaxVoicesPerBucket = 96)、子 sound 请求队列(每桶每帧 g_audMaxChildSoundRequestsPerBucket = 64)、sound 槽与 requested settings 槽的空闲链表等 soundpool.h:80-136、enginedefs.h:33-42。槽索引是 u8(0xff 表示空),所以每桶 sound 槽上限 255、requested settings 槽上限 96。
audSoundManager::InitClass 从 audioconfig 读取 engineSettings_NumBuckets(缺省 24)、NumSoundsPerBucket(缺省 128)、NumRequestedSettingsPerBucket(缺省 64),槽大小取最大 sound 类型尺寸,并保留 2 个 reserved bucket soundmanager.cpp:183-229。这一设计的动机是 PS3:bucket 是 SPU 作业的 DMA 单位(PS3 beta 构建会缩小每桶容量以放进 SPU 本地存储)。在非 PPU 平台上,Update 只是在音频线程上按桶加锁、用 ParentSoundIterator 遍历顶层 sound 调 ProcessHierarchy,然后处理该桶的子 sound 请求 soundmanager.cpp:886-915;PS3 分支把非保留桶交给 SoundUpdateJob/环境更新作业,保留桶在 PPU 上处理 soundmanager.cpp:592-730。
4.5 sound 类型族(metadata 驱动)#
sounddefs.h 是由 metadata 编译工具自动生成的(“Automatically generated metadata structure definitions - do not edit”),SOUNDDEFS_SCHEMA_VERSION 54,AUD_NUM_SOUNDDEFS 35(不含抽象对象)src/dev_ng/rage/base/src/audiosoundtypes/sounddefs.h:1-38。基类 Sound 的公共字段包括 ClassId(__BANK 下与 24 位 NameTableOffset 打包)、Flags(2 位三态打包)、Volume/Pitch/Pan 及其 Variance、preDelay、StartOffset、Attack/Release、DopplerFactor、Category、LPF/HPF、VolumeCurve、EffectRoute、三个混响发送量等 sounddefs.h:302-352。
| TYPE_ID | 类型 | 类别 |
|---|---|---|
| 1/3/9/17 | LoopingSound、TwinLoopSound、RetriggeredOverlappedSound、SequentialOverlapSound | 循环/重触发容器 |
| 2/29/30 | EnvelopeSound、FluctuatorSound、AutomationSound | 参数调制 |
| 4 | SpeechSound | 语音(见 music-radio-speech.md) |
| 5/6/7/13/14/22/26 | OnStopSound、WrapperSound、SequentialSound、MultitrackSound、RandomizedSound、SwitchSound、IfSound | 逻辑/组合容器 |
| 8/31 | StreamingSound、ExternalStreamSound | 流式与外部 PCM 流 |
| 10/11/20/21 | CrossfadeSound、CollapsingStereoSound、DirectionalSound、KineticSound | 空间/混合 |
| 12/18/19 | SimpleSound(单个 wave)、ModularSynthSound、GranularSound | 产生 PCM 的叶子来源 |
| 15 | EnvironmentSound | 控制一个虚拟 voice 的真正叶子 |
| 16 | DynamicEntitySound | 从实体查询子 sound |
| 23/24/25/27/28 | VariableCurveSound、VariablePrintValueSound、VariableBlockSound、MathOperationSound、ParameterTransformSound | 变量运算 |
| 32-35 | SoundSet、NoteMap、SoundSetList、SoundHashList | 非 sound 的辅助对象 |
编号证据见各 TYPE_ID 定义,例如 sounddefs.h:458、sounddefs.h:965、sounddefs.h:1083、sounddefs.h:1199、sounddefs.h:1926、sounddefs.h:2077。EnvironmentSound 的头文件说明它“通过虚拟 voice 控制物理 wave 的播放,计算音量、音高、位置等” src/dev_ng/rage/base/src/audiosoundtypes/environmentsound.h:24-27;例如 SimpleSound 在 Init 时通过 SOUNDFACTORY.GetEnvironmentSound 给自己挂一个环境 sound 子节点 src/dev_ng/rage/base/src/audiosoundtypes/simplesound.cpp:60。父节点向子节点传递的参数通过 audSoundCombineBuffer 逐层合并。
4.6 实例化与“无虚函数”分派#
audSoundFactory::GetInstanceFromMetadata 读取 metadata 首字节作为 ClassId,必要时锁桶,用 gSoundClassFactory(bucketId, classID) 在桶内 placement 构造,父子关系须在 Init() 之前设置;Init 失败且效果路由有效时删除 src/dev_ng/rage/base/src/audioengine/soundfactory.cpp:78-130。AUD_SOUND_USE_VIRTUALS = 0、AUD_CPP_MEMBER_INVOKES = 0 sound.h:55、sound.h:65:Update/Play/Prepare/Kill/ActionReleaseRequest/ComputeDuration/Pause/Destruct 通过以 ClassId 索引的静态函数指针表分派(SOUND_FUNC_TABLE),SPU 上还要对表做 PIC 重定位 soundfactory.cpp:642-740。audSoundBase 的注释要求子类不得引入新虚函数,以保证 vptr 位置跨平台一致 sound.h:250-263。注意 PPU_ONLY_SOUND_FN 包裹了 audStreamingSound 与 audExternalStreamSound,即这两类在 SPU 作业中不可用 soundfactory.cpp:652-675。
5. 音频 metadata(.dat)格式与加载#
所有音频数据对象(sounds、curves、categories、dynamic mixer、audio config、synth、speech、以及游戏侧 GameObjects)都用 audMetadataManager 加载,不经过 parser/PSO。
5.1 文件布局#
LoadMetadataChunk_Internal 的读取顺序(证据 src/dev_ng/rage/base/src/audioengine/metadatamanager.cpp:161-330):
- 文件名:
path + schemaVersion + ext,ext在__BANK下为空、否则为.rel;找不到带版本号的文件时回退到不带版本号metadatamanager.cpp:212-227。 u32 schemaVersion:与代码*_SCHEMA_VERSION比较,小于/大于分别断言“Data is out of date”/“Code is out of date”metadatamanager.cpp:236-247。LoadMetadata:u32 size+ 对象数据块(可装入调用方预分配缓冲)metadatamanager.cpp:494-506。LoadStringTable:u32 size、u32 numStrings+ 字符串数据(bank 名等)metadatamanager.cpp:1017-1038。- 对象映射:
BuildObjectMap(内嵌名字:u32 numObjects、u32 nameTableSize,然后逐对象u8 len + name + offset)或BuildObjectMap_External(只有哈希→偏移,名字在<file>.nametable中,仅在名字可用时加载)metadatamanager.cpp:527-600、metadatamanager.cpp:851-871。
内存中的 audMetadataChunk 保存数据指针、{offset,key} 映射数组、可选 256 个 MapBin 的加速表(optimisedMap)、字符串表、名字表及 __BANK 下的对象覆盖记录 src/dev_ng/rage/base/src/audioengine/metadatamanager.h:61-130。一个 manager 可挂多个 chunk(BASE、DLC 包等),支持 ReserveChunk 预留顺序、UnloadMetadataChunk、SetChunkEnabled metadatamanager.h:224-247。__BANK 下若 RAVE 连接且该 chunk 被指定用 RAVE 数据,则通过 rc.RequestMetadataChunk 拉取并以内存文件形式加载 metadatamanager.cpp:175-186。
5.2 metadata 类型一览#
类型名(Init 的 metadataType) | 文件来源 | 加载者 |
|---|---|---|
AudioConfig | audioconfig.dat(名字表始终加载,因为 wave slot 初始化需要) | audConfig::InitClass src/dev_ng/rage/base/src/audiohardware/config.cpp:14-19 |
Sounds | metadataPaths_Sounds | audSoundFactory::Init soundfactory.cpp:482-532 |
Curves / Categories / DynamicMixer | metadataPaths_* | curverepository.cpp:29、categorymanager.cpp:56、dynamicmixmgr.cpp:162 |
ModularSynth | optamp.dat(运行时,chunk OPTIMISED);合成器编辑器构建另加载 modsynth.dat | synthSynthesizer::InitClass(第 9 节) |
| Speech | metadataPaths_Speech/Speech2 | audSpeechSound(见 music-radio-speech.md) |
GameObjects | audio:/config/game.dat | 游戏层 audNorthAudioEngine(见 game-audio.md) |
各路径键由 audEngineConfig::Init 从 audioconfig 读取(metadataPaths_Sounds/Curves/Categories/Effects/Speech/Speech2,以及听者朝向、声速、bucket 配置)src/dev_ng/rage/base/src/audioengine/engineconfig.cpp:12-32。全局 sound 变量由 audioconfig 中的 GlobalVariableList 初始化(上限 g_MaxSoundManagerVariables = 64),并预先查找 Game.Audio.RMSLevels.* 等 RMS 变量 soundmanager.cpp:257-276、enginedefs.h:25。
metadata 编译工具(RAVE / 资产流水线)本身不在本页范围,见 ../24-toolchain/asset-pipeline.md。
6. Wave 数据:audWaveSlot、AWC 容器与流式#
wave slot 是预分配的音频内存槽,由 audioconfig 中的 WaveSlotsList 定义;每个 Slot 有 LoadType(SLOT_LOAD_TYPE_WAVE 单 wave / BANK 整个 bank / STREAM 流式)、Size、MaxHeaderSize、可选 StaticBank。静态 bank 的头/数据大小与配置不符时报错并就地放大;Size == 0 的槽仅告警(注释说 AMBIENT_SCRIPT_SLOT 11~16 属正常)src/dev_ng/rage/base/src/audiohardware/waveslot.cpp:510-622。单批次最多 g_MaxWaveLoadsPerBatch = 8 个 wave 加载 src/dev_ng/rage/base/src/audiohardware/waveslot.h:43-45。
- bank 文件:
ComputeBankFilePath取 bank 名前 8 个字符作为 pack 前缀,查sm_SearchPaths(DLC 挂载注册)否则用驱动配置的 wave 根目录,拼成<root>/<bankName>.awc;平台目录为x64/xboxone/ps4;DLC_XMAS*被重定向到dlc_mpxmas_604490:,resident的LOW*低延迟 bank 走update:waveslot.cpp:1747-1784。读取使用pgStreamer::Open/Read(可加锁以保证连续读)waveslot.cpp:1859-1901。 - AWC = ADAT 容器:头
{magic 'ADAT', version 0xFF000001 + 标志位, numObjects, totalHeaderSize};对象表项u32中 29 位为对象名哈希(注释称 GTA IV 在 64600 个 wave 名中只有 4 次碰撞)、3 位为数据块数(≤8);数据表项u64打包 28 位偏移、28 位大小、8 位类型哈希。标志位包括快速查找表、连续/流式打包以及“加密数据”标志(本页不展开)src/dev_ng/rage/base/src/audiodata/container.h:19-79。 - 流式:
audStreamingWaveSlot每槽kNumStreamingBlocksPerWaveSlot = 2个块,通过pgStreamer异步加载块,必要时才重新加载 bank 头src/dev_ng/rage/base/src/audiohardware/streamingwaveslot.h:43-95。PS3 SPU 路径下,sound 作业产生的 wave slot 请求在 PPU 上回放到_SyncStreamingWaveSlotRequest/_SyncWaveSlotRequestsoundmanager.cpp:849-874。 - 解码器:
audDecodeManager::IsFormatSupported中 PCM 与 ADPCM 全平台支持,XMA2 仅 Xenon/Durango,MP3 仅 PS3/Orbis,ATRAC9 显式禁用;AllocateDecoder在RSG_AUDIO_X64(PC/Orbis/Durango)下分配audDecoderAdpcm,Orbis 为audDecoderPS4Mp3,Durango 为audDecoderXboxOneXma,PS3 一律走audDecoderSpusrc/dev_ng/rage/base/src/audiohardware/decodemgr.cpp:95-200、src/dev_ng/rage/base/src/audiohardware/driverdefs.h:13。每混音帧解码上限kMaxDecodesPerFrame:Orbis 10、其他 20mixer.h:20-24。
7. Voice 管理与虚拟化#
PCM source 类型:WAVEPLAYER / GRAINPLAYER / MODULARSYNTH / SUBMIX / SYNTHCORE / EXTERNALSTREAM src/dev_ng/rage/base/src/audiohardware/pcmsource_interface.h:14-19;池上限 kMaxPcmSources = 768,物理 voice 上限 kMaxVoices = 128 mixer.h:26、mixer.h:56。虚拟 voice 存在于 bucket 的 voiceSettings 中(4.4 节),由 audEnvironmentSound 每帧写入路由、音量矩阵和虚拟化得分。
得分:GTA 定制的 audEnvironment::ComputeVoiceRoutes 计算 score = ~0U - totalVolume * currentPeakLevel * 65536,其中 totalVolume = min(1, (wet+dry) * postSubmixAtten),currentPeakLevel 来自 wave 的峰值数据——得分越小越重要 src/dev_ng/rage/base/src/audioengine/environment_game.cpp:588-597;控制 submix 的环境 sound 得分恒为 0 src/dev_ng/rage/base/src/audiosoundtypes/environmentsound.cpp:921。
分配(audVoiceMgr::Update 非 SPU 分支):统计空闲物理 voice → BuildPrioritisedVoiceList → AllocatePhysicalVoices(budget, ...) → 执行停止/启动列表 → UpdatePhysicalVoices src/dev_ng/rage/base/src/audiohardware/voicemgr.cpp:604-661。
- 构表时遍历所有 bucket 的已分配 voice:已结束的 PCM 源得分
~0;ShouldPlayPhysically或(已物理且不可取消)得分 0;虚拟中且已请求停止得分~0;虚拟→物理需加启动成本(PS3 30、其他 5);已物理播放的 voice 得分减0x72081(注释:约等于 −60 dB 处 1 dB,用于防止两个等响声源来回抖动);分组 voice 以组内最高优先级参与排序,非分组 voice 用基数排序src/dev_ng/rage/base/src/audiohardware/virtualisation.cpp:62-194、src/dev_ng/rage/base/src/audiohardware/virtualisation.h:26-39。 - 分配时双指针合并遍历 voice 表与 group 表:每帧可用物理 voice 数
physicalVoicesThisFrame和成本预算budgetRemaining同时约束;一个组只有在“剩余 voice ≥ 组内 voice 数且预算 ≥ 组总成本”时整组变为物理virtualisation.cpp:417-477。 - 预算:常规
kVoiceMixingBudget = 4000,物理 voice 数min(128, NumPhysicalVoices);PC 上SetCPULimitedAudio(true)(低配机)改为预算 2000、driverSettings_NumPhysicalVoices_Reduced(缺省 96)。每帧可用数还要减去NumNewPhysicalVoiceBufferVoices预留量;MaxVirtualVoices必须是 32 的倍数voicemgr.cpp:132-242。
8. 混音器:submix 图与 DSP 效果#
8.1 audMixerDevice::MixBuffers()#
混音器是“基于 buffer 的多通道 32 位浮点软件混音器,支持任意输出通道与 submix 路由” src/dev_ng/rage/base/src/audiohardware/device.h:81-87。一个混音帧(256 样本)在 Big Mixer Lock 内执行(device.cpp:169-388):
ExecuteCommandBuffers();若暂停(或fiIsShowingMessageBox)则输出清零并返回。- 路由变化后
ComputeProcessingGraph():从各 end point 递归给 submix 分配处理阶段device.cpp:1458-1562。 - 所有 PLAYING voice
Premix()→GeneratePcm():遍历 PCM source 池,引用计数为 0 的释放;有物理 buffer 的GenerateFrame(),无物理 buffer 的SkipFrame()(虚拟播放推进时间);等待同步信号的源(audMixerSyncManager)延后到第二轮;结果写回PcmSourceState(播放位置、峰值等,供音频线程读取)device.cpp:390-555。 - voice
Mix()(滤波/包络后混入 submix)→ 按处理顺序submix.Mix(),阶段切换时释放上一阶段 submix 缓存 → 全部PostMix()。 - 非 Xenon/Durango/Orbis 平台在此做硬削波(这三个平台在输出交错时处理);非 PS3/Orbis 平台若输出模式为立体声则做 6→2 下混
device.cpp:266-309。 - voice
PostMix()、m_MixTimerFrames++、录像捕获UpdateAudioCapture()、同步管理器与解码管理器PostMix。
初始化(audMixerDevice::Init):帧缓冲池 driverSettings_NumFrameBuffers(缺省 340);先建 6(PS3 为 8)路输出 mix buffer;m_PcmSourcePool 容量 768;创建 "Master" submix 作为 end point,其 buffer 即输出 buffer device.cpp:590-729。容量:submix ≤ kMaxSubmixes = 40 mixer.h:48,每个 submix ≤32 输入、≤8 输出、≤8 效果、≤8 通道 src/dev_ng/rage/base/src/audiohardware/submix.h:38-41,end point ≤4 device.h:419-420。
目录中大量 *job.cpp / *_job.cpp(SubmixJob、VoiceMixJob、WavePlayerJob、GrainPlayerJob、VirtualisationJob、SoundUpdateJob 等)是 PS3 SPU 作业入口:例如 SubmixJob_job.cpp 只是定义作业名并包含 .job,submixjob.cpp 在 __SPU 下关闭断言 src/dev_ng/rage/base/src/audiohardware/SubmixJob_job.cpp:1-2、src/dev_ng/rage/base/src/audiohardware/submixjob.cpp:8-25。在 PC/PS4/XB1 上,MixBuffers 中的 voice/submix 处理都在混音线程上顺序完成(上述循环中没有作业派发)。
8.2 GTA 的 submix 路由图#
audioengine/environment_game.{h,cpp} 是 GTA 定制的 audEnvironment 部分(AUD_GAME_OVERRIDDEN_ENVIRONMENT/ROUTING/ROUTE_EFFECTS/VOLUME_IMPLEMENTATION = 1)src/dev_ng/rage/base/src/audioengine/environment_game.h:10-15。InitRoutes() 建图如下:
证据:environment_game.cpp:80-311;SEPERATE_MUSIC_ROUTE 在 PC 为 0、其他为 1 environment_game.cpp:39-43;车辆引擎/排气各 8 路、对话 2 路的效果路由枚举 environment_game.h:95-119;反射输出 2 路 environment_game.h:15。车辆 submix 启动时全部静音,由游戏按需分配(AssignVehicleEngineSubmix 等,environment_game.cpp:677-729)。Durango/Orbis 上 SFX 被设为主输出 submix,Music 被设为 “restricted output”——即系统录像/分享时可以单独屏蔽受版权限制的音乐 environment_game.cpp:264-267。metadata 中的 EffectRoutes(AS_PARENT、MUSIC、FRONT_END、POSITIONED、三档全湿混响、SFX、MASTER、PADSPEAKER_1、POSITIONED_MUSIC、PADHAPTICS_1)决定 sound 进哪条路由 sounddefs.h:98-114。
8.3 DSP 效果#
效果接口 audDspEffect(Init(numChannels, channelMask)、Process(audDspEffectBuffer&)、SetParam),类型 ID 有 BIQUAD_FILTER、COMPRESSOR、DELAY、DELAY4、VARIABLEDELAY、R360LIMITER、REVERB、REVERB4、WAVESHAPER、SYNTHCORE、REVERB_PROG、EARLY_REFLECTIONS、UNDERWATER src/dev_ng/rage/base/src/audiohardware/dspeffect.h:23-109。实现位于 audioeffecttypes/;其中带 _pc / _psn / _xenon 后缀的文件是各代平台的变体实现(如 compressoreffect_xenon.cpp、delayeffect_psn.cpp)。GTA 在混响 submix 上挂载:Small = audEarlyReflectionEffect(预设 default_small_er)+ audUnderwaterEffect;Medium = audDelayEffect4 + audReverbEffect4(RoomSize 0.666、Damping 0.5);Large = 默认旁路的 HPF/LPF biquad + audDelayEffect4 + audReverbEffect4(RoomSize 0.66、Damping 0.05)environment_game.cpp:1170-1210。synthCoreDspEffect 允许把编译后的合成程序当作 submix 效果使用 src/dev_ng/rage/base/src/audiosynth/synthcore.h:285。
9. 合成器 audiosynth(AMP)#
audiosynth 由两部分组成:
- 模块化合成器(编辑器侧):
synthSynthesizer+ 几十种模块(振荡器、滤波器、包络、延迟、FFT、Freeverb、MVerb、sequencer、MIDI 输入等)以及大量*view.cpp/ui*.cpp编辑 UI。__SYNTH_EDITOR构建下以modsynth.dat(chunkBASE)为基础并额外加载optamp.dat,还可通过 RAVE 加载额外 chunksrc/dev_ng/rage/base/src/audiosynth/synthesizer.cpp:61-120。 synthCore字节码运行时(游戏侧):非编辑器构建只加载audio:/config/optamp.dat(Xenon/PS3 走update:路径),对象类型为CompiledSynthsynthesizer.cpp:68-90。FindOptimizedMetadata从最后加载的 chunk 往前查找,因此 DLC chunk 可覆盖同名合成器synthesizer.cpp:186-198。
CompiledSynth 格式(生成的 synthobjects.h):NumBuffers、NumRegisters、≤4 个输出 buffer、programSizeBytes、numStateBlocks、costEstimate、u16 Operations[](程序入口)、≤256 个常量、≤256 个输入变量(名字哈希 + 初值)等 src/dev_ng/rage/base/src/audiosynth/synthobjects.h:268-318。指令编码为 u16:高 8 位操作码、4 位输入数、4 位输出数;操作数引用为 类型<<8 | 索引,类型为寄存器 R、常量 C、变量 V 或 buffer B src/dev_ng/rage/base/src/audiosynth/synthopcodes.h:15-23。运行时限制 kMaxRegisters = 48、kMaxBuffers = 16、kMaxStateBlocks = 64、kMaxVariables = 20 synthcore.h:95-98;synthCore::Init 超限则拒绝初始化 src/dev_ng/rage/base/src/audiosynth/synthcore.cpp:165-200。costEstimate 被用作虚拟化成本 synthcore.cpp:142-151。
播放链路:audModularSynthSound::AudioPrepare 分配一个 AUD_PCMSOURCE_SYNTHCORE PCM source,并通过命令设置 CompiledSynthNameHash 与可选 PresetNameHash;编辑器构建改用全局 g_SynthEditorSlotId src/dev_ng/rage/base/src/audiosoundtypes/modularsynthsound.cpp:180-200。合成器在 GTA 中的主要用户之一是车辆引擎(颗粒引擎与合成效果),见 game-audio.md。
颗粒合成属于 audiohardware:audGrainPlayer(顶层协调者,是一种 audPcmSource)、audGranularMix(从单个 wave 播放 grain)、audGranularSubmix(生成一组 grain,例如同步循环)src/dev_ng/rage/base/src/audiohardware/grainplayer.h:102-106、src/dev_ng/rage/base/src/audiohardware/granularmix.h:18-21、src/dev_ng/rage/base/src/audiohardware/granularsubmix.h:16-20。
10. 类别、曲线、变量与 mix group#
- Category:由 RAVE 定义,用于控制一组 sound 的音量等属性;每个
EnvironmentSound向上查找其类别,并把类别设置与自身合并,类别本身也沿层次向上合并src/dev_ng/rage/base/src/audioengine/category.h:90-93。AUD_ATOMIC_CATEGORIES = 0category.h:27,因此CommitCategorySettings等原子路径在本构建中关闭;类别解析在音频线程audSoundManager::Update开头执行(PS3 可放到 SPU)soundmanager.cpp:487-535。游戏通过audCategoryControllerManager管理类别控制器(GTA 在UpdateAudioThread回调中更新它)。 - Mix group / 动态层次:
NUM_AUD_MIXGROUPS = 16,须与NUM_DYNAMIC_HEIRACHIES一致src/dev_ng/rage/base/src/audioengine/dynamicmixmgr.h:22;dynamix metadata 定义MixerPatch(最多 32 个 mix category)与AudSceneState(最多 8 个状态)等src/dev_ng/rage/base/src/audioengine/dynamixdefs.h:116-201。场景(scene)层面的动态混音由游戏侧audDynamicMixer实现,见 game-audio.md。 - Curve:16 种曲线类型:
Constant、Linear、LinearDb、PiecewiseLinear、EqualPower、ValueTable、Exponential、DecayingExponential、DecayingSquaredExponential、SineCurve、OneOverX、OneOverXSquared、DefaultDistanceAttenuation(Clamped)、DistanceAttenuationValueTablesrc/dev_ng/rage/base/src/audioengine/curvedefs.h:43-483。 - 变量:全局变量 64 个(sound manager),实体变量块 256 个,
VariableBlockSound提供局部变量;游戏每帧写入Game.Clock.*、Game.Player.*等全局变量(见 game-audio.md)。
11. 平台输出驱动#
audDriver::InitMixer 的选择 src/dev_ng/rage/base/src/audiohardware/driver.cpp:275-309:
| 平台 | 设备类 | 混音驱动方式 | 证据 |
|---|---|---|---|
| PC 默认 | audMixerDeviceXAudio2(device_xaudio_pc) | 创建 XAudio2、IAudioClient/IMMNotificationClient 探测设备,mastering voice + 一个无 SRC 的 source voice;独立 PRIO_TIME_CRITICAL 混音线程每 ≥5333 µs 生成一帧并入输出队列,队列空出一半以上时同一轮多生成一帧;无音频设备时把混音器置为 suspended | device_xaudio_pc.cpp:143-312、device_xaudio_pc.cpp:609、device_xaudio_pc.cpp:740-840 |
PC -dsound | audMixerDevicePc(WAVE_FORMAT_EX) | 自有混音线程 PRIO_TIME_CRITICAL | device_pc.cpp:56、device_pc.cpp:278 |
PC -asio | audMixerDeviceAsio(依赖 audioasiolib,即 Steinberg ASIO SDK 封装) | ASIO 混音线程 | device_asio.cpp:130 |
| Orbis | audMixerDeviceOrbis | sceAudioOutOpen 打开主输出与“受限内容”输出两个 MAIN 端口,另按用户打开手柄扬声器端口(单声道 float);混音线程 PRIO_TIME_CRITICAL,每帧 MixBuffers 后用 sceAudioOutOutput 输出交错数据。注释说明该调用在缓冲被调度后即返回,所以代码自行双缓冲(bounce);混音循环本身没有 sleep,这表明节拍来自 sceAudioOutOutput 的端口排队(推断) | device_orbis.cpp:287-311、device_orbis.cpp:419、device_orbis.cpp:491-546、device_orbis.cpp:649-653 |
| Durango | audMixerDeviceXAudio2(device_xboxone,#else 分支) | 两个 audXAudio2Instance(主输出与 DVR 受限输出,AUD_DVR_OUTPUT),混音线程默认在核 4(-audioDeviceCore 可改),两个输出队列都未满时才混音 | device_xboxone.cpp:41、device_xboxone.cpp:150-169、device_xboxone.cpp:307-340 |
| PS3 / Xenon(遗留) | audMixerDevicePs3 / device_360 | 不在 NG 构建中 | driver.cpp:298-303 |
源码中没有 WASAPI 独立输出设备类:PC 只是在 XAudio2 设备内部使用 MMDevice/IAudioClient 查询设备信息。
录像/回放渲染:AUDIO_CAPTURE_ENABLED 在 PC/Durango/Orbis 开启 mixer.h:28。捕获时停止硬件输出,混音线程改为等待 sm_TriggerUpdateSema,由游戏逐帧 TriggerUpdate()/WaitOnMixBuffers() 泵动混音器;退出捕获后丢弃 kAudioBuffersToDropAfterCapturing = 200 个缓冲 device_xaudio_pc.cpp:751-796、device.h:45。与视频编辑器的配合见 ../19-persistence-replay/replay-and-video.md。
12. 其余目录#
| 目录 | 内容与现状 | 证据 |
|---|---|---|
rage/suite/src/audmusic | 不是交互式音乐系统,而是“用户自定义音乐”(主机 HUD 播放器)的抽象 audMusic(Update/SetVolume/播放控制/曲目枚举),只有 audMusicXenon 与空壳 audMusicPsn 实现;在 game/、rage/base、rage/suite 其余目录中都没有引用 | rage/suite/src/audmusic/music.h:21-84、music_xenon.h:19、music_psn.h:14 |
rage/suite/src/vieweraudio | vwrAudioManager:查看器/示例程序用的最小音频封装,自己调用 InitializeAudio、持有 GameObject metadata;被 sample_simpleworld 引用 | AudioMgr.h:24-65、AudioMgr.cpp:123 |
rage/base/src/audioscriptsoundtypes | audScriptSoundVM:32 寄存器的浮点小 VM(LOAD/STORE、算术、三角/波形、dB 换算、比较与跳转);除 unity 文件外没有任何 sound 类型或游戏代码引用,判定为未启用 | scriptsoundvm.h:17-34、opcodes.h:6-60 |
rage/base/src/audioasiolib | ASIO SDK(asio.h、asiodrivers、asiolist),供 PC -asio 输出 | driver.cpp:283-287 |
rage/base/src/audiodata | ADAT 容器读写与构建(containerbuilder)、simpletypes(audioconfig 的 ravesimpletypes) | 第 6 节 |
audioengine/ambisonics* | AUD_ENABLE_AMBISONICS 1(注释 HACK_GTA4),每音频帧 m_Ambisonics.Update() | ambisonics.h:10、engine.cpp:606-608 |
audioengine/remotecontrol*、remotedebug*、widgets* | __BANK:RAVE 连接、音频调试器、RAG 控件 | engine.cpp:266-280 |
13. 容量与调优常量速查#
| 常量 | 值 | 证据 |
|---|---|---|
| 采样率 / 混音帧 | 48000 Hz / 256 样本(5.333 ms) | mixer.h:14-18 |
| 物理 voice / PCM source / submix | 128 / 768 / 40 | mixer.h:26-56 |
| 最大音高比 | 4(最高 192 kHz 回放) | mixer.h:37-46 |
| 最大输出通道 | 6 | device.h:309 |
| 命令缓冲块数 | PC 3,其他 2;默认大小 GameThread 32 KB、AudioThread 64 KB | device.h:39-43、engine.cpp:362、soundmanager.cpp:241 |
| 命令延迟 | 3 混音帧 | device.cpp:995-996 |
| requested settings 缓冲 | NG 4,旧平台 3 | enginedefs.h:44-49 |
| 音频帧目标 | 15 ms(暂停 10 ms) | engine.cpp:103-104 |
| bucket | 24 × 128 sound 槽 × 64 reqset 槽,保留 2(audioconfig 可改);每桶虚拟 voice 96 | soundmanager.cpp:185-226、enginedefs.h:33-42 |
| controller | 实体 2048、sound 引用 2048、environment group 512 | controller.h:19-21 |
| 虚拟化预算 | 4000(PC 低配 2000 / 96 voice) | voicemgr.cpp:202-220 |
| 帧缓冲池 | 340(driverSettings_NumFrameBuffers) | device.cpp:610-614 |
| 托管效果 | g_MaxManagedEffects = 24 | enginedefs.h:31 |
14. 构建配置差异#
__BANK:RAVE 远程编辑(AUD_SUPPORT_RAVE_EDITING = __BANK,enginedefs.h:10)、metadata 名字表、ClassId:8 + NameTableOffset:24打包、混音器计量(AUD_SUPPORT_METERING,mixer.h:10)、按 PCM source 类型禁用(g_DisablePCMSource,driver.cpp:147-154)。- 非
__BANK:metadata 文件带.rel扩展名。 __DEV:-tracecommandpackets打印每个命令包;PREPARE 超时打印 sound 名。__SYNTH_EDITOR:MIDI 输入设备、合成器编辑 UI、modsynth.dat。- 命令行:
-noaudio/-audio、-cheapaudioeffects、-nodynamicmixer、-noaudiogap、-triggeredaudio(PS3)、-asio/-dsound、-audiooutputchannels、-noaudiohardware等engine.cpp:77-85。
15. 约束与边界行为#
- 所有能创建 sound 的线程都必须先
InitClientThread并拥有audController;在未注册线程上写命令会触发Verifyf("No command buffer set up for this thread")并丢弃device.cpp:876-943。 - 客户端线程每帧必须先
WaitOnThreadCommandBufferProcessing再写、写完FlagThreadCommandBufferReadyToProcess;否则下次 Flag 会断言“already ready for processing”。 - 音频线程严重滞后时,requested settings 在阻塞 1 秒后进入“非原子”降级模式且不会自动恢复(
sm_IgnoreBlockedAudioThread只置 true)。 - sound 层次不能跨 bucket;单桶每帧动态子 sound 请求 ≤64、虚拟 voice ≤96,超出时创建失败(具体失败分支在
audSoundPool,本页未逐行展开)。 - 任何
audSound子类不得新增虚函数(函数表分派 + SPU vptr 约束)。 - metadata 的 schema 版本必须与代码一致,否则断言;DLC chunk 顺序由预留决定(GTA 在初始化时为固定 DLC 顺序预留 chunk,见 game-audio.md)。
- 混音器暂停(
SetPaused或系统消息框)时输出静音但命令仍被执行。
16. 未决问题 / 证据不足#
audEnvironmentSound的完整音量/声像/遮挡合成(environment.cpp、environmentsound.cpp、environment_game.cpp::ComputeVoiceRoutes的上半部分)只看了得分部分,未逐步展开衰减与滤波公式。audSoundPool在桶满、子请求溢出时的具体失败路径与audSound::sm_Pool的“reserved bucket”调度策略未逐行核对。audioeffecttypes各效果的算法(Freeverb/Dattorro/ER 等)与_pc/_psn/_xenon变体在 NG 构建中是否被编译,未核对工程文件。audAmbisonics在 GTA V 中的实际用途(是否参与正式输出解码)未深入。audCategoryControllerManager、categorycontrollermgr的调度细节仅概述。