2D 数字人虚拟人SDK ,可以通过语音完成对虚拟人实时驱动。
部署成本低: 无需客户提供技术团队进行配合,支持低成本快速部署在多种终端及大屏; 网络依赖小:可落地在地铁、银行、政务等多种场景的虚拟助理自助服务上; 功能多样化:可根据客户需求满足视频、媒体、客服、金融、广电等多个行业的多样化需求
提供定制形象的 AI 主播,智能客服等多场景形象租赁,支持客户快速部署和低成本运营; 专属形象定制:支持定制专属的虚拟助理形象,可选低成本或深度形象生成; 播报内容定制:支持定制专属的播报内容,应用在培训、播报等多种场景上; 实时互动问答:支持实时对话,也可定制专属问答库,可满足咨询查询、语音闲聊、虚拟陪伴、垂类场景的客服问答等需求。
项目 | 描述 |
---|---|
系统 | 支持 Android 7.0+ ( API Level 24 )到 Android 13 ( API Level 33 )系统。 |
CPU架构 | armeabi-v7a, arm64-v8a |
硬件要求 | 要求设备 CPU4 核极以上,内存 4G 及以上。可用存储空间 500MB 及以上。 |
网络 | 支持 WIF 及移动网络。如果使用云端问答库,设备带宽(用于数字人的实际带宽)期望 10mbps 及以上。 |
开发 IDE | Android Studio Koala \mid 2024.1.1 Patch 1 |
内存要求 | 可用于数字人的内存 >= 400MB |
dependencies {
// duix_client_sdk_release_${version}.aar放到libs目录下(必选)
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
// sdk 中使用到 exoplayer 处理音频(必选)
implementation 'com.google.android.exoplayer:exoplayer:2.14.2'
// 如使用SDK中的ASR功能 (可选)
implementation "org.java-websocket:Java-WebSocket:1.5.1"
// 如果使用SDK中的答疑接口需要SSE组件(可选)
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:okhttp-sse:4.10.0'
// 如果需使用本地JWT签名需要依赖(可选)
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0'
...
}
权限要求, AndroidManifest.xml中,增加如下配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA"/> <!-- 可选,多模态使用相机需要 -->
</manifest>
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
demo为方便更换appId调试在MainActivity中设置,实际也可以在Application进行SDK初始化
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
VirtualFactory.init(mContext, mAppId, mAppKey)
}
}
参考demo BaseActivity 示例 注意:所列举的权限为最低权限要求,如果缺少某一个,会导致 sdk 出现异常。
渲染需要下载模型文件及基础配置文件,每个模特对应一个模型文件压缩包,基础配置是运行模型需要的公共文件。SDK中提供了模型下载 的工具类VirtualModelUtil处理模型检查、下载及清理。
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 返回基础配置是否下载完成
*/
boolean checkBaseConfig();
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 返回模型文件是否下载完成
*/
boolean checkModel(String name)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
name | String | 模型文件的下载连接或模型名称 |
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 启动基础配置文件下载
*/
void baseConfigDownload(Context context, ModelDownloadCallback callback)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
context | Context | App上下文 |
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)
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 启动基础配置文件下载
*/
void modelDownload(Context context, String modelUrl, ModelDownloadCallback callback)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
context | Context | App上下文 |
modelUrl | String | 模型文件的下载地址 |
callback | ModelDownloadCallback | 模型下载的回调接口 |
耗时操作,不要在UI线程中调用
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 清理播报过程中产生的音频缓存。
*/
boolean clearAudioCache(Context context)
耗时操作,不要在UI线程中调用
函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil
/**
* 清理播报过程中产生的音频缓存。
*/
boolean clearModelCache(Context context)
SDK授权可以选择使用后台生成token和客户端生成两种方案,使用客户端生成可降低网络传输造成的key泄漏风险。注意: 客户端生成 需要依赖jackson-databind库以完成JWT签名工作。
函数定义: ai.guiji.duix.sdk.client.Player
/**
* 后台授权生成会话
*/
void initLicense(String conversationId, AuthCallback authCallback)
参数说明:
参数 | 类型 | 描述 |
---|---|---|
conversationId | String | 平台生成的会话ID |
authCallback | AuthCallback | 授权状态回调 |
其中AuthCallback定义:
public interface AuthCallback {
void onSuccess(SessionInfo sessionInfo);
void onError(int msgType, int msgSubType, String msg);
}
onError中msgType的取值:
取值 | 说明 |
---|---|
-1000 | 后台获取签名异常 |
-1001 | 创建会话异常 |
-1002 | 请提供合法的appId, appKey, conversationId |
-1003 | JWT签名发生异常 |
函数定义: ai.guiji.duix.sdk.client.Player
/**
* 后台授权生成会话
*/
void initLicenseLocal(String conversationId, AuthCallback authCallback)
参考demo CallActivity.kt示例: ai.guiji.duix.display.ui.activity.CallActivity
package ai.guiji.duix.display.ui.activity
// import ...
class CallActivity : BaseActivity() {
companion object {
const val GL_CONTEXT_VERSION = 2
const val requestCameraPermission = 1
}
private lateinit var modelPath: String
private lateinit var conversationId: String
private lateinit var binding: ActivityCallBinding
private var player: Player? = null
private var lastQARequest: IQA? = null // 记录上次的问答请求
private var audioRecorder: AudioRecordCore? = null
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
keepScreenOn()
binding = ActivityCallBinding.inflate(layoutInflater)
setContentView(binding.root)
// 启动会话需要的基本信息
modelPath = intent.getStringExtra("modelPath") ?: ""
conversationId = intent.getStringExtra("conversationId") ?: ""
Glide.with(mContext).load("file:///android_asset/bg/bg1.png").into(binding.ivBg)
player = VirtualFactory.getPlayer(mContext)
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?.setDisplay(renderer)
player?.addCallback(playerCallback)
player?.initLicense(conversationId, object: AuthCallback{
override fun onSuccess(sessionInfo: SessionInfo) {
runOnUiThread {
// 授权成功,启动数字人
Log.e(TAG, "授权成功,启动数字人")
setVirtualModel()
}
}
override fun onError(msgType: Int, msgSubType: Int, msg: String?) {
runOnUiThread {
Toast.makeText(
mContext, "授权异常 msgType: " +
"$msgType msgSubType: $msgSubType msg: $msg", Toast.LENGTH_LONG
).show()
finish()
}
}
})
}
override fun onDestroy() {
super.onDestroy()
player?.removeCallback(playerCallback)
player?.release()
audioRecorder?.release()
}
private val playerCallback = object : Player.Callback{
override fun onPlayStart() {
Log.e(TAG, "onPlayStart")
}
override fun onPlayEnd() {
Log.e(TAG, "onPlayEnd")
playEnd()
}
override fun onPlayProgress(current: Long, total: Long) {
}
override fun onError(msgType: Int, msgSubType: Int, msg: String?) {
Log.e(TAG, "onError msgType: $msgType msgSubType: $msgSubType msg: $msg")
runOnUiThread {
when(msgType){
-1000 -> {
// 授权异常相关
Toast.makeText(
mContext, "心跳鉴权异常 msgType: " +
"$msgType msgSubType: $msgSubType msg: $msg", Toast.LENGTH_LONG
).show()
finish()
}
-1001 -> {
// 音频播放异常相关
playEnd()
}
}
}
}
}
private fun setVirtualModel(){
val model = Model(modelPath)
player?.setVirtualModel(model, object :
SetVirtualModelCallback {
override fun onSetVirtualModelStatus(
msgType: Int,
msgSubType: Int,
msg: String?
) {
runOnUiThread {
if (msgType == 0){
// 模型应用完成
} else {
Log.e(TAG, "设置模型异常 msgType: " +
"$msgType msgSubType: $msgSubType msg: $msg")
Toast.makeText(
mContext, "设置模型异常 msgType: " +
"$msgType msgSubType: $msgSubType msg: $msg", Toast.LENGTH_SHORT
).show()
finish()
}
}
}
override fun onDownloadStart(url: String?, msg: String?) {
runOnUiThread {
Toast.makeText(mContext, "模型下载中,请稍等...", Toast.LENGTH_SHORT).show()
}
}
override fun onDownloadStatus(
url: String?,
resource: Int,
event: Int,
current: Long,
total: Long,
msg: String?
) {
Log.e(TAG, "onDownloadStatus resource: $resource event: $event current: $current total: $total msg: $msg")
}
override fun onDownloadComplete(url: String?, msg: String?) {
}
})
}
}
函数定义: ai.guiji.duix.sdk.client.VirtualFactory
public static Player getPlayer(Context context) {
return new Player(context);
}
返回: 一个player 注意:需要注意的是,一个应用中,应该只有一个 player ,如果多个可能会有性能问题
调用示例如下:
player = VirtualFactory.getPlayer(mContext)
调用示例如下:
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?.setDisplay(renderer)
调用示例如下:
player?.addCallback(playerCallback)
playerCallback定义:
public interface Callback {
// 音频开始播放
void onPlayStart();
// 音频播放完成
void onPlayEnd();
// 音频播放进度
void onPlayProgress(long current, long total);
// 发生异常
void onError(int msgType, int msgSubType, String msg);
}
其中onError msgType定义:
取值 | 说明 | 其他 |
---|---|---|
-1000 | 授权异常 | 未授权或心跳异常 |
-1001 | 音频播放异常 | 音频文件或下载异常 |
函数定义: ai.guiji.duix.sdk.client.Player;
/**
* 初始化授权
*/
boolean initLicense(String conversationId, AuthCallback authCallback)
函数定义: ai.guiji.duix.display.client.Player
/**
* 设置数字人模型
* @param model 模型信息
* @param callback 参考 {@link SetVirtualModelCallback}
*/
void setVirtualModel(Model model, SetVirtualModelCallback callback)
其中Model定义如下: ai.guiji.duix.display.client.bean.Player
public class Model {
/**
* 构建模型对象,name参数可以传递sd卡存储的解压后的文件夹名称。或者是网络地址。可以提前使用下载服务将模型下载。
* 或者在使用过程中下载模型文件。因模型文件较大,下载模型文件需要一定的时间。
* @param name 模型名称(本地缓存的模型名称,比如"xiaoai")或者网络url地址(zip文件的下载路径)
*/
private String name;
private boolean isCloseAudio;
}
参数说明:
参数名 | 类型 | 说明 |
---|---|---|
name | String | 数字人名称(例如"liangwei_540s")或数字人资源的下载路径(例如"https://cdn.guiji.ai/duix/location/liangwei_540s.zip") |
isCloseAudio | boolean | true:关闭声音,false:打开声音 |
其中SetVirtualModelCallback定义: ai.guiji.duix.sdk.client.callback
public interface SetVirtualModelCallback {
/**
* 设置数字人形象返回结果
*/
void onSetVirtualModelStatus(int msgType, int msgSubType, String msg);
/**
* 模型或基础配置文件未同步,触发下载模型文件
*/
default void onDownloadStart(String url, String msg) {
}
/**
* 模型下载状态信息
*/
default void onDownloadStatus(String url, int resource, int event, long current, long total, String msg) {
}
/**
* 模型或基础配置文件下载完成
*/
default void onDownloadComplete(String url, String msg) {
}
}
其中onSetVirtualModelStatus的返回参数说明:
参数名 | 类型 | 取值 | 说明 |
---|---|---|---|
msgType | int | 0,成功,其他失败 | 可以作为业务逻辑判断 |
msgSubType | int | 在 msgType 不为 0 的情况下,可能会有非 0 的取值 | 不能作为业务逻辑判断 |
msg | String | 提示信息 |
msgType取值
取值 | 说明 | |
---|---|---|
0 | 设置数字人成功 | 0,成功,其他失败 |
-1000 | 授权未成功 | |
-1001 | 模型文件不存在 | |
-1002 | 模型文件或基础配置文件下载失败 | |
-1003 | 模型加载异常 | |
-1004 | 模型切换频繁 |
其中onDownloadStart的返回参数说明:
参数名 | 类型 | 说明 |
---|---|---|
url | String | 模型文件或基础配置文件下载地址 |
msg | String | 提示信息 |
其中onDownloadStatus的返回参数说明:
参数名 | 类型 | 取值 | 说明 |
---|---|---|---|
url | String | 模型文件或基础配置文件下载地址 | |
resource | int | 0: 模型文件 1: 基础配置文件 | 下載文件类型 |
event | int | 0: 下载文件 1: 解压文件 | 动作 |
current | long | 当前下载进度 | |
total | long | 文下大小 |
其中onDownloadComplete的返回参数说明:
参数名 | 类型 | 说明 |
---|---|---|
url | String | 模型文件或基础配置文件下载地址 |
msg | String | 提示信息 |
函数定义: ai.guiji.duix.sdk.client.Player
/**
* 以WAV方式驱动数字人形象, pcm 格式要求为: 16000hz ,单声道, 16 位深度
* @param path wav文件在SD卡的路径
*/
void speak(String path)
调用示例:
player?.speak(path)
函数定义: ai.guiji.duix.sdk.client.Player
void stopAudio()
调用示例:
player?.stopAudio()
函数定义:
void requireMotion()
调用示例:
player?.requireMotion()
函数定义:
startAsr(ASRCallback callback)
其中ASRCallback的定义:
ai.guiji.duix.sdk.client.callback.ASRCallback
public interface ASRCallback {
/**
* asr返回识别内容
* @param content 识别的文本内容
* @param end 是否是整句结束
*/
void onASRResult(String content, boolean end);
/**
* -1000 - 授权异常
* -1001 -1000 ASR连接异常
* -1002 - ASR启动异常
*
*/
void onError(int code, int subCode, String msg);
default void onReady(){}
}
onError中msgType取值:
取值 | 说明 | |
---|---|---|
-1000 | 授权未成功 | |
-1001 | ASR连接异常 | |
-1002 | ASR启动异常 |
调用示例:
player?.startAsr(asrCallback)
录音提供的pcm格式为16000采样率、单通道、16bit深度,每次传输640bytes(20ms)
函数定义:
boolean sendRecord(byte[] audio)
调用示例:
player?.sendRecord(buffer)
函数定义:
stopAsr()
调用示例:
player?.stopAsr()
函数定义:
IQA startAnswer(String content, AnswererCallback callback)
参数说明:
参数名 | 类型 | 说明 |
---|---|---|
content | String | 大模型回复的问题 |
callback | AnswererCallback | 回复的回调接口 |
返回值IQA是答疑接口的抽象,可通过该对象中断答疑流程
其中AnswererCallback的定义:
ai.guiji.duix.sdk.client.callback.AnswererCallback
public interface AnswererCallback {
/**
* @param code 0 成功,其他失败
* @param msg 异常信息
* @param info 返回的部分回复内容
*/
void onMessage(int code, String msg, String info);
}
onMessage中code取值:
取值 | 说明 | |
---|---|---|
0 | 成功 | |
-1001 | 网络异常 | |
-1002 | 后台服务异常 | |
-1003 | 数据解析异常 | |
-1004 | 大模型异常 |
调用示例:
player?.startAnswer("你好啊,很高兴认识你", callback)
函数定义:
IQA startAnswer(String content, byte[] jpgBytes, AnswererCallback callback)
参数说明:
参数名 | 类型 | 说明 |
---|---|---|
content | String | 大模型回复的问题 |
jpgBytes | byte[] | JPEG格式的图片数据 |
callback | AnswererCallback | 回复的回调接口 |
调用示例:
player?.startAnswer("你好啊,很高兴认识你", jpgBytes, callback)
函数定义:
IQA startAnswer(String content, byte[] jpgBytes, boolean useTTS, boolean useContext, long timeout, AnswererCallback callback)
参数说明:
参数名 | 类型 | 说明 |
---|---|---|
content | String | 大模型回复的问题 |
jpgBytes | byte[] | JPEG格式的图片数据 |
useTTS | boolean | 是否合成音频文件 |
useContext | boolean | 是否使用上下文 |
timeout | long | 接口超时时间(单位ms) |
callback | AnswererCallback | 回复的回调接口 |
调用示例:
player?.startAnswer("你好啊,很高兴认识你", jpgBytes, true, true, 15000, callback)
函数定义:
/**
* 将文本转成语言
*/
boolean tts(String content, boolean download, TTSCallback callback)
参数说明:
参数名 | 类型 | 说明 |
---|---|---|
content | String | 需要合成语音的文本内容 |
download | boolean | 是否自动下载到设备 |
callback | TTSCallback | 合成的状态回调 |
其中TTSCallback的定义:
ai.guiji.duix.sdk.client.callback.TTSCallback
public interface TTSCallback {
void onSuccess(String urlOrPath);
void onError(int msgType, int msgSubType, String msg);
}
当调用tts接口时download参数为true时urlOrPath返回的是本地SD卡路径,当download为false时返回的是网络url
onError中msgType的取值:
取值 | 说明 | |
---|---|---|
-1000 | 合成请求异常 | |
-1001 | 下载异常 |
调用示例:
player?.tts(content, true, callback)
函数定义:
void removeCallback(Callback callback)
调用示例:
player?.removeCallback(playerCallback)
函数定义:
void release();
调用示例:
player?.release()
检查模型是否下载完成 函数定义: ai.guiji.duix.sdk.client.VirtualModelUtil
boolean checkModel(String name);
返回值:
取值 | 说明 |
---|---|
true | 模型已同步完成 |
false | 本地模型文件不存在或异常 |
调用示例如下:
VirtualModelUtil.checkModel()
检查基础配置文件是否下载完成 函数定义: ai.guiji.duix.sdk.client.VirtualModelUtil
boolean checkBaseConfig();
返回值:
取值 | 说明 |
---|---|
true | 基础配置已同步完成 |
false | 本地配置文件不存在或异常 |
调用示例如下:
VirtualModelUtil.checkBaseConfig()
基础配置文件下载 函数定义: ai.guiji.duix.sdk.client.VirtualModelUtil
boolean baseConfigDownload(Context context, ModelDownloadCallback callback);
ModelDownloadCallback的定义:
public interface ModelDownloadCallback {
void onDownloadProgress(String url, long current, long total);
void onUnzipProgress(String url, long current, long total);
void onDownloadComplete(String url, File dir);
void onDownloadFail(String url, int code, String msg);
}
onDownloadProgress返回参数说明:
参数名 | 类型 | 说明 |
---|---|---|
url | String | 文件下载地址 |
current | long | 当前下载进度 |
total | long | 文件总大小 |
onUnzipProgress返回参数说明:
参数名 | 类型 | 说明 |
---|---|---|
url | String | 文件下载地址 |
current | long | 当前解压进度 |
total | long | 解压总大小 |
onDownloadComplete返回参数说明:
参数名 | 类型 | 说明 |
---|---|---|
url | String | 文件下载地址 |
dir | File | 保存的文件夹地址 |
onDownloadFail返回参数说明:
参数名 | 类型 | 说明 |
---|---|---|
url | String | 文件下载地址 |
code | int | 下载模型错误码 |
msg | String | 提示消息 |
调用示例如下:
VirtualModelUtil.baseConfigDownload(mContext, callback)
模型文件下载 函数定义: ai.guiji.duix.sdk.client.VirtualModelUtil
boolean modelDownload(Context context, String modelUrl, ModelDownloadCallback callback);
调用示例如下:
VirtualModelUtil.modelDownload(
mContext,
"https://cdn.guiji.ai/duix/location/liangwei_540s.zip",
callback
)
如果代码使用了混淆,请在proguard-rules.pro中配置:
-keep class com.btows.ncnntest.**{*; }
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}