VoiceStudio 实测:10 万星的本地语音克隆,我在 16GB M4 上从源码跑通了引擎,也踩了三个坑

VoiceStudio 这两周一直挂在 GitHub 日榜上:开源、本地跑、被叫作 ElevenLabs 的免费替代,语音克隆、声音设计、配音、有声书一整套,星数已经过了 10 万(11 月 4 日 101,011,今天再看已是另一个数字)。

我没装它的桌面版。不是嫌弃,是因为这个项目真正值得看的部分在下面一层 —— 它默认的合成引擎 OmniVoice 有一份独立的 C++ 实现,纯 ggml,能编、能跑、能用命令行驱动。GUI 干的事,命令行都干得了,而且在无窗口环境里,我可以把每一步的耗时、内存、输出质量都量下来。

这篇给你三样东西:一条能照着敲的编译、下模型、合成路径;一张 M4/16GB 上的真实耗时表(官方仓库里那张 RTF 表到今天还是空的);三个我踩到的坑,其中一个会让你「明明生成了 wav,听起来却一个字都听不懂」,而程序不会报任何错。

它其实是个壳,模型在下面

VoiceStudio 本体不是模型。它是 Electron 桌面端 + Python 后端(默认 http://localhost:3900,本机回环免鉴权)+ 一个 MCP 服务器,底下挂十几个 TTS 引擎:OmniVoice(默认)、IndexTTS 2.5、CosyVoice 3、VoxCPM2、KittenTTS、dots.tts、gpt-sovits…… 转录那边是 WhisperX / Faster-Whisper / Parakeet 一堆。

真正干活的是引擎。默认引擎 OmniVoice 来自小米与 k2-fsa,骨干是 Qwen3 0.6B,外加一个 HuBERT + DAC + RVQ 的音频 tokenizer,输出 24kHz 单声道,官方标称 646 种语言。有个独立项目 omnivoice.cpp 把它移植成 C++17 + ggml,MIT 协议,能编到 CPU / CUDA / Metal / Vulkan —— 这就是本文的主角。

对 16GB 笔记本用户来说,关键点只有一个:Q8_0 量化的权重一共 945MB(语言模型 656MB + tokenizer 289MB),塞进内存毫无压力,跑起来还是 GPU 加速的。这跟 colibri 那种靠磁盘流式硬扛大模型的思路完全相反:模型够小,就不需要腾挪。

编译:cmake 配置 4.5 秒,编译 9 秒

cmake 我装在临时 venv 里,不动系统环境:

Bash
python3 -m venv /tmp/ovo-venv
/tmp/ovo-venv/bin/pip install cmake

拉代码时一定要带 --recurse-submodules,ggml 是子模块,忘了它编译会直接失败:

Bash
git clone --recurse-submodules https://github.com/ServeurpersoCom/omnivoice.cpp.git
cd omnivoice.cpp
git checkout 886fc079838ca7400cb2b42b36e2a65aa1daabe8   # VoiceStudio 钉住的版本
/tmp/ovo-venv/bin/cmake -B build -DCMAKE_BUILD_TYPE=Release \
    -DGGML_METAL=ON -DGGML_BLAS=ON -DGGML_NATIVE=ON
/tmp/ovo-venv/bin/cmake --build build -j 8

M4 / macOS 26.5.2 / Apple Clang 上,配置 4.5 秒,编译 39 个编译单元加 Metal shader 嵌入一共 9 秒。产物 build/omnivoice-tts 只有 385KB —— 它就是个壳,真正的算子在 libggml*.dylib 里。这是我整条链里最省事的一步。

下模型之前,先看许可

权重在 Serveurperso/OmniVoice-GGUF,四档量化两两配对:

档位 语言模型 tokenizer 合计 官方定位
F32 2.46 GB 734 MB 3.2 GB 参考 / 调试
BF16 1.23 GB 373 MB 1.6 GB 最高精度
Q8_0 656 MB 289 MB 945 MB 推荐默认
Q4_K_M 407 MB 252 MB 659 MB 显存最低

比容量更该先看的是许可:权重是 CC-BY-NC 4.0。 上游代码 Apache 2.0,但预训练权重因为训练数据(Emilia 等)的限制只能非商用,GGUF 是权重的转换,继承同一条款。VoiceStudio 的更新日志里写法一致:默认 OmniVoice 属于「非明确商用许可」,在模型目录里第一次使用要手动接受一次条款。

所以「免费替代 ElevenLabs」的准确说法是:个人用途、内部试验免费;拿去给客户做商业配音、塞进付费产品,默认模型不行。 要商用得换引擎,而换之前每个引擎的权重许可都得单独确认 —— 这是我看这类项目的第一顺位,比跑 demo 优先。

下载(本机 Hugging Face 直连可用,hf-mirror.com 也通,两条路都验过;但 10 月底那阵子直连是超时的,镜像更稳):

Bash
mkdir -p models && cd models
BASE=https://huggingface.co/Serveurperso/OmniVoice-GGUF/resolve/main
curl -L -O $BASE/omnivoice-base-Q8_0.gguf
curl -L -O $BASE/omnivoice-tokenizer-Q8_0.gguf
shasum -a 256 *.gguf     # 和模型卡的 LFS oid 逐字对

我这边总带宽被压到 1.2MB/s 左右,945MB 花了十九分钟。最后那句 shasum 不是仪式感 —— 下面第一个坑就是靠它抓出来的。

三种用法,命令行全覆盖

文本从 stdin 进,wav 从 -o 出,-o - 直接吐到 stdout(我用管道接了 2.16 秒的 wav,正常)。

Bash
BIN=./build/omnivoice-tts
M=models/omnivoice-base-Q8_0.gguf
C=models/omnivoice-tokenizer-Q8_0.gguf

# 1. 指定语言,让模型自己挑一个稳定音色
echo "本地语音合成这几年变化很大。" | $BIN --model $M --codec $C --lang Chinese -o out.wav

# 2. 声音设计:用属性描述一个声音
echo "这句话是用属性描述出来的声音。" | $BIN --model $M --codec $C --lang Chinese \
    --instruct "女,青年,中音调" -o design.wav

# 3. 声音克隆:参考音频 + 它的逐字稿
echo "这句话是用属性描述出来的声音。" | $BIN --model $M --codec $C --lang Chinese \
    --ref-wav ref.wav --ref-text ref.txt -o clone.wav

--instruct 只有六类,每类最多一个值,冲突直接报错:性别(男/女)、年龄(儿童/少年/青年/中年/老年)、音调(极低/低/中/高/极高)、风格(耳语)、英语口音(10 种)、中文方言(河南话、陕西话、四川话、贵州话、云南话、桂林话、济南话、石家庄话、甘肃话、宁夏话、青岛话、东北话)。方言只有中文,口音只有英文。

长文本不用手动切。超过 30 秒估算时长会自动分块,每块约 15 秒、70 个字符上下,块间交叉淡入淡出。我拿 276 字的稿子试,日志给出 5 块、块长 70/66/70/74/29 字,交叉淡化后输出 60.4 秒整段。

实测:M4 / 16GB / Metal

环境:Apple M4(10 核 CPU / 10 核 GPU),16GB 统一内存,macOS 26.5.2,Python 3.13.12,模型 Q8_0 + Q8_0,全程 Metal 后端,--seed 默认随机。

每次调用都是独立进程,所以「总耗时」含一次完整加载。我用两字用例(你好。,输出 0.83 秒音频、总耗 2.1 秒)标定冷启动:加载大约 1.6 秒。表里的「净合成」是总耗时减去这 1.6 秒,RTF = 净合成 ÷ 音频时长。

用例 字数 音频时长 总耗时 净合成 RTF 峰值内存 ASR 复读 CER
默认音色(带 –lang) 27 6.04s 5.63s 3.53s 0.58 1306MB 0.000
声音设计 女/青年/中音调 20 4.03s 4.65s 2.55s 0.63 1306MB 0.000
声音设计 男/老年/低音调 20 4.52s 4.61s 2.51s 0.56 1306MB 0.000
声音克隆(10 秒参考音) 20 4.88s 13.26s 11.16s 2.29 1306MB 0.000
长稿 30 秒(单次生成) 138 30.06s 30.07s 27.97s 0.93 1306MB 0.022
长稿 60 秒(分 5 块) 276 60.40s 101.20s 99.10s 1.64 — 0.022
不传 –lang 同句 27 6.04s 5.65s 3.55s 0.59 1306MB 0.000

几条读数值得单独拎出来:

RTF 0.56–0.63 是三种常用路径的水平,也就是合成 1 秒音频花半秒多。 这台机器没独显,这个成绩放进官方那张空白的 benchmarks 表里算能看的了。但注意它的耗时曲线不好看:30 秒单次生成 RTF 0.93,60 秒分块涨到 1.64 —— 文本越长,单位成本越高,跟「批量更划算」的直觉相反。我没做进一步拆解,只能说这是分块路径的实测表现,别拿短句的 RTF 去估长稿的交付时间。

克隆比默认音色慢四倍:RTF 2.29。 同样的 20 字、同样的输出时长量级,净合成 11.16 秒,而默认音色只要 2.5 秒。多出来的开销在参考音的前处理(静音裁剪、逐字稿对齐)和条件化上。做批量配音的话,这个倍数要算进排期。

声音设计确实改变语速。 同一个句子,「女,青年,中音调」出 4.03 秒,「男,老年,低音调」出 4.52 秒,长了 12%。也就是说想调语速,靠 instruct 只能间接影响,真要定长还是用 --duration。

–lang 不是可选项。 同一句话,写 --lang Chinese 时基频中位数 118.2Hz,不写变成 175.2Hz —— 音色是另一个。音频时长恰好都是 6.04 秒,但那是巧合,不是等价。中文内容一律显式写 --lang Chinese。

峰值内存 1306MB,其中语言模型 620.9MB。挂在后台常驻完全没负担。这个数字是按 Q8_0 那对权重量的。

坑一:权重坏了,程序照样给你一个 wav

这是整篇最值钱的一条。

我第一次下载用的是分段并发(六个线程按 4MB 切 range 打 resolve 链接)。文件大小对得上、命令正常打印 wrote out.wav, 72000 samples、退出码 0,一切看起来都好。生成的音频有谐波、有包络、有语句停顿,频谱平坦度 0.006(不是噪声)。

直到我把它丢给系统语音识别复读 —— 空的。英文也一样,识别出来只有一个「And」。

查下去才发现 shasum -a 256 和模型卡的 LFS oid 对不上。ggml 加载时只校验张量字节偏移是否落在文件长度内,不校验内容。 长度够,坏权重就能加载、能推理,然后吐出一段「像人声但不是人声」的东西。

量化特征也很清楚:那份输出的自相关峰只有 0.17,而正常浊音帧在 0.6 以上 —— 它有声带振动般的周期性外观,却没有稳定基频。这种输出,看文件大小、看采样率、看退出码、看有没有报错,全都发现不了。只有两个办法抓得住:校验 SHA256,或者让它自己念一遍再听写。

用 huggingface-cli download 会自动校验;用 curl 拉 resolve 链接,校验这一步得你自己补。

坑二:两处文档推荐的 codec 不是同一个

仓库 README 的 quick start 里,配 Q8_0 语言模型用的是 omnivoice-tokenizer-F32.gguf(734MB);而 Hugging Face 模型卡的文件表里,Q8_0 档位对应的是 omnivoice-tokenizer-Q8_0.gguf(289MB),还明写「recommended default」。

我一开始照 README 拿了 F32,白下 445MB。那一次的峰值内存是 2033MB,比 Q8_0 这套高 727MB —— 不过那一组数据同时还用着坏权重(见坑一),两个变量一起动了,不能算干净对照;能确定的是,codec 从 289MB 换到 734MB 之后内存确实明显上涨。两个都能跑,但 16GB 机器没有任何理由用大那个。照 README 抄命令的人,大概率会跟着多花这 445MB。

坑三:仓库里的二进制是 0 字节

如果你以为能直接抓一个现成的 omnivoice-tts 可执行文件,会踩到第三个坑。VoiceStudio 仓库的 bin/ 下确实有 omnivoice-tts-darwin-arm64 这类文件,但 git clone 下来的全是 0 字节占位符 —— 真二进制由 CI 构建,只随安装包分发,不进仓库。

代码层对这事有防御(binary_preflight.py 会检查文件非空、带真实 Mach-O/ELF 魔数,不合格就回落到 Python 内联后端),所以用 GUI 不会崩;但想直接拿二进制跑命令行,只有两条路:从安装包 resources/bin/ 里抠,或者像本文这样自己编译。

怎么验收:不靠耳朵也能判断合成质量

我听不到这些音频。没有试听条件的时候,「听起来自然吗」这个问题就没法回答,所以我搭了一套替代方案:系统 TTS 造参考音 + 系统 ASR 闭卷听写。

思路很直:让 macOS 自己念一段已知文本,导出 24kHz 单声道 wav 当克隆参考;合成完把输出丢回系统语音识别,让它「闭卷复读」,再跟原文算字错率(CER)。识别对了说明内容对;识别不出来,要么音频坏了,要么发音崩了。

参考音两行搞定:

Bash
say -v Meijia -o ref.aiff "大家好,我是这个博客的作者。今天我们来聊聊本地语音合成。"
afconvert -f WAVE -d LEI16@24000 -c 1 ref.aiff ref.wav
afinfo ref.wav | grep -E "duration|sample rate"

复读用 macOS 26 的 SpeechAnalyzer(Swift,本地识别,不联网,100 行以内能写完)。这条路的产出正好能和 S1-mini 那篇的 ASR 洗稿流程接上:先用识别把音频压成文本,再决定要不要人工听。

这套方法有个必须先校准的地方:参考音本身要能通过复读。我第一次用系统 zh_CN 的 Flo 念参考音,同一句话识别 CER 0.361 —— 「暗夜独行」被念成「阅读行」,「语音合成」变成「本事何成」。换成 Meijia 后 CER 0.000。所以选参考音别随手挑一个中文嗓音,先用 ASR 验一遍,否则你后面所有打分都被这个噪声地板吃掉。

基线干净之后,表里那些 0.000 才有意义。同时也要说清楚它的边界:CER 0 只证明内容对,不证明音色像、不像真人。想看音色有没有学到,我用了另一个粗指标 —— 基频中位数。参考音 201.7Hz(p10–p90 154.8–283.0),克隆输出 240.0Hz(162.4–279.1),区间基本重合,说明音高范围被带过来了;但也确实往上偏了 38Hz。

至于长稿那 0.022 的 CER(138 字错 3 个字位),是三处同音字差异:「对齐字幕」听成「对其字幕」、「整段」听成「整断」、「机器味」听成「机器位」。这三处在我两次生成里都稳定复现。但我无法区分是模型读错了、还是识别听错了 —— 没有试听条件,这个结论只能停在「不能保证逐字正确」。这也是我建议正式稿件必须人工过一遍的原因。

顺带说:带 GPT-5.6、2026 年 11 月 5 日、0.15 美元、40% 的那句中英混排,CER 是 0.000。数字和缩写它读对了,这点比预想的好。

什么场景适合,什么场景别用

适合:

  • 长稿批量出音。改一句重新生成一句,比重录便宜太多。有声书、课程稿、内部培训材料都在这一类。
  • 给 Agent 加一张嘴。命令行 stdin/stdout 友好,-o - 能直接进管道;VoiceStudio 那边还带 MCP 服务器,generate_speech / clone_voice / transcribe 三个工具就能让 Claude Code、Cursor 这类客户端念出来。音频理解那头的对照实验我在 LFM3 那篇做过,一头进一头出,正好凑成闭环。
  • 离线、数据不出机器。音频素材不进别人服务器,这一条本身就值钱。

别用:

  • 商业配音。默认权重 CC-BY-NC 4.0,除非换引擎并逐项核对许可。
  • 要求一字不差的正式稿。CER 不为 0 就代表有字位可疑,数字、缩写、中英混排是高风险区(我这句侥幸全对,不代表每次都对)。
  • 多说话人对话。一次一个音色,双人对谈得分别生成再拼,衔接得自己处理。
  • 需要快速试错的场景。冷启动 1.6 秒、克隆 RTF 2.29,参数翻来覆去调的时候,等待感比想象中重。

还没测的部分

桌面版 GUI(没装,也没窗口环境)、视频配音与有声书两条工作流、本地 HTTP API(localhost:3900 的 /v1/audio/speech)、MCP 服务器的实际接入、Q4_K_M 与 BF16 的对比、Windows / Linux、646 种语言里中英文之外的语种。中文方言标签我只确认了参数会被接受,没逐条验证发音 —— 12 种方言听起来准不准,这个必须靠耳朵。

一句话收尾:这东西值得装,但请先看许可、先校验权重、别把「能生成 wav」当成成功。我在坏权重上浪费了将近半小时 —— 那段时间里它每一句都「成功」了。

推荐阅读

  • floci 实测:LocalStack 停摆后的免费 AWS 模拟器,我无 Docker 从源码跑通了 Agent 本地云链路

    LocalStack 社区版强制登录后,MIT 开源的 AWS 模拟器 floci 成了热门替代品。我在 16GB MacBook 上从源码编译 2.4.0 实测:无 Docker 跑通 S3、Dyn…

  • colibri 实测:大模型不装内存、改「摆放」,我在 16GB M4 上摸清了它的真实边界

    GitHub 46,000 star 的纯 C 推理引擎 colibri,把 744B 大模型的专家权重留在磁盘、按 token 流式读取。我在 16GB M4 Mac 上实测:规划器裁决显示内存下限…

  • Lightpanda 实测:给 AI Agent 换个不吃内存的无头浏览器,我在 M4 上和 Chrome 真刀真枪比了一轮

    GitHub 4 万 star 的 Zig 无头浏览器 Lightpanda,专为 AI Agent 抓网页而生。我在 16GB M4 Mac 上实测:与 Chrome headless 同页对比,峰…

  • unslop 实测:用免费模型把「一眼 AI 生成」的界面改成有设计感的页面

    GitHub 三天 3500+ star 的 unslop 技能,专治 AI 生成界面的紫渐变、发光、emoji 图标和乱间距。我用 opencode 加免费模型真实跑了一遍:从安装、造重 slop …