集成设备端
微信 VoIP 通话涉及小程序、业务服务端、探鸽云平台、设备端四方交互。设备端基于探鸽云 Nano SDK,负责处理下行呼叫通知、管理通话状态、建立 WHIP 连接、收发信令与媒体流。
接口归属说明:
- 探鸽云提供:Nano SDK C API(
TiRtcWhipConnect、TiRtcSendCommand、TiRtcServiceRequest等)、信令交互(cmdw=0x2000/0x2001)- 业务服务端提供:HTTP API(
/v1/voip/device/*)及下行通道消息(call_incoming等),需业务方按 集成业务服务端 自行实现- 微信提供:小程序 VoIP 插件、
iot_voip_notify回调
一、需要实现什么
按开发顺序,设备端 VoIP 模块需要依次完成:
| 顺序 | 模块 | 做什么 |
|---|---|---|
| 1 | SDK 初始化 | TiRtcInit + TiRtcStart,等待 SYS_STARTED |
| 2 | 上报媒体能力 | 调用 POST /v1/voip/device/profile,上线时完成 |
| 3 | 监听下行通道 | 接收 call_incoming、call_cancel、callers_update |
| 4 | 来电处理 | 判断接听/拒接/忙线,建立 WHIP 连接 |
| 5 | 主叫处理 | 调 POST /v1/voip/device/call → 等回铃 → WHIP 连接 |
| 6 | 通话中 | 收发音频流,处理挂断信令(0x2001) |
| 7 | 会话清理 | 断开连接、释放资源、回到待机状态 |
二、SDK 初始化
通话前必须先启动 Nano SDK。
#include "tiRTC.h"
static TIRTCCALLBACKS cbs; // 必须 static 或全局,SDK 只存指针
// license 格式:"device_id,device_key"
char license[256];
snprintf(license, sizeof(license), "%s,%s", device_id, device_key);
// 可选:覆盖服务入口
TiRtcSetOption(TIRTC_OPT_SERVICE_ENDPOINT, endpoint, strlen(endpoint));
memset(&cbs, 0, sizeof(cbs));
cbs.on_event = on_event;
cbs.on_command = on_command; // 接收对端信令
cbs.on_audio = on_audio; // 接收下行音频
cbs.on_conn_error = on_conn_error;
cbs.on_disconnected = on_disconnected;
// 其余回调填空函数,不能留 NULL
TiRtcStart(license, &cbs);
// 等待 on_event(TIRTC_EVENT_SYS_STARTED) 后才能做连接操作回调注册要点:
| 回调 | 必须实现 | 用途 |
|---|---|---|
on_event | 是 | 等待 SYS_STARTED / SYS_STOPPED |
on_command | 是 | 接收对端挂断 0x2001,执行断开逻辑 |
on_audio | 是 | 接收下行音频数据 |
on_conn_error | 是 | 连接异常时清理会话状态 |
on_disconnected | 是 | 对端断开时清理会话状态 |
| 其余 | 需填空函数 | SDK 要求所有字段非 NULL |
三、调用业务服务端 API
以下 HTTP 接口由业务服务端按 集成业务服务端 自行实现,设备端对接调用。
3.1 上报媒体能力
POST /v1/voip/device/profile
必须在上线时调用,否则微信回调到达时服务端无法获取设备能力,呼叫通知将下发失败。
// 请求(鉴权方式由业务方自定,如 JWT)
{
"screen_width": 0,
"screen_height": 0,
"audio_rate": 8000,
"audio_channels": 1,
"video_mt": "",
"no_video": true,
"calling_timeout_sec": 30
}各参数取值范围以探鸽云 Token 接口 为准。每次上线调用一次即可,服务端会覆盖更新。
3.2 查询授权用户列表
GET /v1/voip/device/callers
返回已授权可通话的微信用户列表。设备主动呼叫前从此列表选取目标用户。
3.3 设备主动呼叫小程序
POST /v1/voip/device/call
{ "device_id": "TIRZ00000001", "wx_user_openid": "o4DLd5...", "wx_room_type": "voice" }成功后进入主叫等待窗口。服务端内部调微信 iot/voip/call,用户接听后设备会收到 call_incoming。
四、监听下行消息(业务服务端推送)
业务服务端通过长连接通道向设备推送消息,技术选型不限。以 MQTT 为例:设备按 sn_{device_id} 身份连接 Broker,订阅指令通道和通知通道。消息格式与字段含义详见 集成业务服务端 · 设备下行通道。
所有消息使用 JSON 信封 {"type": "...", "channel": "wx", "payload": {...}}。
4.1 call_incoming — 来电通知
{
"type": "call_incoming",
"channel": "wx",
"payload": {
"peer_id": "whips://wxvoip?...",
"token": "v1.eyJ...",
"wx_room_id": "wxf830863...",
"wx_user_openid": "o4DLd5...",
"wx_server_token": "...",
"wx_session_key": "...",
"wx_payload": "...",
"wx_from": "miniapp"
}
}设备收到后必须回复确认,再进入来电状态判断。
4.2 call_cancel — 取消呼叫
{ "type": "call_cancel", "channel": "wx", "payload": { "wx_room_id": "wxf830863..." } }根据 wx_room_id 匹配当前通话,匹配到则结束振铃或断开会话。
4.3 callers_update — 授权变更
{ "type": "callers_update", "channel": "wx", "payload": {} }收到后重新调 GET /v1/voip/device/callers 刷新本地授权列表。
五、来电处理(设备被叫)
设备收到 call_incoming 后,根据当前状态决定行为:
收到 call_incoming
│
├─ 已在通话中(IN_CALL)→ 拒接,reason=7(用户忙)
│ TiRtcServiceRequest("/v1/wxvoip/reject", ...)
│
├─ 已有待确认来电 → 拒接,reason=7
│
├─ 正在外呼中,openid 匹配 → 自动接听(主叫回铃场景)
│ TiRtcWhipConnect → 收到 0x2000(双方对讲建立成功)
│
├─ 正在外呼中,openid 不匹配 → 拒接,reason=5(设备忙线)
│
└─ 空闲 → 进入振铃,等待用户操作
├─ 接听 → TiRtcWhipConnect → 收到 0x2000(双方对讲建立成功)
└─ 拒接 → TiRtcServiceRequest("/v1/wxvoip/reject", reason=7)5.1 建立 WHIP 连接(接听)
#include "tiRTC.h"
static void on_whip_connect(int err, tirtc_conn_t hconn, void *user_data) {
if (err != 0) {
// err 为 TIRTC_E_*,TiRtcGetErrorStr(err) 获取描述
return;
}
// WHIP 连接成功,稍后 on_command 会收到 cmdw=0x2000(双方对讲建立成功)
}
// 被叫接听
TiRtcWhipConnect(peer_id, token, on_whip_connect, NULL);
// 主叫回铃,同样建连即可
TiRtcWhipConnect(peer_id, token, on_whip_connect, NULL);
// 返回 0 仅表示参数校验通过,真正结果看回调重要:
peer_id缓冲区须 ≥ 1024 字节(含 URL 编码的 session_key / session_token,长度不固定)。缓冲区过小会被截断,导致-40012 TIRTC_E_SERVER_ERROR。
5.2 拒接(未建连前)
设备未调用 TiRtcWhipConnect 时,不能通过命令通道发送挂断。应使用服务请求接口:
#include "tiRTC.h"
// 字段全部来自 call_incoming payload
char body[1024];
snprintf(body, sizeof(body),
"{"
"\"wx_app_id\":\"%s\","
"\"wx_model_id\":\"%s\","
"\"wx_session_token\":\"%s\","
"\"wx_room_id\":\"%s\","
"\"wx_payload\":\"%s\","
"\"hangup_reason\":%d"
"}",
wx_app_id, wx_model_id, wx_server_token, wx_room_id, wx_payload, reason);
// 设备已用 license 启动,token 传 NULL
TiRtcServiceRequest("/v1/wxvoip/reject", body, NULL, on_reject_resp, NULL);| 字段 | 来源 |
|---|---|
wx_app_id | call_incoming payload |
wx_model_id | call_incoming payload |
wx_session_token | call_incoming payload 的 wx_server_token |
wx_room_id | call_incoming payload |
wx_payload | call_incoming payload,可为空字符串 |
hangup_reason | 5=忙线(外呼冲突)、7=用户拒接 |
详见 设备服务请求接口。
六、主叫处理(设备呼叫小程序)
6.1 发起呼叫
调业务服务端 POST /v1/voip/device/call(见上方 3.3 节),成功后进入主叫等待窗口。
6.2 收到回铃
设备收到 call_incoming 后,发现 wx_user_openid 与外呼目标一致 → 自动接听:
// 建立 WHIP 连接,成功后 on_command 收到 0x2000(双方对讲建立成功)
TiRtcWhipConnect(peer_id, token, on_whip_connect, NULL);6.3 中止呼叫
设备在等待窗口内可主动取消。若尚未建立 WHIP 连接,标记本地状态结束即可;若已建连,发送挂断信令:
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);七、通话中信令与媒体
7.1 信令
| 命令字 | 方向 | 含义 | 触发时机 |
|---|---|---|---|
0x2000 | 平台→设备 | 接通 | WHIP 连接成功后收到,双方对讲建立成功 |
0x2001 | 双向 | 挂断 | 设备发送(主动挂断)/ 设备接收(对端挂断) |
发送挂断(设备主动):
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);接收挂断(对端或云端下发):
static void on_command(tirtc_conn_t hconn, uint32_t cmdw,
const void *data, uint32_t len) {
if (cmdw == 0x2001) {
// 校验 data 为合法 JSON,解析 reason
TiRtcDisconnect(hconn); // 释放资源
}
}命令字语义详见 微信VoIP通话命令。
7.2 音视频收发
通话接通后,基于 TiRTC 连接收发音视频流。
| 参数 | 值 |
|---|---|
stream_id(音频) | 10 |
stream_id(视频) | 11 |
音频:编码格式取决于设备上报的 down_audio_mt,可选 alaw、amr、opus(默认 alaw)。以 G.711 A-law(8kHz / 单声道 / 320 字节每帧 40ms)为例:
TIRTCFRAMEINFO fi;
memset(&fi, 0, sizeof(fi));
fi.stream_id = 10;
fi.media = TIRTC_AUDIO_ALAW; // G.711 A-law;amr/opus 用对应枚举
fi.flags = TIRTC_AUDIOSAMPLE_8K16B1C; // 8kHz, 16bit, 1ch
fi.ts = (uint32_t)(pts_ms & 0xFFFFFFFF);
fi.length = 320; // G.711a 8kHz 1ch = 320 字节/40ms
int rc = TiRtcSendAudioStream(hconn, &fi, pkt);
// rc > 0: 已发送字节数
// rc < 0: 错误;TIRTC_E_INVALID_HANDLE(-40002) 短暂重试不同编码对应的帧参数见探鸽云 Token 接口字段说明。audio_rate 支持 8000 / 16000,audio_channels 支持 1 / 2,设备按自身能力上报,服务端组装 Token 请求时透传。
视频:编码格式取决于 video_mt 或 up_video_mt / down_video_mt,可选 h264、h265、mjpeg、none。纯音频设备设 no_video: true 即可。
接收:on_audio / on_video 回调中获得对端音视频帧。
八、实现要点
| 要点 | 说明 |
|---|---|
peer_id 不可自行拼接 | 来自服务端 call_incoming,原样传给 TiRtcWhipConnect |
token 不可自行签发 | 同上,由探鸽云 Token 接口生成 |
| 缓冲区大小 | peer_id 字符串含 URL 编码参数,建议 ≥ 1024 字节 |
| 回调不能阻塞 | SDK 回调在内部线程执行,只能设标志位,禁止 sleep / IO |
TiRtcWhipConnect 返回 0 ≠ 成功 | 仅表示参数校验通过,真正结果在回调 err 参数里 |
TIRTCCALLBACKS 须 static | SDK 只存指针不拷贝,局部变量函数返回后失效 |
0x2000 由平台下发 | WHIP 连接成功后通过 on_command 收到,设备无需发送 |
| 拒接在 WHIP 前 | 未建连时用 TiRtcServiceRequest,不能发 0x2001 |
完整 SDK API 见 TiRTC · C API 参考,服务请求接口见 设备服务请求接口,信令字见 微信VoIP通话命令。