# 快速入门

AIUI为开发者提供了多种集成方式,帮助开发者开发出多样化的语音交互应用。

下面以天气查询为例,教您如何快速打造一个天气查询的app,快速了解集成与交互流程。

# Demo体验

我们先体验一下天气查询Demo:

① 用浏览器扫描下方二维码,安装WeatherInquiryDemo(暂时只支持Android手机);

② 打开WeatherInquiryDemo,允许app开启录音权限,点击开始录音就可以开始与它交互了。比如您可以问:合肥的天气怎么样北京明天的天气,查询结果如下图所示:

# 开发步骤

在体验了Demo之后,本节将一步步引导开发者如何完成这个简单的天气查询Demo,开发者可下载完整工程代码 (opens new window)

# 云端配置

# 创建应用

进入AIUI开放平台 (opens new window),登录您的账号,选择我的应用->创建新应用,填写您应用的相关信息与应用平台后,便创建好应用了。

# 配置技能

应用创建完成后,您可以点击该应用,进入应用配置页面,点击添加技能,勾选天气技能,如需同时使用其他技能,也可以一起勾选。

# SDK集成

本章节仅用Android平台和iOS平台来说明AIUI SDK的集成过程,其他平台接口有略微差别,但过程一致,也可参考此章节进行开发集成。

# 下载SDK

AIUI开放平台 (opens new window)创建完应用后,在该应用的SDK下载页面即可下载对应的SDK包。SDK包里包含MSC与AIUI库,本章节仅介绍AIUI库的入门使用,MSC库中包含唤醒与合成能力,如需了解MSC库,请访问MSC开发指南 (opens new window)

# Android 平台集成步骤

# 导入SDK

打开Android Studio,创建一个新的工程,将下载的Android SDK压缩包中libs目录下的libaiui.so以及AIUI.jar拷贝至Android工程的libs目录下,并将SDK包中assets目录下cfg文件夹以及res目录下vad文件夹拷贝至工程中。工程结构如下图所示:

:-:

将AIUI.jar添加至工程依赖,将app module下的gradle配置文件中指定默认jniLibs目录为libs。

android {
...
	sourceSets {
		main {
			jniLibs.srcDirs = ['libs']
		}
	}
...
}
# 添加用户权限

在工程AndroidManifest.xml文件中添加如下权限,如在Android6.0及以上手机中集成使用,请动态申请所需权限。

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

注意:如需在打包或者生成APK的时候进行混淆,请在proguard.cfg中添加如下代码:

-keep class com.iflytek.**{*;}
-keepattributes Signature
# 创建布局文件

可参考工程源码 (opens new window)添加布局。

# 创建AIUIAgent

SDK中提供的AIUIAgent就是和AIUI交互的桥梁。创建AIUIAgent,示例如下:

//创建AIUIAgent
AIUIAgent mAIUIAgent = AIUIAgent.createAgent(context,getAIUIParams(),mAIUIListener);

createAgent方法包含三个参数:

  • 第一个参数类型为Context;
  • 第二个参数类型为String,具体值是通过读取assets目录下的cfg/aiui_phone.cfg文件而获得的字符串;
  • 第三个参数类型为AIUIListener,是AIUI事件回调监听器。

getAIUIParams()具体示例如下所示:

private String getAIUIParams() {
	String params = "";
	AssetManager assetManager = getResources().getAssets();
	try {
		InputStream ins = assetManager.open( "cfg/aiui_phone.cfg" );
		byte[] buffer = new byte[ins.available()];
		
		ins.read(buffer);
		ins.close();
		
		params = new String(buffer);
	} catch (IOException e) {
		e.printStackTrace();
	}
	return params;
}

mAIUIListener具体示例如下所示:

AIUIListener mAIUIListener = new AIUIListener() {

	@Override
	public void onEvent(AIUIEvent event) {
		switch (event.eventType) {
			//唤醒事件
			case AIUIConstant.EVENT_WAKEUP:
			{
				break;
			}
			//结果事件(包含听写,语义,离线语法结果)
			case AIUIConstant.EVENT_RESULT:
			{
				break;
			}
			//休眠事件
			case AIUIConstant.EVENT_SLEEP:
			{
				break;
			}
			// 状态事件
			case AIUIConstant.EVENT_STATE: {
				mAIUIState = event.arg1;
				if (AIUIConstant.STATE_IDLE == mAIUIState) {
					// 闲置状态,AIUI未开启
				} else if (AIUIConstant.STATE_READY == mAIUIState) {
					// AIUI已就绪,等待唤醒
				} else if (AIUIConstant.STATE_WORKING == mAIUIState) {
					// AIUI工作中,可进行交互
				}
			} break;
			//错误事件
			case AIUIConstant.EVENT_ERROR:
			{
				break;
			}
		}
	}
}
# 语音语义理解示例

发送CMD_WAKEUP消息至AIUI,使AIUI处于唤醒状态,再发送开始录音消息,使麦克风录入音频,并通过AIUIListener的回调,获取语义结果。代码示例如下:

// 先发送唤醒消息,改变AIUI内部状态,只有唤醒状态才能接收语音输入
if( AIUIConstant.STATE_WORKING != mAIUIState ){
	AIUIMessage wakeupMsg = new AIUIMessage(AIUIConstant.CMD_WAKEUP, 0, 0, "", null);
	mAIUIAgent.sendMessage(wakeupMsg);
}
		
// 打开AIUI内部录音机,开始录音
String params = "sample_rate=16000,data_type=audio";
AIUIMessage writeMsg = new AIUIMessage( AIUIConstant.CMD_START_RECORD, 0, 0, params, null );
mAIUIAgent.sendMessage(writeMsg);

如出现20006错误,请注意下应用是否拥有录音权限。返回的语义结果,参考语义结果说明文档

# 结果解析

在AIUIEventListener回调中,可以收到来自AIUI的多种消息,具体示例如下:

private AIUIListener mAIUIListener = new AIUIListener() {

	@Override
	public void onEvent(AIUIEvent event) {
		switch (event.eventType) {
			case AIUIConstant.EVENT_WAKEUP: 
				//唤醒事件
				Log.i( TAG,  "on event: "+ event.eventType );
			break;

			case AIUIConstant.EVENT_RESULT: {
				//结果解析事件
				try {
					JSONObject bizParamJson = new JSONObject(event.info);
					JSONObject data = bizParamJson.getJSONArray("data").getJSONObject(0);
					JSONObject params = data.getJSONObject("params");
					JSONObject content = data.getJSONArray("content").getJSONObject(0);
					
					if (content.has("cnt_id")) {
						String cnt_id = content.getString("cnt_id");
						JSONObject cntJson = new JSONObject(new String(event.data.getByteArray(cnt_id), "utf-8"));
						String sub = params.optString("sub");
						if ("nlp".equals(sub)) {
							// 解析得到语义结果
							String resultStr = cntJson.optString("intent");
							Log.i( TAG, resultStr );
						}
					}
				} catch (Throwable e) {
					e.printStackTrace();
				}
			} break;

			case AIUIConstant.EVENT_ERROR: {
				//错误事件
				Log.i( TAG,  "on event: "+ event.eventType );
				Log.e(TAG, "错误: "+event.arg1+"\n"+event.info );
			} break;

			case AIUIConstant.EVENT_VAD: {
				if (AIUIConstant.VAD_BOS == event.arg1) {
					//语音前端点
				} else if (AIUIConstant.VAD_EOS == event.arg1) {
					//语音后端点
				} 
			} break;
			
			case AIUIConstant.EVENT_START_RECORD: {
				Log.i( TAG,  "on event: "+ event.eventType );
				//开始录音
			} break;
			
			case AIUIConstant.EVENT_STOP_RECORD: {
				Log.i( TAG,  "on event: "+ event.eventType );
				// 停止录音
			} break;

			case AIUIConstant.EVENT_STATE: {	
				// 状态事件
				mAIUIState = event.arg1;
				if (AIUIConstant.STATE_IDLE == mAIUIState) {
					// 闲置状态,AIUI未开启
				} else if (AIUIConstant.STATE_READY == mAIUIState) {
					// AIUI已就绪,等待唤醒
				} else if (AIUIConstant.STATE_WORKING == mAIUIState) {
					// AIUI工作中,可进行交互
				}
			} break;

			default:
				break;
		}
	}
};

# iOS 平台集成步骤

# 导入SDK

开发者需要将下载的iOS SDK压缩包中l的iflyAIUI.framework添加到开发者工程中,配置iflyAIUI.framework路径。假设开发者的Xcode工程目录与iflyAIUI.framework的目录位置关系是如下图所示这样的:

那么,开发者需要注意正确设置iflyAIUI.framework的路径: 依次点击TARGETS -> Build Setting -> Framework Search Path,双击修改路径,如下图所示。

# 添加配置文件和资源文件

开发者需要将AIUIDemo中resource文件夹添加到开发者自己的工程中,该文件夹下包括AIUI需要读取的配置参数文件和语音识别过程中需要的vad资源。

# 添加系统库依赖

开发者集成AIUI SDK可以复用AIUIDemo中的部分源码以提高开发效率。AIUIService.h、AIUIService.mm、TTSViewController.h、TTSViewController.m、UnderstandViewController.h以及UnderstandViewController.mm 等文件实现了AIUI SDK 接口调用参考示例,由于这些文件使用C++和OC编写,开发者在使用XCode进行编译时要额外注意添加libicucore.tbd、libc++.tbd和libz.tbd三个系统库依赖。如下图所示:

# 设置Bitcode

Xcode 7,8默认开启了Bitcode,而Bitcode 需要工程依赖的所有类库同时支持。AIUI SDK暂时还不支持Bitcode,需要开发者关闭该设置。只需在Targets -> Build Settings 中搜索Bitcode 即可,找到相应选项,设置为NO。 如下图所示:

# 用户隐私权限配置

iOS 10发布以来,苹果为了用户信息安全,加入隐私权限设置机制,让用户来选择是否允许。 隐私权限配置可在info.plist 新增相关privacy字段,AIUI SDK中需要用到的权限主要包括麦克风权限、联系人权限和地理位置权限:

<key>NSMicrophoneUsageDescription</key>
<string></string>
<key>NSLocationUsageDescription</key>
<string></string>
<key>NSLocationAlwaysUsageDescription</key>
<string></string>
<key>NSContactsUsageDescription</key>
<string></string>

即在Info.plist 中增加下图设置:

# 初始化工作目录

AppDelegate中初始化,设置日志是否开启、日志打印级别和设置工作目录等。初始化设置不强制要求放在在AppDelegate中,只需要保证在创建AIUI客户端代理之前调用即可,开发者可以自行选择。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

	NSArray  *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
	
	NSString *cachePath = [paths objectAtIndex:0];
	cachePath = [cachePath stringByAppendingString:@"/"];
	NSLog(@"cachePath=%@",cachePath);
	
	[IFlyAIUISetting setSaveDataLog:NO];
	[IFlyAIUISetting setLogLevel:LV_INFO];
	[IFlyAIUISetting setAIUIDir:cachePath];
	[IFlyAIUISetting setMscDir:cachePath];
	
	return YES;
}
# 创建AIUIAgent

SDK中提供的AIUIAgent就是和AIUI交互的桥梁。先创建AIUIAgent,然后发送唤醒消息使AIUI处于Working状态,示例如下:

@property IFlyAIUIAgent *aiuiAgent;
	
// 读取aiui.cfg配置文件
NSString *cfgFilePath = [[NSBundle mainBundle] pathForResource:@"aiui" ofType:@"cfg"];
NSString *cfg = [NSString stringWithContentsOfFile:cfgFilePath encoding:NSUTF8StringEncoding error:nil];
    
//创建AIUIAgent
_aiuiAgent = [IFlyAIUIAgent createAgent:cfg withListener:self];
# 语音语义理解示例

发送CMD_WAKEUP消息至AIUI,使AIUI处于唤醒状态,再发送开始录音消息,使麦克风录入音频。代码示例如下:

//发送唤醒消息
IFlyAIUIMessage *wakeuMsg = [[IFlyAIUIMessage alloc]init];
wakeuMsg.msgType = CMD_WAKEUP;
[_aiuiAgent sendMessage:wakeuMsg];
	
//发送开始录音消息
IFlyAIUIMessage *msg = [[IFlyAIUIMessage alloc] init];
msg.msgType = CMD_START_RECORD;
[_aiuiAgent sendMessage:msg];
# 结果回调

实现IFlyAIUIListener协议类接口,在onEvent事件获取结果回调。代码示例如下:

- (void) onEvent:(IFlyAIUIEvent *) event {

	switch (event.eventType) {
			
		case EVENT_CONNECTED_TO_SERVER:
		{
			//服务器连接成功事件
			NSLog(@"CONNECT TO SERVER");
		} break;
			
		case EVENT_SERVER_DISCONNECTED:
		{
			//服务器连接断开事件
			NSLog(@"DISCONNECT TO SERVER");
		} break;
		
		case EVENT_START_RECORD:
		{
			//开始录音事件
			NSLog(@"EVENT_START_RECORD");
		} break;
			
		case EVENT_STOP_RECORD:
		{
			//停止录音事件
			NSLog(@"EVENT_STOP_RECORD");
		} break;
			
		case EVENT_STATE:
		{
			//AIUI运行状态事件
			switch (event.arg1)
			{
				case STATE_IDLE:
				{
					NSLog(@"EVENT_STATE: %s", "IDLE");
				} break;
					
				case STATE_READY:
				{
					NSLog(@"EVENT_STATE: %s", "READY");
				} break;
					
				case STATE_WORKING:
				{
					NSLog(@"EVENT_STATE: %s", "WORKING");
				} break;
			}
		} break;
			
		case EVENT_WAKEUP:
		{
			//唤醒事件
			NSLog(@"EVENT_WAKEUP");
		} break;
			
		case EVENT_SLEEP:
		{
			//休眠事件
			NSLog(@"EVENT_SLEEP");
		} break;
			
		case EVENT_VAD:
		{
			switch (event.arg1)
			{
				case VAD_BOS:
				{	
						//前端点事件
					NSLog(@"EVENT_VAD_BOS");
				} break;
					
				case VAD_EOS:
				{
					//后端点事件
					NSLog(@"EVENT_VAD_EOS");
				} break;
					
				case VAD_VOL:
				{
						//音量事件
					NSLog(@"vol: %d", event.arg2);
				} break;
			}
		} break;
			
		case EVENT_RESULT:
		{
			NSLog(@"EVENT_RESULT");
			[self processResult:event];
		} break;
			
		case EVENT_CMD_RETURN:
		{
			NSLog(@"EVENT_CMD_RETURN");
		} break;
			
		case EVENT_ERROR:
		{
			NSString *error = [[NSString alloc] initWithFormat:@"Error Message:%@\nError Code:%d",event.info,event.arg1];
			NSLog(@"EVENT_ERROR: %@",error);
		} break;
	}
}


//处理结果
- (void)processResult:(IFlyAIUIEvent *)event{
	
	NSString *info = event.info;
	NSData *infoData = [info dataUsingEncoding:NSUTF8StringEncoding];
	NSError *err;
	NSDictionary *infoDic = [NSJSONSerialization JSONObjectWithData:infoData options:NSJSONReadingMutableContainers error:&err];
	if(!infoDic){
		NSLog(@"parse error! %@", info);
		return;
	}
	
	NSLog(@"infoDic = %@", infoDic);
	
	NSDictionary *data = [((NSArray *)[infoDic objectForKey:@"data"]) objectAtIndex:0];
	NSDictionary *params = [data objectForKey:@"params"];
	NSDictionary *content = [(NSArray *)[data objectForKey:@"content"] objectAtIndex:0];
	NSString *sub = [params objectForKey:@"sub"];
	
	if([sub isEqualToString:@"nlp"]){
		
		NSString *cnt_id = [content objectForKey:@"cnt_id"];
		if(!cnt_id){
			NSLog(@"Content Id is empty");
			return;
		}
		
		NSData *rltData = [event.data objectForKey:cnt_id];
		if(rltData){
			NSString *rltStr = [[NSString alloc]initWithData:rltData encoding:NSUTF8StringEncoding];
			NSLog(@"nlp result: %@", rltStr);
		}
	} else if([sub isEqualToString:@"tts"]){
		NSLog(@"receive tts event");
		
		NSString *cnt_id = [content objectForKey:@"cnt_id"];
		if(cnt_id){
			//合成音频数据
			NSData *audioData = [event.data objectForKey:cnt_id];
			
			//当前音频块状态:0(开始),1(中间),2(结束),3(一块)
			int dts = [(NSNumber *)[content objectForKey:@"dts"] intValue];
			
			//合成进度
			int text_per = [(NSNumber *)[content objectForKey:@"text_percent"] intValue];
			
			NSLog(@"dataLen=%lu, dts=%d, text_percent=%d", (unsigned long)[audioData length], dts, text_per);
		}
	}
}

# 交互体验

将构建好的app安装到手机中,开发者可以用日常交流方式与刚写好的Demo进行自然交互,如今天天气怎么样合肥的天气等,AIUI会将语义结果返回并显示在app中。更多的技能支持与说法请参考AIUI技能商店。