☰
VST3 SDK 3.6.14:目录分层、AGain样例与出声链路
2026/10/27 12:20:06 网站建设 项目流程

简介:VST SDK 3.6.14 Build-24 是 Steinberg 面向音频插件开发者发布的官方开发包,定位于帮助具备 C++ 基础的音乐软件开发者构建与数字音频工作站兼容的 VST3 插件,覆盖音频处理、合成器、均衡器、压缩器等方向。包内提供 IAudioProcessor、IEditController 等接口定义,配合示例工程、API 参考文档、跨平台构建脚本与 VSTPluginExample 测试宿主,可用于理解参数自动化、多通道处理、64 位精度、插件延迟补偿及音频处理与界面分离的模块化架构,也保留与 VST2 插件兼容的参考路径。压缩包约 86.17MB,文件总数与类型明细在页面暂未列出,实际以压缩包内容为准。该资源已有 202 人学习,适合想系统进入音乐软件开发、对照官方接口与示例完成插件原型验证的开发者查阅。

1. 2019 年的 VST3 SDK 3.6.14,为什么现在还有人翻出来

VST、VST SDK、VST3 三个词经常被混着搜,其实指向同一套东西的不同层面:VST 是插件格式,VST3 是第三代规范,VST SDK 是官方放出来的实现包。手上这份vst-sdk_3.6.14_build-24_2019-11-29.zip是 2019 年底的一个完整快照,从纯虚接口定义、参考基类、样例工程,到三个平台的编译入口,全都塞在一个压缩包里。

上个月帮人看一个编译能过、宿主里死活不出现的插件,最后发现对方照着网上的新教程改了目录结构,而那套目录是更晚的版本才有的。这类问题在这份老包上特别常见:接口本身很稳,稳到 2019 年编出来的二进制现在还能被主流宿主正常加载;但外围的工程组织、样例命名、编译脚本跟现在的教程差了一代,照抄就会出问题。它适合两种人,一是要维护存量插件、不想整体升级的;二是想把 VST3 从「被扫到」到「出声音」这条链路一次看透的,看完再去看新版本能省很多事。

2. 解包先看这三层:pluginterfaces、public.sdk、base 的分工与依赖顺序

拿到一个 SDK 压缩包,很多人的第一反应是找样例工程双击打开,然后被一堆找不到的头文件拦住。这套 SDK 的目录划分是有明确依赖方向的,搞清楚谁依赖谁,编译报错基本能自己定位。这一章先把三层结构摊开,再说样例工程该怎么挑。

2.1 插件与宿主之间的唯一契约在 pluginterfaces

pluginterfaces是整套东西里最独立的一层,只依赖自己内部的pluginterfaces/base,不引用public.sdk的任何东西。里面全是纯虚类和 FUID 常量,没有一行实现代码。宿主加载你的插件之后,能调用的每一个方法都在这几个头文件里。

解包之后先瞄一眼结构:

unzip -q vst-sdk_3.6.14_build-24_2019-11-29.zip -d sdk cd sdk/VST_SDK/VST3_SDK ls pluginterfaces/base ls pluginterfaces/vst

base目录下会看到funknown.h、fuid.h、ipluginbase.h、fplatform.h。funknown.h定义了FUnknown和tresult,顺手把kResultOk、kResultTrue、kResultFalse、kInvalidArgument这些返回值常量也定义了——注意kResultOk和kResultTrue的值都是 0,写代码时混用不影响运行,但读代码的人会误解你的意图。

vst目录下的头文件才是干活的地方:

头文件核心接口/类型什么时候必须翻它
ivstcomponent.hIComponent、BusInfo、PlugType写总线数量、总线信息、激活、状态存取
ivstaudioprocessor.hIAudioProcessor、ProcessData、ProcessSetup写process()、延迟、尾巴、采样精度
ivsteditcontroller.hIEditController、ParameterInfo写参数定义、参数显示、界面创建
vstspeaker.hSpeakerArr、SpeakerArrangement判断宿主给的是单声道、立体声还是多声道
ivstevents.hIEventList、Event写乐器类插件收音符和控制器消息
ivstmidicontroller.hIMidiMapping让宿主的 MIDI CC 能映射到你的参数

这层不依赖public.sdk,意味着你在宿主侧写一个最小加载器时,只引这几个头文件就够,不用把整个 SDK 拖进去。反过来也成立:SDK 里那些现成的基类全都可以不用,自己从FUnknown开始实现IComponent完全合法,只是工作量大。

2.2 public.sdk 是「可以不用的脚手架」

public.sdk提供的是参考实现,不是硬性要求。它的价值在于把引用计数、总线管理、参数容器这些重复劳动封装好了,让样例工程的代码量能压到几百行。

三个子目录要分清:

  • public.sdk/source/main/:模块入口。dllmain.cpp、linuxmain.cpp、macmain.cpp负责导出GetPluginFactory这个宿主唯一会去找的符号;pluginfactory.cpp落地了BEGIN_FACTORY_DEF/DEF_CLASS2/END_FACTORY这三个宏背后的工厂实现。
  • public.sdk/source/vst/:可以继承的基类。vstaudioeffect.cpp里的AudioEffect、vsteditcontroller.cpp里的EditController、vstsinglecomponenteffect.cpp里的SingleComponentEffect、vstparameters.cpp里的Parameter和RangeParameter。
  • base/source/:基础设施。FObject、FString、UString、UBStream这些,被上面两层共同引用。

编译顺序是base→public.sdk→ 你的插件。漏掉base会在链接阶段出现一堆FUnknown、FObject相关符号未定义。用 g++ 手工编一版大概长这样:

g++ -std=c++11 -fPIC -shared -O2 \ -I. -Ipluginterfaces \ -Ipublic.sdk/source/vst \ -Ibase/source \ MyPlugin.cpp \ public.sdk/source/main/linuxmain.cpp \ public.sdk/source/main/pluginfactory.cpp \ public.sdk/source/vst/vstinitiids.cpp \ base/source/baseiids.cpp \ base/source/fobject.cpp \ base/source/fstring.cpp \ -o MyGain.so

参数含义很直白:-fPIC -shared生成位置无关的动态库,-O2放到处理音频的代码路径上会明显影响单块耗时,别用-O0测性能。这条命令里的源文件清单是手工挑过的,只带了一个插件跑起来必须的那几个;链接报未定义符号时,把报错里的类名在base/source和public.sdk/source下grep -rn "类名::方法名"一遍,就能确定该补哪个.cpp。样例工程自带的 makefile 是更可靠的清单来源,不确定就以它为准。

2.3 以 AGain 为基准,判断一个样例工程能不能直接改

样例目录里有十来个工程,挑错了会走很多冤枉路。先列出来:

find public.sdk/samples/vst -maxdepth 1 -mindepth 1 -type d | sort

在 3.6.14 这一版里,最值得当起点的是again。它是一个带界面的增益插件,四层结构齐全,代码量在一个晚上能读完:

文件职责换成自己的插件时改不改
source/againprocess.h/.cppAudioEffect子类,实现IAudioProcessor的process()改,核心 DSP 都在这
source/againcontroller.h/.cppEditController子类,管参数和参数显示改,参数表要重写
source/againeditor.h/.cppVSTGUI 界面改或砍掉
source/again.cpp工厂入口、UID 定义、模块初始化必须改,UID 不能撞
source/version.h版本字符串改

note_expression_synth虽然更完整,但它带音符表达和事件处理,第一次上手容易被大量事件代码带偏;adelay足够小,可控制器和界面是残的,改完还得自己补。AGain 是唯一一个「四层都在、每层都不长」的工程。

把自己插件建在 AGain 上,只需要动四个地方:again.cpp里两处 UID 必须换成新生成的值(同一份 SDK 里两个插件 UID 相同会被宿主当成同一个)、工厂宏里传递厂商名和插件名的参数、getBusInfo里的总线名、以及处理类里的 DSP。剩下的骨架可以原样留着。

3. 写出第一个能出声的 Processor:总线谈判、process 循环与延迟上报

process()本身很短,难的是它前面那一串协商。宿主不会无条件按你想要的格式调用,它先问、你答、谈不拢就降级,这条链路走错了,process()里拿到的数据和你想的完全不是一回事。这一章按调用顺序把链路走一遍。

3.1 setupProcessing 之前的总线谈判

宿主加载插件后的调用顺序大致是:initialize()→ 查getBusCount()/getBusInfo()→activateBus()→setBusArrangements()→setActive(true)→setupProcessing()→setProcessing(true)→ 反复process()。中间任何一步返回失败,后面都不会发生。

先看getBusInfo:

tresult PLUGIN_API MyProcessor::getBusInfo (MediaType type, BusDirection dir, int32 index, BusInfo& info) { if (type != kAudio) return kInvalidArgument; // 这个插件不收事件 if (index != 0) return kInvalidArgument; // 只有一条总线 info.busType = kMain; // kMain 表示宿主会自动连上,kAux 需要用户手动配 info.channelCount = 2; // 这里给的是默认值,真正生效的以 setBusArrangements 为准 info.flags = BusInfo::kDefaultActive; UString128 ("MyGain").copyTo (info.name, 128); return kResultOk; }

busType传kMain和kAux直接决定宿主会不会自动把这条总线连到通道上。写效果器时输入和输出都给kMain,做成侧链才用kAux。channelCount这里填的是「我默认想要几个声道」,后面setBusArrangements才是真正的谈判桌。

谈判的实现是这样:

tresult PLUGIN_API MyProcessor::setBusArrangements (SpeakerArrangement* inputs, int32 numIns, SpeakerArrangement* outputs, int32 numOuts) { if (numIns != 1 || numOuts != 1) return kResultFalse; // 只做 1 进 1 出 // 输入输出必须同构,避免出现「单声道进立体声出」这种要额外写混音逻辑的情况 if (inputs[0] != outputs[0]) return kResultFalse; if (inputs[0] != SpeakerArr::kStereo && inputs[0] != SpeakerArr::kMono) return kResultFalse; // 多声道一律拒绝 // 把谈下来的结果同步到自己的总线对象,process() 里才不会按 <p> <a href="https://download.csdn.net/download/momomomo90/12273641" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询