帮你快速理解、总结文档立即下载

Linux & RTOS 设备

最近更新时间:2026-09-09 16:53:32
我的收藏
本文将介绍设备与 App 之间的双向通话,既包括设备主动呼叫 App,也包括设备响应 App 来电(接听/拒绝),以及通话建立后的音视频推流与挂断。

前提条件

在接入通话功能前,请您先完成 登录与注册 文档中跑通 tc_iot_login 登录功能。如果您已经完成,可以跳过该操作。
说明:
如果您需要呼叫小程序的功能,请您先完成 激活 TWeCall 的接入。

接入步骤

步骤 1:注册通话事件回调

登录成功后,调用 tc_iot_av_init 注册 tc_iot_av_observer_s,通话相关的回调如下:
回调
说明
on_call_requested
【被叫】收到对端来电时触发,携带 contact(对端信息)与 option(通话媒体类型等)。
on_call_accepted
【主叫】主动呼叫被对端接听时触发,通话建立;此时才创建发送通道并开始推流。
on_call_rejected
【主叫】主动呼叫被对端拒绝时触发,应结束本次呼叫并释放资源。
on_call_timeout
呼叫无人应答超时时触发(主叫拨出无人接听、或被叫来电未及时处理均可能触发)。
on_call_hangup
对端挂断通话时触发。
#include "tc_iot_av.h"

static void on_call_requested(const tc_iot_contact_s *contact, tc_iot_call_option_t option);
static void on_call_accepted(const tc_iot_contact_s *contact);
static void on_call_rejected(const tc_iot_contact_s *contact);
static void on_call_timeout(const tc_iot_contact_s *contact);
static void on_call_hangup(const tc_iot_contact_s *contact);

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 av_rc = tc_iot_av_init(&observer);
if (av_rc != TC_IOT_ERR_SUCCESS) {
printf("tc_iot_av_init failed: %d\\n", av_rc);
}

步骤 2:主动发起呼叫(可选)

设备主动呼叫 App 端联系人时,请您先通过 tc_iot_av_get_contacts 拉取联系人列表,获取小程序端的信息 tc_iot_contact_s,再调用 tc_iot_av_call 发起呼叫。呼叫发出后,等待对端响应:
对端接听触发 on_call_accepted
对端拒绝触发 on_call_rejected
无人应答超时触发 on_call_timeout
tc_iot_contact_s 来自 tc_iot_av_get_contacts 接口,该接口分页拉取联系人:cursorNULL 拉取首页,后续使用回调返回的 next_cursor 继续拉取,limit 为单页数量。
// tc_iot_av_call 所需的 tc_iot_contact_s 来自 tc_iot_av_get_contacts 的回调返回
static void on_contacts(tc_iot_error_e error_code, const char *error_message,
const tc_iot_contact_s *contacts, uint32_t count,
const char *next_cursor) {
if (error_code != TC_IOT_ERR_SUCCESS) {
printf("[demo] get_contacts failed: %d %s\\n", error_code, error_message ? error_message : "");
return;
}
for (uint32_t i = 0; i < count; i++) {
printf("[demo] contact[%u] user_id=%s user_name=%s\\n",
i, contacts[i].user_id, contacts[i].user_name ? contacts[i].user_name : "");
}
// 若 next_cursor 非空,可再次调用 tc_iot_av_get_contacts(next_cursor, limit, on_contacts) 拉取下一页
// 从列表中选定目标联系人后,即可将其 tc_iot_contact_s 传入 tc_iot_av_call 发起呼叫
}

// 拉取首页联系人(limit 为单页数量)
tc_iot_av_get_contacts(NULL, 20, on_contacts);
说明:
下方 start_outgoing_call 示例为便于演示,直接用固定的 user_id 构造 tc_iot_contact_s;实际使用时,应从 tc_iot_av_get_contacts 返回的联系人列表中选取目标联系人。
通话选项 tc_iot_call_option_t 字段说明如下:
字段
说明
media_content
通话媒体类型:TC_IOT_MEDIA_CONTENT_AUDIO(纯语音)、TC_IOT_MEDIA_CONTENT_VIDEO(纯视频)或 TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO(音视频)。
video_quality
视频清晰度,如 TC_IOT_VIDEO_QUALITY_SD;纯语音通话可忽略。
custom_data
透传给对端的自定义数据(可选),最大长度 MAX_CUSTOM_DATA_LENGTH
注意:
tc_iot_av_call 返回成功仅代表呼叫已发出,通话尚未建立。请勿在此时立即推流,务必等到 on_call_accepted(对端接听)回调触发后再创建发送通道并推流。
// 主动发起呼叫。发起时选用的 media_content 需在 on_call_accepted 中复用,
// 因 on_call_accepted 只携带 contact,不再回传 option,故这里用全局变量记住。
static tc_iot_call_option_t g_outgoing_option;

static void start_outgoing_call(const char *peer_user_id) {
tc_iot_contact_s contact = {0};
contact.user_id = peer_user_id;

memset(&g_outgoing_option, 0, sizeof(g_outgoing_option));
g_outgoing_option.media_content = TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO; // 音视频通话
g_outgoing_option.video_quality = TC_IOT_VIDEO_QUALITY_SD;

tc_iot_error_e rc = tc_iot_av_call(&contact, g_outgoing_option, NULL);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("[demo] tc_iot_av_call failed: %d\\n", rc);
return;
}
printf("[demo] calling %s, waiting for accept ...\\n", peer_user_id);
// 呼叫已发出,等待 on_call_accepted / on_call_rejected / on_call_timeout
}

// 主叫:对端接听,通话建立,此时创建发送通道并开始推流(沿用发起时的 media_content)
static void on_call_accepted(const tc_iot_contact_s *contact) {
printf("[demo] on_call_accepted user_id=%s\\n", contact->user_id);
// create_call_channels(g_outgoing_option.media_content, g_outgoing_option.video_quality);
// start_capture_and_push(...) // 详见步骤 4/5/6
}

// 主叫:对端拒绝,结束本次呼叫
static void on_call_rejected(const tc_iot_contact_s *contact) {
printf("[demo] on_call_rejected user_id=%s\\n", contact->user_id);
// 停止推流并释放资源(若已创建)
}

步骤 3:响应来电,接听或拒绝

设备作为被叫,收到 on_call_requested 后,根据业务逻辑调用 tc_iot_av_accept 接听,或调用 tc_iot_av_reject 拒绝:
static void on_call_requested(const tc_iot_contact_s *contact, tc_iot_call_option_t option) {
printf("[demo] on_call_requested user_id=%s media=%d\\n", contact->user_id, option.media_content);

// 示例:直接接听,实际业务可结合按键、APP 联动等方式决定接听或拒绝
tc_iot_error_e rc = tc_iot_av_accept(contact, NULL);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("[demo] tc_iot_av_accept failed: %d\\n", rc);
}

// 拒绝来电:tc_iot_av_reject(contact, NULL);
}

步骤 4:创建发送通道

通话建立后(被叫方 tc_iot_av_accept 接听成功,或主叫方收到 on_call_accepted),根据通话的 media_content 创建对应的发送通道:
接口
说明
tc_iot_create_video_channel(channel_id, quality)
创建视频发送通道,通话场景固定使用通道 0quality 为视频清晰度。
tc_iot_create_audio_channel()
创建音频发送通道,无需传入 channel_id
注意:
设备通常只有一路麦克风采集源,tc_iot_create_audio_channel 建议全局只创建一次。
static tc_iot_av_channel_t *g_video_channel = NULL;
static tc_iot_av_channel_t *g_audio_channel = NULL;

// 通话建立后,根据媒体类型创建对应的发送通道
static void create_call_channels(tc_iot_media_content_e media_content,
tc_iot_video_quality_e quality) {
if (media_content == TC_IOT_MEDIA_CONTENT_VIDEO ||
media_content == TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO) {
g_video_channel = tc_iot_create_video_channel(0, quality);
if (!g_video_channel) {
printf("[demo] create video channel failed\\n");
}
}

if ((media_content == TC_IOT_MEDIA_CONTENT_AUDIO ||
media_content == TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO) &&
!g_audio_channel) {
g_audio_channel = tc_iot_create_audio_channel();
if (!g_audio_channel) {
printf("[demo] create audio channel failed\\n");
}
}

// 启动本地采集(示意,详见步骤 4/5 推流示例)
// start_capture_and_push(...)
}

步骤 5:推送视频帧

采集到一帧编码后的视频数据后,填充 tc_iot_video_frame 并调用 tc_iot_push_video_frame 推送到视频发送通道:
字段
说明
codec
视频编码格式,TC_IOT_VIDEO_CODEC_H264TC_IOT_VIDEO_CODEC_H265
type
帧类型,关键帧 TC_IOT_VIDEO_FRAME_TYPE_IDR 或非关键帧 TC_IOT_VIDEO_FRAME_TYPE_P
rotation
画面旋转角度,如 TC_IOT_VIDEO_ROTATION_0
width / height
画面宽高(像素)。
data / data_size
编码后的视频裸流数据指针与长度。
pts_ms
帧时间戳(毫秒)。
说明:
建议推送第一个 IDR 关键帧之后才开始持续推流,以保证对端能正常解码;在 IDR 帧之前推送 P 帧,对端可能无法解码画面。
void push_video_frame_sample(tc_iot_av_channel_t *video_channel,
const uint8_t *encoded_data, size_t encoded_size,
bool is_key_frame) {
if (!video_channel) {
return;
}

tc_iot_video_frame frame;
memset(&frame, 0, sizeof(frame));
frame.codec = TC_IOT_VIDEO_CODEC_H264;
frame.type = is_key_frame ? TC_IOT_VIDEO_FRAME_TYPE_IDR : TC_IOT_VIDEO_FRAME_TYPE_P;
frame.rotation = TC_IOT_VIDEO_ROTATION_0;
frame.width = 480;
frame.height = 320;
frame.data = (uint8_t *)encoded_data;
frame.data_size = encoded_size;
frame.pts_ms = current_time_ms(); // 由业务自行实现,返回当前毫秒时间戳

tc_iot_error_e err = tc_iot_push_video_frame(video_channel, &frame);
if (err != TC_IOT_ERR_SUCCESS) {
printf("[demo] push video frame failed: %d\\n", err);
}
}

步骤 6:推送音频帧

采集到一帧编码后的音频数据后,填充 tc_iot_audio_frame 并调用 tc_iot_push_audio_frame 推送到音频发送通道:
字段
说明
codec
音频编码格式,支持 TC_IOT_AUDIO_CODEC_PCM / AAC / OPUS / G722
sample_rate
采样率,如 TC_IOT_AUDIO_SAMPLE_RATE_16000
channels
声道数,TC_IOT_AUDIO_CHANNEL_MONO(单声道)或 TC_IOT_AUDIO_CHANNEL_STEREO(双声道)。
frame_duration_ms
单帧时长(毫秒),如 20ms 一帧。
data / data_size
编码后的音频裸流数据指针与长度。
pts_ms
帧时间戳(毫秒)。
void push_audio_frame_sample(tc_iot_av_channel_t *audio_channel,
const uint8_t *pcm_data, size_t pcm_size) {
if (!audio_channel) {
return;
}

tc_iot_audio_frame frame;
memset(&frame, 0, sizeof(frame));
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 = (uint8_t *)pcm_data;
frame.data_size = pcm_size;
frame.pts_ms = current_time_ms(); // 由业务自行实现,返回当前毫秒时间戳

tc_iot_error_e err = tc_iot_push_audio_frame(audio_channel, &frame);
if (err != TC_IOT_ERR_SUCCESS) {
printf("[demo] push audio frame failed: %d\\n", err);
}
}

步骤 7:挂断通话

通话中主动挂断,调用 tc_iot_av_hangup;对端挂断时会触发 on_call_hangup,此时应停止本地推流并销毁发送通道:
void hangup_call(const char *user_id) {
tc_iot_contact_s contact = {0};
contact.user_id = user_id;

tc_iot_error_e rc = tc_iot_av_hangup(&contact, NULL);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("[demo] tc_iot_av_hangup failed: %d\\n", rc);
}
}

static void on_call_hangup(const tc_iot_contact_s *contact) {
printf("[demo] on_call_hangup user_id=%s\\n", contact->user_id);
// 停止推流并销毁发送通道(同实时监控场景的 on_monitor_end 处理)
}
说明:
己方来电未及时处理时会触发 on_call_timeout,处理方式与 on_call_hangup 一致:停止推流并释放资源。

完整示例代码

以下示例整合登录、通话事件响应、发起语音/视频呼叫、以及使用模拟数据推送音视频帧的完整流程,用于验证通话链路是否正常。实际接入时,请将模拟数据替换为真实的摄像头/麦克风采集数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include "tc_iot.h"
#include "tc_iot_av.h"

static volatile int s_login_done = 0;
static tc_iot_error_e s_login_rc = TC_IOT_ERR_SUCCESS;

static tc_iot_av_channel_t *g_video_channel = NULL;
static tc_iot_av_channel_t *g_audio_channel = NULL;
static volatile bool g_pushing = false;
static pthread_t g_video_thread;
static pthread_t g_audio_thread;

// 主叫发起呼叫时选用的通话选项。on_call_accepted 只携带 contact 不回传 option,
// 故在此记住 media_content / video_quality,供接听后创建发送通道复用。
static tc_iot_call_option_t g_outgoing_option;

static uint64_t current_time_ms(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// 登录回调:与快速接入一致
static void on_login(tc_iot_error_e error_code, const char *error_message) {
s_login_rc = error_code;
s_login_done = 1;
if (error_code != TC_IOT_ERR_SUCCESS) {
printf("[demo] login failed: %d, msg=%s\\n", error_code,
error_message ? error_message : "");
}
}

// 模拟视频采集:独立线程定时生成占位视频帧(首帧标记为 IDR)并推送。
// 实际接入时替换为真实摄像头采集与 H264/H265 编码逻辑。
static void *video_push_thread_func(void *arg) {
static uint8_t dummy_video[64] = {0};
bool first_frame = true;

while (g_pushing) {
if (g_video_channel) {
tc_iot_video_frame vframe;
memset(&vframe, 0, sizeof(vframe));
vframe.codec = TC_IOT_VIDEO_CODEC_H264;
vframe.type = first_frame ? TC_IOT_VIDEO_FRAME_TYPE_IDR
: TC_IOT_VIDEO_FRAME_TYPE_P;
vframe.rotation = TC_IOT_VIDEO_ROTATION_0;
vframe.width = 480;
vframe.height = 320;
vframe.data = dummy_video;
vframe.data_size = sizeof(dummy_video);
vframe.pts_ms = current_time_ms();
tc_iot_push_video_frame(g_video_channel, &vframe);
first_frame = false;
}
usleep(40 * 1000); // 模拟 25fps
}
return NULL;
}

// 模拟音频采集:独立线程定时生成占位音频帧并推送。
// 实际接入时替换为真实麦克风采集与 PCM/AAC 编码逻辑。
static void *audio_push_thread_func(void *arg) {
static uint8_t dummy_audio[320] = {0}; // 16kHz/mono/20ms 对应的 PCM 采样点数

while (g_pushing) {
if (g_audio_channel) {
tc_iot_audio_frame aframe;
memset(&aframe, 0, sizeof(aframe));
aframe.codec = TC_IOT_AUDIO_CODEC_PCM;
aframe.sample_rate = TC_IOT_AUDIO_SAMPLE_RATE_16000;
aframe.channels = TC_IOT_AUDIO_CHANNEL_MONO;
aframe.frame_duration_ms = 20;
aframe.data = dummy_audio;
aframe.data_size = sizeof(dummy_audio);
aframe.pts_ms = current_time_ms();
tc_iot_push_audio_frame(g_audio_channel, &aframe);
}
usleep(20 * 1000); // 20ms 一帧
}
return NULL;
}

// 根据通话媒体类型创建发送通道并启动推流线程
static void start_call_streams(tc_iot_media_content_e media_content) {
if (media_content == TC_IOT_MEDIA_CONTENT_VIDEO ||
media_content == TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO) {
g_video_channel = tc_iot_create_video_channel(0, TC_IOT_VIDEO_QUALITY_SD);
}
if (media_content == TC_IOT_MEDIA_CONTENT_AUDIO ||
media_content == TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO) {
g_audio_channel = tc_iot_create_audio_channel();
}

if (!g_pushing) {
g_pushing = true;
pthread_create(&g_video_thread, NULL, video_push_thread_func, NULL);
pthread_create(&g_audio_thread, NULL, audio_push_thread_func, NULL);
}
}

// 停止推流并销毁发送通道
static void stop_call_streams(void) {
if (g_pushing) {
g_pushing = false;
pthread_join(g_video_thread, NULL);
pthread_join(g_audio_thread, NULL);
}
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;
}
}

static void on_call_requested(const tc_iot_contact_s *contact, tc_iot_call_option_t option) {
printf("[demo] on_call_requested user_id=%s media=%d\\n", contact->user_id, option.media_content);

tc_iot_error_e rc = tc_iot_av_accept(contact, NULL);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("[demo] tc_iot_av_accept failed: %d\\n", rc);
return;
}
start_call_streams(option.media_content);
}

// 主叫:主动发起一路音视频呼叫。对端接听后在 on_call_accepted 中才开始推流。
static void start_outgoing_call(const char *peer_user_id) {
tc_iot_contact_s contact = {0};
contact.user_id = peer_user_id;

memset(&g_outgoing_option, 0, sizeof(g_outgoing_option));
g_outgoing_option.media_content = TC_IOT_MEDIA_CONTENT_AUDIO_VIDEO;
g_outgoing_option.video_quality = TC_IOT_VIDEO_QUALITY_SD;

tc_iot_error_e rc = tc_iot_av_call(&contact, g_outgoing_option, NULL);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("[demo] tc_iot_av_call failed: %d\\n", rc);
return;
}
printf("[demo] calling %s, waiting for accept ...\\n", peer_user_id);
}

// 主叫:对端接听,通话建立,创建发送通道并推流(沿用发起呼叫时的 media_content)
static void on_call_accepted(const tc_iot_contact_s *contact) {
printf("[demo] on_call_accepted user_id=%s\\n", contact->user_id);
start_call_streams(g_outgoing_option.media_content);
}

// 主叫:对端拒绝,结束本次呼叫并释放资源
static void on_call_rejected(const tc_iot_contact_s *contact) {
printf("[demo] on_call_rejected user_id=%s\\n", contact->user_id);
stop_call_streams();
}

static void on_call_timeout(const tc_iot_contact_s *contact) {
printf("[demo] on_call_timeout user_id=%s\\n", contact->user_id);
stop_call_streams();
}

static void on_call_hangup(const tc_iot_contact_s *contact) {
printf("[demo] on_call_hangup user_id=%s\\n", contact->user_id);
stop_call_streams();
}

int main(int argc, char **argv) {
if (argc < 7) {
printf("Usage: %s -p <product_id> -d <device_id> -s <device_secret> [-u <peer_user_id>]\\n", argv[0]);
return 0;
}

const char *product_id = NULL;
const char *device_id = NULL;
const char *device_secret = NULL;
const char *peer_user_id = NULL; // 传入则设备主动呼叫该 App 账号,否则等待来电
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
product_id = argv[++i];
} else if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) {
device_id = argv[++i];
} else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
device_secret = argv[++i];
} else if (strcmp(argv[i], "-u") == 0 && i + 1 < argc) {
peer_user_id = argv[++i];
}
}
if (!product_id || !device_id || !device_secret) {
printf("device info error\\n");
return 1;
}

// 初始化与登录:与快速接入一致
tc_iot_config_s config;
memset(&config, 0, sizeof(config));
config.storage_path = "./";
config.log_level = TC_IOT_LOG_LEVEL_INFO;

tc_iot_error_e rc = tc_iot_init(&config);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("tc_iot_init failed: %d\\n", rc);
return 1;
}

tc_iot_device_info_s device_info;
device_info.product_id = product_id;
device_info.device_id = device_id;
device_info.device_secret = device_secret;
device_info.region = "ap-guangzhou";

rc = tc_iot_login(&device_info, on_login);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("tc_iot_login call failed: %d\\n", rc);
tc_iot_deinit();
return 1;
}

int waited_ms = 0;
while (!s_login_done && waited_ms < 5000) {
usleep(100 * 1000);
waited_ms += 100;
}
if (!s_login_done || s_login_rc != TC_IOT_ERR_SUCCESS) {
printf("login timeout or failed\\n");
tc_iot_deinit();
return 1;
}
printf("[demo] login success\\n");

// 注册通话事件回调(本文核心内容)
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;

rc = tc_iot_av_init(&observer);
if (rc != TC_IOT_ERR_SUCCESS) {
printf("tc_iot_av_init failed: %d\\n", rc);
tc_iot_deinit();
return 1;
}

if (peer_user_id) {
// 主叫模式:登录后主动呼叫指定 App 账号,等待对端接听(on_call_accepted)
printf("[demo] outgoing call mode, peer=%s\\n", peer_user_id);
start_outgoing_call(peer_user_id);
} else {
// 被叫模式:等待 App 来电(on_call_requested)
printf("[demo] ready, waiting for incoming call from app (60s)...\\n");
}
sleep(60);

// 释放资源
stop_call_streams();
tc_iot_av_deinit();
tc_iot_logout();
tc_iot_deinit();
printf("[demo] main exit\\n");
return 0;
}

编译并运行

将上述代码覆盖到 登录与注册 中已创建的项目 main.cCMakeLists.txt 无需改动,重新编译并运行即可。
1. 重新编译。
cmake --build build -j
2. 运行可执行文件,传入设备三元组。不带 -u 时设备等待 App 来电(被叫);带 -u <peer_user_id> 时设备登录后主动呼叫指定 App 账号(主叫)。
# 被叫:等待 App 来电
./build/iot_quickstart_demo -p YOUR_PRODUCT_ID -d YOUR_DEVICE_NAME -s 'YOUR_DEVICE_SECRET'

# 主叫:登录后主动呼叫 App 账号 PEER_USER_ID
./build/iot_quickstart_demo -p YOUR_PRODUCT_ID -d YOUR_DEVICE_NAME -s 'YOUR_DEVICE_SECRET' -u PEER_USER_ID
3. 验证通话链路:
被叫场景:通过 APP 对该设备发起语音或视频呼叫,观察设备端日志输出 on_call_requested 及后续推流日志;挂断后观察 on_call_hangup 日志。
主叫场景:以 -u 方式运行,观察设备端日志输出 calling ...,APP 接听后触发 on_call_accepted 并开始推流;APP 拒绝则触发 on_call_rejected
出现上述日志即代表通话链路验证成功。

常见问题

现象
排查建议
APP 发起呼叫后,设备端未收到 on_call_requested
确认设备已成功登录且 tc_iot_av_init 调用成功;确认 on_call_requested 已正确赋值(未被 memset 清零覆盖)。
tc_iot_av_accept 返回失败
确认设备已成功登录;确认 contact.user_id 有效;确认当前没有其他通话正在进行。
调用 tc_iot_av_call 后对端 App 未收到呼叫
确认设备已成功登录且 tc_iot_av_init 调用成功;确认 contact.user_id 为有效的 App 端账号且对端在线;确认当前没有其他通话正在进行。
主叫已接通(on_call_accepted 已触发),但对端看不到画面或听不到声音
确认在 on_call_accepted(而非 tc_iot_av_call 返回成功)后才创建发送通道并推流;确认沿用了发起呼叫时的 media_content;确认视频首帧为 IDR 关键帧。
通话已接通,但对端看不到画面或听不到声音
确认接通后已根据 media_content 创建对应的发送通道并开始推流;确认视频首帧为 IDR 关键帧。
多次通话后设备资源占用持续增长
检查 on_call_hangup / on_call_timeout 是否都正确停止了推流并销毁了发送通道,避免通道未释放导致的资源泄漏。

联系我们

如果您在接入或使用过程中有任何疑问或者建议,欢迎 联系我们 提交反馈。