iOS常见开发问题(全链路)

1、DDS相关配置项

Q1:如何配置成外部录音机模式?

A:DDS初始化之前可配置以下选项可将录音机模式改成外部录音机。

NSMutableDictionary *params = [[NSMutableDictionary alloc]init]; ... //设置外部录音机模式 
[params setObject:@"external" forKey:K_RECORDER_MODE]; ... 
[[DDSManager shareInstance] startWithdelegate:self DDSConfig:params];

Q2:外部录音机模式下,支持哪几种音频格式的音频输入?接口分别是什么?

A:外部录音机模式下支持自定义输入pcm、opus、sbc格式的音频,提供的接口如下。

//feedPcm 
[[DDSManager shareInstance] feedPcm:pcm]; 
//feedSbc 
[[DDSManager shareInstance] feedSbc:sbc]; 
//feedopus 
[[DDSManager shareInstance] feedOpus:opus];

注:外部录音机使用接口,必须将配置项K_RECORDER_MODE设为external。

Q3:如何切换场景模式

A:DDS场景模式有两种,分别为正常模式和静音模式。

//场景模式切换, 参数:1,正常模式;0,静音模式 
[[DDSManager shareInstance] setDDSMode:1]; 
[[DDSManager shareInstance] setDDSMode:0];

Q4:iOS系统音频队列相关设置

A:一般使用SDK之前需要设置iOS的音频策略。

//初始化SDK之前调用此方法,初始化音频策略。 
- (void)setAudioConfig{    
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord  withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionMixWithOthers error:nil];    
[[AVAudioSession sharedInstance] setActive:YES error:nil]; }

注:音频策略有可能会被其他app修改,在使用SDK之前请确保业务逻辑层切到正确的音频策略。

Q5:常用常规相关配置

A:一般常用的常规配置如下。

DDSManager *manager = [DDSManager shareInstance]; NSMutableDictionary *paramDic = [[NSMutableDictionary alloc] init]; 
[paramDic setObject:@"27xxxxxxx" forKey:K_PRODUCT_ID];     //产品ID 
[paramDic setObject:@"prod" forKey:K_ALIAS_KEY];      //分支 
[paramDic setObject:@"1dsfdfd" forKey:K_USER_ID];      //USER_ID 
[paramDic setObject:@"duicore.zip" forKey:K_DUICORE_ZIP];    //内核路径 
[paramDic setObject:@"product.zip" forKey:K_CUSTOM_ZIP];    //资源路径 
[paramDic setObject:@"profile" forKey:K_AUTH_TYPE];      //授权类型 //授权新增 
[paramDic setObject:@"88xxxxxxxxxxxxxxxxxxxx" forKey:K_API_KEY];  //API_KEY 
[paramDic setObject:@"76xxxxxxxxxxxxxxxxxxxx" forKey:K_PRODUCT_KEY]; //PRODUCT_KEY [paramDic setObject:@"d0xxxxxxxxxxxxxxxxxxxx" forKey:K_PRODUCT_SECRET]; //PRODUCT_SECRET 
int serverType = 2; 
[manager startWithdelegate:self DDSConfig:paramDic withConfigServerType:serverType];

Q6:错误提示音以及识别提示音如何关闭?

A:关闭识别提示音以及关闭错误提示音操作如下。

//关闭识别提示音 
[params setObject:@"false" forKey:K_ASR_TIPS]; 
//关闭错误提示音 
[params setObject:@"true" forKey:K_CLOSE_TIPS];

Q7:调试日志导出相关操作?

A:DDS导出日志相关步骤如下。

//SDK日志 
//日志目录:Documents/ SDKLog.log 
[DDSManager setLogWriteToFile:YES];  
/内核日志 
//日志目录:Documents/ duicore/ AIEVTLOG.txt 
[params setObject:[NSNumber numberWithInt:1] forKey:K_LOG_LEVEL]; 
[params setObject:@"true" forKey:@"DEBUG_MODE"]; 

注:如有崩溃请附带崩溃栈(符号化的崩溃栈)以及问题发生时间点,请确保崩溃栈指向 SDK。

Q8:如何响应nativeApi信息?

A:在语音交互中,若客户端想同步设备端的数据至技能中,可通过nativeApi消息实现,在产品技能中配置nativeApi指令,客户端需实现SDK的NativeApiObserver的AINativeApiDelegate回调方法-(void)onQuery:(NSString )nativeApi data:(NSString)data,并在其中做出响应,然后根据控件类型,回传数据至服务端,参考nativeAPI编写说明

//NativeApiObserver对象 
@property(nonatomic,strong) NativeApiObserver *nativeApiObserver;  
//初始化并设置AINativeApiDelegate 
if(!self.nativeApiObserver){    
 self.nativeApiObserver = [[NativeApiObserver alloc] init];    、 
 self.nativeApiObserver.delegate = self;
}  
//订阅相关NativeApi 
NSArray *nativeApiArray = [[NSArray alloc] initWithObjects:@"sys.query.contacts", nil]; 
[[DDSManager shareInstance] subscribe:nativeApiArray observer:self.nativeApiObserver]; 
//实现代理方法 
//NativeApiObserver的消息响应 
 -(void)onQuery:(NSString *)nativeApi data:(NSString *)data {   
  //拨打联系人    
  if ([nativeApi isEqualToString:@"sys.query.contacts"]) {        
  NSLog(@"%@, sys.query.contacts data:%@", TAG, data);     
  NSMutableDictionary  *dic = [self dictionaryWithJsonString:data];     
  ListWidget * listWidget = [[ListWidget alloc] init];      
  ContentWidget * contentWidget = [[ContentWidget alloc] init];   
  if ([[dic allKeys] containsObject:@"联系人"]) {    
   [contentWidget setTitle:[dic objectForKey:@"联系人"]];    
    if ([[contact.myDict allKeys] containsObject:[dic objectForKey:@"联系人"]]) {    
    [contentWidget setSubTitle:[contact.myDict objectForKey:[dic objectForKey:@"联系人"]]]; 
    [contentWidget addExtra:@"phone" Value:[contact.myDict objectForKey:[dic objectForKey:@"联系人"]]]; 
   } else {  
    NSLog(@"%@, 本地没有这个联系人",TAG);           
    }
   } else {        
   NSLog(@"%@, 服务端没有返回联系人",TAG);        
  }        
  [listWidget addContentWidget:contentWidget];        
  [[DDSManager shareInstance] feedbackNativeApiResult:nativeApi duiWidget:listWidget];         NSLog(@"%@, 收到联系人的消息,调用feedbackNativeApiResult反馈给服务端", TAG);  
 }
}

注:-(void)feedbackNativeApiResult:(NSString*)nativeApi duiWidget:(id)duiWidget 此方法duiWidget回传有多种格式,请选择后台对应技能配置的相对应的控件回传。

Q9:如何响应command信息?

A:command消息是在DUI平台技能中配置的,语音对话服务会返回客户端指令, 详细见对话回复配置客户端动作。客户端需实现SDK的CommandObserver的AICommandDelegate回调方法-(void)onCall:(NSString )command data:(NSString)data,并在其中做出响应。

//CommandObserver对象 
@property(nonatomic,strong) CommandObserver *commandObserver;  
//初始化并设置AICommandDelegate 
if(!self.commandObserver){    
 self.commandObserver = [[CommandObserver alloc] init];    
 self.commandObserver.delegate = self; 
}  
//订阅相关commandApi 
NSArray *commandArray = [[NSArray alloc] initWithObjects: @"open.door", nil]; 
[[DDSManager shareInstance] subscribe:commandArray observer:self.commandObserver]; 
//实现代理方法
//CommandObserver的消息响应 
-(void)onCall:(NSString *)command data:(NSString *)data {    //响应命令唤醒词open.door      
 if([command isEqualToString:@"open.door"]) {   
  NSLog(@"%@, addCommandWakeup open.door data:%@", TAG, data);    
 }
 }

Q10:如何更新词库?

A:做更新词库的操作前,请确保该词库存在。

NSMutableArray *arr = [[NSMutableArray alloc]init]; 
[arr addObject:@"苏州电视台"]; 
[arr addObject:@"思必驰"]; 
NSString * reqId = [[DDSManager shareInstance]updateVocab:@"sys.歌手名" contents:arr addOrDelete:YES]; 
NSLog(@"%@, 更新词库接口调用了, 请求的ID:%@", TAG, reqId);

Q11:为什么更新词库会失败?

A:当错误码为 400时,一般有如下4种情况:

  1. 词库名不合法,词库名称种只能包含A-Z a-z 1-9 . 以及中文,不能含有特殊字符。

  2. 上传的数据格式不合法。

  3. 不符合规则的请求: {“errMsg”:“failed to validate item 1: string too long, expected at most 10, got 11”,“errId”:130031} 。

  4. 词库名称无法训练(产品可能没有训练语言模型,或者产品训练的语言模型中没有使用该词库), 这种情况服务除了回400错误码之外,body还会以json形式返回一个错误{“errMsg”:“vocab name not found in lexmap”,“errId”:130008}, 这个错误通常有两种情况 :

a. 对于较新的产品,已不支持默认生成latest的默认版本兜底,需要确保产品上传热词的时候带上了aliasKey参数指定分支(分支应当保证和访问ddsserver的分支一致)。

b. 保证技能中定制了对应pattern的说法,然后将该技能添加到对应解决方案或者产品中进行训练(如果是通过中加技能发布的话,需要确保定制了pattern的技能不是内置技能)如果具体不知道怎么操作,请联系技能团队。

Q12:如何自定义duicore.zip、product.zip、dds.bundle路径?

A:设置上述文件的自定义路径时,需要将其对应的md5文件放置到同级目录下。

//duicore.zip [paramDic setObject:@"xxx/xxx/duicore.zip" forKey:K_DUICORE_ZIP]; //product.zip [paramDic setObject:@"xxx/xxx/product.zip" forKey:K_CUSTOM_ZIP]; //dds.bundle [paramDic setObject:@"xxx/xxx/dds.bundle" forKey:@"DDS_BIN"];

注:duicore.zip 和 product.zip 的 md5文件需要放置到同目录下。

Q13:SDK如何同步位置信息?

A:SDK默认是有定位,如需自定义精确位置需同步位置信息,可在初始化SDK完成后,做如下操作。

NSMutableDictionary *dicData = [[NSMutableDictionary alloc] init]; 
[dicData setObject:@"苏州" forKey:@"city"];        //城市 
[dicData setObject:@"30.53543" forKey:@"latitude"];      //经纬度 
[dicData setObject:@"107.1243" forKey:@"longitude"]; 
[dicData setObject:@"2018-10-27T18:22:45+0800" forKey:@"time"]; 
NSString * reqId = @"UUID"; 
NSString *deviceJson = [self dataTOjsonString:dicData]; 
NSMutableArray * array = [[NSMutableArray alloc] init]; 
[array addObject:deviceJson]; 
[array addObject:reqId]; 
[[DDSManager shareInstance] publishTopic:@"upload.location" list:array];

Q14:开发调试环境下,如何获取对应的音频文件?

A:音频文件包括识别音频、vad音频、唤醒音频、tts音频等,保存开启方法如下。

[params setObject:@"/a/b" forKey:K_CACHE_PATH];  // 调试信息保存路径,如果不设置则保存在默认沙盒目录 [params setObject:@"true" forKey:K_ASR_DEBUG];  // 用于识别音频调试, 开启后在沙盒目录下会生成识别音频 
[params setObject:@"true" forKey:K_VAD_DEBUG];  // 用于过vad的音频调试, 开启后在沙盒目录下会生成过vad的音频 
[params setObject:@"true" forKey:K_TTS_CACHE]; 
[params setObject:@"true" forKey:K_TTS_DEBUG];  // 用于tts音频调试, 开启后在沙盒目录下会自动生成tts音频 
[params setObject:@"true" forKey:K_WAKEUP_DEBUG]; // 用于唤醒音频调试, 开启后在沙盒目录下会生成唤醒音频

注:release版本请务必关闭,否则可能会影响性能。

Q15:DDS初始化失败都有哪些原因?

A:常见的几个原因如下。

  1. 配置的产品id与product.zip文件里的产品id不匹配。

对应的日志: pid not match, probably another product scaned。

  1. 授权失败。

对应的日志: auth fail。

  1. 端口号占用。

检查是否已有加载dds的应用正在运行状态。

Q16:如何设置自定义音频文件,播报对应文字时,直接播报文件内容?

A:设置自定义音频文件后,播报设置的对应的name时,就会播报对应的音频文件的内容,设置代码如下。

//自定义音频文件 
NSMutableArray * array = [[NSMutableArray alloc] init];
NSMutableDictionary * pcmDic = [[NSMutableDictionary alloc] init]; 
NSString * pcmPath = [self getResourcesFile:@"nhxc.pcm"]; 
[pcmDic setObject:@"开始为您播放" forKey:@"name"]; 
[pcmDic setObject:@"pcm" forKey:@"type"]; 
[pcmDic setObject:pcmPath forKey:@"path"]; 
[array addObject:pcmDic];  
NSMutableDictionary * mp3Dic = [[NSMutableDictionary alloc] init]; 
NSString * mp3Path = [self getResourcesFile:@"zaine.mp3"]; 
[mp3Dic setObject:@"我在,有什么可以帮你" forKey:@"name"]; 
[mp3Dic setObject:@"mp3" forKey:@"type"]; 
[mp3Dic setObject:mp3Path forKey:@"path"]; 
[array addObject:mp3Dic];  
NSMutableDictionary * wavDic = [[NSMutableDictionary alloc] init]; 
NSString * wavPath =[self getResourcesFile:@"xiaole.wav"]; 
[wavDic setObject:@"为您播放" forKey:@"name"]; 
[wavDic setObject:@"wav" forKey:@"type"]; 
[wavDic setObject:wavPath forKey:@"path"]; 
[array addObject:wavDic];  
NSString * pcmJson = [[self dataTOjsonString:array] stringByReplacingOccurrencesOfString:@"\\" withString:@""]; 
[params setObject:pcmJson forKey:K_CUSTOM_AUDIO];

Q17:如何设置自定义deviceName?

A:设置自定义deviceName,需要更改两个配置,一个配置为是@“DEVICE_NAME”,一个是@“USE_LOCAL_DEVICE_NAME”。

[paramDic setObject:@"123455555" forKey:@"DEVICE_NAME"]; 
[paramDic setObject:@"true" forKey:@"USE_LOCAL_DEVICE_NAME"];

Q18:私有化部署有可能涉及到服务配置地址有那些?

[paramDic setObject:@"xxxxxx" forKey:@"AUTH_SERVER"];//授权地址 
[paramDic setObject:@"xxxxxx" forKey:@"TTS_SERVER"];//合成服务地址 
[paramDic setObject:@"xxxxxx" forKey:@"CBRIDGE_ADDR"];//对话服务地址

Q19:dds中网络监控判断在哪边能够接收处理?

A:可以订阅sys.network.type 这个topic来进行处理,在sdk使用中会反馈:

1,none。

2,Wi-Fi。

3,true(2G/3G/4G/5G)这三种情况

2、Asr相关FAQ

Q1:DDS中使用Asr如何使用?

A:在DDS中也有单独抛出的识别模块ASREngineManager提供使用。

//初始化并实现ASREngineManagerDelegate,开启监听识别。 
[[[DDSManager shareInstance] getASRInstance] startListening:self]; //结束本次识别 [[[DDSManager shareInstance] getASRInstance] stopListening]; //取消本次识别 
[[[DDSManager shareInstance] getASRInstance] cancel];  //ASREngineManagerDelegate中的回调方法  //识别回调,检测到用户说话 
-(void) ASRBeginningOfSpeech{    
NSLog(@"%@, ASREngine 检测到用户开始说话", TAG);
} 
//识别回调,检测到用户结束说话 
-(void) ASREndOfSpeech{ 
NSLog(@"%@, ASREngine 检测到用户结束说话", TAG);
} 
//识别回调,送去识别的音频数据 
-(void) ASRBufferReceived:(NSData *) buffer{    
NSLog(@"%@, ASREngine 用户说话的音频数据: %@", TAG, buffer); 
}
//识别回调,实时反馈结果 
-(void) ASRPartialResults:(NSString *) results{    
NSLog(@"%@, ASREngine 实时识别结果反馈: %@", TAG, results); 
}
//识别回调,最终识别结果 
-(void) ASRFinalResults:(NSString *) results{    
NSLog(@"%@, ASREngine 最终识别结果反馈: %@", TAG, results);
} 
//识别回调,识别过程中产生的错误 
-(void)ASRError:(NSString*) error{    
NSLog(@"%@, ASREngine 识别过程中发生的错误: %@", TAG, error);
}   
//识别回调,音量大小 
-(void) ASRRmsChanged:(float)rmsdB{    
NSLog(@"%@, ASREngine 用户说话的音量分贝: %f", TAG, rmsdB);
} 

注:不要在对话模式中单独使用Asr模块,如需使用请使用前先关闭对话。

Q2:音量实时回调如何配置?

A:单独使用Asr模块时,也可以配置实时音量回调开关,操作如下。

//打开实时音量回调开关 
ASREngineManager *engine = [[DDSManager shareInstance] getASRInstance]; 
[engine enableVolume:YES];  
//获取音量数值回调 
-(void) ASRRmsChanged:(float)rmsdB{    
NSLog(@"%@, ASREngine 用户说话的音量分贝: %f", TAG, rmsdB); 
}

Q3:Asr识别结果返回的数据结构以及各个字段代表的意义?

{    
"wavetime":1776,       //时间    
"rectime":0,   
"version":"1.0.11.2020.6.15.09:34:21",  //版本    
"optype":"gram",    
"net_type":0,    
"systime":4050,        
"res":"aicar.1.3.0",      //识别资源    
"conf":0.686343,       //置信度    
"post":{        
 "sem":{            
  "skill_id":"2019092700000734"  //命中技能        
  }
 },    
"pinyin":"xia yi bu",      //拼音    
"prutime":0,    
"delayframe":3,    
"sestime":4050,    
"eof":1,         //是否结束    
"delaytime":2,    
"rec":"下 一 步"        //识别文字 
}

Q4:如何针对实时反馈进行识别结果的解析,包含中间结果和最终结果的展示?

A:

如果是中间结果看var字段的值即可;如果想在UI页面展示每次识别的最终结果,则需要累计拼接每次的text字段的值+当前var的值(没有该字段则认为空字符串)
即每次识别结果解析方案: all text + current var。该方案可适用于如下两种识别结果类型
  
类型一:
1.{"eof":0,"var":"你好"}
2.{"eof":0,"text":"你好"}
3.{"eof":0,"var":"我"}
4.{"eof":0,"var":"我要"}
5.{"eof":0,"var":"我要去"}
6.{"eof":0,"var":"我要去国家"}
7.{"eof":0,"var":"我要去国家大"}
8.{"eof":0,"var":"我要去国家大剧院"}
9.{"eof":0,"text":"我要去国家大剧院"}
10.{"eof":1,"text":""}
  
parsing rules
1. (text1)"" + (var1)"你好"
2. "" + (text2)"你好" + (var2)""
3. "" + "你好" + (text3)"" + (var3)"我"
4. "" + "你好" + "" + (text4)"" + (var4)"我要"
5. "" + "你好" + "" + "" + (text5)"" + (var5)"我要去"
6. "" + "你好" + "" + "" + "" + (text6)"" + (var6)"我要去国家"
7. "" + "你好" + "" + "" + "" + "" + (text7)"" + (var7)"我要去国家大"
8. "" + "你好" + "" + "" + "" + "" + "" + (text8)"" + (var8)"我要去国家大剧院"
9. "" + "你好" + "" + "" + "" + "" + "" + "" + (text9)"我要去国家大剧院" + (var9)""
10. "" + "你好" + "" + "" + "" + "" + "" + "" + "我要去国家大剧院" + (text10)"" + (var10)""
=========> "你好我要去国家大剧院"
  
类型二:
1.{"eof":0,"var":"你好"}
2.{"eof":0,"var":"你好我"}
3.{"eof":0,"var":"你好我要"}
4.{"eof":0,"var":"你好我要去"}
5.{"eof":0,"var":"你好我要去国家"}
6.{"eof":0,"var":"你好我要去国家大"}
7.{"eof":0,"var":"你好我要去国家大剧院"}
8.{"eof":1,"text":"你好我要去国家大剧院"}
  
parsing rules
1. (text1)"" + (var1)"你好"
2. "" + (text2)"" + (var2)"你好我"
3. "" + "" + (text3)"" + (var3)"你好我要"
4. "" + "" + "" + (text4)"" + (var4)"你好我要去"
5. "" + "" + "" + "" + (text5)"" + (var5)"你好我要去国家"
6. "" + "" + "" + "" + "" + (text6)"" + (var6)"你好我要去国家大"
7. "" + "" + "" + "" + "" + "" + (text7)"" + (var7)"你好我要去国家大剧院"
8. "" + "" + "" + "" + "" + "" + "" + (text8)"你好我要去国家大剧院" + (var8)""
=========> "你好我要去国家大剧院"
  
如果想进行识别结果的矫正,该方式同样适用
1.{"var":"我","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
2.{"var":"我要","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
3.{"var":"我要去","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
4.{"var":"我要去博","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
5.{"var":"我要去博士","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
6.{"var":"我要去博士有","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
7.{"var":"我要去博世有限","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
8.{"var":"我要去博世有限公","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","eof":0}
9.{"eof":1,"recordId":"8a77fb5b8afc4c9a1f87a9870d504a0a","text":"我要去博世有限公司","sessionId":"acb20fc2f02c86fcbed9d9dcb49766af","pinyin":"wo yao qv bo shi you xian gong si"}
  
parsing rules
1. (text1)"" + (var1)"我"
2. "" + (text2)"" + (var2)"我要"
3. "" + "" + (text3)"" + (var3)"我要去"
4. "" + "" + "" + (text4)"" + (var4)"我要去博"
5. "" + "" + "" + "" + (text5)"" + (var5)"我要去博士"
6. "" + "" + "" + "" + "" + (text6)"" + (var6)"我要去博士有"
7. "" + "" + "" + "" + "" + "" + (text7)"" + (var7)"我要去博世有限"
8. "" + "" + "" + "" + "" + "" + "" + (text8)"" + (var8)"我要去博世有限公"
9. "" + "" + "" + "" + "" + "" + "" + (text9)"我要去博世有限公司" + (var9)""
=========> "我要去博世有限公司"

Q5:如何增强全链路产品识别不准确问题?

A:例如,增强说法打电话给李四,可通过下面步骤:

1.新建技能,比如:“识别增强技能”。
2.新建任务,比如:“打电话增强”。
3.新建意图,比如:“增强1次”,只录入说法“打电话给#联系人#”,标记为“弱说法”,不配置对话。
4.重复步骤3,重复N次:新建意图“增强N次”。
5.发布技能。
6.产品添加技能“识别增强技能”,优先级顺序排到最后面。
7.发布产品。
注:增强N次,则识别训练N次; 若识别依然不准确,请联系项目经理。

3、TTS相关FAQ

Q1:DDS中使用TTS如何使用?

A:在DDS中也有单独抛出的合成模块TTSEngineManager提供使用。

//初始化并设置TTSEngineManagerProtocol 
[[[DDSManager shareInstance] getTTSInstance] setTTSEngienManagerDelegate:self];  
//speak方法 
[[[DDSManager shareInstance] getTTSInstance] speak:@"思必驰智能人机对话技术突破了传统语音技术不能很好支持复杂语音交互功能的难题,使语音输入不再局限于呆板简单的句式限制,在复杂环境和自然口语交流的情况下,能够保证优异的语音分析精度和稳健的人机对话性能;能够提供基于不完整或不准确的语音识别结果进行智能语义推理,通过针对特定领域特定应用需求、对话目标、对话行为、对话状态和对话上下文的统计建模,解决了传统系统设计中规则无法覆盖实际对话状态的问题,同时增强系统对于错误识别结果和错误推理结果的自适应性,大幅度提升用户的语音交互体验。" priority:1 ttsId:@"test_tts"];
//TTSEngineManagerProtocol 回调方法 
//实现TTS回调,开始播报 
-(void) TTSBeginning:(NSString*)ttsId{    
 NSLog(@"%@, TTSEngine 开始播报: %@", TAG, ttsId); 
} 
//实现TTS回调,合成的音频数据 
-(void) TTSReceived:(NSData*)data{    
 NSLog(@"%@, TTSEngine 收到音频,此方法会回调多次,直至data为0,音频结束: %@", TAG, data);
}  
//实现TTS回调,播报结束 
-(void) TTSEnd:(NSString*)ttsId status:(int)status{
 NSLog(@"%@, TTSEngine TTS播报结束, ttsid: %@ status: %d", TAG, ttsId, status); 
} 
//实现TTS回调,合成过程中的错误 
-(void) TTSError:(NSString*) error{    
 NSLog(@"%@, TTSEngine 出现错误: %@", TAG, error);
 }

注:不要在对话模式中单独使用TTS模块,如需使用请使用前先关闭对话。

Q2:TTS的播报优先级是怎样的?

A:TTS播报的优先级分为0,1,2,3。

优先级,优先级0-保留,与aios语音交互同级,仅限内部使用;

优先级1-正常,默认选项,同级按序播放;

优先级2-重要,可以打断优先级1,同级按序播放,播报完毕后继续播报优先级1;

优先级3-紧急,可以打断优先级1或优先级2,同级按序播放,播报完毕后播报下一句优先。

Q3:TTS音频支持哪些格式?

A:tts下载音频支持mp3与wav格式,设置方式是dui网站配置, 本地暂未接口可以设置。

Q4:TTS可配置的参数有哪些?

A:可配置合成音、配置播报语速、配置音量等。

//设置播报合成音 
[[[DDSManager shareInstance] getTTSInstance] setSpeaker:@"gdgm"]; //
设置播报合成音 
[[[DDSManager shareInstance] getTTSInstance] setSpeaker:@"abc" withResPath:[self getResourcesFile:@"gdgm.bin"]]; 
//设置播报语速 
[[[DDSManager shareInstance] getTTSInstance] setSpeed:1.0]; 
//设置播报音量 
[[[DDSManager shareInstance] getTTSInstance] setVolume:50]; 
//获取播报合成音 
NSString * speaker = [[[DDSManager shareInstance] getTTSInstance] getSpeaker];
NSLog(@"%@, TTS合成音是:%@", TAG, speaker); 
//获取播报语速 
float speed = [[[DDSManager shareInstance] getTTSInstance] getSpeed];
NSLog(@"%@, TTS语速是:%f", TAG, speed); 
//获取播报音量 
int tts_volume = [[[DDSManager shareInstance] getTTSInstance] getVolume]; 
NSLog(@"%@, 音量大小:%d", TAG, tts_volume); 

Q5:设备连接蓝牙耳机后,播报没有声音?

A:很有可能是设置音频策略有关系,可添加如下代码测试是否可以解决问题。

//遇到上述情况请试用如下代码,改变音频队列的类型,重点:AVAudioSessionCategoryOptionAllowBluetoothA2DP  

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetoothA2DP|AVAudioSessionCategoryOptionMixWithOthers error:nil]; 
[[AVAudioSession sharedInstance] setActive:YES error:nil];

注:修改音频策略后,后续如果业务逻辑层需要,按需要切回自己所需要的音频策略。

Q6:合成播报的文字里面有多音字,如何让其播报正确的读音?

A:TTS 合成是支持ssml格式文本输入合成的,具体ssml相关的可参考此链接:SSML说明文档,多音字读指定拼音和音调可如下操作。

[[[DDSManager shareInstance] getTTSInstance]setTTSEngienManagerDelegate:self]; 
[[[DDSManager shareInstance] getTTSInstance] speak:@"<?xml version=1.0 encoding=utf8?><speak xml:lang=cn>我叫<phoneme py=\"zeng1 hang2\">曾行</phoneme>。</speak>" priority:1 ttsId:@"test" type:@"ssml"];

4、唤醒相关FAQ

Q1:唤醒词有几类,他们之间又有什么区别?

A:唤醒词介绍:当前除了主唤醒,唤醒词的类型还包括了 副唤醒词、命令唤醒词(快捷唤醒词)、打断唤醒词、快速唤醒词。

副唤醒词:如果您期望为设备增加一个别名,可以通过设置该类型唤醒词来完成; 该唤醒词与主唤醒次语词功能一致,作为类似于昵称的功能。

命令唤醒词(快捷唤醒词): 如果您期望在唤醒的时候执行一条指令,可以通过设置该类型唤醒词来完成;比如说法“打开台灯”是一条命令唤醒词,那么dds在唤醒后会下发一条命令到回调,开发者可以接收该回调用于控制硬件-打开台灯。

打断唤醒词:如果您期望在唤醒的时候能同时打断语音播报,可以通过设置该类型唤醒词来完成(说明:打断需要设备开启回声消除);该功能是模拟在使用过程中的打断并切换行为的操作.

快速唤醒词:您想通过代码来控制快速唤醒,QuickStart词为类似“导航去”、“我想听”等,此类唤醒词只在oneshot模式下生效,作用为在未唤醒状态下语音输入“导航去天安门”,可直接进入对话流程。

注:打断唤醒词,打断操作需要开启回声消除,需要多麦支持,目前iOS设备都为单麦。

Q2:如何获取主唤醒词?

A:如想获取现在所配置的主唤醒词,可如下操作。

DDSManager *manager = [DDSManager shareInstance]; 
NSMutableArray *arr = [manager getWakeupWords]; 
NSString *minWakeupWords = [[DDSManager shareInstance] getMinorWakeupWord];

Q3:如何添加、删除主唤醒词?

A:如想要添加、删除主唤醒词以及相关配置,可如下操作。

//添加唤醒词相关配置 
WakeupWord *wakeWord1 = [[WakeupWord alloc]init]; 
wakeWord1.word = @"你好小鹏"; 
wakeWord1.pinyin = @"ni hao xiao peng"; 
wakeWord1.threshold = @"0.124"; 
wakeWord1.greeting = @[@"小鹏来了"];  
WakeupWord *wakeWord2 = [[WakeupWord alloc]init]; 
wakeWord2.word = @"你好小龙"; 
wakeWord2.pinyin = @"ni hao xiao long"; 
wakeWord2.threshold = @"0.124"; 
wakeWord2.greeting = @[@"小龙来了"];  
//添加一个主唤醒词 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] addOneMainWakeupWord:wakeWord1]; 
//添加多个主唤醒词 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] addMainWakeupWords:@[wakeWord1,wakeWord2]]; 
//移除一个主唤醒词 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] removeOneMainWakeupWord:wakeWord1]; //移除多个主唤醒词 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] removeMainWakeupWords:@[wakeWord1,wakeWord2]]; 
//清空主唤醒词列表 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] clearMainWakeupWords];

Q4:如何在SDK中开启、关闭OneShot功能?

A:oneshot功能一般都在dui后台控制台可以配置,如需在端侧开关,可如下操作。

//开始OneShot 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] enableOneShot]; 
//关闭OneShot 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] disableOneShot];

Q5:DUI网站配置的唤醒词与本地设置的唤醒词有何区别?

A:

  1. DUI网站目前可以配置的唤醒词类型为主唤醒词与命令唤醒词。

  2. 唤醒词使用优先级: 本地设置 > 云端配置. 如果本地有设置唤醒词,则会覆盖云端的唤醒词,以本地设置为准。

Q6:如何配置能无感知得到唤醒结果,且不进入对话?

A:需要配置如下配置,不会播报欢迎语,但是能拿到唤醒结果,且不会进入对话状态。

NSMutableDictionary *dicData = [[NSMutableDictionary alloc] init]; 
[dicData setObject:@(YES) forKey:@"isCatchWkrt"]; 
NSString *deviceJson = [self dataTOjsonString:dicData]; 
NSMutableArray *array = [[NSMutableArray alloc] init]; 
[array addObject:deviceJson]; 
[[DDSManager shareInstance]publishTopic:@"catch.wakeup_result" list:@[deviceJson]];

5、技能相关FAQ

Q1:如何在客户端动态调整某些技能之间的优先级?

A:如需要调整技能的优先级,可如下操作。

AIUpdateContextIntent *intent = [[AIUpdateContextIntent alloc]init]; 
intent.contextIntentKey = @"skillPriority"; 
//contextIntentValue的顺序就是技能的优先级顺序 
intent.contextIntentValue = @[@"2018013100000005",@"2018013100000006"]; 
[[DDSManager shareInstance] updateProductContext:intent];

Q2:如何在客户端动态调整产品中参与调度的技能列表?

A:如需调整产品中参与调度的技能列表,可如下操作。

AIUpdateContextIntent *intent = [[AIUpdateContextIntent alloc]init]; 
intent.contextIntentKey = @"dispatchSkillList"; 
// contextIntentValue的顺序就是技能列表 
intent.contextIntentValue = @[@"2018013100000005",@"2018013100000006"]; 
[[DDSManager shareInstance] updateProductContext:intent];

Q3:如何在客户端动态调整指定技能的对话风格模式?

A:如需调整,可如下操作。

AIUpdateContextIntent *intent = [[AIUpdateContextIntent alloc]init]; 
NSMutableDictionary *valueDic = [[NSMutableDictionary alloc]init]; 
[valueDic setObject:@"adult" forKey:@"ageRange"]; 
[valueDic setObject:@"screen" forKey:@"display"]; 
NSError *error; 
NSString *valueJson; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:valueDic                                                   options:NSJSONWritingPrettyPrinted error:&error]; 
if (!jsonData) {    
 NSLog(@"Got an error: %@", error);
 } else {
  valueJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 
 } 
 valueJson = [valueJson stringByReplacingOccurrencesOfString:@"\n" withString:@""];        intent.contextIntentKey = @"dispatchSkillList"; 
 intent.contextIntentValue = valueJson; 
 intent.contextIntentSkillId = @"2018013100000005"; 
 [[DDSManager shareInstance] updateSkillContext:intent];

Q4:如何在DDS中使用本地技能?

A:如需使用本地技能,可如下操作。

1、dui控制台对应产品,创建添加本地技能。

2、SDK授权成功之后,设置对话模式。

[[DDSManager shareInstance]setDialogMode:1];

3、端侧订阅topic,asr.speech.text.original。

NSArray *messageArray = [[NSArray alloc] initWithObjects:@"asr.speech.text.original", nil]; [[DDSManager shareInstance] subscribe:messageArray observer:self.messageObserver];

4、启动识别。

[[[DDSManager shareInstance] getASRInstance] startListening:self];

5、topic响应回调- (void)onMessage:(NSString )message data:(NSString)data ,处理内容,获取结果。

//MessageObserver的消息响应 
- (void)onMessage:(NSString *)message data:(NSString *)data {    
if ([message isEqualToString:@"asr.speech.text.original"]) {        
 NSLog(@"%@, asr.speech.text.original响应了,数据: %@", TAG, data);    
 } 
}

Q5:自定义修改 DMResult 时,上报了speekUrl,但是播放音的音频的音色跟speekUrl的音色确不一样

A:大概率是参数修改不正确导致,speakUrl相关修改可参照如下俩种情况:

  1. .dm 下有 speak 字段,修改 .dm.nlg,修改 .speak.text,如果想走预合成那么就在 speak 下添加 speakUrl即 .dm.speak.speakUrl。
{     
 "dm":{         
  "nlg":"你好呀,很高兴和你聊天",        
  "speak":{            
    "type":"text",            
    "text":"你好呀,很高兴和你聊天",    
        "speakUrl":"http:\/\/tts.dui.ai\/runtime\/v1\/longtext\/20569e7a-de09-4e5a-b103-05e8baf63380:015adbcd-caea-4487-9365-66794f07e512?productId=278575322"        
        },        
      }, 
}
  1. .dm下没有speak字段,修改.dm.nlg,如果走预合成那么就在 dm 同级目录下添加 speakUrl 即 .speakUrl。
{     
 "dm":{         
  "nlg":"你好呀,很高兴和你聊天",
    },     
    "speakUrl":"http:\/\/tts.dui.ai\/runtime\/v1\/longtext\/20569e7a-de09-4e5a-b103-05e8baf63380:015adbcd-caea-4487-9365-66794f07e512?productId=278575322"  
 }

Q6:在使用第三方语义的情况下,自定义 DMResult 时,如遇多轮对话中,第一轮修改三方语义修改拼接对应 json 数据对话正常,播报完之后,不说话(识别为空)第二轮不符合预期,也不语音报错,是什么问题?

A: 在多轮对话中,首轮不使用思必驰语义,首轮结束,不说话,识别为空,dui平台上技能配置错误播报中,识别为空时会默认重复上一轮对话的播报,所以会走到思必驰链路上,此时,要么就是改对应技能对应的识别为空的错误播报,要么可以通过返回的 dm 结果修改 json,通过对返回的 json 中的errorId 来做出识别为空的判断,去改变 json 格式和播报内容,从而达到目的。

Q7:技能的相关错误在哪能接收处理?

A:可以订阅sys.dialog.error 这个topic来进行处理,但是注意,这个topic也会包含一些除对话外的错误比如网络不通畅,连接服务超时等,后者可用cdm.error来接收处理。

6、系统相关FAQ

Q1:系统打断语音,如电话、siri 等,业务层如何处理?

A:可参考苹果官方这一部分的文档https://developer.apple.com/documentation/avfaudio/avaudiosession/responding_to_audio_session_interruptions?language=objc,大致操作如下,可以检测到打断的开始和结束时机。

-(void)handleInterruption:(NSNotification *)noty{
 NSDictionary *userInfo = noty.userInfo;    
 NSUInteger typeValue = [userInfo[AVAudioSessionInterruptionTypeKey] integerValue];      
 switch (typeValue) {        
 case AVAudioSessionInterruptionTypeBegan:            
  NSLog(@"debug====打断开始");            
  break;        
 case AVAudioSessionInterruptionTypeEnded:            
  NSLog(@"debug====打断结束");            
  break;        
 default:            
  break;    
 } 
} 
-(void)setUpNotification{    
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];     
}

Q2:关于 SDK 在 App 切到后台后再次返回后失效问题解决方案

A:针对此问题,现 SDK 有如下一个解决方案。

1、添加监听DDS_SDK_NEED_RELOAD通知,收到此通知时去重启。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadSDK)  name:@"DDS_SDK_NEED_RELOAD" object:nil]; 
-(void)reloadSDK{   
 dispatch_async(dispatch_get_main_queue(), ^{ 
  //音频队列的设置        
  [self setAudioConfig];  
  //重新初始化SDK        
  [self initDDSManager];    
  }); 
} 

2、切后台以及切回前台的处理,下为 Demo 中的示例,实际情况请根据业务逻辑的逻辑来做处理。

//进入前台调用此方法 
- (void)applicationBecomeActive{    
 [[[DDSManager shareInstance] getDDSWakeupEngineManager] enableWakeup];
}  
//进入后台调用此方法 
- (void)applicationEnterBackground{   
 [[DDSManager shareInstance]stopDialog];    
 [[[DDSManager shareInstance] getDDSWakeupEngineManager] disableWakeup]; 
}

Q3:为什么我App 跳转到 XX音乐等软件后返回来之后无法唤醒了?

A:跳转到其他的音乐播放或使用到音频队列的App 之后,返回 App 需重新设置音频策略(详见FAQ 1- Q4),并调用enableWakeup方法。

//初始化SDK之前调用此方法,初始化音频策略。 
- (void)setAudioConfig{    
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionMixWithOthers error:nil];    
[[AVAudioSession sharedInstance] setActive:YES error:nil]; 
}  
//如需打开唤醒调用 enableWakeup 方法 
[[[DDSManager shareInstance] getDDSWakeupEngineManager] enableWakeup];

注:音频策略有可能会被其他app修改,在使用SDK之前请确保业务逻辑层切到正确的音频策略。