Firefly AI相关:数字人demo的编译与使用

第二课:数字人 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) :white_check_mark:
安装 /userdata/DH/data 存在,/lib/libdigital_human_library.so 存在 :white_check_mark:
服务 systemctl is-active digital_man = active,/tmp/dh_ipc.sock 存在 :white_check_mark:
授权 日志出现 I rkauth: verify license key ok :white_check_mark:
画面 dri/0/summary 中 Video Port0: ACTIVE + HDMI-A-1 + 1920x1080 :white_check_mark:
渲染负载 进程 CPU 约 110%(ps -o %cpu -p $(pgrep -f rknn3_make_digital_man_demo)) :white_check_mark:
说话 发文本后画面口型变化 / 有声音 需人工看屏

:warning: 不要用"日志里有没有收到文本"来判断:实测服务不会为每条文本打印日志。

五、常见问题(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 来确认

1 Like