# 硅基数字人SDK

# 一、产品介绍

2D 数字人虚拟人SDK ,可以通过语音完成对虚拟人实时驱动。

# 1. 适用场景

部署成本低: 无需客户提供技术团队进行配合,支持低成本快速部署在多种终端及大屏; 网络依赖小:可落地在地铁、银行、政务等多种场景的虚拟助理自助服务上; 功能多样化:可根据客户需求满足视频、媒体、客服、金融、广电等多个行业的多样化需求

# 2. 核心功能

提供定制形象的 AI 主播,智能客服等多场景形象租赁,支持客户快速部署和低成本运营; 专属形象定制:支持定制专属的虚拟助理形象,可选低成本或深度形象生成; 播报内容定制:支持定制专属的播报内容,应用在培训、播报等多种场景上; 实时互动问答:支持实时对话,也可定制专属问答库,可满足咨询查询、语音闲聊、虚拟陪伴、垂类场景的客服问答等需求。

# 二、SDK集成

# 1. 支持的系统和硬件版本

项目 描述
系统 支持 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

# 2. SDK集成

  1. 下载duix_sample_android_5.0.5.zip (opens new window)
  2. 引入 sdk aar 包: duix_client_sdk_release_${version}.aar
  3. app 目录新建 libs 目录,放入 aar 包,在 build.gradle 中增加配置如下
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>

# 三、SDK调用及API说明

# 1. 初始化SDK

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)
    }
}

# 2. 动态申请系统权限

参考demo BaseActivity 示例 注意:所列举的权限为最低权限要求,如果缺少某一个,会导致 sdk 出现异常。

# 3. 模型下载安装

渲染需要下载模型文件及基础配置文件,每个模特对应一个模型文件压缩包,基础配置是运行模型需要的公共文件。SDK中提供了模型下载 的工具类VirtualModelUtil处理模型检查、下载及清理。

# (3.1) 基础配置文件检查

函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil

/**
 * 返回基础配置是否下载完成
 */
boolean checkBaseConfig();

# (3.2) 模型文件检查

函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil

/**
 * 返回模型文件是否下载完成
 */
boolean checkModel(String name)

参数说明:

参数 类型 描述
name String 模型文件的下载连接或模型名称

# (3.3) 基础配置文件下载

函数定义:
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)

# (3.4) 模型文件下载

函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil

/**
 * 启动基础配置文件下载
 */
void modelDownload(Context context, String modelUrl, ModelDownloadCallback callback)

参数说明:

参数 类型 描述
context Context App上下文
modelUrl String 模型文件的下载地址
callback ModelDownloadCallback 模型下载的回调接口

# (3.5) 清理WAV音频缓存

耗时操作,不要在UI线程中调用

函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil

/**
 * 清理播报过程中产生的音频缓存。

 */
boolean clearAudioCache(Context context)

# (3.6) 清理缓存的模型文件

耗时操作,不要在UI线程中调用

函数定义:
ai.guiji.duix.sdk.client.VirtualModelUtil

/**
 * 清理播报过程中产生的音频缓存。
 */
boolean clearModelCache(Context context)

# 4. SDK授权并创建会话

SDK授权可以选择使用后台生成token和客户端生成两种方案,使用客户端生成可降低网络传输造成的key泄漏风险。注意: 客户端生成 需要依赖jackson-databind库以完成JWT签名工作。

# (4.1) 后台生成token

函数定义: 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签名发生异常

# (4.2) 使用客户端签名生成会话

函数定义: ai.guiji.duix.sdk.client.Player

/**
 * 后台授权生成会话
 */
void initLicenseLocal(String conversationId, AuthCallback authCallback)

# 5. 启动数字人渲染

参考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?) {

            }
        })
    }
}

# (5.1) 初始化player

函数定义: ai.guiji.duix.sdk.client.VirtualFactory

public static Player getPlayer(Context context) {
    return new Player(context);
}

返回: 一个player 注意:需要注意的是,一个应用中,应该只有一个 player ,如果多个可能会有性能问题

调用示例如下:

player = VirtualFactory.getPlayer(mContext)

# (5.2) 通过 GLTextureView.Renderer 展示数字人

调用示例如下:

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)

# (5.3) 设置数字人回调事件

调用示例如下:

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 音频播放异常 音频文件或下载异常

# (5.4) 授权使用数字人

函数定义: ai.guiji.duix.sdk.client.Player;

/**
 * 初始化授权
 */
boolean initLicense(String conversationId, AuthCallback authCallback)

# (5.5) 设置数字人形象

函数定义: 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 提示信息

# (5.6) 以WAV的形式驱动数字人

函数定义: ai.guiji.duix.sdk.client.Player

/**
 * 以WAV方式驱动数字人形象, pcm 格式要求为: 16000hz ,单声道, 16 位深度
 * @param path  wav文件在SD卡的路径
 */
void speak(String path)

调用示例:

player?.speak(path)

# (5.7) 数字人停止播报

函数定义: ai.guiji.duix.sdk.client.Player

void stopAudio()

调用示例:

player?.stopAudio()

# (5.8) 数字人播放动作区间

函数定义:

void requireMotion()

调用示例:

player?.requireMotion()

# (5.9) 开启ASR识别

函数定义:

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)

# (5.10) 发送PCM录音数据

录音提供的pcm格式为16000采样率、单通道、16bit深度,每次传输640bytes(20ms)

函数定义:

boolean sendRecord(byte[] audio)

调用示例:

player?.sendRecord(buffer)

# (5.11) 关闭ASR识别

函数定义:

stopAsr()

调用示例:

player?.stopAsr()

# (5.12) 大模型问答

函数定义:

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)

# (5.13) 支持多模态的大模型问答

函数定义:

IQA startAnswer(String content, byte[] jpgBytes, AnswererCallback callback)

参数说明:

参数名 类型 说明
content String 大模型回复的问题
jpgBytes byte[] JPEG格式的图片数据
callback AnswererCallback 回复的回调接口

调用示例:

player?.startAnswer("你好啊,很高兴认识你", jpgBytes, callback)

# (5.14) 全量参数调用大模型问答

函数定义:

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)

# (5.15) TTS文本转语音

函数定义:

/**
 * 将文本转成语言
 */
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)

# (5.16) 清空回调接口

函数定义:

void removeCallback(Callback callback) 

调用示例:

player?.removeCallback(playerCallback)

# (5.17) 资源释放

函数定义:

void release();

调用示例:

player?.release()

# 6 工具类

# (6.1) 模型工具类

检查模型是否下载完成 函数定义: 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配置

如果代码使用了混淆,请在proguard-rules.pro中配置:

-keep class com.btows.ncnntest.**{*; }
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}