2D 数字人虚拟人SDK ,可以通过语音完成对虚拟人实时驱动。
项目 | 描述 |
---|---|
系统 | 支持 Android 10+ 系统。 |
CPU架构 | armeabi-v7a, arm64-v8a |
硬件要求 | 要求设备 CPU8 核及以上(骁龙8 Gen2),内存 8G 及以上。可用存储空间 1GB 及以上。 |
网络 | 无(完全本地运行) |
开发 IDE | Android Studio Giraffe 2022.3.1 Patch 2 |
内存要求 | 可用于数字人的内存 >= 800MB |
dependencies {
// duix_client_sdk_release_${version}.aar放到libs目录下(必选)
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
// SDK中签名需要使用 (必选)
implementation 'com.auth0:java-jwt:3.18.1'
// 如使用SDK中LiveKit语音交互功能需要使用 (可选)
implementation "org.java-websocket:Java-WebSocket:1.5.1"
...
}
权限要求, AndroidManifest.xml中,增加如下配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<!-- 使用LiveKit模式需要语音权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>
单纯的数字人渲染模式
graph LR
A[VirtualFactory初始化] --> B[检查配置与模型]
B[检查配置与模型] --> C[构建 Player 实例]
C --> D[调用 init 初始化]
D --> E[展示形象 / 渲染]
E --> F[PCM 或 WAV 音频驱动]
F --> G[播放控制与动作触发]
G --> H[资源释放]
使用LiveKit语音交互模式
graph LR
A[VirtualFactory初始化] --> B[检查配置与模型]
B[检查配置与模型] --> C[构建 Player 实例]
C --> D[调用 init 初始化]
D --> E[展示形象 / 渲染]
E --> F[播报开场白]
F --> G[采集麦克风音频]
G --> H[返回语音识别内容]
G --> I[大模型回复]
I --> J[PCM音频驱动嘴形与动作触发]
J --> G
J --> K[资源释放]
init接口定义: ai.guiji.duix.sdk.client.VirtualFactory
/**
* 初始化,使用默认的工作路径(/sdcard/Android/data/包名/files/duix/)
*/
int init(Context context, String appId, String appKey);
/**
* 自定义工作路径初始化
*/
int init(String appId, String appKey, String workPath)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
context | Context | 非空,App上下文 |
appId | String | 非空,平台生成的appId |
appKey | String | 非空,平台生成的appKey |
workPath | String | 自定义工作路径 |
返回参数说明:
取值 | 说明 |
---|---|
0 | 初始化成功 |
-1000 | 设置文件路径失败 |
-1001 | Context为空 |
-1002 | appId为空 |
-1003 | appKey为空 |
调用示例:
参考demo App实例 ai.guiji.duix.display.ui.activity.MainActivity
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
VirtualFactory.init(mContext, mAppId, mAppKey)
}
}
渲染需要下载模型文件及基础配置文件,每个模特对应一个模型文件压缩包,基础配置是运行模型需要的公共文件。SDK中提供了模型下载 的工具类VirtualModelUtil处理模型检查、下载及清理。
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 返回基础配置是否下载完成
*/
boolean checkBaseConfig();
调用示例:
if (VirtualModelUtil.checkBaseConfig()){
// 下一步
} else {
// 下载配置文件包
}
函数定义: ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 返回模型文件是否下载完成
*/
boolean checkModel(String name)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
name | String | 模型文件的下载连接或模型名称 |
调用示例:
if (VirtualModelUtil.checkModel(mModelPath)){
// 下一步
} else {
// 下载模型
}
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 启动基础配置文件下载
*/
void baseConfigDownload(Context context, ModelDownloadCallback callback)
void baseConfigDownload(Context context, String url, ModelDownloadCallback callback)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
context | Context | App上下文 |
url | String | baseConfig网络存储地址 |
callback | ModelDownloadCallback | 模型下载的回调接口 |
其中ModelDownloadCallback的接口定义:
ai.guiji.duix.sdk.client.callback.ModelDownloadCallback
/**
* 下载完成的事件
* @param url 下载链接
* @param dir 模型文件夹的保存路径
*/
void onDownloadComplete(String url, File dir)
/**
* 下载失败的事件
* @param url 下载链接
* @param code 异常码
* @param msg 异常消息
*/
void onDownloadFail(String url, int code, String msg)
/**
* 下载进度
* @param url 下载链接
* @param current 当前下载的字节进度
* @param total 需要下载zip文件大小
*/
void onDownloadProgress(String url, long current, long total)
/**
* zip文件解压进度
* @param url 下载链接
* @param current 当前解压的字节进度
* @param total 需要解压的zip文件大小
*/
void onUnzipProgress(String url, long current, long total)
调用示例:
VirtualModelUtil.baseConfigDownload(mContext, mBaseConfigUrl, modelDownloadCallback)
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 启动基础配置文件下载
*/
void modelDownload(Context context, String modelUrl, ModelDownloadCallback callback)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
context | Context | App上下文 |
modelUrl | String | 模型文件的下载地址 |
callback | ModelDownloadCallback | 模型下载的回调接口 |
调用示例:
VirtualModelUtil.modelDownload(mContext, mModelPath, modelDownloadCallback)
函数定义: ai.guiji.duix.sdk.client.Player
/**
* Player构造函数,默认使用LiveKit语音交互模式
*/
Player(Context context, RenderSink renderSink, Callback callback)
/**
* Player构造函数,可以通过useLiveKit=false关闭LiveKit模式,自行对接语音驱动模块。
*/
Player(Context context, RenderSink renderSink, boolean useLiveKit, Callback callback)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
context | Context | App上下文 |
renderSink | RenderSink | 渲染数据接口 |
useLiveKit | boolean | 是否使用LiveKit语音交互 |
callback | Callback | Player的各种回调事件 |
使用 SDK 提供的 DUIXRenderer
和 DUIXTextureView
可快速实现支持透明通道的渲染。也可以自己实现RenderSink接口自定义渲染逻辑。
其中RenderSink的定义如下: ai.guiji.duix.sdk.client.render.RenderSink
/**
* 渲染管道,通过该接口返回渲染数据
*/
public interface RenderSink {
// frame中的buffer数据以bgr顺序排列
void onVideoFrame(ImageFrame imageFrame);
}
其中Callback的定义如下: ai.guiji.duix.sdk.client.Player$Callback
public interface Callback {
// 数字人初始化完成并返回本地模型信息
void onInitSuccess(ModelInfo modelInfo);
// 数字人初始化失败
void onInitError(int code, int subCode, String msg);
// 数字人开始播放音频
void onPlayStart();
// 数字人音频播放完成
void onPlayEnd();
// 数字人播放音频异常
void onPlayError(int code, String msg);
// 数字人播放开始播放动作区间
void onMotionStart(String name);
// 数字人播放完成播放动作区间
void onMotionComplete(String name);
// --- 以下是LiveKit 相关回调 ---
// 接收到LiveKit开始推送音频的信号,可在此回调中调用startPush()函数让数字人准备播报
default void onSpeakStart(){}
// 接收到LiveKit推送的PCM音频数据,可在此回调中调用pushPcm()函数让数字人播报音频
default void onSpeakBuffer(ByteBuffer buffer){}
// 接收到LiveKit推送音频结束的信号,可在此中调中调用stopPush()函数通知数字人音频推送完毕
default void onSpeakEnd(){}
// 接收到LiveKit推送的音频数据对应的文本内容
default void onSpeakText(String text){}
// 接收到LiveKit推送的请求播放动作事件
default void onRequireMotion(String motion){}
// 接收到LiveKit推送的音频识别内容
default void onAsrResult(String content, boolean end){}
// 接收到LiveKit推送的连接异常信息
default void onLiveKitConnectError(int code, String msg){}
// 渲染每帧的统计信息
default void onRenderReport(int resultCode, boolean isLip, long useTime){}
}
调用示例:
val renderer =
DUIXRenderer(binding.glTextureView)
binding.glTextureView.setEGLContextClientVersion(GL_CONTEXT_VERSION)
binding.glTextureView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
binding.glTextureView.isOpaque = false // 透明
binding.glTextureView.setRenderer(renderer)
binding.glTextureView.renderMode =
GLSurfaceView.RENDERMODE_WHEN_DIRTY // 一定要在设置完Render之后再调用
player = Player(mContext, renderer, playerCallback)
函数定义: ai.guiji.duix.sdk.client.Player
/**
* Player初始化
* @param conversationId 平台申请的会话ID
* @param modelUrl 下载完成的模型地址
*/
void init(String conversationId, String modelUrl)
在Callback中异步回调初始化结果
参数说明:
参数 | 类型 | 描述 |
---|---|---|
conversationId | String | 平台申请的会话ID |
modelUrl | String | 下载完成的模型地址 |
调用示例:
player?.init(conversationId, modelPath)
仅在LiveKit模式时使用该接口
函数定义: ai.guiji.duix.sdk.client.Player
public void sendAudio(byte[] audio)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
audio | byte[] | 麦克风采集的音频数据 |
采集的PCM音频的格式: 16k采样率单通道16位深度
对接成功后会在Callback回调中返回识别结果。
调用示例:
player?.sendAudio(buffer)
PCM格式:16k采样率单通道16位深
函数定义: ai.guiji.duix.sdk.client.Player
// 通知服务开始推送音频,在LiveKit模式中onSpeakStart回调时需要调用该函数
void startPush()
// 推送PCM数据,在LiveKit模式中onSpeakBuffer回调时需要调用该函数
void pushPcm(byte[] buffer)
// 完成一段音频推送(音频推送完就调要该函数,而不是等播放完成再调用。),在LiveKit模式中onSpeakEnd回调时需要调用该函数
void stopPush()
startPush、pushPcm、stopPush需要成对调用,pushPcm不宜过长。可以在一整段音频推送完后调用stopPush结束当前会话,下一段音频再使用startPush重新开启推送。
每段startPush到stopPush中间的音频数据最少要1秒(32000字节)否则无法触发口型驱动,可以自行使用空白帧填充。
调用示例:
val thread = Thread {
player?.startPush()
val inputStream = assets.open("pcm/2.pcm")
val buffer = ByteArray(320)
var length = 0
while (inputStream.read(buffer).also { length = it } > 0){
val data = buffer.copyOfRange(0, length)
player?.pushPcm(data)
}
player?.stopPush()
inputStream.close()
}
thread.start()
函数定义: ai.guiji.duix.sdk.client.Player
void playAudio(String wavPath)
该函数兼容旧的wav驱动数字人接口,在内部实际是调用了PCM推流方式实现驱动。
参数说明:
参数 | 类型 | 描述 |
---|---|---|
wavPath | String | 16k采样率单通道16位深的wav本地文件 |
调用示例:
player?.playAudio(wavPath)
当数字人正在播报时调用该接口终止播报。
函数定义: ai.guiji.duix.sdk.client.Player
boolean stopAudio();
调用示例:
player?.stopAudio()
模型中支持新的动作区间标注(SpecialAction.json)
函数定义: ai.guiji.duix.sdk.client.Player
/**
* 播放指定动作区间
* @param name 动作区间名称,在init成功回调时,可以在@{ModelInfo.getSilenceRegion()}中获取到可用的动作区间
* @param now 是否立即播放 true: 立即播放; false: 等待当前静默区间或动作区间播放完毕后播放
*/
void startMotion(String name, boolean now)
调用示例:
player?.startMotion("打招呼", true)
随机播放场景及旧的标注协议(config.json)
函数定义: ai.guiji.duix.sdk.client.Player
/**
* 随机播放一个动作区间
* @param now 是否立即播放 true: 立即播放; false: 等待当前静默区间或动作区间播放完毕后播放
*/
void startRandomMotion(boolean now);
调用示例:
player?.startRandomMotion(true)
函数定义:
void release();
调用示例:
player?.release()
如果代码使用了混淆,请在proguard-rules.pro中配置:
-keep class ai.guiji.duix.DuixNcnn{*; }
问题现象 | 可能原因 | 解决方案 |
---|---|---|
init 回调失败 | 模型路径错误或未下载完成 | 使用 checkModel 检查模型状态 |
渲染黑屏 | EGL 配置或纹理视图设置错误 | 使用 SDK 提供示例中的设置方法 |
PCM 无播报效果 | 格式不符或未调用 startPush | 确保音频格式正确并调用推送方法 |
模型下载过慢 | 网络不稳定或 CDN 受限 | 支持自建模型文件托管服务 |