# 云对云服务端
# 1. 运行环境要求
- x86_64 运行环境
- glibc 2.28 或以上
- libcurl 7.84.0 或以上
# 2. 服务端消息接收与推送
对指定的 openid 发起呼叫后,若呼叫成功,微信开放平台会按照标准的 消息推送 方法向开发者后台推送消息。 开发者按照消息推送指引配置回调地址,消息加密方式必须选择「安全模式」。
需要注意的是,服务商角色可能需要服务多个小程序,可以使用 第三方平台开发。
推送的消息大概如下:
{"ToUserName":"gh_27632a25xxx","Encrypt":"xxxx"}
// Encrypt 解密后的内容,参考字段说明。
开发者后台收到消息通知后,需要及时回复该请求,否则可能触发微信后台的重发,正确的回复格式如下:
{"errcode":0,"errmsg":"ok"}
errcode 为 0 代表回复成功,openid 所在的微信即响铃。若 errcode 为非 0 值,则微信侧不会响铃,此次通话取消。
字段说明:
参数 | 数据类型 | 说明 |
---|---|---|
Action | String | 平台访问开发者后台的事件类型,本接口固定为join_voip_room |
RoomId | String | 本次通话的房间id |
SessionKey | String | 本次通话的 sessionkey |
ServerToken | String | 本次通话的服务端凭证 |
Payload | String | 发起通话时第三方传入的自定义payload |
ModelId | String | Modelid |
Sn | String | Sn |
内容示例:
{
"ToUserName":"gh_12345678",
"FromUserName":"openid",
"CreateTime":1709621375,
"MsgType":"event",
"Event":"iot_voip_notify",
"Action":"join_voip_room",
"Payload":"hello",
"RoomId":"wxf830863afde621ebWmpfVoip112343434123434",
"SessionKey":"COrwxeDmrJQuEOrwxeDmrJQuGhhrpMKQHttT1pG4eldlqIhi/L2qFpMQkn0iGCpUjLhxpYobRqjPK4DHWCHgRBi123412341234",
"ServerToken":"xyrOs/pwIHEZf3MFNjbhRHkL5XOmGDtW2nkor6EcmRLtHcoI6mP123412341234==",
"ModelId":"fJ4exxxxxxx",
"Sn":"1234"
}
# 3. 服务端 VOIP SDK
服务端 sdk 是单例模式,即 sdk 同时只能处理一个音视频通话. 由于开发者的服务端往往需要同时处理多个通话,因此云服务器后台接入服务端 sdk 时,需要每次有新请求时创建一个新进程,并在进程内调用服务端 sdk 接口完成本次的音视频通话.
通过消息推送得到此通呼叫的 server_token 与 payload 后,即可使用 sdk 建立通话。
服务端 sdk 需要数据文件夹路径,调用 wx_init 函数完成初始化工作(不需要小程序 appid 和 modelid). 然后通过 join 接口加入 VoIP 房间,同时接收设备的音视频数据流,通过服务端 sdk 的相关接口填入数据或收到数据,即可完成设备与微信用户的音视频流通信。
# 4. 接口说明
具体接口如下,使用方法请参考 sdk 包中 example 目录下的 demo 代码。
wmpf.h
// Copyright (c) 2023, Tencent Inc.
// All rights reserved.
#pragma once
#include "wmpf/macros.h"
#include "wmpf/module.h"
#include "wmpf/operation.h"
#include "wmpf/types.h"
WX_BEGIN_DECLS
typedef enum wx_wxa_flavor {
WX_WXA_FLAVOR_RELEASE = 0, // 小程序正式版
WX_WXA_FLAVOR_DEBUG = 1, // 小程序开发版
WX_WXA_FLAVOR_DEMO = 2, // 小程序体验版
} wx_wxa_flavor_t;
#define WX_INIT_CONFIG_TAG 0x00001
/**
* @brief WMPF 初始化结构体
*
* 强烈建议你在使用本结构体前 memset 置零, 避免遗忘某个项的设置.
*/
typedef struct wx_init_config {
wx_struct_t common;
/**
* @brief WMPF 日志文件夹
*
* 用于存储 WMPF 日志文件.
*
* 若此项填 NULL, WMPF 将不输出日志.
*/
const char* log_dir;
/**
* @brief WMPF 数据文件夹
*
* 用于存储 WMPF 数据文件.
*
* WMPF 数据文件夹内会保存和设备有关的重要的一次性的注册信息,
* 无法在删除后恢复. 如果你需要清空设备数据,那么你需要备份 WMPF 的数据文件夹.
*/
const char* data_dir;
/**
* @brief 产品 ID.
*
* 该项填 0.
*/
int product_id;
/**
* @brief HostAppID
*
* 该项填 NULL.
*/
const char* host_appid;
/**
* @brief 设备 ID (SN)
*
* 设备的唯一标识符 (唯一序列号), 该项需要由厂商定义.
*/
const char* device_id;
/**
* @brief 设备签名
*
* 该项填 NULL.
*/
const char* device_signature;
/**
* @brief 设备签名版本
*
* 该项填 0.
*/
int device_signature_version;
/**
* @brief Model ID
*
* Model ID 是调用小程序设备相关接口的重要凭证.
* 在设备接入时从【小程序管理后台】申请获得的 Model ID.
*
* @see
* https://developers.weixin.qq.com/miniprogram/dev/framework/device/device-access.html
* https://developers.weixin.qq.com/miniprogram/dev/framework/device/device-message.html
*/
const char* model_id;
/**
* @brief 小程序 AppID
*
* 小程序 AppID 是小程序的唯一标识符, 在 VoIP 场景下此项必填.
* 在 VoIP 发起通话后, 被拨打的微信用户手机将拉起这里被指定的小程序用于通话.
*/
const char* wxa_appid;
/**
* @brief 小程序版本
*
* 对于 VoIP 场景: 要在小程序正式版运行微信 VoIP,
* 需要先提交设备给微信审核通过后才能获得相关权限. 因此在接入调试期间,
* 你需要使用小程序开发版/体验版测试 VoIP 功能的可用性.
*
* 要设置为开发版还是体验版, 请向你的小程序开发同事咨询.
*
* 注意, 如果设备开发同事需要使用小程序开发版测试 VoIP 功能,
* 设备开发同事必须先获得小程序开发者权限,
* 并及时扫描小程序开发同事的微信开发者工具的真机调试二维码,
* 提前在本地微信客户端打开一次开发版小程序, 否则微信客户端在收到 VoIP
* 通话强提醒时, 会缺少小程序开发版代码包, 导致小程序启动失败.
*/
wx_wxa_flavor_t wxa_flavor;
/**
* @brief RPMB 设备路径
*
* 如果设备有 RPMB 且 Key 处于可写状态时, 应当采用 RPMB 方式初始化 SDK.
* 并传入 RPMB 设备路径 (一般为 /dev/mmXXXXX)
*/
const char* rpmb_device;
/**
* @brief 是否仅仅支持 H265
*
* 如果设备只支持 H265,可以设置此字段为 true.
*
* 如果不设置,微信 voip 后台会自适应,开发者也可以发送 H265 流,但无法决定手机端过来的是什么类型流.
* 如果设置 true,则发送和接收都为 H265。
*/
bool h265_only;
/**
* @brief SDK 接收小程序推流是否为 4:3 的流。
*
* true: SDK 收到流的宽高比 4:3 --- 320x240 480x352 640x480 1280x720 1920x1080
* false: SDK 收到流的宽高比 3:4 --- 240x320 352x480 480x640 720x1080 1080x1920
*/
bool video_landscape;
/**
* @brief 订阅 SDK 期望收到的分辨率
*
* 开发者可以用此配置来订阅一个分辨率的长边值。
* 此功能主要是针对那些不希望收到可变分辨率视频流的设备,如果不使用此功能,SDK 收到的视频流在不同的网络环境下会有不同的分辨率。
* 此功能需要同时向微信提交 appid,待开通订阅机制后才生效。
* 目前这个配置仅支持如下两个值:
* 320: video_landscape = true 时收到的是 320x240,video_landscape = fale 时收到的是 240x320
* 640: 不支持 video_landscape = true 模式,video_landscape = fale 时收到的是 480x640
*/
int subscribe_video_length;
} wx_init_config_t;
/**
* @brief 初始化 WMPF
*
* 在执行任意 WMPF 调用之前需要调用 wx_init() 初始化 WMPF.
* 函数提供设备的基础信息、启动并运行 WMPF.
*
* 该函数是异步函数, 会产生网络请求, 请确保在调用本函数时网络通畅.
*
* @param config (nonnull) 初始化参数, 包含设备的初始信息
* @param get_module (nonnull) WMPF 获取厂商提供的接口的回调函数, 实现方法参见
* wx_get_module_t 的文档
* @return 运行是否成功
* - WXERROR_INVALID_ARGUMENT: 输入参数不合法,或者产品 ID、设备
* ID、设备签名不匹配
* - WXERROR_TIMEOUT: 超时
* - WXERROR_RESOURCE_EXHAUSTED: 网络未联通或者本地磁盘写入失败
* - WXERROR_FAILED_PRECONDITION: 设备未通过 adddevice 注册, 或者 wx_init
* 已被调用过了, 或者当前系统时间不正确.
* - WXERROR_INTERNAL: 其他错误
*/
WX_API wx_operation_t wx_init(const wx_init_config_t* config,
wx_get_module_t get_module);
/**
* @brief 停止 WMPF
*
* 调用该函数之前, 你需要确保创建的 wx_operation, wx_voip_session 等对象都已经被
* destroy, 否则 SDK 会崩溃.
*
* @return 操作是否成功
*/
WX_API wx_error_t wx_stop();
WX_END_DECLS
cloudvoip_server.h
#pragma once
#include "wmpf/macros.h"
#include "wmpf/operation.h"
#include "wmpf/types.h"
WX_BEGIN_DECLS
typedef struct wx_cloudvoip_session* wx_cloudvoip_session_t;
typedef enum wx_cloudvoip_session_type {
WX_CLOUDVOIP_SESSION_VIDEO = 0, // 音视频通话
WX_CLOUDVOIP_SESSION_AUDIO = 1, // 纯音频通话
} wx_cloudvoip_session_type_t;
typedef enum wx_cloudvoip_session_status {
WX_CLOUDVOIP_SESSION_IDLE = 0, // 初始状态
WX_CLOUDVOIP_SESSION_CALLING = 1, // 拨打电话中
WX_CLOUDVOIP_SESSION_TALKING = 2, // 通话中 (被拨打的微信用户接听了电话)
WX_CLOUDVOIP_SESSION_REJECTED = 3, // 被拨打的微信用户拒绝接听电话
WX_CLOUDVOIP_SESSION_CANCELED = 4, // 拨打过程中, 设备取消了电话拨打
WX_CLOUDVOIP_SESSION_HANGUP_BY_CALLER = 5, // 通话时设备挂断了电话
WX_CLOUDVOIP_SESSION_HANGUP_BY_CALLEE =
6, // 通话时被拨打的微信用户挂断了电话
WX_CLOUDVOIP_SESSION_ABORTED = 7, // 发生异常
WX_CLOUDVOIP_SESSION_BUSY = 8, // 被拨打的微信用户处于占线状态
WX_CLOUDVOIP_SESSION_TIMEOUT = 9, // 超时未接听
} wx_cloudvoip_session_status_t;
typedef enum wx_cloudvoip_hangup_reason {
WX_CLOUDVOIP_HANGUP_REASON_UNKNOWN = 0,
WX_CLOUDVOIP_HANGUP_REASON_MANUAL = 1, // 用户手动挂断/取消
// 主叫挂断原因
WX_CLOUDVOIP_HANGUP_REASON_SYSTEM = 6, // 被系统电话挂断
WX_CLOUDVOIP_HANGUP_REASON_APP = 7, // 被其他应用挂断
WX_CLOUDVOIP_HANGUP_REASON_DEVICE = 8, // 采集播放设备启动失败
WX_CLOUDVOIP_HANGUP_REASON_TIMEOUT = 10, // 超时挂断
WX_CLOUDVOIP_HANGUP_REASON_REJECT = 11, // 拒绝通话,指的是没有进入通话即挂断
} wx_cloudvoip_hangup_reason_t;
#define WX_CLOUDVOIP_SESSION_LISTENER_TAG 0xF00001
typedef struct wx_cloudvoip_session_listener {
wx_struct_t common;
/**
* @brief 当前 VoIP 通话状态改变
*
*/
void (*status)(wx_cloudvoip_session_t session,
void* user_data,
wx_cloudvoip_session_status_t);
} wx_cloudvoip_session_listener_t;
/**
* @brief 服务端侧加入 VoIP 房间并创建 VoIP 会话对象
*
* 需要注意:
* 此接口用于设备端 SDK 发起的通话加入。
* 服务端 SDK 是单例模式的,你不能在单进程内同时创建两个或以上的
* wx_cloudvoip_session 实例. 因此你需要使用多进程模式来调用服务端 SDK.
* 因此此处可能还涉及 IPC 需要厂商云自行实现.
*
* 发起 VoIP 通话流程:
* 1. 设备端 SDK 调用 call 方法发起通话
* 2. 微信后台请求厂商云后台,通知云后台有新的 VoIP 会话已经发起
* 3. 云后台调用服务端 SDK 的 wx_cloudvoip_session_join 方法加入 VoIP 会话
* 4. 云后台通过 audio_module, camera_module 传输音视频流给服务端 SDK
* 5. 服务端 SDK 将接收到的音视频流发送给微信后台,继而发送到用户手机微信中
*
* @param listener 会话状态回调
* @param user_data 回调用户数据
* @param wxa_appid 小程序 AppId
* @param device_id 设备 SN
* @param model_id Model ID
* @param server_token 微信后台向云后台通知 VoIP 会话发起时携带的值
* @param payload 设备端调用 wx_cloudvoip_client_call 函数传入的 payload
* @param session_out VoIP 会话结果
* @return
* - WXERROR_INVALID_ARGUMENT: 参数错误
* - WXERROR_FAILED_PRECONDITION: wx_init 未调用或未完成.
*/
WX_API wx_operation_t
wx_cloudvoip_session_join(wx_cloudvoip_session_type_t,
const struct wx_cloudvoip_session_listener* listener,
void* user_data,
const char* wxa_appid,
const char* device_id,
const char* model_id,
const char* server_token,
const char* payload,
wx_cloudvoip_session_t* session_out);
/**
* @brief 呼叫成功后挂断 VoIP 通话
*
* 由于设备端 SDK 不提供挂断方法,云对云场景下,由厂商云后台调用服务端 SDK
* 的挂断函数挂断 VoIP 通话.
*
* @param session 要挂断的 VoIP 会话
* @param reason 挂断 VoIP 通话的原因
* @return 操作是否成功
*/
WX_API wx_operation_t
wx_cloudvoip_session_hangup(wx_cloudvoip_session_t session,
wx_cloudvoip_hangup_reason_t reason);
/**
* @brief 作为接听方(小程序呼叫设备)挂断 VoIP, 如果是设备呼叫小程序,则不建议使用此接口。
*
* 小程序呼设备的场景,设备端挂断 voip 接口,如果 session 已创建,则需要使用 wx_cloudvoip_session_hangup 挂断
*
* @param wxa_appid 小程序 AppId
* @param device_id 设备 SN
* @param model_id Model ID
* @param server_token 微信后台向云后台通知 VoIP 会话发起时携带的值
* @param payload 设备端调用 wx_cloudvoip_client_join 函数传入的 payload
* @return 操作是否成功
*/
WX_API wx_operation_t wx_cloudvoip_listener_hangup(
const char* wxa_appid,
const char* device_id,
const char* model_id,
const char* server_token,
const char* payload,
wx_cloudvoip_hangup_reason_t reason);
/**
* @brief 销毁 VoIP 会话对象
*
* @param session
* @return WX_API
*/
WX_API void wx_cloudvoip_session_destroy(wx_cloudvoip_session_t session);
/**
* @brief 得到当前 session 的 roomid
*
* roomid: voip 通话所在房间的标识。
*
* @param session
* @return roomid 字符串 或 "".
*/
WX_API const char* wx_cloudvoip_session_get_roomid(wx_cloudvoip_session_t session);
/**
* @brief 设置对端(微信)的带宽以强制流控,设置后对端的上行码率会在这个值上下波动
*
* @param session
* @param bandwidth 码率,单位为 kbps. 比如 100,则码率为 100 kbps
* @return
* WXERROR_OK: 操作成功
* WXERROR_FAILED_PRECONDITION: session 不满足条件
* WXERROR_INTERNAL: 内部错误
*/
WX_API wx_error_t wx_cloudvoip_session_set_remote_bandwidth(wx_cloudvoip_session_t session, int bandwidth);
/**
* @brief 服务端侧加入 VoIP 房间并创建 VoIP 会话对象
*
* 需要注意:
* 此接口用于服务端请求 https://api.weixin.qq.com/wxa/business/iot/voip/call 发起的通话加入
* 服务端 SDK 是单例模式的,你不能在单进程内同时创建两个或以上的
* wx_cloudvoip_session 实例. 因此你需要使用多进程模式来调用服务端 SDK.
* 因此此处可能还涉及 IPC 需要厂商云自行实现.
*
* 发起 VoIP 通话流程:
* 1. 服务端请求 https://api.weixin.qq.com/wxa/business/iot/voip/call 发起通话
* 2. 微信后台请求厂商云后台,通知云后台有新的 VoIP 会话已经发起
* 3. 云后台调用服务端 SDK 的 wx_cloudvoip_session_cloud_call_join 方法加入 VoIP 会话
* 4. 云后台通过 audio_module, camera_module 传输音视频流给服务端 SDK
* 5. 服务端 SDK 将接收到的音视频流发送给微信后台,继而发送到用户手机微信中
*
* @param listener 会话状态回调
* @param user_data 回调用户数据
* @param wxa_appid 小程序 AppId
* @param server_token 微信后台向云后台通知 VoIP 会话发起时携带的值
* @param roomid 房间id
* @param session_key 房间session_key
* @param payload 设备端调用 wx_cloudvoip_client_call 函数传入的 payload
* @param session_out VoIP 会话结果
* @return
* - WXERROR_INVALID_ARGUMENT: 参数错误
* - WXERROR_FAILED_PRECONDITION: wx_init 未调用或未完成.
*/
WX_API wx_operation_t
wx_cloudvoip_session_cloud_call_join(wx_cloudvoip_session_type_t,
const struct wx_cloudvoip_session_listener* listener,
void* user_data,
const char* wxa_appid,
const char* server_token,
const char* roomid,
const char* session_key,
const char* payload,
wx_cloudvoip_session_t* session_out);
WX_END_DECLS