离线音乐渲染工具:高效检查音频循环和过渡

Wwise 技巧和工具

高效地整合音乐

在使用 Wwise 整合游戏音乐时,要播放整首音乐才能确认其循环是否自然。比如,若有段两分钟的音乐,要确保循环点之间无缝衔接,得花整整两分钟从头听到尾。

同样,在检查互动音乐中的段落变换和过渡时也是如此。尤其对于长音轨末尾附近的过渡,没问题的话预览一次可能就够了。要是有任何问题,修正设置和素材之后还得重新播放整条音轨。

这种反复检查的过程耗时又费力,时间紧张的话可能会影响到品质。

为了解决这一问题,PlatinumGames 开发了独有的工具 Music Render。藉此,借助 Wwise 的离线渲染 API,高效地检查音频循环和过渡。在此,我们就来介绍一下该工具的使用示例和技术配置。

 

Music Render:使用示例

下面以 Wwise 示例工程 Cube 为例展示了如何利用 Music Render 有效解决在通过 Wwise 整合音乐时常见的一些问题。

 

示例 1:简单的循环

"Story" Music Playlist Container 是个只包含一个 Music Segment 的简单循环。它的时长约为一分钟。也就是说,检查循环需要一分钟的时间。

image1

 

这个时候就要用到 Music Render 了。为此,可从 Wwise Playlist 菜单激活该工具并设置以下参数。

  • Rendering time:检查循环需要多长时间(1 ~ 10分钟)
  • Application to open the WAV file:比如,MAGIX 的 SOUND FORGE。

image2

 

单击 [Start Rendering] 可创建 SoundBank 并自动实施渲染。在渲染完成后,会在 SOUND FORGE 中打开 WAV 文件。然后,便可从循环点快速播放 Music Segment 来检查循环。

image3

 

示例 2:复杂的播放列表

"Explore" Music Playlist Container 包含多个 Music Segment。假如没有工具,就要花很长时间播放整个 Music Segment 来检查其过渡是否存在问题。

image5

 

在渲染过程中,Music Render 会在生成的波形文件中自动为段落过渡点添加标记。这样不用听完整首乐曲就可检查过渡;它们会从这些标记位置接着往后播放。

image6

 

示例 3:互动过渡

"Wwise 201 Music" Music Switch Container 会将音轨切换为 Combat 或 Explore(由 "Gameplay_Switch" Switch 触发)。

image8

 

在检查过渡时通常要手动切换音轨,但 Music Render 提供更高效的方法,其允许在创建和渲染 Event 时自动切换。
通过配置以下各项创建示例 Event:

  • 播放 Switch Container
  • 每 15 秒更改 Switch 值

image9

 

在通过 Music Render 对 Event 做离线渲染时会创建音乐波形,并以 15 秒为间隔(15、30、45…)切换音轨。若发现不自然的过渡,可对过渡或素材进行修改,然后重新渲染来确认修复效果。这种自动化操作可确保即便对于难以手动再现的问题也能快速检查并加以修正。

image10

 

技术配置

Music Render 由前端和后端组成。

 

前端 (UI)

  • 编程语言:C#
  • UI 框架:WPF
  • 启动:作为附加组件 (.exe) 在 Wwise 设计工具中启动
  • 功能:
    • 通过 WAAPI 与 Wwise 设计工具进行通信
    • 创建 Event 和 SoundBank
    • 向后端发送渲染命令,并在渲染完成后打开 WAV 文件

 

后端(渲染流程)

  • 编程语言:C++
  • 启动:从前端启动 (.dll)
  • 功能:
    • 通过 Wwise SDK 初始化声音引擎
    • 加载 SoundBank、发送 Event 并通过离线渲染来输出音频
    • 在完成整个流程后将结果返回给前端

 

后端细节

有关初始化 Wwise 声音引擎和加载 SoundBank 等基本流程,请参阅官方文档。本节总结了离线渲染需要特别注意的点。不妨了解一下。

 

所需全局变量

AK::OfflineRendering::g_fFrameTimeInSeconds = 1.0f / 60.0f;
AK::OfflineRendering::g_bOfflineRenderingEnabled = true;

这些变量在 AkProfile.cpp 中定义。虽然通常要有相应 Wwise 授权才能访问源代码,但创建自用的离线渲染工具时应该不需要授权。

 

离线渲染 API

AK::SoundEngine::StartOutputCapture(settings.WavFileName);
AK::SoundEngine::StopOutputCapture();

 

通过回调添加标记

static void MusicCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
    AK::SoundEngine::AddOutputCaptureMarker("Entry");
}

 

整段代码

下面贴出了主要的代码段落。其中包含 MusicRender 类以及该类中使用的结构定义。另外,还包含离线渲染所需的所有技术点,不过去掉了错误处理部分。

// MusicRender.h

struct MusicRenderSettings{
    wchar_t*    SoundBankFolderName;
    wchar_t*    StreamFolderName;
    wchar_t*    SoundBankFileName;
    wchar_t*    PluginDllFolderName;
    wchar_t*    Event;
    int         RenderingDurationSec;
    wchar_t*    WavFileName;
};

typedef void(__stdcall* NotifyProgress)(float);

extern "C" __declspec(dllexport) void __stdcall MusicRender(const MusicRenderSettings&, const NotifyProgress);

// MusicRender.cpp

namespace AK {
    namespace OfflineRendering
    {
        extern AkReal32 g_fFrameTimeInSeconds;
        extern bool g_bOfflineRenderingEnabled;
    }
}

CAkFilePackageLowLevelIODeferred g_lowLevelIO;

class CMusicRender
{
public:
    const AkGameObjectID LISTENER_ID = 0;
    const AkGameObjectID GAME_OBJECT_MUSIC = 1;
    
    AkPlayingID m_iPlayingID = 0;

    void Initialize(const MusicRenderSettings& settings)
    {
        AK::SoundEngine::RegisterGameObj(LISTENER_ID, "Listener (Default)");
        AK::SoundEngine::SetDefaultListeners(&LISTENER_ID, 1);

        g_lowLevelIO.SetBasePath(settings.SoundBankFolderName);

        AK::StreamMgr::SetCurrentLanguage(AKTEXT("English(US)"));

        AkBankID bankID;
        AK::SoundEngine::LoadBank("Init.bnk", bankID);
        AK::SoundEngine::LoadBank(settings.SoundBankFileName, bankID);

        AK::SoundEngine::RegisterGameObj(GAME_OBJECT_MUSIC, "Music");

        g_lowLevelIO.SetBasePath(settings.StreamFolderName);
    }

    void Terminate(const MusicRenderSettings& settings)
    {
        AK::SoundEngine::StopPlayingID(m_iPlayingID);

        AK::SoundEngine::UnregisterGameObj(GAME_OBJECT_MUSIC);

        AK::SoundEngine::UnloadBank(settings.SoundBankFileName, NULL);
        AK::SoundEngine::UnloadBank("Init.bnk", NULL);
    }

    void Main(const MusicRenderSettings& settings, const NotifyProgress callback)
    {

        AK::OfflineRendering::g_fFrameTimeInSeconds = 1.0f / 60.0f;
        AK::OfflineRendering::g_bOfflineRenderingEnabled = true;
        
        AK::SoundEngine::StartOutputCapture(settings.WavFileName);
        {
            m_iPlayingID = AK::SoundEngine::PostEvent(settings.Event, GAME_OBJECT_MUSIC, AK_MusicSyncEntry, MusicCallback);

            const int callbackIntervalMS = 10;
            DWORD lastNotificationTimeMS = GetTickCount64();

            float renderTimeSec = settings.RenderingDurationSec;
            float audioBufferSec = AK::OfflineRendering::g_fFrameTimeInSeconds;
            for (float timeSec = 0; timeSec < renderTimeSec; timeSec += audioBufferSec) {
                AK::SoundEngine::RenderAudio();
                if (GetTickCount64() - lastNotificationTimeMS >= callbackIntervalMS) {
                    callback(timeSec / renderTimeSec);
                    lastNotificationTimeMS = GetTickCount64();
                }
            }
        }

        AK::OfflineRendering::g_bOfflineRenderingEnabled = false;

        AK::SoundEngine::StopOutputCapture();
    }

    static void MusicCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
    {
        AK::SoundEngine::AddOutputCaptureMarker("Entry");
    }

};

void __stdcall MusicRender(const MusicRenderSettings& settings, const NotifyProgress callback)
{
    if (InitSoundEngine(settings) == false) {
        return;
    }

    CMusicRender cMusicRender;
    cMusicRender.Initialize(settings);
    cMusicRender.Main(settings, callback);
    cMusicRender.Terminate(settings);

    TermSoundEngine();
}

 

结语

从大概 2019 年开始,我们在好几个 PlatinumGames 项目中都使用了这一工具。它让我们可以快速检查音乐循环和互动过渡,并在此基础上创作更加复杂精细的音乐作品。借助此工具,您可以轻松完成各项检查,腾出时间来探索如何在游戏中呈现音乐。

另外,若在开发阶段被要求提供声音素材,还可轻松将分割成段落的 WAV 文件合并,并输出为单个 WAV 文件来用作循环素材。因为素材是按照游戏中的音量渲染的,所以跟只包含音效的演示视频合并时,两者的音量自然而然就可以达到平衡。对此,该工具还允许同时渲染多个播放列表容器和 Event。

希望这篇博文能帮助各位更加高效地整合音乐!

田中 直人(NAOTO TANAKA)

田中 直人(NAOTO TANAKA)

自 1996 年以来,田中直人 (Tanaka Naoto) 作为作曲家参与了众多游戏音频项目。直人于 2013 年担任《Metal Gear Rising Revengeance》的音乐总监,并在 Wwise 面世之初与外部作曲家一起积极推动互动音频的引入。他以技术音频设计师的身份参与了 PlatinumGames 的很多项目(包括为 2022 年推出的《Bayonetta 3》整合音乐)。

评论

留下回复

您的电子邮件地址将不会被公布。

更多文章

人人都能用 WAAPI(二)wwise.core 分支

大家好,我是溪夜。 在《人人都能用 WAAPI(一)概述》中,我们用思维导图对 WAAPI 进行了重新归纳,并在配置好开发环境后,一起用 Python 写了几个简单的小程序,体验了 WAAPI...

29.10.2020 - 作者:汪洋

Wwise Unity 速查表

今天我们来说说 Wwise Unity Integration。这些年来,相关学习资源越来越丰富,随时都可以拿来参考。就拿 Audiokinetic 官方资源来说,YouTube...

19.1.2021 - 作者:麦斯·麦雷蒂·桑德鲁普 (Mads Maretty Sønderup)

在 Wwise 中设定 Audio Object

未来遥不可及,往日似水流年。亦如费里斯•布勒 (Ferris Bueller) 所说:“生活快速向前,时光转瞬即逝。如不偶尔驻足停留,就可能会错失良辰。”距离 Wwise 2021...

24.8.2021 - 作者:戴米安·卡斯特鲍尔(Damian Kastbauer)

Wwise Unity集成手动安装指南

0、上下文 社区中有不少朋友曾遇到Wwise...

8.3.2022 - 作者:侯晨钟

ReaWwise:连接REAPER和Wwise

介绍...

23.9.2022 - 作者:安德鲁·科斯塔 (Andrew Costa)

使用 Wwhisper 连通 REAPER 和 Wwise

大家好,我叫托马斯,是一名来自法国的技术音频设计师,很高兴能在 Audiokinetic 博客上跟大家说说自己开发的 Wwhisper 脚本。...

23.6.2025 - 作者:Thomas Fritz

更多文章

人人都能用 WAAPI(二)wwise.core 分支

大家好,我是溪夜。 在《人人都能用 WAAPI(一)概述》中,我们用思维导图对 WAAPI 进行了重新归纳,并在配置好开发环境后,一起用 Python 写了几个简单的小程序,体验了 WAAPI...

Wwise Unity 速查表

今天我们来说说 Wwise Unity Integration。这些年来,相关学习资源越来越丰富,随时都可以拿来参考。就拿 Audiokinetic 官方资源来说,YouTube...

在 Wwise 中设定 Audio Object

未来遥不可及,往日似水流年。亦如费里斯•布勒 (Ferris Bueller) 所说:“生活快速向前,时光转瞬即逝。如不偶尔驻足停留,就可能会错失良辰。”距离 Wwise 2021...