# 快速入门

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

下面以虚构的查询用户信息为例,教您如何快速打造一个用户信息查询app,包括技能创建与应用接入,快速了解集成与交互流程。

# Demo体验

我们先体验一下用户信息查询Demo:

# 设计开发流程

# 技能交互模型创建示例

本文详细地讲述查询用户信息技能交互模型创建过程,请按如下步骤进行操作。

# 登录技能开放平台

请使用讯飞开放平台账号登录讯飞AIUI开放平台 (opens new window)技能工作室 (opens new window)。如果你还没有讯飞开放平台账号,请先注册讯飞开放平台账号 (opens new window)

登录成功后,请在屏幕右侧上方进行开发者认证(认证48小时内会开通)。如果已经认证请忽略。

# 创建技能

点击创建技能,输入技能名称 “查询用户信息”和技能标识 “query_info”,点击创建。

# 创建交互模型

选择导航栏交互模型->意图,点击创建意图,在创建意图弹窗中选择创建自定义意图,输入意图的中文名称“查询用户意图”,输入意图英文标识”query_user_intent”。

首次编写语料时,如果对智能贴弧功能不熟悉,建议先关闭智能贴弧功能。否则在添加语料后,系统会自动标记语料中的槽位信息,标记不准,会增加开发者手动调整的工作。

关闭智能贴弧后,在语料中输入设计的说法和语料。

我要查信息

我叫张三

我在合肥

我的手机号是18611112222

标注语料中的关键信息(槽位信息)。

这里我们将用静态实体来表示交互语料中的手机号和用户姓名(真实交互中,用户名和手机号最适合的实现方式是动态实体,这里为演示用静态实体)。

因为系统中没有手机号和用户名实体,首先创建user_phone和user_name这两个自定义静态实体。

在技能工作室,选择我的实体,点击创建实体,在创建实体弹窗中选择“静态实体”,在中文名称中输入“用户手机号”,在英文标识中输入“user_phone”。

在词条管理页,点击添加词条,在下发的词条输入框中依次填入实体内容,如18611111111,18622222222,18633333333。

点击构建实体,等待构建成功,完成实体创建。

同样操作,创建“用户名”实体(英文标识user_name),内容为张三,李四和王五。

实体创建完成后,返回意图语料编辑页。

首先将“我的手机号是18611112222”中的18611112222标注为user_phone,实体选择自定义静态实体user_phone。标注方法为鼠标选中语料中的槽位,在弹出的实体选择框中选定实体。

其次将“我叫张三”中的张三标注为user_name,实体选择自定义静态实体user_name。

最后将“我在合肥”中的“合肥”标注为chinacity,实体选择官方实体IFLYTEK.ChinaCity。

槽位标注后会在实体列表中出现标注的槽位标识和对应的实体。假设在查询用户信息技能中,如果缺失姓名、手机号和城市时,将无法查询用户,所以请在实体列表中对应的槽位标识后勾选对话必须选项,同时填写追问话术“你的姓名是什么”、 “你的手机号是多少”、 “你在哪个城市”。

点击左上方构建技能按钮,待技能构建完成后,查询用户信息交互模型创建完成。

说明:槽位标识会在技能业务代码实现过程中使用,需要保证交互模型中的名称与业务代码保持一致。

# 测试交互模型

在意图页面的最右侧进行交互模型测试(仅支持文本测试)。

  1. 输入“我要查信息”,技能回复“你的手机号是多少”。
  2. 输入“我的手机号是18611111111”,技能回复“你在哪个城市”。
  3. 输入“我在北京”, 技能回复“你的姓名是什么”
  4. 输入“我叫张三”,技能回复“好的”,此时技能完成意图和槽位的识别。

交互模型测试结果满足技能设计的预期,接下来进行技能业务代码实现。

# 下一步

技能发布及管理示例

# 技能发布及管理示例

技能通过模拟测试后,需要经过发布上线才能在应用中配置调用。本节演示查询用户信息技能发布上线以及在应用中配置调用的情况。

# 技能发布上线

请在导航栏中点击“发布”,按要求填写发布信息。

# 版本管理

技能发布上线后,可以在版本管理中查询版本状态。会在线上版本中展现。

此时你可以在应用控制台中语义技能配置中配置该技能。

说明:AIUI中自定义技能只有在发布上线后,应用才可以在语义理解的技能列表中查看并添加。

# 下一步

应用配置语音交互能力示例

# 应用配置语音交互能力示例

# 创建应用

进入AIUI开放平台 (opens new window),登录您的账号,选择应用接入->进入应用

在我的应用页,点击创建应用,进入创建应用页。

在创建应用页,输入应用名称,如“快速入门测试应用”,选择应用平台,如Android,在应用分类中选择应用分类,如“应用-教育-其他”,点击确定创建后,便创建好应用了。

应用创建完成后,自动进入应用控制台页面

# 配置语音识别

在应用配置页语音识别配置区域中,配置语音识别相关选项,如语种选择“中文”,方言选择“普通话”,领域选择“通用”,距离选择“近场”。

点开高级设置,完成识别高级功能配置,如勾选“识别结果优先阿拉伯数字”,这样我们在进行查询用户信息技能交互时,手机号将会优先输出阿拉伯数字格式。

在进行查询用户信息技能交互时,为提升用户名(如张三、李四、王五)的识别率,可以添加热词。在识别热词设置区域中,先点击“下载热词模板”,下载热词文件,用记事本打开后,模板内容如下:

将用户名数据添加到热词文件中,每行一条。

保存文件,在识别热词区域中,点击上传热词,将刚刚编辑的热词文件上传,这样就完成了语音识别的配置。

# 配置语义理解

打开语义理解开关

在语义技能配置区域的全部技能中,点击添加技能按钮,弹出添加技能框。在搜索框中,输入技能名称,如“查询用户信息”后回车,下发技能列表区域展示技能数据。

选中技能后,点击确定,完成技能添加到应用。

技能配置完成后,可在页面右侧的模拟测试框进行效果测试。

# 配置语音合成

打开语音合成开关,选择合适的发音人完成配置。可在语音合成配置区域中选择发音人和文本进行试听。

# 保存配置

在完成语音识别、语义理解和语音合成的相关配置后,点击页面右上方的保存修改按钮,待保存成功后,完成了应用的沙盒场景配置。

至此,该应用就具备了基本的语音交互能力,支持语音识别->语义理解(查询用户信息)->语音合成,并在main_box场景(即沙盒场景)下生效。沙盒场景的名称是在线上场景的名称后加”_box”后缀,如页面中情景模式为main,则沙盒场景为main_box。这样实现的目的是为了防止开发者在编辑应用或者技能时影响线上产品效果。那如何将沙盒场景的配置同步到线上呢?

# 应用审核

新创建的应用,必须先进行应用审核。只有审核通过的应用才可以进行后续的发布上线等操作。

点击导航栏“审核上线”,在打开的审核上线页中如实填写应用的相关信息,并提交申请。

申请提交后,一般会在24h内完成应用审核。如紧急,可线下联系AIUI技术支持加急处理。

# 应用发布上线

应用审核上线通过后,左侧导航栏上线区域内会出现更新发布和版本管理两个按钮。

在导航栏中点击“更新发布”,按要求填写发布信息。

点击发布到线上,此时沙盒场景的配置将同步到线上,即main_box下的配置同步到main。 至此,我们完成了应用的语音交互配置并将配置发布到了线上(即在main场景下生效)。下一章我们将演示,如何在开发者自己的产品中集成该应用能力。

# 下一步

Android平台集成应用能力示例

# Android平台集成应用能力示例

完成平台应用创建与能力配置后,本节用Android平台来说明AIUI SDK的集成过程,其他平台接口有略微差别,但过程一致,具体见下一节其他平台接入参考,也可参考此章节进行开发集成。

点击应用配置页左侧导航栏的开发工具,打开SDK下载页。点击下载最新版AIUI SDK。

# 导入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.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

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

-dontoptimize
-keep class com.iflytek.**{*;}
-keepattributes Signature

# 修改AIUI配置

打开cfg/aiui.cfg,查看和编辑AIUI配置。其中重点关注的配置项如下:

login.appid={你的应用ID}

global.scene={你的场景名称}(注意沙盒场景与线上场景的区别)

# 创建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;
        }
    }
};

将语义结果输出到屏幕,同时将合成结果播报出来。至此,就将应用能力添加到开发者应用中,此时开启应用,语音输入“我要查信息”开启对话,完成演示。

# 下一步

其他平台接入AIUI SDK参考

# 其他平台接入AIUI SDK参考

# Windows&Linux 平台集成步骤

# 导入SDK

在cmake工程里将include及lib文件夹加入到工程中,可参考aiui_sample中的CMakeList.txt

include_directories(${CMAKE_CURRENT_LIST_DIR}/../../include)
link_directories(${CMAKE_CURRENT_LIST_DIR}/../../libs/${PLATFORM})
link_libraries(aiui)

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

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

# 初始化AIUI目录

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

    AIUISetting::setAIUIDir("./AIUI/");
    AIUISetting::setNetLogLevel(aiui::_none);
    AIUISetting::setLogLevel(aiui::_none);

# 创建AIUIAgent

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

string fileParam = readFileAsString("./AIUI/cfg/aiui.cfg");
agent = IAIUIAgent::createAgent(fileParam.c_str(), &listener);

# 语音语义理解示例

发送CMD_WAKEUP消息至AIUI,使AIUI处于唤醒状态,再发送文本数据,代码示例如下:

//发送唤醒消息
IAIUIMessage* wakeupMsg = IAIUIMessage::create(AIUIConstant::CMD_WAKEUP);
agent->sendMessage(wakeupMsg);
wakeupMsg->destroy();
    
//发送开始文本消息
string text = "合肥明天天气怎么样";
Buffer* textData = Buffer::alloc(text.length());
text.copy((char*)textData->data(), text.length());

IAIUIMessage* writeMsg = IAIUIMessage::create(
        AIUIConstant::CMD_WRITE, 0, 0, "data_type=text,tts_res_type=url", textData);
agent->sendMessage(writeMsg);
writeMsg->destroy();

# 结果回调

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

class MyListener : public IAIUIListener
{
public:
    void onEvent(const IAIUIEvent& event) const override
    {
        switch (event.getEventType()) {
            //SDK 状态回调
            case AIUIConstant::EVENT_STATE: {
                switch (event.getArg1()) {
                    case AIUIConstant::STATE_IDLE: {
                        cout << "EVENT_STATE:"
                             << "IDLE" << endl;
                    } break;

                    case AIUIConstant::STATE_READY: {
                        cout << "EVENT_STATE:"
                             << "READY" << endl;
                    } break;

                    case AIUIConstant::STATE_WORKING: {
                        cout << "EVENT_STATE:"
                             << "WORKING" << endl;
                    } break;
                }
            } break;
                //唤醒事件回调
            case AIUIConstant::EVENT_WAKEUP: {
                cout << "EVENT_WAKEUP:" << event.getInfo() << endl;
            } break;
                //休眠事件回调
            case AIUIConstant::EVENT_SLEEP: {
                cout << "EVENT_SLEEP:arg1=" << event.getArg1() << endl;
            } break;
                //VAD事件回调,如找到前后端点
            case AIUIConstant::EVENT_VAD: {
                switch (event.getArg1()) {
                    case AIUIConstant::VAD_BOS: {
                        cout << "EVENT_VAD: BOS" << endl;
                    } break;

                    case AIUIConstant::VAD_EOS: {
                        cout << "EVENT_VAD: EOS" << endl;
                    } break;

                    case AIUIConstant::VAD_VOL: {
                        cout << "EVENT_VAD: VOL" << endl;
                    } break;
                }
            } break;
                //最重要的结果事件回调
            case AIUIConstant::EVENT_RESULT: {
                Json::Value bizParamJson;
                Json::Reader reader;

                if (!reader.parse(event.getInfo(), bizParamJson, false)) {
                    cout << "parse error!" << endl << event.getInfo() << endl;
                    break;
                }
                Json::Value& data = (bizParamJson["data"])[0];
                Json::Value& params = data["params"];
                Json::Value& content = (data["content"])[0];

                string sub = params["sub"].asString();

                if (sub == "nlp" || sub == "iat" || sub == "tts" || sub == "asr") {
                    Json::Value empty;
                    Json::Value contentId = content.get("cnt_id", empty);

                    if (contentId.empty()) {
                        cout << "Content Id is empty" << endl;
                        break;
                    }

                    string cnt_id = contentId.asString();
                    int dataLen = 0;
                    const char* buffer = event.getData()->getBinary(cnt_id.c_str(), &dataLen);

                    if (sub == "tts") {
                        Json::Value&& isUrl = content.get("url", empty);

                        if (isUrl.asString() == "1") {
                            std::cout << string(buffer, dataLen) << std::endl;
                        } else {
                            std::cout << event.getInfo() << std::endl;
                        }
                    } else {
                        if (buffer) {
                            string resultStr = string(buffer, dataLen);
                            cout << sub << ": " << resultStr << endl;
                        }
                    }
                }
            } break;

                //上传资源数据的返回结果
            case AIUIConstant::EVENT_CMD_RETURN: {
                if (AIUIConstant::CMD_BUILD_GRAMMAR == event.getArg1()) {
                    if (event.getArg2() == 0) {
                        cout << "build grammar success." << endl;
                    } else {
                        cout << "build grammar error, errcode = " << event.getArg2() << endl;
                        cout << "error reasion is " << event.getInfo() << endl;
                    }
                } else if (AIUIConstant::CMD_UPDATE_LOCAL_LEXICON == event.getArg1()) {
                    if (event.getArg2() == 0) {
                        cout << "update lexicon success" << endl;
                    } else {
                        cout << "update lexicon error, errcode = " << event.getArg2() << endl;
                        cout << "error reasion is " << event.getInfo() << endl;
                    }
                }
            } break;

            case AIUIConstant::EVENT_ERROR: {
                cout << "EVENT_ERROR:" << event.getArg1() << endl;
                cout << " ERROR info is " << event.getInfo() << endl;
            } break;
        }
    }
};

# 多语言调用

为了方便非C/C++开发者的快速集成,SDK也提供C接口,以供其他开发者使用,需要注意的是,Windows平台下函数导出基于__stdcall调用,其他平台则是基于__cdecl 调用,所所有接口如下,具体含义可参考C++的接口

const char *aiui_get_version();

/*******************************AIUIDataBundle********************************/
typedef void* AIUIDataBundle;

int aiui_db_int(AIUIDataBundle db, const char* key, int defaultVal);
long aiui_db_long(AIUIDataBundle db, const char* key, long defaultVal);
const char *aiui_db_string(AIUIDataBundle db, const char* key, const char* defaultVal);
const char *aiui_db_binary(AIUIDataBundle db, const char* key, int* dataLen);

/*******************************AIUI_AIUIEvent*********************************/
typedef void* AIUIEvent;
typedef void (*AIUIMessageCallback)(const AIUIEvent ae, void *data);

int aiui_event_type(const AIUIEvent ae);
int aiui_event_arg1(const AIUIEvent ae);
int aiui_event_arg2(const AIUIEvent ae);
const char *aiui_event_info(const AIUIEvent ae);
AIUIDataBundle aiui_event_databundle(const AIUIEvent ae);
int aiui_strlen(const char * str);

/**********************************AIUIBuffer***********************************/
typedef void* AIUIBuffer;

void aiui_buffer_destroy(AIUIBuffer ab);
AIUIBuffer aiui_create_buffer_from_data(const void* data, size_t len);

/**********************************AIUIMessage**********************************/
typedef void* AIUIMessage;

AIUIMessage aiui_msg_create(int msgType, int arg1, int arg2, const char* params, AIUIBuffer data);
void aiui_msg_destroy(AIUIMessage msg);

/***********************************AIUI_Agent***********************************/
typedef void* AIUIAgent;
AIUIAgent aiui_agent_create(const char* params, AIUIMessageCallback callback, void *data);
void aiui_agent_send_message(AIUIAgent agent, AIUIMessage msg);
void aiui_agent_destroy(AIUIAgent agent);

/***********************************AIUI_Setting**********************************/
enum LogLevel { _info, _debug, _warn, _error, _none };

bool aiui_set_aiui_dir(const char* szDir);
const char * aiui_get_aiui_dir();
bool aiui_set_msc_dir(const char* szDir);
bool aiui_set_msc_cfg(const char* szCfg);
bool aiui_init_logger(const char* szLogDir = "");
void aiui_set_log_level(LogLevel level);
void aiui_set_net_log_level(LogLevel level);
void aiui_set_save_data_log(bool save, int logSizeMB = -1);
bool aiui_set_data_log_dir(const char* szDir);
bool aiui_set_raw_audio_dir(const char* dir);
bool aiui_is_mobile_version();
void aiui_set_system_info(const char* key, const char* val);
# python
#引入库 
# windows
aiui = ctypes.windll.LoadLibrary("aiui.dll") 
# Linux
aiui = ctypes.cdll.LoadLibrary("libaiui.so") 

class AIUIEventListener:
    @abc.abstractmethod
    def OnEvent(self, ev: IAIUIEvent): #回调函数
        pass
    
class IAIUIAgent:
    aiui_agent = None
    listenerWrapper = None
    AIUIListenerCallback = None

    def __init__(self, aiui_agent):
        self.aiui_agent = aiui_agent
        self.aiui_agent_send_message = aiui.aiui_agent_send_message
        self.aiui_agent_send_message.argtypes = [c_void_p, c_void_p]
        self.aiui_agent_destroy = aiui.aiui_agent_destroy
        self.aiui_agent_destroy.argtypes = [c_void_p]
    
    # 函数原型 void aiui_agent_send_message(void* agent, void *msg)
    def sendMessage(self, msg: IAIUIMessage):
        return self.aiui_agent_send_message(self.aiui_agent, msg.aiui_msg)
    
    # 函数原型 void aiui_agent_destroy(void* agent)
    def destroy(self):
        self.aiui_agent_destroy(self.aiui_agent)
        self.AIUIListenerCallback = None
        self.listenerWrapper = None
        self.aiui_agent = None

    # 函数原型 IAIUIAgent *aiui_agent_create(const char *cfg, void *listener, void *user_data)
    @staticmethod
    def createAgent(params: str, listener):
        _f = aiui.aiui_agent_create
        _f.argtypes = [c_char_p, c_void_p, c_void_p]
        _f.restype = c_void_p

        agent = IAIUIAgent(None)
        agent.listenerWrapper = EventCallback(listener)
        agent.AIUIListenerCallback = CFUNCTYPE(None, c_void_p, c_void_p)(agent.listenerWrapper)
        agent.aiui_agent = _f(c_char_p(params.encode('utf-8')), agent.AIUIListenerCallback, None)

        return agent

# C#
class IAIUIAgent {
    public delegate void AIUIMessageCallback(IAIUIEvent ev);
    private IntPtr mAgent = IntPtr.Zero;
    private static AIUIMessageCallback messageCallback;
    
    private static void onEvent(IntPtr ev_) {
        messageCallback(new IAIUIEvent(ev_));
    }
    private IAIUIAgent(AIUIMessageCallback cb, IntPtr agent) {
        messageCallback = cb;
        mAgent = agent;
    }
    public static IAIUIAgent Create(string param, AIUIMessageCallback cb) {
        return new IAIUIAgent(cb, aiui_agent_create(Marshal.StringToHGlobalAnsi(param), onEvent));
    }
    public void SendMessage(IAIUIMessage msg) {
        if (IntPtr.Zero != mAgent)
            aiui_agent_send_message(mAgent, msg.Ptr);
    }
    public void Destroy() {
        if (IntPtr.Zero != mAgent) {
            aiui_agent_destroy(mAgent);
            mAgent = IntPtr.Zero;
        }
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void AIUIMessageCallback_(IntPtr ev);
    
    [DllImport("aiui", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    private extern static IntPtr aiui_agent_create(IntPtr param, AIUIMessageCallback_ cb);
    
    [DllImport("aiui", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    private extern static void aiui_agent_send_message(IntPtr agent, IntPtr msg);
    
    [DllImport("aiui", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    private extern static void aiui_agent_destroy(IntPtr agent);
}

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