音乐、电台与语音对白
本页覆盖 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。
背景与相关页面#
- 脚本命令注册:../14-script-runtime/natives-and-commands.md;剧情/自由模式脚本如何使用对白与音乐事件:../15-script-content/singleplayer-flow.md、../15-script-content/multiplayer-freemode.md。
- 字幕显示与文本表:../18-frontend-ui/text-and-localisation.md、../18-frontend-ui/pause-menu-and-hud.md。
- 面部动画与 clip:../10-animation/rage-animation-and-move.md、../10-animation/game-animation-and-ik.md;过场音频轨见 ../10-animation/cutscenes.md。
- 网络会话(电台同步消息):../16-network-online/network-sessions-and-transitions.md。
- 回放与视频编辑器:../19-persistence-replay/replay-and-video.md。
相关源码文件#
| 文件 | 作用 |
|---|---|
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、kEventWaitForTimemusicplayer.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(
InteractiveMusicMoodGameObject)是一组StemMix;SetMood(mood, triggerTime, volumeOffset, fadeIn, fadeOut)可以在乐曲内某个时间点切换 mood,CrossfadeToStemMix在 stem 音量之间交叉淡变musicplayer.h:166-196、musicplayer.h:311。 - 三个代码驱动的子系统:
audWantedMusic、audIdleMusic、audVehicleMusicmusicplayer.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 = 15src/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、ForceRadioTrackActionsrc/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 | 单条曲目的流式播放、播放时间、下一拍信息(混音电台)、文本 ID | src/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:
- 若被强制(
m_ForceNextTrackCategory)直接返回;PC 上若连续坏曲目超过 4 次进入“恢复模式”(插播广告后持续尝试音乐,主要针对用户音乐)radiostation.cpp:4340-4361。 - 只放音乐的电台直接返回
MUSIC;未播放的已解锁剧情新闻只在玩家正在收听的电台播放,且不连续播新闻radiostation.cpp:4371-4407。 - 接管电台(takeover)走专门函数;其他情况按“上一条类别”选权重表(例如上一条是音乐且禁止背靠背音乐时用
NoBackToBackMusic表;RADIO_34_DLC_HEI4_KULT有专用表),对“该电台有效且已过下次可选时间”的类别求权重和,再用电台自己的 PRNG 抽样radiostation.cpp:4420-4501。 - 抽到新闻但有待播剧情新闻时改为广告;新闻/天气若距上次太近则换其他类别
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 选台词,返回 audSaySuccessValue | speechaudioentity.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)推导 voice | speechaudioentity.h:673-674、speechaudioentity.h:875-877 |
Say 的主干(speechaudioentity.cpp 中 Say(u32 contextPHash, audSpeechInitParams, ...))是一长串“早退”过滤后再申请槽位 src/dev_ng/game/audio/speechaudioentity.cpp:6244-7130:
- 资格过滤:联机中玩家 ped 需通过
IsMPPlayerPedAllowedToSpeak;动物、水下、死亡 ped 不说话;部分 context 有特殊限制(如战吼需持枪)speechaudioentity.cpp:6303-6320、speechaudioentity.cpp:6357-6373、speechaudioentity.cpp:6599-6610。 - 解析 context:先查
TriggeredSpeechContext(含主/备用 context 与各自的冷却),再经g_SpeechManager.ResolveVirtualSpeechContext/GetSpeechContext得到具体SpeechContext(MP 中可按性别、或醉酒朋友替换)speechaudioentity.cpp:6526-6566、speechaudioentity.cpp:6674、speechaudioentity.cpp:6757-6767。 - 资源与距离:流式繁忙时、或超出与空闲槽数量相关的最大距离时,改走
AttemptSayWithBackupContextspeechaudioentity.cpp:6728-6753。 - 时间与重复规避:context 冷却(
GetTimeWhenContextCanNextPlay等)、同一 voice 的重复间隔、短语记忆中已用过的变体不再播放speechaudioentity.cpp:6803-6858、speechaudioentity.cpp:6957-6993;选中的变体号会用于网络同步speechaudioentity.cpp:6861-6866。 - 申请环境语音槽
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):
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。START_SCRIPT_CONVERSATION→StartConversation(把 buffer 拷贝为活动对白)scriptaudioentity.cpp:3662。- 每帧主线程在
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。 - 打断与中止:慢动作打断、脚本请求打断、车内随机打断者、
ShouldWeAbortScriptedConversation/ShouldWePauseScriptedConversation、PauseScriptedConversation/RestartScriptedConversation/SkipToNextScriptedConversationLinescriptaudioentity.cpp:3981-4067、scriptaudioentity.cpp:4520-4615、scriptaudioentity.cpp:4846-5240。对白期间可 duck 电台与乐谱(ShouldDuckForScriptedConversation等)scriptaudioentity.cpp:3864-3883。 - 字幕:
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把事件转给全局fwAudioAnimHandlerInterfacesrc/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::SetRecordersrc/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/StopReplayTrackPreviewradioaudioentity.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中车辆电台选择)未核对。