Android-SDK
一、兼容性说明
类别 | 兼容范围 |
---|---|
系统 | 支持Android 5.0 ~ Android 13 版本,鸿蒙系统未做兼容性验证 |
机型 | 上市的Android手机和平板、及符合具体能力性能要求的Android系统设备 |
网络 | 依赖网络 |
开发环境 | 建议使用Android Studio 进行开发 |
#二、SDK组成
#三、接口调用流程

#四、快速集成
#4.1 导入SDK库
// 已忽略无关代码
dependencies {
//虚拟人SDK及依赖库
api files('libs/AIKit.aar')
api files('libs/vms.aar')
//音视频依赖库
api files('libs/xrtcsdk-4.4.0.aar')
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
}
#4.2 SDK初始化
// 初始化参数构建
BaseLibrary.Params params = BaseLibrary.Params.builder()
.appId("$appId")
.apiKey("$apiKey")
.apiSecret("$apiSecret")
.workDir("/sdcard/iflytek/VMS")//SDK工作路径,这里为绝对路径,路径可自行指定,此处仅为示例
.build();
//注册授权结果监听
VmsHelper.getInst().registerAuthListener(coreListener);
// 初始化
VmsHelper.getInst().initEntry(MainActivity.this.getApplicationContext(), params);
//授权结果回调
private CoreListener coreListener = new CoreListener() {
@Override
public void onAuthStateChange(final ErrType type, final int code) {
runOnUiThread(new Runnable() {
@Override
public void run() {
switch (type) {
case AUTH:
//txt_result.setText("SDK状态:授权结果码" + code);
break;
case HTTP:
//txt_result.setText("SDK状态:HTTP认证结果" + code);
break;
default:
//txt_result.setText("SDK状态:其他错误");
}
}
});
}
};
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
apiKey | String | 是 | 平台创建应用后,生成的唯一应用标识 |
apiSecret | String | 是 | 平台创建应用后,生成的唯一应用秘钥 |
appID | String | 是 | 平台创建应用后,生成的应用ID |
workDir | String | 是 | SDK工作目录,用户可自行指定,但要确保有访问权限 |
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
type | ErrType | 是 | SDK错误类型,0 表示授权错误,1 表示 http 请求错误 |
code | int | 是 | 错误码,0 表示正常 |
#** **
#4.3 注册虚拟人会话状态回调
AiResponseListener vmsListener = new AiResponseListener() {
@Override
public void onResult(String ability, int handleID, List<AiResponse> outputData, Object usrContext) {
if (null != outputData && outputData.size() > 0) {
Log.i(TAG, "VMS:onResult:handleID:" + handleID + ":" + outputData.size() + ",ability:" + ability);
for (int i = 0; i < outputData.size(); i++) {
byte[] bytes = outputData.get(i).getValue();
if (bytes == null) {
continue;
}
String key = outputData.get(i).getKey();
Log.i(TAG, "VMS:onResult内容:" + key + "," + new String(bytes));
switch (key) {
case "session":
String session = new String(bytes); // 会话的session,驱动等接口必须携带该字段,start成功后会回调此字段
break;
case "res_id": //上传背景的回调
resId = new String(bytes);
showTip("上传成功!Start后生效。res_id:" + resId);
break;
case "realtime_status": //用于判断虚拟人播放状态(仅支持ws接口)
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(new String(bytes));
String global_status = jsonObject.optString("vmr_status", "");
if (global_status.equals("2")) { //0开始 1继续 2结束
showTip("播放完毕");
}
} catch (JSONException e) {
e.printStackTrace();
}
break;
}
}
}
}
//事件回调
@Override
public void onEvent(String ability, int handleID, int event, List<AiResponse> eventData, Object usrContext) {
Log.i(TAG, "VMS:onEvent:ability:" + ability + ",event:" + event);
if (ability.equals(VmsHelper.ABILITY_VMS_CTRL) && event == 19) { //追加模式输入间隔超时,需要重置到首帧
interactiveModeIndex = 0; //用于追加模式下传首帧还是中间帧
}
}
//错误通知
@Override
public void onError(String ability, int handleID, int err, String msg, Object usrContext) {
showTip("错误通知:" + msg);
Log.e(TAG, "VMS:onError,ability " + ability + " ERROR::" + msg + ",err code:" + err);
if(ability.equals("vms2d_ping")){ //ping报错
}
switch (err){
case 20014: //打断模式时,打断之前的驱动,会返回此code
Log.w(TAG, "当前任务被打断,Ability " + ability + " ERROR::" + msg + ",err code:" + err);
break;
default:
showTip("错误通知:" + msg);
Log.e(TAG, "错误通知,Ability " + ability + " ERROR::" + msg + ",err code:" + err);
break;
}
}
};
//添加虚拟人回调监听
VmsHelper.getInst().registerListener(vmsListener);
变量名 | 类型 | 非空 | 说明 |
---|---|---|---|
ability | String | 是 | 能力标识ID |
handleID | int | 是 | 会话ID |
usrContext | Object | 否 | 用户自定义标识 |
responseData | List | 是 | 能力执行结果 |
变量名 | 类型 | 非空 | 说明 |
---|---|---|---|
ability | String | 是 | 能力标识ID |
handleID | int | 是 | 会话ID |
event | enum | 是 | 0=未 知;1=开始;2=结束;3=超时;4=进度 |
usrContext | Object | 否 | 用户自定义标识 |
eventData | List | 是 | 事件消息数据 |
变量名 | 类型 | 非空 | 说明 |
---|---|---|---|
ability | String | 是 | 能力标识ID |
handleID | int | 是 | 会话ID |
err | int | 是 | 错误码 |
msg | String | 否 | 错误描述 |
usrContext | Object | 否 | 用户自定义标识 |
变量名 | 类型 | 非空 | 说明 |
---|---|---|---|
key | String | 是 | 输出数据名称 |
type | enum | 是 | 输出数据类型,0=音频;1=文本;2=图片;3=视频;5=个性化数据 |
value | byte[] | 否 | 字节数组类型输出数据 |
varType | enum | 是 | 输出数据变量类型,0=字节数组;1=整型;2=实型;3=布尔型 |
len | int | 是 | 输出数据长度 |
#4.4能力调用
#4.4.1启动虚拟人
//传入拉流视图
ViewGroup virtualContainer = findViewById(R.id.ll_virtual);
VmsHelper.getInst().setVmsView(virtualContainer);
//从配置文件读取参数
AiRequest.Builder in = AiRequest.builder();
in.header().param("uid", mUid);
if (!TextUtils.isEmpty(resId)) { //背景
in.param("res_id", resId);
}
in.service("vmr");
AvatarB avatarB = avatarCtrlB.getCurrentSelectItem();
in.param("avatar_id", avatarB.getAvatar_id());//形象id avatarB.getAvatar_id()
if (!TextUtils.isEmpty(resId)) { //有背景
in.param("scale", 0.9); //[0.1, 1.0] 主播在背景图像中的大小 (仅在个性化背景中生效)
in.param("move_h", 0); //[-4096, +4096] 主播平移像素距离 (仅在个性化背景中生效)
in.param("move_v", 0); //[-4096, +4096] 控制主播在展示画面中的上下移动距离(仅在个性化背景中生效
}else {
//视频分辨率:宽,高
//width must be one of [1920 1280 640 360 720 1080]
//height must be one of [1080 720 360 640 1280 1920]
in.param("width", 1080).param("height", 1920);
}
//响应数据控制
AiRequest.Builder streamBuilder = AiRequest.builder();
streamBuilder.param("protocol", "xrtc");
if (fps > 0) {
streamBuilder.param("fps", fps); //视频帧率 13-25
}
if (bitrate > 0) {
streamBuilder.param("bitrate", bitrate); //视频的码率 800 ~ 2000
}
in.param("stream", streamBuilder);
VMSConfig vmsConfig = VMSConfig.builder()
.videoFillMode(videoFillMode)
.videoRotation(videoRotation)
.videoMirrorType(VideoMirrorType.VIDEO_MIRROR_TYPE_DISABLE)
.videoStreamType(VideoStreamType.VIDEO_STREAM_TYPE_BIG)
.useWebSocket(isWebSocket) //是否使用使用ws接口,非实ws接口不支持时返回状态数据 默认使用ws接口
.interactiveMode(InteractiveMode.valueOf(interactiveMode)) //模式 0 追加模式。1打断模式
.build();
int ret = VmsHelper.getInst().start(in.build(), vmsConfig);
if (ret != 0) {
showTip("start失败");
}
#4.4.2 输入数据
byte[] byteArray = readStream(filePath);
if (byteArray == null || byteArray.length <= 0) {
Log.e(TAG, "audioDrive 当前文件大小异常!");
return;
}
int leftAudioBytes = byteArray.length;
int byteWrites = 1024; //每次输入的字节数
int writeLen = 0;
int index = 0;
isAudioRunning = true;
while (leftAudioBytes > 0) {
boolean isLast = false;
if (leftAudioBytes > byteWrites) {
writeLen = byteWrites;
} else {
isLast = true;
writeLen = leftAudioBytes;
}
leftAudioBytes -= writeLen;
byte[] part = Arrays.copyOfRange(byteArray, index * byteWrites, index * byteWrites + writeLen);
index++;
AiRequest.Builder aiRequest = AiRequest.builder();
//协议头部,用于描述平台特性的参数
aiRequest.header()
.param("uid", mUid)
.param("session", mSession);
if (checkbox_nit.isChecked()) { //非打断模式
//需要实时返回状态数据时传入
aiRequest.service("vms_dispatch");
AiRequest.Builder paramBuilder = AiRequest.builder();
paramBuilder.param("vmr_status", 1);
paramBuilder.param("vmr_action_status", 1);
aiRequest.param("realtime_status", paramBuilder);
}
AiAudio aiAudio = null;
if (index <= 1) {
aiAudio = AiAudio.get("audio").data(part).begin().valid(); //开始帧
} else if (isLast || !isAudioRunning) {
aiAudio = AiAudio.get("audio").data(part).end().valid(); //结束帧
} else {
aiAudio = AiAudio.get("audio").data(part).cont().valid(); //中间帧
}
aiRequest.payload(aiAudio);//音频
int ret = VmsHelper.getInst().audioCtrl(aiRequest.build());
if (ret != 0) {
Log.e(TAG, "audioDrive:start write failed:" + ret);
return;
}
SystemClock.sleep(10); //防止太快了服务器处理不过来
}
#文本输入参考代码如下:
AiRequest.Builder aiRequest = AiRequest.builder();
aiRequest.header()
.param("uid", mUid)
.param("session", mSession)
.param("scene", "main_box");
AiText aiText = null;
if (checkbox_nit.isChecked()) { //非打断模式
aiText = AiText.get("text").data(text).end().valid();
} else {
aiText = AiText.get("text").data(text).once().valid();
}
aiRequest.payload(aiText);//文本
//动作协议
/**
* "type":"action" 动作
* value 动作类型
* wb 对应文本起始位置
* we 对应文本结束位置
*/
String cw = "{\"avatar\":[{\"type\":\"action\",\"value\":\"" + 动作id + "\",\"wb\":0,\"we\":" + text.length() + "}]}";
AiText aiCtrlW = AiText.get("ctrl_w").data(cw).once().valid();
aiRequest.payload(aiCtrlW);//动作
int pitch = 55; //(int) (50 + Math.random() * 100)
Log.d(TAG, "动作驱动文本:" + cw + "," + pitch);
//发音人
aiRequest.service("tts")
.param("vcn", vcn)
.param("speed", 48)
.param("pitch", pitch);
aiRequest.param("volume", 100);
aiRequest.service("vms_dispatch");
aiRequest.param("interactive_mode", interactiveMode);
//请求服务端实时返回状态数据
if (isWebSocket) {
AiRequest.Builder paramBuilder = AiRequest.builder();
paramBuilder.param("tts_status", 1);
paramBuilder.param("vmr_status", 1);
paramBuilder.param("vmr_action_status", 1);
aiRequest.param("realtime_status", paramBuilder);
}
int ret = VmsHelper.getInst().textCtrl(aiRequest.build());
aiRequest.clear();
Log.d(TAG, "enterText ret:" + ret);
if (ret != 0) {
showTip("输入文本指令失败");
}
#interactive_mode、vms_dispatch参数说明:
功能标识 | 功能描述 | 数据类型 | 取值范围 | 必填 | 默认值 |
---|---|---|---|---|---|
interactive_mode | 打断模式 | int | [0,1] (0 追加模式 1 打断模式) | 否 | 1 打断模式 |
realtime_status | 实时返回状态数据 | object | 否 | ||
tts_status | 文本合成状态 0 关闭 1打开 | int | 0 - 1 | 0 | |
vmr_status | 渲染引擎文本实时响应状态 0 关闭 1打开 | int | 0 - 1 | 0 | |
vmr_action_status | 渲染引擎动作实时响应状态 0 关闭 1打开 | int | 0 - 1 | 0 |
#4.4.3 上传背景
#参考代码如下:
String base64Str = Base64.encodeToString(readStream(filePath), Base64.DEFAULT);
AiRequest.Builder aiRequest = AiRequest.builder();
aiRequest.header().param("app_id", getString(R.string.appid)).param("uid", mUid).end();
AiText aiText = AiText.get("background_data").data(base64Str).once().valid();
aiRequest.payload(aiText);//文本
int code = VmsHelper.getInst().loadData(aiRequest.build());
Log.d(TAG, "uploadBackground:output code is " + code);
if (code != 0) {
showTip("背景上传失败:" + code);
}
#上传成功后,可在上文注册的 AiResponseListener 回调中获取 res_id ,在 VmsHelper.getInst().start 接口参数中传入,即可显示背景。
#4.4.4 重置接口
AiRequest.Builder aiRequest = AiRequest.builder();
aiRequest.header()
.param("uid", mUid)
.param("session", mSession);
int ret = VmsHelper.getInst().reset(aiRequest.build());
if (ret == 0) {
isAudioRunning = false;
interactiveModeIndex = 0; //如为追加模式,下次驱动需要重新从首帧开始
}
Log.d(TAG, "reset:" + ret);
#4.4.5 更新VmsConfig
VmsHelper.getInst().updateVmsConfig 用于更新start接口传入的VMSConfig参数(全量更新),如切换前正在进行中会话的追加模式/打断模式
VMSConfig vmsConfig = VMSConfig.builder()
.useWebSocket(isWebSocket) //是否使用使用ws接口,非实ws接口不支持时返回状态数据 默认使用ws接口
.interactiveMode(InteractiveMode.valueOf(interactiveMode)) //模式 0 追加模式。1打断模式 可在会话期间更改
.build();
VmsHelper.getInst().updateVmsConfig(vmsConfig);
#4.4.6 回调
#4.4.7 关闭虚拟人
//关闭虚拟人``int` `ret = VmsHelper.getInst().end();
#4.5 设置视频流相关监听
VmsHelper.getInst().setXrtcListener(new VmsHelper.XrtcListener() {
@Override
public void onUserVideoAvailable(String userId, boolean available) {
}
@Override
public void onError(int errCode, String errMsg, Bundle extraInfo) {
}
@Override
public void onFirstVideoFrame(String userId, int streamType, int width, int height) {
}
@Override
public void onNetworkQuality(IXRTCCloudDef.IXRTCQuality localQuality, ArrayList<IXRTCCloudDef.IXRTCQuality> remoteQuality) {
}
@Override
public void onConnectionLost() {
}
@Override
public void onTryToReconnect() {
}
@Override
public void onConnectionRecovery() {
}
@Override
public void onEnterRoom(int result) {
}
@Override
public void onExitRoom(int reason) {
}
@Override
public void onRenderVideoFrame(String userId, int streamType, IXRTCCloudDef.IXRTCVideoFrame videoFrame) {
}
});
#4.5.1 自定义视频渲染回调说明
出入参 | 参数 | 说明 |
---|---|---|
IN | userId | 视频源的 userId,该参数可以不用理会 |
IN | streamType | 视频流类型,例如是摄像头画面还是屏幕分享画面等等 |
IN | frame | 视频帧数据 |
RETURN | void | 无 |
属性 | 类型 | 必填 | 描述 |
---|---|---|---|
pixelFormat | int | 是 | 视频的像素格式,透明通道视频帧为BGRA格式 |
bufferType | int | 是 | 视频数据结构类 型,目前只支持XRTCVideoBufferType_Buffer |
data | byte[] | 是 | 视频数据 |
width | int | 是 | 视频宽度 |
height | int | 是 | 视频高度 |
timestamp | long | 是 | 视频帧的时间戳,单位毫秒 |
rotation | int | 是 | 视频像素的顺时针旋转角度 |
#4.6 逆初始化SDK
JLibrary.getInst().unInit();
#五.错误码
#5.1 SDK错误码列表
错误码 | 问题分类 | 含义 |
---|---|---|
错误码 | 问题分类 | 含义 |
18000 | 授权错误 | 本地 license 文件不存在 |
18001 | 授权错误 | 授权文件内容非法 |
18002 | 授权错误 | 授权文件解析失败 |
18003 | 授权错误 | payload 内容缺失 |
18004 | 授权错误 | signature 内容缺失 |
18005 | 授权错误 | 授权已过期 |
18006 | 授权错误 | 授权时间错误,设备时间比标准时间慢 30 分钟以上 |
18007 | 授权错误 | 授权应用不匹配(apiKey、apiSecret) |
18008 | 授权错误 | 授权文件激活过期 |
18009 | 授权错误 | 授权 App 信息指针为空 |
18010 | 授权错误 | 离线授权激活文件指定平台与设备平台不匹配 |
18011 | 授权错误 | 离线授权激活文件指定架构与设备 CPU 架构不匹配 |
18012 | 授权错误 | 离线授权激活文件中包含 license 个数异常 |
18013 | 授权错误 | 离线授权激活文件中未找到当前设备 |
18014 | 授权错误 | 离线授权激活文件中设备指纹安全等级非法 |
18015 | 授权错误 | 硬件授权验证失败 |
18016 | 授权错误 | 离线授权激活文件内容非法 |
18017 | 授权错误 | 离线授权激活文件中协议头非法 |
18018 | 授权错误 | 离线授权激活文件中指纹组成项个数为0 |
18100 | 资源错误 | 资源鉴权失败 |
18101 | 资源错误 | 资源格式解析失败 |
18102 | 资源错误 | 资源(与引擎)不匹配 |
18103 | 资源错误 | 资源参数不存在(指针为 NULL) |
18104 | 资源错误 | 资源路径打开失败 |
18105 | 资源错误 | 资源加载失败,workDir 内未找到对应资源 |
18106 | 资源错误 | 资源卸载失败, 卸载的资源未加载过 |
18200 | 引擎错误 | 引擎鉴权失败 |
18201 | 引擎错误 | 引擎动态加载失败 |
18202 | 引擎错误 | 引擎未初始化 |
18203 | 引擎错误 | 引擎不支持该接口调用 |
18204 | 引擎错误 | 引擎 craete 函数指针为空 |
18300 | SDK错误 | SDK 不可用 |
18301 | SDK错误 | SDK 没有初始化 |
18302 | SDK错误 | SDK 初始化失败 |
18303 | SDK错误 | SDK 已经初始化 |
18304 | SDK错误 | SDK 不合法参数 |
18305 | SDK错误 | SDK 会话 handle 为空 |
18306 | SDK错误 | SDK 会话未找到 |
18307 | SDK错误 | SDK 会话重复终止 |
18308 | SDK错误 | 超时错误 |
18309 | SDK错误 | SDK正在初始化中 |
18310 | SDK错误 | SDK会话重复开启 |
18400 | 系统错误 | 工作目录无写权限 |
18401 | 系统错误 | 设备指纹获取失败,设备未知 |
18402 | 系统错误 | 文件打开失败 |
18403 | 系统错误 | 内存分配失败 |
18404 | 系统错误 | 设备指纹比较失败 |
18500 | 参数错误 | 未找到该参数 key |
18501 | 参数错误 | 参数范围溢出,不满足约束条件 |
18502 | 参数错误 | SDK 初始化参数为空 |
18503 | 参数错误 | SDK 初始化参数中 AppId 为空 |
18504 | 参数错误 | SDK 初始化参数中 ApiKey为空 |
18505 | 参数错误 | SDK 初始化参数中 ApiSecret 为空 |
18506 | 参数错误 | ability 参数为空 |
18507 | 参数错误 | input 参数为空 |
18508 | 参数错误 | 输入数据参数 Key 不存在 |
18509 | 参数错误 | 必填参数确实 |
18510 | 参数错误 | output参数缺失 |
18520 | 编解码错误 | 不支持的编解码类型 |
18521 | 编解码错误 | 编解码handle指针为空 |
18522 | 编解码错误 | 编解码模块条件编译未打开 |
18600 | 协议错误 | 协议中时间戳字段缺失 |
18601 | 协议错误 | 协议中未找到该能力 ID |
18602 | 协议错误 | 协议中未找到该资源 ID |
18603 | 协议错误 | 协议中未找到该引擎 ID |
18604 | 协议错误 | 协议中引擎个数为 0 |
18605 | 协议错误 | 协议未被初始化解析 |
18606 | 协议错误 | 协议能力接口类型不匹配 |
18607 | 协议错误 | 预置协议解析失败 |
18608 | 云端错误 | 能力不存在,appid未授权或授权已过 期 |
18700 | 云端错误 | 通用网络错误 |
18701 | 云端错误 | 网络不通 |
18702 | 云端错误 | 网关检查不过 |
18703 | 云端错误 | 云端响应格式不对 |
18704 | 云端错误 | 应用未注册 |
18705 | 云端错误 | 应用 ApiKey & ApiSecret 校验失败 |
18706 | 云端错误 | 引擎不支持的平台架构 |
18707 | 云端错误 | 授权已过期 |
18708 | 云端错误 | 授权数量已满 |
18709 | 云端错误 | 未找到该 App 绑定的能力 |
18710 | 云端错误 | 未找到该 App 绑定的能力资源 |
18711 | 云端错误 | SDK请求参数云端无法解析 |
18712 | 云端错误 | 网络请求 404 错误 |
18713 | 云端错误 | 设备指纹安全等级不匹配 |
18714 | 云端错误 | 服务端无法查询到api_key,请检查api_key和api_secret信息是否填写正确 |
18715 | 云端错误 | 未找到该SDK ID |
18716 | 云端错误 | 未找到该组合能力集合 |
18717 | 云端错误 | SDK组合能力授权不足 |
18718 | 云端错误 | 无效授权应用签名 |
18719 | 云端错误 | 应用签名不唯一 |
18720 | 授权错误 | 能力schema不可用 |
18721 | 授权错误 | 竞争授权: 未找到能力集模板 |
18722 | 授权错误 | 竞争授权: 能力不在模板能力集模板中 |
18801 | 在线能力错误 | 连接建立出错 |
18802 | 在线能力错误 | 结果等待超时 |
#5.2 常见云端错误码
ret="20014", ws打断功能异常
ret="10101", 引擎回话 已结束
ret="10110", 授权不足
ret="10221", 服务端没有可用连接
ret="10222", 请求超时
ret="11200", 功能未授权或授权到期
ret="11203", 并发流控超限
ret="20001", session 无效
ret="20004", 请求类型异常
ret="20007", 动作指令异常
ret="20015", 当前形象id 不存在
ret="20017", 文本审核不通过
ret="10163", json shema 校验异常
ret="20012", 同步请求调用超时
ret="20016", 当前发音人不存在
修改于 2023-12-19 06:19:32