
开源项目:
beiyege-01/dsh-voice-ai-girlfriend技术栈:React 插件 / AudioWorklet / FastAPI / FunASR / OmniVoice / silero-VAD / DUIX / NapCat
目标平台:Windows 11 + NVIDIA GPU(16GB 显存推荐)+ WSL2
读完这篇你能做到:从一台干净电脑开始,装出一个能听、能说、能被打断、还能对口型说话的 AI 助手
你在电脑上用 AI 写代码,通常是打字提问、看字回答。这个项目给那个对话框加了两样东西:
一双耳朵——点一下麦克风,你说话,它听。停顿一下自动断句,不用按键发送。
一张嘴——它的回答会被念出来。不是机器合成的塑料音,而是克隆自你提供的 10 秒录音,可以是任何人的声音。
再加三个不那么常见的东西:
| 能力 | 实测表现 |
|---|---|
| 语音识别 | 5 秒音频 151ms,14 秒音频 155ms |
| 开口延迟 | 说完停顿 1.8 秒后,约 0.5 秒内开口(不含大模型思考时间) |
| 音色克隆 | 首轮 1.6 秒出音,之后每句约 1.3 秒 |
| 抗噪 | -35dB 噪声门,能挡住桌面风扇 |
| 打断 | 说话即打断,服务端同时停止合成(不空烧显卡) |
| 数字人 | 7.3 秒音频 14 秒出片,长回复切段连续播放 |
| 显存占用 | 全链路峰值约 15GB |
能装的:NVIDIA 独立显卡、显存 16GB 以上、Windows 11、硬盘剩 40GB。
装不了的:AMD/Intel 核显、Mac、没有独立显卡的轻薄本。所有模型推理都依赖 CUDA,这个绕不过去。
会很痛苦的:8GB 显存。能跑,但语音和数字人不能同时开,得来回切。
如果你只是想看看这东西长什么样,不用装——项目 README 里有抖音演示链接。装它是为了自己用,不是为了围观。
看一张图就够了:
┌────────────────────────────────────────────┐
│ 浏览器(就是那个聊天界面) │
│ │
│ [对话面板] [女友窗] │
│ 麦克风 ──▶ 文字 ──▶ 回答 ──▶ 声音 ──▶ 播放 │
│ ▲ │
└────────────────────────────────────┼────────┘
│
┌────────────────────────────────────┴────────┐
│ 语音桥接服务(本地 8765 端口) │
│ 负责:听写、合成语音、生成视频、收发 QQ │
└──────┬──────────────┬───────────────────────┘
│ │
┌──────▼──────┐ ┌────▼──────────────────┐
│ TTS 引擎 │ │ 数字人引擎 │
│ 9877 端口 │ │ 9000 端口 │
│ (跑在 WSL) │ │ (跑在 Docker) │
└─────────────┘ └───────────────────────┘
三层,各管一件事:
第一层,浏览器里的插件。 它只是一堆界面代码——麦克风按钮、开关、那个小窗口。它自己不会说话,所有声音都要找第二层要。
第二层,语音桥接服务。 一个跑在本地的 Python 程序,监听 8765 端口。它像一个转接台:收到浏览器发来的录音,转给识别模型;收到文字,转给合成模型。所有 API 都从这里出入。
第三层,真正的模型。 识别、合成、生成视频这些重活,各自由专门的程序干。它们吃显存,所以单独拆出来,出问题好换、好重启。
因为这三层坏一个,不影响其他两个。识别坏了你还能打字提问听语音;合成坏了你还能说话看文字。如果全塞在一个程序里,一个模型加载失败,整个系统就死了。
而且换引擎不用改界面。识别模型从 whisper 换成 FunASR,只改一行配置,浏览器那边完全不知道。
声音合成不在 Windows 上跑。 中间那个 9877 端口,实际跑在 Windows 里的 Linux 子系统(WSL2)中。听起来很折腾,但收益巨大:
| Windows 原生(Qwen3-TTS) | WSL 里跑(OmniVoice) | |
|---|---|---|
| 首轮出音 | 18.3 秒 | 1.6 秒 |
| 平时出音 | 2.2 秒 | 1.3 秒 |
| 占显存 | 7GB 常驻 | 5.3GB |
差了 11 倍的启动速度,值得折腾一次。
插件装了也可能没声音。 插件只有界面,语音能力全在第二、三层。这就是为什么必须先装主仓库的桥接,再装插件——很多人反过来做,装完发现"按钮能点但没反应",就是这个原因。
| 项目 | 要求 | 怎么查 |
|---|---|---|
| 系统 | Win10/11 64 位 | winver |
| 显卡 | NVIDIA,显存 ≥16GB | nvidia-smi |
| 硬盘 | 剩余 ≥40GB | 资源管理器 |
| 内存 | ≥16GB | 任务管理器 |
nvidia-smi 报"不是内部或外部命令",说明没有 N 卡或驱动没装。
显存怎么被吃掉的:
| 组件 | 占用 |
|---|---|
| 语音合成 | 5.3GB 空闲 ~ 11GB 峰值 |
| 语音识别 | ~1GB |
| 数字人 | 4~6GB |
| 同时工作 | ~15GB |
git --version
python --version # 3.10 以上
node --version # LTS 版
npm install -g pnpm
pnpm --version
下载地址:
不需要单独装 CUDA Toolkit,装依赖时会自带。
装完 Python 一定要开个新终端再验证,否则 PATH 没生效,会误判成没装。
| 阶段 | 内容 | 时间 |
|---|---|---|
| 一 | 环境体检 | 5 分钟 |
| 二 | 代码 + Python 环境 | 20 分钟 |
| 三 | 识别模型 | 15 分钟 |
| 四 | 语音合成引擎 | 30~90 分钟 |
| 五 | 声音素材 + 配置 | 10 分钟 |
| 六 | 服务验收 | 10 分钟 |
| 七 | 装插件 + 首次对话 | 25 分钟 |
| 八 | 数字人(可选) | 40 分钟 |
| 九 | QQ(可选) | 20 分钟 |
最小可用只要一到七,走完就能语音对话。
| 端口 | 谁在用 |
|---|---|
| 8765 | 语音桥接服务(核心) |
| 9877 | 语音合成引擎 |
| 3092 | 聊天界面(DSH 0.2.1) |
| 9000 | 数字人引擎 |
| 3000 | QQ 机器人接口 |
| 6099 | QQ 机器人管理页 |
六个端口,装完记不住没关系,出问题时回来看这张表。
跑一遍第二部分的检查命令,五个都有版本号、nvidia-smi 能看到显卡,就往下走。
cd D:\
git clone https://github.com/beiyege-01/dsh-voice-ai-girlfriend.git
cd dsh-voice-ai-girlfriend
后面所有命令都在这个文件夹里执行。
建虚拟环境(相当于给这个项目单独开一个干净的 Python 房间,不影响系统里其他 Python 程序):
python -m venv venv-speech
venv-speech\Scripts\activate
激活成功时,终端行首会出现 (venv-speech)。
文件夹名必须是
venv-speech。 后面的一键启动脚本里写死了这个路径,改成别的名字脚本就找不到 Python 了。
装依赖:
pip install -r bridge\requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
会下载 PyTorch 一整套,5~8GB,几分钟到半小时。国内不走清华镜像基本装不完。
你会看到:最后一行出现 Successfully installed ... 一长串包名。
怎么验证装对了:
venv-speech\Scripts\python.exe -c "import torch; print(torch.__version__, torch.cuda.is_available())"
输出末尾必须是 True。如果是 False,说明装成了 CPU 版或者显卡驱动太旧,这时候不要往下走——后面所有模型都会跑在 CPU 上,慢到不可用。
语音识别用阿里 FunASR 的中文模型,约 850MB。它识别中文比 whisper 准得多:
| 模型 | 同音字错误(测三句古诗) | 准确率 |
|---|---|---|
| whisper-turbo | 尽→静、目→木、楼→牛、庵→安、钱→前 | 86% |
| FunASR | 五处全部纠正,只剩 2 处音近错 | 93% |
下载:
pip install modelscope -i https://pypi.tuna.tsinghua.edu.cn/simple
# 先下到缓存
python -c "from modelscope import snapshot_download; snapshot_download('iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch')"
# 再拷进项目的 models 目录
mkdir models\funasr
xcopy /E /I "%USERPROFILE%\.cache\modelscope\models\iic--speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch\snapshots\master" "models\funasr\speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch"
还要一个小的:silero VAD(2MB,用来判断"你什么时候在说话",打断功能靠它)。从 silero-vad v4.0 拿 files/silero_vad.jit,放到:
models/silero-vad/silero_vad_v4.jit
一定要 v4 版本。 v5 带了额外组件,在新版 PyTorch 下会随机报错(这个项目实测过:同一个文件下午正常、晚上报错),排查起来很折磨。
你会看到:models/ 下出现两个文件夹。
models/
├── funasr/
│ └── speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch/
└── silero-vad/
└── silero_vad_v4.jit ← 约 1.4MB
这是全程唯一有难度的环节。 两条路,先看区别再选:
| 路线 A:OmniVoice | 路线 B:Qwen3-TTS | |
|---|---|---|
| 首轮出音 | 1.6 秒 | 18.3 秒 |
| 平时出音 | 1.3 秒 | 2.2 秒 |
| 占显存 | 5.3GB | 7GB |
| 音色 | 克隆 + 参数设计 | 只能克隆 |
| 难度 | 高(要配 WSL + CUDA) | 低(一条命令) |
建议先走 B。 20 分钟跑通,验证整条链路能出声,再升级到 A。这样卡住时你清楚问题在哪一层。直接上 A 卡住的话,你分不清是界面问题、桥接问题还是模型问题。
pip install modelscope -i https://pypi.tuna.tsinghua.edu.cn/simple
modelscope download --model Qwen/Qwen3-TTS-12Hz-1.7B-Base --local_dir D:\models\Qwen3-TTS-12Hz-1.7B-Base
必须是
-Base版本。 名字带VoiceDesign的那个不支持参考音频克隆,装了也克隆不出你的声音。
记下 D:\models\Qwen3-TTS-12Hz-1.7B-Base 这个路径,第五步要填进配置。
先在 Windows 里装 Linux 子系统:
wsl --install -d Ubuntu-22.04
装完重启,设好 Linux 用户名密码。验证 WSL 里能看见显卡:
wsl -d Ubuntu-22.04 -- nvidia-smi
然后进 WSL 里干活:
wsl -d Ubuntu-22.04
mkdir -p ~/omnivoice-wsl && cd ~/omnivoice-wsl
# 装 uv(比 pip 快很多)
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.local/bin/env
uv venv --python 3.12
source .venv/bin/activate
# PyTorch,必须是 CUDA 13 系列
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu130
# FlashInfer 加速(这是 11 倍提速的来源)
uv pip install flashinfer-python flashinfer-jit-cache --extra-index-url https://flashinfer.ai/whl/cu130
# nvcc,版本检测要用
uv pip install nvidia-cuda-nvcc-cu13
# 引擎本体
uv pip install omnivoice
这一段的源地址和包名会随版本变化。 如果某一行报 404 或找不到包,去 flashinfer 官方安装页 抄当前的 cu13 地址,不要硬扛。
这一步失败很正常,也不影响你能用。 退回路线 B 就行,只是首轮慢 10 倍、音色不能参数设计,日常对话完全够用。
把服务脚本拷到 WSL 家里(仓库已经自带了,不用手写):
cp -f /mnt/d/dsh-voice-ai-girlfriend/bridge/omnivoice/demo_flashinfer.py \
/mnt/d/dsh-voice-ai-girlfriend/bridge/omnivoice/start_server.sh \
~/omnivoice-wsl/
chmod +x ~/omnivoice-wsl/start_server.sh
路径按你项目实际位置改。或者直接从 Windows 跑
bridge\start-omnivoice-wsl.ps1,它会自动部署。
启动(Windows 侧一键,会检测、部署、启动、等就绪、同步配置):
powershell -ExecutionPolicy Bypass -File bridge\start-omnivoice-wsl.ps1
你会看到:
curl.exe -s -o NUL -w "%{http_code}" http://127.0.0.1:9877/
返回 200 就活了。日志在 WSL 的 ~/omnivoice-wsl/server.log。
有个隐蔽的坑:WSL 系统里如果自带 nvcc 12.8,会干扰 FlashInfer 的版本检测,服务直接起不来。
start_server.sh里已经帮你设了CUDA_HOME指向 CUDA 13 的 nvcc,别去动它。
AI 的声音是克隆来的,你给什么它像什么。
| 要求 | 说明 |
|---|---|
| 时长 | 10 秒左右(5~20 秒都行) |
| 内容 | 干净人声,不能有背景音乐、不能有杂音 |
| 说什么 | 用你自己的话自然读一句,不要跨句截取 |
| 文件 | 命名 ref_audio.wav,放项目根目录(和 bridge/ 文件夹同级) |
跨句截取会让合成结果混进参考内容——不是理论风险,实测踩过。
voices/
└── 小雅/ ← 文件夹名就是音色名
├── ref_audio.wav ← 你刚录的那段
└── ref_text.txt ← 这段录音逐字对应的文字(UTF-8 编码)
ref_text.txt 必须和录音逐字一致,标点也要对。文本对不上,音色就飘。
想换声音就多建几个文件夹,界面上有个 🎙️ 按钮可以随时切。
copy bridge\bridge-config.example.json bridge\bridge-config.json
用记事本打开 bridge\bridge-config.json,改四处。
① 跨域白名单 —— 最容易漏,漏了必失败
"cors_origins": [
"http://127.0.0.1:3092",
"http://localhost:3092",
"http://127.0.0.1:3080",
"http://localhost:3080"
]
模板里只有 3080,但新版聊天界面跑的是 3092。不改这里,浏览器会拦住请求,表现是:语音按钮能点、没声音、控制台一堆红字。这个坑排查起来极其浪费时间,因为报错不会指向真正原因。
② 合成引擎地址
路线 A:
"omnivoice": { "base": "http://172.x.x.x:9877" }
填 WSL 的内网 IP(wsl hostname -I 查),或者让启动脚本自动同步。
路线 B:
"tts": {
"model_name": "D:/models/Qwen3-TTS-12Hz-1.7B-Base",
"ref_text": "你录的那句话"
}
③ 默认音色
"persona": { "default_voice": "小雅" }
填 voices/ 下的文件夹名。
④ 数字人端口(第八步才用)
"digital_human": { "duix_base": "http://127.0.0.1:9000" }
是 9000,不是 8383。 网上不少教程照抄镜像里写的 8383,会启动失败——Windows 保留端口段 8347-8446 正好包含 8383,映射不上去。项目里已经改成 9000。
其他配置不用动,都是相对路径自动解析。
先把"声音服务"单独跑通,再去装插件。 这一步省掉,后面出问题你分不清是插件坏了还是服务坏了。
bridge\start-bridge.cmd
会弹一个小终端窗口,不要关它。
浏览器打开:
http://127.0.0.1:8765/api/health
你会看到:
{"status":"ok","stt":false,"tts":false,"stt_error":null,"tts_error":null}
false 是正常的,不是失败。 模型是"用的时候才加载",第一次调用才会载入。很多人看到两个 false 以为装坏了,其实一切正常——这两个值变成 true,只说明你之前调用过。
另开一个终端,在项目根目录、虚拟环境激活状态下:
venv-speech\Scripts\python.exe bridge\smoke_stt.py
对着麦克风说一句话。第一次要等 10~30 秒加载模型,之后每次 150 毫秒级。
venv-speech\Scripts\python.exe bridge\smoke_tts.py --text "你好,我是小雅。"
跑完项目根目录会生成 tts_out.wav,播放它。
听到克隆自你录音的声音,就成功了。 声音不像 → 回去检查 ref_text.txt 是否和录音逐字一致。
| 现象 | 原因 | 怎么办 |
|---|---|---|
| 8765 连不上 | 服务没起 | 看那个小终端窗口的报错 |
tts_error 有内容 |
模型路径错 / 驱动旧 | 检查路径的盘符和斜杠(正斜杠或双反斜杠都行);nvidia-smi 看驱动 |
| 合成引擎连不上 | WSL 服务没起 / IP 变了 | curl http://127.0.0.1:9877/ |
| 第一次合成等很久 | 正常 | 模型加载 + 预热,之后每句 0.5 秒 |
| 识别偶尔是空的 | FunASR 对超短语音或纯噪声返回空 | 正常防护,不是 bug |
| 显存不足 / OOM | 同时开了太多东西 | 关掉数字人,先只跑语音 |
验收标准:health 里 stt 和 tts 都变成 true,而且你确实听到了声音。
这个项目是 DSH(DeepSeek Harness,一个开源 AI 编码工具)的扩展,所以要先有一份 DSH:
git clone https://github.com/deepseek-ai/deepseek-harness.git D:\DSH\deepseek-harness-v021
cd D:\DSH\deepseek-harness-v021
pnpm install
记住这个路径,等一下和以后每天启动都要用。
dsh plugin --profile web-v021 add github:beiyege-01/dsh-voice-ai-girlfriend-plugin
三个容易踩的地方:
| 坑 | 说明 |
|---|---|
--profile 的位置 |
新版必须写在 add 前面,写后面会报参数错 |
| profile 名字 | 0.2.x 用 web-v021;0.1.3 用 web-v013;更旧的用 web |
| pnpm 版本太新 | 会限制构建脚本,按它打印的提示在 pnpm-workspace.yaml 里允许 esbuild,然后重跑 |
cd D:\DSH\deepseek-harness-v021
pnpm dsh --profile web-v021 --port 3092
浏览器打开它打印出来的带 token 的链接。
按顺序做四件事,哪一步不过就停在那查:
① 看控制台
按 F12 打开控制台,应该只有一行:
[ui-voice] loaded, bridge = http://127.0.0.1:8765
出现 CORS 相关的红字 → 回第五步改跨域白名单。
② 看工具行
输入框附近应该有这些控件:麦克风、⚡(插话)、👧(女友窗)、🎭(数字人)、🔊(朗读)、🎙️(音色)、🖼️(待机)、👤(形象)、QQ。
③ 打字试朗读
随便发一句"你好",等她回答完,应该听到语音。
听不到的话,先看 /api/health:
tts:true 但没声音 → 浏览器这边的问题(开多了标签页 / 音量 / 没点过页面——浏览器规定用户没交互过不许自动播音频)tts:false → 客户端根本没发请求,看控制台报错④ 点麦克风说话
点一下(变红脉冲)→ 说一句话 → 停顿 1.8 秒 → 自动识别上屏 → 她语音回答。
走到这里,最小可用版本就完成了。
界面上的按钮看着多,常用的就四个。
| 按钮 | 作用 | 什么场景用 |
|---|---|---|
| 麦克风 | 点一下开始连续听,再点停止 | 主要交互方式 |
| ⚡ | 亮=插话,灭=排队 | 见下面解释 |
| 🔊 | 开/关朗读 | 在办公室不想被听见 |
| 🎭 | 开/关数字人 | 想要口型视频 / 只想快点听 |
这是最容易搞混的一个设计,但它很关键。
插话(⚡ 亮):她正在回答,你开口说第二句,她会立刻停下当前回答来听你说。适合"我改主意了"、"不对,换个方向"。
排队(⚡ 灭):她正在回答,你说话,这句话会排队等她说完再处理。适合"我想到个补充"。
两种模式下,"你说话她立刻闭嘴"这个行为都保留——开关只管你说的话怎么投递,不管打断。
自动打断只在麦克风处于聆听状态时生效。如果你没开麦克风,手动点一下麦克风也能打断。
这个设计是为了省资源:不聆听的时候不占麦克风,也不做语音检测。
这是必须遵守的。
浏览器每个标签页都会独立加载一份插件、各自有一个播放器。开三个标签页,她一句话会被念三遍。
日志里能看到十几个 WebSocket 连接,那就是铁证。关掉多余的标签页就好了。
出问题别急着翻文档,按这个顺序问自己:
1. 桥接收到请求了吗? 看那个 8765 的小终端窗口。没收到 → 问题在浏览器这边(插件、跨域、配置)。收到了 → 问题在服务端。
2. 模型加载了吗? 看 /api/health 的 stt / tts。一直是 false → 模型从没被调用过,说明请求根本没到。
3. 端口通吗?
Test-NetConnection 127.0.0.1 -Port 8765
TcpTestSucceeded : True 才算通。
装的时候
| 症状 | 原因 | 解决 |
|---|---|---|
torch.cuda.is_available() 是 False |
装成 CPU 版 / 驱动旧 | 重装 cu 版 torch,更新显卡驱动 |
| pip 装依赖超时 | 网络 | 加 -i https://pypi.tuna.tsinghua.edu.cn/simple |
| 模型下载失败 | HuggingFace 被墙 | set HF_ENDPOINT=https://hf-mirror.com 后重试 |
| 一键脚本找不到 Python | 虚拟环境改名了 | 目录必须叫 venv-speech |
dsh plugin add 报参数错 |
--profile 位置错 |
写在 add 之前 |
| pnpm 拒绝执行构建脚本 | pnpm ≥10 的安全限制 | 在 pnpm-workspace.yaml 的 allowBuilds 里允许 esbuild |
用的时候
| 症状 | 原因 | 解决 |
|---|---|---|
| 按钮能点但完全没声音 | 跨域白名单少了 3092 | 改 cors_origins |
| 一句话被念好几遍 | 开了多个标签页 | 只留一个 |
| 她念完不回话 / 念到一半乱码 | 长了代码块/URL 被直接念 | 项目已内置文本清洗,若仍出现请提 issue |
| 环境音被识别成"幽灵消息" | 噪声门不够紧 | 项目默认 -35dB;风扇太近仍是物理限制 |
| 说着说着自己被打断 | 喇叭声音被麦克风录回去 | 项目已用 VAD 过滤,确认没有外放音箱贴太近 |
| 声音不像参考录音 | 文本和录音对不上 | 检查 ref_text.txt 逐字一致性 |
| 关掉朗读后显卡还在满载 | — | 已在服务端修掉,若仍出现说明版本旧 |
| 数字人一直"生成中" | 形象视频不在 temp 目录 | 检查 D:\duix_avatar_data\face2face\temp\ |
| 数字人服务起不来 | 端口映射错 | 必须 9000:8383 |
| QQ 收不到语音 | 给自己发语音 / 格式不对 | QQ 不支持自发语音;必须 silk 格式,桥接已内置转换 |
| QQ 消息进不来 | WebSocket 配成了服务器 | 必须配"WebSocket 客户端",地址 ws://127.0.0.1:8765/api/qq/onebot |
如果只记三件事,记这三条:
1. /api/health 里 false 不代表失败。 模型懒加载,第一次用才载入。看到 false 就去修东西,是纯浪费时间。
2. 跨域白名单必须含 3092。 模板里只有 3080,新版界面跑 3092。症状是"按钮能点、没声音",报错不会告诉你原因。
3. 数字人端口是 9000。 镜像文档写 8383,但 Windows 保留段占了它,映射会上不去。项目已改成 9000。
让回答变成一段对口型的视频。不开也完全能用,开了会更好看但更慢。
开之前准备好数据目录(必须在 D 盘,配置写死了):
mkdir -p D:\duix_avatar_data\face2face\temp
放一个正脸说话的 mp4 进去,命名 my_avatar.mp4。
提速 30% 的关键一步(画面不糊,只是帧数降低):
ffmpeg -i my_avatar.mp4 -vf fps=15 -c:v libx264 -crf 20 -preset fast -an my_avatar_15fps.mp4
改名覆盖 my_avatar.mp4(原版先备份)。实测 7.3 秒音频从 20.1 秒降到 14.1 秒。
再关超分(又能快 10%)。在 D:\duix_avatar_data\face2face\config.ini 里:
[digital]
chaofen = 0
batch_size = 2
起容器:
docker compose -f bridge\docker-compose-5060ti.yml up -d
冷启动要 1~3 分钟,别急着以为失败了。检查:
curl.exe "http://127.0.0.1:9000/easy/query?code=probe"
有 JSON 返回(哪怕是"任务不存在")就说明服务在线。
然后点界面上的 🎭 按钮,发一条长一点的消息。女友窗底部会显示"数字人生成中 1/3…",一段生成完立刻播,声音和口型是同步的(音频已经混进视频里了)。
每次开机要手动起三个东西,容易漏。用脚本固化。
先改脚本里的路径。 打开 bridge\start-full-stack.ps1,找到第 54 行附近这一块:
$LatestHarness = 'E:\DSH\deepseek-harness-v021' # ← 改成你的实际路径
$LatestProfile = 'web-v021'
$LatestPort = 3092
这几行是作者自己电脑的路径,不改在你机器上一定失败。
然后每天启动就两步:
bridge\start-full-stack.cmd脚本会按正确顺序把六个东西拉起来,最后打印一张自检表:
| 步骤 | 做什么 |
|---|---|
| 1/6 | 检查 Docker 和数字人容器 |
| 2/6 | 起合成引擎 + 同步配置 + 起桥接 |
| 3/6 | 桥接保险(起不来就修) |
| 4/6 | 起聊天界面 + 打开带 token 的链接 |
| 5/6 | 数字人状态 |
| 6/6 | 汇总自检表 |
常用参数:
bridge\start-full-stack.cmd -NoDsh # 只起语音,不开界面
bridge\start-full-stack.cmd -NoBrowser # 不开浏览器
bridge\start-full-stack.cmd -WithQQ # 顺便拉起 QQ 机器人
已经跑着的不会重复启动(按端口探活判断),随时可以重跑。
出门用手机 QQ 接着聊。
NapCat.Shell.Windows.Node.zip,解压到 D:\QQ\NapCatnapcat\launcher-win10-user.bat 启动(会关掉所有正在运行的 QQ,用小号登录)http://127.0.0.1:6099,token 在 napcat\config\webui.json127.0.0.1:3000,记下 tokenws://127.0.0.1:8765/api/qq/onebotqq 段,重启桥接"qq": {
"enabled": true,
"napcat_base": "http://127.0.0.1:3000",
"napcat_token": "你设的 token",
"target_qq": 你的主号QQ号
}
验证:
curl.exe -X POST http://127.0.0.1:8765/api/qq/send -H "Content-Type: application/json" -d "{\"text\":\"你好\",\"voice\":true}"
主号收到文本 + 语音就通了。
四个必踩的坑:
| 坑 | 原因 |
|---|---|
| 语音收不到 | QQ 不支持给自己发语音;格式必须是 silk(桥接已内置转换) |
| 消息进不来 | WebSocket 配成了"服务器",会抢 8765 端口 |
| 重启后接口丢了 | 必须在管理页里启用保存,不要 kill 进程 |
| 每次都要填 token | 改 webui.json 里的 token 为固定值 |
前面都是"怎么用"。这一节给想改的人——四条设计思路和几个值得看的文件。
1. 流式系统的关键不是"多快",是"什么时候开始"。
大模型输出、语音合成、播放、视频生成,四个环节速度差几十倍。把它们各自做成独立队列,语音合成完一句就播一句,不用等全文——用户感知到的就是快的那个。
2. 打断必须做三层,少一层都不彻底。
播放器停 → 客户端请求中止 → 服务端停止合成。只做前两层,会出现"听着停了,显卡还在满载"。第三层要用协作式取消:客户端断开连接后,服务端循环里定期检查标志位,发现没人听了就 break。
3. 状态判断用"精确 ID",不要用"区间"。
打断后要"吞掉本条回复的剩余部分"。写成"跳过所有序号小于等于当前的",在异步时序里一定会误伤——你以为是吞旧的,实际上新数据已经进来了。改成记具体那一条的 ID。
4. 阈值必须实测,不能拍脑袋。
-35dB 噪声门、1800ms 断句、48 字分段、384ms 打断确认——每个数字背后都是一次对比实验。特别是 dBFS 是负数,守卫条件写反了不会报错,只会静默失效。
| 想了解什么 | 看哪个文件 |
|---|---|
| 录音、断句、打断检测 | dsh-plugin/src/client/voice/recorder.ts |
| 播放队列 | dsh-plugin/src/client/voice/speaker.ts |
| 切句逻辑 | dsh-plugin/src/client/voice/sentences.ts |
| 流式朗读编排(最核心) | dsh-plugin/src/client/voice/reply-listener.tsx |
| 女友窗 + 视频续播 | dsh-plugin/src/client/voice/companion.tsx |
| 音频采集(48k→16k 降采样) | dsh-plugin/src/client/worklets/mic-capture.ts |
| 所有服务端接口 | bridge/voice_bridge.py |
降采样用三点平均,不用插值。
大多数 Windows 声卡是 48kHz,降到 16kHz 恰好是 3:1。直接三点取平均既快又能当抗混叠滤波器,省掉每个音频块 640 次浮点乘法。只有 44.1kHz 才走插值分支。
请求链和播放链分开。
请求(串行):第1句 ──▶ 第2句 ──▶ 第3句
│
播放(排队):第1句播完 ──▶ 第2句播完 ──▶ 第3句
↑ 第2句合成完先排队等
两条链速度不同,硬绑在一起就会互相拖累。分开之后,第 1 句在播的时候,第 3 句可能已经合成好了。
约 7100 行有效代码:
| 部分 | 行数 |
|---|---|
| TypeScript(界面) | ~3100 |
| Python(服务) | ~2100 |
| 文档 | ~1000 |
| 启动脚本 | ~400 |
打印出来逐项打勾:
环境
[ ] nvidia-smi 能看到显卡
[ ] git / python / node / pnpm 四个都有版本号
[ ] torch.cuda.is_available() == True
模型
[ ] models/funasr/ 下有模型文件
[ ] models/silero-vad/silero_vad_v4.jit 约 1.4MB
[ ] 合成引擎在线(9877 能访问,或 Qwen3 路径正确)
配置
[ ] ref_audio.wav 在项目根目录
[ ] voices/<音色名>/ 下有两个文件
[ ] cors_origins 里包含 3092
[ ] 虚拟环境目录叫 venv-speech
服务
[ ] /api/health 返回 status:ok
[ ] smoke_stt 能识别出文字
[ ] smoke_tts 能出声且音色正确
[ ] health 里 stt / tts 变成 true
界面
[ ] 控制台只有一行 [ui-voice] loaded
[ ] 工具行控件齐全
[ ] 打字能触发朗读
[ ] 点麦克风说话能上屏
固化
[ ] start-full-stack.ps1 里的 harness 路径已改成自己的
[ ] 自检表每一项都是 OK
1. 打开 Docker Desktop(只有用数字人才需要)
2. 双击 bridge\start-full-stack.cmd
3. 等自检表打印完
4. 浏览器打开(只开一个标签页)
dsh-voice-ai-girlfriend/
├── bridge/ # 语音服务(Python,可独立跑)
│ ├── voice_bridge.py # 所有接口都在这
│ ├── qq_bridge.py # QQ 相关
│ └── omnivoice/ # WSL 里的合成服务
├── dsh-plugin/ # 浏览器插件源码
│ ├── src/client/voice/ # 录音、播放、切句、女友窗
│ └── build.mjs # 打包脚本
├── assets/bg-images/ # 5 套内置待机动画
├── voices/ # 音色库(自己建)
├── models/ # 模型(自己下,不入库)
└── docs/ # 开发日志