CD Code Digest Hub /GTA V · v2

RAGE 音频引擎:audioengine / audiohardware / soundtypes / synth

87 分钟阅读 18 个章节 3 张图

本页说明 RAGE 底层音频运行时:一个由 metadata 驱动的“声音对象层次”(audSound 树)如何在专用音频线程上被更新,如何通过无锁的多缓冲参数和命令缓冲把结果交给混音线程,再由软件混音器(submix 图 + DSP 效果)生成 48 kHz 浮点输出并交给各平台的输出驱动。GTA V 游戏层(audNorthAudioEngine 及各类 audEntity 派生)全部建立在这一层之上,见 game-audio.md;音乐、电台与语音见 music-radio-speech.md。

背景与相关页面#

相关源码文件#

文件作用
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_*.cppwave 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 的顺序如下:

步骤调用证据
1audEngineUtil::InitClass;__BANK 下初始化 audRemoteControl(连接 RAVE)和 audRemoteDebug,连不上只告警engine.cpp:263-280
2audConfig::InitClass("<relPath><config>/audioconfig.dat"),再 audDriver::GetConfig().Init()、m_Config.Init()engine.cpp:282-293
3非 __FINAL 且驱动配置禁用处理时强制 -noaudio 与 -cheapaudioeffectsengine.cpp:295-301
4m_CurveRepository.Init → m_CategoryManager.Init → (除非 -nodynamicmixer)m_DynamicMixMgr.Initengine.cpp:305-324
5audSoundManager::InitClass(分配 sound 池)→ m_SoundManager.Init(加载 Sounds metadata)engine.cpp:326-338
6synthSynthesizer::InitClass(日志称 “AMP runtime”)engine.cpp:340-345
7audDriver::InitClass(创建混音器、wave slot、voice 管理器);为调用线程建立名为 "GameThread" 的命令缓冲(默认 32 KB,PC 乘系数);m_Environment.Init()engine.cpp:347-367
8audEnvironmentSound::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/InitHardwareMixBuffers():执行命令包、生成 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() 一帧#

  1. 若 m_TriggeredUpdates(PS3 下 -triggeredaudio 且运行 Tuner 时启用)则先等 m_UpdateSema engine.cpp:173-182、engine.cpp:515-519。
  2. 进入混音器的 replay 切换锁;若混音器处于回放捕获且逐帧渲染,则手动 TriggerUpdate() + WaitOnMixBuffers() 驱动混音;随后 WaitOnThreadCommandBufferProcessing()——阻塞直到混音线程消费完本线程即将写入的那块命令缓冲 engine.cpp:521-543。
  3. 用混音器帧数换算引擎时间(见 3.5),计算 m_TimeDeltaInSeconds;__STATS 且 -audiodesigner 下帧间隔 ≥25 ms 告警 engine.cpp:547-577。
  4. 处理 StartVirtualisingEverything():把物理 voice 数设为 0(保存旧值),停止时恢复 engine.cpp:579-589。
  5. m_SoundManager.Update(m_TimeInMs) → audDriver::Update(m_TimeInMs) → (*m_pfUpdateAudioThread)()(游戏注册的回调,GTA 为 audNorthAudioEngine::UpdateAudioThread)→ m_Ambisonics.Update() → __BANK 下 RAVE 远程控制/调试更新 engine.cpp:591-617。
  6. FlagThreadCommandBufferReadyToProcess(m_MixerTimeFrames):把本帧写满的命令缓冲交给混音线程,并打上时间戳 engine.cpp:619。
  7. 帧率限制:目标帧时长 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 初始为 1 src/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() 并 signal ProcessedSema device.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::Commit src/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 N soundmanager.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.GetInstancecontroller.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 置空
CreateAndPlaySoundfire-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/17LoopingSound、TwinLoopSound、RetriggeredOverlappedSound、SequentialOverlapSound循环/重触发容器
2/29/30EnvelopeSound、FluctuatorSound、AutomationSound参数调制
4SpeechSound语音(见 music-radio-speech.md)
5/6/7/13/14/22/26OnStopSound、WrapperSound、SequentialSound、MultitrackSound、RandomizedSound、SwitchSound、IfSound逻辑/组合容器
8/31StreamingSound、ExternalStreamSound流式与外部 PCM 流
10/11/20/21CrossfadeSound、CollapsingStereoSound、DirectionalSound、KineticSound空间/混合
12/18/19SimpleSound(单个 wave)、ModularSynthSound、GranularSound产生 PCM 的叶子来源
15EnvironmentSound控制一个虚拟 voice 的真正叶子
16DynamicEntitySound从实体查询子 sound
23/24/25/27/28VariableCurveSound、VariablePrintValueSound、VariableBlockSound、MathOperationSound、ParameterTransformSound变量运算
32-35SoundSet、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):

  1. 文件名:path + schemaVersion + ext,ext 在 __BANK 下为空、否则为 .rel;找不到带版本号的文件时回退到不带版本号 metadatamanager.cpp:212-227。
  2. u32 schemaVersion:与代码 *_SCHEMA_VERSION 比较,小于/大于分别断言“Data is out of date”/“Code is out of date” metadatamanager.cpp:236-247。
  3. LoadMetadata:u32 size + 对象数据块(可装入调用方预分配缓冲)metadatamanager.cpp:494-506。
  4. LoadStringTable:u32 size、u32 numStrings + 字符串数据(bank 名等)metadatamanager.cpp:1017-1038。
  5. 对象映射: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)文件来源加载者
AudioConfigaudioconfig.dat(名字表始终加载,因为 wave slot 初始化需要)audConfig::InitClass src/dev_ng/rage/base/src/audiohardware/config.cpp:14-19
SoundsmetadataPaths_SoundsaudSoundFactory::Init soundfactory.cpp:482-532
Curves / Categories / DynamicMixermetadataPaths_*curverepository.cpp:29、categorymanager.cpp:56、dynamicmixmgr.cpp:162
ModularSynthoptamp.dat(运行时,chunk OPTIMISED);合成器编辑器构建另加载 modsynth.datsynthSynthesizer::InitClass(第 9 节)
SpeechmetadataPaths_Speech/Speech2audSpeechSound(见 music-radio-speech.md)
GameObjectsaudio:/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/_SyncWaveSlotRequest soundmanager.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 一律走 audDecoderSpu src/dev_ng/rage/base/src/audiohardware/decodemgr.cpp:95-200、src/dev_ng/rage/base/src/audiohardware/driverdefs.h:13。每混音帧解码上限 kMaxDecodesPerFrame:Orbis 10、其他 20 mixer.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):

  1. ExecuteCommandBuffers();若暂停(或 fiIsShowingMessageBox)则输出清零并返回。
  2. 路由变化后 ComputeProcessingGraph():从各 end point 递归给 submix 分配处理阶段 device.cpp:1458-1562。
  3. 所有 PLAYING voice Premix() → GeneratePcm():遍历 PCM source 池,引用计数为 0 的释放;有物理 buffer 的 GenerateFrame(),无物理 buffer 的 SkipFrame()(虚拟播放推进时间);等待同步信号的源(audMixerSyncManager)延后到第二轮;结果写回 PcmSourceState(播放位置、峰值等,供音频线程读取)device.cpp:390-555。
  4. voice Mix()(滤波/包络后混入 submix)→ 按处理顺序 submix.Mix(),阶段切换时释放上一阶段 submix 缓存 → 全部 PostMix()。
  5. 非 Xenon/Durango/Orbis 平台在此做硬削波(这三个平台在输出交错时处理);非 PS3/Orbis 平台若输出模式为立体声则做 6→2 下混 device.cpp:266-309。
  6. 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(chunk BASE)为基础并额外加载 optamp.dat,还可通过 RAVE 加载额外 chunk src/dev_ng/rage/base/src/audiosynth/synthesizer.cpp:61-120。
  • synthCore 字节码运行时(游戏侧):非编辑器构建只加载 audio:/config/optamp.dat(Xenon/PS3 走 update: 路径),对象类型为 CompiledSynth synthesizer.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 = 0 category.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)、DistanceAttenuationValueTable src/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 生成一帧并入输出队列,队列空出一半以上时同一轮多生成一帧;无音频设备时把混音器置为 suspendeddevice_xaudio_pc.cpp:143-312、device_xaudio_pc.cpp:609、device_xaudio_pc.cpp:740-840
PC -dsoundaudMixerDevicePc(WAVE_FORMAT_EX)自有混音线程 PRIO_TIME_CRITICALdevice_pc.cpp:56、device_pc.cpp:278
PC -asioaudMixerDeviceAsio(依赖 audioasiolib,即 Steinberg ASIO SDK 封装)ASIO 混音线程device_asio.cpp:130
OrbisaudMixerDeviceOrbissceAudioOutOpen 打开主输出与“受限内容”输出两个 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
DurangoaudMixerDeviceXAudio2(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/vieweraudiovwrAudioManager:查看器/示例程序用的最小音频封装,自己调用 InitializeAudio、持有 GameObject metadata;被 sample_simpleworld 引用AudioMgr.h:24-65、AudioMgr.cpp:123
rage/base/src/audioscriptsoundtypesaudScriptSoundVM:32 寄存器的浮点小 VM(LOAD/STORE、算术、三角/波形、dB 换算、比较与跳转);除 unity 文件外没有任何 sound 类型或游戏代码引用,判定为未启用scriptsoundvm.h:17-34、opcodes.h:6-60
rage/base/src/audioasiolibASIO SDK(asio.h、asiodrivers、asiolist),供 PC -asio 输出driver.cpp:283-287
rage/base/src/audiodataADAT 容器读写与构建(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 / submix128 / 768 / 40mixer.h:26-56
最大音高比4(最高 192 kHz 回放)mixer.h:37-46
最大输出通道6device.h:309
命令缓冲块数PC 3,其他 2;默认大小 GameThread 32 KB、AudioThread 64 KBdevice.h:39-43、engine.cpp:362、soundmanager.cpp:241
命令延迟3 混音帧device.cpp:995-996
requested settings 缓冲NG 4,旧平台 3enginedefs.h:44-49
音频帧目标15 ms(暂停 10 ms)engine.cpp:103-104
bucket24 × 128 sound 槽 × 64 reqset 槽,保留 2(audioconfig 可改);每桶虚拟 voice 96soundmanager.cpp:185-226、enginedefs.h:33-42
controller实体 2048、sound 引用 2048、environment group 512controller.h:19-21
虚拟化预算4000(PC 低配 2000 / 96 voice)voicemgr.cpp:202-220
帧缓冲池340(driverSettings_NumFrameBuffers)device.cpp:610-614
托管效果g_MaxManagedEffects = 24enginedefs.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 的调度细节仅概述。