周若琳博客

让电脑用我的音色说话:从零搭起一条语音链路

让电脑用我的音色说话:从零搭起一条语音链路

评论
让电脑用我的音色说话:从零搭起一条语音链路

让电脑用我的音色说话:从零搭起一条语音链路

记录第一天:从环境清理到第一段"像我"的声音。

15:31 起点不是语音,是一次工具迁移

工作目录在 15:31 建好。真正开工前先处理了旧助手的资产盘点——那个工具年底就要停运,得先把里面的技能、会话记录和配置盘清楚,决定后面所有脚本和素材放哪、凭证从哪读。

15:55 出了盘点报告:技能 56 个、会话记录 23 个、定时任务为空;配置文件里混着网关口令和第三方接口密钥,属于不能随手外发的敏感内容,当场做了隔离提醒。

16:00 先让它出声

语音的最短路径是找一个免费、不需要 API Key 的合成引擎。选了 edge-tts(对,就是 Edge 浏览器那个在线合成),装进独立的 Python 环境:

bash

python -m edge_tts --voice zh-CN-XiaoxiaoNeural \
  --text "随便说点什么" --write-media voice_reply.mp3

16:00 拿到第一段机器女声。标准播音腔,能听,但一听就不是我。

16:37 换成我自己的音色

转折在这里。之前已经做过一次声音复刻——用一段本人的录音训练出一个专属音色,参数存在配置里:

json

{ "voice_id": "m_zhou_ruolin_v3_...", "model": "flow_01_ex", "sdkappid": 1600166179 }

于是写了个脚本,跳过官方 SDK、用 TC3-HMAC-SHA256 手工签名直接调腾讯云 TRTC 的 TextToSpeech,再用 ffmpeg 做 60–8000Hz 带通去杂。16:37 产出 8.6 秒的成品——这一次,声音是我自己的。

中间踩了两个"报错措辞误导人"的坑:地域参数必须写 ap-shanghai,写广州会报 AppId 不匹配;Voice.Volume 传 0 会被拒绝,最优解是只传音色 ID,其余参数一律不加。

16:56 把过程写成文档

链路跑通后,把整条"造声"经过(音频格式对接、零样本克隆、云端复刻、样本导入、完整文件名清单)整理成 Word,16:56 产出《语音助手诞生全记录.docx》。

排版走的是自动化流水线:内容、版式、转换三段式,中间有一道六维质量门禁,最终满分通过。踩的规则坑挺典型——任何带单位的间距都要走变量、裸色值会被判违规、每一个表格都必须带表头。

17:00 同一份内容做成 PPT

17:00 生成了一份 16 页、16:9 的演示文稿,四色池配黑体加等宽的固定组合,全矢量配图,没用一张位图。

自检只能靠几何越界检查和逐段文本宽度估算——因为这台机器上没装任何能渲染演示文稿的软件,没法截图做视觉复核,只能靠"算"。

17:51 打包带走

最后把整套东西原样打包,好上传到服务器保持功能一致。17:51 产出 13.17 GB 的压缩包,1845 个条目,耗时 95 秒。

这么大的包能这么快,靠两件事:对已经压缩过的高熵文件(模型权重、音频、压缩包)改用"只存储不压缩",省掉大量 CPU;输出文件放在被排除的目录里,避免打包程序把自己也打进去。

包里含明文密钥,说明文件里加了醒目警告。

这一天的关键词

  • edge-tts:免费,能出声,不像我

  • 云端声音复刻:收费,但像

  • 手工 TC3 签名直调 API:不装官方 SDK,省下几百 MB 依赖

  • 自动化文档流水线:内容、版式、转换三段式


让克隆音色开口唱歌:从"念白"到歌声转换,再到模型微调

记录第二天:三次方案迭代——念白、零样本转换、链式微调。

一个假设,先被推翻

最初的思路很直接:既然合成能出我的音色,那让它唱不就行了。核查之后发现此路不通。

现有的三个语音引擎全是朗读型 TTS,接口里只有文本、音色、语速、采样率,没有音符、没有音高曲线、没有时长控制。它们能"念",不能"唱"。

同时把硬件底细摸清:CPU 12 核、内存只有 8 GB,显卡型号一开始取不到(系统把 wmic 和 reg 都拉黑了),后来靠 nvidia-smi 才问出来是 RTX 3070 / 8 GB 显存,CUDA 12.1 可用。

17:07 先做一版"念白"

不能唱,那就先按原曲的时间轴念。脚本把 22 句歌词逐句合成,再按每句的起始时间把音频填进静音轨,拼出 4 分 21 秒的念白版(17:08)。

这一步的价值在于验证了整条"合成、对齐、拼接"的管线,而且分段做了缓存——已生成的片段会跳过,重跑不会重复计费。

换方案:零样本歌声转换

真正的转折是决定用 Seed-VC——一个零样本歌声转换模型:不需要训练,给一段 1–30 秒的参考音频,它就能把一段歌声换成那个人的音色,同时保留原唱的旋律、颤音、换气。

拿到原曲文件后,整条链路当天跑通:

bash

# 分离人声与伴奏
demucs -n htdemucs_ft --two-stems=vocals
# 歌声转换
python inference.py --f0-condition True --diffusion-steps 40 \
  --inference-cfg-rate 0.7 --fp16 True
# 混音
python mix.py

18:35 交付第一版《陪你去流浪》音色版。

网络与依赖的七个坑

这台机器在"下载"上格外费劲,几乎每一步都撞墙:

  • GitHub 不通,走 gh-proxy.com 镜像下源码

  • HuggingFace 不通,换 hf-mirror.com,且必须同时关掉 Xet 分片协议,否则 401

  • PyTorch 的 CUDA 版只有官方源有,国内镜像清一色是 CPU 版

  • torch 在 Windows 上报 fbgemm.dll 加载失败,缺一个 OpenMP 运行库,从本地现成副本拷过去解决

  • 模型硬依赖的一个音频编解码库根本没发布到 PyPI,改成惰性导入绕开

  • HuggingFace 缓存在 Windows 下把快照变成 0 字节占位文件,写脚本按哈希从数据块里还原真实文件

  • 检查点里带着分布式训练遗留的前缀,加载时会自动剥离,日志里那句"Skipped loading 11 keys"是正常的

一个必须记住的参数

--auto-f0-adjust 不能用。它想把旋律整体平移到参考音色的音域,实测却把音高拉低了大约 1.2 个半音,导致人声和伴奏不合调。

正确做法是用 --semi-tone-shift N 手动移调。实测很准:+7 半音对应 229.7 Hz、+12 对应 304.1 Hz,和理论值分毫不差。

18:40 顺手让它说一句话

用第一天训练好的音色直接合成了一句问候语(18:40)。又暴露两个参数坑:音高参数必须是整数、音量传 0 会被拒绝,最后发现"只传音色 ID"最稳。另外采样率只支持 8k / 16k / 24k 三档,请求 48k 会直接报错。

18:47 与 18:59 把当天写成复盘

按惯例,把当天全链路整理成 Word(18:47)和 PPT(18:59)。PPT 走受限的页面描述语法逐页生成,又踩了几个语法坑:正文里不能出现裸的小于号、加粗要用内联样式而不是标签、百分比宽度得写成字符串。

20:15 用户不满意,重做一版

第一版交付后收到反馈:发闷、发糊。

诊断出三个病根,每一个都能追溯到具体数据:音色参考素材是从 64kbps 的音频里截的,频谱在 6 kHz 就断崖;参考音频电平极低(整轨 RMS 低到 −46 dB),而模型并不做归一化,等于"参考条件太弱";混音链里还加了回声和压缩。

重做版的改动逐项对症:

  • 分离改用 4 模型集成的 htdemucs_ft,人声和伴奏分得更干净

  • 参考音频重建为 5 条 44.1kHz 单声道、峰值统一归一化到 −0.9 dBFS

  • 混音里伴奏单位增益、无滤波、无变调,人声按实唱段的功率对齐,而不是整轨 RMS

结果可量化:8 kHz 以上能量从 −57.7 dB 提到 −17.5 dB(原曲是 −19.5),音高中位偏差 0.13 个半音,音高轨迹相关 0.9905,颤音保留 98.6%。

21:30 又一首:《演员》

同一套流程迁移到另一首歌,参考素材改用"留一法"筛选——把每条候选参考拿去生成,再反过来算它和其余参考的相似度,挑最"稳定像同一个人"的那条。

这一版新增了一个指标:振幅包络相关 0.885,说明渐强渐弱、呼吸起伏是跟着原唱走的,而不是机器自己发挥。

21:39 第三轮:模型微调

零样本再好也有天花板。用户反馈"AI 感太重"之后,做了两件事来定位:零样本只取参考的前 25 秒频谱、不做自适应,相似度上不去;而用朗诵素材微调,会把"这个人的高频是空的"一起学进去,8 kHz 以上掉 9–20 dB。

于是设计了链式微调:分三段各 600 步(累计 1800 步),每段单独存模型,避免只保留最新两个检查点的问题。显卡 8 GB 满载但能跑,约 1.7 次迭代每秒。

最终把微调版和未微调版做分频混合:0–8 kHz 用微调版,8 kHz 以上用未微调版。效果是音色相似度从 0.381 提到 0.438(+14.9%),8 kHz 能量从 −15.6 提到 −10.7 dB。

三条硬教训

  • 绝不要在后台任务运行时覆盖它正在执行的脚本——脚本是按字节偏移边读边跑的,覆盖后偏移错位,会执行到错行的内容

  • 用 nohup ... & 起的进程在这套工具里活不下来,必须用工具自身的后台机制

  • 两个版本的输出如果同名,新旧对比的差异检测会失效,跑完要立刻手工留档

这一天的关键词

  • Seed-VC:零样本歌声转换,不训练也能换音色

  • Demucs:4 模型集成分离人声

  • 留一法选参考:用"像不像同一个人"来挑素材

  • 链式微调:把天花板从 0.381 顶到 0.438

  • 全程 0 元:纯本地显卡运算,没调付费云接口


收尾:一首《那些年》,和一次"到底会不会重复扣费"的实测

记录第三天:用微调模型换唱新歌,顺带用账单接口把自己最担心的一件事查清楚。

先换一首歌唱

《演员》的效果还是不理想,反馈是"没情感、不像"。于是换了《那些年》,用同一套链路重跑:

bash

# 320kbps 音源分离,残差 −26.5 dB
demucs -n htdemucs_ft --two-stems=vocals
# 微调模型(1800 步)+ 选定参考,全曲转换
svc_ft.sh
# 零错位高频修复
python spectral_restore.py
# 混音
python mix_v4.py

指标:音色相似度 0.412(基线 0.363)、音高中位偏差 0.11 个半音、音高轨迹相关 0.9965、颤音比 1.001、装饰音相关 0.9908、削波 0。交付完整版、纯净版、副歌试听和 A-B 对照各一份。

一个被翻出来的隐藏缺陷

复盘上一版《演员》时发现,所谓的"高频修复"其实有硬伤:微调版和基线版在全曲范围内根本没有时间对齐(相关只有 0.02–0.04,扩散采样有随机性),低通加高通一混合就会错位,齿音加倍、相位出问题——这可能正是"AI 感重"的一部分来源。

40 秒的短试听能对上(相关 0.9)极具误导性。这种对齐必须用全曲验证。

修复方案换成"零错位"的做法:低频段按源人声的长期频谱做静态均衡,高频段直接用同一信号搬移自造——因为是在同一条信号上做操作,时间必然对齐。修完 8 kHz 处从 −28.1 dB 提到 −13.0 dB。

07:41 一个悬了两天的疑问

前两天查本月账单时看到一个反常的条目:声音克隆,计价单位写着「元/个/天」。而官方文档明明白白写的是「9.9 元/次,按次计费」「音色可反复使用,后续使用不计费」。

两者直接矛盾。如果文档对,那是一次性收费;如果单位对,那我前一天建的几个音色每天都要扣一笔——差别是持续扣费还是一锤子买卖。

07:41 先查了一次,当天账单还没出(日结在 08:22 左右)。只能等。

08:26 到 08:35 实测

结算窗口过去之后,每隔 60 秒查一次本月账单明细,连续 10 次采样:

  • 08:26:17 — 总条数 6,当天新增 0

  • 08:27:18 — 总条数 6,当天新增 0

  • …(中间 7 次完全一致)

  • 08:35:22 — 总条数 6,当天新增 0

最新的条目还停在昨天的域名注册,完全没有出现新的"声音克隆"条目。

参照物很关键:前一天同一窗口在 08:22 就已经出账了。如果真按天收费,此刻必然已经生成一笔。没有,就是没有。

结论:一次性收费

官方文档双证一致——

  • 产品文档:「声音克隆 9.9 元/次,按次计费」

  • 增值服务计费说明:「每个音色 9.9 元,可反复使用,后续使用不计费」

那「元/个/天」是怎么来的?那是后付费日结账单的展示单位,不是计费单位。该条的类型是"按量计费日结"、周期"1 天"——"天"指的是结算周期(每天结一次账),而费用的归属时间起止都等于创建那天,此后不再有同项目的费用期间。属于账单文案歧义。

本月现金支出合计 138.70 元:声音克隆 29.70(一次性,三个音色)、域名注册 109.00(三年,与语音无关);语音合成的字符费全部被赠送包抵扣,实际 0。

那三个克隆音色放着不删也不花钱。

一个账单接口的坑

顺带记一笔:之前写的汇总脚本合计恒为 0。原因是它读行级的"总费用"字段,但那个接口的行级根本没有这个字段——金额只存在于组件明细里,必须逐组件求和。这类"字段存在但为空"的坑,比报错更难发现。

这一天的关键词

  • spectral_restore:零错位高频修复,靠同信号搬移保证对齐

  • 全曲验证:短片段对齐不代表整首对齐

  • 10 次采样实测:用真实接口数据终结一个计费疑问

  • 字段为空 ≠ 没有数据:金额藏在组件明细里

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者