简体中文

基于AVAPIs模块开发网络摄像机-设备端

星空无限传媒xk8027-高清全集免费看

更新日期:2025/2/13


目录


相关demo路径

平台 例子
android SDK目录\Sample\Android\Sample_AVAPIs_Server
linux SDK目录\Sample\Linux\Sample_AVAPIs

常见的配网方式

可以参考:常见的配网方式实作纲要

登录、监听和访问处理

主要使用的API: IOTCAPIs+AVAPIs

流程图:

登录

// 设备登录配置参数
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 (sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
            sleep(1);
        }
    }
}


设备在上电后进行IOTC_Device_LoginEx,如果失败,需要重复调用,直到成功为止。此接口同时会开启局域网搜索功能,即使没有login成功,客户端也可以通过路由器下通过IOTC_Lan_Search等API搜索到,可以通过IOTC_Connect_ByUIDEx等API进行连线。

login成功后,就不再需要调用IOTC_Device_LoginEx,可以继续在此线程,调用IOTC_Get_LoginInfo来检查设备端是否从服务器掉线。

/*
 * 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);
    }
}

如果是以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;
}

访问处理

创建数据通道:

// 初始化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;                          ///< 结构体大小(必须赋值为 sizeof(AVServStartOutConfig)),用于SDK版本兼容性校验
    int32_t         resend;                      ///< 当前AV通道是否支持重传模式(1=支持重传,0=不支持重传),与输入配置的 resend 字段对应
    int32_t         two_way_streaming;           ///< 当前AV通道是否支持全双工通信(1=支持全双工,0=不支持),标识通道数据收发能力
    AvAuthType      auth_type;                   ///< 实际使用的认证方式,枚举类型(如 AV_AUTH_PASSWORD=密码验证,AV_AUTH_TOKEN=Token验证)
    char            account_or_identity[256];    ///< 客户端(APP)在 avClientStartEx 中传入的用户ID/身份标识,用于区分当前连接的客户端身份
} AVServStartOutConfig;
typedef AVServStartOutConfig * LPAVSERV_START_OUT_CONFIG;

全双工的通道,在进行对讲的时候,不需要建立新的avIndex。

接收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--;

发送视频:

// 循环发送视频帧(程序运行期间持续执行)
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,处理下一个
            }
        }
    }
}

如果需要加上动态码率,可以参考以下规则:

  1. 如果有多人在同时观看,且大部分是P2P/RLY模式,降低码率(或者分辨率)
  2. 如果有某个通道重传使用率一直维持在较高水平,则降低码率。

参考代码:

// 存储需要关键帧(I帧)的AV通道索引集合(需自行实现队列/链表逻辑)
array_int avIndex_video_array_need_keyFrame;

// 循环发送视频帧(程序运行期间持续执行)
while (gProcessRun) {
    char* frame;  // 视频帧数据指针(需补充实际取帧逻辑)
    int size;     // 视频帧大小(需补充实际取帧逻辑)

    // 1. 从编码缓冲区取出图像帧(需补充:frame = get_encoded_video_frame(&size, &frameInfo);)
    // 2. 轮询所有需要接收视频的AV通道,逐个处理发送逻辑
    foreach (int _avIndex, avIndex_array_need_video) {  // foreach:遍历需视频推送的avIndex列表
        int avIndex = _avIndex;  // 当前待发送的AV通道索引

        // -------------------------- 关键帧(I帧)逻辑判断 --------------------------
        // 检查当前帧类型 + 该avIndex是否在「需I帧列表」中
        if (frameInfo.flag == IPC_FRAME_FLAG_IFRAME) {
            // 情况1:当前帧是I帧
            if (avIndex_video_array_need_keyFrame.contains(avIndex)) {
                // 已获取I帧,从「需I帧列表」中移除该avIndex,清除标记
                avIndex_video_array_need_keyFrame.remove(avIndex);
            }
        } else {
            // 情况2:当前帧是非I帧(P/B帧)
            if (avIndex_video_array_need_keyFrame.contains(avIndex)) {
                // 该avIndex仍需I帧,跳过当前非I帧,继续等待后续I帧
                // (可选优化:此处可调用强制编码I帧的接口,如force_encode_iframe())
                continue;
            }
        }

        // -------------------------- 发送视频帧 --------------------------
        int ret = avSendFrameData(
            avIndex,
            videoBuf,       // 视频数据缓冲区(需确保与取出的frame数据一致)
            videoSize,      // 视频数据大小(需与实际帧大小匹配)
            &frameInfo,     // 视频帧信息(包含帧类型、分辨率等关键参数)
            sizeof(FRAMEINFO_t)  // 帧信息结构体大小(固定值)
        );

        // -------------------------- 发送结果处理 --------------------------
        if (ret < 0) {
            // 子情况1:缓冲区溢出(帧丢失,需标记该avIndex后续补I帧,避免花屏)
            if (ret == AV_ER_EXCEED_MAX_SIZE) {
                avIndex_video_array_need_keyFrame.append(avIndex);
            }
            // 子情况2:其他发送异常(停止向该avIndex推送视频,清理关联资源)
            else {
                unregedit_client_from_video(SID);  // 从视频推送列表注销该客户端
                continue;  // 跳过当前avIndex,处理下一个通道
            }
        }
    }
}

对讲:

可以参考: 基于AVAPIs的对讲实现


常见的问题以及处理

如何监测传输状况?

/**
 * @brief AV通道缓冲区使用率检查线程
 * @details 独立线程定时遍历所有发送中通道,监控重传缓冲区使用率,
 *          根据使用率触发不同级别的提示或优化操作,避免缓冲区溢出
 * @param arg 线程入参(当前未使用,可根据需求扩展)
 */
void thread_avCheckBufferUsageRate(void* arg) {
    // 线程主循环(程序运行期间持续执行)
    while (programRunning) {
        // 遍历所有正在发送数据的AV通道(all_index_in_send_queue:发送队列中的avIndex集合)
        foreach (int avIndex, all_index_in_send_queue) {
            // 获取当前通道的重传缓冲区使用率(返回值为0.0~1.0的浮点数,1.0表示缓冲区满)
            float rate = avResendBufUsageRate(avIndex);

            // 根据使用率分级处理
            if (rate <= 0.0f) {
                // 级别1:使用率≤0%,传输状态顺畅,无数据堆积
                printf("[AV Buffer Check] avIndex[%d] - 传输顺畅 (usage rate: %.2f%%)\n", 
                       avIndex, rate * 100);
            } 
            else if (rate > 0.03f && rate < 0.7f) {
                // 级别2:使用率3%~70%,数据开始累积(需关注但暂无需干预)
                printf("[AV Buffer Check] avIndex[%d] - 数据在累积中 (usage rate: %.2f%%)\n", 
                       avIndex, rate * 100);
            } 
            else if (rate >= 0.7f) {  // 修正:包含等于70%的情况,避免边界遗漏
                // 级别3:使用率≥70%,数据累积较多,缓冲区溢出风险高
                printf("[AV Buffer Check] avIndex[%d] - 数据累积较多,可能导致缓冲区溢出 (usage rate: %.2f%%)\n", 
                       avIndex, rate * 100);
                
                // 建议优化操作(需在其他线程执行,避免阻塞当前监控线程)
                // 1. 触发缓冲区清理(如调用avClearResendBuf(avIndex),需确认SDK是否支持)
                // 2. 降低视频分辨率(如从1080P调整为720P,减少单帧数据量)
                // 3. 降低视频帧率(如从30fps调整为15fps,减少单位时间内数据量)
            }
        }

        // 每1秒检查一次(平衡监控精度与CPU占用)
        sleep(1);
    }

    // 线程退出提示(可选,便于日志排查)
    printf("[AV Buffer Check] Thread exited normally\n");
}

//清空缓存区

/**
 * @brief 清理AV通道重传缓冲区的线程函数
 * @details 独立线程用于执行缓冲区重置操作,避免在监控线程中直接调用导致阻塞
 * @param arg 传入需要清理缓冲区的avIndex(需通过指针传递int类型)
 */
void thread_CleanResendBuffer(void* arg) {
    // 解析入参:获取需要清理缓冲区的AV通道索引
    int avIndex = *(int*)arg;
    // 释放入参内存(假设调用方通过malloc分配,此处需对应释放避免泄漏)
    free(arg);

    // 重置AV通道缓冲区:
    // - RESET_ALL:重置所有类型的缓冲区(需确认枚举定义)
    // - 5000:超时时间(单位:毫秒),避免无限阻塞
    avServResetBuffer(avIndex, RESET_ALL, 5000);

    // 操作完成提示(可选,便于调试)
    printf("[Buffer Clean] avIndex[%d] buffer reset completed\n", avIndex);
    
    // 线程退出
    pthread_exit(NULL);
}

目前开启重传模式,设备端才会有缓存区,每个avIndex独享各自的缓存区,默认大小为256KB,通常建议值为1~2s的数据量。

如果设备端使用avServSetResendSize修改了设备端的缓存区,APP端建议也做调整,建议值为设备端缓存区大小的3~4倍(默认1MB)。

如何处理弱网传输?

弱网下,常见的处理措施如下:

  1. 降低码率
  2. 降低分辨率
  3. 清缓存区
  4. 丢P帧

码率如何设计?

SDK提供三种连线模式,分别为:

  • LAN:局域网
  • P2P:点对点
  • RLY:数据转发

因为LAN模式通常网络最干净,不易受波动,所以码率可以传比较大。而RLY模式需要服务器中转,所以消耗服务器的流量,建议降低码率。

在可以动态调整码率的情况下,可以在LAN或者P2P模式传比较高清的图像,RLY传标清或者流畅的图像。


相关头文件说明

头文件 说明
AVFRAMEINFO.h 帧信息定义
AVIOCTRLDEFs.h IO控制指令定义
TUTKGlobalAPIs.h 全局的设定
IOTCClient.h 客户端连线相关专用接口定义
IOTCCommon.h 通用接口,默认值,错误码定义
IOTCDevice.h 设备端连线相关专用接口定义
AVClient.h 客户端数据发送相关接口定义
AVCommon.h 通用接口,默认值,错误码定义
AVServer.h 设备端数据发送相关接口定义
VSaaS.h 云存储相关接口定义
DASA.h 码流自适应接口定义

其他文章:


即刻开启您的物联网之旅

联系解决方案专家
Kalay App
资讯安全白皮书
全球专利布局
解决方案
新闻动态
公司动态
行业资讯
媒体报道
永续发展
经营者的话
社会参与
环境永续
公司治理

+86 755 27702549

7×24小时服务热线

法律声明 隐私权条款

关注“TUTK”

TUTK服务尽在掌握

© 2022 星空无限传媒xk8027版权所有粤ICP备14023641号
在线咨询
扫一扫

TUTK服务尽在掌握

全国免费服务热线
+86 755 27702549

返回顶部