四、登錄、監(jiān)聽(tīng)和訪問(wèn)處理

圖:AV Server 工作流程示意圖
主要使用的API: IOTCAPIs+AVAPIs
(一)登錄
設(shè)備在上電后進(jìn)行IOTC_Device_LoginEx,如果失敗,需要重復(fù)調(diào)用,直到成功為止。此接口同時(shí)會(huì)開(kāi)啟局域網(wǎng)搜索功能,即使沒(méi)有l(wèi)ogin成功,客戶端也可以通過(guò)路由器下通過(guò)IOTC_Lan_Search等API搜索到,可以通過(guò)IOTC_Connect_ByUIDEx等API進(jìn)行連線。
login成功后,就不再需要調(diào)用IOTC_Device_LoginEx,可以繼續(xù)在此線程,調(diào)用IOTC_Get_LoginInfo來(lái)檢查設(shè)備端是否從服務(wù)器掉線。
// 設(shè)備登錄配置參數(shù)
DeviceLoginInput auth_option;
printf("thread_Login start\n");
// 在程序運(yùn)行狀態(tài)下循環(huán)嘗試登錄
while (gProcessRun) {
// 配置登錄參數(shù)
auth_option.cb = sizeof(DeviceLoginInput);
auth_option.authentication_type = AUTHENTICATE_BY_KEY;
memcpy(auth_option.auth_key, gIotcAuthkey, IOTC_AUTH_KEY_LENGTH);
// 嘗試設(shè)備登錄(使用帶密鑰的擴(kuò)展登錄函數(shù))
// 如不使用authkey,可替換為IOTC_Device_Login函數(shù)
int ret = IOTC_Device_LoginEx((char*)arg, &auth_option);
printf("IOTC_Device_LoginEx() ret = %d\n", ret);
// 登錄成功則退出循環(huán)
if (ret == IOTC_ER_NoERROR) {
break;
}
// 登錄失敗則處理錯(cuò)誤并重試
else {
PrintErrHandling(ret);
// 等待5秒后重試(可被程序退出信號(hào)中斷)
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}
}
1. 登錄狀態(tài)檢查(基于返回值判斷)
IOTC_Get_Login_Info用以檢查login狀態(tài),可以拿到兩個(gè)值。返回值表示 try login失敗的次數(shù),輸出參數(shù) login_state 為7表示正常在login,并且有收到P2P服務(wù)器回應(yīng)的login response。
/*
* IOTC_Get_Login_Info用以檢查login狀態(tài),可以拿到兩個(gè)值。
* 返回值表示 try login失敗的次數(shù)
* 輸出參數(shù) login_state 為7表示正常在login,并且有收到P2P服務(wù)器回應(yīng)的login response。
*/
unsigned int login_state;
int login_fail_count = 0;
// 循環(huán)檢查登錄狀態(tài)(程序運(yùn)行期間)
while (gProcessRun) {
// 獲取登錄狀態(tài)信息
int ret = IOTC_Get_Login_Info(&login_state);
printf("IOTC_Get_Login_Info() ret = %d, login_state = %u\n", ret, login_state);
// 處理獲取狀態(tài)失敗的情況
if (ret < IOTC_ER_NoERROR) {
break;
}
// 處理多次登錄重試失敗的情況(與服務(wù)器失聯(lián))
if (ret > 4) {
// 當(dāng)無(wú)用戶訪問(wèn)且網(wǎng)絡(luò)暢通時(shí)(網(wǎng)絡(luò)暢通可通過(guò)ping等方式檢測(cè))
if (/* 無(wú)用戶訪問(wèn)條件 */ && /* 網(wǎng)絡(luò)暢通條件 */) {
printf("device logout from p2p server, will call IOTC_Listen_Exit\n");
#ifdef _RESTART_P2P_MODULE_
// 重啟P2P模塊:調(diào)用后IOTC_Listen會(huì)返回IOTC_ER_EXIT_LISTEN
IOTC_Listen_Exit();
break;
#elif defined(_REINIT_SOCKET_)
// 僅重置底層socket,不重新登錄
IOTC_ReInitSocket(0);
continue;
#elif defined(_FORCE_LOGIN_)
// 重置socket并強(qiáng)制重新登錄(無(wú)需重新初始化)
IOTC_ReInitSocket(0);
IOTC_Device_Update_Authkey(device_authkey);
continue;
#endif
}
}
// 5秒后再次檢查(可被程序退出信號(hào)中斷)
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}
2. 登錄狀態(tài)檢查(基于login_state判斷)
unsigned int login_state;
int login_fail_count = 0;
// 循環(huán)檢查登錄狀態(tài)(程序運(yùn)行期間)
while (gProcessRun) {
// 獲取登錄狀態(tài)信息
int ret = IOTC_Get_Login_Info(&login_state);
printf("IOTC_Get_Login_Info() ret = %d, login_state = %u\n", ret, login_state);
// 處理獲取狀態(tài)失敗的情況
if (ret < IOTC_ER_NoERROR) {
break;
}
// 檢查登錄狀態(tài)(7表示與P2P服務(wù)器通信正常)
if (login_state == 7) {
// 狀態(tài)正常,重置失敗計(jì)數(shù)器
login_fail_count = 0;
} else {
// 狀態(tài)異常,遞增失敗計(jì)數(shù)器
if (++login_fail_count > 10) { // 超過(guò)10次檢查異常(約50秒)
// 當(dāng)無(wú)用戶訪問(wèn)且網(wǎng)絡(luò)暢通時(shí)(網(wǎng)絡(luò)暢通可通過(guò)ping等方式檢測(cè))
if (/* 無(wú)用戶訪問(wèn)條件 */ && /* 網(wǎng)絡(luò)暢通條件 */) {
printf("device logout from p2p server, will call IOTC_Listen_Exit\n");
#ifdef _RESTART_P2P_MODULE_
// 重啟P2P模塊:調(diào)用后IOTC_Listen會(huì)返回IOTC_ER_EXIT_LISTEN
IOTC_Listen_Exit();
break;
#elif defined(_REINIT_SOCKET_)
// 僅重置底層socket,不重新登錄
IOTC_ReInitSocket(0);
continue;
#elif defined(_FORCE_LOGIN_)
// 重置socket并強(qiáng)制重新登錄(無(wú)需重新初始化)
IOTC_ReInitSocket(0);
IOTC_Device_Update_Authkey(device_authkey);
continue;
#endif
}
}
}
// 5秒后再次檢查(可被程序退出信號(hào)中斷)
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}
提示:如遇到一些小運(yùn)營(yíng)商的網(wǎng)絡(luò)不穩(wěn)定的情況,建議使用4.3.6.x以上的SDK,并在初始化之前調(diào)用IOTC_Device_Setup_Network_Adaption(IOTC_DEVICE_ADAPTION_MODE_CHOOSE_BEST_UDP_SERVER_OR_SWITCH_TO_TCP)開(kāi)啟TCP自適應(yīng)。
(二)監(jiān)聽(tīng)
unsigned char need_reboot = 0;
// 主循環(huán):處理連接請(qǐng)求
while (gProcessRun) {
// 監(jiān)聽(tīng)連接請(qǐng)求(超時(shí)時(shí)間1000ms)
int SID = IOTC_Listen(1000);
if (SID < 0) {
// 處理監(jiān)聽(tīng)錯(cuò)誤
PrintErrHandling(SID);
// 超過(guò)最大會(huì)話數(shù)
if (SID == IOTC_ER_EXCEED_MAX_SESSION) {
#if TEST_FAST_RECOVERY
// 快速恢復(fù)模式:等待5秒檢查是否仍達(dá)最大在線人數(shù)
for (int i = 0; i < 5 && /* 在線人數(shù)已經(jīng)達(dá)到最大 */; i++) {
sleep(1);
}
#else
// 普通模式:等待5秒后重試
sleep(5);
#endif
continue;
}
// 監(jiān)聽(tīng)超時(shí)(正常情況,繼續(xù)監(jiān)聽(tīng))
else if (SID == AV_ER_TIMEOUT) {
continue;
}
// 退出監(jiān)聽(tīng)(需要重啟)
else if (SID == IOTC_ER_EXIT_LISTEN) {
need_reboot = 1;
break;
}
// 其他異常(可能需要重啟)
else {
// 程序異常處理邏輯
}
}
else {
// 成功獲取會(huì)話ID,創(chuàng)建線程處理AV服務(wù)(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); // 創(chuàng)建線程失敗時(shí)釋放內(nèi)存
} else {
pthread_detach(Thread_ID); // 分離線程,自動(dòng)釋放資源
gOnlineNum++; // 增加在線人數(shù)計(jì)數(shù)
}
}
}
// 處理重啟邏輯
if (need_reboot) {
// 停止所有數(shù)據(jù)收發(fā)
// 等待所有連接退出
// 等待其他AV/IOTC相關(guān)API退出
// 反初始化AV和IOTC模塊
avDeInitialize();
IOTC_DeInitialize();
// 跳轉(zhuǎn)到程序開(kāi)始處重新執(zhí)行
goto BEGIN;
}
(三)訪問(wèn)處理
1. 創(chuàng)建數(shù)據(jù)通道
// 初始化AV服務(wù)啟動(dòng)配置結(jié)構(gòu)體
AVServStartInConfig avStartInConfig;
AVServStartOutConfig avStartOutConfig;
// 清空輸入配置結(jié)構(gòu)體,避免臟數(shù)據(jù)
memset(&avStartInConfig, 0, sizeof(AVServStartInConfig));
// -------------------------- 配置輸入?yún)?shù)(核心參數(shù))--------------------------
// 結(jié)構(gòu)體大?。ü潭ㄙx值,用于SDK版本兼容)
avStartInConfig.cb = sizeof(AVServStartInConfig);
// 關(guān)聯(lián)的IOTC會(huì)話ID(由IOTC_Listen返回)
avStartInConfig.iotc_session_id = SID;
// 數(shù)據(jù)通道使用的IOTC通道ID(默認(rèn)0為主通道)
avStartInConfig.iotc_channel_id = 0;
// 通道創(chuàng)建超時(shí)時(shí)間(單位:秒)
avStartInConfig.timeout_sec = 30;
// 密碼驗(yàn)證回調(diào)函數(shù)(用于客戶端密碼校驗(yàn))
avStartInConfig.password_auth = ExPasswordAuthCallBackFn;
// 設(shè)備端功能限制(0表示不限制,具體參考SDK文檔的AV Module Service Type Definition)
avStartInConfig.server_type = 0;
// 重傳模式配置(1=開(kāi)啟重傳,2=關(guān)閉重傳)
avStartInConfig.resend = 1;
// 非必須回調(diào):修改密碼請(qǐng)求(按需實(shí)現(xiàn))
avStartInConfig.change_password_request = ExChangePasswordCallBackFn;
// 非必須回調(diào):設(shè)備能力查詢(按需實(shí)現(xiàn))
avStartInConfig.ability_request = ExAbilityRequestFn;
// -------------------------- 條件配置:Token認(rèn)證(按需啟用)--------------------------
#if ENABLE_TOKEN_AUTH
// 當(dāng)APP端選擇Token認(rèn)證時(shí),啟用以下回調(diào)
avStartInConfig.token_auth = ExTokenAuthCallBackFn; // Token驗(yàn)證回調(diào)
avStartInConfig.token_delete = ExTokenDeleteCallBackFn; // Token刪除回調(diào)
avStartInConfig.token_request = ExTokenRequestCallBackFn; // Token申請(qǐng)回調(diào)
avStartInConfig.identity_array_request = ExGetIdentityArrayCallBackFn; // 已存在ID查詢回調(diào)
#endif
// -------------------------- 條件配置:安全加密模式 --------------------------
#if ENABLE_DTLS
// 啟用DTLS加密(推薦,安全性更高)
avStartInConfig.security_mode = AV_SECURITY_DTLS;
#else
// 使用默認(rèn)SIMPLE加密(兼容性優(yōu)先)
avStartInConfig.security_mode = AV_SECURITY_SIMPLE;
#endif
// -------------------------- 配置輸出參數(shù) --------------------------
// 結(jié)構(gòu)體大小(固定賦值,用于接收SDK返回?cái)?shù)據(jù))
avStartOutConfig.cb = sizeof(AVServStartOutConfig);
// -------------------------- 創(chuàng)建AV數(shù)據(jù)通道 --------------------------
// avIndex:返回的AV數(shù)據(jù)通道索引,后續(xù)數(shù)據(jù)操作基于此索引
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);
// 啟動(dòng)失?。宏P(guān)閉對(duì)應(yīng)的IOTC會(huì)話,更新在線人數(shù),退出線程
IOTC_Session_Close(SID);
gOnlineNum--;
pthread_exit(0);
}
avServStartEx除了可以拿到avIndex,還可以拿到此通道相關(guān)的一些其他特性(不同版本會(huì)略有差異,以實(shí)際版本為準(zhǔn)),具體如下:
/**
* @brief AV服務(wù)啟動(dòng)輸出配置結(jié)構(gòu)體
* @details 用于接收 avServStartEx 接口啟動(dòng)AV服務(wù)后的返回信息,包含通道能力、認(rèn)證方式、用戶標(biāo)識(shí)等關(guān)鍵結(jié)果
*/
typedef struct AVServStartOutConfig {
uint32_t cb;
int32_t resend;//是否開(kāi)啟重傳
int32_t two_way_streaming;//是否支持全雙工
AvAuthType auth_type;//auth的類型
char account_or_identity[256];//自定義identity信息,如云存可能使用"vsaas"來(lái)標(biāo)識(shí)
} AVServStartOutConfig;
typedef AVServStartOutConfig * LPAVSERV_START_OUT_CONFIG;
提示:全雙工的通道,在進(jìn)行對(duì)講的時(shí)候,不需要建立新的avIndex。
2. 接收IO指令
// 循環(huán)接收并處理AV通道的IO控制指令
while (gProcessRun) {
// 接收IO控制指令:超時(shí)時(shí)間1000ms,緩沖區(qū)大小AV_MAX_IOCTRL_DATA_SIZE
int ret = avRecvIOCtrl(
avIndex,
&ioType,
(char*)ioCtrlBuf,
AV_MAX_IOCTRL_DATA_SIZE,
1000
);
// 處理接收結(jié)果
if (ret >= 0) {
// 成功接收指令:調(diào)用處理函數(shù)(支持圖像、聲音、對(duì)講、回放等業(yè)務(wù)邏輯)
// 注: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; // 錯(cuò)誤退出循環(huán),執(zhí)行資源清理
}
}
// -------------------------- 資源清理與連接關(guān)閉流程 --------------------------
// 1. 停止發(fā)送IO控制數(shù)據(jù)
// 2. 關(guān)閉音視頻發(fā)送(從音視頻客戶端列表中注銷)
unregedit_client_from_video(SID);
unregedit_client_from_audio(SID);
// 3. 關(guān)閉對(duì)講功能(需確保對(duì)應(yīng)資源已釋放)
// 4. 關(guān)閉回放功能(需確保對(duì)應(yīng)資源已釋放)
// 5. 關(guān)閉AV通道與IOTC會(huì)話,更新在線人數(shù)
printf("SID[%d], avIndex[%d], thread_ForAVServerStart exit!!\n", SID, avIndex);
// 關(guān)閉AV服務(wù)通道
avServStop(avIndex);
// 關(guān)閉IOTC會(huì)話連接
IOTC_Session_Close(SID);
// 減少在線人數(shù)計(jì)數(shù)
gOnlineNum--;
3. 發(fā)送視頻
// 循環(huán)發(fā)送視頻幀(程序運(yùn)行期間持續(xù)執(zhí)行)
while (gProcessRun) {
char* frame; // 視頻幀數(shù)據(jù)指針(當(dāng)前代碼未使用,可根據(jù)實(shí)際需求補(bǔ)充邏輯)
int size; // 視頻幀大?。ó?dāng)前代碼未使用,可根據(jù)實(shí)際需求補(bǔ)充邏輯)
// 1. 從編碼緩沖區(qū)取出圖像幀(需補(bǔ)充實(shí)際取幀邏輯,如:frame = get_encoded_video_frame(&size);)
// 2. 輪詢所有需要接收視頻的AV通道,逐個(gè)發(fā)送視頻幀
foreach (int _avIndex, avIndex_need_video) { // foreach:遍歷需視頻推送的avIndex列表
int avIndex = _avIndex; // 當(dāng)前待發(fā)送的AV通道索引
// 發(fā)送視頻幀數(shù)據(jù)(攜帶幀信息FRAMEINFO_t)
int ret = avSendFrameData(
avIndex,
videoBuf, // 視頻數(shù)據(jù)緩沖區(qū)(需確保數(shù)據(jù)有效)
videoSize, // 視頻數(shù)據(jù)大小
&frameInfo, // 視頻幀信息(如幀類型、分辨率等)
sizeof(FRAMEINFO_t) // 幀信息結(jié)構(gòu)體大小
);
// 處理發(fā)送結(jié)果
if (ret < 0) {
// 情況1:緩沖區(qū)溢出(幀丟失,需標(biāo)記后續(xù)發(fā)送關(guān)鍵幀避免花屏)
if (ret == AV_ER_EXCEED_MAX_SIZE) {
need_iframe_flag_by_avIndex = 1; // 標(biāo)記該avIndex需后續(xù)補(bǔ)發(fā)I幀
}
// 情況2:其他發(fā)送異常(停止向該avIndex推送視頻)
else {
unregedit_client_from_video(SID); // 從視頻推送列表中注銷該客戶端
continue; // 跳過(guò)當(dāng)前avIndex,處理下一個(gè)
}
}
}
}
4. 動(dòng)態(tài)碼率配置
如果需要加上動(dòng)態(tài)碼率,可以參考以下規(guī)則:
- 如果有多人在同時(shí)觀看,且大部分是P2P/RLY模式,降低碼率(或者分辨率);
- 如果有某個(gè)通道重傳使用率一直維持在較高水平,則降低碼率。
5. 對(duì)講實(shí)現(xiàn)