# 1. 总体说明
# 1.1 简介
虚拟人交互 SDK(以下简称 AIKit)为接入方提供支持虚拟人服务与视频流服务方案的SDK。接入方需按此集成文档集成 SDK,以使用相关能力。
# 1.2 适用范围
本文档定义了 AIKit 在iOS操作系统中的使用说明和体系结构及API接口。
# 1.3 开发准备
开发者在使用iOS SDK进行开发前,需要经过我们的授权,获取对应的appId、apiKey、apiSecret、avatarid、vcn。
# 1.4 支持平台
支持iOS9.0及以上的平台,支持arm64 架构。
# 2. 使用说明
# 2.1 申请使用
请与平台业务人员联系获取集成 SDK 包,需要提供接入方客户端包名、应用签名等信息,并获取接入方 appId、apiKey、apiSecret、虚拟形象、发音人,以及其他相关资源。
# 3. SDK集成
# 3.1 集成步骤
AIKit.framework
、XRTCSDK.framework
添加至目标项目中。在主工程的 TARGET > Bulid Phases > Link Bianry With Libraries 添加以下依系统赖库。
libc++.tbd libc++abi.tbd Security.framework MobileCoreServices.framework CFNetwork.framework CoreMedia.framework VideoToolbox.framework AudioToolbox.framework CoreVideo.framework CoreAudio.framework CoreMotion.framework
在工程配置Build Settings里的Other Linker Flags中添加-ObjC标记,BitCode 设置为NO。
如图所示:

主工程 Info.plist添加权限设置。
Privacy - Microphone Usage Description #并添加文字说明
主工程添加以下三方开源库
# 建议以cocoPods,carthage方式集成最新版本,需要添加 use_frameworks,可参见demo中的podfile #CocoaLumberjack要求最低版本3.0.0 CocoaLumberjack.framework #SocketRocket要求最低版本0.5.0 SocketRocket.framework
# 4. SDK使用
# 4.1 SDK初始化
初始化SDK,传入相关鉴权参数。
/**
* SDK初始化函数用以初始化整个SDK
* @param param SDK配置参数
* @return 结果错误码,0=成功
*/
int ret = [ILibrary initSDK:^(AiHelperMaker *maker) { maker.appId(@"appId").apiKey(@"apiKey").apiSecret(@"apiSecret").authInterval(300).logOpen(true).iLogOpen(true).workDir(@"xx/xx/workDir");
}];
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
appId | string | 应用ID | 是 |
apiKey | string | 唯一的应用标识 | 是 |
apiSecret | string | 唯一的应用密钥 | 是 |
authInterval | int | 授权更新间隔,单位为秒 | 否,默认为300秒 |
iLogOpen | bool | 是否开启控制台日志输出 | 否 |
logOpen | bool | 是否开始日志输出文件 | 否 |
workDir | string | sdk工作目录 | 否 |
# 4.2 配置虚拟人能力回调、渲染视图
//设置虚拟人能力回调代理
[AiHelper shareInstance].delegate = self;
//设置虚拟人拉流渲染视图
AiHelper.shareInstance.virtualView = self.remoteView;
能力输出回调:AIKitCoreDelegate协议
//result 回调
- (void)aikitOnResult:(NSString*)ability outputData:(NSArray<AIKITDataModel*>*)data usrCtx:(AIKITUserContext*)context;
//event 回调 event:19表示ws已经关闭连接
- (void)aikitOnEvent:(NSString*)ability event:(NSInteger)event eventData:(NSArray<AIKITDataModel*>*)data usrCtx:(AIKITUserContext*)context;
//error 回调
- (void)aikitOnError:(NSString*)ability error:(AIKITError*)error usrCtx:(AIKITUserContext*)context;
# 4.3 上传个性化数据
/// 上传个性化数据
/// @param ability 能力标识
/// @param data 数据
+ (AIKITOutput*)loadDataSync:(NSString*)ability data:(AIKITCustomData*)data;
代码示例:
/// 例如:获取上传背景的res_id
- (nullable NSString *)getResidWithUploadData:(NSData *)fileData{
AIKITCustomData *customData = [[AIKITCustomData alloc] init];
NSString *base64Str = [fileData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
[customData addText:base64Str key:@"background_data" index:0];
AIKITOutput *output = [AiHelper loadDataSync:VMS_ABILITY data:customData];
if (output.code != 0) {
NSLog(@"loadData failed code:%ld",(long)output.code);
return nil;
}
if (output.data.count > 0) {
for (AIKITDataModel *model in output.data) {
Byte *bytes = (Byte *)model.value;
if (bytes == NULL) {
continue;
}
NSData *data = [[NSData alloc] initWithBytes:bytes length:model.len];
NSString *value = [NSString stringWithUTF8String:[data bytes]];
if ([model.key isEqualToString:@"result"]) {
NSError *error;
NSDictionary *dic = [VMTools jsonToArrayOrDictionary:value];
if ([dic.allKeys containsObject:@"data"]) {
NSDictionary *resultData = dic[@"data"];
return resultData[@"res_id"];
}
}
}
}
}
#
/// 上传字幕模板数据,注意字幕模板上传后需要进行文本驱动,详细见demo
AIKITCustomData *customData = [[AIKITCustomData alloc] init];
NSString *base64Str = [data base64EncodedStringWithOptions:0];
if (!kStringIsEmpty(base64Str)) {
[customData addText:base64Str key:@"template_data" index:0];
}
_template_id = [self getResidWithUploadCustomData:customData];
# 4.4 启动虚拟人服务
/// 启动会话
/// @param ability 能力标识
/// @param param AIKITParameters能力参数
/// @param content 用户自定义标识
+ (AiHandle*)start:(NSString*)ability param:(nullable AIKITParameters*)param ctxContent:(nullable AIKITCtxContent*)content;
创建AIKITParameters
:
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
uid | string | 唯一标识 | 是 |
avatarid | string | 形象ID | 是 |
width | int | 视频分辨率:宽 | 是,默认为1280 |
height | int | 视频分辨率:高 | 否,默认为720 |
scale | double | 主播在背景图像中的大小 (0,1.0] | 否,默认为1 |
maskRegion | bool | 形象截取区域,用于控制形象展示区域,比如可以截取半身等 示例 : [0,0,1080,1920] | 否 |
moveH | int | 主播平移像素距离 [-4096, +4096],控制主播中心位置距离合成图像中心位置水平距离,为负数表示向左平移,为正数表示向右平移 | 否,默认为0 |
moveV | int | 主播垂直移动像素距离 [-4096, +4096],控制主播在展示画面中的上下移动距离,0为默认情况,主播在画面中下贴边位置。该值为负数表示向下移动,为正数表示向上移动 | 否,默认为0 |
res_id | string | 上传背景生成唯一标识 | 否 |
interactive_scene | string | 部分形象需要传值为type=live | 否 |
template_id | string | 后处理模板生成的唯一标识 | 否 |
# 4.5 文本驱动
/// 会话模式输入数据
/// @param data AIKITInputData能力数据
/// @param handle 启动服务的handle对象
/// @param param AIKITParameters能力参数
+ (int)write:(nullable AIKITInputData*)data handle:(nonnull AiHandle *)handle param:(nullable AIKITParameters *)param;
代码示例:
AIKITParameters *param = [[AIKITParameters alloc] init];
//发音人
[param addSection:@"tts"];
[param addString:@"vcn" value:vcn];
//文本驱动传rhy有字幕效果,其他可不传入
[param addInt:@"rhy" value:3];
//打断模式:追加、打断
[param addSection:@"vms_dispatch"];
[param addInt:@"interactive_mode" value:interactiveMode];
AIKITInputData *input = [[AIKITInputData alloc] init];
//合成文本
[input addText:text key:@"text" status:DataStatusOnce];
//动作
if (actions.count == 0){
[input addText:@"" key:@"ctrl_w" status:DataStatusOnce];
}else{
NSMutableArray *avatarArray = [NSMutableArray array];
for (NSString *action in actions) {
NSDictionary *dic = @{@"type":@"action",
@"value":action,
@"wb":@2,
@"we":@10,
};
[avatarArray addObject:dic];
}
NSDictionary *avatarDic = @{@"avatar":avatarArray.copy};
NSData *data = [NSJSONSerialization dataWithJSONObject:avatarDic options:NSJSONWritingPrettyPrinted error:nil];
[input addTextData:data key:@"ctrl_w" status:DataStatusOnce];
}
//后处理字幕相关
if (!kStringIsEmpty(_template_id)){
NSString *path = [ConfigManager.sharedInstance getSandBoxDefaultFilePath:ConfigJsonFileTypeWordLevel3];
NSData *templateData = [[NSData alloc] initWithContentsOfFile:path];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:templateData
options:kNilOptions
error:nil];
TemplateModel *model = [TemplateModel yy_modelWithDictionary:json];
if (!kStringIsEmpty(_res_id)){
model.controls.firstObject.elements.firstObject.resource.res = _res_id;
}
NSString *jsonStr = [model yy_modelToJSONString];
if(!kStringIsEmpty(jsonStr)){
[input addText:jsonStr key:@"ctrl_postproc" status:DataStatusOnce];
}
}
//文本、动作附属参数
AIKITParameters *inputParam = [[AIKITParameters alloc] init];
[inputParam addString:@"encoding" value:@"utf8"];
[inputParam addString:@"compress" value:@"raw"];
[inputParam addString:@"format" value:@"plain"];
[input addControl:inputParam key:@"text"];
[input addControl:inputParam key:@"ctrl_w"];
//写入驱动
int ret = [AiHelper write:input handle:self.handle param:param];
if (ret != 0) {
NSLog(@"vms write failed! ret is %ld", (long)ret);
}
创建param
:
tts
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
vcn | string | 合成发言人 | 是 |
rhy | int | 文本驱动传rhy有字幕效果 | 否 |
vms_dispatch
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
interactive_mode | int | 打断模式 [0,1] (0 追加模式 1 打断模式) | 否,默认为1 |
创建input
:
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
text | string | 合成的文本数据 | 是 |
ctrl_w | data | 字偏移控制指令 | 否 |
ctrl_postproc | string | 后处理控制指令 | 否 |
control
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
text | object | 含有 encoding、compress、format参数 | 是 |
encoding | string | 文本编码: utf8 | 是 |
compress | string | 文本压缩格式: raw | 是 |
format | string | 文本格式: raw | 是 |
ctrl_w | object | 含有 encoding、compress、format参数,动作相关 | 否 |
ctrl_postproc | object | 含有 encoding、compress、format参数,后处理相关 | 否 |
# 4.5 ws文本驱动 - 支持回调播报状态
/// 会话模式输入数据
/// @param data AIKITInputData能力数据
/// @param handle 启动服务的handle对象
/// @param param AIKITParameters能力参数
+ (int)write:(nullable AIKITInputData*)data handle:(nonnull AiHandle *)handle param:(nullable AIKITParameters *)param;
代码示例:
DataStatus status = DataStatusEnd;
if (_wsTextStatus == 0) {
status = DataStatusBegin;
}else {
status = DataStatusContinue;
}
if (_wsTextStatus == 0) {
_wsTextStatus ++;
}
AIKITParameters *param = [[AIKITParameters alloc] init];
[param addSection:@"tts"];
[param addString:@"vcn" value:vcn];
//文本驱动传rhy有字幕效果,其他可不传入
[param addInt:@"rhy" value:3];
// [param addSection:@"realtime_status"];
// [param addInt:@"tts_status" value:1];
// [param addInt:@"vmr_status" value:1];
// [param addInt:@"vmr_action_status" value:1];
[param addSection:@"vms_dispatch"];
[param addInt:@"interactive_mode" value:interactiveMode];
[param addSection:@"header"];
[param addInt:@"status" value:status];
AIKITInputData *input = [[AIKITInputData alloc] init];
[input addText:text key:@"text" status:status];
//动作
if (actions.count == 0){
[input addText:@"" key:@"ctrl_w" status:DataStatusOnce];
}else{
NSMutableArray *avatarArray = [NSMutableArray array];
for (NSString *action in actions) {
NSDictionary *dic = @{@"type":@"action",
@"value":action,
@"wb":@2,
@"we":@10,
};
[avatarArray addObject:dic];
}
NSDictionary *avatarDic = @{@"avatar":avatarArray.copy};
NSData *data = [NSJSONSerialization dataWithJSONObject:avatarDic options:NSJSONWritingPrettyPrinted error:nil];
[input addTextData:data key:@"ctrl_w" status:DataStatusOnce];
}
AIKITParameters *inputParam = [[AIKITParameters alloc] init];
[inputParam addString:@"encoding" value:@"utf8"];
[inputParam addString:@"compress" value:@"raw"];
[inputParam addString:@"format" value:@"plain"];
//后处理字幕相关
if (!kStringIsEmpty(_template_id)){
NSString *path = [ConfigManager.sharedInstance getSandBoxDefaultFilePath:ConfigJsonFileTypeWordLevel3];
NSData *templateData = [[NSData alloc] initWithContentsOfFile:path];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:templateData
options:kNilOptions
error:nil];
TemplateModel *model = [TemplateModel yy_modelWithDictionary:json];
if (!kStringIsEmpty(_res_id)){
model.controls.firstObject.elements.firstObject.resource.res = _res_id;
}
NSString *jsonStr = [model yy_modelToJSONString];
if(!kStringIsEmpty(jsonStr)){
[input addText:jsonStr key:@"ctrl_postproc" status:DataStatusOnce];
}
}
[input addControl:inputParam key:@"text"];
[input addControl:inputParam key:@"ctrl_w"];
[input addControl:inputParam key:@"ctrl_postproc"];
int ret = [AiHelper write:input handle:self.handle param:param];
if (ret != 0) {
NSLog(@"vms write failed! ret is %ld text: %@", (long)ret,text);
}
创建param
:
header
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
status | int | 数据状态 0:开始, 1:开始, 2:结束 | 是 |
tts
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
vcn | string | 合成发言人 | 是 |
rhy | int | 文本驱动传rhy有字幕效果 | 否 |
vms_dispatch
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
interactive_mode | int | 打断模式 [0,1] (0 追加模式 1 打断模式) | 否,默认为1 |
创建input
:
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
text | string | 合成的文本数据 | 是 |
ctrl_w | data | 字偏移控制指令 | 否 |
ctrl_postproc | string | 后处理控制指令 | 否 |
control
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
text | object | 含有 encoding、compress、format等参数 | 是 |
encoding | string | 文本编码: utf8 | 是 |
compress | string | 文本压缩格式: raw | 是 |
format | string | 文本格式: raw | 是 |
ctrl_w | object | 含有 encoding、compress、format等参数,动作相关 | 否 |
ctrl_postproc | object | 含有 encoding、compress、format等参数,后处理相关 | 否 |
# 4.6 音频驱动
/// 会话模式输入数据
/// @param data AIKITInputData能力数据
/// @param handle 启动服务的handle对象
+ (int)write:(nullable AIKITInputData*)data handle:(nonnull AiHandle *)handle;
代码示例:
int ret = 0;
//一次性音频文件
int leftAudioBytes = (int)audioData.length;
int byteWrites = 1280;
int writeLen = 0;
int x_sb = 0;
while (leftAudioBytes > 0) {
bool isLast = false;
if (leftAudioBytes > byteWrites) {
writeLen = byteWrites;
} else {
isLast = true;
writeLen = leftAudioBytes;
}
leftAudioBytes -= writeLen;
int loc_dsb = x_sb*byteWrites;
x_sb ++;
@autoreleasepool {
AIKITInputData *inputs = [[AIKITInputData alloc] init];
NSData *part = [audioData subdataWithRange:NSMakeRange(loc_dsb, writeLen)];
//传入音频数据
if (loc_dsb == 0) {
[inputs addAudio:part key:@"audio" status:DataStatusBegin];
} else if (isLast) {
[inputs addAudio:part key:@"audio" status:DataStatusEnd];
} else {
[inputs addAudio:part key:@"audio" status:DataStatusContinue];
}
//音频附属参数
AIKITParameters *audioParam = [[AIKITParameters alloc] init];
[audioParam addString:@"encoding" value:@"raw"];
[audioParam addInt:@"sample_rate" value:16000];
[audioParam addInt:@"seq" value:x_sb];
[inputs addControl:audioParam key:@"audio"];
ret = [AiHelper write:inputs handle:self.handle];
}
if (ret != 0) {
NSLog(@"音频写入失败:%d",ret);
return;
}
[NSThread sleepForTimeInterval:0.04]; //间隔时长
}
创建input
:
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
audio | data | 驱动音频 | 是 |
ctrl_t | data | 字偏移控制指令 | 否 |
control
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
audio | object | 含有 encoding、sample_rate、seq等参数 | 是 |
encoding | string | 编码格式: 默认值raw | 是 |
sample_rate | int | 采样率: 16000 | 是 |
seq | int | 数据序号:标明数据为第几块 | 是 |
ctrl_t | object | 含有 encoding、sample_rate、seq等参数 | 否 |
# 4.7 重置接口
reset 接口用于中止当前正在进行中的所有驱动任务,回到虚拟形象静默状态
/**
* 中止进行中的驱动
* @return 结果错误码,0=成功
*/
int ret = [AiHelper reset:self.handle];
# 4.8 结束虚拟人服务
/**
* 结束会话实例
* @return 结果错误码,0=成功
*/
int ret = [AiHelper end:self.handle];
# 4.9 SDK逆初始化
/**
* SDK逆初始化函数用以释放SDK所占资源
* @return 结果错误码,0=成功
*/
int ret = [ILibrary unInit];
# 字偏移控制指令
ctrl_w
参数 | 类型 | 说明 | 必填 |
---|---|---|---|
avatar | [] | 形象控制,单次输入含1个或多个控制项 | 是 |
type | string | 控制数据类型,支持动作控制 取值: action | 是 |
value | 控制数据取值 | 动作id 如:A_LH_introduced_O | 是 |
wb | int | 字偏移起始位置,char begin | 文本驱动需要;音频驱动不需要该字段 |
we | char | 字偏移终止位置,char end | 动作终止位置(暂不支持)音频驱动时,不要传该字段 |
# 字幕
字幕相关的模板详细见demo,其中subtitle_portrait.json
竖屏字幕模板,subtitle_landscape.json
横屏字幕模板,demo中的字幕模板默认用的subtitle_portrait.json
,想看demo中横屏效果,修改config_new.json
中的 宽高为1920 × 1080,字幕模板使用subtitle_landscape.json
,字幕模板上传后需重新启动虚拟人服务然后进行文本驱动