Tange Cloud for Device
 Rev.345
载入中...
搜索中...
未找到
双向呼叫开发指南

概述

设备可以有以下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_info = get_info;
_tci_cb.get_feature = get_device_feature;
_tci_cb.on_talkback_start = on_talkback_start;
_tci_cb.talkback = talkback;
_tci_cb.on_talkback_stop = on_talkback_stop;
_tci_cb.set_timezone = set_timezone;
_tci_cb.set_time = set_time;
_tci_cb.on_status = on_status;
_tci_cb.request_iframe_ex = request_iframe_ex;
};
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)
获取设备基本信息
sdk 回调函数结构

这里面值得一提的两个回调是是 TciCB::get_feature()

用户需要在 get_feature() 回调里:

  1. 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; //其它不需要的能力查询返回 -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;
case STATUS_UPDATE_SERVICE: //可以知道是不是有wx呼叫服务
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();
TgVdpInit(cfg_path, &tci_cb);
TgVdpSetCmdHandler(p2p_cmd_handler); //可选
//如果要支持微信呼叫
wxInit();
//在此之前准备网络...
//启动服务
//其它业务
...
}
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) {
TciSendFrameEx(0, 0, TCMEDIA_VIDEO_H264, ...); //主码流
TciSendFrameEx(0, 1, TCMEDIA_VIDEO_H264, ...); //辅码流
TciSendFrameEx(0, 0, TCMEDIA_AUDIO_G711A, ...); //音频
},
@ 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() 是异步的,成功调用仅表示发起了呼叫,呼叫/通话状态通过调用时提供的回调函数传出来。

//vdp.h:
int TgVdpCallEx(struct CalleeEx *callees, int size, int timeout, TGVDPCALLBACK cb);
//vdp_demo.c:
int vdp_callback(int status, void *pUser)
{
switch(status)
{
case TVCS_CALLING: printf("*** Calling ...\n"); break;
case TVCS_REJECTED: printf("*** Rejected.\n"); break;
case TVCS_ACCEPTED: printf("*** Call is accepted, in coversation ...\n"); break;
case TVCS_TIMEDOUT: printf("*** Call is timedout\n");
case TVCS_REMOTE_HANGUP: printf("*** Remote hangup.\n"); break;
}
return 0;
}
if(TgVdpCallEx(callees, n_calle, 20, vdp_callback) != 0)
return;
int(* TGVDPCALLBACK)(int status, struct CalleeEx *pCallee)
呼叫/通话状态回调.
定义 vdp.h:102
int TgVdpCallEx(struct CalleeEx *callees, int size, int timeout, TGVDPCALLBACK cb)
呼叫.
@ TVCS_TIMEDOUT
呼叫超时
定义 vdp.h:91
@ TVCS_ACCEPTED
接听
定义 vdp.h:90
@ TVCS_CALLING
呼叫中
定义 vdp.h:88
@ TVCS_REMOTE_HANGUP
对端挂断
定义 vdp.h:92
@ TVCS_REJECTED
拒接
定义 vdp.h:89
被叫
定义 vdp.h:55

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.alibwxcloudvoip_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.hicam_p2p.h 里。

下面的主叫的代码示例(在demo文件夹的 p2p_test.c 里):

头文件:

#include "icam_p2p.h"
#include "ConnectByToken.h"
#include "logfile.h" //用于日志

连接

HP2PCLT g_hP2pClt;
int err;
//建立连接
err = iCamP2pConnectByToken(conntok/*连接token, 从用户平台获取*/, NULL, &g_hP2pClt);
if(err < 0) {
_err("iCamP2pConnectByToken: %d\n", err);
return err;
}
struct P2PCLT * HP2PCLT
p2p连接句柄

呼叫设备, 等待应答

iCamP2pSendCmd(g_hP2pClt, TCI_CMD_CALL, NULL);
int ret;
for(int i=0; i<16; i++) {
ret = iCamP2pRecvResponse(g_hP2pClt, TCI_CMD_CALL, NULL, 0, 1000); //接收应答,超时为1s
if(ret) break;
}
#define TCI_CMD_CALL
呼叫设备,等待用户应答.

拉取音视数据。数据通过回调送出。

int channel = 0; //视频通道=0。普通设备只有一路图像
int vstream = 1; //辅码流
/* 请求视频,通过回调 vframe_cb() 推出来。随视频的可能还有通知 */
printf("Request channel:stream: %d:%d\n", channel, vstream);
iCamP2pStartLiveVideo(g_hP2pClt, channel, vframe_cb, (void*)hP2pClt); //视频通道
iCamP2pSelectStream(g_hP2pClt, channel, vstream); //选择码流,请求I帧
/* 请求音频, 通过 aframe_cb() 推出来 */
iCamP2pStartLiveAudio(g_hP2pClt, vframe_cb, (void*)hP2pClt); //音频
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)
{
if(fi->codec_id) {
printf("video type: %s, length=%d\n", fi->codec_id==TCMEDIA_VIDEO_H264?"h264":fi->codec_id==TCMEDIA_VIDEO_H265?"h265":"??", fi->frame_size);
}
else {
RTMSGHEAD_t *msg = (RTMSGHEAD_t*)fi;
switch(msg->type)
{
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;
if(fi && fi->codec_id == TCMEDIA_AUDIO_G711A) {
printf("audio length: %d\n", fi->frame_size)
}
}
@ 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;
if( (err_talk = iCamP2pStartTalk(g_hP2pClt)) )
_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 通话中挂断

如果是对方挂断,因为呼入通知和通话连接是独立的,存在两种情况:

  • 接听后通话连接失败,设备是没有通知的
  • 接听后通话连接成功,对方先挂断。这种情况没有专门的通知,需要利用现存的机制(例如 STATUS_STREAMING)

参看 wxvoip_demo.c 或 vdp_demo.c 里的示例和说明。

4. 音视频之外的数据通信

前面的说明仅涉及 呼叫/接听/通话。如果开发者还需要传递音视频之外的数据,则需要进一步了解被叫和主叫使用的通信方式和开发接口。

4.1 可传递数据类型

主/被叫端媒体数据之外的数据(以下简称)有两种类型: 命令和消息。

  • 命令: 由主叫先发起。主给被叫发送请求,被叫被动响应并发送应答。
  • 消息:由被叫发出。单向

4.2 实现

主叫

  • 调用 iCamP2pExecute() (或其它预封装命令)发送命令和接收应答
  • 调用 iCamP2pSetCallback() 注册回调,接收从被叫端来的消息

被叫