WIFI智能摄像头项目复述

        项目基于C/S架构,由嵌入式端、云服务器端和客户端三个部分组成。嵌入式端采用的是友善NanoPi Duo2开发板,它自带wifi模块,支持连接OV5460摄像头,可以搭载ubuntu操作系统,它官方文档资源丰富,不仅提供了用来烧录的系统固件,交叉编译工具,提供的系统内还自带了mjpg-streamer和WiringNP的开源库(当然还是要去Github下载源码好在PC机内进行交叉编译的),十分适合用来作为搭载摄像头的嵌入式芯片,同时配置了两个树莓派的舵机连接NanoPi Duo2的GPIO口以控制摄像头的上下左右转动;服务器用的是阿里云公网服务器,主体结构使用TCP+多线程完成,可以实现多个摄像头和多个客户端进行通信,一个摄像头可以对应同时传输数据给多个客户端;客户端使用QT6.51进行开发,主要来显示从服务器收到的图像以及发送一些控制指令, 控制指令的传输使用 json 格式,这样方便服务器的解析,也能减小传输的数据量。

        有关NanoPi Duo2、舵机的配置和控制,mjpg-streamer、WiringNP 和json-C库等开源库的移植及环境变量等的配置、用法的熟悉要花去本项目前前期相当多的时间,好在丰富的官方资料文档足以解决绝大部分困难。

        在通信方式的选择上,我一开始打算采用TCP+UDP的通信方式,及摄像端或客户端先通过TCP主动连接至服务器,服务器再把为对端开辟的UDP套接字的端口号发送给摄像端或客户端(可以提前建立),之后TCP就用来传输舵机命令,UDP用来传递摄像端从mjpg-streamer获取到的jpeg视频图片;但是后来发现,首先我租借的阿里云服务器不支持一下接收超过10100字节的UDP包,因此必须在应用层进行UDP分包后再传输,这不仅麻烦还让我考虑到UDP的不可靠传输问题可能比我想象的更加严重。因为我的数据链路层最多支持1500字节的MTU单元,就算不在应用层进行分包也会在网络层进行IP报分割,而一视频帧大概几万字节数据,要被分为几十个UDP包进行网络传输,又由于传输的图片是原生jpeg格式的,一旦某张图片的一部分数据出现问题,则整张图片都可能将无法成功解析,因此为了视频的连续效果考虑可能还不得不在应用层手动实现UDP包的重传机制,以可靠传输。与其如此,不如直接采用TCP协议进行通信。

        第二次,我打算采用TCP+TCP的方式实现通信,前者传输json-c格式的命令,后者传输数据,但在实现细节上出了一些问题,因为我突发奇想,让服务器接收到摄像端或客户端的TCP连接后,直接根据accept函数返回的地址向对端建立数据连接,这样可以省略一个来回。然而我忽略了,摄像端和客户端所在网络的NAT映射问题,服务器不能以新开辟的套接字的端口号主动访问客户端或摄像端的,因为它们所在网络NAT映射表上没有对应记录。因此还是只能采用第一次的那种两个来回的方法,让服务器通过第一个TCP命令连接通知客户端主动向TCP发起TCP数据连接。

        接下来就是常规的TCP网络通信的过程了,摄像端主要负责通过http协议以本地TCP方式从本机的mjpg-streamer获取视频帧,解包后将视频帧数据直接转发给公网服务器,这个过程中要注意处理TCP粘包问题,本项目在从mjpg-streamer获取视频帧阶段采用两个缓冲区配合来解决粘包问题,一个小的用来存放http报头,大的用来存放视频帧数据,根据http报头中解析到的视频帧大小,将小缓存区内多余的不属于报头部分的内容先拷贝到大缓存区,再由大缓存区接收剩余视频帧数据,即可解决问题,报尾部分用任一缓存区接收都可。至于在摄像端将视频帧转发给服务器及服务器将视频帧转发给客户端的过程中,只要在视频帧数据前,先将视频帧大小以网络字节序的形式发出就可以解决了。

        服务器端程序比较复杂,主要因为涉及到多线程,有线程通信和线程安全方面的问题。概括来说,就是要有一个链表记录摄像端和客户端的信息,包括双发在本端的套接字、存放视频帧的缓冲区以及确保线程同步和安全的互斥锁、条件变量等资源。通过在这个链表中登记或删除信息,基本上就能够完成一摄像端对应多客户端的问题了,当然这个对应的客户端数量是有限制的,以免给服务器造成过大压力。此处本质上就是一个生产者消费者模型。

        客户端的实现就比较草草了,网路通信过程与摄像端类似,本项目只简单采用了QLabel+QPixmap不断刷新来显示视频,其余就是创建好界面绑定好信号即可。其间我在客户端开发过程中遇到的一个比较严重的问题就是,QTcpSocket的相关操作是运行在另一个线程中的,然而我当时却不知道这一点,将QTcpSocket引发的错误交给一个槽处理后,弹出一个QMessageBox警告框,此时就会卡死,原因就是因为在子线程内试图队主线程的框体界面进行操作,解决方法就是通过注册元对象和qt的事务机制让弹出QMessageBox警告框的操作交由主线程完成。还有就是遇到了qt6.51MSCV版的bug,即QTcpSocket即使没有成功建立连接也会发出connected信号,这是被证实了的bug:

        根据在Qt的bug报告系统中被记录和讨论中的信息,这个bug主要发生在Windows平台上,当使用IPv6地址或者无效的IPv4地址时,因为Windows的套接字实现会在连接失败时返回一个错误码,而不是一个错误事件。而QTcpSocket类没有正确地处理这个错误码,而是误认为连接成功了,从而发出了cornected信号。通过更新qt版本或安装补丁,再或通过一些二次判断就可以解决这个问题。

        第二个则不清楚是不是qt的bug所致了,因为考虑到效率原因,客户端接收视频数据的子线程用的是Windows系统的原生C语言系统接口。而子线程本身是在主线程中的QThread创建的,随后将处理任务的对象传进子线程的方式来开始线程任务。问题是当子线程因收到主线程的结束信号,而清理资源并退出run函数之后,QThread总是认为子线程仍在运行,导致程序阻塞在wait处(如此时直接调用deletelater则将导致内存错误),最后不得不在子线程对象run函数的结尾处加上 QThread::currentThread()->exit();来让子线程真正退出,才解决bug。

        可以改进的地方:

        第一个,可以在客户端也应用上生产者消费者模型,采用环形队列方式,多缓存一些视频帧,这样应该可以以牺牲一定的视频实时性为代价,而增加在遇到网络波动时视频的流畅度。

        第二个,对于高清图像的传输,还是应该加上压缩,我目前知道的压缩方法有 P 帧+I 帧,就是对相似的图片做处理,只保留有差异的数据。这个也是我后面毕设要研究的方向。

        第三个,服务器端,不应该有多个 socket 存在。可以只保留一个 TCPsocket 和 一个 UDPsocket,这样可以节省更多的服务器资源

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值