写点什么

Linux 编程 _ 网页视频监控项目

作者:DS小龙哥
  • 2022 年 6 月 10 日
  • 本文字数:9200 字

    阅读完需:约 30 分钟

介绍 HTTP 协议简单的交互流程,使用本地摄像头通过 HTTP 协议向云服务器上传 JPG 图片流,实现网页视频监控效果,支持多个浏览器同时获取数据。

任务 1: 网页视频监控项目

目的: 使用浏览器访问开发板的 USB 摄像头图像数据,实时刷新到达视频的效果。


【1】HTTP 协议: 如何传输数据,让浏览器显示?


【2】线程的并发执行: 多个浏览器同时访问摄像头数据。


【3】USB 摄像头编程: 如果获取摄像头的数据。

1.1 如何显示一张静态的图片

HTTP 协议: 文本协议-----报文: 字符串。


HTTP 服务器基本的交互的步骤:


【1】先创建 HTTP 服务器


【2】使用浏览器(HTTP 客户端)访问 HTTP 服务器:


(1)第一次请求的路径是: / :表示询问: 你需要我做什么?


(2)HTTP 服务器收到请求之后,先向 HTTP 客户端发送应答报文。


再发送需要浏览器处理的数据: 数据类型、数据长度。 :表示分配给浏览器需要做的任务


如果需要浏览器显示一张图片,浏览器在收到任务之后,会解析任务,再次向服务器发送请求:


请求图片(图片的资源路径):


HTTP 服务器收到请求之后,先向 HTTP 客户端发送应答报文。


再发送需要浏览器处理的数据: 数据类型、数据长度。

1.2 采集摄像头数据、显示动态图片

【1】采集摄像头数据: 开一个新的线程


【2】需要将摄像头的数据编码为 JPG 格式—jpglib 只能将 RGB 数据压缩成 JPG 格式保存到文件。


需要使用改进的算法,将 JPG 图像压缩存放到内存里。


【3】需要考虑资源共享: 线程互斥锁+条件变量


(1) 线程 1: 负责采集摄像头的数据,并进行编码压缩 jpg 图像


(2) 线程 2(主线程): 负责等待 HTTP 客户端连接(浏览器),处理与浏览器之间的交互过程。



云服务器: 本身就是一个虚拟电脑。


【1】登录: 使用 ssh 远程登录。


【2】买云服务器: 送一个公网 IP 地址。


【3】也可以购买一个域名。www.1234.com


今天的代码基础之上实现:


跨网段网页视频监控。




int on = 1;
if(setsockopt(http_server_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
printf("setsockopt(SO_REUSEADDR) 设置错误!\n");
exit(-1);
}
//这样可以保证: 端口关闭之后,立即可以再次使用
复制代码

1.3 解决 TCP 服务器退出时,产生退出信号终止进程

signal.h中的宏定义SIG_DFL及SIG_IGN
SIG_DFL,SIG_IGN 分别表示无返回值的函数指针,指针值分别是0和1,
这两个指针值逻辑上讲是实际程序中不可能出现的函数地址值。
SIG_DFL:默认信号处理程序
SIG_IGN:忽略信号的处理程序
/*
往一个已经接收到FIN的套接中写是允许的,接收到的FIN仅仅代表对方不再发送数据。
并不能代表我不能发送数据给对方。
往一个FIN结束的进程中写(write),对方会发送一个RST字段过来,TCP重置。
如果再调用write就会产生SIGPIPE信号
*/
signal(SIGPIPE,SIG_IGN);
复制代码

1.4 HTTP 服务器搭建_显示一静态 JPG 图片

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/ioctl.h>#include <linux/fb.h>#include <sys/ioctl.h>#include <sys/mman.h>#include <string.h>#include <time.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h> #include <pthread.h>#include <signal.h>#include <poll.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>

#define HTTP_SERVER_PORT 1237 /*HTTP服务器端口号*/int http_server_fd; /*HTTP服务器套接字*/

/*函数功能: 处理退出的信号*/void exit_sighandler(int sig){ /*关闭服务器套接字*/ close(http_server_fd); sleep(2); //退出进程 exit(1);}
/*函数功能: 向HTTP客户端发送文件数据*/void HTTPClient_SendFileData(int client_fd,char *type,char *file){ int file_fd; int read_len; struct stat file_buf; unsigned char buffer[1024]; file_fd=open(file,O_RDONLY); if(file_fd<0) { printf("%s文件打开失败!\n",file); return; } /*1. 获取文件的状态信息*/ stat(file,&file_buf); //printf("%d\n",file_buf.st_size); /*2. 构造报文头*/ sprintf(buffer,"HTTP/1.1 200 OK\r\n" \ "Content-type:%s\r\n" \ "Content-Length:%d\r\n" "Server: wbyq\r\n" \ "\r\n",type,file_buf.st_size); read_len=strlen(buffer); /*2. 发送数据*/ do { if(write(client_fd,buffer,read_len)<=0)break; }while((read_len=read(file_fd,buffer,sizeof(buffer)))>0);}
/*函数功能: 处理HTTP客户端的线程*/void *pthread_Handler_HTTP_Client(void *dev){ int Clientfd; unsigned char buffer[1024]; unsigned char *p=buffer; struct pollfd fds; int poll_state; /*poll函数的状态值*/ int recv_len; /*接收的数据长度*/ if(dev==NULL) { pthread_exit(NULL); /*终止线程*/ } Clientfd=*(int*)dev; /*保存客户端套接字描述符*/ free(dev); /*释放空间*/ /*1. 接收客户端的请求报文*/ fds.fd=Clientfd; fds.events=POLLIN; while(1) { /*等待数据*/ poll_state=poll(&fds,1,100); if(poll_state<=0)break; /*数据接收完毕就退出*/ recv_len=read(Clientfd,p,1024); p+=recv_len; if(p-buffer>1024)break; } //printf("buffer=%s\n",buffer); /*1. 判断请求的路径*/ if(strstr(buffer,"GET / HTTP/1.1")) { HTTPClient_SendFileData(Clientfd,"text/html","index.html"); } else if(strstr(buffer,"GET /image.jpg HTTP/1.1")) { HTTPClient_SendFileData(Clientfd,"image/jpeg","123.jpg"); } else if(strstr(buffer,"GET /favicon.ico HTTP/1.1")) { HTTPClient_SendFileData(Clientfd,"image/x-icon","123.ico"); } close(Clientfd);}

/*HTTP服务器创建:1. 创建socket套接字2. 绑定端口号: 服务器创建3. 设置监听端口的数量: 服务器最大等待连接的客户端总数量4. 等待客户端连接*/int main(int argc,char **argv){ /*1. 绑定将要捕获的信号*/ signal(SIGINT,exit_sighandler); signal(SIGSEGV,exit_sighandler); /*2. 创建套接字*/ http_server_fd=socket(AF_INET,SOCK_STREAM,0); if(http_server_fd<0) { printf("HTTP服务器:创建套接字创建失败!\n"); return -1; } /*3. 绑定端口号*/ struct sockaddr_in server_addr; memset(&server_addr,0,sizeof(struct sockaddr_in)); server_addr.sin_family=AF_INET; //IPV4 server_addr.sin_port=htons(HTTP_SERVER_PORT); //需要填大端格式的端口号数据 server_addr.sin_addr.s_addr=0;//inet_addr("192.168.18.3"); /*0=inet_addr("0.0.0.0") ---表示本地所有IP地址*/ if(bind(http_server_fd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in))!=0) { printf("HTTP服务器:绑定端口号失败!\n"); return -2; } /*4. 设置监听客户端连接的数量*/ listen(http_server_fd,50); /*5. 等待客户端连接:阻塞*/ struct sockaddr_in client_addr; int addrlen=sizeof(struct sockaddr_in); pthread_t thread_id; /*线程的ID*/ int *client_fd=NULL; /*保存客户端的套接字描述符*/ while(1) { client_fd=(int*)malloc(sizeof(int)); if(client_fd==NULL) { printf("存放客户端的套接字描述符,空间申请失败!\n"); break; } *client_fd=accept(http_server_fd,(struct sockaddr *)&client_addr,&addrlen); if(*client_fd<0) { break; } /*6. 创建新的线程*/ if(pthread_create(&thread_id,NULL,pthread_Handler_HTTP_Client,(void*)client_fd)!=0) { printf("创建处理HTTP客户端线程失败!\n"); break; } } /*7. 关闭服务器套接字*/ close(http_server_fd); return 0;}
复制代码

1.5 网页视频监控的项目代码_多线程处理

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/ioctl.h>#include <linux/fb.h>#include <sys/ioctl.h>#include <sys/mman.h>#include <string.h>#include <time.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h> #include <pthread.h>#include <signal.h>#include <poll.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <linux/videodev2.h>#include <sys/mman.h>#include "yuv_to_jpeg.h"
#define UVC_VIDEO_DEVICE "/dev/video15" /*UVC摄像头设备节点*/int uvc_video_fd; /*存放摄像头设备节点的文件描述符*/int video_stop_stat=1; /*视频停止状态: 1表示正常执行,0表示退出*/
unsigned char *video_memaddr_buffer[4]; /*存放的是摄像头映射出来的缓冲区首地址*/int Image_Width; /*图像的宽度*/int Image_Height; /*图像的高度*/unsigned char *jpg_video_buffer=NULL; /*转换之后的JPG数据缓冲区首地址*/unsigned int jpg_video_size; /*存放当前JPG数据缓冲区的大小*/pthread_mutex_t mutex; /*互斥锁*/pthread_cond_t cond; /*条件变量*/
#define HTTP_SERVER_PORT 1235 /*HTTP服务器端口号*/int http_server_fd; /*HTTP服务器套接字*/
/*函数功能: 处理退出的信号*/void exit_sighandler(int sig){ video_stop_stat=0; //让摄像头采集线程自动退出 sleep(2); /*关闭服务器套接字*/ close(http_server_fd); //退出进程 exit(1);}
/*函数功能: 向HTTP客户端发送文件数据*/void HTTPClient_SendFileData(int client_fd,char *type,char *file){ int file_fd; int read_len; struct stat file_buf; unsigned char buffer[1024]; file_fd=open(file,O_RDONLY); if(file_fd<0) { printf("%s文件打开失败!\n",file); return; } /*1. 获取文件的状态信息*/ stat(file,&file_buf); //printf("%d\n",file_buf.st_size); /*2. 构造报文头*/ sprintf(buffer,"HTTP/1.1 200 OK\r\n" \ "Content-type:%s\r\n" \ "Content-Length:%d\r\n" "Server: wbyq\r\n" \ "\r\n",type,file_buf.st_size); read_len=strlen(buffer); /*2. 发送数据*/ do { if(write(client_fd,buffer,read_len)<=0)break; }while((read_len=read(file_fd,buffer,sizeof(buffer)))>0);}

/*函数功能: 发送数据流*/void SendVideoData(int Clientfd){ int image_size; unsigned char *image_data; unsigned char buffer[1024]; /*1. 构造报文头: 回应浏览器请求,并告诉浏览器接下来需要使用长连接*/ sprintf(buffer, "HTTP/1.0 200 OK\r\n" \ "Server: wbyq\r\n" \ "Content-Type: multipart/x-mixed-replace;boundary=" "boundarydonotcross" "\r\n" \ "\r\n" \ "--" "boundarydonotcross" "\r\n"); if(write(Clientfd,buffer,strlen(buffer))<0) { return; } /*2. 循环发送数据流: JPG图片*/ image_data=malloc(Image_Width*Image_Height*3); if(image_data==NULL) { printf("循环发送数据流缓冲区申请失败!\n"); return; } while(video_stop_stat) { //阻塞方式等待条件变量,等待成功并上锁 pthread_cond_wait(&cond,&mutex); image_size=jpg_video_size; //保存图片的大小 memcpy(image_data,jpg_video_buffer,image_size); //互斥锁解锁 pthread_mutex_unlock(&mutex); /*2.1 构造报文头: 告诉浏览器发送数据类型和数据的长度*/ sprintf(buffer,"Content-type:%s\r\n" \ "Content-Length:%d\r\n"\ "\r\n","image/jpeg",image_size); if(write(Clientfd,buffer,strlen(buffer))<0) { break; } /*2.2 发送实际的数据*/ if(write(Clientfd,image_data,image_size)<0)break; /*2.3 发送间隔符号*/ sprintf(buffer,"\r\n--" "boundarydonotcross" "\r\n"); //间隔符号 if(write(Clientfd,buffer,strlen(buffer))<0) { break; } } free(image_data); //释放空间}

/*函数功能: 处理HTTP客户端的线程*/void *pthread_Handler_HTTP_Client(void *dev){ int Clientfd; unsigned char buffer[1024]; unsigned char *p=buffer; struct pollfd fds; int poll_state; /*poll函数的状态值*/ int recv_len; /*接收的数据长度*/ if(dev==NULL) { pthread_exit(NULL); /*终止线程*/ } Clientfd=*(int*)dev; /*保存客户端套接字描述符*/ free(dev); /*释放空间*/ /*1. 接收客户端的请求报文*/ fds.fd=Clientfd; fds.events=POLLIN; while(1) { /*等待数据*/ poll_state=poll(&fds,1,100); if(poll_state<=0)break; /*数据接收完毕就退出*/ recv_len=read(Clientfd,p,1024); p+=recv_len; if(p-buffer>1024)break; } //printf("buffer=%s\n",buffer); /*1. 判断请求的路径*/ if(strstr(buffer,"GET / HTTP/1.1")) { HTTPClient_SendFileData(Clientfd,"text/html","index.html"); } else if(strstr(buffer,"GET /?action=stream HTTP/1.1")) { SendVideoData(Clientfd); //发送视频流数据 } else if(strstr(buffer,"GET /favicon.ico HTTP/1.1")) { HTTPClient_SendFileData(Clientfd,"image/x-icon","123.ico"); } close(Clientfd);}

/*函数功能: UVC摄像头初始化返回值: 0表示成功*/int UVCvideoInit(void){ /*1. 打开摄像头设备*/ uvc_video_fd=open(UVC_VIDEO_DEVICE,O_RDWR); if(uvc_video_fd<0) { printf("%s 摄像头设备打开失败!\n",UVC_VIDEO_DEVICE); return -1; } /*2. 设置摄像头的属性*/ struct v4l2_format format; memset(&format,0,sizeof(struct v4l2_format)); format.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示视频捕获设备*/ format.fmt.pix.width=320; /*预设的宽度*/ format.fmt.pix.height=240; /*预设的高度*/ format.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; /*预设的格式*/ format.fmt.pix.field=V4L2_FIELD_ANY; /*系统自动设置: 帧属性*/ if(ioctl(uvc_video_fd,VIDIOC_S_FMT,&format)) /*设置摄像头的属性*/ { printf("摄像头格式设置失败!\n"); return -2; } Image_Width=format.fmt.pix.width; Image_Height=format.fmt.pix.height; printf("摄像头实际输出的图像尺寸:x=%d,y=%d\n",format.fmt.pix.width,format.fmt.pix.height); if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV) { printf("当前摄像头支持YUV格式图像输出!\n"); } else { printf("当前摄像头不支持YUV格式图像输出!\n"); return -3; }
/*3. 请求缓冲区: 申请摄像头数据采集的缓冲区*/ struct v4l2_requestbuffers req_buff; memset(&req_buff,0,sizeof(struct v4l2_requestbuffers)); req_buff.count=4; /*预设要申请4个缓冲区*/ req_buff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ req_buff.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ if(ioctl(uvc_video_fd,VIDIOC_REQBUFS,&req_buff)) /*申请缓冲区*/ { printf("申请摄像头数据采集的缓冲区失败!\n"); return -4; } printf("摄像头缓冲区申请的数量: %d\n",req_buff.count);
/*4. 获取缓冲区的详细信息: 地址,编号*/ struct v4l2_buffer buff_info; memset(&buff_info,0,sizeof(struct v4l2_buffer)); int i; for(i=0;i<req_buff.count;i++) { buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ if(ioctl(uvc_video_fd,VIDIOC_QUERYBUF,&buff_info)) /*获取缓冲区的详细信息*/ { printf("获取缓冲区的详细信息失败!\n"); return -5; } /*根据摄像头申请缓冲区信息: 使用mmap函数将内核的地址映射到进程空间*/ video_memaddr_buffer[i]=mmap(NULL,buff_info.length,PROT_READ|PROT_WRITE,MAP_SHARED,uvc_video_fd,buff_info.m.offset); if(video_memaddr_buffer[i]==NULL) { printf("缓冲区映射失败!\n"); return -6; } }
/*5. 将缓冲区放入采集队列*/ memset(&buff_info,0,sizeof(struct v4l2_buffer)); for(i=0;i<req_buff.count;i++) { buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ buff_info.index=i; /*缓冲区的节点编号*/ buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ if(ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info)) /*根据节点编号将缓冲区放入队列*/ { printf("根据节点编号将缓冲区放入队列失败!\n"); return -7; } }
/*6. 启动摄像头数据采集*/ int Type=V4L2_BUF_TYPE_VIDEO_CAPTURE; if(ioctl(uvc_video_fd,VIDIOC_STREAMON,&Type)) { printf("启动摄像头数据采集失败!\n"); return -8; } return 0;}
/*函数功能: 采集摄像头的数据,并进行处理*/void *pthread_video_Data_Handler(void *dev){ /*循环采集摄像头的数据*/ struct pollfd fds; fds.fd=uvc_video_fd; fds.events=POLLIN;
struct v4l2_buffer buff_info; memset(&buff_info,0,sizeof(struct v4l2_buffer)); int index=0; /*表示当前缓冲区的编号*/ /*申请空间:存放转换之后的JPG数据*/ jpg_video_buffer=malloc(Image_Width*Image_Height*3); if(jpg_video_buffer==NULL) { printf("JPG转换的缓冲区申请失败!\n"); exit(0); } while(video_stop_stat) { /*1. 等待摄像头采集数据*/ poll(&fds,1,-1);
/*2. 取出一帧数据: 从采集队列里面取出一个缓冲区*/ buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ ioctl(uvc_video_fd,VIDIOC_DQBUF,&buff_info); /*从采集队列取出缓冲区*/ index=buff_info.index; //printf("采集数据的缓冲区的编号:%d\n",index);
/*3. 处理数据: YUV转JPG数据格式*/ //video_memaddr_buffer[index]; /*当前存放数据的缓冲区地址*/ //阻塞方式获取互斥锁 pthread_mutex_lock(&mutex);
jpg_video_size=yuv_to_jpeg(Image_Width,Image_Height,Image_Width*Image_Height*3,video_memaddr_buffer[index],jpg_video_buffer,50); //互斥锁解锁 pthread_mutex_unlock(&mutex); /*唤醒所有等待的线程: 浏览器获取数据*/ pthread_cond_broadcast(&cond); /*测试代码*/ //printf("jpg_video_size=%d(kb),src=%d(kb)\n",jpg_video_size/1024,(Image_Width*Image_Height*3)/1024); /*4. 将缓冲区再次放入采集队列*/ buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap内存映射*/ buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*视频捕获设备*/ buff_info.index=index; /*缓冲区的节点编号*/ ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info); /*根据节点编号将缓冲区放入队列*/ } close(uvc_video_fd); //关闭摄像头}
/*HTTP服务器创建:1. 创建socket套接字2. 绑定端口号: 服务器创建3. 设置监听端口的数量: 服务器最大等待连接的客户端总数量4. 等待客户端连接*/int main(int argc,char **argv){ struct sockaddr_in client_addr; int addrlen=sizeof(struct sockaddr_in); pthread_t thread_id; /*线程的ID*/ int *client_fd=NULL; /*保存客户端的套接字描述符*/ //初始化互斥锁 pthread_mutex_init(&mutex,NULL); //初始化条件变量 pthread_cond_init(&cond,NULL);
/*1. 绑定将要捕获的信号*/ signal(SIGINT,exit_sighandler); signal(SIGSEGV,exit_sighandler); /*2. 创建套接字*/ http_server_fd=socket(AF_INET,SOCK_STREAM,0); if(http_server_fd<0) { printf("HTTP服务器:创建套接字创建失败!\n"); return -1; } /*3. 绑定端口号*/ struct sockaddr_in server_addr; memset(&server_addr,0,sizeof(struct sockaddr_in)); server_addr.sin_family=AF_INET; //IPV4 server_addr.sin_port=htons(HTTP_SERVER_PORT); //需要填大端格式的端口号数据 server_addr.sin_addr.s_addr=0;//inet_addr("192.168.18.3"); /*0=inet_addr("0.0.0.0") ---表示本地所有IP地址*/ if(bind(http_server_fd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in))!=0) { printf("HTTP服务器:绑定端口号失败!\n"); return -2; } /*4. 设置监听客户端连接的数量*/ listen(http_server_fd,50); /*5.处理摄像头并创建新线程:负责摄像头的数据采集*/ if(UVCvideoInit()) /*摄像头初始化*/ { close(http_server_fd); return -1; } if(pthread_create(&thread_id,NULL,pthread_video_Data_Handler,NULL)!=0) { close(http_server_fd); printf("创建摄像头的数据采集线程失败!\n"); return -1; } //设置线程的分离属性 pthread_detach(thread_id); /*7. 等待客户端连接:阻塞*/ while(1) { client_fd=(int*)malloc(sizeof(int)); if(client_fd==NULL) { printf("存放客户端的套接字描述符,空间申请失败!\n"); break; } *client_fd=accept(http_server_fd,(struct sockaddr *)&client_addr,&addrlen); if(*client_fd<0) { break; } /*8. 创建新的线程*/ if(pthread_create(&thread_id,NULL,pthread_Handler_HTTP_Client,(void*)client_fd)!=0) { printf("创建处理HTTP客户端线程失败!\n"); break; } //设置线程的分离属性 pthread_detach(thread_id); } /*9. 关闭服务器套接字*/ close(http_server_fd); return 0;}
复制代码


发布于: 2022 年 06 月 10 日阅读数: 23
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
Linux编程_网页视频监控项目_6月月更_DS小龙哥_InfoQ写作社区