概述
设备可以有以下3种功能:
- 仅作主叫(仅呼出)
- 仅作被叫(仅接收呼入)
- 即可作主叫也可作被叫(即可呼出也接收呼入)
1. 选择SDK
我们提供了不同层次的开发接口,以满足不同用户和产品的需求。
库及头文件 | 功能 |
libtgCloud.a(或 libtgCloud-tgwebrtc.a)
TgCloudApi.h | - 被叫。其它库都会依赖此库
- 可被动接收命令和发送应答(目前不能主动发送命令)
- 可发送消息到对端(无应答)
- API接口前缀: Tci |
libtgWxVoip.a, libwxcloudvoip_client.a
wx.h | - 微信VoIP, 用于支持小程序。可以呼入和呼出
- 与被叫库合用 |
libtgCallerP2p.a
icam_p2p.h | - dev-to-dev主叫库。 可以呼叫和连接另一台设备
- 可以发送命令并等待应答(同步)
- 接收被叫发来的消息
- API接口前缀: iCamP2p |
libtgVdp.a
vdp.h | - 主叫+被叫
- 调用了以上三个库-
- 封装了呼叫逻辑,支持同时呼叫到微信小程序和多台设备。
- 统一了主叫和被叫的音视频发送方法
- 开发者仍需调用少量被叫 TciXxxx 和 主叫 iCamP2pXxxx 接口。
- WxVoIP 的接口被隐藏无需使用。 |
如上图,蓝线为库的依赖关系,绿框和橙框为两种库的选择组合:绿色组合的【应用1】模式,橙色组合的【应用2】模式
说明: 从 sdk rev.216起,libtgWxVoip.a作为libtgVdp.a下的一个可选插件存在。不需要微信呼叫功能时,不用链接 libtgWxVoip.a,从而减少目标程序空间及依赖。如果要支持微信呼叫,使用者要调用 wxInit() (在wx.h里申明)向sdk注册。
- A1: 如果你已有IPC设备,要支持呼叫微信小程序,选择【应用2】,这种情况需要直接调用 wx.h 中接口(参见 2.2 WX VoIP 接口)。
- A2: IPC设备,要支持呼叫另一个设备(即使不准备支持微信呼叫),选择【应用1】(主要是libtgCallerP2p.a的使用会稍麻烦)
- A3: 开发楼宇对讲类似的应用,选择【应用1】。
【应用1】仍然需要用到少量【IPC开发包】的接口和数组结构。
2. 呼叫
2.1 Video Door Phone(VDP)接口
[Update at: 2024/08/26]
2.1.1 设备要求
- 如果要支持微信呼叫,支持h264解码(微信小程序发往设备的视频只支持 H.264)
- 配置设备的 Screen 能力
2.1.2 库和头文件
- 包含 vdp.h/TgCloudApi.h
- 如果要支持微信呼叫,包含 wx.h
- 连接文首提及的所有的库(如果不用支持微信呼叫,则不需libtgWxVoip.a 和 libwxcloudvoip_client.a)
2.1.3 程序简介
最基本的VDP应用实现包括:
- 一个 TciCB 结构,包含一组回调函数指针。结构中的回调有的必须实现(例如设置时间/时区,获取设备信息, 请求I帧等。demo里面有实现代码参考),有的是可选的(例如配网/ota升级)。
- 一个可选的p2p命令回调
设置 TciCB 结构:
static struct TciCB _tci_cb;
static void init_tcicb()
{
_tci_cb.get_feature = get_device_feature;
};
void(* on_talkback_stop)(void)
结束对讲
int(* set_time)(time_t time)
设置时间
int(* request_iframe_ex)(int channel, int vstream)
请求指定图像通道的I帧
int(* on_talkback_start)(void)
开始对讲
int(* talkback)(TCMEDIA at, const uint8_t *audio, int len)
对讲数据回调 格式在前面已经协商过
int(* on_status)(int event, const void *pData, int len)
sdk内部状态
int(* set_timezone)(const char *tzs)
设置时区
int(* get_info)(TCIDEVICEINFO *info)
获取设备基本信息
这里面值得一提的两个回调是是 TciCB::get_feature() 和 。
用户需要在 get_feature() 回调里:
- 在 Screen 里返回屏幕配置。微信小程序只支持 640x480 / 480x640 / 320x240 / 240x320 四种尺寸的屏幕配置,并且发给设备的视频只支持h264编码。
int get_device_feature(const char* key, char* buf, int bytes)
{
if(strcmp(key, "Screen") == 0) {
strcpy(buf, "h264:320x240:0");
return 0;
}
return -1;
}
在 on_status() 回调里要处理呼入STATUS_INCALL(对室内机而言) 和 设备绑定状态(STATUS_LOGON/STATUS_DELETED)的处理, 实现细节见 vdp_demo.c:
int on_status(
int status,
const void *pData,
int len)
{
switch(status)
{
break;
break;
break;
case STATUS_INCALL:
{
struct InCallInfo *pIci = (struct InCallInfo*)pData;
}
}
return 0;
}
@ STATUS_DELETED
设备被删除. data: NULL.
@ STATUS_UPDATE_SERVICE
更新云服务. data: TCISERVICEINFO *; len: sizeof(TCISERVICEINFO)
@ STATUS_LOGON
设备上线. data: NULL.
如果要支持微信呼叫,需要显示调用 wxInit() 注册微信voip模块(除此之外不需要调用任何其它 WX VoIP接口)。
程序的入口就很简单:
int main()
{
init_tcicb();
...
}
void TgVdpSetCmdHandler(TGCMDHANDLER func)
设置p2p命令处理回调, 代替 TciSetCmdHandler().
int TgVdpInit(const char *cfg_path, struct TciCB *cbs)
VDP 初始化.
int TgVdpStart(int isBound, const char *uuid)
开启VDP 服务.
int wxInit(void)
注册微信Voip模块.
在另外的线程里,向SDK送流。
while(1) {
},
@ TCMEDIA_VIDEO_H264
H.264 "h264"
@ TCMEDIA_AUDIO_G711A
G.711A "g711a" "alaw"
int TciSendFrameEx(int channel, int stream, TCMEDIA mt, const uint8_t *pFrame, int length, uint32_t ts, int uFrameFlags)
发送实时音视频帧, SDK内部会将数据分发到云端和APP.
设备必须要支持双码流(主码流为高清,辅码流为VGA)。送给小程序的固定为辅码流,过高的码流容易导致小程序断开。
NOTE
TgVdpInit() 会定制自己的回调, 应用不能再调用TciInit()/TciSetCmdHandler()/TciSetCallback() 接口。如果要处理命令,调用 @ref TgVdpSetCmdHandler()
来注册命令处理回调。
2.1.4 呼叫
sequenceDiagram
title: VDP 呼叫流程
participant A as 主叫A
participant B as 被叫B/C/...
participant S as 开发者后台
participant T as 探鸽平台
A->>S: 1.请求呼叫B/C/...
S->>T: 2.请求呼叫对象B/C/...的连接token
T-->>S: 3.返回呼叫对象的连接token
S-->>A: 4.连接token
A->>B: 5.TgVdpCallEx()
B->>B: 6.TgVdpAccept()/TgVdpReject()
B->>A: 7.应答(接听或拒接)
A->>A: 8.自动开始音视频收发
呼叫前先从平台获取呼叫对象列表(信息包含对象类型和连接token)。然后调用 `TgVdpCallEx()` 呼叫,`TgVdpCall`接收呼叫对象 `struct CalleeEx` 数组和起时值作参数,也就是可以一次呼叫多个用户。
TgVdpCallEx() 是异步的,成功调用仅表示发起了呼叫,呼叫/通话状态通过调用时提供的回调函数传出来。
int vdp_callback(int status, void *pUser)
{
switch(status)
{
case TVCS_ACCEPTED: printf(
"*** Call is accepted, in coversation ...\n");
break;
}
return 0;
}
if(
TgVdpCallEx(callees, n_calle, 20, vdp_callback) != 0)
return;
int(* TGVDPCALLBACK)(int status, struct CalleeEx *pCallee)
呼叫/通话状态回调.
int TgVdpCallEx(struct CalleeEx *callees, int size, int timeout, TGVDPCALLBACK cb)
呼叫.
2.1.5 取消呼叫/通话中挂断(主叫或被叫方)
这些功能都调用 TgVdpHangup(). 被叫挂断实际调用了 TciHangup().
TgVdpHangup() 是一个多功能接口。必须在 TgVdpCallEx() 成功后的任意时间调用一次。但不能在TgVdpCall() 的回调里调用。
2.1.6 Demo
VDP的demo位于demo/Vdp 目录下:
vdp_demo.c 楼宇对讲框架演示,主叫和被叫都可以使用同一框架。
indoor_demo.c 楼宇对讲室内机框架,使用的是 IPC 库
2.2 WX VoIP 接口
[Update at: 2024/08/26]
2.2.1 设备要求
- 支持h264解码(微信小程序发往设备的视频只支持 H.264)
2.2.2 库和头文件
- 包含头文件
wx.h
- 连接库
libtgWxVoip.a
和libwxcloudvoip_client.a
2.2.3 初始化
在 TciStart()
前调用 wxInit()
在 Profile 里添加描述: Profile={ "WxVoIP":1 }
如果有屏幕,设置 Screen. 格式只能是h264, 屏幕大小只支持 320x240/240x320/640x480/480x640 4种之一。例子: Screen=h264:240x320
2.2.4 呼叫和取消呼叫
调用 int wxCall(const char *openid)
呼叫小程序用户。
openid
是小程序用户标识,由接口 wxGetUsers()
返回(TIVS客户要使用别的接口)。
使用 wxCancelCall() 取消先前的呼叫。
2.2.5 通话状态改变
对微信呼叫来说,wxCall()和TciAcceptInCall()是异步的,返回成功表示已呼出和开始接听。小程序端接听和挂断通过 p2p命令 TCI_CMD_ANSWERTOCALL 通知应用。
命令参数 Tcis_AnswerToCall::state 的取值有微调:其低16位为原来的呼叫状态 ECALLSTATE. 当状态为 CALLSTATE_ANSWERED 时,高16为应答来源(1表示应答来自微信小程序)
2.3 探鸽Caller接口
使用 VDP ,而不是直接调用此片提到的接口。保留此文档只供开发者了解内部实现
2.3.1. 库和头文件
库文件: libtgCallerP2p.a
头文件: ConnectByToken.h
, icam_p2p.h
2.3.1 呼叫流程
sequenceDiagram
title: Tange Caller 呼叫流程
participant A as 主叫A
participant B as 被叫B
participant S as 开发者后台
participant T as 探鸽平台
A->>S: 1.请求呼叫B
S->>T: 2.请求B的连接token
T-->>S: 3.返回B的连接token
S-->>A: 4.连接token
A->>B: 5.ConnectByToken(token)并且发送呼叫TCI_CMD_CALL
B->>B: 6.收到STATUS_CALL, 等待用户接听(或拒接)
B->>A: 7.应答TCI_CMD_CALL(接听或拒接)
2.3.2 使用讲解
主叫获取连接token
后,调用 ConnectByToken(token)
建立到被叫的连接。之后就是请求音视频数据的过程。
主叫API除连接操作 ConnectByToken()
外,其它都是 iCamP2p
开头,申明在 ConnectByToken.h
和 icam_p2p.h
里。
下面的主叫的代码示例(在demo
文件夹的 p2p_test.c
里):
头文件:
#include "ConnectByToken.h"
#include "logfile.h"
连接
int err;
err = iCamP2pConnectByToken(conntok, NULL, &g_hP2pClt);
if(err < 0) {
_err("iCamP2pConnectByToken: %d\n", err);
return err;
}
struct P2PCLT * HP2PCLT
p2p连接句柄
呼叫设备, 等待应答
int ret;
for(int i=0; i<16; i++) {
ret = iCamP2pRecvResponse(g_hP2pClt,
TCI_CMD_CALL, NULL, 0, 1000);
if(ret) break;
}
#define TCI_CMD_CALL
呼叫设备,等待用户应答.
拉取音视数据。数据通过回调送出。
int channel = 0;
int vstream = 1;
printf("Request channel:stream: %d:%d\n", channel, vstream);
ICAMP2P_C_EXPORTS int iCamP2pStartLiveVideo(HP2PCLT hClt, int channel, FRAMECALLBACK cb, void *pUser)
开启实时图像.
ICAMP2P_C_EXPORTS int iCamP2pStartLiveAudio(HP2PCLT hClt, FRAMECALLBACK cb, void *pUser)
开启实时音频.
ICAMP2P_C_EXPORTS int iCamP2pSelectStream(HP2PCLT hClt, int channel, int stream)
选择码流.
音视和视频回调
static void vframe_cb(
FRAMEHEAD_t *fi,
unsigned char *data,
void *pUser)
{
char line[1024];
int slen;
if(fi)
{
}
else {
{
break;
}
}
}
else
{
fprintf(stderr, "Error when receive video: %d\n", (int)(long)data);
}
}
static void aframe_cb(
FRAMEHEAD_t *fi,
unsigned char *data,
void *pUser)
{
char line[1024];
int slen;
}
}
@ RTM_UPDATE_CALL_STATE
呼叫结束.
@ TCMEDIA_VIDEO_H265
H.265 "h265"
unsigned short type
消息类型。 RTMTYPE
在媒体流中插入的 消息/数据帧 帧头 (for App Developer).
unsigned int frame_size
Size of frame
unsigned short codec_id
非0时, 低8位为 TCMEDIA, 高8位为视频通道号.
Audio/Video Frame Header Info
开启对讲,成功后可以推送音视频。
int err_talk;
_err("StartTalk: %d\n", err_talk);
else {
while(do_talk) {
}
}
ICAMP2P_C_EXPORTS int iCamP2pSendMedia(HP2PCLT hClt, TCMEDIA mt, uint32_t ts, uint32_t uFrameFlags, const uint8_t *pMedia, int size)
发关对讲音视频
ICAMP2P_C_EXPORTS int iCamP2pStartTalk(HP2PCLT hClt)
开始对讲.
运行期间定时检查连接是否正常
while(1) {
if(iCamP2pCheckValid(g_hP2pClt)) sleep(2);
else goto clean_out;
}
3. 被叫(公共)
这部分逻辑是通用的,不受限于呼入类型。 这部分接口位于libtgCloud.a/TgCloudApi.h里。
3.1 呼入通知
用户呼叫设备时,设备端(被叫端)通过 TciCB::on_status(STATUS_INCALL, pIci, ...) 回调收到 STATUS_INCALL 通知。第二个参数pIci
是一个struct InCallInfo
指针。struct InCallInfo::type
给出呼叫来源。
如果 pIci 为 NULL, 则意味着对方取消呼叫。
3.2 接听或拒接
固件判断当前是否正在通话或正在呼叫,是则要调用 TciRejectInCall(const struct InCallInfo *pIci) 拒绝呼入。否则要显示一个界面展示是谁呼叫,并给用户决定是否接听。
- 调用
TciAcceptInCall(const struct InCallInfo *)
接听
- 调用
TciRejectInCall(const struct InCallInfo *)
拒接。
应用不能阻塞on_status
调用。STATUS_INCALL 回调返回0
时,SDK会释放pIci
指向的空间。所以如果应用弹出了待接界面,在回调里要保存 pIci
指针并返回1
.
如果使用VDP接口,使用 TvdpAccept(struct InCallInfo *)
和 TvdpReject(struct InCallInfo *)
接口。
在功能上与上述接口是完全一样的。
3.3 通话中挂断
如果是对方挂断,因为呼入通知和通话连接是独立的,存在两种情况:
参看 wxvoip_demo.c 或 vdp_demo.c 里的示例和说明。
4. 音视频之外的数据通信
前面的说明仅涉及 呼叫/接听/通话。如果开发者还需要传递音视频之外的数据,则需要进一步了解被叫和主叫使用的通信方式和开发接口。
4.1 可传递数据类型
主/被叫端媒体数据之外的数据(以下简称)有两种类型: 命令和消息。
- 命令: 由主叫先发起。主给被叫发送请求,被叫被动响应并发送应答。
- 消息:由被叫发出。单向
4.2 实现
主叫:
被叫: