C语言/ARM嵌入式-V4L视频监控系统

niko

发布日期: 2021-03-29 08:16:18 浏览量: 189
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

摘 要

随着数码相机和互联网的普及,使得人们的生活方式发生了改变。摄像头软件成为日常应用中必可少的应用软件之一,本次课程设计利用QT 设计了一款基于嵌入式视频服务器的监控系统,在linux2.6.30.4的内核基础上移植V4L2驱动模块并使用其API接口对摄像头进行设置和对所监控对象实时数据采集。系统采用TQ2440 开发板为硬件平台,在其上搭建LINUX 系统的网络视频服务器,用QT 为用户图形界面开发了客户端软件。客户端和服务端之间通过Socket通信,实现视频监控。其中在客户端连接上服务端之后,可以服务端发来的数据处理后作为图像显示在界面,提供了截图的功能、保存截图的功能。

关键词:Linux QT;通信;视频监控;V4L2

一、设计内容与要求

1.1 基础题设计内容及要求

  • 编写程序将数组内容倒置a[]=”123456789”

  • 创建两线程,A线程循环打印数组a[100],B线程循环将数组成员+1,要求利用互斥锁,使每次输出a[0]==a[99]

  • 通过Makefile,将project中的两个.c编译成.a,另一个.c调用.a的函数,要求实现静态库的生成和调用,运行结果正确

    1.2 V4L视频监控系统设计内容及要求

  • 完成videodev.o驱动程序并编译成功

  • 将驱动程序复制到用户目录

  • 测试并运行成功摄像头监控

1.3 设计的实现方案

在实现的过程中,我们首先要在服务器端通过videodev.o驱动程序用于打开摄像头,其方法试是我们创建数据缓冲区和一个线程,这个线程用于采集视频,通过在视频采集函数中,将套接字加入socket中,监控数据中是否含有套接字,若有则创建另一个用于向客服端通过socket发送采集视频的线程,然后我们要运用虚拟机中的QT编译工具,对要实现的视频摄像头的基本界面进行设计,布局完成之后,再对各个模块进行进行信号与槽函数的实现,最后再对整体进行优化设计,使得编写好的软件能够顺利流畅的运行。

二、总体设计

2.1 视频监控系统的总体设计

Qt是一个1991年由奇趣科技开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程式,也可用于开发非GUI程式,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,易于扩展,允许组件编程。服务器使用基于ARM9 的Qt2440开发板为硬件搭载平台和linux系统内核的软件平台。此视频监控系统主要由服务器、摄像头、客户端三部分模块组成。向操作系统内核移植了videodev.o驱动,并使用此来驱动网络摄像头模块,服务器对所采集到的视频数据提出来,并通过网口使用TCP/IP网络协议传输方式向客户端发送含有套接字socket的视频数据。如图所示:

2.2 客户端设计

首先用QT Creator 创建文件,利用QT 工具画好整体的主界面,建立一个主窗口。然后在这个主窗口上方放置一个行编辑框用来输入服务器IP、端口号,下方进行视频监控显示。客户端主要是连接到服务器,在客户端连接上服务端之后,将服务器传过来的数据进行处理,将数据作为JDPG图像显示在界面上。客户可以实现对图片的截取、保存。客户端总体流程框图如图所示:

2.3 服务器端设计

首先需要安装交叉编译器,将程序用arm-linux-gcc编译成适合TQ2440开发板下可运行的程序,并通过连接下载工具,使之在开发板上运行。服务器端总体流程图如图三所示:

三、具体分析

3.1 客户端具体设计

3.1.1 客户端主界面设计

QT 是一个支持多操作系统平台的应用程序开发框架,通过建立一个函数调用connect()函数把这个插槽和一个信号连接起来,这样就完成了一个事件和响应代码的连接。这里我们采用了多线程技术,主线程在主窗口绘图,实时显示监控视频;次线程建立TCP 连接,接收来自服务器的数据,当接收够一帧的数据后发射信号给主线程,让主线程来绘图。客户端的主要界面用到了控件labelVideo用于显示视频,btnConnect用于按钮connect,capture_btn用于按钮Captrue,显示端口和IP地址是用的txtPort和txtIP。主要界面如图所示:

3.1.2 数据接收线程的设计

我们首先初始化并建立一个TCP 连接,当连接出错时,返回错误并提示。然后通过QTcpSocket 类的bytesAvailable()函数来判断是否有数据可读,若可读则通过该函数的返回值来判断这一帧有多少数据需要读。所以客户端接受来自服务器端发来的数据,数据是由摄像头采集而来的,数据以jepg图片的格式连接,所以在客户端要做的就是将每一张图片从数据中分离出来,而后将其显示在主界面的视频浏览框内。实现流程图如图五所示:

其实现主要代码如下:

  1. //读取数据
  2. void MainWindow::readMesg() {
  3. qba= tcpSocket->readAll();
  4. for(int i = 0;i < qba.count();i++){
  5. if(enRecv) imageBuff += qba[i];
  6. if(lastbyte == (char)0xFF) {
  7. if(qba[i] == (char)0xD8) {
  8. qDebug()<<"START"<<endl;
  9. enRecv = true;
  10. }

3.2 服务器端具体设计

3.2.1视频采集模块的设计

视频的采集模块调用V4L,实现对原始视频数据的采集,其实现过程如下:先定义两个线程,一个线程用于采集视频,通过调用函数*pthread_video来实现,其中在函数*pthread_video里面又定义两个函数,一个是用于打开视频,开始采集,第二个函数对采集到的视频保存到数据缓冲区databufer中,另外一个线程用于发送采集到的视频,也是通过调用函数*pthread_snd实现,首先等待客服端连接,如果连接成功,则将数据写到套接字newsd中。

当数据流采集成功后,通过文件描述符集fdsr来监听,客户端是否有连接,如果有连接则通过套接字socket发送给客户端。主要的具体代码如下:

  1. void *pthread_video(void *arg)//视频采集函数
  2. {
  3. pthread_detach(pthread_self());//?
  4. video_on();//打开视频,开始采集
  5. databuf=(buf_t *)malloc(sizeof(buf_t)+buffers[0].length);//分配空间
  6. while(1)
  7. {
  8. video();//采集数据,保存
  9. }
  10. // video_off();
  11. return NULL;
  12. }
  13. void video_on()//打开视频,开始采集
  14. {
  15. enum v4l2_buf_type type;
  16. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  17. if (ioctl (fd, VIDIOC_STREAMON, &type) < 0)
  18. {
  19. printf("VIDIOC_STREAMON error\n");
  20. // return -1;
  21. }
  22. }
  23. void *pthread_snd(void *socketsd)//采集完数据,发送数据
  24. {
  25. pthread_detach(pthread_self());//让自己的线程不阻塞
  26. int sd=((int )socketsd);
  27. int newsd,ret,i=0;
  28. newsd=accept(sd,NULL,NULL);//等待客服端连接,
  29. if(newsd==-1)
  30. {
  31. perror("accept");
  32. return NULL;
  33. }
  34. while(1)//如果连接成功,
  35. {
  36. pthread_mutex_lock(&g_lock);
  37. pthread_cond_wait(&g_cond,&g_lock);
  38. write(newsd,databuf->buf,databuf->datasize);//写数据到socket文件
  39. if(ret==-1)
  40. {
  41. printf("client is out\n");
  42. }
  43. pthread_mutex_unlock(&g_lock);
  44. }
  45. return NULL;
  46. }

3.2.2 摄像头模块设计

本次课程设计的摄像头系统对摄像头模块使用单独线程管理。在pthread_video线程创建之前,调用videodev.o驱动接口对摄像头的格式进行设置,设置完成创建pthread_video线程,进入pthread_video线程进行视频录制。具体实现代码如下:

  1. struct v4l2_buffer buf;
  2. memset(&buf,0,sizeof(buf));
  3. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4. buf.memory = V4L2_MEMORY_MMAP; buf.index = 0;
  5. while(1) {
  6. if (ioctl(video_fd, VIDIOC_DQBUF, &buf) == -1) {
  7. return -1;
  8. }
  9. memcpy(databuf->buf,buffers[buf.index].start,buffers[buf.index].length);
  10. databuf->datasize=buf.bytesused; pthread_cond_signal(&g_cond);
  11. usleep(500);
  12. if(ioctl(video_fd,VIDIOC_QBUF,&buf)==-1){
  13. return -1;
  14. }
  15. }

3.2.3 视频传输模块设计

本系统采用的是TCP/IP协议进行视频传输,使用socket技术奖采集到的JPEG的二进制码流传输到网络视频监控端,服务器首先建立socket流式套接字与本地端口进行绑定,然后通过客户端与服务器的通讯信息来开/关闭自己线程。主要代码如下:

  1. int main()
  2. {
  3. signal(SIGPIPE,SIG_IGN);//忽略返回信息
  4. int ret;
  5. struct sockaddr_in server_addr;//定义一个结构体变量
  6. pthread_t tid;//线程
  7. socklen_t addrlen;//长度
  8. pthread_mutex_init(&g_lock,NULL);//线程互斥锁
  9. pthread_cond_init(&g_cond,NULL);//线程互斥锁
  10. server_addr.sin_family=AF_INET;
  11. server_addr.sin_port=htons(SERVERPORT);
  12. server_addr.sin_addr.s_addr=INADDR_ANY;
  13. //inet_pton(AF_INET,SERVERIP,&server_addr.sin_addr);
  14. addrlen=sizeof(struct sockaddr_in);
  15. int sd=socket(AF_INET,SOCK_STREAM,0);//创建套接字描述符(协议、本地地址、本地端口)
  16. if(sd==-1)
  17. {
  18. perror("socket\n");
  19. exit(1);
  20. }
  21. // fd=open("dev/video0",O_RDWR | O_NONBLOCK,0);
  22. fd=open("/dev/video0",O_RDWR,0);//打开摄像头设备文件
  23. if(fd==-1)
  24. {
  25. perror("open");
  26. return 0;
  27. }
  28. ret=bind(sd,(struct sockaddr *)&server_addr,addrlen);//绑定套接字,将本地ip地址绑定到端口号,(套接字描述符、本地地址、地址长度)
  29. if(ret==-1)
  30. {
  31. perror("bind");
  32. exit(1);
  33. }
  34. ret=listen(sd,20);//服务器最多连接20个客服端(套接字描述符,最大请求数)
  35. if(ret==-1)
  36. {
  37. perror("listen\n");
  38. exit(1);
  39. }
  40. fd_set fdsr;//创建文件描述符集
  41. int maxsock=sd;
  42. struct timeval tv;
  43. //mark();
  44. localMem();//申请物理内存
  45. ret=pthread_create(&tid,NULL,pthread_video,NULL);//创建线程采集数据
  46. while(1)
  47. {
  48. FD_ZERO(&fdsr);//文件描述符的初始化
  49. FD_SET(sd,&fdsr);//套接字放入文件描述符
  50. tv.tv_sec=30;
  51. tv.tv_usec=0;
  52. ret=select(maxsock+1,&fdsr,NULL,NULL,NULL);//通过文件描述符集监听是否有连接
  53. if(ret<0)
  54. {
  55. perror("select");
  56. break;
  57. }
  58. else if(ret==0)
  59. {
  60. printf("timeout\n");
  61. continue;
  62. }
  63. if(FD_ISSET(sd,&fdsr))
  64. {
  65. ret=pthread_create(&tid,NULL,pthread_snd,((void *)sd));//9 创建线程,执行函数pthread_snd,发送数据
  66. }
  67. }
  68. return 0;
  69. }

四、系统测试

4.1客户端测试

连接好客户端与服务器,开始视频监控,监控成功图如图所示:

五、基础题

编写程序将数组内容倒置a[]=”123456789”

程序代码实现如下:

  1. char a[]="0123456789";
  2. char tmp;
  3. int i = 0,j = 0;
  4. for(i = 0,j = strlen(a)-1;i<=strlen(a)/2-1;i++,j--)
  5. {
  6. tmp = a[i];
  7. a[i] = a[j];
  8. a[j] = tmp;
  9. }
  10. for(i = 0;i<strlen(a);i++)
  11. printf("%c",a[i]);
  12. printf("\n");

创建两线程,A线程循环打印数组a[100],B线程循环将数组成员+1,要求利用互斥锁,使每次输出a[0]==a[99]

程序源代码如下:

  1. #include<sys/types.h>
  2. #include<sys/ipc.h>
  3. #include<pthread.h>
  4. #include<stdio.h>
  5. #include<stdlib.h>
  6. #include<unistd.h>
  7. #include<string.h>
  8. pthread_mutex_t mutex;
  9. int a[3]={0,1,2};
  10. void *thrd_fun1(void *arg)
  11. {
  12. int thrd_num=(int)arg;
  13. int i;
  14. while(1)
  15. {
  16. pthread_mutex_lock(&mutex);
  17. for(i=0;i<3;i++)
  18. {
  19. printf("a[%d]=%d\n",i,a[i]);
  20. sleep(2);
  21. }
  22. pthread_mutex_unlock(&mutex);
  23. sleep(2);
  24. }
  25. pthread_mutex_destroy(&mutex);
  26. pthread_exit(NULL);
  27. }
  28. void *thrd_fun2(void *arg)
  29. {
  30. int thrd_num=(int)arg;
  31. int i;
  32. while(1)
  33. {
  34. pthread_mutex_lock(&mutex);
  35. for(i=0;i<3;i++)
  36. {
  37. a[i]++;
  38. }
  39. printf("++ finished\n");
  40. pthread_mutex_unlock(&mutex);
  41. sleep(4);
  42. }
  43. pthread_mutex_destroy(&mutex);
  44. pthread_exit(NULL);
  45. }
  46. int main(void)
  47. {
  48. pthread_t thread[2];
  49. int no=0,res;
  50. void *thrd_ret;
  51. res=pthread_create(&thread[0],NULL,thrd_fun1,(void*)no);
  52. if(res!=0)
  53. {
  54. printf("Create thread 1 failed\n");
  55. exit(res);
  56. }
  57. res=pthread_create(&thread[1],NULL,thrd_fun2,(void*)no);
  58. if(res!=0)
  59. {
  60. printf("Create thread 1 failed\n");
  61. exit(res);
  62. }
  63. for(no=0;no<2;no++)
  64. {
  65. res =pthread_join(thread[no],&thrd_ret);
  66. if(!res)
  67. {
  68. printf("Thread %d exit\n",no);
  69. }
  70. else
  71. {
  72. printf("Thread %d exit failed\n",no);
  73. }
  74. }
  75. return 0;
  76. }

通过Makefile,将project中的两个.c编译成.a,另一个.c调用.a的函数,要求实现静态库的生成和调用,运行结果正确

编写静态库程序thread.c如下:

  1. #include <stdio.h>
  2. void pf1(void)
  3. {
  4. printf("********\n");
  5. return;
  6. }
  7. void pf2(void)
  8. {
  9. printf("#########\n");
  10. return;
  11. }

该程序定义两个函数,分别打印不同的内容,该程序将被编译成.a静态库。

编写调用程序call.c如下:

  1. extern void pf1(void);
  2. extern void pf2(void);
  3. int main(void)
  4. {
  5. pf1();
  6. pf2();
  7. return 0;
  8. }

该程序对静态库进行调用,调用静态库中的两个函数pf1和pf2。

编写Makefile如下:

  1. CC=gcc
  2. CPPFLAGS=-c
  3. OBJS = thread.o
  4. SOURCE = thread.c
  5. CALL_SOURCE=call.c
  6. LIB = libthread.a
  7. EXEC=call
  8. AR=ar
  9. thread: ${OBJS}
  10. ${CC} -c ${SOURCE} -o ${OBJS}
  11. ${AR} rcsv $(LIB) thread.o
  12. ${CC} -o ${EXEC} ${CALL_SOURCE} -L. -lthread
  13. .PHONY : clean
  14. clean :-rm -f ${OBJS} ${EXEC} ${LIB}

Makefile文件实现对静态库程序编译成.a静态库,并且编译调用静态库的程序call.c为可执行文件call

总 结

通过这次比较完整的一个程序的设计,我以全身心投入的状态投入到课程设计中,甚至熬夜通宵写代码,不过这也锻炼了我的综合运用所学的基础知识,解决实际问题的能力,其中QT的学习可谓是收获颇丰,通过QT对软件整体布局的掌控,对信号与槽函数的连接,以及对函数功能的处理,都使我的能力得到了锻炼,丰富了我的经验。

提高是有但问题还在,这一次设计让我积累了许多实际经验,也暴露出一些问题,比如想实现一个功能花了很多时间,但是最后还是半途而废,这些在以为的学习和锻炼中,希望得到解决。

开始编写这个V4L视频监控摄像头的时候,没有头绪,在通过图书馆一些书籍的借阅后,终于对摄像头的编程有了一些了解,但这还不够,在同学耐心的指导下,终于完成了这次大作业,感谢老师和同学们的帮助。

上传的附件 cloud_download 2010441729-陈灵-V4L视频监控系统.rar ( 3.78mb, 9次下载 )

发送私信

5
文章数
0
评论数
最近文章
eject