1 张 GPU 卡 + 1 台 NUC → 双屏独立数字人讲解 + PPT 叠加 + 观众互动问答
企业展厅和产品发布会上,传统的 PPT 讲解依赖真人讲解员,存在排班成本高、讲解水平参差不齐、无法 7×24 运行等问题。随着实时数字人技术的成熟,我们尝试用 AI 数字人替代真人讲解员,实现产品 PPT 的自动化讲解。
核心需求:
经过多轮方案对比,最终确定了 "NUC 控制中心 + A100 单卡推理" 的精简架构:
┌─────────────────────────────────────────────────────────────┐
│ A100 服务器(仅占用 1 张卡,其余 7 张留给大模型) │
│ │
│ GPU 7(80GB) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ LiveTalking + MuseTalk ≈ 6GB │ │
│ │ ├── session_1 → 数字人 A(屏1) │ │
│ │ └── session_2 → 数字人 B(屏2) │ │
│ │ CosyVoice TTS ≈ 3GB │ │
│ │ 合计 ≈ 9GB / 80GB │ │
│ └───────────────────────────────────────────────────────┘ │
│ GPU 0-6: 其他大模型(不动) │
└─────────────────────────┬───────────────────────────────────┘
│ 局域网(WebRTC)
┌─────────────────────────▼───────────────────────────────────┐
│ NUC(i9 / 16GB / Ubuntu) │
│ ├── 调度引擎 + WebSocket + FunASR CPU + Nginx ≈ 3GB │
│ ├── 浏览器标签页① → HDMI → 大屏1(数字人A + PPT叠加) │
│ └── 浏览器标签页② → Type-C → 大屏2(数字人B + PPT叠加) │
│ 内存合计 ≈ 6GB / 16GB │
└─────────────────────────────────────────────────────────────┘
这是方案精简的关键洞察:NUC 是一台电脑接两块屏,不是两台电脑。LiveTalking 天然支持多会话(--max_session),一个进程可以同时服务多个 WebRTC 连接。两个浏览器标签页各自建立独立的 session,共享同一套模型权重,不额外占显存。
类比:就像一个视频网站服务器同时给 100 个用户推流,LiveTalking 也是同样的道理——模型只加载一次,推理时按 session 分发。
| 层级 | 选型 | 选型理由 |
|---|---|---|
| 数字人驱动 | LiveTalking + MuseTalk | 开源、实时、支持多会话、中文效果好 |
| 语音合成 | CosyVoice | 支持声音克隆、中文自然度高 |
| PPT 渲染 | reveal.js | Web 原生、支持动画、WebSocket 可控 |
| 绿幕抠像 | WebGL Shader | GPU 加速、零延迟、浏览器原生 |
| 语音识别 | FunASR(CPU 模式) | Nano 模型仅 500MB 内存、1.5x 实时 |
| 知识问答 | RAG(已有部署) | 直接调 API,不额外占算力 |
| 调度引擎 | Python asyncio | 异步并发、双屏独立编排 |
传统方案是数字人和 PPT 各占半屏,效果生硬。我们采用全屏叠加——数字人以透明前景的形式浮在 PPT 页面上方,类似直播带货的画中画效果。
浏览器全屏(1920×1080) ┌─────────────────────────────┐ │ z:1 ┌───────────────────┐ │ │ │ PPT 背景层 │ │ ← reveal.js 渲染 │ │ (全屏铺满) │ │ │ └───────────────────┘ │ │ z:2 ┌──────────┐ │ │ │ 数字人前景 │ │ ← WebRTC 视频流 │ │(绿幕抠像) │ │ 位置可配置 │ └──────────┘ │ │ z:3 ┌───────────────────┐ │ │ │ 实时字幕条 │ │ ← ASR 转写 │ └───────────────────┘ │ └─────────────────────────────┘
LiveTalking 支持输出绿幕背景的视频流。浏览器端通过 WebGL Fragment Shader 实时做绿幕透明处理,性能远优于逐像素 Canvas 操作:
precision mediump float;
uniform sampler2D u_video; // 数字人视频(绿幕背景)
uniform sampler2D u_ppt; // PPT 背景
varying vec2 v_texCoord;
void main() {
vec4 video = texture2D(u_video, v_texCoord);
vec4 ppt = texture2D(u_ppt, v_texCoord);
// 在 HSV 色彩空间检测绿色
float maxC = max(video.r, max(video.g, video.b));
float minC = min(video.r, min(video.g, video.b));
float diff = maxC - minC;
float hue = 0.0;
if (diff > 0.01 && maxC == video.g) {
hue = 60.0 * (2.0 + (video.b - video.r) / diff);
}
// 绿幕区域 → 显示 PPT 背景
float isGreen = step(60.0, hue) * step(hue, 180.0)
* step(0.15, diff) * smoothstep(0.25, 0.45, video.g);
gl_FragColor = mix(ppt, video, 1.0 - isGreen);
}
根据 PPT 内容动态调整数字人位置,避免遮挡关键信息:
// 讲解脚本中定义每页的数字人位置
{
"slide": 3,
"title": "数据治理体系架构",
"narration": "请看这张架构图...",
"avatar_position": "right-bottom", // 不遮挡左侧架构图
"avatar_size": "medium"
}
这是本方案最核心的设计决策。在算力紧张的情况下,我们通过三个手段把资源占用压到最低:
LiveTalking 的 --max_session 参数允许单个进程同时服务多个 WebRTC 会话。两个数字人共享同一套 MuseTalk 模型权重,显存占用从 12GB(两个独立进程)降到 6GB(一个进程两路会话)。
FunASR Nano 模型专门为 CPU 优化:INT8 量化后仅 100MB,内存占用 500MB 以下,识别速度达 1.5 倍实时。NUC 的 i9 处理器完全够用,零 GPU 占用。
CosyVoice 占 2-4GB 显存,和数字人的 6GB 放在同一张 A100 80GB 卡上,总共才用 9GB,对大模型的运行几乎无影响。
| 服务 | 端口 | 内存 | 说明 |
|---|---|---|---|
| Nginx | 80 | ~50MB | PPT HTML 托管 + API 反向代理 |
| WebSocket Server | 9090 | ~100MB | PPT 翻页控制指令推送 |
| Orchestrator | 8080 | ~300MB | 双屏讲解调度引擎 |
| FunASR | 10095 | ~500MB | 语音识别(CPU 推理) |
| Chromium × 2 | — | ~3GB | 两个全屏 kiosk 标签页 |
联动翻页的核心思路:PPT 转成 Web 页面,用同一个调度引擎同时控制数字人和 PPT。
采用 JSON 格式定义每页的讲解内容、数字人行为和问答策略:
{
"slides": [
{
"id": 1,
"title": "产品概述",
"narration": "欢迎来到我们的产品展示...",
"avatar_position": "right-bottom",
"avatar_size": "medium",
"post_qa": true,
"qa_context": "产品概述"
},
{
"id": 2,
"title": "核心架构",
"narration": "接下来介绍核心架构设计...",
"avatar_position": "left-bottom",
"post_qa": true,
"qa_context": "技术架构"
}
]
}
async def play_slide(orchestrator, slide):
"""单页讲解流程"""
# 1. PPT 翻页(通过 WebSocket 推送到浏览器)
await orchestrator.goto_slide(slide["id"])
await asyncio.sleep(0.8) # 等翻页动画完成
# 2. 调整数字人位置
await orchestrator.position_avatar(slide["avatar_position"])
# 3. 数字人开始讲解(调用 LiveTalking /human API)
orchestrator.narrate(slide["narration"])
await orchestrator.wait_narration_end()
# 4. 可选:进入观众问答
if slide.get("post_qa"):
orchestrator.narrate("大家有什么问题吗?")
question = await orchestrator.listen(timeout=45)
if question:
answer = orchestrator.call_rag(question, slide["qa_context"])
orchestrator.narrate(answer)
# 5. 自动翻到下一页
| 接口 | 说明 |
|---|---|
POST /offer | WebRTC 协商,获取数字人视频流 |
POST /human | 发送文字驱动数字人说话(echo=播报 / chat=对话) |
POST /interrupt_talk | 打断数字人当前说话 |
POST /set_audiotype | 切换数字人播放的自定义视频 |
互动问答是数字人讲解系统的"灵魂"——让展示从单向播报变成双向对话。
观众提问 → 麦克风采集 → FunASR 语音识别(CPU) → 文字转写
↓
数字人回答 ← TTS 语音合成 ← RAG 知识库生成回答 ← 问题 + 上下文
| 策略 | 说明 |
|---|---|
| 主动开启 | 每页讲完后,数字人询问"大家有什么问题吗?" |
| 等待超时 | 等待 30-60 秒无提问,自动翻到下一页 |
| 追问支持 | 回答后可再给一次追问机会(超时 20 秒) |
| 打断机制 | 数字人讲解中可通过控制台打断,进入问答 |
| 兜底回答 | RAG 无法回答时,回复"这个问题我记录下来,稍后由专家解答" |
# FunASR Nano 模型,CPU 推理,INT8 量化
# 模型仅 100MB,内存占用 ~500MB,识别速度 1.5x 实时
cd /opt/FunASR
python -m funasr.bin.asr_server \
--model-dir FunAudioLLM/Fun-ASR-Nano-2512 \
--device cpu \
--port 10095
┌──────────────────────────────────────────┐ │ 展厅 │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ 大屏 1 │ │ 大屏 2 │ │ │ │ 产品线 A │ │ 产品线 B │ │ │ └─────┬────┘ └────┬─────┘ │ │ │HDMI │Type-C │ │ ┌─────▼────────────────────▼─────┐ │ │ │ NUC(藏在电视后面) │ │ │ └──────────────┬─────────────────┘ │ │ │ 网线 │ │ ┌───▼───┐ │ │ │ 交换机 │ │ │ └───┬───┘ │ │ │ 局域网/专线 │ │ ┌───────▼───────┐ │ │ │ 机房 A100 服务器│ │ │ └───────────────┘ │ │ │ │ 🎤 麦克风1 🎤 麦克风2 │ └──────────────────────────────────────────┘
移动部署只需搬运 NUC + 两块大屏 + 麦克风,A100 留在机房通过网络连接。搭建时间约 15 分钟:
配置 systemd 服务,NUC 开机后自动启动全部服务和双屏浏览器:
# start.sh — 一键启动所有服务
#!/bin/bash
# 1. FunASR(CPU 模式)
cd /opt/FunASR && python -m funasr.bin.asr_server \
--model-dir FunAudioLLM/Fun-ASR-Nano-2512 \
--device cpu --port 10095 &
# 2. WebSocket Server + 调度引擎
cd /opt/digital-human && python orchestrator.py &
# 3. Nginx
sudo systemctl start nginx
# 4. 双屏浏览器(kiosk 全屏模式)
chromium-browser --kiosk http://localhost/screen1.html &
sleep 2
chromium-browser --kiosk --new-window http://localhost/screen2.html &
| 阶段 | 任务 | 周期 | 产出 |
|---|---|---|---|
| P0 | A100 部署 LiveTalking + CosyVoice | 3-5 天 | 单屏数字人跑通 |
| P1 | 绿幕录制 avatar + 声音克隆 | 3-5 天 | 数字人形象 + 统一音色 |
| P2 | PPT 转 reveal.js + 叠加页面开发 | 1 周 | Web 幻灯片 + 抠像叠加 |
| P3 | NUC 部署:FunASR + 调度引擎 + 双屏 | 1 周 | 双屏自动讲解 |
| P4 | 讲解脚本编写 + PPT 联动翻页 | 1 周 | 完整讲解流程 |
| P5 | RAG 对接 + 问答流程 | 1 周 | 观众互动能力 |
| P6 | 双屏联调 + 稳定性测试 | 1 周 | 可交付系统 |
一开始用普通绿色布料 + 顶灯,抠像边缘有明显绿边。换成专业的绿幕柔光箱 + 均匀打光后,效果立刻提升一个档次。灯光均匀度 > 绿幕材质 > 抠像算法。
Nano 模型在安静环境下识别率很高,但展厅噪音大。解决方案:用指向性麦克风 + VAD(语音活动检测)过滤非人声段 + 在脚本中设置"请对着麦克风提问"的引导语。
PowerPoint 的复杂动画(如渐变擦除、3D 旋转)在 reveal.js 中无法完美还原。建议:简化动画效果,用 CSS transition 替代复杂动画,或者干脆用静态图 + 数字人口述来表达。
WebRTC 对网络延迟敏感。展厅场景下 NUC 和 A100 在同一局域网,延迟 <1ms,完全没问题。移动部署时如果走 VPN 跨网段,延迟可能到 20-50ms,数字人会有轻微卡顿。建议移动场景也用专线或同局域网。
最简单的方案是根据 TTS 生成的音频时长来估算讲完时间。更精确的方案是监听 LiveTalking 的音频播放完成回调。不要用固定定时器——不同文案长度差异很大。
这套方案的核心价值在于极致的资源利用效率:
技术选型上,LiveTalking 的多会话能力是方案可行的基础,FunASR 的 CPU 模式解放了 GPU 资源,WebGL Shader 实现了零延迟的绿幕抠像叠加。这些开源工具的组合,让"1 张卡 + 1 个盒子"的精简架构成为可能。