Skip to content

集成设备端

微信 VoIP 通话涉及小程序、业务服务端、探鸽云平台、设备端四方交互。设备端基于探鸽云 Nano SDK,负责处理下行呼叫通知、管理通话状态、建立 WHIP 连接、收发信令与媒体流。

接口归属说明

  • 探鸽云提供:Nano SDK C API(TiRtcWhipConnectTiRtcSendCommandTiRtcServiceRequest 等)、信令交互(cmdw=0x2000/0x2001
  • 业务服务端提供:HTTP API(/v1/voip/device/*)及下行通道消息(call_incoming 等),需业务方按 集成业务服务端 自行实现
  • 微信提供:小程序 VoIP 插件、iot_voip_notify 回调

阅读本文前,请先了解 通话流程集成业务服务端


一、需要实现什么

按开发顺序,设备端 VoIP 模块需要依次完成:

顺序模块做什么
1SDK 初始化TiRtcInit + TiRtcStart,等待 SYS_STARTED
2上报媒体能力调用 POST /v1/voip/device/profile,上线时完成
3监听下行通道接收 call_incomingcall_cancelcallers_update
4来电处理判断接听/拒接/忙线,建立 WHIP 连接
5主叫处理POST /v1/voip/device/call → 等回铃 → WHIP 连接
6通话中收发音频流,处理挂断信令(0x2001
7会话清理断开连接、释放资源、回到待机状态

二、SDK 初始化

通话前必须先启动 Nano SDK。

c
#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

必须在上线时调用,否则微信回调到达时服务端无法获取设备能力,呼叫通知将下发失败。

json
// 请求(鉴权方式由业务方自定,如 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

json
{ "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 — 来电通知

json
{
  "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 — 取消呼叫

json
{ "type": "call_cancel", "channel": "wx", "payload": { "wx_room_id": "wxf830863..." } }

根据 wx_room_id 匹配当前通话,匹配到则结束振铃或断开会话。

4.3 callers_update — 授权变更

json
{ "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 连接(接听)

c
#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 时,不能通过命令通道发送挂断。应使用服务请求接口:

c
#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_idcall_incoming payload
wx_model_idcall_incoming payload
wx_session_tokencall_incoming payload 的 wx_server_token
wx_room_idcall_incoming payload
wx_payloadcall_incoming payload,可为空字符串
hangup_reason5=忙线(外呼冲突)、7=用户拒接

详见 设备服务请求接口


六、主叫处理(设备呼叫小程序)

6.1 发起呼叫

调业务服务端 POST /v1/voip/device/call(见上方 3.3 节),成功后进入主叫等待窗口。

6.2 收到回铃

设备收到 call_incoming 后,发现 wx_user_openid 与外呼目标一致 → 自动接听:

c
// 建立 WHIP 连接,成功后 on_command 收到 0x2000(双方对讲建立成功)
TiRtcWhipConnect(peer_id, token, on_whip_connect, NULL);

6.3 中止呼叫

设备在等待窗口内可主动取消。若尚未建立 WHIP 连接,标记本地状态结束即可;若已建连,发送挂断信令:

c
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);

七、通话中信令与媒体

7.1 信令

命令字方向含义触发时机
0x2000平台→设备接通WHIP 连接成功后收到,双方对讲建立成功
0x2001双向挂断设备发送(主动挂断)/ 设备接收(对端挂断)

发送挂断(设备主动):

c
TiRtcSendCommand(hconn, 0x2001, "{\"reason\":0}", 12);
TiRtcDisconnect(hconn);

接收挂断(对端或云端下发):

c
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,可选 alawamropus(默认 alaw)。以 G.711 A-law(8kHz / 单声道 / 320 字节每帧 40ms)为例:

c
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 / 16000audio_channels 支持 1 / 2,设备按自身能力上报,服务端组装 Token 请求时透传。

视频:编码格式取决于 video_mtup_video_mt / down_video_mt,可选 h264h265mjpegnone。纯音频设备设 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 须 staticSDK 只存指针不拷贝,局部变量函数返回后失效
0x2000 由平台下发WHIP 连接成功后通过 on_command 收到,设备无需发送
拒接在 WHIP 前未建连时用 TiRtcServiceRequest,不能发 0x2001

完整 SDK API 见 TiRTC · C API 参考,服务请求接口见 设备服务请求接口,信令字见 微信VoIP通话命令

微信VoIP通话