这篇会介绍设备如何实现获取小程序联系人列表、呼叫微信小程序、响应通话状态回调与挂断通话,帮助你快速完成 TWeCall(微信通话)的设备端接入。
一、前提条件
·已完成 登录与注册 文档中的登录功能,设备能够成功 tc_iot_login 并保持在线。
·已在物联网开发平台完成 TWeCall 服务激活与小程序授权,详见 微信通话服务(TWeCall)。
·已完成小程序接入文档 设备呼叫小程序 中授权小程序音视频通话。
二、接入步骤
TWeCall 设备端接入分为以下步骤:
1. 注册通话事件回调。
2. 获取联系人列表。
3. 发起呼叫。
4. 处理通话状态回调。
5. 创建通话推流通道并推送音视频帧。
6. 挂断通话。
三、步骤 1:注册通话事件回调
调用 tc_iot_av_init 初始化音视频模块并注册通话事件回调。设备作为主叫时,需要关注对端的接听、拒绝、超时、挂断回调;设备作为被叫时,需要关注来电请求回调。
tc_iot_error_e tc_iot_av_init(const tc_iot_av_observer_s *observer);
observer 回调说明
| 回调 | 触发时机 | 说明 |
| on_call_requested | 收到对端(小程序)发起的来电请求。 | 设备作为被叫时触发,在此回调中调用 tc_iot_av_accept 接听或 tc_iot_av_reject 拒绝。 |
| on_call_accepted | 对端接听呼叫。 | 设备作为主叫时触发,此时可开始推送音视频。 |
| on_call_rejected | 对端拒绝呼叫。 | 设备作为主叫时触发。 |
| on_call_timeout | 对端无人接听,呼叫超时。 | 设备作为主叫时触发。 |
| on_call_hangup | 对端主动挂断。 | 通话过程中对端挂断时触发。 |
| on_audio_frame_received | 收到对端音频帧。 | 用于播放对端音频。 |
| on_video_frame_received | 收到对端视频帧。 | 用于播放对端视频。 |
注意:observer 在 tc_iot_av_init 时会被 SDK 内部拷贝,传入栈变量即可,无需长期保活。
// 通话状态回调static void on_call_accepted(const tc_iot_contact_s *contact) { printf("[twecall] on_call_accepted: user_id=%s\\n", contact->user_id); // 对端已接听,开始上推音视频(见步骤 5) start_call_streams();}static void on_call_rejected(const tc_iot_contact_s *contact) { printf("[twecall] on_call_rejected: user_id=%s\\n", contact->user_id); // 对端拒接,清理本地状态}static void on_call_timeout(const tc_iot_contact_s *contact) { printf("[twecall] on_call_timeout: user_id=%s\\n", contact->user_id); // 呼叫超时,清理本地状态}static void on_call_hangup(const tc_iot_contact_s *contact) { printf("[twecall] on_call_hangup: user_id=%s\\n", contact->user_id); // 对端挂断,停止推流并清理通道(见步骤 5/6) stop_call_streams();}// 被叫:收到来电请求static void on_call_requested(const tc_iot_contact_s *contact, tc_iot_call_option_t option) { printf("[twecall] on_call_requested: user_id=%s media=%d quality=%d\\n", contact->user_id, option.media_content, option.video_quality); // 这里选择接听,也可调用 tc_iot_av_reject 拒绝 tc_iot_av_accept(contact, NULL);}// 初始化tc_iot_av_observer_s observer;memset(&observer, 0, sizeof(observer));observer.on_call_requested = on_call_requested;observer.on_call_accepted = on_call_accepted;observer.on_call_rejected = on_call_rejected;observer.on_call_timeout = on_call_timeout;observer.on_call_hangup = on_call_hangup;tc_iot_error_e rc = tc_iot_av_init(&observer);if (rc != TC_IOT_ERR_SUCCESS) { printf("tc_iot_av_init failed: %d\\n", rc);}
四、步骤 2:获取联系人列表
调用 tc_iot_av_get_contacts 拉取可呼叫的微信小程序联系人列表。接口采用游标(cursor)分页,首次调用传 NULL,后续传入上一次回调返回的 next_cursor 继续拉取,直到 next_cursor 为 NULL 表示已到末尾。
注意:说明:
注意:如果设备调用 tc_iot_av_get_contacts 后获取到联系人列表为空,说明小程序没有正常完成音视频授权。建议用户将小程序删除,重新添加小程序。小程序第一次授权时,会有如下弹窗提示。
注意:
tc_iot_error_e tc_iot_av_get_contacts(const char *cursor, uint32_t limit, tc_iot_av_contacts_cb callback);
| 参数 | 说明 |
| cursor | 分页游标,首次传 NULL(SDK 内部等价于 "0"),后续传入上次回调的 next_cursor。 |
| limit | 本次拉取数量,有效范围 (0, 1024]。 |
| callback | 结果回调,原型见下。 |
回调原型
typedef void (*tc_iot_av_contacts_cb)(tc_iot_error_e error_code, const char *error_message, const tc_iot_contact_s *contacts, uint32_t count, const char *next_cursor);
| 参数 | 说明 |
| error_code | TC_IOT_ERR_SUCCESS 表示成功,其余为失败码。 |
| contacts | 联系人数组指针,失败时为 NULL。 |
| count | 本次返回的联系人数量,0 表示已无更多联系人。 |
| next_cursor | 下一页游标,NULL 表示已是最后一页,无需继续拉取。 |
联系人结构体
typedef struct { const char *user_id; // 呼叫目标标识,格式 "modelId/wxAppId/openId" const char *user_name; // 用户昵称(可能为空) const char *avatar_url; // 头像 URL(可能为空)} tc_iot_contact_s;
注意:说明:
注意:user_id 是发起呼叫的关键标识,由 modelId、wxAppId、openId 三段以 / 拼接而成。该字符串内存由 SDK 在回调期间管理,回调返回后会被释放,如需长期使用请自行拷贝。
示例:完整拉取所有联系人
static const char *g_target_user_id = NULL;static void on_get_contacts(tc_iot_error_e error_code, const char *error_message, const tc_iot_contact_s *contacts, uint32_t count, const char *next_cursor) { printf("[twecall] get_contacts: code=%d count=%u next_cursor=%s\\n", error_code, count, next_cursor ? next_cursor : "(end)"); if (error_code != TC_IOT_ERR_SUCCESS) { printf("get_contacts failed: %s\\n", error_message ? error_message : ""); return; } for (uint32_t i = 0; i < count; i++) { printf(" [%u] user_id=%s name=%s\\n", i, contacts[i].user_id, contacts[i].user_name ? contacts[i].user_name : ""); // 记录第一个联系人作为呼叫目标 if (g_target_user_id == NULL) { g_target_user_id = strdup(contacts[i].user_id); } } // next_cursor 非空说明还有更多联系人,继续翻页 if (next_cursor != NULL) { tc_iot_av_get_contacts(next_cursor, 50, on_get_contacts); }}// 首次拉取,每页 50 条tc_iot_av_get_contacts(NULL, 50, on_get_contacts);
五、呼叫体验版小程序(重要)
注意:特别强调:
注意:若在 tc_iot_login 登录后调用,设置将不会生效。
注意:/* "DEBUG" 开发版 "DEMO" 体验版*/tc_iot_call_experiment_api("set_wx_miniapp_flavor", "DEMO", NULL);
选定联系人后,调用 tc_iot_av_call 向该联系人(微信小程序)发起呼叫。接口为异步调用,结果通过 callback 返回。
tc_iot_error_e tc_iot_av_call(const tc_iot_contact_s *contact, tc_iot_call_option_t option, tc_iot_av_operation_cb callback);
| 参数 | 说明 |
| contact | 呼叫目标,contact.user_id 必填(取自 tc_iot_av_get_contacts 返回的 user_id)。 |
| option | 呼叫选项,见下表。 |
| callback | 操作结果回调:TC_IOT_ERR_SUCCESS 表示呼叫请求已成功发出,对端是否接听需等待 步骤4 的状态回调。 |
呼叫选项 tc_iot_call_option_t
| 字段 | 说明 |
| media_content | 媒体内容,见下表枚举。 |
| video_quality | 视频清晰度,见下表枚举。 |
| custom_data | 自定义数据(最长256字节),随呼叫信令透传到小程序端。 |
媒体内容枚举 tc_iot_media_content_e
| 取值 | 说明 |
| TC_IOT_MEDIA_CONTENT_AUDIO | 仅音频通话。 |
| TC_IOT_MEDIA_CONTENT_VIDEO | 仅视频通话。 |
| TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO | 音视频通话(默认推荐)。 |
清晰度枚举 tc_iot_video_quality_e
| 取值 | 说明 |
| TC_IOT_VIDEO_QUALITY_LD | 流畅(低清晰度)。 |
| TC_IOT_VIDEO_QUALITY_SD | 标清。 |
| TC_IOT_VIDEO_QUALITY_HD | 高清。 |
| TC_IOT_VIDEO_QUALITY_FHD | 超清(全高清)。 |
示例
static void on_call_op(tc_iot_error_e error_code, const char *error_message) { printf("[twecall] call op: code=%d msg=%s\\n", error_code, error_message ? error_message : "");}// 使用联系人列表中的 user_id 发起音视频呼叫tc_iot_contact_s contact = {0};contact.user_id = g_target_user_id; // 取自步骤 2contact.user_name = "";contact.avatar_url = "";tc_iot_call_option_t option = {0};option.media_content = TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO;option.video_quality = TC_IOT_VIDEO_QUALITY_SD;tc_iot_error_e rc = tc_iot_av_call(&contact, option, on_call_op);if (rc != TC_IOT_ERR_SUCCESS) { printf("tc_iot_av_call failed: %d\\n", rc);}
注意:tc_iot_av_call 返回 TC_IOT_ERR_SUCCESS 仅表示呼叫信令已发出,并不代表通话已建立。需等待 on_call_accepted 回调后,通话才算真正接通,此时再开始上推音视频。
相关说明:
在设备呼叫微信小程序的场景中,设备默认接收参数如下:
| 参数项 | 默认值 |
| 视频编码 | H264 |
| 音频编码 | AAC |
| 分辨率 | 320×480 |
| 帧率 | 8 fps |
客户可根据实际产品需求修改上述参数,调用以下接口进行设置:
tc_iot_call_experiment_api("set_expected_av_params", "{\\"expected_audio_codec\\":\\"G722\\"," "\\"expected_video_codec\\":\\"MJPEG\\"," "\\"expected_video_rotation\\":0," "\\"max_fps\\":5," "\\"max_video_width\\":240," "\\"max_video_height\\":320}", NULL);
参数说明:
| 参数名 | 说明 | 取值范围 / 可选值 |
| expected_video_codec | 期望接收的视频编码格式。 | "H264"、"H265"、"MJPEG" |
| expected_audio_codec | 期望接收的音频编码格式。 | "AAC"、"OPUS"、"G722" |
| max_fps | 最大接收帧率。 | 5~10 |
| max_video_width | 期望接收的视频宽度。 | 根据设备实际需求填写。 |
| max_video_height | 期望接收的视频高度。 | 根据设备实际需求填写。 |
| expected_video_rotation | 期望微信发送的视频方向。 | 0:竖屏;1:横屏。 |
注意:1. 必须在 tc_iot_login 之前调用 tc_iot_call_experiment_api()接口设置"set_expected_av_params"。 设备 SDK 登录后会立即上报 expected_av_params 参数,且整个生命周期内仅上报一次。若在登录后调用,设置将不会生效。
注意:2. 当设备需要发送 H.265(HEVC)编码的视频数据时,必须在发起呼叫前预先声明源视频编码格式,否则微信小程序端将无法正常解码播放。
注意:请严格按照以下顺序进行接口调用:
注意:2.1 调用 tc_iot_call_experiment_api 设置设备端发送的视频编码为 H.265。
注意:2.2 调用 tc_iot_av_call 发起呼叫。
注意:tc_iot_call_experiment_api("set_source_av_params", "{\\"source_video_codec\\":\\"H265\\"}", NULL);
六、步骤 4:处理通话状态回调
呼叫发出后,设备会依次收到以下回调,业务侧需在对应回调中管理本地状态:
| 回调 | 业务建议处理 |
| on_call_accepted | 通话接通,创建推流通道并开始推送音视频帧(步骤5)。 |
| on_call_rejected | 对端拒接,清理呼叫状态,恢复待机。 |
| on_call_timeout | 无人接听,清理呼叫状态,可重试或提示用户。 |
| on_call_hangup | 通话中挂断,销毁推流通道(步骤6),停止采集。 |
示例:状态机式处理
static bool g_in_call = false;static tc_iot_av_channel_t *g_video_channel = NULL;static tc_iot_av_channel_t *g_audio_channel = NULL;static void on_call_accepted(const tc_iot_contact_s *contact) { printf("[twecall] on_call_accepted: user_id=%s\\n", contact->user_id); g_in_call = true; start_call_streams(); // 见步骤 5}static void on_call_rejected(const tc_iot_contact_s *contact) { printf("[twecall] on_call_rejected: user_id=%s\\n", contact->user_id); g_in_call = false;}static void on_call_timeout(const tc_iot_contact_s *contact) { printf("[twecall] on_call_timeout: user_id=%s\\n", contact->user_id); g_in_call = false;}static void on_call_hangup(const tc_iot_contact_s *contact) { printf("[twecall] on_call_hangup: user_id=%s\\n", contact->user_id); g_in_call = false; stop_call_streams(); // 见步骤 6}
注意:说明:
注意:建议在收到 on_call_accepted 后再启动编码器与推流,避免对端尚未接听就占用编码资源。on_call_rejected、on_call_timeout、on_call_hangup 均表示通话结束,应统一做资源清理。
七、步骤 5:创建通话推流通道并推送音视频帧
通话接通后(on_call_accepted 触发),创建音视频发送通道并向 SDK 推送编码后的音视频裸流。
tc_iot_av_channel_t *tc_iot_create_video_channel(int channel_id, tc_iot_video_quality_e quality);tc_iot_av_channel_t *tc_iot_create_audio_channel();tc_iot_error_e tc_iot_push_video_frame(tc_iot_av_channel_t *av_channel, const tc_iot_video_frame *frame);tc_iot_error_e tc_iot_push_audio_frame(tc_iot_av_channel_t *av_channel, const tc_iot_audio_frame *frame);tc_iot_error_e tc_iot_destroy_video_channel(tc_iot_av_channel_t *av_channel);tc_iot_error_e tc_iot_destroy_audio_channel(tc_iot_av_channel_t *av_channel);
| 接口 | 说明 |
| tc_iot_create_video_channel | 创建视频发送通道,channel_id 通话场景固定为 0。 |
| tc_iot_create_audio_channel | 创建音频发送通道,无需传 channel_id。 |
| tc_iot_push_video_frame | 推送一帧编码后的视频。 |
| tc_iot_push_audio_frame | 推送一帧编码后的音频。 |
| tc_iot_destroy_video_channel | 销毁视频通道。 |
| tc_iot_destroy_audio_channel | 销毁音频通道。 |
视频帧 tc_iot_video_frame
| 字段 | 说明 |
| codec | 视频编码格式,H264 , H265 或 MJPEG。 |
| type | 帧类型,IDR 关键帧或 P 帧。 |
| rotation | 画面旋转角度(0/90/180/270)。 |
| width / height | 画面宽高(像素)。 |
| data / data_size | 编码后视频裸流数据指针与长度。 |
| pts_ms | 帧时间戳(毫秒),传0的话由 SDK 内部填写。 |
音频帧 tc_iot_audio_frame
| 字段 | 说明 |
| codec | 音频编码格式,推荐 PCM ,OPUS ,AAC,G722。 |
| sample_rate | 采样率,推荐 16000。 |
| channels | 声道数,推荐 MONO(单声道)。 |
| frame_duration_ms | 单帧时长(毫秒),推荐 20ms。 |
| data / data_size | 编码后音频裸流数据指针与长度。 |
| pts_ms | 帧时间戳(毫秒)。 |
示例
static void start_call_streams(void) { // 视频通道:通话场景 channel_id = 0 g_video_channel = tc_iot_create_video_channel(0, TC_IOT_VIDEO_QUALITY_SD); // 音频通道:全局唯一 g_audio_channel = tc_iot_create_audio_channel(); // 之后由采集线程持续调用 push 推流}// 推送一帧视频(在视频采集/编码线程中调用)static void push_one_video_frame(void) { tc_iot_video_frame frame = {0}; frame.codec = TC_IOT_VIDEO_CODEC_H264; frame.type = TC_IOT_VIDEO_FRAME_TYPE_IDR; // 首帧用 IDR frame.rotation = TC_IOT_VIDEO_ROTATION_0; frame.width = 640; frame.height = 480; frame.data = encoded_video_buf; frame.data_size = encoded_video_len; frame.pts_ms = current_time_ms(); tc_iot_push_video_frame(g_video_channel, &frame);}// 推送一帧音频(在音频采集线程中调用)static void push_one_audio_frame(void) { tc_iot_audio_frame frame = {0}; frame.codec = TC_IOT_AUDIO_CODEC_PCM; frame.sample_rate = TC_IOT_AUDIO_SAMPLE_RATE_16000; frame.channels = TC_IOT_AUDIO_CHANNEL_MONO; frame.frame_duration_ms = 20; frame.data = audio_buf; frame.data_size = audio_len; frame.pts_ms = current_time_ms(); tc_iot_push_audio_frame(g_audio_channel, &frame);}
八、步骤 6:挂断通话
通话过程中,任意一方均可挂断。设备主动挂断调用 tc_iot_av_hangup;对端挂断时设备会收到 on_call_hangup 回调。
无论哪种情况,都应销毁推流通道、停止采集。
tc_iot_error_e tc_iot_av_hangup(const tc_iot_contact_s *contact, tc_iot_av_operation_cb callback);
| 参数 | 说明 |
| contact | 通话对端,contact.user_id 需与呼叫/接听时一致。 |
| callback | 操作结果回调。 |
示例
static void on_hangup_op(tc_iot_error_e error_code, const char *error_message) { printf("[twecall] hangup op: code=%d msg=%s\\n", error_code, error_message ? error_message : "");}// 主动挂断tc_iot_contact_s contact = {0};contact.user_id = g_target_user_id;tc_iot_av_hangup(&contact, on_hangup_op);// 销毁通道(主动挂断与收到 on_call_hangup 后均需调用)static void stop_call_streams(void) { if (g_video_channel) { tc_iot_destroy_video_channel(g_video_channel); g_video_channel = NULL; } if (g_audio_channel) { tc_iot_destroy_audio_channel(g_audio_channel); g_audio_channel = NULL; }}
注意:tc_iot_av_hangup 的 callback 用于确认挂断信令发送结果。即使主动挂断,也建议在 on_call_hangup 或 callback 中统一做通道销毁,避免重复释放。
九、完整示例代码
以下示例整合上述步骤,演示设备拉取联系人、呼叫小程序、接通后推流、挂断的完整流程。
#include <stdbool.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include "tc_iot.h"#include "tc_iot_av.h"// ==================== 全局状态 ====================static bool g_in_call = false;static tc_iot_av_channel_t *g_video_channel = NULL;static tc_iot_av_channel_t *g_audio_channel = NULL;static char g_target_user_id[128] = {0};// ==================== 工具 ====================static uint64_t current_time_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;}static void start_call_streams(void) { g_video_channel = tc_iot_create_video_channel(0, TC_IOT_VIDEO_QUALITY_SD); g_audio_channel = tc_iot_create_audio_channel(); printf("[twecall] call streams started\\n");}static void stop_call_streams(void) { if (g_video_channel) { tc_iot_destroy_video_channel(g_video_channel); g_video_channel = NULL; } if (g_audio_channel) { tc_iot_destroy_audio_channel(g_audio_channel); g_audio_channel = NULL; } printf("[twecall] call streams stopped\\n");}// ==================== 通话状态回调 ====================static void on_call_requested(const tc_iot_contact_s *contact, tc_iot_call_option_t option) { printf("[twecall] on_call_requested: user_id=%s media=%d quality=%d\\n", contact->user_id, option.media_content, option.video_quality); // 设备作为被叫,这里选择接听 tc_iot_av_accept(contact, NULL);}static void on_call_accepted(const tc_iot_contact_s *contact) { printf("[twecall] on_call_accepted: user_id=%s\\n", contact->user_id); g_in_call = true; start_call_streams();}static void on_call_rejected(const tc_iot_contact_s *contact) { printf("[twecall] on_call_rejected: user_id=%s\\n", contact->user_id); g_in_call = false;}static void on_call_timeout(const tc_iot_contact_s *contact) { printf("[twecall] on_call_timeout: user_id=%s\\n", contact->user_id); g_in_call = false;}static void on_call_hangup(const tc_iot_contact_s *contact) { printf("[twecall] on_call_hangup: user_id=%s\\n", contact->user_id); g_in_call = false; stop_call_streams();}static void on_audio_frame_received(const tc_iot_audio_frame *frame) { // 实际接入时:将 frame->data 送入音频播放器}static void on_video_frame_received(const tc_iot_video_frame *frame) { // 实际接入时:将 frame->data 送入视频解码器/渲染器}static void on_av_op(tc_iot_error_e error_code, const char *error_message) { printf("[twecall] av op: code=%d msg=%s\\n", error_code, error_message ? error_message : "");}// ==================== 获取联系人 ====================static void make_call(const char *user_id);static void on_get_contacts(tc_iot_error_e error_code, const char *error_message, const tc_iot_contact_s *contacts, uint32_t count, const char *next_cursor) { printf("[twecall] get_contacts: code=%d count=%u next_cursor=%s\\n", error_code, count, next_cursor ? next_cursor : "(end)"); if (error_code != TC_IOT_ERR_SUCCESS) { printf("get_contacts failed: %s\\n", error_message ? error_message : ""); return; } for (uint32_t i = 0; i < count; i++) { printf(" [%u] user_id=%s name=%s\\n", i, contacts[i].user_id, contacts[i].user_name ? contacts[i].user_name : ""); if (g_target_user_id[0] == '\\0' && contacts[i].user_id) { strncpy(g_target_user_id, contacts[i].user_id, sizeof(g_target_user_id) – 1); } } // 还有更多联系人,继续翻页 if (next_cursor != NULL) { tc_iot_av_get_contacts(next_cursor, 50, on_get_contacts); return; } // 拉取完成,呼叫第一个联系人 if (g_target_user_id[0] != '\\0') { make_call(g_target_user_id); } else { printf("[twecall] no contact to call\\n"); }}// ==================== 发起呼叫 ====================static void make_call(const char *user_id) { printf("[twecall] calling user_id=%s\\n", user_id); tc_iot_contact_s contact = {0}; contact.user_id = user_id; contact.user_name = ""; contact.avatar_url = ""; tc_iot_call_option_t option = {0}; option.media_content = TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO; option.video_quality = TC_IOT_VIDEO_QUALITY_SD; tc_iot_error_e rc = tc_iot_av_call(&contact, option, on_av_op); if (rc != TC_IOT_ERR_SUCCESS) { printf("[twecall] tc_iot_av_call failed: %d\\n", rc); }}// ==================== 挂断 ====================static void hangup_call(void) { if (!g_in_call) { return; } tc_iot_contact_s contact = {0}; contact.user_id = g_target_user_id; tc_iot_av_hangup(&contact, on_av_op);}// ==================== main ====================int main(int argc, char **argv) { // 1) 初始化 + 登录(参考快速接入文档) // … tc_iot_init … tc_iot_call_experiment_api("set_wx_miniapp_flavor", "DEMO", NULL); tc_iot_call_experiment_api("set_expected_av_params", "{\\"expected_audio_codec\\":\\"G722\\"," "\\"expected_video_codec\\":\\"MJPEG\\"," "\\"expected_video_rotation\\":0," "\\"max_fps\\":5," "\\"max_video_width\\":240," "\\"max_video_height\\":320}", NULL); // … tc_iot_login … // 2) 注册通话事件回调 tc_iot_av_observer_s observer; memset(&observer, 0, sizeof(observer)); observer.on_call_requested = on_call_requested; observer.on_call_accepted = on_call_accepted; observer.on_call_rejected = on_call_rejected; observer.on_call_timeout = on_call_timeout; observer.on_call_hangup = on_call_hangup; observer.on_audio_frame_received = on_audio_frame_received; observer.on_video_frame_received = on_video_frame_received; tc_iot_error_e rc = tc_iot_av_init(&observer); if (rc != TC_IOT_ERR_SUCCESS) { printf("tc_iot_av_init failed: %d\\n", rc); return -1; } // 3) 拉取联系人,回调中自动发起呼叫 tc_iot_av_get_contacts(NULL, 50, on_get_contacts); // 4) 通话结束后挂断 // hangup_call(); // 5) 反初始化 tc_iot_av_deinit(); return 0;}
注意:说明:
注意:1. 示例中省略了 tc_iot_init / tc_iot_login 的细节,请参考 登录与注册 完成登录。
注意:2. 实际接入时,音视频帧推送需替换为真实摄像头采集与编码逻辑。
注意:3. SDK 自带 iot_video_demo 示例已完整实现上述流程,可参考 src/demo/iot_video_demo/ 目录。
注意:4. 以上仅为单人通话场景演示。监控/通话及呼叫中接听第二个呼入电话等复杂场景,请参考 iot_video_demo。
十、编译并运行
SDK 自带的 iot_video_demo 已集成 TWeCall 完整流程,可直接编译验证:
cd iot# Linux 平台(CC TRTC,功能全)bash build.sh –platform=linux –trtc=cc# 将 CC TRTC 动态库加入系统动态库搜索路径export LD_LIBRARY_PATH=tc_iot_sdk/sdk/lib:$LD_LIBRARY_PATH# 运行(替换为你的设备三元组)./build/iot_video_demo -p YOUR_PRODUCT_ID -d YOUR_DEVICE_NAME -s 'YOUR_DEVICE_SECRET'
成功启动后会看到:
tc_iot_av_init success
在另一个终端通过命名管道 /tmp/tciot 发命令:
# 拉取联系人列表echo "get_contacts -n 50" > /tmp/tciot# 呼叫小程序联系人(user_id 取自联系人列表)echo "call -u user_id" > /tmp/tciot# 挂断echo "hangup -u user_id" > /tmp/tciot
十一、常见问题
| 现象 | 排查建议 |
| get_contacts 回调返回 count=0 | 确认设备已完成 TWeCall 激活与小程序授权;确认设备已绑定可呼叫联系人。 |
| tc_iot_av_call 返回成功但回调中提示 TWeCall device not active | TWeCall 是增值服务,需要在控制台激活。单击 TWeCall 激活 查看接口说明。 |
| tc_iot_av_call 返回成功但回调中提示wechat VoIP API error | 检查小程序中设备是否授权 VoIP 音视频通话。 |
| tc_iot_av_call 返回 TC_IOT_ERR_NOT_INITIALIZED | 确认已调用 tc_iot_av_init,且 tc_iot_login 成功在线。 |
| tc_iot_av_call 返回成功但长时间无 on_call_accepted | 等待 on_call_timeout;确认 user_id 格式正确(modelId/wxAppId/openId);确认小程序端在线且已授权。 |
| on_call_accepted 后对端看不到画面 | 确认首帧推送的是 IDR 关键帧;确认视频通道已创建且 channel_id=0;确认 width/height 与编码实际尺寸一致。 |
| on_call_accepted 后对端听不到声音 | 确认音频通道已创建;确认 sample_rate(推荐 16000)、channels(推荐 MONO)配置正确。 |
| 多次通话后内存持续增长 | 确认每次 on_call_hangup 后均调用了 tc_iot_destroy_video_channel / tc_iot_destroy_audio_channel。 |
| tc_iot_av_get_contacts 返回 TC_IOT_ERR_INVALID_ARGUMENT | 确认 limit 在 (0, 1024] 范围内。 |
十二、联系我们
如果你在接入或使用过程中有任何疑问或者建议,欢迎 联系我们 提交反馈。