当前位置: 首页>前端>正文

MFC 监控文件被拷贝 监控文件怎么拷贝

视频监控程序设计那点事儿

这个项目前后历时两个礼拜,包括一周多的项目开发以及后期的收尾总结。

一、框架结构图以及项目分析。

图略。

开发板上的应用程序要做的工作如下:

1、采集摄像头视频数据到内存。

2、发送数据,即传输视频。

PC上的QT应用程序要做的工作如下:

1、接收视频数据

2、显示数据

二、项目分析。

开发板这边

1、如何调用USB摄像头驱动,来采集视频。

回答:访问设备文件,像操作文件一样。那么设备文件名又是什么?

2、视频格式是什么?如何知道这个视频格式?

3、Socket网络发送

PC这边

1、socket网络接收

2、显示视频

(1)、显示一幅图。(如何在Qt显示.jpg图)

(2)、不停地显示,更新视频

这个当然不是我总结的,曹工第一节课的时候给我们讲了这些东西。以我现在做完项目的角度看,这个项目分析是很到位的,简练清晰。但是,我记得我当时听的感觉不能说云里雾里,但是总觉不是那么清晰明朗自己也不是那么自信。我可能明白你说的每句话的意思,但是只是知道而已,不能深刻理解,不知道每个步骤该怎么去实现,头脑很空,没什么想法。视频监控啊,我平时QQ上也没跟人视频过,这会要输在起跑线上了,我好担心啊。视频那个设备到底是什么东西啊,黑黑的一个东西,谁知道是什么小怪兽啊。视频设备跑出的数据应该是数据流啥的,数据流又是什么东西啊,我跟它不熟啊,我只知道老家门前那条溪,溪里有水,水流啊流啊。。。流的好长好长。。。

啊。。好难啊,好神秘啊,都不会啊,快要死了,有木有!!!

三、开发过程

(1)、如何用Qt显示一个.jpg图片。

这是本项目的切入点,有了这个唐古拉山脉源头,长江之洪流猛兽必将一触即发,势不可挡。

其实我们都没接触过Qt,大伙也都没什么界面编程经验,都是从头学起。其实现在我做完项目了,你要是问我熟悉Qt吗,我会说,了解,但是应该还没入门。所以当你看到有关Qt的部分,你可一笑而过。当然,这并不影响我们做项目。现学现用,先搞出东西再说。老规矩,从helloworld学起。点击一个按钮,label会显示helloworld。下面这个句子涵盖了Qt的一个主要特征:信号和槽机制。

connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(ShowHello() ) )

做完helloworld这个小程序,可以同样可以做这么个小程序:单击,出来一个小图片。这个代码百度来的。

QLabel*label=new QLabel ("", 0); //初始化qlabel
QPixmappm ("/yanjl/qt/qt_jpg/han.jpg"); //设定要显示的图片
ui->label->setPixmap(pm);//将图片加载到label上
ui->label->setGeometry(0, 0, 240, 320 ); //屏幕大小,初始位置

(2)、Qt显示两幅图片

按道理说,中间只要加个时间间隔,就能显示隔一段时间显示图片了。但是我们几个搞了好久一直都没搞出来,主要是因为对Qt太陌生了,但是这个好像对后来影响不大。不做没关系。

(3)、从arm传送一个.jpg图到PC上。

1、socket如何传送一个.jpg图。

收到任务后,我们几个都很苦恼,之前只用socket传送过字符,.jpg该怎么传啊。百度啊百度。当时搜到了一个代码,人家说能能实现传送一个图片的,不管三七二十一,先拿过来用用。本来我还蛮客气的,照着他的代码我一个个字符的敲到我的电脑上,后来看到纪柯和东东都在调试他们的程序时,我就有点急了。出最后的绝招:copy!哈哈,太过瘾了。你们逼我的!

最后既然还真能传送。东东看着网上图片那人,说了一句很搞笑的话,长这么丑的人既然能做的出来。

客户端读取图片文件并发送:

fread(buf,MAX_LEN,1,fq);
write(sockfd,buf,MAX_LEN);

服务端接收并存成文件:

read(client_fd,buf,MAX_LEN);
fwrite(buf,MAX_LEN,1,fp);

2、其实第二天我才去理解这些代码。原来不管什么格式,都当成普通二进制文件操作!我马上拿起来了谭浩强的C,把文件操作那章复习了一遍。以前学的都不知道怎么用。还写了一个文件复制的小程序。

(4)、从arm传送多个.jpg图到PC上。

我想啊,多个.jpg嘛,加个for()循环应该就可以。上面的MAX_LEN,当初都设为256字节,远小于图片大小。你想想会出现什么问题?客服端几个文件不停的发送,服务端不停的接收。在并没有同步的机制下,接收端不会保存你想要的几个文件。

那么该怎么办呢?这个问题折腾了我好长一段时间。我在发完每个文件的的最后一帧256字节,只发三个字符:new,服务端检测到这个new,接收文件结束。你觉得会检测到new么?事实验证是不会的。这个问题折腾了我好久。我还有用记发一个文件的次数,搞了一两天后终于崩溃了,就放弃了。设MAX_LEN为文件大小,一次性发送,问题暂时解决。

现在来看这个问题,其实我觉得这个小程序挺有意思,挺难的,应该很有技术含量。因为每次发送了一定字节,如256,接收那边每次接收的并不会是256(这个现象是我后来在Qt里QTcpSocket->bytesAvailable()这里发现的)。这样的话,呵呵,我现在也没搞定。

我在这里逗留了很长时间。其实东东、赵恒涛早就转向QTcpSocket,QTcpServer了。落后就要挨打,这个道理你懂的。

(5)、QTcpServer接收并显示一个图片。

我在没有任何预习的情况下,听了曹工对这部分的讲解。预习了,听课心里会踏实点。

那晚曹工向我们展示了他作为一个老程员,接收新知识的能力和处理问题的底气霸气!他没有接触过这部分知识,但是在一个小时内,百度一个程序,看基本流程,查QThelp手册,查QtcpServer类,点击FortuneServer Example,看基本流程,查QT类和函数,一步一步实现了接收并显示图片的程序。让吾等鼠辈大开眼界。QT的学习大底如此。

这里讲讲基本流程。曹工说,QTcpSocket、QtcpServer是在socket的基础上封装各种类,原理都一样,大差不差。QT的特征是信号和槽,实现多线程,比平时要容易很多。当然哥们几个都是有socket基础的,很骄傲很自豪啊!

1、侦听QTcpServer.listen(QHostAddress::Any,5023)

2、有连接请求时,QTcpServer产生newConnection()信号,调用nextPendingConnection()接受请求并返回QTcpSocket,就可以用这个类进行通信了。

3、每收到一个数据包,QTcpSocket会readyRead()信号,调用QtcpSocket.readAll()读取数据,读到QByteArraybytearray类型数组里;

4、存成一个图片QFile->write(bytearray),或直接显示成一个图片Qpixmap.loadFromData(bytearray);

大概就是这样的。还有QT的调试技巧,qDebug()<< str,设置断点,单步调试等等。

(6)、USB摄像头拍摄一个图片

这里讲解几个基础知识和基于V4L2的USB摄像头的图像采集流程。内容较多,大都是copy的,自行略过啊,只是本人觉得比较重要啊。写到这,我觉得好累啊,快不行了。

1、首先要理解V4L2的作用。

Videofor linux, 简称V4L,内核视频处理模块,现在已经发展到V4L2,是linux内核里支持影像设备的一组APIs。

这里重点是理解V4L2的作用。设备有了驱动不就行了吗。V4L2为应用层提供API函数。同时也为驱动程序的编写提供统一的接口。这样的话,就屏蔽了不同摄像头的差别,应用程序不需要修改。我想,就像QQ视频,大部分摄像头都能用就是这个道理。

2、应用程序操作摄像头设备即对/dev/video0设备文件。开发板上是/dev/video1。

3、lsusb查看usbhost是否插入。这里区别usbhost与usbdevice。

4、V4L2规范中不仅定义了通用API元素(CommonAPI Elements),图像的格式(ImageFormats),输入/输出方法(Input/Output),还定义了Linux内核驱动处理视频信息的一系列接口(Interfaces),这些接口主要有:

视频采集接口——VideoCapture Interface;

  视频输出接口——VideoOutput Interface;

  视频覆盖/预览接口——VideoOverlay Interface;

  视频输出覆盖接口——VideoOutput Overlay Interface;

编解码接口——CodecInterface。

我们这里用的是视频采集接口,下面的type都是V4L2_BUF_TYPE_VIDEO_CAPTURE

5、视频采集的帧缓冲区

  前期初始化完成后,只是解决了一帧视频数据的格式和大小问题,而连续视频帧数据的采集需要用帧缓冲区队列的方式来解决,即要通过驱动程序在内存中申请几个帧缓冲区来存放视频数据。

  应用程序通过API接口提供的方法(VIDIOC_REQBUFS)申请若干个视频数据的帧缓冲区,申请帧缓冲区数量一般不低于3个,每个帧缓冲区存放一帧视频数据,这些帧缓冲区在内核空间。

  应用程序通过API接口提供的查询方法(VIDIOC_QUERYBUF)查询到帧缓冲区在内核空间的长度和偏移量地址。

  应用程序再通过内存映射方法(mmap),将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据。

  (1)将帧缓冲区在视频输入队列排队,并启动视频采集

  在驱动程序处理视频的过程中,定义了两个队列:视频采集输入队列(incomingqueues)和视频采集输出队列(outgoingqueues),前者是等待驱动存放视频数据的队列,后者是驱动程序已经放入了视频数据的队列。

  应用程序需要将上述帧缓冲区在视频采集输入队列排队(VIDIOC_QBUF),然后可启动视频采集。

  (2)循环往复,采集连续的视频数据

  启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。

  应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。

最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集。

6、用V4L2采集视频的程序流程和相关API

  V4L2采集视频操作基本按照打开视频设备、设置视频格式、启动视频采集,循环处理视频数据、停止视频采集、关闭视频设备,具体操作通过ioctl等函数来实现。一般操作流程如下:

  (1)打开视频设备文件。intfd=open("/dev/video0",O_RDWR);

  (2)查询视频设备的能力,比如是否具有视频输入,或者音频输入输出等。ioctl(fd_v4l,VIDIOC_QUERYCAP, &cap)

  (3)设置视频采集的参数

  设置视频的制式,制式包括PAL/NTSC,使用ioctl(fd_v4l,VIDIOC_S_STD, &std_id)

  设置视频图像的采集窗口的大小,使用ioctl(fd_v4l,VIDIOC_S_CROP, &crop)

  设置视频帧格式,包括帧的点阵格式,宽度和高度等,使用ioctl(fd_v4l,VIDIOC_S_FMT, &fmt)

  设置视频的帧率,使用ioctl(fd_v4l,VIDIOC_S_PARM, &parm)

  设置视频的旋转方式,使用ioctl(fd_v4l,VIDIOC_S_CTRL, &ctrl)

  (4)向驱动申请视频流数据的帧缓冲区

  请求/申请若干个帧缓冲区,一般为不少于3个,使用ioctl(fd_v4l,VIDIOC_REQBUFS, &req)

  查询帧缓冲区在内核空间中的长度和偏移量ioctl(fd_v4l,VIDIOC_QUERYBUF, &buf)

  (5)应用程序通过内存映射,将帧缓冲区的地址映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。

  buffers[i].start= mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED,fd_v4l, buffers[i].offset);

  (6)将申请到的帧缓冲全部放入视频采集输出队列,以便存放采集的数据。ioctl(fd_v4l, VIDIOC_QBUF, &buf)

  (7)开始视频流数据的采集。ioctl(fd_v4l, VIDIOC_STREAMON, &type)

  (8)驱动将采集到的一帧视频数据存入输入队列第一个帧缓冲区,存完后将该帧缓冲区移至视频采集输出队列。

  (9)应用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区。ioctl(fd_v4l, VIDIOC_DQBUF, &buf) ,应用程序处理该帧缓冲区的原始视频数据。

  (10)处理完后,应用程序的将该帧缓冲区重新排入输入队列,这样便可以循环采集数据。ioctl(fd_v4l, VIDIOC_QBUF, &buf)

  重复上述步骤8到10,直到停止采集数据。

  (11)停止视频的采集。ioctl(fd_v4l, VIDIOC_STREAMOFF, &type)

  (12)释放申请的视频帧缓冲区unmap,关闭视频设备文件close(fd_v4l)。

以上的程序流程,包含了视频设备采集连续的视频数据的逻辑关系。而在实际运用中,往往还要加入对视频数据进行处理(如压缩编码)的工作,否则,视频流数据量相当大,需要很大的存储空间和传输带宽。

本人的程序也是网上当的,而且是清晰无错型,一运行就成功的。你应该也能找到的。这样的话就能拍摄一张.jpg图片了。以前一直以为那个摄像头会自动不停的拍一些数据流,你也是这么认为的,有木有?!

那么到这里,整个项目的思路其实都很清楚了。你会像一头牛一样低着头不顾一切的往前走,拦也拦不住的,直到成功。

(7)、拍一张.jpg图,加入socket部分,直接传输给QtcpServer。

write(sockfd,buffers[buf.index].start,buffers[buf.index].length)

(8)、视频流的形成

其实本人小心起见,并没有一下子就写好了所有代码。这里还做了两个小实验。

1、QT显示多张图片。其实就是client发送十张图片,每张间隔1秒,你在QT上面能看到你想要的效果。

2、V4L2拍摄多张图片。这个也简单的,for(intn=0,n++,n<10)

综合上面小实验,你是不是觉得离成功只有一步之遥了。哈哈,对了,就是你想的那样。

至此,项目就成功了。你能在PC的QT上面看到帅帅的自己,太棒了。

(9)、摄像头移植ARM板上。

理论上讲,我们会觉得没有任何问题。移植嘛,改下设备号/dev/video1,编译的时候arm-linux-gcc,ping通arm和PC,chmod+x等等啊。

但是,注意啊,这里有个但是。

运行的时候,我在我的QT上面没有看到流畅的视频流,只要半个头,而且一闪一闪的,当时那个心理落差啊。还我帅锅!

这个问题本人搞了两天才搞定,当然现在也不是完全明白!刚开始我还怀疑是不是ARM板上的网卡驱动有问题。你想啊,我在PC上抓拍并显示的好好的,怎么在ARM上就不行了。

问题是什么呢,到底是什么问题呢,呵呵,别急啊。

关于32768(文件大小)这个问题

如果逻辑思维够强,你一定会去看QT的调试输出;如果你足够细心,你一定能看出里面的问题。

这里能看到以下信息:

1、CorruptJPEG data: premature end of data segment

回答:这个是说一个图片数据还未接收完,就被显示了。

2、Starting/yanjl/qt_build/arm/qt_v4l2_jpgs/hh/qtsocket...

listenis ok! 
startread picture! 
"datelen: 2920 , total_size 2920
"
"length:2920 
"
startread picture! 
"datelen: 29848 , total_size 32768
"
"length:32768 
"
startread picture! 
"datelen: 5840 , total_size 5840
"
"length:5840 
"
startread picture! 
"datelen: 1460 , total_size 7300
"
"length:7300 
"
startread picture! 
"datelen: 23360 , total_size 30660
"
"length:30660 
"
startread picture! 
"datelen: 2108 , total_size 32768
"
"length:32768

回答:

2920+29848=32768
5840+1460+23360+2108=32768

包的发送过程。发了32768,但是readall()并不马上接收32768,而是分段接收!对了,这就是问题的所在!我觉得这里跟步骤4很像啊,有木有!?

当然我只知道是这样的,但是不知道为什么。同一台PC上面,socket的发送与接收都是一次性的。我也不知道为什么。

解决这个问题:收到包数据为一帧数据完整时,才显示

if(total_size<=32768)
{
bytearray1= tcpClientConnection->readAll();
bytearray2.append(bytearray1);//bytearray添加到bytearray2后面
bytearray1.clear();
}
if(total_size==32768)

{显示图片}

我一直想有没什么函数能一次读到数据的。

这里的分段有规律吗。我一直以为TPC最大包的大小是64k,我才发了32k而已。还有我后来搞的图片大了,640*480,是一百多k,客户端那边既然能一次发送完。是不是发送大小无限制。

最后我还测了图片的接收速度,每秒钟8、9帧左右。

(10)、总结

在这里我感谢曹工这段时间的指导和栽培,曹工更多的时候是教我们学习的方法,该怎么去思考。这段时间我过得很开心也很充实,认识了纪柯和东东,对未来也更有信心。

就到这吧。多多指教。


https://www.xamrdz.com/web/2zj1942504.html

相关文章: