CD Code Digest Hub /GTA V · v2

音乐、电台与语音对白

48 分钟阅读 11 个章节

本页覆盖 GTA V 中“内容型”音频:交互式乐谱(game/audio/music)、电台系统(audRadioStation / audRadioSlot / audRadioTrack / 电台发声器 / PC 自定义电台)、语音(audSpeechSound 查找表、audSpeechAudioEntity、audSpeechManager、警用扫描)、脚本对白与字幕、口型同步数据、rage/framework/src/fwaudio,以及这些系统与回放/视频编辑器的接口。引擎层机制见 rage-audio-engine.md,游戏层总体(线程、实体、流槽、GameObjects)见 game-audio.md。

注意:rage/suite/src/audmusic 不是交互式音乐系统,它只是 Xenon/PS3 用户自定义音乐(HUD 播放器)的抽象,GTA V 未引用;见 rage-audio-engine.md 第 12 节。GTA V 的交互式音乐完全位于 game/audio/music。

背景与相关页面#

相关源码文件#

文件作用
game/audio/music/musicplayer.{h,cpp}audInteractiveMusicManager:乐谱播放状态机、mood/stem mix、节拍图
game/audio/music/musicevent*.{h,cpp}、musicaction*.{h,cpp}、*action.{h,cpp}音乐事件与 9 种动作
game/audio/music/{wanted,idle,vehicle}music.{h,cpp}代码驱动的自动配乐
game/audio/radiostation.{h,cpp}、radiotrack.{h,cpp}、radioslot.{h,cpp}、radioemitter.{h,cpp}、radioaudioentity.{h,cpp}、radiostationhistory.h、radiostationtracklistcollection.*、userradiotrackmanager.*电台
rage/base/src/audiosoundtypes/speechsound.{h,cpp}、speechdefs.h语音查找表与 SpeechSound
game/audio/speechaudioentity.{h,cpp}、speechmanager.{h,cpp}、scannermanager.*、policescanner.*角色语音、语音槽、警用扫描
game/audio/scriptaudioentity.{h,cpp}、game/script/commands_audio.cpp脚本对白、字幕、脚本音频
rage/framework/src/fwaudio/audioentity.{h,cpp}、audmesh.{h,cpp}fwAudioEntity(动画音频事件)、音频网格资源结构
game/audio/replayaudioentity.*、game/control/replay/Audio/*回放音频

1. 交互式音乐(audInteractiveMusicManager)#

1.1 结构#

audInteractiveMusicManager : naAudioEntity 是全局单例(Instantiate() 于 audNorthAudioEngine::InitClass 中创建,见 game-audio.md),并且 IsUnpausable() 恒为 true(暂停菜单下也会更新)src/dev_ng/game/audio/music/musicplayer.h:48-69。

  • 状态 kStateSilent / kStateUnscripted / kStateScripted,模式 kModePlaying / kModeStopped / kModeSuspended;内部事件队列项 kEventPrepareTrack、kEventSnapCategoriesToMood、kEventCrossFadeCategoriesToMood、kEventWaitForWaveslotFree、kEventWaitForTime musicplayer.h:71-100。
  • 乐曲是一个多 stem 的流式 sound(audStreamingSound *m_MusicSound),最多 AUD_NUM_MUSIC_STEMS = 8 个 stem,每个 stem 的音量由一个 audCategoryController 控制(m_pCatCtrlStems);另有音乐/one-shot 及其 ducking 类别控制器 musicplayer.h:46、musicplayer.h:317-345。
  • mood(InteractiveMusicMood GameObject)是一组 StemMix;SetMood(mood, triggerTime, volumeOffset, fadeIn, fadeOut) 可以在乐曲内某个时间点切换 mood,CrossfadeToStemMix 在 stem 音量之间交叉淡变 musicplayer.h:166-196、musicplayer.h:311。
  • 三个代码驱动的子系统:audWantedMusic、audIdleMusic、audVehicleMusic musicplayer.h:259-262、musicplayer.h:352-355。

1.2 音乐事件与动作#

脚本的 TRIGGER_MUSIC_EVENT(name) 直接调用 g_InteractiveMusicManager.GetEventManager().TriggerEvent(eventName) src/dev_ng/game/script/commands_audio.cpp:3109-3112。

  • audMusicEvent::Init 在 GameObjects 中查找名字:若是单个 MusicAction 对象则包装成单动作事件,若是 MusicEvent 则逐个创建其 Actions[];事件池化(FW_REGISTER_CLASS_POOL),同时活跃事件 ≤ kMaxActiveEvents = 15 src/dev_ng/game/audio/music/musicevent.cpp:49-99、src/dev_ng/game/audio/music/musicevent.h:16-48。
  • audMusicEvent::Prepare() 依次准备各动作,汇总为 PREPARED/PREPARING/PREPARE_FAILEDmusicevent.cpp:101-120;脚本 PREPARE_MUSIC_EVENT 以脚本线程 ID 为 owner 调用 GetEventManager().PrepareEvent(...) commands_audio.cpp:3091-3097。动作接口只有 Prepare()、Trigger()、Cancel() src/dev_ng/game/audio/music/musicaction.h:18-34。
  • 动作工厂按 GameObject 类型 ID 实例化 9 种动作:StartTrackAction、StopTrackAction、SetMoodAction、StartOneShotAction、StopOneShotAction、RadioDjSpeechAction、FadeInRadioAction、FadeOutRadioAction、ForceRadioTrackAction src/dev_ng/game/audio/music/musicactionfactory.cpp:36-57。即音乐事件不仅控制乐谱,还能让电台淡入淡出、强制电台曲目或触发 DJ 语音。

1.3 节拍图与时序约束#

乐曲 wave 内嵌 marker:加载时扫描两个 stem 的 marker,TEMPO marker 构建 audTempoMap(位置、BPM、拍号数据),SILENCE marker 记录静音区(startTimeS、durationS)src/dev_ng/game/audio/music/musicplayer.cpp:1655-1695。stem mix 可附带 MusicTimingConstraint,切换时间由 ComputeNextPlayTimeForConstraints 计算 musicplayer.cpp:1419-1427:BarConstraint 找到下一个满足 bar % PatternLength == ValidBar-1 的小节起点;BeatConstraint 用 ValidSixteenths 位掩码按 60/Tempo 的拍长对齐到允许的十六分音符;另有 SilenceConstraint musicplayer.cpp:1724-1765、src/dev_ng/game/audio/gameobjects.h:5419、gameobjects.h:5489、gameobjects.h:6869。节拍图也供脚本同步(GetMillisecondsUntilAudioSync、IsAudioSyncNow)与回放编辑器(GetPrevReplayBeat/GetNextReplayBeat)使用 musicplayer.h:196-197、musicplayer.h:239-240。

观察到的疑似缺陷:处理第二个 stem(markers1)的 SILENCE 分支中,durationS 与 startTimeS 读取的是 markers0[j] 而不是 markers1[j] musicplayer.cpp:1687-1694。仅当第一个 stem 没有 SILENCE marker 时才会走到该分支,影响范围取决于素材(未验证)。

1.4 代码驱动的配乐#

  • audWantedMusic::Update:脚本未允许任务中通缉音乐、屏幕已淡出、脚本禁用(WantedMusicDisabled)、-nowantedmusic 或回放编辑模式时让位(必要时触发 WANTED_MUSIC_DISABLED 事件);否则依据玩家通缉等级触发相应音乐事件 src/dev_ng/game/audio/music/wantedmusic.cpp:21-65。
  • audIdleMusic:空闲 60–120 s 后播放 60–100 s 的空闲配乐 src/dev_ng/game/audio/music/idlemusic.cpp:36-39。
  • 回放编辑模式下 Game.VideoEditor.* 变量与慢动作逻辑会影响音乐(见 game-audio.md 第 2 节)。

2. 电台#

2.1 组成#

类型职责证据
audRadioStation一个电台的节目编排:两条曲目(当前 + 下一条)交替、类别选择、DJ 语音、新闻、锁定/隐藏/收藏、历史;最多 g_MaxRadioStations = 96 个电台src/dev_ng/game/audio/radiostation.h:16、radiostation.h:155-209
audRadioTrack单条曲目的流式播放、播放时间、下一拍信息(混音电台)、文本 IDsrc/dev_ng/game/audio/radiotrack.h:23、radiotrack.h:164-167
audRadioSlot物理收听槽(g_MaxRadioSlots = 3),把某电台绑定到发声器、持有最多 2 个 audStreamSlot(当前/下一曲交叉)、处理换台(retune)src/dev_ng/game/audio/radioslot.h:18-107
audRadioEmitter 派生:audEntityRadioEmitter(车辆/实体)、audStaticRadioEmitter、audAmbientRadioEmitter电台声音在世界中的位置;每槽最多 g_NumRadioStationShadowedEmitters = 9 个发声器src/dev_ng/game/audio/radioemitter.h:87、radioemitter.h:133、radioemitter.h:261、radiotrack.h:21
audRadioAudioEntity玩家电台(车辆、手机电台、前端)、换台音效、音量淡变、驱动电台与槽的更新src/dev_ng/game/audio/radioaudioentity.h:41

2.2 更新与“虚拟收听”#

audRadioAudioEntity::PreUpdateService(North 线程,经 controller 的实体 PreUpdate)末尾无论是否暂停都调用 audRadioSlot::UpdateSlots 与 audRadioStation::UpdateStations(注释:暂停时也需更新以便前端音乐等能获得槽位)src/dev_ng/game/audio/radioaudioentity.cpp:1531、radioaudioentity.cpp:1675-1677;主线程上的 GameUpdate() 调用 audRadioStation::GameUpdate() radioaudioentity.cpp:1057-1059。

UpdateStations 每帧更新所有电台(PC 上还根据是否有用户曲目锁定 RADIO_19_USER),然后更新 DJ 语音,并在解锁/收藏数量变化时重新排序电台列表 src/dev_ng/game/audio/radiostation.cpp:1889-1937。每个电台的 Update 维护两条曲目:当前曲结束时切换到已重叠播放的下一曲并写入历史,否则准备/初始化下一曲;没有物理流的电台以“虚拟”方式推进播放时间 radiostation.cpp:3007-3127(IsStreamingPhysically/IsStreamingVirtually/SetPhysicalStreamingState 见 radiostation.cpp:3320-3374)。这表明玩家切到任一电台时都能“中途”听到连续的节目,而物理解码只发生在被 audRadioSlot 占用的少数电台上。物理流槽只有 5 个(RADIO1_A/B、RADIO2_A/B、RADIO3_A),按优先级与音乐、脚本、过场竞争,见 game-audio.md 第 11 节。

2.3 节目编排:类别、权重、新闻、DJ#

曲目类别(GameObjects 枚举 TrackCats):ADVERTS、IDENTS、MUSIC、NEWS、WEATHER、DJSOLO、TO_NEWS、TO_AD、USER_INTRO/OUTRO、INTRO_MORNING/EVENING、TAKEOVER_MUSIC/IDENTS/DJSOLO gameobjects.h:780-800。ComputeNextTrackCategory(timeInMs) 的逻辑 radiostation.cpp:4333-4530:

  1. 若被强制(m_ForceNextTrackCategory)直接返回;PC 上若连续坏曲目超过 4 次进入“恢复模式”(插播广告后持续尝试音乐,主要针对用户音乐)radiostation.cpp:4340-4361。
  2. 只放音乐的电台直接返回 MUSIC;未播放的已解锁剧情新闻只在玩家正在收听的电台播放,且不连续播新闻 radiostation.cpp:4371-4407。
  3. 接管电台(takeover)走专门函数;其他情况按“上一条类别”选权重表(例如上一条是音乐且禁止背靠背音乐时用 NoBackToBackMusic 表;RADIO_34_DLC_HEI4_KULT 有专用表),对“该电台有效且已过下次可选时间”的类别求权重和,再用电台自己的 PRNG 抽样 radiostation.cpp:4420-4501。
  4. 抽到新闻但有待播剧情新闻时改为广告;新闻/天气若距上次太近则换其他类别 radiostation.cpp:4511-4530。

新闻故事最多 kMaxNewsStories = 256 条,状态位 RADIO_NEWS_UNLOCKED、RADIO_NEWS_ONCE_ONLY radiostation.h:14、radiostation.h:63-67。DJ 语音分 INTRO / OUTRO / GENERAL / TIME / TO 五类,状态 IDLE → PREPARING → PREPARED → PLAYING radiostation.h:76-92;CreateCompatibleDjSpeech、FindCompatibleDjSpeechVariation 等负责挑选与当前曲目时长/位置兼容的 DJ 台词,交互音乐动作 RadioDjSpeechAction 可通过 PrepareIMDrivenDjSpeech/TriggerIMDrivenDjSpeech 驱动 radiostation.cpp:3455、radiostation.cpp:3636-3668、radiostation.cpp:3741。曲目列表来自 RadioStationTrackList GameObjects,可被脚本锁定/解锁/强制(LockTrackList、UnlockTrackList、ForceMusicTrackList、QueueTrackList)radiostation.cpp:5544、radiostation.cpp:5682-5780。DLC 电台通过 GameObjects chunk 中的 RADIO_STATIONS_DLC 列表合并(MergeRadioStationList)radiostation.cpp:786。

radiostation.h 中还存在一个以 HEIST3_HIDDEN_RADIO_* 宏实现的隐藏电台功能及其配套的可调参数键名;该功能的开关宏 HEIST3_HIDDEN_RADIO_ENABLED 为 0 radiostation.h:42。本页不复述其中的键值。

2.4 联机同步与历史#

每个电台用 audRadioStationSyncData(名字哈希、随机种子、当前曲目播放时间、下一曲 TrackId{4 位类别, 8 位索引}、最近 kRadioHistoryLength = 15 条历史)描述可复现的节目状态 radiostation.h:230-257、src/dev_ng/game/audio/radiostationhistory.h:16。网络会话在消息中调用 PopulateSyncData 打包、在接收端 SyncStation 还原 src/dev_ng/game/network/Sessions/NetworkSession.cpp:18427、NetworkSession.cpp:15361。进入联机时 ResetHistoryForNetworkGame() 关闭现有曲目以生成“网络兼容”的曲目 ID radiostation.cpp:1026-1060。这表明各客户端独立运行相同的确定性编排,只同步种子与历史,而不传输音频(推断:未逐行确认所有类别都走同一 PRNG)。

2.5 PC 自定义电台#

PC 上 audUserRadioTrackManager 扫描用户音乐(扫描状态 UNSCANNED/IDLE/SCANNING/SUCCEEDED/FAILED,播放模式 RADIO/SEQUENTIAL/RANDOM)src/dev_ng/game/audio/userradiotrackmanager.h:41-55;播放时 audRadioTrack 创建 ExternalStreamSound,由 audMediaReader(平台媒体解码,PC 有 WMF/WM 读取器实现)喂 PCM src/dev_ng/game/audio/radiotrack.cpp:455-497、src/dev_ng/rage/base/src/audiohardware/mediareader.h:54-130。PC 音频堆为此额外预留 13 MB(见 game-audio.md 第 1.2 节)。

3. 语音#

3.1 语音数据:audSpeechSound 与查找表#

audSpeechSound 没有子 sound,播放一个由“voice + context + variation”推导出的 wave src/dev_ng/rage/base/src/audiosoundtypes/speechsound.h:36-39。查找表由 Speech Builder 工具生成:voice 项 {contexts 偏移, nameHash, numContexts, painHash},context 项 {bankNameIndex, variationData 偏移, nameHash, contextData, numVariations} src/dev_ng/rage/base/src/audiosoundtypes/speechdefs.h:18-53。查找表以 "SpeechSound" metadata 类型加载(不加载名字表),并支持按 chunk 增量加载 DLC 语音(LoadSpeechMetadata)src/dev_ng/rage/base/src/audiosoundtypes/speechsound.cpp:65-86。查询 API:变体数量、bank ID(FindBankIdForVoiceAndContext)、由 voice/context/variation 推导 bank 与 wave 名哈希 speechsound.h:65-125。GTA 在 InitClass 中常驻加载 dlc_gtao_speech.dat,DLC 语音通过 AUDIO_SPEECHDATA 挂载器加载(见 game-audio.md 第 1.4 节)。

3.2 角色语音:audSpeechAudioEntity#

audSpeechAudioEntity : naAudioEntity 由 CPed 按需持有(见 game-audio.md 第 3 节)src/dev_ng/game/audio/speechaudioentity.h:629。主要入口:

API用途证据
Say(context, speechParams, voiceHash, preDelay, replyingPed, ...)(字符串或部分哈希版本)环境语音(ambient speech):按 context(如 BUMP、GANG_BUMP)和 SpeechParams GameObject 选台词,返回 audSaySuccessValuespeechaudioentity.h:699-709、src/dev_ng/game/audio/speechaudioentity.cpp:359-362
SayWhenSafe延后到安全时机再说speechaudioentity.h:718
PlayScriptedSpeech(voice, context, variation, waveSlot, ...)脚本/对白台词speechaudioentity.h:679-680
InflictPain(audDamageStats&)痛呼speechaudioentity.h:835
SetAmbientVoiceName(...)指定 / 由 PedVoiceGroups(按种族 PedRaceToPVG)推导 voicespeechaudioentity.h:673-674、speechaudioentity.h:875-877

Say 的主干(speechaudioentity.cpp 中 Say(u32 contextPHash, audSpeechInitParams, ...))是一长串“早退”过滤后再申请槽位 src/dev_ng/game/audio/speechaudioentity.cpp:6244-7130:

  1. 资格过滤:联机中玩家 ped 需通过 IsMPPlayerPedAllowedToSpeak;动物、水下、死亡 ped 不说话;部分 context 有特殊限制(如战吼需持枪)speechaudioentity.cpp:6303-6320、speechaudioentity.cpp:6357-6373、speechaudioentity.cpp:6599-6610。
  2. 解析 context:先查 TriggeredSpeechContext(含主/备用 context 与各自的冷却),再经 g_SpeechManager.ResolveVirtualSpeechContext / GetSpeechContext 得到具体 SpeechContext(MP 中可按性别、或醉酒朋友替换)speechaudioentity.cpp:6526-6566、speechaudioentity.cpp:6674、speechaudioentity.cpp:6757-6767。
  3. 资源与距离:流式繁忙时、或超出与空闲槽数量相关的最大距离时,改走 AttemptSayWithBackupContext speechaudioentity.cpp:6728-6753。
  4. 时间与重复规避:context 冷却(GetTimeWhenContextCanNextPlay 等)、同一 voice 的重复间隔、短语记忆中已用过的变体不再播放 speechaudioentity.cpp:6803-6858、speechaudioentity.cpp:6957-6993;选中的变体号会用于网络同步 speechaudioentity.cpp:6861-6866。
  5. 申请环境语音槽 GetAmbientSpeechSlot(this, &hadToStopSpeech, priority, scriptThreadId),成功后更新 context 使用时间,并把台词参数填入 m_DeferredSayStruct 供 CompleteSay() 使用;若为取得槽位而打断了别的语音(hadToStopSpeech),则标记等待槽位并返回 AUD_SPEECH_SAY_DEFERRED,否则立即完成 speechaudioentity.cpp:6997-7039、speechaudioentity.cpp:7093-7131。

脚本 PLAY_PED_AMBIENT_SPEECH_NATIVE 即调用 ped->GetSpeechAudioEntity()->Say(context, speechParams, ...),并禁止对网络克隆 ped 使用“同步到网络”选项 src/dev_ng/game/script/commands_audio.cpp:983-998。

3.3 语音资源管理:audSpeechManager#

audSpeechManager 直接派生自 audEntity(注释说它可能并不需要是实体)src/dev_ng/game/audio/speechmanager.h:310。它管理:

  • 环境语音槽 g_MaxAmbientSpeechSlots = 6,状态 VACANT / ALLOCATED / WALLA / STOPPING;按优先级分配(GetAmbientSpeechSlot(entity, &hadToStopSpeech, priority, scriptThreadId),可能打断低优先级语音),脚本可预留一个槽 speechmanager.h:23-35、speechmanager.h:321-339。
  • 痛呼槽:状态 LOADING / READY / IN_USE / USED_UP / FINISHING,按声音类型(玩家/男/女/特定/呼吸)与强度(弱/普通/强悍/混合)组织 bank;另有主角专属呼吸类型(Michael/Franklin/Trevor/MP)speechmanager.h:37-70。
  • 短语记忆(g_PhraseMemorySize = 70)、电话对话记忆(100)用于避免重复 speechmanager.h:25-27;动物语音 bank(audAnimalBankSlot,主线程 UpdateAnimalBanks)。

3.4 警用扫描(police scanner)#

scannermanager / policescanner 把犯罪报告、请求支援、发现嫌犯、单/多单位调度、嫌犯倒地、随机闲聊、脚本台词、火车等句型拼接为短语序列(每句 ≤16 个短语,最多缓冲 32 句),状态 IDLE → PREPARING → PLAYBACK → POSTDELAY(以及处理警察调度交互),优先级 AMBIENT … CRITICAL,使用 4 个大槽 + 4 个小槽,单位类型 NORMAL/SWAT/AIR/FBI,22 种 CDI 语音 src/dev_ng/game/audio/scannermanager.h:32-99。数据来自 ScannerCrimeReport、ScannerArea、ScannerSpecificLocation、ScannerVoiceParams 等 GameObjects。由 NA_POLICESCANNER_ENABLED 编译开关包裹,North 线程每帧 g_AudioScannerManager.Update()(见 game-audio.md)。

4. 脚本对白(conversation)与字幕#

对白由 audScriptAudioEntity 实现,容量见 game-audio.md 第 10 节(60 行、36 个说话人 0–9/A–Z)。流程(函数均在 scriptaudioentity.cpp):

  1. CREATE_NEW_SCRIPTED_CONVERSATION → StartNewScriptedConversationBuffer;ADD_PED_TO_CONVERSATION / 前端说话人 → AddConversationSpeaker / AddFrontendConversationSpeaker;ADD_LINE_TO_CONVERSATION → AddLineToScriptedConversation(line, speaker, context, subtitle, listener, volume, isRandom, ..., isPadSpeakerRoute),以 SFX_ 开头的 context 被视作 SFX 命令(IsContextAnSFXCommand,scriptaudioentity.cpp:3176-3184;如 SFX_CODENAME_POST 程序化台词)src/dev_ng/game/audio/scriptaudioentity.cpp:3297、scriptaudioentity.cpp:3349-3375、scriptaudioentity.cpp:3585-3623。注册表(ADD_LINE_TO_CONVERSATION、ADD_PED_TO_CONVERSATION 等)见 commands_audio.cpp:3927-3939。
  2. START_SCRIPT_CONVERSATION → StartConversation(把 buffer 拷贝为活动对白)scriptaudioentity.cpp:3662。
  3. 每帧主线程在 audNorthAudioEngine::StartUpdate 中调用 g_ScriptAudioEntity.UpdateConversation() src/dev_ng/game/audio/northaudioengine.cpp:2091,进而 ProcessScriptedConversation 预载下一行(预载语音槽 4 个)、在当前行结束后按 GetPauseTimeForConversationLine 停顿再播下一行 scriptaudioentity.cpp:581、scriptaudioentity.cpp:1355、scriptaudioentity.cpp:3144。
  4. 打断与中止:慢动作打断、脚本请求打断、车内随机打断者、ShouldWeAbortScriptedConversation / ShouldWePauseScriptedConversation、PauseScriptedConversation / RestartScriptedConversation / SkipToNextScriptedConversationLine scriptaudioentity.cpp:3981-4067、scriptaudioentity.cpp:4520-4615、scriptaudioentity.cpp:4846-5240。对白期间可 duck 电台与乐谱(ShouldDuckForScriptedConversation 等)scriptaudioentity.cpp:3864-3883。
  5. 字幕:ShowSubtitles 先查 <subtitle>_<variation两位数> 文本标签,不存在则退回无变体的标签;字幕开关打开(或该文本不受前端字幕设置控制)时调用 CMessages::AddMessage(pString, TextBlock, ...) 显示(强制显示 3000 ms,否则 10000 ms);注释记录了“字幕关闭时不调用 AddMessage 以免清掉 god text”的设计决定 scriptaudioentity.cpp:2599-2651。

5. 口型同步(lip sync)#

口型数据嵌在语音 wave 自身:audSpeechAudioEntity::GetVisemeData 在当前语音 sound 的 wave 中查找名为 lipsync64(64 位)或 lipsync 的数据块(另检查 custom_lipsync),该块是一个动画资源;通过 ProcessClipDictionaryResource 就地解析成 crClipDictionary,取第 0 个 clip 作为 m_VisemeAnimationData src/dev_ng/game/audio/speechaudioentity.cpp:3384-3468。AudioClipDictionary : crClipDictionary 是为此定制的资源类型,在构造/析构时维护流式分配器计数 speechaudioentity.h:588-624。CPed 在语音预载完成后调用 GetVisemeData 取得关联 clip 用于面部动画 src/dev_ng/game/Peds/Ped.cpp:8546-8556。面部 clip 的播放机制见 ../10-animation/game-animation-and-ik.md。

6. fwaudio#

rage/framework/src/fwaudio 只有两个模块:

  • fwAudioEntity : audEntity:给实体加入动画音频事件(最多 AUDENTITY_NUM_ANIM_EVENTS = 4 个 u32 哈希,去重),PreUpdateService 中调用纯虚 ProcessAnimEvents();带一个动画循环 sound 槽;静态 HandleAnimEvent 把事件转给全局 fwAudioAnimHandlerInterface src/dev_ng/rage/framework/src/fwaudio/audioentity.h:20-95、src/dev_ng/rage/framework/src/fwaudio/audioentity.cpp:16-57。事件来源:MoVE 输出参数中带 audio 属性的关键帧——有音频实体的 fwEntity 走 AddAnimEvent,否则走默认处理器 src/dev_ng/rage/framework/src/fwanimation/move.cpp:325-345;同步场景导演组件同样转发 src/dev_ng/rage/framework/src/fwanimation/directorcomponentsyncedscene.cpp:428。GTA 注册的默认处理器 naAnimHandler 对无音频实体的物件(如武器)、建筑等分派 northaudioengine.cpp:5694-5714。
  • agPolyMesh(audmesh.h):可 pgBase 资源化的“音频网格”(多边形、区域、区域间音频连接、四边拼接),注释说明其布局类似导航网格 src/dev_ng/rage/framework/src/fwaudio/audmesh.h:17-94。使用者只有 game/audio/audiogeometry.*(其 include 已被注释,未启用)与离线工具 src/dev_ng/tools/AudMeshGenerator;运行时遮挡实际使用的是 portal 路径(见 game-audio.md 第 5.3 节)。

7. 与回放 / 视频编辑器的交互(简述)#

  • 录制:audRequestedSettings 提供 audSoundRecorderInterface(AUD_ENABLE_RECORDER_INTERFACE 在 PC/Durango/Orbis 开启),回调覆盖 sound 删除、停止、客户端变量、各类 requested setting 变化 src/dev_ng/rage/base/src/audioengine/requestedsettings.h:23-42;回放系统用 replaySoundRecorder 实现并在初始化时 audRequestedSettings::SetRecorder src/dev_ng/game/control/replay/Audio/ReplaySoundRecorder.h:11、src/dev_ng/game/control/replay/ReplayInternal.cpp:666。此外 control/replay/Audio/ 下有环境声、子弹掠过、碰撞、过场、动态混音、爆炸、脚步、近战、抛射物、脚本、语音、武器等专用音频包(如脚步包回放时调用 AddFootstepEvent,见 game-audio.md 第 9 节)。
  • 编辑器:audReplayAudioEntity 管理编辑器时间线上的音效/环境标记与流(含预览、对音乐的 ducking)src/dev_ng/game/audio/replayaudioentity.h:60-111;电台实体提供配乐曲目预览 StartReplayTrackPreview/StopReplayTrackPreview radioaudioentity.h:75-76;交互音乐提供回放节拍查询(1.3 节)。
  • 导出:视频导出时混音器进入捕获模式,由游戏逐帧泵动混音(见 rage-audio-engine.md 第 11 节与 game-audio.md 第 12 节)。

8. 约束与边界行为#

  • 交互音乐每次只有一个乐曲流和最多 8 个 stem;mood 切换受节拍/小节/静音约束,可能延迟到下一个满足条件的时间点。
  • 电台物理播放受 3 个 audRadioSlot 和 5 个物理流槽限制,且与乐谱、脚本流、过场共享优先级(过场最高);其余电台只做虚拟推进。
  • 环境语音同时最多 6 条(不含脚本对白与痛呼),超出时按优先级打断或失败。
  • 字幕依赖文本标签存在;带变体与不带变体的标签都缺失时只告警。
  • 口型数据缺失时 GetVisemeData 不产出 clip,面部不做口型(推断:调用方以 NULL 判空)。

9. 未决问题 / 证据不足#

  • audSpeechAudioEntity::Say 只梳理了主干;CompleteSay、回复语音(replying ped)、痛呼与呼吸、电话/对讲机音效路由等细节未逐行分析(该文件是 game/audio 最大的源文件)。
  • audRadioStation 的随机步长(RandomizeCategoryStride)、顺序电台、混音电台(beat marker)、接管电台的规则未展开。
  • 过场音频(audCutsceneAudioEntity、audCutTrackCutScene/SynchedScene)、前端音频(audFrontendAudioEntity)、语音聊天(audVoiceChatAudioEntity)仅列出,未分析。
  • audVehicleMusic、电台的车辆类型偏好(audRadioAudioEntity 中车辆电台选择)未核对。