技术纪实 · 项目复盘
她是怎么诞生的
语音助手「周若琳」从 0 到 1 全记录
对接微信 SILK 语音 · 音色克隆训练 · 语音样本导入 · 完整文件清单
记录时间:2026-10-10
来源:QClaw 工作区实战记录
一句话说清:她本来是只会打字的。后来,她有了自己的声音——一条能直接播放的语音。这份记录完整还原了这条声音从无到有的全部过程:怎么对接微信的语音格式(SILK)、怎么把一个人的声音「训」成一个可复用的音色、语音样本是怎么一步步导入进来的,以及过程中产出的全部脚本、样本和文件。它既是技术复盘,也是一份可以直接照着复现的操作手册。
目录
一、缘起:让 AI 用声音说话
这一章说的是「为什么要让她开口」,以及一开始定下来的目标和技术选型。
(一)需求
2026 年 10 月 7 日,目标被明确下来:不再只发文字,而是用「她自己的音色」发语音消息。
这里有一个关键限定——要的不是一个 mp3 附件,而是那种可以直接点开的语音气泡。
(二)目标链路
整套系统的核心链路其实只有四步:
(三)方案选型
音色合成的引擎,当时定下两条路:
主路线:CosyVoice 零样本克隆——给一段参考录音即可,免训练。
备选路线:云端 TTS(火山 / 讯飞 / MiniMax),填上 Key 即可切换。
同时做了两个「减负」决策:去掉对外部 ffmpeg 的依赖(SILK 编码改用纯 Python 实现),并把参考文本 REF_TEXT 作为手填默认方式,省掉必装的语音识别。
二、第一关:对接「前任」SILK
要让她发语音,第一个绕不开的问题就是:微信的语音到底是什么格式?这一章记录打通 SILK 的全部过程。
(一)为什么是 SILK
微信的语音消息不是 mp3,也不是 wav,而是 SILK 编码。这就是必须先打通的那道关。
有两个参数是踩出来的:
采样率:微信语音采用 24000 Hz;如果发出去播不了,就退到 16000 或 8000 试。
发送方式:发送时
asVoice: True这个参数,决定了它是一条语音气泡还是一个文件附件。
(二)第一代实现:纯 Python 编码
voice_module.py(v0.2)是第一代核心模块,一次性把三件事都串起来:
TTS:
cosyvoice与cloud双路径,可切换;SILK 编码:
wav_to_silk()用「wave 读取 → s16le PCM →pysilk.encode()」的纯 Python 方式完成,不依赖 ffmpeg;发送:
send_voice()提供两种接法(Skill 内message工具 / 独立脚本走 HTTP 网关)。
(三)第二代实现:edge-tts + silk-wasm
为了让链路更快、更稳,第二代 voice_send.py(v5.0)把 TTS 换成了 edge-tts,SILK 编码改用 Node.js 的 silk-wasm:
配套还有一个 Node 版本 voice_send.js(v3.0),以及负责实际编码的 _silk_encode.mjs。
(四)16kHz 版本
因为「24k 播不出来」的可能性一直存在,专门做了一版 16kHz:voice_send_16k.py,产出 voice_out_16k.silk。
(五)直出语音气泡:给微信插件打补丁
前面几条路产出的 .silk 文件,仍然只是文件。要变成语音气泡,必须让微信通道认识这种文件。
为此写了 patch_weixin_voice.py——直接给 OpenClaw 微信插件的 channel.js 打补丁,插入一段语音分支。它做的事情相当底层:
判断
audioAsVoice === true且是本地.silk/.slk/.opus文件;用
silk-wasm解码算出播放时长playtime;内联重实现一遍上传逻辑:MD5 校验 → AES-128-ECB 加密 → 获取上传地址(
media_type: 4即 VOICE)→ 上传 CDN;构造
VoiceItem(encode_type: SILK、sample_rate: 24000、playtime),调用/ilink/bot/sendmsg发出。
(六)结局与兜底
结局并不完美:服务端确实收下了(返回了 message_id),CDN 的 downloadParam 也有效,但客户端就是不渲染语音气泡。尝试过 crypto 修复、sendMessageItemWeixin、文本字段、16kHz 编码(voice_out_16k.silk)等多种方式,最终判定:机器人协议不支持出站语音气泡。
于是方案落地为兜底策略:以音频文件(MP3)的形式发语音。这一步虽然退让了,但保证了「她能出声」这件事先成立。
三、第二关:训练音色(克隆)
这一关是整个项目最花时间、也最曲折的部分,前后走了两条完全不同的技术路线。
(一)路线一:本地 CosyVoice-300M 零样本克隆
第一条路线是在本机跑开源模型,用「零样本克隆」的方式合成音色。下面按环境、样本、脚本、坑四个部分展开。
1. 环境与依赖
本地语音栈的安装清单为:
安装过程记录在 voice_install.log 与 voice_install2.log 中。模型为 CosyVoice-300M(约 5.6 GB),纯 CPU 推理。
2. 参考音样本的演进
「训练」在零样本克隆里,其实是选样本。这一路换了非常多轮,因为音色好不好,几乎完全取决于那段参考录音:
3. 关键脚本
voice_ref/_voice_reply_clone.py:主力合成脚本。加载 CosyVoice 零样本推理 → 用librosa.time_stretch调语速 → 用soundfile直接导出 MP3(不依赖 ffmpeg)。voice_ref/run_cosyvoice_long.py:修正了原版「每轮覆盖」的问题,把流式输出的多个 chunk 拼接成完整音频。voice_ref/_transcribe_new.py/_transcribe_new2.py/_transcribe_new3.py:用 Whisper 转写参考录音,得到REF_TEXT。voice_ref/_extract_poem.py/_extract_new3.py/_extract_seg2.py:从长录音里切出干净片段。voice_ref/_combine_ref.py:把两段样本拼接。voice_ref/_slow_clone.py/_slow_mp3.py:慢速处理。
4. 踩过的坑
(二)路线二:云端腾讯云 TRTC 声音复刻(升级)
第二条路线把合成搬到云端。这一节说明升级的原因、方案对比,以及最终落地的接口链路。
1. 为什么升级
本地 CosyVoice 的天花板很明显:慢、偏念稿、无更大模型可换。既然要「正常说话、没有朗读腔、自然、带感情」,就必须转向云端语音克隆 API。
2. 方案调研
调研结论汇总在 voice_model_upgrade_research_20261010.md,候选方案对比如下:
最终选了腾讯云——理由是自助、限时免费,且中文自然度高。
3. 接口链路:两次转向
第一次尝试:声音复刻 VRS「一句话版」
域名 vrs.tencentcloudapi.com,版本 2020-08-24,链路为:GetTrainingText → DetectEnvAndSoundQuality → CreateVRSTask → DescribeVRSTaskStatus。
但这条路有个硬性要求:必须按指定文本重新录一段。用户不愿重录,尝试用现成样本提交后,检测直接返回 DetectionCode=-2(文不对本)。此路作废。
第二次尝试:TRTC 对话式 TTS(成功)
域名 trtc.tencentcloudapi.com,版本 2019-07-22,内容不限:
VoiceClone:PromptAudio(base64 音频,6–180 秒)+SdkAppId+VoiceName→ 返回VoiceIdTextToSpeech:Text+Voice.VoiceId+Model→ 返回 base64 音频
参数:SdkAppId = 1600166179,Model = flow_01_ex。
4. 音色版本迭代
音色一共克隆了三版,配置分别落地:
5. 踩过的坑:Region
官方文档写 ap-guangzhou,但实际调用 TextToSpeech 会报错:
实测 ap-shanghai 与 ap-beijing 均可成功合成,最终 TENCENT_REGION 固定为 ap-shanghai。
6. 参考音降噪
为了彻底去杂,写了 _denoise_ref.py,对参考音做三步处理:
noisereduce去非稳态噪声(呼吸 / 环境),prop_decrease=0.9;noisereduce去稳态噪声(底噪 / 电流声),prop_decrease=0.6;Butterworth 带通滤波 80–7500 Hz。
产物为 voice_ref/_ref_clean16k_v3.wav——这正是 v3 音色的参考源。
合成侧同样加了「去杂保险」:_trtc_say.py 在生成后用 ffmpeg 做带通(highpass=60, lowpass=8000)+ 24kHz / 128kbps 覆盖输出。
7. 云端脚本
tc_voice_clone.py:参考音频 →VoiceId(含 ffmpeg 预处理为 16k 单声道)tc_tts.py:文本 → MP3(命令行传文本)_trtc_say.py:内置多条情感文案,按 key 选择合成 + 去杂_tc_check.py/_tc_train_text.py/_tc_detect.py/_tc_probe2.py:凭证校验、取训练文本、音质检测与探针
效果:秒级合成、自然度高,用户确认「非常完美」。
(三)两条路线对比
四、第三关:语音样本怎么导入
知道了原理,还要知道具体怎么把一段录音变成她的声音。
(一)完整导入流程
接收:用户以「文件」形式发送录音(如 m4a),落盘到媒体目录
C:\Users\百羽\.qclaw\media\inbound\。存档:复制到
voice_ref/并规范化命名(如ref_new.m4a)。解码:用 ffmpeg 统一转为 16 kHz 单声道 wav(如
_trtc_ref16k.wav)。转写:用 Whisper 转写内容,得到参考文本
REF_TEXT(若走 TRTC 克隆则非必需)。切片段:从长录音中截取干净的 6–12 秒片段(零样本克隆的最佳区间)。
降噪:
noisereduce两轮 + 带通滤波(可选但强烈建议)。克隆:提交参考音到云端,得到
VoiceId;或直接用本地样本做零样本合成。合成:文本 →
VoiceId→ MP3 → 去杂 → 发送。
(二)三类发送方式
五、全流程总览
把前面三关串起来看,一条语音从「用户录音」走到「发出声音」,中间要经过下面四个阶段。
六、文件清单
以下为本次「造声」过程中产出的全部关键文件,均位于 C:\Users\百羽\.openclaw\workspace\。
(一)核心脚本
(二)参考音频样本
(三)训练与合成产物
(四)文档记录
(五)依赖与运行环境
(六)配置与凭证
凭证统一存放于工作区
.env(TENCENT_SECRET_ID、TENCENT_SECRET_KEY、TENCENT_APP_ID、TENCENT_TRTC_SDKAPPID、TENCENT_REGION)。音色配置:
voice_ref/trtc_voice.json(voice_id/name/model/sdkappid)。
安全提示:.env 内含敏感密钥,仅限本机使用,不应复制、外发或写入任何日志。
七、经验与坑(教训清单)
把上面三关里最值得记住的九条,单独列在这里。
微信语音的格式是 SILK,采样率 24000;播不出来再退 16000 / 8000。
asVoice: True决定气泡还是附件——但机器人协议最终不支持出站语音气泡,兜底发 MP3 文件。零样本克隆对相同样本是确定性的:重跑不会改变音色,想换声音只能换样本。
样本质量 > 样本长度:6–12 秒干净片段最佳,拼接长样本反而更差。
朗读腔是本地模型的硬伤,靠换样本只能改善、无法根治——这才有了云端升级。
腾讯云 TRTC 的 Region 必须用
ap-shanghai,文档写的ap-guangzhou会报 AppId mismatch。「一句话版」要求按指定文本重录,现成录音提交会被判定文不对本(
DetectionCode=-2)。降噪是加分项:两轮
noisereduce+ 带通滤波,能明显提升克隆纯净度。输出端也要去杂:ffmpeg 带通(60–8000 Hz)是保证「发出的音干净」的最后一道保险。
八、时间线
整个项目从启动到音色定版,前后只用了三天。
评论