简介:VirtualWife是一个开源的虚拟数字人项目,面向AI开发者、直播技术实践者及数字人兴趣爱好者,旨在降低B站虚拟直播的技术门槛,提供可快速部署、可定制的本地化交互方案。资源包共246个文件,涵盖97个Python核心逻辑脚本(含LLM对接、语音驱动、动作合成模块)、40个TypeScript/17个TSX前端交互组件(直播间UI与VRM渲染控制)、16个FBX/8个VRM三维模型资源、以及Dockerfile、多套Nginx配置(chatbot.conf/chatvrm.conf等)和启动/停止脚本(start.bat/stop.bat),完整支撑从模型加载、大模型对话(OpenAI/OLLAMA双后端)、实时语音驱动到B站推流的全链路流程。压缩包大小为130.2MB,结构清晰,模块解耦度高,附带详细README与环境配置说明(.env_example、dockerfile、yaml配置)。目前已有653人学习下载,适合希望深入理解虚拟人直播架构、复用成熟工程框架或二次开发个性化数字人的中高级开发者。
1. VirtualWife 是什么?不是“AI女友”玩具,而是一套可落地的本地化虚拟人直播工作流
你刷到过那种B站直播间里永远在线、语气自然、能接弹幕还能讲冷笑话的“数字人主播”吗?VirtualWife.zip 这个资源,就是把这类效果从“演示视频”拉进你本地电脑的真实入口——它不依赖云端API调用渲染,不强制绑定某家大模型服务,核心逻辑跑在你自己的显卡上。项目本质是:一个轻量级、模块解耦的虚拟人驱动框架,用 Python + PyTorch 构建底层通信层,通过 WebSocket 桥接 OBS(或同类推流工具),把 LLM 的文本输出实时驱动为口型同步、表情微动、肢体节奏可控的 2D/3D 虚拟形象。它真正解决的是“中小团队想试水虚拟人直播但买不起动辄上万的商业中台”的痛点。适合三类人:想快速验证直播话术与人设匹配度的内容运营、需要本地化部署规避数据外泄风险的教育类机构、以及正在做 AIGC 工具链集成的开发者。注意:它不提供现成的高精度3D模型或语音克隆服务,而是给你一套“怎么把 openai/ollama 的 response 变成 OBS 里能播的画面”的完整 glue code —— 这恰恰是网上90%教程缺失的关键一环。
2. 架构拆解与选型依据:为什么用 WebSocket 而非 RTMP?为什么绕开 Unity?
2.1 整体通信链路:从弹幕到口型的四段式流转
VirtualWife 的设计哲学是“最小侵入、最大复用”。它不重写 OBS,也不自建渲染引擎,而是把自身定位为“OBS 插件的协作者”和“LLM 的翻译官”。整个数据流分四段:
- 输入端:B站弹幕通过官方 WebHook 或第三方弹幕抓取 SDK(如
bilibili-api)实时接入; - 决策端:用户配置的 LLM(OpenAI API 或本地 ollama 模型)处理弹幕语义,生成回复文本;
- 驱动端:文本经规则引擎(含关键词触发、情绪映射表、停顿插入器)转化为带时间戳的动作指令序列(如
{"mouth":"open","duration":0.3,"emotion":"smile"}); - 输出端:指令通过 WebSocket 推送给 OBS 中运行的
obs-websocket插件,由其控制预设的虚拟人源(图片序列/Spine动画/Blender Live Link)播放对应帧。
提示:这种设计意味着你完全可以用自己训练的 TTS 模型替换默认语音合成,只要输出音频文件路径符合约定格式(
./audio/{timestamp}.wav),后续流程自动衔接。
2.2 为什么弃用 RTMP 直推?WebSocket 是更稳的“神经突触”
很多新手会疑惑:既然最终要推流到B站,为什么不直接用 FFmpeg 把虚拟人画面编码后 RTMP 推送?原因有三:
- 延迟不可控:RTMP 封包+编码+网络抖动导致端到端延迟常超 3 秒,弹幕互动感归零;
- 状态难同步:无法实时感知 OBS 当前场景切换、音轨开关等状态,容易出现“嘴在动但没声音”;
- 调试黑盒化:一旦推流失败,需同时排查 FFmpeg 参数、CDN 配置、OBS 输出设置三层问题。
而 WebSocket 方案将控制权收归本地:OBS 只负责“执行”,VirtualWife 只负责“发令”,两者通过 JSON 指令交互,每条指令带request_id和timeout_ms,失败时可重试或降级为静态表情。实测在 i5-1135G7 + RTX3050 笔记本上,从弹幕捕获到口型启动平均耗时 420ms(含 ollama 本地推理),比纯 RTMP 方案稳定 3.2 倍(基于 1000 条弹幕压力测试)。
2.3 为什么不用 Unity/Unreal?Python 生态已足够“够用”
项目未引入大型游戏引擎,核心考量是工程落地效率:
- 启动成本低:Python 环境一键安装,OBS 插件市场已有成熟
obs-websocket,无需额外编译 C++ 插件; - 调试友好:所有动作指令可在终端实时打印(
--debug-mode参数开启),配合 OBS 的“源预览”功能,能秒级验证口型帧是否对齐; - 模型兼容性好:ollama 支持的 Llama3、Qwen2、Phi-3 等模型,通过
ollama run <model>即可调用,无需转换为 ONNX 或 TensorRT 格式。
当然,这不是否定引擎方案的价值——如果你需要物理级布料模拟或复杂光照,Unity 仍是首选。但对“让虚拟人开口说话并回应弹幕”这个具体目标,VirtualWife 的技术栈选择是经过真实项目验证的性价比最优解。
3. 快速启动:从解压到首播的六步实操(含关键参数说明)
3.1 环境准备:Python 3.10+ 与 OBS 插件安装
# 创建隔离环境(推荐,避免包冲突) python -m venv vwife_env source vwife_env/bin/activate # Linux/macOS # vwife_env\Scripts\activate.bat # Windows # 安装核心依赖(requirements.txt 已预置在 zip 包根目录) pip install -r requirements.txt # 安装 OBS WebSocket 插件(必须!否则无法控制画面) # 访问 https://github.com/obsproject/obs-websocket/releases 下载对应 OBS 版本的插件 # Windows:下载 obs-websocket-5.1.0-Windows-Installer.exe,双击安装 # macOS:下载 obs-websocket-5.1.0-macOS.dmg,拖入 Applications # Linux:编译安装(详见插件文档),或使用 snap install obs-studio --classic逻辑说明:
requirements.txt中obs-websocket-python==5.1.0与插件版本严格对应,若版本错配会导致连接拒绝。pydub用于音频切片,websockets用于建立长连接,bilibili-api默认启用弹幕监听(需申请 B站开发者 key)。
3.2 配置 LLM 接入:OpenAI 与 ollama 的双模式切换
编辑config.yaml文件,关键字段如下:
llm: provider: "ollama" # 可选 "openai" 或 "ollama" openai: api_key: "sk-xxx" # OpenAI 官方密钥 model: "gpt-4o-mini" # 支持 streaming 的模型 ollama: host: "http://localhost:11434" # ollama 服务地址 model: "qwen2:1.5b" # 本地已拉取的模型名(执行 ollama list 查看) prompt: system: "你是一个活泼的二次元虚拟主播,用中文回答,每句话不超过20字,带emoji。" user_prefix: "观众说:" assistant_prefix: "你回复:"参数说明:
provider切换决定整个推理链路走向;ollama.host必须与ollama serve启动地址一致(默认127.0.0.1:11434);system提示词直接影响回复风格,实测qwen2:1.5b在 6GB 显存下可稳定运行,响应速度约 1.2s/token。
3.3 虚拟人源准备:支持三种格式的“即插即用”
OBS 中需提前创建以下三类源(任选其一):
| 类型 | 要求 | VirtualWife 控制方式 |
|---|---|---|
| 图片序列 | 命名为mouth_001.png~mouth_010.png的 10 帧 PNG,存于assets/mouth/ | 发送{"type":"image_sequence","frame":3}切换至第3帧 |
| Spine 动画 | 导出为spine.json+atlas+png,路径assets/spine/ | 发送{"type":"spine","animation":"talk"}播放 talk 动画 |
| Blender Live Link | Blender 4.2+ 启用blender-obs-link插件,创建空对象vocalizer | 发送{"type":"blender","property":"vocalizer.mouth_open","value":0.8} |
注意:首次使用 Spine/Blender 方案需在 OBS 中添加对应源,并勾选“启用 WebSocket 控制”。图片序列最简单,适合快速验证;Spine 适合 2D 手绘风;Blender 适合需要骨骼绑定的 3D 模型。
3.4 启动服务与 OBS 连接
# 启动 VirtualWife 主程序(后台运行) python main.py --config config.yaml --debug-mode # 终端将输出类似: # [INFO] Connected to OBS at ws://127.0.0.1:4444 # [INFO] Bilibili live room 123456 connected # [INFO] LLM provider: ollama, model: qwen2:1.5b逻辑说明:
main.py启动后自动连接 OBS WebSocket(默认端口 4444),并初始化弹幕监听器。--debug-mode会打印每条弹幕原始内容、LLM 输入 prompt、生成的指令 JSON,是排查“嘴不动”“答非所问”问题的第一现场。
3.5 OBS 场景配置:三步完成“虚拟人开口”
- 添加虚拟人源:在 OBS “来源”面板点击
+→ 选择对应类型(如“图像序列”)→ 设置路径为assets/mouth/; - 启用 WebSocket 控制:右键该源 → “属性” → 勾选
Enable WebSocket control; - 设置推流参数:设置 → 推流 → 服务选“自定义”,服务器填
https://live-push.bilivideo.com,流密钥从B站直播间后台复制。
关键验证点:启动 VirtualWife 后,在 OBS 日志窗口(帮助 → 日志 → 打开日志目录)搜索
websocket,应看到Received message: {"type":"image_sequence","frame":5}类似记录,证明指令已送达。
3.6 首播测试:用弹幕触发第一次口型同步
向直播间发送弹幕你好,观察:
- 终端是否打印
Received danmaku: 你好; - 是否调用 LLM 并输出
LLM response: 你好呀~(๑•̀ㅂ•́)و✧; - OBS 中虚拟人源是否在 0.5 秒内切换至
mouth_005.png(对应“啊”口型); - 是否生成
./audio/1712345678.wav音频文件并被 OBS 自动加载为音轨。
若仅文字回复无口型,大概率是 OBS 源未勾选
Enable WebSocket control;若口型错位,检查assets/mouth/下 PNG 命名是否连续(不能缺007)。
4. 避坑指南:五个血泪经验换来的常见问题排查清单
4.1 现象:OBS 连接成功,但弹幕后虚拟人毫无反应
原因:OBS WebSocket 插件虽运行,但 VirtualWife 发送的指令未被源识别。常见于两种情况:
- 源类型选择错误(如用“图像”源加载了 Spine 动画,但未安装 Spine 插件);
- OBS 场景中该源被禁用(眼睛图标关闭)或置于其他场景未激活。
解决:在 OBS 中右键虚拟人源 → “高级” → “重新加载源”;确认当前场景中该源可见且启用;检查config.yaml中llm.provider是否拼写错误(如ollma少字母)。
4.2 现象:LLM 回复正常,但口型帧始终停留在第一张
原因:动作指令中的frame字段未按实际 PNG 数量动态计算。VirtualWife 默认将文本长度映射为帧号(如 5 字 →frame:5),但若assets/mouth/只有 8 张图,而指令发frame:12,OBS 会静默丢弃。
解决:修改driver/mouth_driver.py中get_frame_by_text_length()函数,增加边界判断:
def get_frame_by_text_length(text: str) -> int: frame = min(len(text), 8) # 8 为 mouth/ 下实际 PNG 数量 return max(1, frame) # 确保不小于 14.3 现象:B站弹幕能收到,但部分弹幕被跳过(尤其含 emoji 或特殊符号)
原因:bilibili-api默认对弹幕内容做 HTML 解码,而某些 emoji 会被转义为\uXXXX格式,导致 LLM 提示词污染。例如观众说:👍可能变成观众说:\U0001f44d。
解决:在danmaku/bilibili_listener.py的on_danmaku()方法中添加清洗:
import re def clean_danmaku(text: str) -> str: # 移除 Unicode 转义,保留可见 emoji text = re.sub(r'\\U[0-9a-fA-F]{8}', '', text) text = re.sub(r'\\u[0-9a-fA-F]{4}', '', text) return text.strip()4.4 现象:ollama 模型响应极慢(>10s),CPU 占用 100%
原因:ollama run默认使用 CPU 推理,未启用 GPU 加速。qwen2:1.5b 在 CPU 上 token 生成速度约 3 token/s,而 RTX3050 可达 42 token/s。
解决:重启 ollama 服务并指定 GPU:
# Linux/macOS OLLAMA_NUM_GPU=1 ollama serve # Windows(PowerShell) $env:OLLAMA_NUM_GPU="1"; ollama serve验证:访问
http://localhost:11434/api/tags,返回 JSON 中details.families应包含"cuda"。
4.5 现象:OBS 音频轨有声音,但虚拟人口型不同步(嘴快声慢/嘴慢声快)
原因:TTS 生成的 WAV 时长与口型帧持续时间未对齐。VirtualWife 默认按文本字数分配口型帧(每字 0.2s),但 TTS 实际耗时受语速、停顿影响。
解决:启用音频时长校准模式,在config.yaml中添加:
tts: enable_duration_calibration: true # 启用后自动读取 WAV 元数据 base_duration_per_char: 0.15 # 基础字数时长(秒) mouth_open_ratio: 0.6 # 口型张开占总时长比例程序会解析pydub.AudioSegment.from_file().duration_seconds,动态调整帧切换节奏。
5. 进阶技巧:用规则引擎实现“情绪驱动口型”,让虚拟人真正“活”起来
5.1 情绪-口型映射表的设计逻辑
单纯按字数驱动口型,会让虚拟人像复读机。真正的“生动感”来自情绪与生理反应的耦合:人开心时嘴角上扬幅度大、语速快;悲伤时语速慢、闭口时间长。VirtualWife 内置emotion_mapper.py,将 LLM 返回的response文本经正则匹配打上情绪标签,再映射为口型参数:
| 情绪类型 | 触发关键词 | 口型张开幅度 | 帧切换速度 | 附加动作 |
|---|---|---|---|---|
happy | 😄、哈哈、耶、太棒 | 0.8~1.0 | 快(0.15s/帧) | 额外播放blink_happy.mp4 |
sad | 😢、呜呜、难过 | 0.2~0.4 | 慢(0.3s/帧) | 帧序列切换为mouth_sad_*.png |
angry | 😠、气死、讨厌 | 0.6~0.9 | 快(0.1s/帧) | 播放head_shake.gif |
neutral | 无匹配 | 0.4~0.6 | 中(0.2s/帧) | 无 |
关键实现:
emotion_mapper.py不依赖外部 NLP 库,用 12 行正则覆盖 92% 的弹幕情绪表达,避免引入transformers带来的内存暴涨。
5.2 自定义情绪规则:三步扩展你的专属人设
假设你要打造一个“毒舌程序员”人设,希望他说“bug”时翻白眼,说“需求”时扶额。只需修改config/emotion_rules.yaml:
- name: "dev_sarcastic" trigger: ["bug", "需求", "改一下"] emotion: "sarcastic" mouth_sequence: ["mouth_sarcastic_01.png", "mouth_sarcastic_02.png"] actions: - type: "overlay" source: "eye_roll.gif" duration: 1.2 - type: "text" content: "又来?" position: [0.5, 0.3] # x,y 归一化坐标然后在main.py中注册该规则:
from driver.emotion_mapper import EmotionMapper mapper = EmotionMapper(rules_path="config/emotion_rules.yaml") # 后续调用 mapper.get_emotion(response) 即可返回定制化指令5.3 实时调试:用--dry-run模式跳过硬件操作
开发新规则时,反复启停 OBS 极其耗时。VirtualWife 提供--dry-run参数:
python main.py --config config.yaml --dry-run此时程序只打印:
[DryRun] Would send to OBS: {"type":"image_sequence","frame":7} [DryRun] Would play audio: ./audio/1712345678.wav [DryRun] Would show overlay: eye_roll.gif不建立 WebSocket 连接,不调用 ollama,不生成音频,纯逻辑验证。我一般在写完新规则后,先--dry-run跑 10 条测试弹幕,确认输出指令符合预期,再关掉 dry-run 正式联调。
5.4 性能压测:单机支撑 5000 人直播间的关键参数
在某高校线上讲座项目中,我们用 VirtualWife 承载了峰值 5200 人的 B站直播间。关键优化点全在config.yaml:
danmaku: batch_size: 5 # 每次批量处理 5 条弹幕(降低 LLM 调用频次) cooldown_ms: 800 # 同一用户 800ms 内重复弹幕过滤 llm: stream_response: true # 启用流式响应,边生成边驱动口型 tts: cache_enabled: true # 同一句子命中缓存,跳过 TTS 重生成 obs: websocket_retry: 3 # 连接失败重试 3 次,避免瞬时抖动中断实测数据:启用
batch_size:5后,ollama 调用次数下降 63%,GPU 显存占用稳定在 3.2GB(RTX3050),CPU 占用从 95% 降至 42%。
从那以后我每次上线新直播间,都强制走一遍--dry-run+--debug-mode的组合验证,哪怕只是改了一个标点。因为虚拟人直播的“翻车”往往不在代码,而在那条没被 catch 的 emoji 转义,或那个少写了一位的帧编号。希望帮到你。
本文还有配套的精品资源,点击获取