简体中文

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

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

更新日期: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);
	int ret = IOTC_Device_LoginEx((char *)arg, &auth_option); //如不使用authkey,则使用IOTC_Device_Login做login
	printf("IOTC_Device_LoginEx() ret = %d\n", ret);
	if(ret == IOTC_ER_NoERROR) {
		break;
	} else {
		PrintErrHandling (ret);
		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){//此时已经有一段时间与服务器失联了,ret表示retry login失败的次数。
		if(无用户访问 && 网络畅通){ //网络畅通可以通过ping或者其他方式做测试
			printf("device logout from p2p server,will call IOTC_Listen_Exit\n");
		#ifdef _RESTART_P2P_MODULE_
			IOTC_Listen_Exit(); //调用此API后,IOTC_Listen会返回IOTC_ER_EXIT_LISTEN;
			break;
		#elif defined(_REINIT_SOCKET_)
			IOTC_ReInitSocket(0);//只重置底层socket,不做login。不需要重新初始化。
			continue;
		#elif defined(_FORCE_LOGIN_)
			IOTC_ReInitSocket(0);//重置底层socket,不做login。
			IOTC_Device_Update_Authkey(device_authkey);//调用此API,将强制重新login。不需要重新初始化。
			continue;
		#endif
		}
	}
	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;
	} 
	if(login_state == 7){//与P2P服务器通信正常
		login_fail_count = 0;
	}
	else{
		if(login_fail_count++ > 10){//已经超过10次检查都是无法恢复,大概时间即10*5=50s
			if(无用户访问 && 网络畅通){ //网络畅通可以通过ping或者其他方式做测试
				printf("device logout from p2p server,will call IOTC_Listen_Exit\n");
		#ifdef _RESTART_P2P_MODULE_
			IOTC_Listen_Exit(); //调用此API后,IOTC_Listen会返回IOTC_ER_EXIT_LISTEN;
			break;
		#elif defined(_REINIT_SOCKET_)
			IOTC_ReInitSocket(0);//只重置底层socket,不做login。不需要重新初始化。
			continue;
		#elif defined(_FORCE_LOGIN_)
			IOTC_ReInitSocket(0);//重置底层socket,不做login。
			IOTC_Device_Update_Authkey(device_authkey);//调用此API,将强制重新login。不需要重新初始化。
			continue;
		#endif
			}
		}
	}
	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)
{
	// Accept connection only when IOTC_Listen() calling
	int SID = IOTC_Listen(1000);
	if (SID < 0) {
		PrintErrHandling (SID);
		if (SID == IOTC_ER_EXCEED_MAX_SESSION) {
#if TEST_FAST_RECOVERY
			for(int i = 0;i<5 && 在线人数已经达到最大;i++){
			   sleep(1);         
			}
#else
			  sleep(5);   
#end 
			continue;
		}
		else if(SID == AV_ER_TIMEOUT){
			continue;        
		}
		else if(SID == IOTC_ER_EXIT_LISTEN){
			need_reboot = 1; 
			break;                            
		}
		else{
			//程序异常,可能要重启        
		}
	}

	//因avServStart是阻塞型的接口,所以此处另外开线程去处理。
	int *sid = (int *)malloc(sizeof(int));
	*sid = SID;
	pthread_t Thread_ID;
	ret = pthread_create(&Thread_ID, NULL, &thread_ForAVServerStart, (void *)sid);
	if(ret < 0) {
		printf("pthread_create failed ret[%d]\n", ret);
	} else {
		pthread_detach(Thread_ID);
		gOnlineNum++;
	}
}
if(need_reboot){
	//停止所有数据收发
	//等待所有连接退出
	//等待其他跟AV/IOTC相关的API退出
	avDeInitialize();
	IOTC_DeInitialize();
	goto BEGIN; //重新执行程序
}

访问处理

创建数据通道:

AVServStartInConfig avStartInConfig;
AVServStartOutConfig avStartOutConfig;

memset(&avStartInConfig, 0, sizeof(AVServStartInConfig));
avStartInConfig.cb               = sizeof(AVServStartInConfig);
avStartInConfig.iotc_session_id  = SID; //此为IOTC_Listen返回的session id。
avStartInConfig.iotc_channel_id  = 0; //数据通道将使用的IOTC通道,默认以0为主通道
avStartInConfig.timeout_sec      = 30; //创建通道超时
avStartInConfig.password_auth    = ExPasswordAuthCallBackFn; //验证密码的回调
avStartInConfig.server_type      = 0; //设备端支持的功能,填0表示不限制,具体可以参考SDK Readme.html的AV Module Service Type Definition
avStartInConfig.resend           = 1; //是否支持重传模式,1为开启重传,2位关闭重传
avStartInConfig.change_password_request = ExChangePasswordCallBackFn; //修改密码的回调,非必须实现
avStartInConfig.ability_request  = ExAbilityRequestFn;//设备支持的功能的回调,非必须实现

#if ENABLE_TOKEN_AUTH //使用token验证的方式,如果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
	avStartInConfig.security_mode = AV_SECURITY_DTLS; //使用DTLS加密,建议
#else
	avStartInConfig.security_mode = AV_SECURITY_SIMPLE; //使用默认加密
#endif

avStartOutConfig.cb              = sizeof(AVServStartOutConfig);

int avIndex = avServStartEx(&avStartInConfig, &avStartOutConfig); 
//此avIndex为数据通道Index,后续的数据参数,将直接在avIndex上进行。
if(avIndex < 0)
{
	printf("avServStartEx failed!! SID[%d] code[%d]!!!\n", SID, avIndex);
	printf("thread_ForAVServerStart: exit!! SID[%d]\n", SID);
	IOTC_Session_Close(SID);
	gOnlineNum--;
	pthread_exit(0);
}

avServStartEx除了可以拿到avIndex,还可以拿到此通道相关的一些其他特性,具体可以参考:

typedef struct AVServStartOutConfig {
	uint32_t cb;
	int32_t resend; //avIndex是否支持重传
	int32_t two_way_streaming;//avIndex是否全双工
	AvAuthType auth_type;//是密码验证还是token验证
	char account_or_identity[256];//用户ID,app端在avClientStartEx里面填入
} AVServStartOutConfig;
typedef AVServStartOutConfig * LPAVSERV_START_OUT_CONFIG;

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

接收IO指令:

while(gProcessRun)
{
	ret = avRecvIOCtrl(avIndex, &ioType, (char *)ioCtrlBuf, AV_MAX_IOCTRL_DATA_SIZE, 1000);
	if(ret >= 0)
	{
		//io指令的定义,可以参考SDK Readme.html的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,关闭通道和连接
	printf("SID[%d], avIndex[%d], thread_ForAVServerStart exit!!\n", SID, avIndex);
	avServStop(avIndex);
	IOTC_Session_Close(SID);
	gOnlineNum--;

发送视频:

while(gProcessRun){
	char* frame;
	int size;
	//从编码缓存区取出图像帧
	//轮询所有的avIndex,往需要送图像的avIndex送图像。
	foreach(int _avIndex,avIndex_need_video){
		int avIndex = _avIndex;
		int ret = avSendFrameData(avIndex, videoBuf, videoSize, &frameInfo, sizeof(FRAMEINFO_t));
		if(ret<0){
			if(ret == AV_ER_EXCEED_MAX_SIZE){//缓存区溢出,该帧丢失,需要关键帧,不然会花屏
				need_iframe_flag_by_avIndex = 1;            
			} 
			else{
			   unregedit_client_from_video(SID);//其他异常,不再往此avIndex发送图像。
			   continue; 
			}
		}
	}
}

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

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

参考代码:

array_int avIndex_video_array_need_keyFrame;//队列或者链表,用以存放需要key frame的avIndex,需自己实现
while(gProcessRun){
	char* frame;
	int size;
	//从编码缓存区取出图像帧
	//轮询所有的avIndex,往需要送图像的avIndex送图像。
	foreach(int _avIndex,avIndex_array_need_video){
		int avIndex = _avIndex;
		//检查此avIndex是否需要关键帧
		if(frameInfo.flag == IPC_FRAME_FLAG_IFRAME){
			if(avIndex_array_need_video_keyFrame.contains(avIndex)){
				//关键帧,清楚标志
			   avIndex_video_array_need_keyFrame.remove(avIndex);     
			}
		}
		else{
			if(avIndex_video_array_need_keyFrame.contains(avIndex)){
				//非关键帧,继续等待,或者强制编码I帧。
				continue;
			}      
		}
		
		int ret = avSendFrameData(avIndex, videoBuf, videoSize, &frameInfo, sizeof(FRAMEINFO_t));
		if(ret<0){
			if(ret == AV_ER_EXCEED_MAX_SIZE){//缓存区溢出,该帧丢失,需要关键帧,不然会花屏
				avIndex_video_array_need_keyFrame.append(avIndex);         
			} 
			else{
			   unregedit_client_from_video(SID);//其他异常,不再往此avIndex发送图像。
			   continue; 
			}
		}
	}
}

对讲:

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


常见的问题以及处理

如何监测传输状况?

//建议另外一个线程定时循环检查。
void thread_avCheckBufferUsageRate(void* arg){
	while(programRunning){
		//遍历所有正在发送的通道
		foreach(int avIndex,all_index_in_send_queue){
			float rate = avResendBufUsageRate(avIndex);
			if(rate <= 0.0){
				printf("传输顺畅\n");
			}
			else if(rate > 0.03 && rate < 0.7){
				printf("数据在累积中\n");
			}
			else if(rate > 0.7){
				printf("数据在累积较多,可能导致缓存区溢出\n");
				//考虑清缓存,建议放其他线程。
				//考虑降低分辨率。
				//考虑降低fps。
			}
		}
	}
	sleep(1);
}

//清空缓存区

void thread_CleanResendBuffer(void*arg){
	int avIndex = *(int*)arg;
	avServResetBuffer(avIndex,RESET_ALL,5000);
}

目前开启重传模式,设备端才会有缓存区,每个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 物联智慧科技(深圳)有限公司版权所有粤ICP备14023641号
在线咨询
扫一扫

TUTK服务尽在掌握

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

返回顶部