第二课:数字人 demo(examples/digital_man)
本课在板子上直接原生编译数字人 demo,装成系统服务,再通过 Unix socket 发文本驱动数字人播报。
全流程已在实机验证(含授权通过 + HDMI 输出)。数字人依赖 ROCKDH 算法授权,没有账号时
只要拿到key_digital_human.lic直接放置即可,不需要账号。
课程目标
-
掌握 SDK 源码 demo 的板端原生编译流程(
build-linux.sh,不设交叉编译器) -
掌握
system/+install.sh的板端部署模式 -
学会放置 rkauth 授权文件并判断授权是否生效
一、准备环境
1.1 硬件
| 设备 | 要求 |
|---|---|
| 开发板 | RK3588 类主板(RK1828 卡不是必须,本 demo 算法跑在主板上) |
| 显示器 | HDMI 接板子:数字人是渲染型 demo,不接屏就没有画面(HDMI-A-1 必须是 connected) |
| 音频 | HDMI 音频或蓝牙音箱(不加则只有画面、没声音,见 FAQ Q4) |
| PC | 只需能 ssh/scp 到板子,不需要交叉编译器 |
1.2 板端依赖
板子上要有 gcc / g++ / cmake(SDK 系统镜像默认带):
gcc --version | head -1
cmake --version | head -1
1.3 SDK 对应目录
examples/digital_man/
├── build-linux.sh # 一键构建入口(板端执行)
├── CMakeLists.txt
├── src/ # 源码
├── third_party/ # digital_human SDK + rockit(约 1.3GB)
├── system/ # 构建产物落地处(bin/ + lib/ + userdata/DH + install.sh)
└── README_CN.md
二、任务介绍
数字人 demo 的运行形态:
文本来源 ──(Unix dgram socket /tmp/dh_ipc.sock)──> digital_man 常驻服务
├─ 文本 → TTS 语音
├─ 口型/表情驱动
└─ Rockchip 渲染 → HDMI 输出
-
由 systemd 服务
digital_man管理(ExecStart=/bin/rknn3_make_digital_man_demo) -
对外接口极简:往 socket 发
id=end=正文(如1=1=你好)即可让它说话
三、操作步骤
步骤 1:打包并送到板子
# PC 端(打包约 650MB)
cd <SDK>/examples
tar czf /tmp/digital_man-src.tgz digital_man
# 送到板子
scp /tmp/digital_man-src.tgz firefly@<板子IP>:/tmp/
# 板端解压(解压后约 1.3GB)
ssh firefly@<板子IP>
sudo tar xzf /tmp/digital_man-src.tgz -C /userdata
ls /userdata/digital_man # CMakeLists.txt build-linux.sh src system third_party ...
步骤 2:【编译前必做】把音频输出卡改成你的板子
数字人的音频卡是写死在代码里的(src/VideoProducer.cpp):
memcpy(aoAttr.u8CardName, "hw:1,0", 7); // 1 = 板子的第二张声卡
不同板子的声卡编号不一样。先把本机播放设备列出来再决定填哪个:
aplay -l # 列出所有播放设备,看 card N 与名称
实测这块板(Firefly AIO-3588L-QUA-RK182X):
card 0: rockchiphdmi0 [rockchip-hdmi0] ← HDMI 音频
card 1: rockchipbt [rockchip,bt] ← 蓝牙 SCO
原代码 hw:1,0 指向蓝牙卡,所以"只看得到画面、听不到声音"。要 HDMI 出声就改成 hw:0,0:
cd /userdata/digital_man
sed -i 's/hw:1,0/hw:0,0/' src/VideoProducer.cpp
grep -n "u8CardName" src/VideoProducer.cpp # 确认已变成 hw:0,0
顺序很重要:这一步必须在编译之前做;改完再编译才会生效。
如果你的板子 HDMI 是 card2/card3,就填对应的hw:N,0。
步骤 3:板端编译
cd /userdata/digital_man
chmod +x build-linux.sh
./build-linux.sh
不要设置
GCC_COMPILER:板端编译默认用系统 gcc/g++,设置了反而可能找不到工具链。
成功标志(日志末尾):
-- Installing: /userdata/digital_man/system/bin/rknn3_make_digital_man_demo
-- Installing: /userdata/digital_man/system/lib/libdigital_human_library.so
步骤 4:安装(库 / 模型 / 服务)
cd /userdata/digital_man/system
chmod +x install.sh
sudo ./install.sh
安装内容:
| 目标 | 内容 |
|---|---|
/lib/*.so |
rockit、rockx2、digital_human、rknnrt 等 |
/userdata/DH/ |
模型与前端字典(约 780MB) |
/etc/systemd/system/digital_man.service |
服务单元 |
步骤 5:放置授权文件
有 lic 文件时不需要账号,直接放:
sudo cp key_digital_human.lic /userdata/key_digital_human.lic
sudo chmod 644 /userdata/key_digital_human.lic
grep license_path /userdata/DH/rockx_auth_config.json # 必须是 /userdata/key_digital_human.lic
要自己生成 lic(有账号)时:
cd /userdata/DH/rkauth_tool/Linux/aarch64/
./rkauth_tool_bin -u <user> -p <password> -o /userdata/key_digital_human.lic -m digital_human
步骤 6:启动服务
sudo systemctl daemon-reload
sudo systemctl restart digital_man
sudo systemctl enable digital_man # 开机自启(install.sh 不会自动 enable)
systemctl is-active digital_man # active
注意:
digital_man.service装好后默认没有 enable,重启板子后不会自动运行,
表现为/tmp/dh_ipc.sock不存在。补一条systemctl enable digital_man即可。
步骤 7:验证(三件事)
# ① 授权是否通过
journalctl -u digital_man -n 20 --no-pager | grep -E "rkauth|socket initialized"
# TextControl: socket initialized at /tmp/dh_ipc.sock
# I rkauth: auth by license_path /userdata/key_digital_human.lic
# I rkauth: verify license key ok ← 授权生效
# ② HDMI 是否真的在输出
cat /sys/kernel/debug/dri/0/summary | head -8
# Video Port0: ACTIVE
# Connector:HDMI-A-1
# Display mode: 1920x1080p60.00
# ③ IPC 是否就绪
ls -l /tmp/dh_ipc.sock
# ④ 音频卡是否指向你改的那张(关键)
PID=$(pgrep -f rknn3_make_digital_man_demo | head -1)
ls -l /proc/$PID/fd | grep pcm
# 期望:pcmC0D0p → card0(HDMI)
# 如果还是 pcmC1D0p,说明代码没改或没重编
步骤 8:发文本给数字人(一条命令)
先装一个一行命令的小工具(只需一次):
sudo tee /usr/local/bin/dh >/dev/null <<'EOF'
#!/bin/bash
set -e
SOCK=/tmp/dh_ipc.sock
if [ "$1" = "-f" ]; then TEXT=$(cat "$2")
elif [ -n "$1" ]; then TEXT="$*"
elif [ ! -t 0 ]; then TEXT=$(cat)
else echo "用法: dh <文本> | echo 文本 | dh | dh -f 文件" >&2; exit 1; fi
[ -S "$SOCK" ] || { echo "错误: 找不到 $SOCK (服务没起来?)" >&2; exit 1; }
python3 - "$TEXT" <<'PY'
import socket, sys
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
s.sendto(("1=1=" + sys.argv[1]).encode("utf-8"), "/tmp/dh_ipc.sock")
print("对数字人说: " + sys.argv[1])
PY
EOF
sudo chmod +x /usr/local/bin/dh
之后交互就很简单:
dh 你好,我是数字人 # 直接说一句
echo "换一句试试" | dh # 管道
dh -f speech.txt # 从文件读
报文格式是 id=end=正文(用 = 分隔,不是 id=1;end=1;text=)——这是最容易踩的坑:
// src/TextControl.cpp 的解析逻辑
p1 = msg.find('='); // 第一个 '='
p2 = msg.find('=', p1 + 1); // 第二个 '='
nTextId = stoi(msg.substr(0, p1)); // 第一段 = id
bIsEnd = stoi(msg.substr(p1+1, p2-p1-1));// 第二段 = 是否最后一段
strText = msg.substr(p2 + 1); // 剩下全部 = 正文
发送:
python3 - <<'EOF'
import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
# 正确: 1=1=正文
s.sendto("1=1=大家好,欢迎来到Firefly数字人课堂".encode("utf-8"), "/tmp/dh_ipc.sock")
print("sent")
EOF
发送后看日志确认 TTS 真的在工作:
journalctl -u digital_man -n 10 --no-pager
# [TTS Thread] Got audio status: 0
# zh,大家好,欢迎来到Firefly数字人课堂。!.!.!
# RTF: 0.12 ← 实时率,越小越快
四、结果验证
| 项 | 判据 | 实测 |
|---|---|---|
| 编译 | system/bin/rknn3_make_digital_man_demo 存在(file 显示 aarch64 ELF) |
|
| 安装 | /userdata/DH/data 存在,/lib/libdigital_human_library.so 存在 |
|
| 服务 | systemctl is-active digital_man = active,/tmp/dh_ipc.sock 存在 |
|
| 授权 | 日志出现 I rkauth: verify license key ok |
|
| 画面 | dri/0/summary 中 Video Port0: ACTIVE + HDMI-A-1 + 1920x1080 |
|
| 渲染负载 | 进程 CPU 约 110%(ps -o %cpu -p $(pgrep -f rknn3_make_digital_man_demo)) |
|
| 说话 | 发文本后画面口型变化 / 有声音 | 需人工看屏 |
不要用"日志里有没有收到文本"来判断:实测服务不会为每条文本打印日志。
五、常见问题(FAQ)
Q1:日志报 W rkauth: license_path /userdata/key_digital_human.lic can not read?
没有 lic 或路径不对。把 key_digital_human.lic 放到 /userdata/ 下并 chmod 644,
再 systemctl restart digital_man;路径必须与 rockx_auth_config.json 的 license_path 一致。
Q2:服务 active,但画面黑的 / 日志报 Layer ... VoDev 0 is not enabled?
没接显示器或没识别。查 cat /sys/class/drm/card0-HDMI-A-1/status,要 connected;
建议先接屏再上电。
Q3:install.sh 把桌面快捷方式装到了 /home/linaro/Desktop,我的用户是 firefly?
脚本按 ROCKDH 默认用户 linaro 写的,把这几个 .desktop 复制到 /home/firefly/Desktop/ 即可。
Q4:只看得到画面、听不到声音?
根因:音频卡写死在代码里(src/VideoProducer.cpp 的 u8CardName),默认 hw:1,0 指向蓝牙卡。
① 查看播放设备
aplay -l # card 编号 + 名称
aplay -L # 列出所有 PCM(含 default/plug 等)
② 选择播放设备(改代码 → 重新编译 → 重装 → 重启)
cd /userdata/digital_man
sed -i 's/hw:1,0/hw:0,0/' src/VideoProducer.cpp # 换成你的 card 号
./build-linux.sh
cd system && sudo ./install.sh
sudo systemctl restart digital_man
③ 验证真的在走那张卡
PID=$(pgrep -f rknn3_make_digital_man_demo | head -1)
ls -l /proc/$PID/fd | grep pcm
# pcmC0D0p = card0(本次改成 HDMI) pcmC1D0p = card1(原来的蓝牙)
改对之后,speaker-test -D hw:0,0 会报 Device or resource busy —— 这不是错误,
说明数字人已经独占了这张卡,正在播放。
音频卡是编译期写进二进制的,所以"改卡号"必须发生在
./build-linux.sh之前。
Q5:服务 active、HDMI 也 connected,但屏幕是黑的?
先看日志有没有这一行:
vo_gpu_composer {splice:183} Layer 0 is not enabled!
有就是桌面在抢屏幕:板子默认启动到 graphical.target,GNOME(gdm)占着 /dev/dri/card0,
Rockit VO 无法启用自己的图层。确认方法:
ps -eo pid,comm | grep -E "gnome-shell|gdm" # 看到 gnome-shell 就是在占屏
fuser -v /dev/dri/card0 # 看谁持有 card0
systemctl get-default # graphical.target 就是有桌面
数字人是 VO 直出,必须独占显示。切到多用户(无桌面)再启动:
sudo systemctl isolate multi-user.target
sudo systemctl restart digital_man
成功后日志里是 VoDev 0 Layer 0 update ...(不再有 Layer 0 is not enabled),
/sys/kernel/debug/dri/0/summary 里 Video Port0: ACTIVE 且 Esmart0-win0: ACTIVE。
注意:
systemctl isolate multi-user.target会同时关掉整个图形栈,屏幕会黑一下再重新出图;
恢复桌面用sudo systemctl isolate graphical.target(但那会重新抢占屏幕,数字人又会没画面)。
想固定"开机就是数字人",可以直接sudo systemctl set-default multi-user.target。
Q6:发文本后没反应 / 只念出乱码?
先检查报文格式。代码期望 id=end=正文(两个 = 分隔),例如 1=1=你好。
写成 id=1;end=1;text=你好 时,第二段会被解析成 1;end(stoi 失败→0),
正文变成 1;text=你好,于是要么不出声、要么念出奇怪的内容。
# 正确
python3 -c 'import socket;s=socket.socket(socket.AF_UNIX,socket.SOCK_DGRAM);s.sendto("1=1=你好".encode(),"/tmp/dh_ipc.sock")'
其它检查:① 服务 active;② /tmp/dh_ipc.sock 存在;
③ journalctl -u digital_man -n 10 里应出现 [TTS Thread] 与 RTF: 行。
本课小结
-
源码 demo 三板斧:打包推板 → 板端
build-linux.sh→install.sh+systemctl -
板端原生编译不需要交叉工具链,最省事;只要板上有 gcc/cmake
-
授权是"文件级"的:拿到
key_digital_human.lic放对路径就生效,verify license key ok是判据 -
渲染类 demo 的"真的出画面"要靠
dri/0/summary的Video Port0: ACTIVE来确认