基于TCP/IP协议的网络摄像头的QT项目

这篇具有很好参考价值的文章主要介绍了基于TCP/IP协议的网络摄像头的QT项目。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

项目简述:

1.服务器

 步骤一:首先搭建一个基本的服务器框架。

 1.初始化服务器的函数主体

 2.等待连接

步骤二:数据库的使用,本次项目使用的Sqlite3数据库

1.数据库初始化

2.登录时使用数据库 

3.注册时使用数据库

步骤三:摄像头的调用与数据传输

1.V4L2框架的使用

2.实现图像的发送

步骤四:实现服务器的并发

2.客户端

步骤一:实现登录

步骤二:实现服务器采集的图像的显示

1.准备

2.实现对应的槽函数

3.运行效果



项目简述:

        本项目是基于TCP/IP协议进行开发,使用QT写了一个客户端,在Ubuntu上使用C语言写了一个TCP服务器,同时依仗V4L2框架来调用本机的摄像头,将摄像头采集到的数据传输至QT客户端,在客户端的界面上显示。同时出于项目完整性和实用性考虑,笔者还在客户端添加了登录界面,服务器也写了对应的匹配机制,用户可以注册,登录本系统,此外还添加了一个天气查询系统,方便用户能实时获取天气情况。接下来笔者就分版块讲解一下本项目的构成。

        首先我们先从服务器讲起,首先本项需要使用TCP/IP协议,所以Ubuntu需要保持网络通畅,至于网络的配置,笔者就不再赘述,可以自行度娘。 

1.服务器

 步骤一:首先搭建一个基本的服务器框架。

 1.初始化服务器的函数主体

//服务器初始化*************************************************************************
int tcpinit(int port)
{
    int sockfd  = socket(AF_INET, SOCK_STREAM, 0);        //创建套接字
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }
       
    printf("套接字创建成功----------\n"); 
    
    //设置套接字允许端口重用 
    int on = 1; //定义int整形变量,保存端口重用的开关,1:开 0:关
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
        perror("setsockopt");
        return -1;
    }
      
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(port);
    seraddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    if(bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) < 0)    //绑定本机ip和端口号
    {
        perror("bind");
        return -1;
    }

    printf("绑定成功----------------\n");

    if(listen(sockfd, 1024) < 0)    //监听
    {
        perror("listen");
        return -1;
    }
    printf("监听成功----------------\n");
    return sockfd;
}

 2.等待连接

//tcp等待连接**************************************************************************
int tcpwait(int conn_fd)
{	
		struct sockaddr_in cliaddr;
    	socklen_t len = sizeof(cliaddr);
        connfd = accept(conn_fd, (struct sockaddr *)&cliaddr, &len);    //等待连接,每当有一个客户端连接时,都会生成一个新的连接套接字
        if(connfd < 0)
        {
            perror("accept");
            return -1;
        }

        printf("ip:%s--port:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
        return connfd;
}

        按正确的创建顺序依次调用这些函数就能实现一个基本的服务器搭建了。

步骤二:数据库的使用,本次项目使用的Sqlite3数据库

1.数据库初始化

//数据库****************************************************************************
int sqliteInit()
{
	//1、打开或者创建数据库文件,得到数据库的句柄指针
	
	// SQLITE_OK == 0
	if( sqlite3_open("./yy.db", &db) != SQLITE_OK)
	{
		printf("open: %s\n", sqlite3_errmsg(db));
		return -1;
	}
	//创建数据库表
	char sql[128] = "create table if not exists user(账号 varchar , 密码 varchar);";
	char *errmsg = NULL;
	//执行SQL 语句
	if( sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){
		printf("create: %s\n", errmsg);
		return -1;
	}
}

        首先要打开或者创建数据库文件,要得到数据库的句柄指针,在示例中笔者并未写出,其实是写在了头文件中,因为在客户端与服务器登录过程的数据交互中,要频繁的调用数据库,而数据库的唯一识别特征就是句柄指针,将句柄指针放于头文件中可使得所有需要使用数据库的文件访问句柄指针。执行sqlite3_open函数后会在服务器本地创建一个数据库文件,笔者取名为yy.db。然后紧接着执行sql语句,在数据库中创建一个名为user的表用于存放客户端注册的用户数据。数据库初始化完成后就可以正常使用数据库了。

2.登录时使用数据库 

int login(int connfd)
{
	char *errmsg = NULL;
    char **result;
    int R = 0;
    int C = 0;
    char lgbuf[32] = {0};
	struct Info info;
	
	read(connfd, lgbuf, sizeof(lgbuf));        	//接受消息,应该接收到账号密码
	
	sscanf(lgbuf, "%s %s", info.id, info.pw);
	printf("id= %s\n",info.id);
	printf("pw= %s\n",info.pw);
	printf("log\n");
	
	char sql[128] = "select *from user;";
	if(sqlite3_get_table(db,sql,&result,&R,&C,&errmsg) != SQLITE_OK)
    {
        printf("gettable:%s\n",errmsg);
        return -1;
    }
    int k = C;
    int m;
    int flag = 1;
    
    for(int i = 0;i < R;i++)
    {
        if(strcmp(result[k],info.id) == 0)
        {
            printf("user:%s\n",result[k]);
            printf("user right\n");

            m = k + 1;    
            if(strcmp(result[m],info.pw) == 0)
            {
                printf("pwd:%s\n",result[m]);
                printf("pswd right\n");
                
                data[3] = 0x00;
                write(connfd, data, 32);   //发送给客户端判断用户名密码是否正确
                
                flag = 0;
                return 2;
            }
            else
            {
            	data[3] = 0x02;
               	if(write(connfd, data, 32) < 0)
				{
					printf("login fail\n");
					continue;
				} 
            }
        }
        k += 2;    //向后偏移一行数据
    }
    printf("flag = %d\n",flag);

    if(flag == 1)
    {
    	data[3] = 0x01;
        if(write(connfd, data, 32) < 0)
		{
			printf("进入注册\n");		//发送给客户端判断是否需要注册或者用户名错误
		}	 
		return 1;
    }

}

        首先,当客户端与服务器成功连接时,客户端就发送一个名为登录的信号做判断,进而使得服务器这端成功进入登录函数中,登录函数中最重要的部分就是在数据库中查询用户数据,要查询用户数据就要执行查询的sql语句,将说有结果查出,使用sqlite3_get_table()函数可以将查询带的之返回出来,并放入了result中进行保存,再将其中的值与接收到的账号进行比对,如果账号正确才能进行下一步,也就是密码的比对工作。值得注意的是,sqlite3数据库存储数据,第一行是字段名,所以遍历时应从第二行开始,并且是现将每行的数据存满后才存储下一行,笔者只设置了两个字段,所以在循环遍历时,需要将位置向后偏移两个位置才能访问到下一个对应字段的数据。如果在遍历账户时就未找到数据,那就返回一个值,用于服务器做决策判断,并向客户端发送一个信息,告诉客户端没有该用户。

3.注册时使用数据库

//注册过程**********************************************************************
int regist(int connfd)
{
	struct Info info;
	char buf[32] = {0};
	char sql1[128] = {0};
    char *errmsg1 = NULL;
    char **result;
    char *errmsg2 = NULL;
    int R = 0;
    int C = 0;
    int i;
    char sql2[128] = "select * from user;";
    while(1)
    {
		read(connfd, buf, sizeof(buf));        	//接受消息,应该接收到需要注册的账号密码
		
		sscanf(buf, "%s %s", info.id, info.pw);
		printf("id= %s\n",info.id);
		printf("pw= %s\n",info.pw);
		
		printf("nuser: %s\n",info.id);
		printf("npwd : %s\n",info.pw);
		if(sqlite3_get_table(db,sql2,&result,&R,&C,&errmsg1) != SQLITE_OK)
		{
		    printf("gettable:%s\n",errmsg1);
		    return -1;
		}
		int k = C;
		printf("R*C = %d\n",R*C);
		int flag = 1;

		for(i = 0;i < R;i++)
		{
		    if(strcmp(result[k],info.id) == 0)
		    {   
		    	data[3] = 0x05;
		        write(connfd,data,32);  //发送给客户端判断用户是否存在
		        flag = 0;
		    }
		    k += 2;
		}
		if(flag == 0)
		{
			return 3;
			break;
		}
		
		if(flag == 1)
		{
		    sprintf(sql1,"insert into user(账号, 密码)values('%s','%s');",info.id, info.pw);
		    if(sqlite3_exec(db,sql1,NULL,NULL,&errmsg2) != SQLITE_OK)
		    {
		        printf("insert:%s\n",sqlite3_errmsg(db));
		        return -1;
		    }
			data[3] = 0x06;
		    write(connfd,data,32);  //发送给客户端判断是否注册成功
		    return 3;
		}
    }
	printf("regist\n");
}

        在登陆过程中数据库未检测到用户账号信息后,客户端就该进行注册操作了,此时服务器任将首先接收到客户端的账户信息,步骤和登陆过程是基本一样,但是不同的是,在遍历完账号信息以后,如果发现待注册账号与数据库本身已有账号相同时,发送一个信息给客户端,告诉客户端此账户已存在,如果确定是未注册过的账户就可以进行注册,也就是执行插入信息的sql语句,插入完成后发送一个消息告诉客户端注册成功。

步骤三:摄像头的调用与数据传输

1.V4L2框架的使用

int camera_init(char *devpath, unsigned int *width, unsigned int *height, unsigned int *size)
{
	int i;
	int fd = -1;;
	int ret;
	struct v4l2_buffer vbuf;
	struct v4l2_format format;
	struct v4l2_capability capability;
	/*open 打开设备文件*/
	if((fd = open(devpath, O_RDWR)) == -1){
        perror("camera_init open");
		return -1;		
	}
	/*ioctl 查看支持的驱动*/
	ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
	if (ret == -1) {
		perror("camera->init");
		return -1;
	}
	/*判断设备是否支持视频采集*/
	if(!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
		fprintf(stderr, "camera->init: device can not support V4L2_CAP_VIDEO_CAPTURE\n");
		close(fd);
		return -1;
	}
	/*判断设备是否支持视频流采集*/
	if(!(capability.capabilities & V4L2_CAP_STREAMING)) {
		fprintf(stderr, "camera->init: device can not support V4L2_CAP_STREAMING\n");
		close(fd);
		return -1;
	}
	/*设置捕获的视频格式 MYJPEG*/
	memset(&format, 0, sizeof(format));
	format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;			    //永远都是这个类型
	format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;		//设置采集图片的格式
	format.fmt.pix.width = *width;
	format.fmt.pix.height = *height;
	format.fmt.pix.field = V4L2_FIELD_ANY;				//设置图片一行一行的采集
	ret = ioctl(fd, VIDIOC_S_FMT, &format);				//ioctl	是设置生效
	if(ret == -1)
		perror("camera init");
	else {
		fprintf(stdout, "camera->init: picture format is mjpeg\n");
	}

	ret = ioctl(fd, VIDIOC_G_FMT, &format);
	if (ret == -1) {
		perror("camera init");
		return -1;
	}

	/*向驱动申请缓存*/
	memset(&reqbufs, 0, sizeof(struct v4l2_requestbuffers));
	reqbufs.count	= REQBUFS_COUNT;					//缓存区个数
	reqbufs.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbufs.memory	= V4L2_MEMORY_MMAP;					//设置操作申请缓存的方式:映射 MMAP
	ret = ioctl(fd, VIDIOC_REQBUFS, &reqbufs);			
	if (ret == -1) {	
		perror("camera init");
		close(fd);
		return -1;
	}
	/*循环映射并入队*/
	for (i = 0; i < reqbufs.count; i++){
		/*真正获取缓存的地址大小*/
		memset(&vbuf, 0, sizeof(struct v4l2_buffer));
		vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		vbuf.memory = V4L2_MEMORY_MMAP;
		vbuf.index = i;
		ret = ioctl(fd, VIDIOC_QUERYBUF, &vbuf);
		if (ret == -1) {
			perror("camera init");
			close(fd);
			return -1;
		}
		/*映射缓存到用户空间,通过mmap讲内核的缓存地址映射到用户空间,并切和文件描述符fd相关联*/
		bufs[i].length = vbuf.length;
		bufs[i].start = mmap(NULL, vbuf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, vbuf.m.offset);
		if (bufs[i].start == MAP_FAILED) {
			perror("camera init");
			close(fd);
			return -1;
		}
		/*每次映射都会入队,放入缓冲队列*/
		vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		vbuf.memory = V4L2_MEMORY_MMAP;
		ret = ioctl(fd, VIDIOC_QBUF, &vbuf);
		if (ret == -1) {
			perror("camera init");
			close(fd);
			return -1;
		}
	}
	/*返回真正设置成功的宽.高.大小*/
	*width = format.fmt.pix.width;
	*height = format.fmt.pix.height;
	*size = bufs[0].length;

	return fd;
}


int camera_start(int fd)
{
	int ret;

	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	/*ioctl控制摄像头开始采集*/
	ret = ioctl(fd, VIDIOC_STREAMON, &type);
	if (ret == -1) {
		perror("camera->start");
		return -1;
	}
	fprintf(stdout, "camera->start: start capture\n");

	return 0;
}

int camera_dqbuf(int fd, void **buf, unsigned int *size, unsigned int *index)
{
	int ret;
	fd_set fds;
	struct timeval timeout;
	struct v4l2_buffer vbuf;

	while (1) {
		FD_ZERO(&fds);
		FD_SET(fd, &fds);
		timeout.tv_sec = 2;
		timeout.tv_usec = 0;
		ret = select(fd + 1, &fds, NULL, NULL, &timeout);	//使用select机制,保证fd有图片的时候才能出对
		if (ret == -1) {
			perror("camera->dbytesusedqbuf");
			if (errno == EINTR)
				continue;
			else
				return -1;
		} else if (ret == 0) {
			fprintf(stderr, "camera->dqbuf: dequeue buffer timeout\n");
			return -1;
		} else {
			vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			vbuf.memory = V4L2_MEMORY_MMAP;
			ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);	//出队,也就是从用户空间取出图片
			if (ret == -1) {
				perror("camera->dqbuf");
				return -1;
			}
			*buf = bufs[vbuf.index].start;
			*size = vbuf.bytesused;
			*index = vbuf.index;

			return 0;
		}
	}
}

int camera_eqbuf(int fd, unsigned int index)
{
	int ret;
	struct v4l2_buffer vbuf;

	vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vbuf.memory = V4L2_MEMORY_MMAP;
	vbuf.index = index;
	ret = ioctl(fd, VIDIOC_QBUF, &vbuf);		//入队
	if (ret == -1) {
		perror("camera->eqbuf");
		return -1;
	}

	return 0;
}

int camera_stop(int fd)
{
	int ret;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
	if (ret == -1) {
		perror("camera->stop");
		return -1;
	}
	fprintf(stdout, "camera->stop: stop capture\n");

	return 0;
}

int camera_exit(int fd)
{
	int i;
	int ret;
	struct v4l2_buffer vbuf;
	vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vbuf.memory = V4L2_MEMORY_MMAP;
	for (i = 0; i < reqbufs.count; i++) {
		ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);
		if (ret == -1)
			break;
	}
	for (i = 0; i < reqbufs.count; i++)
		munmap(bufs[i].start, bufs[i].length);
	fprintf(stdout, "camera->exit: camera exit\n");
	return close(fd);
}

       V4L2 是专门为 linux 设备设计的一套视频框架,其主体框架在 linux 内核,可以理解为是整个 linux 系统上面的视频源捕获驱动框架。其广泛应用在嵌入式设备以及移动端、个人电脑设备上面,市面上的编码产品类如:SDV、手机、IPC、行车记录仪都会用到这个框架来进行视频采集。此次项目借用了这样一个完整的代码文件,主要是将采集到的图像进行了入队出队的操作,笔者能力有限,如果想深入了解V4L2框架请自行度娘。

//摄像头************************************************************
int cam_init()
{
	int cam_fd = camera_init("/dev/video0", &width, &height, &s);
	if(0 > cam_fd)
	{
		perror("cam_fd");
		return -1;
	}
	printf("w:%d\nh:%d\nsize:%d\n",width,height,s);
	
	if(0 > camera_start(cam_fd))
	{
		perror("cam_start");
		return -1;
	}
	return cam_fd;
}

int pic_send(int camfd ,int fd)
{		
	char *buf;
	int len;
	char lenstr[20]={0};

	camera_dqbuf(camfd,(void **)&buf,&len, &index1);
	
	sprintf(lenstr,"%d",len);
	
	write(fd,lenstr,sizeof(lenstr));

	int ret = write(fd,buf,len);
	if(ret < ret)
	{
		perror("camwrite");
	}
	//printf("len = %d ret = %d\n", len, ret);
	camera_eqbuf(camfd, index1);
}

        为了方便理解切需要与客户端通信,笔者在这里定义两个函数,通过调用框架中的初始化设置函数来初始化摄像头,最后返回一个关于摄像头的文件描述符。其次还定义了一个函数用于图像的发送,依旧是带用了框架中图片的入队出队函数实现发送图像至客户端。

2.实现图像的发送

int run_pic_send(int arg)
{
	int connfd = arg;
	char buf[4];
	int camfd = cam_init();
	while(1)
	{
		if(camflag == 0)
		{
			break;
		}
		else
		{
			pic_send(camfd,connfd);
		}
	}
	camera_stop(camfd);
	camera_exit(camfd);
}
   

      其实就一个函数,在函数被调用时初始化摄像头,循环的发送队列中的图片,如果客户端发出关闭摄像头的信息,服务器将关闭摄像头。

步骤四:实现服务器的并发

void *getData(void *arg)
{
    char getbuf[32] = {0};
    connfd = *(int *)arg;
    while(1)
    {
        int ret = read(connfd, getbuf, sizeof(getbuf));		//接收消息,应该接收到“Login”
        printf("getbuf: %s\n", getbuf);
        if(ret < 0)
        {   
            perror("read");
            break;
        }
        
       	if(ret == 0)
        {
            printf("client quit------------\n");
            break;
        }
        
        if(NULL != strstr(getbuf, "Register"))
        {
        	printf("注册\n");
        	int ret_reg = regist(connfd);
        	printf("ret_reg= %d\n",ret_reg);		//调用注册函数 
        	if(ret_reg == 3){
        		continue;
        	}
        }
        
        if(NULL != strstr(getbuf, "Login"))
        {
        	int ret_login = login(connfd);	// 调用登陆函数
        	
        	printf("ret_login=%d\n", ret_login);
        	
        	if(ret_login == 1)
        	{	
        		memset(getbuf, 0, sizeof(getbuf));
        		continue;	
        	}
        }
		
		if(NULL != strstr(getbuf, "OPEN_CAM"))
		{
			camflag = 1;
			int ret_cam_exit = run_pic_send(connfd);
			if(ret_cam_exit == 4)
			{
				continue;
			}
			memset(getbuf, 0, sizeof(getbuf));
		}
		
		if(NULL != strstr(getbuf, "CLOSE_CAM"))
		{
			camflag = 0;
			memset(getbuf, 0, sizeof(getbuf));
			continue;
		}
    	printf("@@@@@@@@@\n");
    }   
    close(connfd);
    //退出子线程 
    pthread_exit(NULL);
}

        本次项目笔者使用的是创建线程来实现服务器的并发,所以我们需要创建一个函数来调用其他的所有功能函数,将这个函数作为主体函数传入线程创建函数中,这样就可以实现服务器的并发。

2.客户端

步骤一:实现登录

基于TCP/IP协议的网络摄像头的QT项目

         首先我们可以在ui拖拽界面上先快速搭建一个登录界面的主要框架,两个行编辑框用于显示账号密码,两个普通按钮用于触发相应功能的槽函数。

    w2 = NULL;
    this->setFixedSize(this->width(), this->height());
    this->setWindowTitle("登录:)");
    sockfd = new QTcpSocket(this);
    sockfd->connectToHost("192.168.219.148", 8081);
    sockfd->waitForConnected(2000);
    if(sockfd->state() == QAbstractSocket::UnconnectedState)
    {
        qDebug() << "连接服务器失败!请查看网络";
        return;
    }
    else
    {
        qDebug() << "连接成功!!!!!";
        connect(ui->LoginButton, SIGNAL(clicked()), this, SLOT(LoginHandle()));
        connect(sockfd, SIGNAL(readyRead()), this, SLOT(RecvHandle()));
    }

        首先创建套接字,连接服务器ip和端口,再触发对应的条件时,打印对应的语句,在登陆成功后调用槽函数。

void Widget::LoginHandle()
{
    QString user = ui->UserEdit->text();
    QString pw = ui->PwEdit->text();
    QString sign = "Login";
    sockfd->write(sign.toStdString().c_str(), 32);
    QString send_info = user+" "+pw;
    sockfd->write(send_info.toStdString().c_str(), 32);
    send_info.clear();
    flag = 0;
}

        然后就是实现登录时的数据交互,这里我们写一个槽函数,我们主要做的就是将行编辑框中的信息发送给服务器,用sockfd这个类中的write成员函数实现发送。


void Widget::RecvHandle()
{
        //点击登录时flag= 0
        if(!flag)
        {
            QByteArray data =  sockfd->readAll();
            qDebug()<<data;
            {
                if((uchar)data[3] == 0x00)
                {
                    this->hide();
                    w2 = new Form();
                    w2->show();

                    flag=1;
                    return;
                }
                else if((uchar)data[3] == 0x01)
                {
                    QMessageBox::warning(this, "提示", "没有该用户,请注册!");
                }
                else if((uchar)data[3] == 0x02)
                {
                    QMessageBox::warning(this, "提示", "账号或密码错误!");
                }
                data.clear();
            }
        }
        else
        {
            //点击注册时,flag = 1
            QByteArray data = sockfd->readAll();
            if((uchar)data[0] == 0xff)
            {
                if((uchar)data[3] == 0x05)
                {
                    QMessageBox::warning(this, "提示", "用户已存在,请重新输入!");
                }
                else if((uchar)data[3] == 0x06)
                {
                    QMessageBox::warning(this, "提示", "注册成功!");
                    flag1 = 1;
                }
            }
            data.clear();
        }
}

        当服务器接收到账号密码后回发信号用作客户端的判断,当登陆成功以后将登录界面隐藏,然后将第二个界面显示出来,这样就实现了简单的登录功能。

步骤二:实现服务器采集的图像的显示

1.准备

基于TCP/IP协议的网络摄像头的QT项目

        首先依旧是通过拖拽搭建一个界面,使用lable标签显示摄像头画面。

    manager = new QNetworkAccessManager(this);  //新建QNetworkAccessManager对象

    camfd = new QTcpSocket(this);
    camfd->connectToHost("192.168.1.21", 8081);
    camfd->waitForConnected(2000);

    if(camfd->state() == QAbstractSocket::UnconnectedState)
    {
        qDebug() << "连接服务器失败!请查看网络";
        return;
    }else{
        qDebug() << "连接成功!!!!!";
    }
        connect(camfd, SIGNAL(readyRead()), this, SLOT(myrecv()));
        connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));//关联信号和槽
        connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(checkW()));//关联信号和槽
        //一旦服务器有消息发送过来,就会触发readyRead()信号 --> myrecv();

         先把需要的对象new出来,因为我们需要查询天气,所以首先new一个manager,用于查询天气,在new一个连接套接字,当连接套接字读取到信息就发出信号,调用槽函数myrecv(),当manager连接完成后发送一个信号,调用replyFinished()槽函数,当按钮点击触发chekw函数。

2.实现对应的槽函数

void Form::myrecv()
{
    if(flag)
    {
        if(camfd->bytesAvailable() < 20){
            return;
        }
        camfd->read(arr,20);
        len = atoi(arr);
        flag = 0;
       // qDebug() << "len" << len;
    }
    else
    {
        if(camfd->bytesAvailable() < len){
            return ;
        }
        int ret = camfd->read(buf,len);
        if(ret <= 0)
            return ;
       // qDebug()<<"ret = "<<ret;
        QPixmap pix;
        pix.loadFromData((uchar *)buf,len,"jpg");
        ui->label_image->setPixmap(pix);
        flag = 1;
    }
}

        接收到摄像头的图片数据,再将其放进一个lable进行显示,在此之前会首先接收到服务发送的标志位,当标志位大于20时,客户端才能正常的显示画面。

void Form::checkW() //点击查询请求天气数据
{
    QString local_city = ui->city->text().trimmed(); //获得需要查询天气的城市名称
    char quest_array[256] = "http://wthrcdn.etouch.cn/weather_mini?city=";
    QNetworkRequest quest;
    sprintf(quest_array, "%s%s", quest_array, local_city.toUtf8().data());
    quest.setUrl(QUrl(quest_array));
    quest.setHeader(QNetworkRequest::UserAgentHeader, "RT-Thread ART");
    /*发送get网络请求*/
    manager->get(quest);
}

void Form::replyFinished(QNetworkReply *reply)  //天气数据处理槽函数
{
    qDebug() << "recv weather data!!";
    QString all = reply->readAll();

    //ui->textBrowser->setText(all); //将接收到的数据显示出来

    QJsonParseError err;
    QJsonDocument json_recv = QJsonDocument::fromJson(all.toUtf8(), &err);//解析json对象
    qDebug() << err.error;
    if (!json_recv.isNull())
    {
        QJsonObject object = json_recv.object();

        if (object.contains("data"))
        {
            QJsonValue value = object.value("data");  // 获取指定 key 对应的 value
            if (value.isObject())
            {
                QJsonObject object_data = value.toObject();
                if (object_data.contains("forecast"))
                {
                    QJsonValue value = object_data.value("forecast");
                    if (value.isArray())
                    {
                        QJsonObject today_weather = value.toArray().at(0).toObject();
                        weather_type = today_weather.value("type").toString();

                        QString recommend = object.value("data").toObject().value("ganmao").toString();
                        QString low = today_weather.value("low").toString();
                        QString high = today_weather.value("high").toString();
                        tmp = low.mid(low.length() - 3, 4) + "~" + high.mid(high.length() - 3, 4);
                        QString strength = today_weather.value("fengli").toString();
                        strength.remove(0, 8);
                        strength.remove(strength.length() - 2, 2);
                        wind = today_weather.value("fengxiang").toString() + strength;
                        ui->weather->setText(weather_type); //显示天气类型
                        ui->tmp->setText(tmp);   //显示温度
                        ui->wind->setText(wind); //显示风力
                        ui->rec->setText(recommend);
                    }
                }
            }
        }

    }
    else
    {
        qDebug() << "json_recv is NULL or is not a object !!";
    }
    reply->deleteLater(); //销毁请求对象
}

        紧接着是天气查询的槽函数的实现,首先是向对应的查询网址发送一个查询请求,当接受到查询请求之后会回传一个数据包,着这个数据包是json格式的,按照对应的格式进行数据的提取,再将其显示在对应的行编辑框中,这样就可以实现天气查询的功能了。

3.运行效果

基于TCP/IP协议的网络摄像头的QT项目

 这是登录界面效果,很有感觉。

基于TCP/IP协议的网络摄像头的QT项目

这是主界面效果 ,就很有趣。文章来源地址https://www.toymoban.com/news/detail-435069.html

到了这里,关于基于TCP/IP协议的网络摄像头的QT项目的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 毕设项目——基于Qt、PYTHON智能校园防御系统应用程序,实现了摄像头数据采集、人脸识别、口罩识别、 数据统计等功能

    完整项目地址:https://download.csdn.net/download/lijunhcn/88453470 项目结构 环境选型 语言:Python 操作系统:Windows 数据库:MySQL 窗口界面:PyQT API接口:百度AI接口,用以实现人脸登陆与注册 远程MySQL表结构 远程表结构sql脚本 项目背景 智能校园防御软件是实现了一款基于摄像头数据

    2024年02月04日
    浏览(44)
  • Qt音视频开发42-网络推流(视频推流/本地摄像头推流/桌面推流/网络摄像头转发推流等)

    上次实现的文件推流,尽管优点很多,但是只能对现在存在的生成好的音视频文件推流,而现在更多的场景是需要将实时的视频流重新推流分发,用户在很多设备比如手机/平板/网页/电脑/服务器上观看,这样就可以很方便的将分散的视频流统一集中的流媒体服务器上,然后统

    2024年02月03日
    浏览(48)
  • Qt编写推流综合应用示例(文件推流/桌面推流/本地摄像头/网络摄像头/转发推流/视频分发)

    1.1 文件推流 指定网卡和监听端口,接收网络请求推送音视频等各种文件。 实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。 可指定多种模式,0-直接播放、1-下载播放。 实时打印显示各种收发请求和应答数据。 每个文件对应MD5加密的唯一标识符,

    2023年04月22日
    浏览(43)
  • Qt/C++编写推流综合应用示例(文件推流/桌面推流/本地摄像头/网络摄像头/转发推流/视频分发)

    1.1 文件推流 指定网卡和监听端口,接收网络请求推送音视频等各种文件。 实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。 可指定多种模式,0-直接播放、1-下载播放。 实时打印显示各种收发请求和应答数据。 每个文件对应MD5加密的唯一标识符,

    2024年02月11日
    浏览(39)
  • Android 外接基于UVC协议的摄像头并实现预览

    先来一段从网上找到的理论知识,对UVC协议有初步的印象 UVC协议:USB Video Class,USB视频类,是一种为USB视频捕获设备定义的协议标准。 Android 平台支持使用即插即用的 USB 摄像头(即网络摄像头),但前提是这些摄像头采用标准的 Android Camera2 API 和摄像头 HIDL 接口。网络摄

    2024年02月14日
    浏览(40)
  • 使用QT的QML实时显示海康威视网络摄像头的视频数据

    需求背景: 开发的监控软件中有个监控页面需要实时显示网络摄像头的数据,整个监控软件是基于QT的QML语言开发的。在QML中播放视频使用MediaPlayer组件就可以,但网上看到的一些都是播放录制好的视频文件,对于实时播放摄像头数据介绍的比较少。 开发环境: Debian11.3 Qt

    2024年02月09日
    浏览(108)
  • 基于QT设计的无人机地面站(摄像头录像拍摄)

    通过QT设计一款无人机地面站软件,需要包含基本的RTSP拉流功能,对接无人机平台的RTSP流。此外,需要完成拍照、录像、OSD叠加功能;完成按钮控制云台进行拍照、录像、变焦、指点运动等。在此基础上,完成对应的目标跟踪识别。 技术要求 (1)采用QT平台,设计Windows端及

    2024年02月08日
    浏览(61)
  • 【使用VS开发的第一个QT项目——实现相机功能(包括QT下载、配置、摄像头程序)】

    下载链接 windows程序的后缀是.exe Ubuntu程序的后缀是.run 按照安装指示操作、注册QT,然后出现”选择“界面时 勾选“MinGW 7.3.0 64-bit”,“MSVC 2017 64-bit”;点击“Developer and Designer Tools”前的尖号,打开其中选项,勾选“MinGW 7.3.0 64-bit”。 在VS\\\"工具\\\"→\\\"扩展与更新\\\"→\\\"联机\\\"中搜

    2024年02月07日
    浏览(59)
  • 海康威视摄像头二次开发_云台控制_视频画面实时预览(基于Qt实现)

    需求:需要在公司的产品里集成海康威视摄像头的SDK,用于控制海康威视的摄像头。 拍照抓图、视频录制、云台控制、视频实时预览等等功能。 开发环境: windows-X64(系统) + Qt5.12.6(Qt版本) + MSVC2017_X64(使用的编译器) 海康威视提供了 设备网络SDK ,设备网络SDK是基于设备私有网

    2024年02月13日
    浏览(60)
  • 基于海康SDK实现Python调用海康威视网络摄像头

    本文参考博客,写得很好: Python调用海康威视网络相机之——python调用海康威视C++的SDK Python调用海康威视网络相机C++的SDK 写本文的目的,也是快速复盘,所以没有很详细 保存视频流到本地可参考下一篇:基于海康SDK实现Python保存海康威视网络摄像头拍摄的视频 Windows11 Vis

    2024年02月02日
    浏览(67)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包