# 腾讯小微设备APP使用文档

# 一、简介

# 1.1 设备APP形态

为了简化微信小微在Android平台的对接难度,提升方案的集成度、安全性,我们提供设备APP的方式进行对接。简单来说,我们把小微的全部功能封装成了一个主要运行在后台的Android APP,该APP以aidl的方式对外提供小微服务。

在使用小微功能时,首先启动小微APP,它会运行在后台,等待外部的bind。在您自己的应用启动后,以client的形式与小微APP建立连接,并使用小微的全部功能。

bind

# 1.2 资源和文档

为了便于接入,我们提供以下内容

  1. 小微设备APP(apk文件)
  2. aar包(包含小微的数据结构类和接口文件)
  3. user demo(源代码,除唤醒模块外,其它部分遵循MIT开源协议)
  4. 说明文档(PDF)

# 1.3 voip和联系人

为了简化交互逻辑,小微设备APP封装了音视频通话(voip)和联系人功能,这部分的activity和UI已经由小微完成,并适配了大部分分辨率,细节接口不对外开放。您可以通过指令来拉起联系人扫码绑定、联系人操作和voip通话界面。如果您发现我们的UI在您的设备上适配有问题,请联系我们解决。

# 1.4 小微设备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开始重复上述操作。

# 二、账号体系

# 2.1 官网注册

接入小微前,首先需要在小微的官方平台上注册,并获得产品标识ProductId(PID)。PID唯一标识了您的产品,假如您有两款不同的产品,建议申请两个PID,OTA以及其它定制服务以PID为粒度。

小微后台采用签名认证方式来确保您的设备的合法性。对于每一个PID,您需要使用我们提供的shell脚本或c++算法源代码或windows工具来自己生成一组秘钥对,并将公钥publickKey提供给我们;对应的私钥privateKey您应妥善保存,后续用于对您的设备进行签名,签名算法为ecdsa,具体信息可以参考对应的readme。我们获得公钥后,会为您分配一个秘钥版本KeyVersion(未来您可以更新秘钥并替换版本)。这个流程在网站上有详细说明,这里不赘述。

# 2.2 Login参数

在与设备APP建立连接后,需要调用登录接口,需要传入以下4个参数:

  • SN 设备serial number,字符串,应保证每台设备拥有唯一的SN
  • Licence 利用私钥对SN的签名结果(SN = ecdsa(SN, privateKey)),显然这也是设备唯一的
  • KeyVersion 签名使用的秘钥版本,在官网上传publicKey时获得
  • PID 一个系列的产品PID均相同

# 2.3 特别说明

  • 一般的,privateKey应保存云端或产线中,在设备生产时生成上述4个参数,并烧录在设备中。建议不要将privateKey和签名算法保存在设备中,这样安全无法得到保证。
  • 在测试阶段,我们可以提供少量的测试账号供您直接使用。
  • 切勿多台设备以相同账号登录,会导致互相踢掉线

# 三、接口说明

在xiaowei-api-sdk.aar中,包含了小微设备APP的通讯接口,在xiaowei-model.aar中,包含了小微业务的数据结构、基础工具类和全局错误码、控制指令定义。在这里对一些关键的接口进行说明,具体细节接口请参考代码说明。

# 3.1 com.tencent.wxservice.IXWService

这是小微设备APP的base接口,包含了一些基础功能,如建立连接后需要进行的login,删除所有联系人并回复设备初始状态的unBindAll、以及其它模块实例获取接口。

  • login(...)小微基础Service,如设备登录、清空联系人、获取其它模块的实例等。
  • getAudioApi()获取语音请求实例,在这个实例中发起语音/文本请求,并设置回调监听。
  • getMusicPlayerApi()获取资源管理器实例,在这个实例中上报您的设备播放状态、管理从小微获得的媒体资源列表,同时进行一些通用的请求。
  • getSkillApi()获得技能管理器实例,在这个实例中,您可以拉起voip,管理闹钟、视频等本地/第三方技能。
  • int startXiaoweiService()获取小微服务是否成功 0 成功 其他结果失败。
  • getDeviceManager()获取设备管理信息。
  • getSDKVersion()获取SDK版本号。
  • getUin()获取设备UIN。
  • unBindAll()解绑所有管理员。
  • int refreshQRCodeUrl()刷新二维码。

# 3.2 com.tencent.audio.IAIAudio

这个接口封装了小微的语音/文本请求及回调,这是小微APP的核心。

String request(int type, byte[] requestData, XWRequestInfo param)用于发起语音请求。其中type定义在了XWCommonDef.java中的RequestType有以下类型:

  • VOICE,语音请求,此时requestData为PCM音频数据,需要多次调用该接口分片发送音频数据。
  • TEXT,文本请求,此时requestData即为请求文本,UTF-8。
  • ONLY_TTS,TTS请求,此时requestData即为请求文本,UTF-8,您将会在异步的response中获得需要的TTS
  • WAKEUP_CHECK,唤醒校验,类似语音请求。 setAudioRequestListenerIAudioRequestListener 设置语音请求监听 。 int requestCancel(String voiceId)通知SDK强制取消这次请求。 小微的所有请求(即调用request(...)方法)均为异步请求,request(...)接口的返回值请求的ID,您需要从异步回调的ID中与之对应。即通过setAudioRequestListener(...)接口来设置一个监听。注意小微后台有可能会主动产生推送,例如小程序端给设备端点播了一首歌,此时IAudioRequestListener接收到的响应内的ID是由后台产生的随机值。

# 3.3 com.tencent.xiaowei.common.ICommon

在这个接口中,包含了小微的通用请求,如资源获取、状态上报等。

  • getMorePlaylist(...),getPlayDetailInfo(...),refreshPlayList(...) 分别用于拉取更多播放资源、拉取播放资源详情和刷新播放链接。这三个接口是废弃接口,目前您仍然可以使用它,但是我们并不建议,因为通过这三个接口发起的请求,会从通用的响应中返回(IAudioRequestListener),您需要将调用这三个接口时的返回值(ID)与响应中的ID对应。
  • request(String cmd, String subCmd, String params, in IOnRspListener listener)是我们推荐的通用请求接口,几乎所有的请求都能从这里发起,您只需要填写cmd、subCmd和params即可,关于命令字,请参考后文的详细说明。
  • reportPlayState(...),设备状态上报接口,您需要在设备播放状态变化的时候通过该接口向小微上报,以便小微能够正确知道您的状态并回答您的请求。
  • String requestTTS(in byte[] strText, RequestListener listener)发起TTS请求。
  • void cancelTTS(String resId)取消TTS请求。

# 3.4 com.tencent.xiaowei.listener.IAudioRequestListener

这个接口只有一个回调方法,即:

boolean onRequest(String voiceId, int event, in XWResponseInfo rspData, in byte[] extendData);

当您调用IAIAudio.request(...)ICommon.{废弃的接口}或直接收到来自小微后台的push后,就会触发这个回调。在这个回调里您需要处理相关的结果,这在后文将详细说明。

# 3.5 com.tencent.xiaowei.listener.IOnDeviceLoginListener

这个接口的4个事件都很关键,您需要在Login时set这个Listener并处理这里的事件。

  • onLoginComplete(int errorCode),登录成功或失败,errorCode为0则成功,非0您需要检查您的登录参数。登录成功后才能使用小微的其它接口。
  • onLogoutComplete(int errorCode),退出登录成功。
  • onNetStatusChange(int newStatus),网络状态变化,注意和登录成功区分开,0离线,1在线。这个回调表示当前设备与小微后台的连接状态,当处于离线时,您的请求可能无法到达。
  • hasBindToManager用户是否绑定设备的返回。
  • onQrCodeRefresh(String qrCodeUrl, int expireTime)获取二维码和二维码过期时间 ,5分钟过期,过期前需要主动刷新。
  • onBinderListChange(in List<DeviceBinderInfo> binderList)设备绑定列表发生变化后此接口会返回新的绑定列表。

# 3.6 com.tencent.xiaowei.skill.IXWSkill

  • void initVideoAgent(String appId,String secretKey,IDeviceVideoServiceAidl videoService)初始化视频中间件。
  • void voiceAIVideo(String requestText) 发送ASR 请求文本给视频中间件 。
  • void setVoipEventCallBack(IVoipCallEventListener callEventListener)voip 事件回调 停止/恢复client端的录音功能。
  • void openVoipContactsList()进入SDK端用户列表。在这个列表中用户操作的是SDK端的VOIP逻辑。
  • void startCallVoip(String binder_username,String nickname)用于VOIP打电话。
  • 用户收到VOIP逻辑接口完全没有必要暴露,接听是一个监听在SDK端一直运行监听,只要收到直接跳转。 //TODO解释一下VOIP到达接口,这里应该只暴露出去收到voip这个事件,所有接打voip的逻辑都在内部,不暴露出去。

# 3.7 com.tencent.xiaowei.listener.IOnRspListener

  • void onRsp(String voiceId, int error, String json)获取历史播放的歌单。

# 3.8 com.tencent.xiaowei.listener.IVoipCallEventListener

  • void onVoipStart()voip已开启。
  • void onVoipEnd()voip已关闭。

# 3.9 com.tencent.xiaowei.skill.IDeviceVideoServiceAidl

  • void playTTS(String json)播放TTS。
  • void onAsrResult(String result)视频中间件相应结果回调。
  • void onEnterVideoEnv()在腾讯视频环境内,所有的音频或文本请求都分发给腾讯视频。
  • void onExitVideoEnv()退出腾讯视频环境。

# 四、语音请求流程

# 4.1 采音

在初始化完成后(onLoginComplete(0)),即可调用小微相关接口来发起小微语音请求。语音请求对音频数据的要求和注意事项:

  • 16位,16kHz,单声道pcm数据
  • 分片提交,每片大小为64~6400个数据点
  • SDK不会对声音进行压缩之外的任何处理,设备需要自行实现软硬件降噪和回声消除,以便达到更佳的识别效果
  • 小微团队会对设备的采音质量进行评估,您的设备采音质量需要达到相关规定

# 4.2 唤醒

设备唤醒由厂商自行实现,也可使用小微团队提供的唤醒方案。小微同时支持本地唤醒云端校验功能,可参考接口具体文档。在某些情况下,小微会主动要求设备唤醒,如多轮对话,此时厂商应按照小微的要求直接唤醒设备,并发起语音请求。

# 4.3 发起请求

# 4.3.1 简介

文本请求只需调用一次请求接口,语音请求则需要多次调用,以语音请求为例,您需要多次使用xiaowei_request接口不断将语音数据发送给小微SDK,SDK会将数据压缩并上传至后台进行识别,在这个过程中,您会通过相应的回调接口(Linux为on_request_callback,Android已将其封装为onRequestCallback Listener)收到一系列的ASR事件,如检测到说话,获得ASR结果等。在设备端可以进行相应的UI展示。一般的语音请求流程如下:

ASR

其中 Event 一般都会遵循如下顺序回调:

  1. ON_DIALOG_BEGIN (一轮对话开始)
  2. ON_SPEAK(开始说话)
  3. 多个ON_RECOGNIZE (持续收到中间结果)
  4. ON_SILENT (静音)
  5. ON_RESPONSE (收到响应)
  6. ON_DIALOG_END (一轮对话结束)

# 4.3.2

如上文所述,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

  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
     */
    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可能为中间结果,表示唤醒成功了,还在继续检测连续说话或者已经在连续说话了
     */
    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(XWResponseInfo),XWResourceInfo代表一个具体的资源。

在正常情况下,是按照二维数组的顺序一个一个进行播放。如果发生用户主动切换“上一首”、“下一首”操作,则需要按照Group这个一维数组的维度进行切换。具体的意义可以参考下图:

正常情况下先播放resources[0].resource[0],然后播放resources[0].resource[1],当第一个resources的资源全部播放完毕之后,播放第二个resources。而当切歌时,则应该从resources[0]直接切换到resources[1]

# hasMorePlaylist

这个字段表示是否还有更多的资源可以拉取,在某些skill下,例如音乐,点歌时只会返回若干首(默认6首)资源,当您快要播放完毕的时候,可以调用资源拉取接口来获得更多的资源。

# hasHistoryPlaylist

在某些sikill下(目前只支持音乐),我们还为您维护了历史播放列表,当用户使用上一首功能到第一首的时候,可以调用拉取历史列表的接口来获取历史歌单。

# recoveryAble

当前资源(resourceGroup)被打断后是否应该继续播放。例如音乐一般就是可以继续播放的,当设备播放音乐时,如果与小微闲聊几句,聊完之后音乐应该恢复播放。天气就是一种不能恢复播放的资源,当询问小微天气后,小微正在播报天气的过程中如果打断她,后续就不应该再播放天气了。

# isNotify

这个字段表示当前资源是否是一个notify资源。对于notify资源,不应该对当前的资源列表产生任何操作,这里在下面play_behavior里面有体现。notify资源应该单独起一个线程去播放,播完就释放。在我们的Android Demo中,当收到notify资源时,当前正在播放的资源音量会降低到20%,然后另起一个播放器播放notify,二者叠加。当notify播放完毕后,当前资源恢复100%音量。常见的notify例子是,播放音乐的时候对小微说收藏这首歌,然后收到notify:"收藏xxx成功"。

# playBehavior

资源列表拼接类型,如替换当前列表、插入尾部、插入头部等。

# resourceListType

当前列表或历史列表

# responseText

小微回答对应的文本,一般来说resourceGroup[0].resources[0]就是这句话的TTS。

# controlId

控制指令ID,例如播放、暂停等。当这个值为0时,表示默认操作,也就是按顺序播放(处理)所有资源。注意当某次返回既有资源又有control_id时,应该先按照play_behavior处理资源,然后再执行控制指令。

# controlValue

与control_id对应,某些控制没有control_value,例如暂停。而某些控制则有,例如快进到某个位置,这个value就是快进到的offset值。

# 七、状态上报&厂商约束

由于控制层是开源的,小微SDK并不知道您的设备当前状态。另一方面,您的设备可能有物理按键,用户可以通过按键来对设备进行操作,这个过程小微SDK也是不知道的。然而很多场景下,小微SDK是需要知道设备状态的,如小程序可以实时显示设备状态,又例如您询问小微,这首歌是谁唱的,小微是需要知道设备当前播放状态的。

# 厂商约束

由于上述原因,我们要求您必须按照我们的规则来上报当前设备状态,否则你的服务将得不到保证。状态上报接口为:

  • reportPlayState(XWPlayStateInfo stateInfo)

您应该在设备状态变化或者收到需要上报的控制指令之后及时上报,只需要上报大资源,即打断后能够恢复的资源(isRecovery = true),例如音乐,FM。无需上报小资源,例如TTS播放。这里列举一下常用场景的上报:

  1. 切歌:报2次,首先第一首歌stoped、然后第二首歌playing(如果能获取到prepare状态的话,先报prepare再playing)。
  2. 播放过程中唤醒了开始语音请求(这时候正常音乐是要暂停的):上报paused
  3. 操作(2)点了一首新歌,并且要准备播放了:先报刚才那首暂停的歌stoped,再报新歌playing
  4. 操作(2)问了一下天气或者闲聊:首先无需任何上报,音箱播放天气或者闲聊的TTS,对话结束之后正常逻辑应该恢复播放音乐,然后这时候上报playing。
  5. 该播放的东西播完了,无事可做:上报idle