VOICEVOX 引擎 API 集成应用开发指南

本文基于 macOS 用 Song 应用的实现记录,并非将官方 VOICEVOX 编辑器的内部结构原样移植,而是整理作为外部应用如何稳定地使用引擎 API 所需的要点。 为了让 Xcode/Swift 以外的环境也能参考,先介绍通用方针,然后在各条目结尾附上面向 Xcode 的补充说明。
本页基于 原文指南 完整整理,不省略内容。
1. 先通过 URL 注册引擎

engine_manifest.json 为前提进行管理。而作为外部客户端应用,采用”先注册已启动的 HTTP API 服务器的 URL”的方式反而更好处理。
典型的 URL 如下。
| 引擎 | 示例 |
|---|---|
| 官方 VOICEVOX 引擎 | http://127.0.0.1:50021 |
| Laravel 版引擎 | http://127.0.0.1:50513 |
Laravel 版如果不指定
--port=50513 而以默认 8000 启动,也会在 8001 之后寻找端口;但一旦指定端口,则该调整功能被禁用。
注册时不要只保存 URL 字符串,而应作为连接检查至少调用以下 API,并将获取到的元信息一并保存。
| API | 用途 |
|---|---|
GET /engine_manifest | 获取 uuid、引擎名称、frame_rate、默认采样率等 |
GET /version | 获取用于显示与兼容性检查的版本 |
GET /singers | 获取歌唱风格列表 |
GET /singer_info?speaker_uuid=...&resource_format=... | 获取图标与附加信息 |
engineId 与 styleId。URL 因用户环境而异,而 engineId 对应 /engine_manifest 的 uuid,也便于与 .vvproj 的 singer.engineId 保持一致。
Xcode 补充: 在 Song 应用中,将 baseURL、engineId、name、frameRate、defaultSamplingRate、version、singers 保存在 RegisteredEngine 中,并保存到 UserDefaults。URL 在使用前对末尾斜杠、查询串、片段进行规范化,即可将 http://127.0.0.1:50021/ 与 http://127.0.0.1:50021 视为等同。在 macOS 应用中通过 HTTP 进行本地连接时,还需要检查 sandbox 与 App Transport Security 的设置。
2. 不要把公司应用式的”引擎启动”揽在自己身上
如果外部应用从一开始就想实现与官方编辑器一样的引擎启动管理,就得处理各操作系统的进程启动、内置二进制、端口冲突、更新、日志管理等。特别是使用官方引擎之外的实现时,指定引擎可执行文件位置并启动的方式很难对上号。 一开始最稳妥的做法是固定为以下流程。- 用户先启动引擎。
- 在应用中输入 URL。
- 应用获取元信息并注册。
- 渲染时通过已注册的
engineId解析为 URL。
TextField、注册 / 重新连接 按钮、官方/Laravel 版的预设按钮、已注册引擎列表,便于操作。异步连接时显示 ProgressView,连接错误与其直接显示 LocalizedError 的文案,不如加上面向用户的说明作为前缀。
3. 引擎注册后再让用户选歌手/风格
在 VOICEVOX 的 API 中,最终用于合成的声音由传给speaker 查询参数的 style ID 决定。Song 每条轨道都有歌手风格,因此 UI 上应从已注册引擎展开 singers,将歌手名、风格名、style ID 作为选项。
保存的值只需以下两项即可。
.vvproj 兼容的 Song 数据配合,也便于支持多引擎。打开项目时若对应的 engineId 未注册,可显示为”未注册的引擎”,并提示用户去注册 URL。
Xcode 补充: 在 Swift 中让轨道持有 Singer(engineId: String, styleId: Int),用于显示时可从 RegisteredEngine.singers 拍平为像 SingerStyleOption 这样供 ViewModel 使用的数组。切换歌手指派时,应使已渲染的音频失效。
4. 若考虑 .vvproj 兼容,采用以 tick 为基准的 Song 模型
VOICEVOX 编辑器的 Song 数据是以 tick 为基准来存储音符与速度的。虽然引擎 API 本身最终需要帧长,但应用内部的编辑模型采用 tick 基准更适合钢琴卷帘、速度变化、拍号、Undo/Redo,以及 .vvproj 的读写。
最基本的结构如下。
| 元素 | 主要字段 |
|---|---|
| Song | tpqn, tempos, timeSignatures, tracks, trackOrder |
| Track | name, singer, notes, gain, pan, solo, mute |
| Note | id, position, duration, noteNumber, lyric |
| Tempo | position, bpm |
| TimeSignature | measureNumber, beats, beatType |
frame_rate 计算 frame_length。
Xcode 补充: 使用 Codable 读写类似 .vvproj 的 JSON 时,tracks 建议用以 ID 为键的 Dictionary,trackOrder 作为显示顺序数组单独分开。保存前对 trackOrder 中的重复、不存在的 ID、未排序的轨道进行校验,可以避免读入后 UI 出问题。
5. Song 合成实现为四阶段 API
Song 的渲染流程比 Talk 的/audio_query → /synthesis 多。基本流水线为以下 4 个阶段。
/sing_frame_audio_query、/sing_frame_f0、/sing_frame_volume 使用 singing teacher 的 style ID 6000,只有最后的 /frame_synthesis 才使用实际所选歌手的 styleId。这一约定在官方引擎和 Laravel 版引擎中都是一致的。
各 API 请求中,关键是不要破坏 Score 与 FrameAudioQuery 的对应关系。Score.notes[].frame_length 根据引擎的 frame_rate 计算,静音用 key: null、lyric: "" 表示。
Xcode 补充: 在 Swift 中可以创建像 VoicevoxEngineSongAPI 这样的 protocol,将实现替换为 URLSession 客户端便于测试。对返回 JSON 的端点将 Content-Type / Accept 设为 application/json;/frame_synthesis 的响应作为 WAV 二进制 Data 处理。HTTP 非 2xx 应作为包含状态码和响应体的错误抛出。
6. 按 phrase 拆分并进行缓存
如果每次都对整首 Song 一次性合成,稍作编辑就要重新生成所有轨道,UI 响应会变差。像官方编辑器那样,将连续音符视为 phrase,并按休止符切分的设计更合适。 对每个 phrase 分别缓存以下阶段。| 缓存 | 输入中应包含的内容 |
|---|---|
| AudioQuery | 引擎 ID、frame rate、速度、Score、音域调整 |
| F0 | AudioQuery、音高编辑、phrase 位置 |
| Volume | AudioQuery、F0、影响音量生成的值 |
| Voice/WAV | 合成用 Query、最终 style ID |
actor 持有渲染缓存,可以在 Swift Concurrency 中方便地管理状态。开始新的渲染时推进 generation 编号,旧的 API 响应即使返回也不采纳。将 Task.checkCancellation() 与 generation 检查结合起来,即可同时应对取消与重渲染。
7. 帧率与静音时长根据引擎元信息决定
ScoreNote.frame_length 通过秒数与 frame_rate 计算得出。frame_rate 不要写死为固定值,而应在注册时从 /engine_manifest 获取并保存。
Song 合成时会在 phrase 首尾插入静音。若首静音过短,音素生成可能不稳定,因此实现中应在”实际静音”、“四分音符”、“从最低秒数反算得到的 tick”之间取得平衡。末尾也追加短暂的静音,必要时对末尾静音区间做淡出。
此外,帧长在取整后应校正使其不小于等于 0。较短的音符或紧跟着速度变化时,容易因取整误差产生 0 帧。
Xcode 补充: 将 tickToSecond / secondToTick 设为纯函数并写好测试,就能在钢琴卷帘、播放头、渲染、视频输出中复用同一转换。frameLength = Int(round(seconds * frameRate)) 的结果应通过将差值传递给相邻音符等方式,保证至少 1 帧。
8. 参数编辑按”生成前”与”合成前”的作用点分开
在 Song 中,音域、音量、音高、音量、音素时长的编辑会分别影响不同阶段。| 编辑 | 生效时机 |
|---|---|
keyRangeAdjustment | 生成 Score 时的 key,以及 F0 生成后的 pitch shift |
volumeRangeAdjustment | /frame_synthesis 之前的音量数组 |
| 音高编辑 | /sing_frame_f0 之后的 F0 数组 |
| 音量编辑 | /sing_frame_volume 之后的 volume 数组 |
| 音素时长编辑 | FrameAudioQuery.phonemes |
SongParameterEditApplicator 这样的纯逻辑中,便于测试。对数组越界访问、负音量等要显式修正或抛错。
9. 播放、混合、导出使用同一套轨道判定
渲染完成后,将各 phrase 的 WAV 放到时间轴上进行播放。多轨道场景下,若普通播放、整体 WAV 导出、Stem 导出中 solo/mute/gain/pan 的判定不一致,对用户来说行为难以预测。 作为通用规则:只要存在任一 solo 轨道,就只输出 solo 轨道;不存在 solo 时则输出未 mute 的轨道。Stem 导出时单独输出目标轨道,与普通的 solo/mute 状态分开处理。 Xcode 补充: 在 macOS 上可以用AVAudioEngine、AVAudioPlayerNode、AVAudioMixerNode 构建播放图。导出应准备与普通播放分开的离线渲染路径,并共用 WAV 编码。播放中如果发生编辑导致已渲染音频失效,应停止播放并明确显示状态。
10. UI 要区分”未注册”、“未指派”、“未渲染”
在 VOICEVOX 引擎 API 集成应用中,失败原因可以分为多种。| 状态 | 应显示的指引 |
|---|---|
| 引擎未注册 | 在引擎连接界面注册 URL |
| 引擎未启动 | 启动引擎后重新连接 |
项目的 engineId 未注册 | 额外注册对应的引擎 URL |
| 轨道未指派歌手 | 选择已注册的歌手风格 |
| 编辑后音频已过期 | 需要重新渲染 |
| 渲染中 | 允许取消 |
renderingState 设为 idle、rendering、rendered、stale、failed 之类的 enum,便于统一控制按钮 disabled 与状态显示。错误消息不仅在状态栏,也应可通过 Alert 显示。
11. 多引擎场景下将”URL”与”engineId”的生命周期分开
多引擎支持时最容易混淆的是 URL 与engineId 的角色差异。
| 值 | 生命周期 | 用法 |
|---|---|---|
| URL | 因用户环境而异 | 用作 API 调用地址 |
engineId | 引擎实现/模型侧的标识 | 用作项目内的引用 |
styleId | 引擎内的风格标识 | 用作 speaker 参数 |
engineId 与 styleId。在应用设置侧保存 engineId -> URL 的映射。这样即使收到他人的项目文件,只要在自己的环境中把同一引擎按 URL 注册,就能渲染。
Xcode 补充: 在渲染前通过类似 Dictionary(uniqueKeysWithValues: registeredEngines.map { ($0.engineID, $0) }) 的方式从 engineId 解析出 RegisteredEngine。若未找到则不发起 HTTP 请求,而提前失败为”歌手对应的引擎未注册”。
12. 测试将 HTTP 客户端、模型转换、渲染计划分开
始终启动 VOICEVOX 引擎本体进行测试成本较高,因此在常规的自动化测试中 mock HTTP,只验证应用侧的请求生成与状态迁移。 优先测试的项目如下。| 目标 | 需确认的内容 |
|---|---|
| URL 规范化 | 末尾斜杠或带路径的 URL 是否按预期处理 |
| 引擎注册 | 能调用 /engine_manifest、/version、/singers、/singer_info 并构建保存用模型 |
.vvproj I/O | trackOrder 与 tracks 的一致性,旧/未来版本的处理 |
| tick/second 转换 | 跨越速度变化时可以来回转换 |
| Score 生成 | 静音、frame_length、默认歌词、音域调整 |
| 渲染计划 | 在发起 API 调用前检测出未注册引擎、未指派歌手、engineId 不匹配 |
| 缓存 | 同输入可复用,编辑后失效 |
URLProtocol 的 URLSessionConfiguration.ephemeral,就能在没有真实 HTTP 服务器的情况下测试 URLSession 客户端。将渲染器通过 protocol 注入 API,actor 的缓存状态提供快照获取方法便于验证。
推荐的实现顺序
如果从最小构成开始,推荐以下顺序。
与其一开始就追求官方编辑器的全部功能,不如先把 URL 注册、歌手指派、四阶段 API、重新渲染状态这些做扎实,这样对官方引擎与 Laravel 版引擎都能兼容良好。