## VOIP

小微提供的voip能力使得设备与腾讯小微小程序之间能够进行音视频通话。在Linux设备上，考虑到场景和设备性能的原因，目前只提供纯音频的通话能力，不提供视频能力。SDK提供音频拨打逻辑和音频流传输通道，录音、回声消除以及播放由设备端自行完成。音频格式为pcm，16bit。

voip功能在小微SDK中是一个可选功能，他是单独的so存在，默认提供，若您无需voip模块，在编译工程时需要定义`__NO_VOIP`这个宏，或者不要引入voipSDK.h这个头文件，并且不要链接voip相关的lib。

小微的voip事实上是一个多人通话，只是目前在业务逻辑上仅开放了双人的通话，因此逻辑较为复杂，为了降低接入成本，一些错误处理逻辑并未暴露接口出来，出错后基本上挂断即可。voip的具体参数请参考头文件中的说明，这里对voip的流程进行说明。


### VOIP流程

为了便于使用，voip内部已经维护了一个状态机，确保同时只有一个通话正在进行，此时若有其他通话接入，将被直接拒绝，无需上层操作。


#### 接口说明

##### 1. `int voip_service_start(VOIP_CALLBACK *callback, VOIP_INIT_PARAM *param);`

初始化VOIP引擎，在logincomplete之后调用，只需要调用一次，无需反复start和stop。

##### 2. `int voip_service_stop();`

彻底不需要VOIP的时候调用，一般无需调用

##### 3. `int voip_create_call(const char *username, const char *appId, int type);`

发起主叫，注意这里只能给自己的绑定者拨打。

* username 和binder的username对应。
* appId 打给谁，填nullptr即可打给小微小程序
* type 0：默认，1：音视频 2：纯音频，Linux版本只支持纯音频


##### 4. `int voip_answer();`

被叫时接听voip。

##### 5. `int voip_hangup();`

挂断voip，一共有以下使用场景：

* 被叫时拒绝
* 主叫时在接通前取消
* 通话时主动挂断

##### 6. `int voip_send_audio_data(const unsigned char *pData, int dataLen, int playDelayMS);`

通话开始后，发送音频数据，需要不断调用。需要按照回调的要求来采集并且发送。此接口调用频率和每次发送的数据量请参考`on_voip_param_change`。

* pData pcm数据
* dataLen 数据长度
* playDelayMS 播放延时，根据经验来看，设计成100比较好


##### 7.`int voip_get_audio_data(unsigned char *pBuf, int bufLen);`

接收音频数据，这里的无需关注返回值，若没有数据，调用此接口也会获得空的pcm音频。调用频率和send一致。获得数据后自行播放出来即可。

* pBuf 待写入的数据buf，SDK会将音频数据写进去，请严格计算
* bufLen pBuf的长度

#### 回调逻辑

voip的回调一共有以下事件：

##### 1.`void (*on_voip_invited)(const char *fromUsername, int type)`

设备被叫时会收到此回调，fromUsername为绑定者的username，type为通话类型，目前Linux版本只支持2，即纯音频通话，您必须处理此invite，否则对方会一直等待直到超时。

##### 2.`void (*on_voip_join_room)(int result, bool isMaster);`

加入房间的结果。小微的voip事实上是一个多人通话，当您主动发起通话时，首先需要创建并且加入房间，然后邀请对方进入房间（邀请过程由SDK自动完成）。主叫时加入房间失败，通话直接结束。被叫加入房间失败，设备端需重试或调用`int voip_hangup()`来拒绝（重试接口目前未开放，请直接挂断）。

* result 非0就失败，0成功
* isMasetr true就是主叫，false为被叫

##### 3.`void (*on_voip_invite_result)(int result);`

作为主叫方邀请对方，发送的邀请结果。0成功非0失败，失败之后，需调用int voip_hangup()`来终止本次通话（理论上可以重新再邀请，但是本功能暂时保留）。这里一般来说很难失败。

##### 4.`void (*on_voip_cancel)(bool isMaster);`

voip被取消，场景比较多。在通话开始前，作为主叫方，一般是对方拒绝接听；作为被叫方，一般是对方在还自己未选择接听或拒绝的时候就取消了通话。在通话开始后，对方主动挂断，也会收到此回调。无论如何，在收到本回调之后，都意味着通话正常结束。

##### 5. `void (*on_voip_talking)()`

通话开始，可以开始发送和接收数据。这里其实是判断了房间人数从1变为2，即开始通话。

##### 6. `void (*on_voip_finish)()`

通话非正常结束，一般是由于某个流程或网络突然中断等原因造成的。此时停止收发音频，退出voip即可，无需调用其它接口来停止本次通话。

##### 7. `void (*on_voip_param_change)(unsigned int sampleRate, unsigned int sampleLenInms, unsigned int channels, const char *extends);`

voip房间参数改变，这个接口十分重要。房间创建好，通话正式开始前会收到此回调，理论上在通话过程中也会多次改变。收到此回调后，一定要按照要求来动态改变采样率、每帧数据长度和通道数。音频的格式永远是PCM，16位。

* sampleRate 采样率 ，如16000
* sampleLenInms 每次发送/接受数据的长度，单位ms，例如这里的值是30，那么就是每30ms发送和接收一次数据，数据真正的长度据此和sampleRate来计算即可。
* channels 几个通道，一般是1
* extends 更为复杂的配置参数，是一个PB，暂时reserved

##### 8. 其它

剩下的回调可以暂时无视，在多人通话、UI展示、静音切换等场景下使用。若需要使用，请咨询我们。

#### 接口调用逻辑
使用voip时，应按照以下流程操作。

1. init SDK时，首先等待logincomplete。
2. 调用`voip_service_start(...)`接口初始化voip引擎，此接口只需要调用一次。
3. 主动发起通话时，调用`voip_create_call(...)`来发起；接听通话时，首先会收到`on_voip_invited(...)`，然后调用`voip_answer()`来接听。
4. 在任何时候，若需要停止voip，调用`voip_hangup()`，包括主叫时放弃、被叫时拒绝和通话过程中挂断。
5. 通话过程中持续调用`voip_send_video_data(...)`和`voip_get_video_data()`来发送和接收音频流。
6. 一般情况下无需调用`voip_service_stop()`，除非需要重置初始化参数。


#### 总结流程图

##### 主叫流程
![createCall](https://res8.wxqcloud.qq.com.cn/wxdoc/09b49ab0-06e6-4d82-bdc4-cb393632955b.png)


##### 被叫流程
![answerCall](https://res8.wxqcloud.qq.com.cn/wxdoc/b5d2e293-b2f0-4e35-8939-9edf57925d28.png)


### 常见问题

#### 设备如何接听VOIP

理论上可以以任意的方式，例如手动按设备的某个按键。但是对于无屏音箱，我们推荐语音接听的方式。在收到被叫信息后，我们的推荐处理流程如下：

* step1 开始响铃，铃声我们跟头文件一起提供了。
* step2 可以去binderList查一下谁打来的，优先获取remark，没有就获取nickname，然后使用小微的TTS合成接口去合成“收到来自xxx的电话，接听还是拒绝”？
* step3 拿到TTS后，铃声降低，播放TTS，同时开始V2A请求，让小微返回“接听”或“拒绝”。这个过程若回声消除不行，可以在TTS播放完之后再发起V2A请求。
