四、登录、监听和访问处理

图:AV Server 工作流程示意图
主要使用的API: IOTCAPIs+AVAPIs
(一)登录
设备在上电后进行IOTC_Device_LoginEx,如果失败,需要重复调用,直到成功为止。此接口同时会开启局域网搜索功能,即使没有login成功,客户端也可以通过路由器下通过IOTC_Lan_Search等API搜索到,可以通过IOTC_Connect_ByUIDEx等API进行连线。
login成功后,就不再需要调用IOTC_Device_LoginEx,可以继续在此线程,调用IOTC_Get_LoginInfo来检查设备端是否从服务器掉线。
// 设备登录配置参数
DeviceLoginInput auth_option;
printf("thread_Login start\n");
// 在程序运行状态下循环尝试登录
while (gProcessRun) {
// 配置登录参数
auth_option.cb = sizeof(DeviceLoginInput);
auth_option.authentication_type = AUTHENTICATE_BY_KEY;
memcpy(auth_option.auth_key, gIotcAuthkey, IOTC_AUTH_KEY_LENGTH);
// 尝试设备登录(使用带密钥的扩展登录函数)
// 如不使用authkey,可替换为IOTC_Device_Login函数
int ret = IOTC_Device_LoginEx((char*)arg, &auth_option);
printf("IOTC_Device_LoginEx() ret = %d\n", ret);
// 登录成功则退出循环
if (ret == IOTC_ER_NoERROR) {
break;
}
// 登录失败则处理错误并重试
else {
PrintErrHandling(ret);
// 等待5秒后重试(可被程序退出信号中断)
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}
}
1. 登录状态检查(基于返回值判断)
IOTC_Get_Login_Info用以检查login状态,可以拿到两个值。返回值表示 try login失败的次数,输出参数 login_state 为7表示正常在login,并且有收到P2P服务器回应的login response。
/*
* IOTC_Get_Login_Info用以检查login状态,可以拿到两个值。
* 返回值表示 try login失败的次数
* 输出参数 login_state 为7表示正常在login,并且有收到P2P服务器回应的login response。
*/
unsigned int login_state;
int login_fail_count = 0;
// 循环检查登录状态(程序运行期间)
while (gProcessRun) {
// 获取登录状态信息
int ret = IOTC_Get_Login_Info(&login_state);
printf("IOTC_Get_Login_Info() ret = %d, login_state = %u\n", ret, login_state);
// 处理获取状态失败的情况
if (ret < IOTC_ER_NoERROR) {
break;
}
// 处理多次登录重试失败的情况(与服务器失联)
if (ret > 4) {
// 当无用户访问且网络畅通时(网络畅通可通过ping等方式检测)
if (/* 无用户访问条件 */ && /* 网络畅通条件 */) {
printf("device logout from p2p server, will call IOTC_Listen_Exit\n");
#ifdef _RESTART_P2P_MODULE_
// 重启P2P模块:调用后IOTC_Listen会返回IOTC_ER_EXIT_LISTEN
IOTC_Listen_Exit();
break;
#elif defined(_REINIT_SOCKET_)
// 仅重置底层socket,不重新登录
IOTC_ReInitSocket(0);
continue;
#elif defined(_FORCE_LOGIN_)
// 重置socket并强制重新登录(无需重新初始化)
IOTC_ReInitSocket(0);
IOTC_Device_Update_Authkey(device_authkey);
continue;
#endif
}
}
// 5秒后再次检查(可被程序退出信号中断)
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}
2. 登录状态检查(基于login_state判断)
unsigned int login_state;
int login_fail_count = 0;
// 循环检查登录状态(程序运行期间)
while (gProcessRun) {
// 获取登录状态信息
int ret = IOTC_Get_Login_Info(&login_state);
printf("IOTC_Get_Login_Info() ret = %d, login_state = %u\n", ret, login_state);
// 处理获取状态失败的情况
if (ret < IOTC_ER_NoERROR) {
break;
}
// 检查登录状态(7表示与P2P服务器通信正常)
if (login_state == 7) {
// 状态正常,重置失败计数器
login_fail_count = 0;
} else {
// 状态异常,递增失败计数器
if (++login_fail_count > 10) { // 超过10次检查异常(约50秒)
// 当无用户访问且网络畅通时(网络畅通可通过ping等方式检测)
if (/* 无用户访问条件 */ && /* 网络畅通条件 */) {
printf("device logout from p2p server, will call IOTC_Listen_Exit\n");
#ifdef _RESTART_P2P_MODULE_
// 重启P2P模块:调用后IOTC_Listen会返回IOTC_ER_EXIT_LISTEN
IOTC_Listen_Exit();
break;
#elif defined(_REINIT_SOCKET_)
// 仅重置底层socket,不重新登录
IOTC_ReInitSocket(0);
continue;
#elif defined(_FORCE_LOGIN_)
// 重置socket并强制重新登录(无需重新初始化)
IOTC_ReInitSocket(0);
IOTC_Device_Update_Authkey(device_authkey);
continue;
#endif
}
}
}
// 5秒后再次检查(可被程序退出信号中断)
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}
提示:如遇到一些小运营商的网络不稳定的情况,建议使用4.3.6.x以上的SDK,并在初始化之前调用IOTC_Device_Setup_Network_Adaption(IOTC_DEVICE_ADAPTION_MODE_CHOOSE_BEST_UDP_SERVER_OR_SWITCH_TO_TCP)开启TCP自适应。
(二)监听
unsigned char need_reboot = 0;
// 主循环:处理连接请求
while (gProcessRun) {
// 监听连接请求(超时时间1000ms)
int SID = IOTC_Listen(1000);
if (SID < 0) {
// 处理监听错误
PrintErrHandling(SID);
// 超过最大会话数
if (SID == IOTC_ER_EXCEED_MAX_SESSION) {
#if TEST_FAST_RECOVERY
// 快速恢复模式:等待5秒检查是否仍达最大在线人数
for (int i = 0; i < 5 && /* 在线人数已经达到最大 */; i++) {
sleep(1);
}
#else
// 普通模式:等待5秒后重试
sleep(5);
#endif
continue;
}
// 监听超时(正常情况,继续监听)
else if (SID == AV_ER_TIMEOUT) {
continue;
}
// 退出监听(需要重启)
else if (SID == IOTC_ER_EXIT_LISTEN) {
need_reboot = 1;
break;
}
// 其他异常(可能需要重启)
else {
// 程序异常处理逻辑
}
}
else {
// 成功获取会话ID,创建线程处理AV服务(avServStart是阻塞接口)
int* sid = (int*)malloc(sizeof(int));
*sid = SID;
pthread_t Thread_ID;
int ret = pthread_create(&Thread_ID, NULL, &thread_ForAVServerStart, (void*)sid);
if (ret < 0) {
printf("pthread_create failed ret[%d]\n", ret);
free(sid); // 创建线程失败时释放内存
} else {
pthread_detach(Thread_ID); // 分离线程,自动释放资源
gOnlineNum++; // 增加在线人数计数
}
}
}
// 处理重启逻辑
if (need_reboot) {
// 停止所有数据收发
// 等待所有连接退出
// 等待其他AV/IOTC相关API退出
// 反初始化AV和IOTC模块
avDeInitialize();
IOTC_DeInitialize();
// 跳转到程序开始处重新执行
goto BEGIN;
}
(三)访问处理
1. 创建数据通道
// 初始化AV服务启动配置结构体
AVServStartInConfig avStartInConfig;
AVServStartOutConfig avStartOutConfig;
// 清空输入配置结构体,避免脏数据
memset(&avStartInConfig, 0, sizeof(AVServStartInConfig));
// -------------------------- 配置输入参数(核心参数)--------------------------
// 结构体大小(固定赋值,用于SDK版本兼容)
avStartInConfig.cb = sizeof(AVServStartInConfig);
// 关联的IOTC会话ID(由IOTC_Listen返回)
avStartInConfig.iotc_session_id = SID;
// 数据通道使用的IOTC通道ID(默认0为主通道)
avStartInConfig.iotc_channel_id = 0;
// 通道创建超时时间(单位:秒)
avStartInConfig.timeout_sec = 30;
// 密码验证回调函数(用于客户端密码校验)
avStartInConfig.password_auth = ExPasswordAuthCallBackFn;
// 设备端功能限制(0表示不限制,具体参考SDK文档的AV Module Service Type Definition)
avStartInConfig.server_type = 0;
// 重传模式配置(1=开启重传,2=关闭重传)
avStartInConfig.resend = 1;
// 非必须回调:修改密码请求(按需实现)
avStartInConfig.change_password_request = ExChangePasswordCallBackFn;
// 非必须回调:设备能力查询(按需实现)
avStartInConfig.ability_request = ExAbilityRequestFn;
// -------------------------- 条件配置:Token认证(按需启用)--------------------------
#if ENABLE_TOKEN_AUTH
// 当APP端选择Token认证时,启用以下回调
avStartInConfig.token_auth = ExTokenAuthCallBackFn; // Token验证回调
avStartInConfig.token_delete = ExTokenDeleteCallBackFn; // Token删除回调
avStartInConfig.token_request = ExTokenRequestCallBackFn; // Token申请回调
avStartInConfig.identity_array_request = ExGetIdentityArrayCallBackFn; // 已存在ID查询回调
#endif
// -------------------------- 条件配置:安全加密模式 --------------------------
#if ENABLE_DTLS
// 启用DTLS加密(推荐,安全性更高)
avStartInConfig.security_mode = AV_SECURITY_DTLS;
#else
// 使用默认SIMPLE加密(兼容性优先)
avStartInConfig.security_mode = AV_SECURITY_SIMPLE;
#endif
// -------------------------- 配置输出参数 --------------------------
// 结构体大小(固定赋值,用于接收SDK返回数据)
avStartOutConfig.cb = sizeof(AVServStartOutConfig);
// -------------------------- 创建AV数据通道 --------------------------
// avIndex:返回的AV数据通道索引,后续数据操作基于此索引
int avIndex = avServStartEx(&avStartInConfig, &avStartOutConfig);
if (avIndex < 0) {
printf("avServStartEx failed!! SID[%d] code[%d]!!!\n", SID, avIndex);
printf("thread_ForAVServerStart: exit!! SID[%d]\n", SID);
// 启动失败:关闭对应的IOTC会话,更新在线人数,退出线程
IOTC_Session_Close(SID);
gOnlineNum--;
pthread_exit(0);
}
avServStartEx除了可以拿到avIndex,还可以拿到此通道相关的一些其他特性(不同版本会略有差异,以实际版本为准),具体如下:
/**
* @brief AV服务启动输出配置结构体
* @details 用于接收 avServStartEx 接口启动AV服务后的返回信息,包含通道能力、认证方式、用户标识等关键结果
*/
typedef struct AVServStartOutConfig {
uint32_t cb;
int32_t resend;//是否开启重传
int32_t two_way_streaming;//是否支持全双工
AvAuthType auth_type;//auth的类型
char account_or_identity[256];//自定义identity信息,如云存可能使用"vsaas"来标识
} AVServStartOutConfig;
typedef AVServStartOutConfig * LPAVSERV_START_OUT_CONFIG;
提示:全双工的通道,在进行对讲的时候,不需要建立新的avIndex。
2. 接收IO指令
// 循环接收并处理AV通道的IO控制指令
while (gProcessRun) {
// 接收IO控制指令:超时时间1000ms,缓冲区大小AV_MAX_IOCTRL_DATA_SIZE
int ret = avRecvIOCtrl(
avIndex,
&ioType,
(char*)ioCtrlBuf,
AV_MAX_IOCTRL_DATA_SIZE,
1000
);
// 处理接收结果
if (ret >= 0) {
// 成功接收指令:调用处理函数(支持图像、声音、对讲、回放等业务逻辑)
// 注:IO指令定义参考SDK文档「AV Module Reference of AV IO Control」,也可自定义格式
Handle_IOCTRL_Cmd(SID, avIndex, ioCtrlBuf, ioType);
}
else if (ret != AV_ER_TIMEOUT) {
printf("avIndex[%d], avRecvIOCtrl error, code[%d]\n", avIndex, ret);
break; // 错误退出循环,执行资源清理
}
}
// -------------------------- 资源清理与连接关闭流程 --------------------------
// 1. 停止发送IO控制数据
// 2. 关闭音视频发送(从音视频客户端列表中注销)
unregedit_client_from_video(SID);
unregedit_client_from_audio(SID);
// 3. 关闭对讲功能(需确保对应资源已释放)
// 4. 关闭回放功能(需确保对应资源已释放)
// 5. 关闭AV通道与IOTC会话,更新在线人数
printf("SID[%d], avIndex[%d], thread_ForAVServerStart exit!!\n", SID, avIndex);
// 关闭AV服务通道
avServStop(avIndex);
// 关闭IOTC会话连接
IOTC_Session_Close(SID);
// 减少在线人数计数
gOnlineNum--;
3. 发送视频
// 循环发送视频帧(程序运行期间持续执行)
while (gProcessRun) {
char* frame; // 视频帧数据指针(当前代码未使用,可根据实际需求补充逻辑)
int size; // 视频帧大小(当前代码未使用,可根据实际需求补充逻辑)
// 1. 从编码缓冲区取出图像帧(需补充实际取帧逻辑,如:frame = get_encoded_video_frame(&size);)
// 2. 轮询所有需要接收视频的AV通道,逐个发送视频帧
foreach (int _avIndex, avIndex_need_video) { // foreach:遍历需视频推送的avIndex列表
int avIndex = _avIndex; // 当前待发送的AV通道索引
// 发送视频帧数据(携带帧信息FRAMEINFO_t)
int ret = avSendFrameData(
avIndex,
videoBuf, // 视频数据缓冲区(需确保数据有效)
videoSize, // 视频数据大小
&frameInfo, // 视频帧信息(如帧类型、分辨率等)
sizeof(FRAMEINFO_t) // 帧信息结构体大小
);
// 处理发送结果
if (ret < 0) {
// 情况1:缓冲区溢出(帧丢失,需标记后续发送关键帧避免花屏)
if (ret == AV_ER_EXCEED_MAX_SIZE) {
need_iframe_flag_by_avIndex = 1; // 标记该avIndex需后续补发I帧
}
// 情况2:其他发送异常(停止向该avIndex推送视频)
else {
unregedit_client_from_video(SID); // 从视频推送列表中注销该客户端
continue; // 跳过当前avIndex,处理下一个
}
}
}
}
4. 动态码率配置
- (1)如果有多人在同时观看,且大部分是P2P/RLY模式,降低码率(或者分辨率);
- (2)如果有某个通道重传使用率一直维持在较高水平,则降低码率。
5. 对讲实现