## 接入形式

为了简化微信小微在Android平台的对接难度，提升方案的集成度、安全性，我们提供设备APP的方式进行对接。简单来说，我们把小微的全部功能封装成了一个主要运行在后台的Android APP，该APP以aidl的方式对外提供小微服务。

在使用小微功能时，首先启动小微设备APP，它会运行在后台，等待外部的bind。在您自己的应用启动后，以client的形式与小微APP建立连接，并使用小微的全部功能。

![appsdk](https://res8.wxqcloud.qq.com.cn/wxdoc/cc65374a-8254-44f9-a1d1-5513f30b7707.png)

### 设备APP提供的功能

#### 1. 小微的基础对话能力

小微设备APP接受音频或文本数据，对于音频数据进行实时的云端ASR解析，您可以获得文本并实时显示。在完成云端vad检测或由您本地触发静音后，小微后台将完成语义分析和资源请求流程，并将最终的响应结果、TTS以及播放资源回调至用户APP，由用户APP进行资源播放和展示。

#### 2. 小微丰富的媒体资源

小微整合了多方资源，如QQ音乐、企鹅FM、墨迹天气、百科问答等。您只需要通过语音请求即可获取这些资源，资源格式主要为文本和URL，您的播放器需要支持以下类型的URL播放：

* MP3
* m3u8
* AAC（m4a）
* flac


小微的TTS也是URL类型，为了更好的用户体验，需要播放器支持chunk流播放（MP3格式）。

#### 3. 微信音视频通话

为了简化交互逻辑，小微设备APP封装了音视频通话（voip）和联系人功能，这部分的activity和UI已经由小微完成，并适配了大部分分辨率，细节接口不对外开放。您可以通过指令来拉起联系人扫码绑定、联系人操作和voip通话界面。如果您发现我们的UI在您的设备上适配有问题，请联系我们解决。

#### 4. 腾讯视频语音控制

小微设备APP内部集成了腾讯视频相关的控制接口，在设备端安装腾讯视频APP后，即可通过小微来进行视频点播和控制。

### 资源和文档
为了便于接入，我们提供以下内容

1. 小微设备APP（apk文件）
2. aar包（包含小微的数据结构类和接口文件）
3. user demo（源代码，除唤醒模块外，其它部分遵循MIT开源协议）

## 设备APP使用流程
小微设备APP的主要功能及使用步骤为（以语音请求为例）：

1. 等待用户APP连接，连接成功后，由用户APP调用初始化接口，将设备信息传给小微设备APP，小微设备APP将进行鉴权和登录操作。
2. 等待登录成功（登录成功前其它接口调用均会失败）。
3. 设备唤醒，这个过程由您自己实现。在我们的demo中提供了一个唤醒模型，但这个模型**仅供您体验使用**，我们并不提供其商业licence。
4. 由您自己实现录音，并使用小微的语音请求接口，不断将音频数据交给小微设备APP。
5. 等待小微设备APP的异步回调，您可以在这些回调中获得ASR实时结果、最终响应及媒体资源等。
6. 您自行展示ASR文本、answer文本、播放answer TTS和其它媒体资源（如音乐点播）。
7. 一轮对话结束，下次请求从“4”重新开始。
8. 当您不需要小微服务时，直接断开aidl即可。此时小微设备APP会自行logout，这就意味着每次您重新建立aidl连接时，都需要从1开始重复上述操作。

## 登录并启动小微服务
接入小微前，首先需要在小微的[官方平台上注册](https://xiaowei.weixin.qq.com)，并获得产品标识ProductId(PID)。PID唯一标识了您的产品，假如您有两款不同的产品，建议申请两个PID，OTA以及其它定制服务以PID为粒度。

小微后台采用签名认证方式来确保您的设备的合法性。对于**每一个PID**，您需要使用我们提供的shell脚本或c++算法源代码或windows工具来自己生成一组秘钥对，并将**公钥publickKey**提供给我们；对应的**私钥privateKey**您应妥善保存，后续用于对您的设备进行签名，签名算法为ecdsa，具体信息可以参考对应的readme。我们获得公钥后，会为您分配一个秘钥版本KeyVersion(未来您可以更新秘钥并替换版本)。这个流程在网站上有详细说明，这里不赘述。

#### Login参数
在与设备APP建立连接后，需要调用登录接口，需要传入以下5个参数:

* **SN** 设备serial number，字符串，应保证**每台设备拥有唯一的SN**
* **Licence** 利用私钥对SN的签名结果(SN = ecdsa(SN + PID, privateKey))，显然这也是设备唯一的
* **KeyVersion** 签名使用的秘钥版本，在官网上传publicKey时获得
* **PID** 一个系列的产品PID均相同
* **appuin** 与PID对应，注册时获得

#### 特别说明
* 一般的，privateKey应保存云端或产线中，在设备生产时生成上述5个参数，并烧录在设备中。建议不要将privateKey和签名算法保存在设备中，这样安全无法得到保证。
* 在测试阶段，我们可以提供少量的测试账号供您直接使用。
* **切勿多台设备以相同账号登录，会导致互相踢掉线。**


## 语音请求
### 采音
在初始化完成后（`onLoginComplete(0)`），即可调用小微相关接口来发起小微语音请求。语音请求对音频数据的要求和注意事项：

* 16位，16kHz，单声道pcm数据
* 分片提交，每片大小为64~6400个数据点
* SDK不会对声音进行压缩之外的任何处理，设备需要自行实现软硬件降噪和回声消除，以便达到更佳的识别效果
* 小微团队会对设备的采音质量进行评估，您的设备采音质量需要达到[相关规定]()

### 唤醒
设备唤醒由厂商自行实现，也可使用小微团队提供的唤醒方案。小微同时支持本地唤醒云端校验功能，可参考接口具体文档。在某些情况下，小微会主动要求设备唤醒，如多轮对话，此时厂商应按照小微的要求直接唤醒设备，并发起语音请求。注：当闹钟响起时，无屏设备唤醒或触碰任意按键即结束闹铃，有屏设备唤醒或触碰按钮即结束闹铃。


### 发起请求

#### 简介
文本请求只需调用一次请求接口，语音请求则需要多次调用，以语音请求为例，您需要多次使用`request (...)`接口不断将语音数据发送给小微设备APP，设备APP会将数据压缩并上传至后台进行识别，在这个过程中，您会通过相应的回调接口(`IAudioRequestListener.onRequest(...)`)收到一系列的ASR事件，如检测到说话，获得ASR结果等。在设备端可以进行相应的UI展示。一般的语音请求流程如下：

![ASR](https://res8.wxqcloud.qq.com.cn/wxdoc/d7fb533a-6e76-45ee-ade2-34e697350ed0.png)

其中 Event 一般都会遵循如下顺序回调：

1. `ON_DIALOG_BEGIN` (一轮对话开始)
2. `ON_SPEAK`（开始说话）
3. 多个`ON_RECOGNIZE` (持续收到中间结果)
4. `ON_SILENT` (静音)
5. `ON_RESPONSE` (收到响应)
6. `ON_DIALOG_END` (一轮对话结束)


如上文所述，`String IAIAudio.request(int type,in byte[] requestData, in XWRequestInfo param)`是小微最关键的接口，语音或文本数据从这里输入。

当语音或文本请求时，每一轮请求有个唯一的ID（voiceID），即上述接口的返回值，如果请求失败，将返回空的ID。在一轮语音请求中，您需要多次调用该接口，其中第一个参数type为`XWCommonDef.RequestType.VOICE`，每次调用均会返回相同的ID。在请求过程中，会有若干事件从`IAudioRequestListener.onRequest(...)`接口中返回。


`XWRequestInfo` 是请求的上下文信息，每次请求都需要携带，这是维护多对话的标识，同时也配置了对话的一些属性，首轮请求时param.contextId应为空，多轮请求时应携带上一轮请求response中的这个参数。在每一轮请求中，第一次调用`request(...)`时应将`param.voiceRequestBegin`置为true。


### 特别说明
小微SDK提供文本请求的方式，即跳过ASR这一步，此时每次请求您只需要调用一次请求接口即可，此时requestData即为请求文本：
```
IAIAudio.request(XWCommonDef.RequestType.TEXT,in byte[] requestData, null);
```


## 流程总结
这里通过下面一张图，简要总结一下使用小微的流程
![step](https://res8.wxqcloud.qq.com.cn/wxdoc/89d115e3-e6ce-4d4b-be5d-3c47d85dc2f2.png)

1. 初始化小微device，并等待成功
2. 自己唤醒，与小微无关
3. 调用request接口发送第1个语音包
4. 收到`on_dialog_begin`回调
5. 调用request接口发送第2个语音包
6. 调用request接口发送第m个语音包
7. 收到`on_speak`回调
8. 调用request接口发送第n个语音包
9. 收到`on_recognize`回调，拿到ASR实时结果
10. 调用request接口发送第k个语音包
11. 收到`on_recognize`回调，拿到ASR实时结果
12. 调用request接口发送第p个语音包
13. 收到`on_silence`回调，停止发送语音包
14. 等待一小会儿
15. 收到`on_response`回调，获得NLP结果和资源
16. 对小微而言，对话到此结束，收到`on_dialog_end`
17. 用户自己处理资源和控制命令(或者是我们提供的开源控制层)
18. 结束

上述流程是一个最标准的流程，当然在这个过程中可以主动停止对话，或者主动发送静音包，这在接口说明中有详细描述。如果用户发起的是文本请求，那么上述过程第3步就变成了发送文本请求包，5到第13步就不存在了。

## 最终响应

在上述中的流程图中，有一个`ON_RESPONSE`事件，即在回调中拿到了最终响应，也就是获得了NLP的结论和资源。这里详解一下response的数据结构，即`XWResponseInfo`

#### XWResponseInfo详解

```

/**
     * 场景信息，请求命中了哪个技能，根据不同的技能做不同的响应，如音乐就打开播放器播放
     */
    public XWAppInfo appInfo;

    /**
     * 上一次的场景信息，可以暂时不关注
     */
    public XWAppInfo lastAppInfo;

    /**
     * 结果 {@link com.tencent.xiaowei.def.XWCommonDef.XWeiErrorCode}
     */
    public int resultCode;

    /**
     * voice ID，即请求的ID，唯一标识了一次请求。
     */
    public String voiceID;

    /**
     * 上下文信息，关注里面的sessionID，如果非空则说明对话未结束，设备应该继续发起请求，并携带此context
     */
    public XWContextInfo context;

    /**
     * 请求文本，ASR结果文本，请求的语音转文本的实时结果，可以展示
     */
    public String requestText;

    /**
     * 响应扩展数据，json格式
     */
    public String responseData;

    /**
     * 用户扩展的意图信息，json格式
     */
    public String intentInfoForUser;
    /**
     * 资源集合list
     */
    public XWResGroupInfo[] resources;

    /**
     * 资源列表类型，可能为当前列表、历史列表等类型{@link com.tencent.xiaowei.def.XWCommonDef.ResourceListType}
     */
    public int resourceListType;

    /**
     * 向下是否有更多资源
     */
    public boolean hasMorePlaylist;
    /**
     * 向上是否有更多资源
     */
    public boolean hasMorePlaylistUp;
    /**
     * 是否有历史记录
     */
    public boolean hasHistoryPlaylist;

    /**
     * 资源是否可以暂停恢复
     */
    public boolean recoveryAble;

    /**
     * 资源列表拼接类型{@link com.tencent.xiaowei.def.XWCommonDef.PlayBehavior}
     */
    public int playBehavior;

    /**
     * 这个响应的资源是通知或者提示，不应该影响当前该场景的列表变化，只是插播一下。例如音乐场景中询问"现在在放什么歌"，"周杰伦 稻香"这个TTS就是这种资源。
     */
    public boolean isNotify;

    /**
     * 云端唤醒校验结果，0表示非该类结果，1表示唤醒校验失败，2废弃 3表示请求正式结束，可以处理最终响应，4可能为中间结果，表示唤醒成功了，还在继续检测连续说话或者已经在连续说话了，可以做一些UI展示，请求继续
     */
    public int wakeupFlag;

    /**
     * 自动化测试扩展数据，无需关注，一般为空值
     */
    @Deprecated
    public String autoTestData;

    /**
     * 回答文本
     */
    public String responseText;

    /**
     * 控制指令
     */
    public int controlID;

    /**
     * 控制指令的值
     */
    public String controlValue;

```



#### appInfo
这轮请求命中了哪一个技能，如音乐、闲聊等，一般来讲用户应根据命中的不同技能进行不同的操作，如打开音乐播放器，加载UI等。

#### lastAppInfo
这个和appInfo结构一样，标识了您当前处于的skill状态，这个状态事实上是依赖于您的状态上报的，这在后续有详细说明。


#### voiceID
这个响应针对哪一次请求，voiceID唯一标识了一次请求。


#### context
上下文信息。每次请求您都需要携带一个context，同样在返回时您会获得一个context。在context中维护了多轮对话的状态，当您收到响应时发现context中申明了id和speakTimeout时，则表明这是一个多轮对话，在下次请求的时候带上这个id标记。关于context的具体说明请参考接口说明。

#### responseData
这个字段主要针对一些自定义技能，小微APP在responseData中返回某些技能所需的额外数据或资源。

#### intentInfoForUser
这个字段向用户暴露意图槽位以及用户定制的一些返回。该字段默认不返回任何数据，如果您需要定制化某些内容，请与商务联系。

#### resources
小微返回的资源全部位于这里。
XWResGroupInfo是一个数组，在每个元素中又有若干个resources(XWResourceInfo)，XWResourceInfo代表一个具体的资源。

在正常情况下，是按照二维数组的顺序一个一个进行播放。如果发生用户主动切换“上一首”、“下一首”操作，则需要按照Group这个一维数组的维度进行切换。具体的意义可以参考下图：

![](https://res8.wxqcloud.qq.com.cn/wxdoc/94974366-987b-4295-9357-71122c4bf1bc.png)

正常情况下先播放`resources[0].resource[0]`，然后播放`resources[0].resource[1]`，当第一个resources的资源全部播放完毕之后，播放第二个resources。而当切歌时，则应该从`resources[0]`直接切换到`resources[1]`。


#### hasMorePlaylist
这个字段表示是否还有更多的资源可以拉取，在某些skill下，例如音乐，点歌时只会返回若干首(默认6首）资源，当您快要播放完毕的时候，可以调用资源拉取接口来获得更多的资源。

#### hasHistoryPlaylist
在某些sikill下(目前只支持音乐)，我们还为您维护了历史播放列表，当用户使用上一首功能到第一首的时候，可以调用拉取历史列表的接口来获取历史歌单。

#### recoveryAble
当前资源(resourceGroup)被打断后是否应该继续播放。例如音乐一般就是可以继续播放的，当设备播放音乐时，如果与小微闲聊几句，聊完之后音乐应该恢复播放。天气就是一种不能恢复播放的资源，当询问小微天气后，小微正在播报天气的过程中如果打断她，后续就不应该再播放天气了。

#### isNotify
这个字段表示当前资源是否是一个notify资源。对于notify资源，不应该对当前的资源列表产生任何操作，这里在下面playBehavior里面有体现。notify资源应该单独起一个线程去播放，播完就释放。在我们的Android Demo中，当收到notify资源时，当前正在播放的资源音量会降低到20%，然后另起一个播放器播放notify，二者叠加。当notify播放完毕后，当前资源恢复100%音量。常见的notify例子是，播放音乐的时候对小微说收藏这首歌，然后收到notify:"收藏xxx成功"。

#### playBehavior
资源列表拼接类型，如替换当前列表、插入尾部、插入头部等。

#### resourceListType
当前列表或历史列表

#### responseText
小微回答对应的文本，一般来说resourceGroup[0].resources[0]就是这句话的TTS。

#### controlId
控制指令ID，例如播放、暂停等。当这个值为0时，表示默认操作，也就是按顺序播放(处理)所有资源。注意当某次返回既有资源又有controlId时，应该先按照playBehavior处理资源，然后再执行控制指令。

#### controlValue
与controlId对应，某些控制没有controlValue，例如暂停。而某些控制则有，例如快进到某个位置，这个value就是快进到的offset值。

## 状态上报及厂商约束
由于控制层是开源的，小微SDK并不知道您的设备当前状态。另一方面，您的设备可能有物理按键，用户可以通过按键来对设备进行操作，这个过程小微SDK也是不知道的。然而很多场景下，小微SDK是需要知道设备状态的，如小程序可以实时显示设备状态，又例如您询问小微，这首歌是谁唱的，小微是需要知道设备当前播放状态的。

#### 厂商约束
由于上述原因，我们要求您必须按照我们的规则来上报当前设备状态，否则你的服务将得不到保证。状态上报接口为：

* `reportPlayState(XWPlayStateInfo stateInfo)`

您应该在设备状态变化或者收到需要上报的控制指令之后及时上报，只需要上报大资源，即打断后能够恢复的资源(isRecovery = true)，例如音乐，FM。无需上报小资源，例如TTS播放。这里列举一下常用场景的上报：

 1. 切歌：报2次，首先第一首歌abort、然后第二首歌start(如果可以的话，先报preload，再start)。
 2. 播放过程中唤醒了开始语音请求(这时候正常音乐是要暂停的)：上报paused
 3. 操作2点了一首新歌，并且要准备播放了：先报刚才那首暂停的歌abort，再报新歌start
 4. 操作2问了一下天气或者闲聊：首先无需任何上报，音箱播放天气或者闲聊的TTS，对话结束之后正常逻辑应该恢复播放音乐，然后这时候上报resume。
 5. 该播放的东西播完了，无事可做：上报idle
 6. 一首歌自然播放完毕，开始播放下一首：上报第一首stop，再上报第二首start
 7. 所有的URL资源，如果可以的话，start之前先报preload
 8. 播放列表资源全部播放完毕，退出播放，上报finished，注意携带skilid。此时如果自动去播放别的了，就该报什么报什么，如果什么也不播放了，就上报idle。

服务器有时会主动下发一些消息推送，当设备收到推送的消息时，需要设备去处理播报tts。我们要求您必须按照我们的规则去处理。否则你的服务将得不到保证。如：开机提示；iot设备控制结果或查询结果的推送等。注：服务器会过滤，避免在休息时间下发消息。



## 微信互动

微信互动支持的资源类型包括音乐、图片、文件、视频、地理位置、公众号文章。

音乐资源复用音乐技能，使用“音乐”skillId和数据格式。

其他资源类型使用“微信互动”技能skillId：“8dab4796-fa37-4114-1000-7637fa2b0001”。设备通过解析XWResourceInfo来获取分享的具体内容。

XWResourceInfo.format标识资源内容类型，包含：
```java
public class XWCommonDef {
		....
    /**
     * 资源格式定义
     */
    public interface ResourceFormat {
      ....
      RESTYPE_IMAGE_SHARE = 10;
      RESTYPE_FILE_SHARE = 11;
      RESTYPE_POI_SHARE = 12;
      RESTYPE_VIDEO_SHARE = 13;
      RESTYPE_URL_SHARE = 14;
      ....
    }
}
```

XWResourceInfo.extendInfo放置公共信息，例如分享人昵称，分享人头像。
```json
{
    "share_user": {
        "nickname": "",
        "headimgurl": ""
    }
}
```
XWResourceInfo.content为资源内容，下面具体介绍每种资源的格式

#### 图片
```json
{
    "download_url": "",
    "enckey": "0"
}
```
下载后的图片是加密的，需要根据enckey解密后才能展示。
[解密接口](../interface/interface#微信互动资源解密)

#### 文件
```json
{
    "type": "pdf",
    "name": "xxx.pdf",
    "size": 0,
    "md5": "0",
    "download_url": ""
}
```
无加密，下载后需要更改文件名和文件后缀

#### 视频
```json
 {
    "type": "",
    "size": 0,
    "md5": "",
    "url": ""
}
```

无加密

#### 地理位置

```json
{
    "latitude": 39.983982086,
    "longitude": 116.308128357,
    "scale": 15,
    "label": "北京市北京市海淀区北三环西路66号",
    "name": "彩和坊路"
}
```

#### 公众号文章

```json
{
    "url": "",
    "title": "",
    "des": "",
    "appname": ""
}
```
