Repository: feiyangqingyun/QWidgetExe Branch: master Commit: 637a2d118faf Files: 16 Total size: 191.6 KB Directory structure: gitextract_qe57eg3v/ ├── LICENSE ├── README.md ├── lightbutton/ │ ├── frmlightbutton.cpp │ ├── frmlightbutton.h │ ├── frmlightbutton.ui │ ├── lightbutton.cpp │ ├── lightbutton.h │ ├── lightbutton.pro │ └── main.cpp ├── snap_dataout/ │ └── readme.md ├── snap_iottool/ │ └── readme.md ├── snap_map/ │ └── readme.md ├── snap_mapwidget/ │ └── readme.md ├── snap_video_camera/ │ └── readme.md ├── snap_video_demo/ │ └── readme.md └── snap_video_gb28181/ └── readme.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ 木兰宽松许可证, 第1版 木兰宽松许可证, 第1版 2019年8月 http://license.coscl.org.cn/MulanPSL 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第1版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的一方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括仅因您或他人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如您或您的“关联实体”直接或间接地(包括通过代理、专利被许可人或受让人),就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 条款结束。 如何将木兰宽松许可证,第1版,应用到您的软件 如果您希望将木兰宽松许可证,第1版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [2019] [name of copyright holder] [Software Name] is licensed under the Mulan PSL v1. You can use this software according to the terms and conditions of the Mulan PSL v1. You may obtain a copy of Mulan PSL v1 at: http://license.coscl.org.cn/MulanPSL THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v1 for more details. Mulan Permissive Software License,Version 1 Mulan Permissive Software License,Version 1 (Mulan PSL v1) August 2019 http://license.coscl.org.cn/MulanPSL Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v1 (this License) with following terms and conditions: 0. Definition Software means the program and related documents which are comprised of those Contribution and licensed under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, or are controlled by, or are under common control with a party to this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. Contribution means the copyrightable work licensed by a particular Contributor under this License. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed, excluding of any patent claims solely be infringed by your or others’ modification or other combinations. If you or your Affiliates directly or indirectly (including through an agent, patent licensee or assignee), institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability The Software and Contribution in it are provided without warranties of any kind, either express or implied. In no event shall any Contributor or copyright holder be liable to you for any damages, including, but not limited to any direct, or indirect, special or consequential damages arising from your use or inability to use the Software or the Contribution in it, no matter how it’s caused or based on which legal theory, even if advised of the possibility of such damages. End of the Terms and Conditions How to apply the Mulan Permissive Software License,Version 1 (Mulan PSL v1) to your software To apply the Mulan PSL v1 to your work, for easy identification by recipients, you are suggested to complete following three steps: i. Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii. Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii. Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [2019] [name of copyright holder] [Software Name] is licensed under the Mulan PSL v1. You can use this software according to the terms and conditions of the Mulan PSL v1. You may obtain a copy of Mulan PSL v1 at: http://license.coscl.org.cn/MulanPSL THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v1 for more details. ================================================ FILE: README.md ================================================ ## 0 前言说明 1. **项目作品:[https://qtchina.blog.csdn.net/article/details/97565652](https://qtchina.blog.csdn.net/article/details/97565652)** 2. **视频主页:[https://space.bilibili.com/687803542](https://space.bilibili.com/687803542)** 3. **网店地址:[https://shop244026315.taobao.com](https://shop244026315.taobao.com)** 4. **联系方式:QQ(517216493)微信(feiyangqingyun)推荐加微信。** 5. **公 众 号:Qt实战/Qt入门和进阶/Qt教程/Qt软件** 6. **版本支持:所有项目已经全部支持Qt4/5/6所有版本以及后续版本。** 7. 监控作品体验:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 8. 其他作品体验:[https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A](https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A) 提取码:o05q 9. 监控系统在线文档:[http://www.qtcdev.com/video_system/](http://www.qtcdev.com/video_system/) 10. 大屏系统在线文档:[http://www.qtcdev.com/bigscreen/](http://www.qtcdev.com/bigscreen/) 11. 物联网系统在线文档:[http://www.qtcdev.com/iotsystem/](http://www.qtcdev.com/iotsystem/) ## 1 推流综合应用 ### 1.1 功能特点 1. 支持各种本地音视频文件和网络音视频文件,格式包括mp3、aac、wav、wma、mp4、mkv、rmvb、wmv、mpg、flv、asf等。 2. 支持各种网络音视频流,网络摄像头,协议包括rtsp、rtmp、http等。 3. 支持本地摄像头设备推流,可指定分辨率、帧率、格式等。 4. 支持本地桌面采集推流,可指定屏幕索引、采集区域、起始坐标、帧率等,也支持指定窗口标题进行采集。 5. 可实时切换预览视频文件,可切换音视频文件播放进度,切换到哪里就推流到哪里。预览过程中可以切换静音状态和暂停推流。 6. 可指定重新编码推流,任意源头格式可选强转264或265格式。 7. 可转换分辨率推流,设置等比例缩放或者指定分辨率进行转换。 8. 推流的清晰度、质量、码率都可调,可以节约网络带宽和拉流端的压力。 9. 音视频文件自动循环不间断推流。 10. 音视频流有自动掉线重连机制,重连成功自动继续推流。 11. 支持各种流媒体服务程序,包括但不限于mediamtx、ZLMediaKit、srs、LiveQing、nginx-rtmp、EasyDarwin、ABLMediaServer。 12. 通过配置文件自动加载对应流媒体程序的协议和端口,自动生成推流地址和各种协议的拉流地址。可以通过配置文件自己增加流媒体程序。 13. 可选rtmp、rtmp格式推流,推流成功后,支持多种格式拉流,包括但不限于rtsp、rtmp、hls、flv、ws-flv、webrtc等。 14. 在软件上推流成功后,可以直接单击网页预览,实时预览推流后拉流的画面,多画面网页展示。 15. 软件界面上可单击对应按钮,动态添加文件和目录,可手动输入地址。 16. 推拉流实时性极高,延迟极低,延迟时间大概在100ms左右。 17. 极低CPU资源占用,4路主码流推流只需要占用0.2%CPU。理论上常规普通PC机器推100路毫无压力,主要性能瓶颈在网络。 18. 可以推流到外网服务器,然后通过手机、电脑、平板等设备播放对应的视频流。 19. 每路推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照策略随机生成hash值。也支持自动按照指定标识后面加数字的方式递增命名。比如设置标识为字母v,策略为标识递增,则每添加一个对应的推流码命名依次是v1、v2、v3等。 20. 根据推流协议自动转码格式,默认策略按照选择的推流协议,比如rtsp支持265而rtmp不支持,如果是265的文件而选择rtmp推流,则自动转码成264格式再推流。 21. 音视频同步推流,在拉流和采集的时候就会自动处理好同步,同步后的数据再推流。 22. 表格中实时显示每一路推流的分辨率和音视频数据状态,灰色表示没有输入流,黑色表示没有输出流,绿色表示原数据推流,红色表示转码后的数据推流。 23. 自动重连视频源,自动重连流媒体服务器,保证启动后,推流地址和打开地址都实时重连,只要恢复后立即连上继续采集和推流。 24. 根据不同的流媒体服务器类型,自动生成对应的rtsp、rtmp、hls、flv、ws-flv、webrtc拉流地址,用户可以直接复制该地址到播放器或者网页中预览查看。 25. 添加的推流地址等信息自动存储到文件,可以手动打开进行修改,默认启动后自动加载历史记录。 26. 可以指定生成的网页文件保存位置,方便作为网站网页发布,可以直接在浏览器中输入网址进行访问,发布后可以直接在局域网其他设备比如手机或者电脑打开对应网址访问。 27. 可选是否开机启动、后台运行等。网络推流添加的rtsp地址可勾选是否隐藏地址中的用户信息。 28. 自带设备推流模块,自动识别本地设备,包括本地的摄像头和桌面,可以手动选择不同的是视频和音频采集设备进行推流。 29. 自带文件点播模块,添加文件后用户可以拉取地址点播,用户端可以任意切换播放进度。支持各种浏览器(谷歌chromium、微软edge、火狐firefox等)、各种播放器(vlc、mpv、ffplay、potplayer、mpchc等)打开请求。 30. 文件点播模块实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。 31. 文件点播模块采用纯QTcpSocket通信,不依赖流媒体服务程序,核心源码不到500行,注释详细,功能完整。 32. 支持任意Qt版本(Qt4、Qt5、Qt6),支持任意系统(windows、linux、macos、android、嵌入式linux等)。 ### 1.2 效果图 ![](snap_video_push/video_push_preview.jpg) ## 2 监控onvif组件 ### 2.1 功能特点 1. 广播搜索设备,支持IPC和NVR,依次返回。 2. 可选择不同的网卡IP进行对应网段设备的搜索。 3. 依次获取Onvif地址、Media地址、Profile文件、Rtsp地址。 4. 可对指定的Profile获取视频流Rtsp地址,比如主码流地址、子码流地址。 5. 可对每个设备设置Onvif用户信息,用于认证获取详细信息。 6. 可实时预览摄像机图像。 7. 支持云台控制,可上下左右调节云台,支持绝对移动、相对移动、连续移动三种方式,可对图像拉近拉远。 8. 支持获取预置位集合、调用预置位、添加预置位、删除预置位等。 9. 支持图片参数设置,包括亮度、对比度、饱和度、锐度等。 10. 支持Qt4和Qt6任意Qt版本以及后续Qt版本,亲测Qt4.7到Qt6.7。 11. 支持任意编译器,亲测mingw、msvc、gcc、clang。 12. 支持任意操作系统,亲测xp、win7、win10、android、linux、嵌入式linux、树莓派全志H3等。 13. 支持任意Onvif摄像机和NVR,亲测海康、大华、宇视、天地伟业、华为、海思芯片内核等,可定制开发。 14. 支持对指定IP地址及onvif地址进行单播搜索,比如跨网段情况下非常有用。 15. 支持指定过滤条件过滤搜索设备,比如只搜索某个网段的设备或者针对某个地址的设备。 16. 支持搜索间隔和搜索策略设置,保证所有设备搜索回来,在大量设备现场很有用(亲测上千个摄像机现场,搜索回来的设备数量比摄像机厂家自带搜索工具还要准确)。 17. 可对设备进行重启、网络参数获取等。 18. 支持各种事件订阅(入侵报警、越界报警、遮挡报警等)、Onvif抓图等操作。 19. 支持NTP校时和时间同步设置。 20. 支持OSD相关操作,可以增删改查OSD信息。 21. 内置了线程实时执行Onvif指令队列,排队最大速度的执行对应的指令,执行结果信号发出。 22. 采用的最底层的TCP+UDP通信机制,原创最底层协议解析,纯QtWidget编写。 23. 超级小巧轻量,总共约3000行代码,不依赖任何第三方的库和组件,跨平台。 24. 封装好了通用的数据发送和接收解析的函数,可以非常方便的自行拓展其他Onvif处理。 25. 工具上提供了收发数据文本框,显示收发的数据,方便查看和分析。 26. 支持所有Onvif设备,代码工整,接口友好,直接引入pri即可使用。 ### 2.2 效果图 ![](snap_video_onvif/video_onvif1.jpg) ![](snap_video_onvif/video_onvif2.jpg) ![](snap_video_onvif/video_onvif3.jpg) ![](snap_video_onvif/video_onvif4.jpg) ![](snap_video_onvif/video_onvif_app3.jpg) ## 3 监控gb28181组件 ### 3.1 功能特点 1. 支持设备注册、注销、心跳、校时、注册认证、注销认证等。 2. 设备上线后可以手动获取设备状态、设备信息、配置信息、预置位信息等。 3. 设备上线后自动获取设备通道信息,包括中文通道名称。识别到通道上线离线变化,会重新获取该设备的所有通道信息。 4. 支持视频点播,可以分别点播主码流和子码流,内置rtp解包线程,解包后发给视频播放组件解码播放。 5. 每个设备每个通道支持点播多个视频,通过ssrc区分,支持共用端口和不同端口收流。 6. 支持对某个设备下面所有通道、某个通道、某个通道对应的某个流分别关闭。 7. 支持录像文件查询和回放,回放控制支持暂停播放、继续播放、倍速播放、切换播放进度。 8. 支持录像文件下载,支持倍速比如8倍速下载,可同时多线程批量下载。 9. 回放和下载同时支持IPC和NVR,比如摄像头自带的SD存储卡录像文件回放,NVR上的硬盘录像文件回放。 10. 支持云台控制,向上、向下、向左、向右、左上、右上、左下、右下方位移动,镜头放大缩小,光圈放大缩小,镜头聚焦放焦。 11. 支持预置位信息的查询、调用、添加、修改、删除等操作。 12. 自动目录订阅功能,通道上线下线都有对应的信号通知。 13. 内置定时读取通道信息机制,以保证通道信息是最新的,比如有些NVR是不断更新的通道信息。 14. 内置订阅警情和位置移动功能,订阅后各种警情事件比如运动目标检测报警、入侵检测报警、徘徊检测报警等自动上报。 15. 支持语音对讲功能,可以直接在视频窗体的悬浮条上单击语音对讲按钮,再次单击关闭对讲,对讲期间悬浮条常驻显示。 16. 支持设备布防撤防,布防后警情信息会主动上报。 17. 国标服务同时支持udp和tcp方式,可选只监听一种或者两种都监听,tcp方式自动处理粘包问题。 18. 国标拉流同时支持udp、tcp被动、tcp主动三种方式,每个通道都可以自由选择何种拉流方式。 19. 内置拉流端口池,每次拉流从中取出一个,关闭流自动回收端口号,重复利用。 20. 收流端口自动纠错,自动跳过被占用的端口,不会出现端口占用导致收流失败的情况。 21. 支持三种取流方式自动检测离线重连,检测到离线后,自动重启点播拉流整个流程。 22. 录像文件回放,上一个完成后自动切换到下一个继续回放,直到所有回放完成。支持高达8倍速回放。 23. 视频播放自适应硬解码,极低资源占用,实时性极好,带悬浮条显示视频流信息,可以直接在悬浮条单击按钮保存录像文件到本地。 24. 支持几千路国标消息交互并发,实时视频流支持64路同时显示,可以拓展更多路数。 25. 支持阿里云等云服务器,可以分别设置内网监听地址和外网访问地址,一般云服务器上是监听地址用内网,对外访问用外网地址。 26. 支持视频分发,也就是推流,视频通道打开后可以自动推流到流媒体服务器,其他需要的地方拉流即可,支持rtsp、rtmp、hls、webrtc等方式拉流。 27. 视频分发也叫推流分发,表格方式展示正在推流的信息,其中包括显示统计哪些流正在被多少个地址拉取,比如有两个地方通过rtsp打开了取流,则对应推流地址行所在rtsp列显示数量2,非常直观的展示有多少个拉流。 28. 视频分发支持无人观看超时自动关闭推流和点播,提高带宽的利用率,没人观看太久的时候,没必要点播拉流和推流。在后台服务模式下,通道推流自动复用,当该通道已经存在点播推流,则复用该路流数据,不会再去点播,节约资源。 29. 提供后台服务功能,定义了一套私有协议,根据私有协议进行交互,支持tcp、http、mqtt等方式交互,方便第三方程序接入集成。通信协议非常完整,支持获取设备列表、获取指定通道视频地址、云台控制、预置位操作、录像查询、录像回放、录像下载、回放倍速等控制、警情消息通知、视频点播和关闭等。 30. 支持注册重定向,方便做负载均衡和区域化部署,这样可以支持几十万个设备连接都没问题。 31. 支持图像抓拍,可以设置抓拍最多10张图片,可设置抓拍间隔,抓拍到的图片会通过信号通知。 32. 实时预览和录像回放都支持推流,推流支持叠加文字和图片水印以及各种ffmpeg支持的滤镜效果,支持多个水印同时叠加。 33. 同时支持gb28181-2011、gb28181-2016、gb28181-2022以及后续可能的所有协议版本。 34. SIP解析和交互采用纯Qt底层代码实现,udp/tcp通信交互,祖传原创代码解析,不依赖任何第三方。 35. 代码量少,gb28181交互部分共几千行代码,注释详细,接口友好,使用极其简单,提供非常详细的使用示例。 36. 支持海康、大华、宇视、华为、天地伟业等所有国标设备,包括一些没有ssrc的设备。 37. 支持所有Qt版本和编译器以及操作系统,包括但不限于win、linux、mac、android、嵌入式linux、树莓派香橙派、国产os等。 ### 3.2 效果图 ![](snap_video_gb28181/snap/video_gb28181_0.jpg) ![](snap_video_gb28181/snap/video_gb28181_1.jpg) ![](snap_video_gb28181/snap/video_gb28181_2.jpg) ![](snap_video_gb28181/snap/video_gb28181_3.jpg) ![](snap_video_gb28181/snap/video_gb28181_4.jpg) ![](snap_video_gb28181/snap/video_gb28181_5.jpg) ![](snap_video_gb28181/snap/video_gb28181_6.jpg) ![](snap_video_gb28181/snap/video_gb28181_7.jpg) ![](snap_video_gb28181/snap/video_gb28181_8.jpg) ## 4 地图综合应用 ### 4.1 功能特点 1. 支持多种地图内核,默认采用天地图,可选百度地图、高德地图、腾讯地图、谷歌地图、通用地图等。 2. 同时支持在线地图和离线地图两种模式,离线地图方便在不联网的场景中使用。 3. 支持各种地图控件的启用,比如地图导航、地图类型、缩略图、比例尺、全景导航、实时路况、绘图工具、结果面板等。 4. 支持多种地图功能的动态启用禁用,比如地图拖曳、键盘操作、滚轮缩放、双击放大、连续缩放、地图测距等。 5. 提供众多js函数接口用于交互,参数极其丰富,能够想到的应用场景需求都有实现。 6. 统一的信号槽机制,地图中的结果统一信号发送出去,收到后根据type类型区分。 7. 支持地图交互,比如鼠标按下获取对应位置的经纬度。单击标注点弹出对应点的信息。 8. 支持添加标注、删除标注、移动标注、清空标注,支持更新标注的图片、尺寸、位置、旋转角度等。 9. 标注点可以指定图标图片和尺寸,支持gif动图,支持指定以图片中心对齐还是底部中心对齐。可以设置旋转角度,带富文本提示信息。 10. 所有覆盖物比如多边形、矩形、圆形、标注点灯,都支持动态绑定单击、双击、拖曳开始、拖曳结束等事件,对应信号发出来,可以根据对应的信号处理逻辑,比如拖曳期间更新折线的坐标点集合。 11. 标注点事件支持单击发信号通知和自己弹框显示信息,弹框信息支持html富文本。 12. 提供地址转坐标和坐标转地址接口,同时支持在线和离线两种方式。 13. 支持各种图形绘制,包括标注点、折线图、多边形、矩形、圆形、弧线等。 14. 可显示悬浮的绘图工具栏,直接在地图上划线、标注点、矩形、圆形等。 15. 支持各种区域搜索,比如矩形区域、圆形区域,可以按照关键字匹配将搜索结果显示在地图中。 16. 可动态添加离线的行政区边界点数据。可以搜索行政区划并获取该区域的边界点数据。数据可以保存到文件以便离线使用。 17. 支持点聚合功能,多个小标注点合并到一个大标注点,防止点密集导致交互不友好。 18. 可以添加海量点,每个点都可以单击获取对应坐标和信息。 19. 所有的覆盖物信息比如标注点、矩形、多边形、折线图等,都可以主动获取对应的信息比如坐标点和路径等。 20. 支持路径规划,支持公交路线、自驾路线、步行路线、骑行路线,不同查询支持不同策略,可选最少时间、最少换乘、不走高架等。 21. 路径规划结果可以显示在地图中,也可以获取到路径点坐标集合。这个数据可以保存到文件,以便发给机器人或者无人机做导航用来轨迹移动。 22. 可以设置不同的地图视图比如街道图、卫星图、混合图。 23. 可以设置不同的样式,比如午夜蓝、青草绿等样式风格。 24. 可以设置地图的旋转角度和倾斜角度,一般需要地图厂家支持以及浏览器支持webgl特性。 25. 提供经纬度坐标纠偏转换功能,比如传入的GPS坐标需要转换到百度地图坐标或者高德地图坐标。各种坐标系转换全部离线函数,支持地球坐标系WGS-84、火星坐标系GCJ-02、百度坐标系BD-09之间的互相转换,涵盖了各种地图的坐标系。 26. 提供动态轨迹点移动功能,按照给定的经纬度坐标集合平滑移动,就算是两个点之间移动也会自动计算生成多个平滑的路径点来移动。 27. 所有函数接口都提供了非常详细的使用示例,满足各个水平区间的人员学习使用。 28. 组件同时支持qwidget和qml,支持编译到安卓系统运行,安卓上也支持离线运行。 ### 4.2 其他功能 1. 地图示例中分别提供了标注点示例、覆盖物示例、路径规划示例、兴趣点搜索示例、其他示例等,非常详细。 2. 提供离线地图下载模块,可以选择不同的地图内核,比如百度地图或者谷歌地图,不同的地图类型,比如下载街道图还是卫星图,不同的地图层级,多线程批量极速下载。 3. 表格行实时显示对应的瓦片下载进度,有下载超时时间,重试次数,每个瓦片下载完成都发送信号通知,参数包括下载用时。 4. 提供省市轮廓图下载模块,自动下载各个地区的轮廓图,保存到脚本文件或者文本文件。 5. 支持手动调整不同区域的轮廓边界,调整后可以主动获取调整后的边界点集合。 6. 提供动态点位示例,手动在地图上选点并添加标注,附带自定义的信息比如速度和时间等。 7. 提供海量点位示例,批量添加标注点、点聚合、海量点。用于测试环境中支持的最大点位性能。 8. 提供动态轨迹示例,在地图上鼠标按下选择起点和终点后,查询路线,获取路径轨迹点,模拟轨迹平滑移动。可以筛选数据将过多的路径点筛选到设定的点数。 9. 提供轨迹回放示例,按照指定的轨迹点列表回放,也可以导入轨迹点数据进行回放。同时支持在街道图、卫星图、混合图中回放轨迹。 10. 提供高级绘图示例,可以动态添加、删除、修改、清空图元,可以对地图上的所有覆盖物开启编辑和关闭编辑,编辑阶段直接拖动位置和大小。可以对地图上的所有图元开启监听,监听拖动等事件,拿到对应信号进行处理。 11. 提供雷达扫描示例,演示在地图组件基础上进行二开,封装了雷达扫描类,扫描的同时有几个标注点移动。 12. 提供航线模拟示例,可以设置移动的间隔和每次移动的步长,可以设置顺时针还是逆时针旋转。一个飞机图标圆形旋转。 13. 提供航线规划示例,可以直接在地图上动态添加标注点,两个点之间直线相连,同时有箭头指示方向和角度,选中点高亮显示。 14. 提供高程海拔示例,演示传入经纬度获取对应点的高程海拔数据,无需联网离线使用,瞬间出结果。 15. 提供省市区域地图示例,采用echart组件,同时支持闪烁点图、迁徙图、区域地图、世界地图、仪表盘等。可以设置标题、提示信息、背景颜色、文字颜色、线条颜色、区域颜色等各种颜色。 16. 省市区域地图示例,内置世界地图、全国地图、省份地图、地区地图,可以精确到县,所有地图全部离线使用。可设置城市的名称、值、经纬度集合。 17. 内置通用浏览器组件,同时支持webkit/webengine/miniblink等内核。提供网页控件示例,演示打开网页和本地网页文件。 18. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。 ### 4.3 效果图 ![](snap_map/snap/map100.jpg) ![](snap_map/snap/map200.jpg) ![](snap_map/snap/map300.jpg) ![](snap_map/snap/map400.jpg) ![](snap_map/snap/map500.jpg) ![](snap_map/snap/map600.jpg) ![](snap_map/snap/map700.jpg) ![](snap_map/snap/map800.jpg) ![](snap_map/snap/map900.jpg) ## 5 自定义控件大全 ### 5.1 功能特点 1. 超过205个精美控件并持续不断迭代更新升级,种类超多,控件类型极其丰富。 2. 涵盖了各种仪表盘、进度条、进度球、指南针、曲线图、标尺、温度计、导航条、导航栏,flatui、高亮按钮、滑动选择器、农历、广告轮播、饼状图、环形图、时间轴、拓展控件、增强控件等。 3. 每个类都是独立的一个.h头文件和.cpp实现文件组成,零耦合,不依赖其他文件,方便单个控件独立出来以源码形式集成到项目中,方便直观。 4. 控件数量远超其他第三方控件库比如qwt集成的控件数量,使用方式也比其简单友好零耦合。 5. 支持任意Qt版本,亲测Qt4/5/6的所有版本,全部纯Qt编写,QWidget+QPainter绘制。 6. 支持任意编译器,包括但不限于mingw、msvc、gcc、clang等编译器。 7. 支持任意操作系统,包括但不限于windows、linux、mac、android、uos、银河麒麟、各种国产linux、嵌入式linux、树莓派、香橙派、全志H3等。 8. 支持编译生成设计师插件,可直接集成到QtCreator的控件栏中,和自带的控件一样使用,大部分效果只要设置几个属性即可,极为方便。 9. 支持编译生成独立的非插件形式的动态库文件,体积小,比如嵌入式linux不支持designer只需要动态库的形式。 10. 每个控件都有一个单独的完整的使用demo,方便参考学习单个控件使用,非常适合初学者。 11. 提供一个所有控件使用的集成的example,方便快速查看所有控件的效果。 12. 支持直接源码集成到example的方式,方便编译到安卓,for web套件等。 13. 支持编译成wasm文件,直接网页运行,可以在谷歌、火狐、edge等浏览器运行,原生性能。 14. 每个控件的源代码都有详细中文注释,都按照统一设计规范编写,方便学习自定义控件的编写。 15. 每个控件都内置默认配色,demo对应的配色都非常精美。 16. 部分控件提供多种样式风格选择,多种指示器样式选择。 17. 所有控件自适应布局和窗体拉伸变化,自动缩放。 18. 配套额外的自定义控件属性设计器,类似组态设计器,纯中文属性名称,支持拖曳设计,所见即所得,支持导入导出xml格式。 19. 集成fontawesome图形字体+阿里巴巴iconfont收藏的几百个图形字体,享受图形字体带来的乐趣。 20. 所有控件最后生成一个dll动态库文件,可以直接集成到qtcreator中拖曳设计使用。 21. 控件源码全部分门别类存放,pri模块形式集成,提供控件对照表快速查找对应控件和说明。 ### 5.2 效果图 ![](snap_quc/000.gif) ## 6 属性组态设计器 ### 6.1 功能特点 1. 自动加载插件文件中的所有控件生成列表,默认自带的控件超过120个。 2. 拖曳到画布自动生成对应的控件,所见即所得。 3. 右侧中文属性栏,改变对应的属性立即应用到对应选中控件,直观简洁,非常适合小白使用。 4. 独创属性栏文字翻译映射机制,效率极高,可以非常方便拓展其他语言的属性栏。 5. 所有控件的属性自动提取并显示在右侧属性栏,包括枚举值下拉框等。 6. 支持手动选择插件文件,外部导入插件文件。 7. 可以将当前画布的所有控件配置信息导出到xml文件。 8. 可以手动选择xml文件打开控件布局,自动根据xml文件加载控件。 9. 可拉动滑动条、勾选模拟数据复选框、文本框输入,三种方式来生成数据应用所有控件。 10. 控件支持八个方位拉动调整大小,自适应任意分辨率,可键盘上下左右微调位置。 11. 左侧控件列表栏控件名称中文显示。 12. 支持将选中控件移到最前面和移到最后面显示。 13. 可以一键删除当前选中控件和清空所有控件。 14. 支持自定义属性,可以填写中文属性名称和对应的值,导出带自定义属性。 15. 打通了串口采集、网络采集、数据库采集三种方式设置数据。 16. 作为组态的雏形软件,可以自由拓展自己的功能。 17. 代码极其精简,注释非常详细,可以作为组态的雏形,自行拓展更多的功能。 18. 纯Qt编写,支持任意Qt版本、任意编译器、任意系统。 ### 6.2 效果图 ![](snap_property/property.jpg) ## 7 跨平台输入法 ### 7.1 功能特点 1. 纯QWidget编写,原创输入法机制,没有任何第三方动态库的依赖。 2. 支持各种Qt版本,包括Qt4、Qt5、Qt6及后续版本。 3. 支持各种编译器,包括mingw、msvc、gcc、clang、wasm等。 4. 支持各种目标平台,包括windows、linux、macos、android、嵌入式linux等。 5. 支持任意控件输入,包括文本框、微调框、可编辑下拉框、表格行等,以及网页中的输入控件。 6. 支持中文、英文、数字、繁体、特殊字符、字母大小写等输入,可以自由切换。 7. 无需更改原有项目代码,不用任何额外代码,自动识别需要输入的控件,弹出输入法面板,不需要输入法代码写得到处都是。 8. 使用极为方便,通过源码集成到对应项目,源码是一个pri组件,只要在项目的pro文件引入即可。 9. 接口极其丰富,在众多输入应用场景中不断迭代完善至今。 10. 界面清晰简洁,UI美观友好,高仿IOS输入法,非常适合触摸设备。 11. 顶部单行文字面板和弹出多行多列文字面板选词,支持滑动选词。 12. 具有词汇记忆功能,之前选中过的词语放到候选词最前面。 13. 具有自由造词功能,可以直接打开词汇文件写入自定义词组,最高级别显示。 14. 造词支持单个拼音多个汉字,自动调整优先级。 15. 内置数字小键盘,在微调框这种只需要输入数字的地方,自动弹出数字小键盘,更美观。 16. 可通过设置弱属性,控制是弹出全键盘的数字面板还是数字小键盘。 17. 可设置回车后自动隐藏输入法面板还是要执行插入回车换行符。 18. 可对部分不需要输入的输入框设置禁用输入法。 19. 多版面字符页面,可以自行拓展各种字符展示,可用于多语言输入。 20. 输入法面板尺寸自由设置,采用布局自适应任何分辨率,同时适用于一些小分辨率的设备。 21. 输入法面板位置支持控件正下方/正上方、永远居中、底部拉伸填充等多种方式。 22. 界面自适应屏幕大小,输入法弹出位置为控件底部时,当超过桌面右边或者底部时,自动调整位置。 23. 支持Qt程序嵌入的浏览器中的网页中的文本框等控件的输入。 24. 可以分别设置面板按键字体大小、候选词字体大小、图标尺寸、顶部高度等。 25. 属性控制数字输入,例如需要文本框默认弹出的是数字面板,则设置代码 ui->lineEdit->setProperty("flag", "number")。 26. 属性控制大写输入,例如需要文本框默认输入字母永远大写,则设置代码 ui->lineEdit->setProperty("upper", true)。 27. 自由控制需要显示输入法和不需要显示输入法,当某些控件不需要弹出输入法,只需要对应不需要弹出输入法的控件设置属性noinput为真即可。例如ui->lineEdit->setProperty("noinput", true)。 28. 实现了长按超过500毫秒重复执行按下的键的功能。例如长按退格键,不断删除。 29. 支持单拼、全拼、模糊拼音输入,智能分页算法,可任意翻页查看汉字词组。 30. 默认自带5种皮肤颜色,可通过代码设置皮肤样式,用户也可用QSS自定义皮肤。 31. 谷歌内核的输入法引擎,品质保证,字库文件1MB,不依赖数据库,资源占用低效率极高。支持模糊拼音,比如nh=你好。 32. 可选windows专有版本,支持外部程序输入,比如输入到记事本、QQ聊天窗口等。 33. 可选硬键盘同步输入版本,外接实体键盘,类似搜狗输入法弹出小的候选词面板,可以快捷键切换输入法状态。 34. 整个输入法核心代码行数1000行左右,非常小,不会对程序体积造成负担。 35. 代码结构极为清晰,注释详细,非常容易阅读和理解,可自行修改和拓展满足各种需求。 ### 7.2 效果图 ![](snap_input/input2019.gif) ## 8 自定义界面大全 ### 8.1 功能特点 1. 自带20套精美皮肤样式,分门别类有黑色系、深色系、扁平系、常规系等。 2. 皮肤生成器只需要简单几步就可以生成一套自定义的皮肤。 3. 自带了26种uidemo,非常漂亮美观,涵盖了主界面布局、菜单切换等各种效果,总有一款适合你。 4. 所有代码和demo注释都非常详细整齐整洁,非常适合初学者学习。 5. uidemo由简入难,可以一步步学习下去,从入门到熟悉。 6. uidemo从常规的客户端到app端到触摸端等都有,既有鼠标操作的也有触摸操作的。 7. 皮肤中的qss样式表内容,覆盖了几乎所有的控件,非常适合学习每个控件的qss样式如何设置,而且分门别类非常清晰。 8. 自带的qthelper类,集大成之所长,集多年功力于此,功能极其丰富。 9. 内置了自定义无边框的消息框、错误框、询问框、右下角信息框、输入框、日期范围选择框等。 10. 默认开启边框阴影效果,可以设置阴影颜色和边框粗细。 11. 对话框会根据内容的长度自动调整宽高,以便调整到最合适的大小。 12. 对话框支持倒计时关闭,调用的时候设置倒计时参数即可。 13. 内置万能图形字体类,上千套精美图形字体图标,只要指定值即可。 14. 通用无边框窗体,标题栏可以自定义图标、文字居中、右上角按钮,内置了下拉菜单换肤。 15. 集成CRC校验、获取应用程序文件名、文件路径、设置窗体居中显示、设置翻译文件、设置编码、设置延时、设置系统时间等各种静态方法,不满意找我。 16. 自适应多屏幕,自动识别当前鼠标在哪个屏幕就作为当前屏幕区域,包括右下角弹窗等坐标都精准定位。 17. 支持任意Qt版本、任意编译器、任意系统。 18. 持续更新迭代升级,可运行在windows、linux、macOS、嵌入式linux等各种系统上。 ### 8.2 效果图 ![](snap_uidemo/qui.gif) ![](snap_uidemo/uidemo25.gif) ![](snap_uidemo/uidemo26.gif) ## 9 自定义图表 ### 9.1 功能特点 1. 可设置X轴Y轴范围值。 2. 可设置背景颜色、文本颜色、网格颜色。 3. 可设置三条曲线颜色、颜色集合。 4. 可设置是否显示十字定位线,支持分别开启横向或者纵向定位线。 5. 可设置十字定位线的宽度、颜色。 6. 可设置是否显示数据点、数据点的大小。 7. 可设置是否填充背景颜色形成面积图。 8. 可设置各种交互模式,比如拖动、滚轮缩放等。 9. 可设置坐标轴间距、第二坐标系可见。 10. 提供接口setDataLine直接设置曲线,支持多条。 11. 提供接口setDataBar直接设置柱状图,支持多条形成堆积图。 12. 提供接口setLabs设置文本标签替代key,包括X轴和Y轴。 13. 提供清空数据、重绘图表、外部获取QCustomPlot对象等函数接口,这样就可以进行更加详细的参数设置。 14. 提供函数 start() 和 stop() 来模拟正弦曲线。 15. 可设置柱状图显示值、值的位置(顶部、中间、底部)、精确度、颜色。 16. 支持鼠标移动到数据点高亮显示数据点,显示数据提示信息。 17. 可设置提示信息位置:自动处理、顶部、右上角、右侧、右下角、底部、左下角、左侧、左上角等。 18. 可设置是否校验数据产生不同的背景颜色,柱状图的每根柱子都可以根据数据生成不同背景颜色,比如预警黄色、报警红色。 19. 可设置是否显示图例、图例位置、图例行数、图例单行显示。 20. 支持多条曲线、柱状图、柱状分组图、横向柱状图、横向分组图、柱状堆积图等。 21. 内置 N>15 套精美颜色,自动取颜色集合的颜色,省去配色的烦恼。 22. 每条柱状图都可以设置不同的颜,分组柱状图可以设置颜色交替。 23. Y轴数值支持百分比显示,可拓展改成成其他格式。 24. 内置平滑曲线算法,支持平滑曲线绘制,传入点集合即可。 25. 同时支持 QCustomPlot 1.3、2.0、2.1等后续所有版本。 26. 提供多坐标轴示例,一条曲线对应自己独立的坐标轴。 27. 提供多曲线图示例,一个曲线控件中绘制多条独立的曲线,曲线有自己的独立的坐标轴,非常适用于一个设备需要分开展示多条曲线的场景,而不是共用一套坐标系。 28. 支持Qt4-Qt6任意Qt版本,支持任意编译器、任意操作系统。 ### 9.2 效果图 ![](snap_customplot/customplot.gif) ## 10 视频综合应用组件 ### 10.1 基础功能 1. 支持各种音频视频文件格式,比如mp3、wav、mp4、asf、rm、rmvb、mkv等。 2. 支持本地摄像头设备和本地桌面采集,支持多设备和多屏幕。 3. 支持各种视频流格式,比如rtp、rtsp、rtmp、http、udp等。 4. 本地音视频文件和网络音视频文件,自动识别文件长度、播放进度、音量大小、静音状态等。 5. 文件可以指定播放位置、调节音量大小、设置静音状态等。 6. 支持倍速播放文件,可选0.5倍、1.0倍、2.5倍、5.0倍等速度,相当于慢放和快放。 7. 支持开始播放、停止播放、暂停播放、继续播放。 8. 支持抓拍截图,可指定文件路径,可选抓拍完成是否自动显示预览。 9. 支持录像存储,手动开始录像、停止录像,部分内核支持暂停录像后继续录像,跳过不需要录像的部分。 10. 支持无感知切换循环播放、自动重连等机制。 11. 提供播放成功、播放完成、收到解码图片、收到抓拍图片、视频尺寸变化、录像状态变化等信号。 12. 多线程处理,一个解码一个线程,不卡主界面。 ### 10.2 特色功能 1. 同时支持多种解码内核,包括qmedia内核(Qt4/Qt5/Qt6)、ffmpeg内核(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6)、vlc内核(vlc2/vlc3)、mpv内核(mpv1/mp2)、mdk内核、海康sdk、easyplayer内核等。 2. 非常完善的多重基类设计,新增一种解码内核只需要实现极少的代码量,就可以应用整套机制,极易拓展。 3. 同时支持多种画面显示策略,自动调整(原始分辨率小于显示控件尺寸则按照原始分辨率大小显示,否则等比缩放)、等比缩放(永远等比缩放)、拉伸填充(永远拉伸填充)。所有内核和所有视频显示模式下都支持三种画面显示策略。 4. 同时支持多种视频显示模式,句柄模式(传入控件句柄交给对方绘制控制)、绘制模式(回调拿到数据后转成QImage用QPainter绘制)、GPU模式(回调拿到数据后转成yuv用QOpenglWidget绘制)。 5. 支持多种硬件加速类型,ffmpeg可选dxva2、d3d11va等,vlc可选any、dxva2、d3d11va,mpv可选auto、dxva2、d3d11va,mdk可选dxva2、d3d11va、cuda、mft等。不同的系统环境有不同的类型选择,比如linux系统有vaapi、vdpau,macos系统有videotoolbox。 6. 解码线程和显示窗体分离,可指定任意解码内核挂载到任意显示窗体,动态切换。 7. 支持共享解码线程,默认开启并且自动处理,当识别到相同的视频地址,共享一个解码线程,在网络视频环境中可以大大节约网络流量以及对方设备的推流压力。国内顶尖视频厂商均采用此策略。这样只要拉一路视频流就可以共享到几十个几百个通道展示。 8. 自动识别视频旋转角度并绘制,比如手机上拍摄的视频一般是旋转了90度的,播放的时候要自动旋转处理,不然默认是倒着的。 9. 自动识别视频流播放过程中分辨率的变化,在视频控件上自动调整尺寸。比如摄像机可以在使用过程中动态配置分辨率,当分辨率改动后对应视频控件也要做出同步反应。 10. 音视频文件无感知自动切换循环播放,不会出现切换期间黑屏等肉眼可见的切换痕迹。 11. 视频控件同时支持任意解码内核、任意画面显示策略、任意视频显示模式。 12. 视频控件悬浮条同时支持句柄、绘制、GPU三种模式,非绝对坐标移来移去。 13. 本地摄像头设备支持指定设备名称、分辨率、帧率进行播放。 14. 本地桌面采集支持设定采集区域、偏移值、指定桌面索引、帧率、多个桌面同时采集等。还支持指定窗口标题采集固定窗口。 15. 录像文件同时支持打开的视频文件、本地摄像头、本地桌面、网络视频流等。 16. 瞬间响应打开和关闭,无论是打开不存在的视频或者网络流,探测设备是否存在,读取中的超时等待,收到关闭指令立即中断之前的操作并响应。 17. 支持打开各种图片文件,支持本地音视频文件拖曳播放。 18. 视频流通信方式可选tcp/udp,有些设备可能只提供了某一种协议通信比如tcp,需要指定该种协议方式打开。 19. 可设置连接超时时间(视频流探测用的超时时间)、读取超时时间(采集过程中的超时时间)。 20. 支持逐帧播放,提供上一帧/下一帧函数接口,可以逐帧查阅采集到的图像。 21. 音频文件自动提取专辑信息比如标题、艺术家、专辑、专辑封面,自动显示专辑封面。 22. 视频响应极低延迟0.2s左右,极速响应打开视频流0.5s左右,专门做了优化处理。 23. 支持H264/H265编码(现在越来越多的监控摄像头是H265视频流格式)生成视频文件,内部自动识别切换编码格式。 24. 支持用户信息中包含特殊字符(比如用户信息中包含+#@等字符)的视频流播放,内置解析转义处理。 25. 支持滤镜,各种水印及图形效果,支持多个水印和图像,可以将OSD标签信息和各种图形信息写入到MP4文件。 26. 支持视频流中的各种音频格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支持,推荐选择AAC兼容性跨平台性最好。 27. 内核ffmpeg采用纯qt+ffmpeg解码,非sdl等第三方绘制播放依赖,gpu绘制采用qopenglwidget,音频播放采用qaudiooutput。 28. 内核ffmpeg和内核mdk支持安卓,其中mdk支持安卓硬解码,性能非常凶残。 29. 可以切换音视频轨道,也就是节目通道,可能ts文件带了多个音视频节目流,可以分别设置要播放哪一个,可以播放前设置好和播放过程中动态设置。 30. 可以设置视频旋转角度,可以播放前设置好和播放过程中动态改变。 31. 视频控件悬浮条自带开始和停止录像切换、声音静音切换、抓拍截图、关闭视频等功能。 32. 音频组件支持声音波形值数据解析,可以根据该值绘制波形曲线和柱状声音条,默认提供了声音振幅信号。 33. 标签和图形信息支持三种绘制方式,绘制到遮罩层、绘制到图片、源头绘制(对应信息可以存储到文件)。 34. 通过传入一个url地址,该地址可以带上通信协议、分辨率、帧率等信息,无需其他设置。 35. 保存视频到文件支持三种策略,自动处理、仅限文件、全部转码,转码策略支持自动识别、转264、转265,编码保存支持指定分辨率缩放或者等比例缩放。比如对保存文件体积有要求可以指定缩放后再存储。 36. 支持加密保存文件和解密播放文件,可以指定秘钥文本。 37. 提供的监控布局类支持64通道同时显示,还支持各种异型布局,比如13通道,手机上6行2列布局。各种布局可以自由定义。 38. 支持电子放大,在悬浮条切换到电子放大模式,在画面上选择需要放大的区域,选取完毕后自动放大,再次切换放大模式可以复位。 39. 各组件中极其详细的打印信息提示,尤其是报错信息提示,封装的统一打印格式。针对现场复杂的设备环境测试极其方便有用,相当于精确定位到具体哪个通道哪个步骤出错。 40. 同时提供了简单示例、视频播放器、多画面视频监控、监控回放、逐帧播放、多屏渲染等单独窗体示例,专门演示对应功能如何使用。 41. 监控回放可选不同厂家类型、回放时间段、用户信息、指定通道。支持切换回放进度。 42. 可以从声卡设备下拉框选择声卡播放声音,提供对应的切换声卡函数接口。 43. 支持编译到手机app使用,提供了专门的手机app布局界面,可以作为手机上的视频监控使用。 44. 代码框架和结构优化到最优,性能强悍,注释详细,持续迭代更新升级。 45. 源码支持windows、linux、mac、android等,支持各种国产linux系统,包括但不限于统信UOS/中标麒麟/银河麒麟等。还支持嵌入式linux。 46. 源码支持Qt4、Qt5、Qt6,兼容所有版本。 ### 10.3 视频控件 1. 可动态添加任意多个osd标签信息,标签信息包括名字、是否可见、字号大小、文本文字、文本颜色、背景颜色、标签图片、标签坐标、标签格式(文本、日期、时间、日期时间、图片)、标签位置(左上角、左下角、右上角、右下角、居中、自定义坐标)。 2. 可动态添加任意多个图形信息,比如人工智能算法解析后的图形区域信息直接发给视频控件即可。图形信息支持任意形状,直接绘制在原始图片上,采用绝对坐标。 3. 图形信息包括名字、边框大小、边框颜色、背景颜色、矩形区域、路径集合、点坐标集合等。 4. 每个图形信息都可指定三种区域中的一种或者多种,指定了的都会绘制。 5. 内置悬浮条控件,悬浮条位置支持顶部、底部、左侧、右侧。 6. 悬浮条控件参数包括边距、间距、背景透明度、背景颜色、文本颜色、按下颜色、位置、按钮图标代码集合、按钮名称标识集合、按钮提示信息集合。 7. 悬浮条控件一排工具按钮可自定义,通过结构体参数设置,图标可选图形字体还是自定义图片。 8. 悬浮条按钮内部实现了录像切换、抓拍截图、静音切换、关闭视频等功能,也可以自行在源码中增加自己对应的功能。 9. 悬浮条按钮对应实现了功能的按钮,有对应图标切换处理,比如录像按钮按下后会切换到正在录像中的图标,声音按钮切换后变成静音图标,再次切换还原。 10. 悬浮条按钮单击后都用名称唯一标识作为信号发出,可以自行关联响应处理。 11. 悬浮条空白区域可以显示提示信息,默认显示当前视频分辨率大小,可以增加帧率、码流大小等信息。 12. 视频控件参数包括边框大小、边框颜色、焦点颜色、背景颜色(默认透明)、文字颜色(默认全局文字颜色)、填充颜色(视频外的空白处填充黑色)、背景文字、背景图片(如果设置了图片优先取图片)、是否拷贝图片、缩放显示模式(自动调整、等比缩放、拉伸填充)、视频显示模式(句柄、绘制、GPU)、启用悬浮条、悬浮条尺寸(横向为高度、纵向为宽度)、悬浮条位置(顶部、底部、左侧、右侧)。 ### 10.4 效果图 ![](snap_video_demo/video_demo1.jpg) ![](snap_video_demo/video_demo2.jpg) ![](snap_video_demo/video_demo3.jpg) ![](snap_video_demo/video_demo4.jpg) ![](snap_video_demo/video_demo5.jpg) ![](snap_video_demo/video_demo6.jpg) ![](snap_video_demo/video_demo7.jpg) ![](snap_video_demo/video_demo_android3.jpg) ## 11 音频综合应用组件 ### 11.1 功能特点 1. 自动计算音频振幅,绘制音频振幅曲线和音频数据曲线。 2. 支持音频录制,可选音频输入设备、采样频率、通道等参数,Qt5默认保存wav格式,Qt6默认保存mp3格式,Qt6可选wma、aac等格式。 3. 提供音频发送示例,将采集到的音频数据通过TCP或UDP发出去。 4. 提供音频接收示例,将网络TCP或UDP收到的音频数据播放出来。 5. 音频发送和接收,相当于语音对讲,响应延迟小于0.2s,实时性极高。 6. 可以编译成手机版本,形成手机和电脑等端语音对讲功能。 7. 可以拓展作为视频监控的语音对讲模块。 8. 源码支持Qt4、Qt5、Qt6,兼容所有版本。 ### 11.2 效果图 ![](snap_audio_demo/audio_demo1.jpg) ![](snap_audio_demo/audio_demo_android.jpg) ## 12 本地摄像头组件 ### 12.1 功能特点 1. 同时支持 qcamera、ffmpeg、v4l2 三种内核解析本地摄像头。 2. 提供函数 findCamera 自动搜索环境中的所有本地摄像头设备,搜索结果信号发出。 3. 支持自动搜索和指定设备两种模式,自动搜索模式下会将搜索到的第一个设备作为当前设备打开。 4. 支持同时打开多路设备,亲测4路,受限于具体的环境比如带宽。 5. 支持自动重连,默认开启,失败后会自动重新搜索和尝试打开。 6. ffmpeg方案、v4l2方案都支持回调模式(采集后转成QImage绘制)和句柄模式(采集后YUV数据GPU绘制,性能高)。 7. 视频显示位置自动调整算法,当视频分辨率超过显示控件大小则等比例缩放居中显示,不超过则原尺寸居中显示,还可设置拉伸填充显示。(自动调整、等比例缩放、拉伸填充)。 8. 可选不同的分辨率来打开摄像头,支持 160x120、320x240、640x480、800x600、1280x720、1280x960、1920x1080 等。 9. 可选不同的帧率来打开摄像头,支持 0(采用默认值)、5、、10、15、20、25、30 等。 10. 支持抓拍截图,传入文件名则自动保存截图文件,不传入则将图片数据QImage信号发出。 11. 提供函数接口 开始播放play、停止播放stop、暂停播放pause、继续播放next。 12. 支持动态热插拔加载,包括自动读取所有设备名称到下拉框。 13. 支持录像文件存储,提供开始录像recordStart、暂停录像recordPause、停止录像recordStop 等函数。 14. 提供二维码示例,自动采集画面识别二维码,支持自动将识别到的二维码重新生成大图。 15. 二维码识别支持设置热点区域,对该区域内的图片进行裁切并识别,在大分辨率图像采集的时候非常有用,提升速度和效率。 16. 支持选择图片文件解析二维码,手动输入文本内容生成二维码。 17. 提供图片传输示例,自动将打开的摄像头视频实时传输出去,服务器端接收后解析显示。此方案可以作为将本地的摄像头实时画面远程传输,比如嵌入式板子上的摄像头画面传输到PC端显示。 18. 支持等比例拉伸填充显示,画面宽高小于显示控件的宽高则以原视频大小为准,大于则按照显示控件的尺寸等比例缩放居中。 19. 视频控件悬浮条自带开始和停止录像切换、声音静音切换、抓拍截图、关闭视频等功能。 20. 音频组件支持声音波形值数据解析,可以根据该值绘制波形曲线和柱状声音条,默认提供了声音振幅信号。 21. 代码框架和结构优化到极致,性能彪悍,持续迭代更新升级。 22. 源码支持Qt4、Qt5、Qt6,兼容所有版本。 ### 12.2 效果图 ![](snap_video_camera/video_camera2.jpg) ![](snap_video_camera/video_camera3.jpg) ![](snap_video_camera/video_camera4.jpg) ## 13 物联网组件 ### 13.1 功能特点 1. 支持多种物联网通信协议,包括modbus和mqtt。 2. 协议方式支持串口com通信、网络tcp通信、网络udp通信、网络websocket通信。 3. 数据规则支持rtu模式和网络模式,网络rtu模式也就是modbus rtu over tcp/udp/websocket。相当于modbus串口协议数据走网络方式通信。 4. 支持批量连续写入寄存器数值和单个写入寄存器数值。 5. 支持数据顺序格式的设置,比如大端小端,高字节在前低字节在前的设置。支持Short_AB、Short_BA、Long_ABCD、Long_CDAB、Long_BADC、Long_DCBA、Float_ABCD、Float_CDAB、Float_BADC、Float_DCBA等。 6. 支持数据位字节数设置,比如短整型、长整型、浮点型等。常规的一般是2字节表示一个数据位,也有设备是4字节表示一个数据位,还有4字节浮点数的形式。后期可能还有8字节一个数据位。 7. 支持mqtt协议,可设置主机地址和端口、协议版本、唯一标号、用户名称、用户密码。 8. 支持mqtt发布主题、订阅主题、取消订阅。 9. 定时自动发布主题,可设置保活时间、超时时间、过期时间。mqtt通信自动重连。 10. mqtt模拟数据收发支持多种格式,文本、json、base64、hex等。 11. mqtt同时支持websocket方式,还支持ssl方式通信。 12. 支持多种采集通讯方式,包括串口和网络等,可自由拓展其他方式。可同时采集多路。 13. 自定义采集间隔(精确到毫秒)和超时次数,超时后自动将离线的文件从轮询队列中移除,加快轮询速度。 14. 可设置最大超时重连间隔,将离线的设备重新探测一次,保证设备恢复正常后能够重新加入轮询队列。 15. 同时提供了设备模拟工具,支持各种协议,支持设定多个设备的数据值。 16. 模拟工具可随机切换模拟数据值,要正常随机数据就模拟生成正常范围的数据,要报警数据就模拟生成报警范围的数据。方便测试。 17. 多线程采集和解析数据,以信号的方式发送解析结果,不卡主线程。 18. 架构采用基类继承方式,通用处理在基类,极易拓展其他通信方式。 19. 接口友好,使用非常简单,设置要采集的地址集合、开始索引集合、采集数量集合、数据顺序格式四个参数即可。会自动组装对应协议的数据发送。 20. 采集后的数据以统一格式的信号发出来,非常简单易用。支持浮点型数据。 21. 采集指令有优先级,如果有自定义的数据需要优先执行。可以将优先级高的指令调用append方法插入即可。可批量采集也可单个采集。 22. 支持利用现有的通信链路发送自定义数据,这个数据可以不是标准的modbus协议,比如有时候需要一些私有协议数据,利用现有链路发送下去执行。 23. 多线程高并发,每个端口采集都是一个独立的线程,互不干扰,支持成千上万个设备采集。 24. 代码做了兼容,支持各种编译器,同时支持Qt4、Qt5、Qt6。 25. 跨平台,支持windows、linux、mac、嵌入式linux、android、各种国产系统和开发板等。 ### 13.2 效果图 ![](snap_iottool/snap/iot_server1.jpg) ![](snap_iottool/snap/iot_mqtt.jpg) ## 14 秘钥生成器 ### 14.1 功能特点 1. 多种开关开启不同的秘钥功能限制。 2. 可以开启机器码限制,一机一码,一台设备对应唯一的一个机器码,自动获取机器码和秘钥文件的机器码核对。 3. 可以开启到期功能限制,超过了指定的日期后,对运行功能增加限制,比如视频上增加水印。程序还可以继续运行,只是功能有限制。 4. 可以开启到期时间限制,到了指定的日期后,弹出到期提醒,自动关闭程序,程序无法继续运行。 5. 可以开启运行时间限制,比如指定程序只能运行30分钟,每次打开程序后,运行30分钟,自动关闭程序,再次打开程序,还可以继续运行30分钟。 6. 可以开启最大数量限制,比如设置最大数量5,则该软件中可以检测添加的设备数量是否超过5个,超过则不允许继续添加。 7. 功能限制可以选择一种或者多种,满足各种需求场景。 8. 内置防日期时间篡改功能,自动记忆秘钥写入时间和秘钥正常运行时间,一旦检测到电脑时间被篡改,程序无法打开,自动关闭程序。运行期间也会自动检测。 9. 支持自动检测读取秘钥文件,如果存在到期日期或者到期时间限制,自动显示剩余天数。 10. 多重加密算法,无法解密和破解。控制端可设定私有的加解密秘钥。 11. 离线使用,无需联网,方便在各种离线设备使用。 12. 同时支持windows、linux、mac系统,支持所有Qt版本。 ### 14.2 效果图 ![](snap_key/keytool.jpg) ![](snap_key/keyserver.jpg) ## 15 多线程文件传输 ### 15.1 功能特点 1. 多线程收发文件,支持加密传输。 2. 接收端支持监听端口接收文件和主动连接服务器接收文件两种方式。 3. 按照 文件开始符+文件大小+文件内容+文件结束符 逐个分包接收。 4. 可对接收的加密过的文件包进行解密输出。 5. 如果采用连接服务器方式接收文件可指定请求文件。 6. 接收端请求文件的形式可以作为通用的程序升级方案。 7. 进度条实时更新收发文件的进度。 8. 发送端可设置每个包最大大小即切片分包数量。 9. 发送端可对文件的每个包进行加密传输。 10. 发送端支持对包进行合并发送。 11. 可指定目录对客户端发来的请求文件进行搜索。 12. 每个功能独立的一个类,接口清晰友好,使用方便。 13. 支持任意Qt版本、任意系统、任意编译器。 ### 15.2 效果图 ![](snap_tcpfile/tcpfile.gif) ## 16 数据库组件 ### 16.1 功能特点 1. 同时支持多种数据库比如odbc、sqlite、mysql、postgresql、sqlserver、oracle、人大金仓等。 2. 一个数据库类即可管理本地数据库通信,也支持远程数据库通信等。 3. 数据库线程支持执行各种sql语句,包括单条和批量。 4. 组件中的所有类打印信息、错误信息、执行结果都信号发出去。 5. 集成数据库通用翻页类(负责具体处理逻辑),搭配分页导航控件(负责外观),形成超级牛逼的翻页控件。 6. 集成数据库自动清理类,设定最大记录数后台自动清理早期数据。 7. 集成自定义委托类,支持复选框、文本框、下拉框、日期框、微调框、进度条等。 8. 同时支持Qt4-Qt6,亲测Qt4.6到Qt6.2任意版本,任意系统和编译器。 9. 本组件无故障 360天7乘24小时 运行在至少上万个现场,商业级别品质保证。 10. 每个类都对应完整详细的使用示例,注释详细,非常适合阅读学习。 11. 可以作为独立的程序运行,比如自动清理早期数据,同步数据到云端。 12. 全部线程处理,不卡界面,自动重连数据库。 13. 普通测试情况,sqlite数据库,数据库发生器每秒钟插入1000条记录约0.003秒钟,同时自动清理数据类每秒钟删除1000条记录约0.13秒,不同线程互不干扰。 ### 16.2 数据库线程 1. 可设置数据库类型,支持多种数据库类型。 2. 数据库类型包括但不限于odbc、sqlite、mysql、postgresql、sqlserver、oracle、人大金仓等。 3. 可设置数据库连接信息包括主机地址、用户信息等。 4. 具有自动重连机制,可设置是否检查连接以及检查间隔。 5. 支持单条sql语句队列,一般用于查询返回数据,每次插入一条执行一条。 6. 支持多条sql语句队列,一般用于远程提交数据,每次插入一条执行多条。 7. 支持批量sql语句队列,一般用于批量更新数据,每次插入多条执行多条。 8. 可设置队列最大数量,限定排队处理的sql语句集合。 9. 通过信号发出 打印信息、错误信息、查询结果。 ### 16.3 通用翻页类 1. 可设置每页多少行记录,自动按照设定的值进行分页。 2. 可设置要查询的表名、字段集合、条件语句、排序语句。 3. 可设置第一页、上一页、下一页、末一页、翻页按钮。 4. 可设置当前页、总页数、总记录数、每页记录数、查询用时标签页。 5. 多线程查询总记录数,数据量巨大时候不会卡主界面。 6. 建议条件字段用整型类型的主键,速度极快。 7. 提供查询结果返回信号,包括当前页、总页数、总记录数、查询用时等信息。 8. 可设置所有列或者某一列对齐样式例如居中或者右对齐。 9. 可增加列用于标识该条记录,设定列的位置、标题、宽度。 10. 提供函数直接执行第一页、上一页、下一页、末一页。 11. 提供函数直接跳转到指定页。 12. 根据是否第一页、末一页自动禁用对应的按钮。 13. 本控件是翻页功能类,和翻页控件navpage完美搭配,形成超级牛逼的翻页控件。 ### 16.4 分页导航控件 1. 可设置页码按钮的个数。 2. 可设置字体大小。 3. 可设置边框圆角角度、大小、颜色。 4. 可设置正常状态背景颜色、文字颜色。 5. 可识别悬停状态背景颜色、文字颜色。 6. 可设置按下状态背景颜色、文字颜色。 7. 可设置选中状态背景颜色、文字颜色。 8. 可设置导航位置居中对齐、左对齐、右对齐。 9. 可设置是否显示提示标签控件。 10. 自动计算总页码数显示隐藏多余按钮。 11. 自动计算切换页码导航。 12. 和分页导航功能类无缝对接完美融合。 ### 16.5 自动清理数据 1. 可设置要清理的对应数据库连接名称和表名。 2. 可设置条件字段。 3. 可设置排序字段。 4. 可设置最大保留的记录数。 5. 可设置执行自动清理的间隔。 6. 后期支持多个数据库和多个表。 7. 建议条件字段用数字类型的主键,速度极快。 8. 增加统计用字段名称设置。 9. 增加自动清理文件夹,超过大小自动删除文件夹中早期文件。 ### 16.6 自定义委托 1. 可设置多种委托类型,例如复选框、文本框、下拉框、日期框、微调框、进度条等。 2. 可设置是否密文显示,一般用于文本框。 3. 可设置是否允许编辑,一般用于下拉框。 4. 可设置是否禁用,一般用来禁用某列。 5. 可设置数据集合,比如下拉框数据集合。 6. 提供值变化信号,比方说下拉框值改动触发。 7. 可设置数据校验自动产生不同的图标。 8. 支持设置校验列、校验规则、校验值、校验成功图标、校验失败图标、图标大小。 9. 可设置校验数据产生不同的背景颜色和文字颜色。 10. 校验规则支持 == > >= < <= != contains,非常丰富。 11. 复选框自动居中而不是左侧,切换选中状态发送对应的信号。 12. 可设置颜色委托,自动根据颜色值绘制背景颜色,自动设置最佳文本颜色。 13. 可设置按钮委托,自动根据值生成多个按钮,按钮按下发送对应的信号。 14. 当设置了委托列时自动绘制选中背景色和文字颜色。 15. 可设置关键字对照表绘制关键字比如原始数据是 0-禁用 1-启用。 16. 可设置复选框对应的映射选中不选中关键字。 17. 根据不同的委托类型绘制,可以依葫芦画瓢自行增加自己的委托。 18. 所有功能封装成1个类,核心代码不到500行,使用极其方便友好。 ### 16.7 效果图 ![](snap_dbtool/dbtool.gif) ## 17 数据导入导出打印 ### 17.1 功能特点 1. 组件同时集成了导出数据到csv、xls、pdf和打印数据。 2. 所有操作全部提供静态方法无需new,数据和属性等各种参数设置采用结构体数据,极为方便。 3. 同时支持QTableView、QTableWidget、QStandardItemModel、QSqlTableModel等数据源。 4. 提供静态方法直接传入QTableView、QTableWidget控件,自动识别列名、列宽和数据内容。 5. 每组功能都提供单独的完整的示例,注释详细,非常适合各阶段Qter程序员。 6. 原创导出数据机制,不依赖任何office组件或者操作系统等第三方库,支持嵌入式linux。 7. 速度超快,9个字段10万行数据只需要2秒钟完成。 8. 只需要四个步骤即可开始急速导出海量数据比如100W条记录到Excel。 9. 同时提供直接写入数据接口和多线程写入数据接口,不卡主界面。 10. 可设置标题、副标题、表名。 11. 可设置导出数据的字段名、列名、列宽。 12. 可设置末尾列自动拉伸填充,默认拉伸更美观。 13. 可设置是否启用校验过滤数据,启用后符合规则的数据特殊颜色显示。 14. 可指定校验的列、校验规则、校验值、校验值数据类型。 15. 校验规则支持 精确等于==、大于>、大于等于>=、小于<、小于等于<=、不等于!=、包含contains。 16. 校验值数据类型支持 整型int、浮点型float、双精度型double,默认文本字符串类型。 17. 可设置随机背景颜色及需要随机背景色的列集合。 18. 支持分组输出数据,比如按照设备分组输出数据,方便查看。 19. 可设置csv分隔符、行内容分隔符、子内容分隔符。 20. 可设置边框宽度、自动填数据类型,默认自动数据类型开启。 21. 可设置是否开启数据单元格样式,默认不开启,不开启可以节约大概30%的文件体积。 22. 可设置横向排版、纸张边距等,比如导出到pdf以及打印数据。 23. 提供图文混排导出数据到pdf以及打印示例,自动分页,支持多图。 24. 提供一个打印样板中同时包括横向纵向排版示例。 25. 提供静态函数将控件截图导出到pdf文件。 26. 提供静态函数将图片转成pdf文件。 27. 提供静态函数将csv文件转成xls文件,支持列宽表名等参数设置。 28. 针对每列可分别设置字段对齐样式、内容对齐样式,包括左对齐、居中对齐、右对齐。 29. 灵活性超高,可自由更改源码设置对齐方式、文字颜色、背景颜色等。 30. 支持任意excel表格软件,包括但不限于excel2003-2021、wps、openoffice等。 31. 纯Qt编写,支持任意Qt版本+任意编译器+任意系统。 ### 17.2 效果图 ![](snap_dataout/dataout.gif) ## 18 图片视频传输 ### 18.1 功能特点 1. 多线程收发图片数据和解析图片数据,不卡主界面。 2. 同时支持TCP和UDP两种模式,封装了TCP模式以及UDP模式的客户端类和服务端类。 3. 图片传输客户端同时支持发送到多个服务端,可以作为一个教师机同屏发送到多个学生机的应用场景。 4. 同时支持多个客户端同时往服务端发送图片,服务端每个连接都会自动开辟线程收发和解析图片数据。 5. 自定义label控件信号槽机制绘制图片,不卡主界面。 6. 自带心跳机制判断离线,自动重连服务器,可设置超时时间。 7. 每个消息都有唯一的消息标识uuid,服务端收到以后会返回对应的uuid消息表示收到,客户端可以根据此返回消息判断服务端解析成功,不用再发,这样可以确保发出去的数据服务器接收到了并解析成功。 8. 每个消息都有唯一的图片标识flag,相当于ID号,根据此标识判断需要解析显示到哪个界面。 9. 图片以base64的字符串格式发送,接收端接收到base64字符串的图片数据解码后重新生成图片。 10. 所有数据的收发都有信号发出去,方便输出查看。 11. 都提供单例类,方便只有一个的时候直接使用无需new。 12. 采用自定义的xml协议,可以自由拓展其他属性字段比如带上图片内容等。 ### 18.2 效果图 ![](snap_video_image/video_image1.gif) ![](snap_video_image/video_image2.gif) ## 19 网络请求服务器 ### 19.1 功能特点 1. 支持多个客户端连接并发同时处理,100个毫无压力。 2. 可设置http请求是长连接还是短连接,默认长连接。 3. 支持多种回复数据格式,其中包括网页内容、json数据等。 4. 服务端示例中同时包含读取文件回复、读取数据库回复。 5. 支持8种配色方案(暗黑、灰黑、深绿、浅黄、深蓝、深黑、暗蓝、默认)。 6. 客户端可指定请求地址,服务端可指定网卡和端口进行监听。 7. 所有请求和连接都有计数,所有在线请求的IP和端口都显示在表格中。 8. 可以提供一个简易的网页配置服务,包括交互,作为设备的web配置。 9. 可自由拓展增加权限校验等,作为一个http请求服务器。 10. 纯Qt实现,代码框架整洁,注释完整,支持任意Qt版本、任意编译器、任意操作系统。 ### 19.2 效果图 ![](snap_httpserver/httpserver.gif) ## 20 自定义委托大全 ### 20.1 功能特点 1. 可设置多种委托类型,例如复选框/文本框/下拉框/日期框/微调框/进度条等。 2. 可设置是否密文显示,一般用于文本框。 3. 可设置是否允许编辑,一般用于下拉框。 4. 可设置是否禁用,一般用来禁用某列。 5. 可设置数据集合,比如下拉框数据集合。 6. 提供值变化信号,比方说下拉框值改动触发。 7. 可设置数据校验自动产生不同的图标。 8. 支持设置校验列/校验规则/校验值/校验成功图标/校验失败图标/图标大小。 9. 可设置校验数据产生不同的背景颜色和文字颜色。 10. 校验规则支持 == > >= < <= != contain,非常丰富。 11. 复选框自动居中而不是左侧,切换选中状态发送对应的信号。 12. 可设置颜色委托,自动根据颜色值绘制背景颜色,自动设置最佳文本颜色。 13. 可设置按钮委托,自动根据值生成多个按钮,按钮按下发送对应的信号。 14. 当设置了委托列时自动绘制选中背景色和文字颜色。 15. 可设置关键字对照表绘制关键字比如原始数据是 0-禁用 1-启用。 16. 可设置复选框对应的映射选中不选中关键字。 17. 根据不同的委托类型绘制,可以依葫芦画瓢自行增加自己的委托。 18. 所有功能封装成1个类不到500行代码,使用极其方便友好。 ### 20.2 需求应用场景 1. 某个字段需要提供下拉框进行选择,下拉框可选是否允许编辑。 2. 某个字段需要提供密码框进行输入,密文显示字段值。 3. 某个字段需要提供日期框下拉选择日期时间。 4. 某个字段需要提供微调框设定值。 5. 某个字段需要提供进度条显示字段值。 6. 某个字段列需要禁用。 7. 各种委托控件可以设置初始的数据集合,比如下拉框。 8. 各种委托控件在值发生变化的时候发出valuechanged信号,比如下拉框选择声音文件的时候进行播放试听,微调框值改变的时候联动其他控件进行处理等。 9. 某个字段根据设定的规则进行数据校验自动产生不同的图标显示,比如报警红色图标/正常绿色图标,一目了然。同时可设置校验列/校验规则/校验值/校验成功图标/校验失败图标/图标大小。 10. 某个字段根据设定的规则进行数据校验自动绘制不同的背景颜色醒目显示,可设定规则包括 == > >= < <= != contains,可设置符合要求的内容文字颜色/背景颜色。 11. 某个字段需要根据内容显示复选框(自动居中),比如内容是 0/禁用/false 等复选框不选中,1/启用/true 等复选框选中,具体选中不选中对应的内容可自定义。 12. 某个字段需要根据内容重新替换显示成自定义的内容,比如值是0而需要显示成“不符合”字样,1显示成“符合”字样。对应的内容替换规则可设置关键字对照表。 13. 某个字段需要根据颜色值显示对应的颜色,同时可以单击选中进行颜色选择。 14. 某列需要显示操作按钮,按钮的个数/文字集合可设定,根据设定的文字集合平分宽度绘制按钮,单击某个按钮发送对应的按钮单击信号,带按钮索引以及行列,用于用户自行处理。 15. 一个类通用所有需要委托的场景,相当于一个轮子用在所有项目中,不需要单独再去写不同的委托类。 16. 一个类通用所有支持委托的控件,比如QTableView/QTableWidget/QListView/QTreeWidget/QListWidget等。 ### 20.3 效果图 ![](snap_dbdelegate/dbdelegate.gif) ## 21 安卓综合应用 ### 21.1 功能特点 1. 封装了通用的Qt安卓组件,打通了常规与java交互机制。 2. 动态切换横屏竖屏及获取当前横屏竖屏状态。 3. 支持手机震动、拨打电话、发送短信。 4. 支持moketoast临时消息、notify顶部任务栏消息。 5. 支持安卓系统层的选择文件,包括相册文件、本地图片、本地音频、本地视频、本地联系人等,返回的路径并不是默认的uri而是转换解析后的正确的路径。 6. 封装了Qt的文件选择及保存对话框,自动根据横屏竖屏调整弹出框大小和位置到适中。 7. 统一的通用的接收从安卓发过来的数据信号,比如选择文件后的路径、抓拍图片完成等信号通知,通过type区分类型。 8. 支持打开系统拍照程序,拍照后返回抓拍的原始图片,非缩略图。 9. 提供调用jar文件示例,调用jar包中的函数,执行返回结果。 10. 提供camera示例,支持切换前置后置摄像头、支持保存录像文件、可调整焦距、抓拍图片、设置图像拉伸策略、设置画面旋转角度、自动对焦等。 11. 提供了video示例,可以播放本地音视频文件,可暂停继续播放,时长显示,进度条切换播放进度。 12. 提供解析二维码示例,可开启自动抓拍识别。 13. 支持全屏显示,屏幕常亮,后期增加开机启动、屏幕唤醒、服务进程等功能。 14. 自动识别电池电量的变化,包括当前是否在充电。 15. 通用广播消息拦截,识别电量变化、存储器挂载、屏幕打开关闭等。 16. 支持多摄像头同时显示和录像,比如同时3路,前提是系统支持多开摄像头。 17. 可选图片传输模块,抓拍图片和二维码解析结果发送到服务器,提供图片传输服务器解析示例。 18. 内置的与java交互示例非常丰富,既有通过实例调用类的方法,也有直接调用静态方法,既有直接调用java内部类,也有通过Java类中转调用方式。 19. 演示交互示例包括私有方法、公有静态方法、无参数无返回值、有参数无返回值、无参数有返回值、有参数有返回值、多个不同类型参数等。 ### 21.2 效果图 ![](snap_android_demo/android_demo0.jpg) ![](snap_android_demo/android_demo5.jpg) ## 22 硬件综合应用 ### 22.1 串口热敏打印 1. 标准热敏打印协议解析,无依赖,支持任意系统。 2. 可打印各种文字信息比如访客单、报警信息等。 3. 可打印条形码即一维码。 4. 可打印二维码,设置二维码尺寸。 5. 支持多线程打印图片。 6. 可设置打印机的工作模式 0-标准模式 1-翻页模式。 7. 可设置各种边距比如行间距、字符间距、左边距等。 8. 可设置字体信息、字符集、文字对齐、加粗等。 9. 可设置串口号和波特率,不同厂家波特率可能不一致。 ### 22.2 身份证阅读器 1. 标准身份证阅读协议解析,无依赖,支持任意系统。 2. 可读取身份证文字信息,比如姓名、性别、名族等。 3. 可读取身份证头像,不同厂家库不一样。 4. 文字信息返回一个信号,头像一个信号,完美。 5. 支持嵌入式linux系统,包括32位64位。 6. 整体流程就是寻找身份证、选取身份证、读身份证信息。 ### 22.3 短信调试器 1. 标准AT命令协议解析,无依赖,支持任意系统。 2. 可设置收发短信模式 0-text模式 1-pdu模式。 3. 可批量发送短信以及支持长短信发送。 4. 可指定序号读取短信和删除短信。 5. 可一次性删除所有短信。 6. 可检测设备是否运行正常。 7. 支持中文短信发送。 8. 支持拨打电话+挂断电话+接听来电。 9. 可识别用户按键反馈,比如电话另一端按下了什么按键。 10. 支持批量发送给多个号码。 ### 22.4 效果图 ![](snap_hard/hard.gif) ## 23 音视频通话 ### 23.1 功能特点 1. 支持局域网和外网音视频实时通话,延迟极低,资源占用极低。 2. 自动获取本地所有视音频输入设备,本地摄像头设备自动罗列所有支持的分辨率、帧率、采集格式等信息。 3. 可以指定采集的视频设备和音频输入设备,自由组合,视频设备可以设置不同的分辨率、帧率、采集格式。 4. 支持本地桌面屏幕作为视频设备采集,支持多个屏幕,自动识别屏幕分辨率。 5. 可以选择不同的声卡设备播放声音。 6. 内置自动重连机制,视音频设备支持热插拔。 7. 支持固定画中画功能,可交换主画面和浮窗画面,可设置画面左右排列等布局方式。 8. 可自定义悬浮画面位置,指定左上角、右上角、左下角、右下角、自定义位置和大小。 9. 内置流媒体服务程序,程序启动后自动启动流媒体服务,自动推拉流。 10. 视音频流数据支持rtsp/rtmp/http/webrtc等方式拉流,可以直接网页上打开视频画面。 11. 实时显示本地音频振幅和远程音量振幅,可以分别对输入输出音量设置静音,方便测试。 12. 支持自定义水印,包括文字和图片水印,支持多个水印,指定任意位置。 13. 支持不同的视音频设备组合,比如本地摄像头加电脑麦克风而不是摄像头的麦克风,比如本地电脑桌面屏幕加摄像头的麦克风等。 14. 纯Qt+ffmpeg编写,支持windows和linux以及macos等系统,支持所有Qt版本、所有系统、所有编译器。 15. 支持嵌入式linux板子和树莓派香橙派等,以及国产linux系统。 ### 23.2 效果图 ![](snap_video_call/video_call.jpg) ## 24 监控设备模拟器 ### 24.1 功能特点 1. 标准onvif协议,支持设备搜索、获取参数、快照抓图等。 2. 支持264/265/aac等标准视音频协议传输。 3. 支持多路批量onvif设备模拟,每一路都独立的端口。 4. 支持本地摄像头采集转成onvif,可选择不同的设备、分辨率、帧率等参数。 5. 支持本地桌面采集转成onvif,可选择不同的屏幕、分辨率、帧率等参数。 6. 支持各种视频文件和视频流转成onvif,可重新设置编码转换以及分辨率转换。 7. 支持4K、8K等高清分辨率,不限制分辨率,非264/265会自动转码推流。 8. 每一路都可以设置统一或者独立的用户验证信息,为空则表示不验证。 9. 可以把任意内容接入到NVR以及视频监控系统,方便保存录像文件,以便回放可查。 10. 也可作为压力测试工具,比如模拟几千路onvif设备,让集成平台软件做接入压力测试。 11. 推出去的流不仅有rtsp格式,还支持rtmp、http、flv、ws-flv、webrtc等方式访问,可以直接网页查看。 12. 在管理工具上可以看到每一路的推流状况以及分辨率信息,非常直观。 13. 支持自动重连拉流,重连推流,保证7乘以24小时稳定运行。 14. 可设置开机自启动运行和后台运行,不显示在任务栏,作为后台服务运行。 15. 可批量添加文件、添加目录,自动将目录下的所有文件添加到模拟器。 16. 多功能添加地址面板,可以选择本地设备和监控设备,本地设备会自动识别摄像头设备和桌面设备,监控设备可以选择不同厂家,自动填充对应rtsp格式,填入用户信息即可,可以批量递增添加监控设备。 17. 可无缝上传到市面上所有的onvif协议设备,包括海康、大华、宇视、华为、天地伟业等,也支持ONVIF Device Manager国际onvif工具。 18. 支持gb28181设备模拟,具备设备注册、设备注销、设备心跳、设备信息、设备配置、设备状态应答等。 19. 支持模拟报警和位置上报等,方便平台侧显示对应设备的实时位置。 20. 支持一键添加批量模拟28181设备,实时显示已注册和已注销状态。 21. 支持将本地桌面、本地摄像头、任意视频文件、视频流文件、手机摄像头等转换成28181设备,添加到NVR或者国标软件平台。 22. sip协议同时支持udp和tcp两种通信方式,视频点播同时支持udp/tcp主动/tcp被动三种方式,涵盖所有可能的场景需求。 23. 无论是onvif设备模拟组件还是28181设备模拟组件,全部原创底层协议解析,纯Qt实现,跨任意平台。 24. 代码结构框架非常清晰,注释详细,代码精简不繁琐,非常易于学习和移植,可以很容易拓展其他接口需求。 25. 支持Qt4/Qt5/Qt6以及后续所有版本、所有编译器、所有开发环境。 26. 支持windows、linux、mac、国产OS、嵌入式linux、RK3588、树莓派、香橙派等系统。 ### 24.2 效果图 ![](snap_video_simulate/video_simulate_onvif1.jpg) ![](snap_video_simulate/video_simulate_onvif2.jpg) ![](snap_video_simulate/video_simulate_onvif3.jpg) ![](snap_video_simulate/video_simulate_onvif4.jpg) ![](snap_video_simulate/video_simulate_gb1.jpg) ## 25 地图控件示例 ### 25.1 功能特点 1. 支持各种地图源,包括天地图、高德地图、腾讯地图、谷歌地图、微软地图等。 2. 标准WGS-84地球坐标系,采用默卡托投影,可以拓展其他坐标系和投影规则。 3. 支持在线和离线两种场景需求,可以自定义在线瓦片地址格式和离线瓦片地址格式。 4. 多线程下载和加载瓦片图片文件,多线程绘制,自动缓存瓦片文件。 5. 在线模式下,可以开启是否缓存文件,指定缓存路径,将下载的瓦片文件存放到本地,默认优先从缓存文件查找,如果存在缓存文件则加载缓存文件,不存在则联网下载。 6. 可以拖动地图,鼠标滚轮放大和缩小地图,以鼠标所在位置作为缩放中心点,提供缩放控件手动单击进行操作。 7. 多图层机制,支持多个瓦片叠加图层和图形绘制图层,全部采用双缓冲技术,所有的图形和瓦片全部绘制到一个图片文件上,最终再将图片文件绘制到地图控件。不可见区域的图层包括覆盖物不会触发绘制,降低CPU占用。 8. 预加载机制,默认绘制的图层大小以当前区域往四周放大两倍,这样在鼠标拖动和缩放的时候,不会看到明显的加载过程,体验更佳。 9. 内置了多种图形覆盖物,包括坐标点、文本、标注点、折线、多边形、矩形、圆形等,可以设置边框颜色粗细、填充颜色和透明度等参数。 10. 标注点支持旋转角度和提示文本,其中提示文本可以设置在标注点的相对位置,标注点图片支持gif动图,可以动态切换静态图和动图。 11. 标注点和提示文本可以设置相对位置,位置包括左侧、右侧、上侧、下侧、中间、左上角、右上角、左下角、右下角。标注点默认按照底部居中对齐,一般圆形图标可以设置中心点对齐。 12. 标注点提示文本可设置背景颜色,透明度、颜色边框和粗细,支持换行和多行文字。 13. 所有的图形可以动态更新前景色、颜色粗细、背景颜色、颜色透明度等。 14. 支持删除单个图形、删除一种类型的图形、删除所有图形、隐藏单个或者所有图形等。 15. 支持动态绘制各种图形,开启后直接在地图上鼠标按下绘制,鼠标右键结束绘制,非常方便快捷。 16. 在对应图形区域鼠标按下,发出图形单击信号,精准识别单击区域,比如折线以鼠标在折线条上作为判断依据,多边形区域以鼠标在整个多边形区域内为准,而不是以矩形区域,包括圆形也是以圆形内部为准。 17. 可以动态启动禁用比例尺、十字线、缩放控件、地图拖曳、键盘操作、滚轮缩放、双击放大、鼠标追踪等特性。 18. 可以任意指定经纬度区域进行瓦片拼接保存成图片文件,也可以直接对整个可视区域或者缓存区域的地图图片文件保存。支持任意多边形轮廓保存成图片,比如某个行政区的瓦片保存。 19. 图形可以动态设置zindex层叠顺序,值越大,越显示在前面,内部维护着一个zindex表,默认按照添加的先后顺序增加,后面添加的显示在前面,主动设置后,按照设置的zindex来绘制。 20. 支持将QWidget对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。 21. 内置MarkerMove轨迹移动类,支持历史轨迹数据回放和实时轨迹移动,可设置图标、轨迹线的颜色和粗细、移动速度、移动间隔、平滑移动等,支持多条轨迹线条同时移动。 22. 内置MarkerLine航迹规划类,支持动态添加航迹点,显示对应箭头,可以动态拖曳调整航迹点的位置,选中点高亮显示。 23. 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。 24. 默认开启缓存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。 25. 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。 26. 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。 27. 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,支持极低性能的嵌入式环境。 28. 原创轻量级,5000行代码,架构漂亮,注释详细,拓展方便,容易学习,适合各种初学者和进阶者,方便二次开发。 29. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。古法编程,不含任何AI代码,品质保证。 ### 25.2 效果图 ![](snap_mapwidget/snap/mapwidget10.jpg) ![](snap_mapwidget/snap/mapwidget20.jpg) ![](snap_mapwidget/snap/mapwidget30.jpg) ![](snap_mapwidget/snap/mapwidget40.jpg) ![](snap_mapwidget/snap/mapwidget50.jpg) ![](snap_mapwidget/snap/mapwidget60.jpg) ## x1 雨田哥作品 ### x1.1 雷达模拟仿真 ![](snap_yutian/radar2.gif) ### x1.2 PDF阅读器 ![](snap_yutian/pdfreader1.gif) ### x1.3 定制化安装包 ![](snap_yutian/install3.gif) ### x1.4 支付宝微信支付 ![](snap_yutian/pay1.png) ![](snap_yutian/pay2.png) ### x1.5 录音播放控件 ![](snap_yutian/audiorecord.gif) ================================================ FILE: lightbutton/frmlightbutton.cpp ================================================ #pragma execution_character_set("utf-8") #include "frmlightbutton.h" #include "ui_frmlightbutton.h" #include "qdatetime.h" #include "qtimer.h" frmLightButton::frmLightButton(QWidget *parent) : QWidget(parent), ui(new Ui::frmLightButton) { ui->setupUi(this); this->initForm(); } frmLightButton::~frmLightButton() { delete ui; } void frmLightButton::initForm() { ui->lightButton2->setBgColor(QColor(255, 107, 107)); ui->lightButton3->setBgColor(QColor(24, 189, 155)); type = 0; QTimer *timer = new QTimer(this); timer->setInterval(1000); connect(timer, SIGNAL(timeout()), this, SLOT(updateValue())); timer->start(); updateValue(); //以下方法启动报警 //ui->lightButton1->setAlarmColor(QColor(255, 0, 0)); //ui->lightButton1->setNormalColor(QColor(0, 0, 0)); //ui->lightButton1->startAlarm(); } void frmLightButton::updateValue() { if (type == 0) { ui->lightButton1->setLightGreen(); ui->lightButton2->setLightRed(); ui->lightButton3->setLightBlue(); type = 1; } else if (type == 1) { ui->lightButton1->setLightBlue(); ui->lightButton2->setLightGreen(); ui->lightButton3->setLightRed(); type = 2; } else if (type == 2) { ui->lightButton1->setLightRed(); ui->lightButton2->setLightBlue(); ui->lightButton3->setLightGreen(); type = 0; } } ================================================ FILE: lightbutton/frmlightbutton.h ================================================ #ifndef FRMLIGHTBUTTON_H #define FRMLIGHTBUTTON_H #include namespace Ui { class frmLightButton; } class frmLightButton : public QWidget { Q_OBJECT public: explicit frmLightButton(QWidget *parent = 0); ~frmLightButton(); private: Ui::frmLightButton *ui; int type; private slots: void initForm(); void updateValue(); }; #endif // FRMLIGHTBUTTON_H ================================================ FILE: lightbutton/frmlightbutton.ui ================================================ frmLightButton 0 0 800 600 Form LightButton QWidget
lightbutton.h
1
================================================ FILE: lightbutton/lightbutton.cpp ================================================ #pragma execution_character_set("utf-8") #include "lightbutton.h" #include "qpainter.h" #include "qpainterpath.h" #include "qevent.h" #include "qtimer.h" #include "qdebug.h" LightButton::LightButton(QWidget *parent) : QWidget(parent) { text = ""; textColor = QColor(255, 255, 255); alarmColor = QColor(255, 107, 107); normalColor = QColor(10, 10, 10); borderOutColorStart = QColor(255, 255, 255); borderOutColorEnd = QColor(166, 166, 166); borderInColorStart = QColor(166, 166, 166); borderInColorEnd = QColor(255, 255, 255); bgColor = QColor(100, 184, 255); showRect = false; showOverlay = true; overlayColor = QColor(255, 255, 255); canMove = false; this->installEventFilter(this); isAlarm = false; timerAlarm = new QTimer(this); connect(timerAlarm, SIGNAL(timeout()), this, SLOT(alarm())); timerAlarm->setInterval(500); } bool LightButton::eventFilter(QObject *watched, QEvent *event) { if (canMove) { static QPoint lastPoint; static bool pressed = false; QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->type() == QEvent::MouseButtonPress) { if (this->rect().contains(mouseEvent->pos()) && (mouseEvent->button() == Qt::LeftButton)) { lastPoint = mouseEvent->pos(); pressed = true; } } else if (mouseEvent->type() == QEvent::MouseMove && pressed) { int dx = mouseEvent->pos().x() - lastPoint.x(); int dy = mouseEvent->pos().y() - lastPoint.y(); this->move(this->x() + dx, this->y() + dy); } else if (mouseEvent->type() == QEvent::MouseButtonRelease && pressed) { pressed = false; } } return QWidget::eventFilter(watched, event); } void LightButton::paintEvent(QPaintEvent *) { int width = this->width(); int height = this->height(); int side = qMin(width, height); //绘制准备工作,启用反锯齿,平移坐标轴中心,等比例缩放 QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); if (showRect) { //绘制矩形区域 painter.setPen(Qt::NoPen); painter.setBrush(bgColor); painter.drawRoundedRect(this->rect(), 5, 5); //绘制文字 if (!text.isEmpty()) { QFont font; font.setPixelSize(side - 20); painter.setFont(font); painter.setPen(textColor); painter.drawText(this->rect(), Qt::AlignCenter, text); } } else { painter.translate(width / 2, height / 2); painter.scale(side / 200.0, side / 200.0); //绘制外边框 drawBorderOut(&painter); //绘制内边框 drawBorderIn(&painter); //绘制内部指示颜色 drawBg(&painter); //绘制居中文字 drawText(&painter); //绘制遮罩层 drawOverlay(&painter); } } void LightButton::drawBorderOut(QPainter *painter) { int radius = 99; painter->save(); painter->setPen(Qt::NoPen); QLinearGradient borderGradient(0, -radius, 0, radius); borderGradient.setColorAt(0, borderOutColorStart); borderGradient.setColorAt(1, borderOutColorEnd); painter->setBrush(borderGradient); painter->drawEllipse(-radius, -radius, radius * 2, radius * 2); painter->restore(); } void LightButton::drawBorderIn(QPainter *painter) { int radius = 90; painter->save(); painter->setPen(Qt::NoPen); QLinearGradient borderGradient(0, -radius, 0, radius); borderGradient.setColorAt(0, borderInColorStart); borderGradient.setColorAt(1, borderInColorEnd); painter->setBrush(borderGradient); painter->drawEllipse(-radius, -radius, radius * 2, radius * 2); painter->restore(); } void LightButton::drawBg(QPainter *painter) { int radius = 80; painter->save(); painter->setPen(Qt::NoPen); painter->setBrush(bgColor); painter->drawEllipse(-radius, -radius, radius * 2, radius * 2); painter->restore(); } void LightButton::drawText(QPainter *painter) { if (text.isEmpty()) { return; } int radius = 100; painter->save(); QFont font; font.setPixelSize(85); painter->setFont(font); painter->setPen(textColor); QRect rect(-radius, -radius, radius * 2, radius * 2); painter->drawText(rect, Qt::AlignCenter, text); painter->restore(); } void LightButton::drawOverlay(QPainter *painter) { if (!showOverlay) { return; } int radius = 80; painter->save(); painter->setPen(Qt::NoPen); QPainterPath smallCircle; QPainterPath bigCircle; radius -= 1; smallCircle.addEllipse(-radius, -radius, radius * 2, radius * 2); radius *= 2; bigCircle.addEllipse(-radius, -radius + 140, radius * 2, radius * 2); //高光的形状为小圆扣掉大圆的部分 QPainterPath highlight = smallCircle - bigCircle; QLinearGradient linearGradient(0, -radius / 2, 0, 0); overlayColor.setAlpha(100); linearGradient.setColorAt(0.0, overlayColor); overlayColor.setAlpha(30); linearGradient.setColorAt(1.0, overlayColor); painter->setBrush(linearGradient); painter->rotate(-20); painter->drawPath(highlight); painter->restore(); } QString LightButton::getText() const { return this->text; } QColor LightButton::getTextColor() const { return this->textColor; } QColor LightButton::getAlarmColor() const { return this->alarmColor; } QColor LightButton::getNormalColor() const { return this->normalColor; } QColor LightButton::getBorderOutColorStart() const { return this->borderOutColorStart; } QColor LightButton::getBorderOutColorEnd() const { return this->borderOutColorEnd; } QColor LightButton::getBorderInColorStart() const { return this->borderInColorStart; } QColor LightButton::getBorderInColorEnd() const { return this->borderInColorEnd; } QColor LightButton::getBgColor() const { return this->bgColor; } bool LightButton::getCanMove() const { return this->canMove; } bool LightButton::getShowRect() const { return this->showRect; } bool LightButton::getShowOverlay() const { return this->showOverlay; } QColor LightButton::getOverlayColor() const { return this->overlayColor; } QSize LightButton::sizeHint() const { return QSize(100, 100); } QSize LightButton::minimumSizeHint() const { return QSize(10, 10); } void LightButton::setText(const QString &text) { if (this->text != text) { this->text = text; this->update(); } } void LightButton::setTextColor(const QColor &textColor) { if (this->textColor != textColor) { this->textColor = textColor; this->update(); } } void LightButton::setAlarmColor(const QColor &alarmColor) { if (this->alarmColor != alarmColor) { this->alarmColor = alarmColor; this->update(); } } void LightButton::setNormalColor(const QColor &normalColor) { if (this->normalColor != normalColor) { this->normalColor = normalColor; this->update(); } } void LightButton::setBorderOutColorStart(const QColor &borderOutColorStart) { if (this->borderOutColorStart != borderOutColorStart) { this->borderOutColorStart = borderOutColorStart; this->update(); } } void LightButton::setBorderOutColorEnd(const QColor &borderOutColorEnd) { if (this->borderOutColorEnd != borderOutColorEnd) { this->borderOutColorEnd = borderOutColorEnd; this->update(); } } void LightButton::setBorderInColorStart(const QColor &borderInColorStart) { if (this->borderInColorStart != borderInColorStart) { this->borderInColorStart = borderInColorStart; this->update(); } } void LightButton::setBorderInColorEnd(const QColor &borderInColorEnd) { if (this->borderInColorEnd != borderInColorEnd) { this->borderInColorEnd = borderInColorEnd; this->update(); } } void LightButton::setBgColor(const QColor &bgColor) { if (this->bgColor != bgColor) { this->bgColor = bgColor; this->update(); } } void LightButton::setCanMove(bool canMove) { if (this->canMove != canMove) { this->canMove = canMove; this->update(); } } void LightButton::setShowRect(bool showRect) { if (this->showRect != showRect) { this->showRect = showRect; this->update(); } } void LightButton::setShowOverlay(bool showOverlay) { if (this->showOverlay != showOverlay) { this->showOverlay = showOverlay; this->update(); } } void LightButton::setOverlayColor(const QColor &overlayColor) { if (this->overlayColor != overlayColor) { this->overlayColor = overlayColor; this->update(); } } void LightButton::setGreen() { textColor = QColor(255, 255, 255); setBgColor(QColor(0, 166, 0)); } void LightButton::setRed() { textColor = QColor(255, 255, 255); setBgColor(QColor(255, 0, 0)); } void LightButton::setYellow() { textColor = QColor(25, 50, 7); setBgColor(QColor(238, 238, 0)); } void LightButton::setBlack() { textColor = QColor(255, 255, 255); setBgColor(QColor(10, 10, 10)); } void LightButton::setGray() { textColor = QColor(255, 255, 255); setBgColor(QColor(129, 129, 129)); } void LightButton::setBlue() { textColor = QColor(255, 255, 255); setBgColor(QColor(0, 0, 166)); } void LightButton::setLightBlue() { textColor = QColor(255, 255, 255); setBgColor(QColor(100, 184, 255)); } void LightButton::setLightRed() { textColor = QColor(255, 255, 255); setBgColor(QColor(255, 107, 107)); } void LightButton::setLightGreen() { textColor = QColor(255, 255, 255); setBgColor(QColor(24, 189, 155)); } void LightButton::startAlarm() { if (!timerAlarm->isActive()) { timerAlarm->start(); } } void LightButton::stopAlarm() { if (timerAlarm->isActive()) { timerAlarm->stop(); } } void LightButton::alarm() { if (isAlarm) { textColor = QColor(255, 255, 255); bgColor = normalColor; } else { textColor = QColor(255, 255, 255); bgColor = alarmColor; } this->update(); isAlarm = !isAlarm; } ================================================ FILE: lightbutton/lightbutton.h ================================================ #ifndef LIGHTBUTTON_H #define LIGHTBUTTON_H /** * 高亮发光按钮控件 作者:feiyangqingyun(QQ:517216493) 2016-10-16 * 1. 可设置文本,居中显示。 * 2. 可设置文本颜色。 * 3. 可设置外边框渐变颜色。 * 4. 可设置里边框渐变颜色。 * 5. 可设置背景色。 * 6. 可直接调用内置的设置 绿色、红色、黄色、黑色、蓝色 等公有槽函数。 * 7. 可设置是否在容器中可移动,当成一个对象使用。 * 8. 可设置是否显示矩形。 * 9. 可设置报警颜色、非报警颜色。 * 10. 可控制启动报警和停止报警,报警时闪烁。 */ #include #ifdef quc class Q_DECL_EXPORT LightButton : public QWidget #else class LightButton : public QWidget #endif { Q_OBJECT Q_PROPERTY(QString text READ getText WRITE setText) Q_PROPERTY(QColor textColor READ getTextColor WRITE setTextColor) Q_PROPERTY(QColor alarmColor READ getAlarmColor WRITE setAlarmColor) Q_PROPERTY(QColor normalColor READ getNormalColor WRITE setNormalColor) Q_PROPERTY(QColor borderOutColorStart READ getBorderOutColorStart WRITE setBorderOutColorStart) Q_PROPERTY(QColor borderOutColorEnd READ getBorderOutColorEnd WRITE setBorderOutColorEnd) Q_PROPERTY(QColor borderInColorStart READ getBorderInColorStart WRITE setBorderInColorStart) Q_PROPERTY(QColor borderInColorEnd READ getBorderInColorEnd WRITE setBorderInColorEnd) Q_PROPERTY(QColor bgColor READ getBgColor WRITE setBgColor) Q_PROPERTY(bool canMove READ getCanMove WRITE setCanMove) Q_PROPERTY(bool showRect READ getShowRect WRITE setShowRect) Q_PROPERTY(bool showOverlay READ getShowOverlay WRITE setShowOverlay) Q_PROPERTY(QColor overlayColor READ getOverlayColor WRITE setOverlayColor) public: explicit LightButton(QWidget *parent = 0); protected: bool eventFilter(QObject *watched, QEvent *event); void paintEvent(QPaintEvent *); void drawBorderOut(QPainter *painter); void drawBorderIn(QPainter *painter); void drawBg(QPainter *painter); void drawText(QPainter *painter); void drawOverlay(QPainter *painter); private: QString text; //文本 QColor textColor; //文字颜色 QColor alarmColor; //报警颜色 QColor normalColor; //正常颜色 QColor borderOutColorStart; //外边框渐变开始颜色 QColor borderOutColorEnd; //外边框渐变结束颜色 QColor borderInColorStart; //里边框渐变开始颜色 QColor borderInColorEnd; //里边框渐变结束颜色 QColor bgColor; //背景颜色 bool showRect; //显示成矩形 bool canMove; //是否能够移动 bool showOverlay; //是否显示遮罩层 QColor overlayColor; //遮罩层颜色 bool isAlarm; //是否报警 QTimer *timerAlarm; //定时器切换颜色 public: QString getText() const; QColor getTextColor() const; QColor getAlarmColor() const; QColor getNormalColor() const; QColor getBorderOutColorStart() const; QColor getBorderOutColorEnd() const; QColor getBorderInColorStart() const; QColor getBorderInColorEnd() const; QColor getBgColor() const; bool getCanMove() const; bool getShowRect() const; bool getShowOverlay() const; QColor getOverlayColor() const; QSize sizeHint() const; QSize minimumSizeHint() const; public Q_SLOTS: //设置文本 void setText(const QString &text); //设置文本颜色 void setTextColor(const QColor &textColor); //设置报警颜色+正常颜色 void setAlarmColor(const QColor &alarmColor); void setNormalColor(const QColor &normalColor); //设置外边框渐变颜色 void setBorderOutColorStart(const QColor &borderOutColorStart); void setBorderOutColorEnd(const QColor &borderOutColorEnd); //设置里边框渐变颜色 void setBorderInColorStart(const QColor &borderInColorStart); void setBorderInColorEnd(const QColor &borderInColorEnd); //设置背景色 void setBgColor(const QColor &bgColor); //设置是否可移动 void setCanMove(bool canMove); //设置是否显示矩形 void setShowRect(bool showRect); //设置是否显示遮罩层 void setShowOverlay(bool showOverlay); //设置遮罩层颜色 void setOverlayColor(const QColor &overlayColor); //设置为绿色 void setGreen(); //设置为红色 void setRed(); //设置为黄色 void setYellow(); //设置为黑色 void setBlack(); //设置为灰色 void setGray(); //设置为蓝色 void setBlue(); //设置为淡蓝色 void setLightBlue(); //设置为淡红色 void setLightRed(); //设置为淡绿色 void setLightGreen(); //设置报警闪烁 void startAlarm(); void stopAlarm(); void alarm(); }; #endif // LIGHTBUTTON_H ================================================ FILE: lightbutton/lightbutton.pro ================================================ QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets greaterThan(QT_MAJOR_VERSION, 5): QT += core5compat TARGET = lightbutton TEMPLATE = app DESTDIR = $$PWD/../bin CONFIG += warn_off SOURCES += main.cpp SOURCES += frmlightbutton.cpp SOURCES += lightbutton.cpp HEADERS += frmlightbutton.h HEADERS += lightbutton.h FORMS += frmlightbutton.ui ================================================ FILE: lightbutton/main.cpp ================================================ #pragma execution_character_set("utf-8") #include "frmlightbutton.h" #include #include int main(int argc, char *argv[]) { QApplication a(argc, argv); QFont font; font.setFamily("Microsoft Yahei"); font.setPixelSize(13); a.setFont(font); #if (QT_VERSION < QT_VERSION_CHECK(5,0,0)) #if _MSC_VER QTextCodec *codec = QTextCodec::codecForName("gbk"); #else QTextCodec *codec = QTextCodec::codecForName("utf-8"); #endif QTextCodec::setCodecForLocale(codec); QTextCodec::setCodecForCStrings(codec); QTextCodec::setCodecForTr(codec); #else QTextCodec *codec = QTextCodec::codecForName("utf-8"); QTextCodec::setCodecForLocale(codec); #endif frmLightButton w; w.setWindowTitle("高亮发光按钮 (QQ: 517216493 WX: feiyangqingyun)"); w.show(); return a.exec(); } ================================================ FILE: snap_dataout/readme.md ================================================ ## һע ԺǵýfileĿ¼£мfileĿ¼¶fileĿ¼ļƵִļͬһĿ¼ excelļ˫ԺᵯʾùԱexcelֹʾ.regֻҪһμɡ - ʾʾ򵥵IJõݵcsvxlspdfʹӡݣֱӴQTableViewQTableWidgetؼ - 뵼ʾcsvʽݣԴΪݿ൱ڽݿϢcsvļcsvļ¶Ӧݿݡ - ʾʾֱӴݿݵxlspdfʹӡݣ֧У顢ԶĩβС - мʾڳʾӱ⣬ͬʱУij˶ֵͽйͻɫʾ - ߼ʾʾ鵼ݣеij豸ݷһ飬ÿԶϢչʾͳƵĶϢ - ߳ʾʾ̵߳ݵxlspdfļָָ10ֶ100Wݣֻ21s - ӲѯʾʾûѡIJѯԲѯļ¼еxlspdfʹӡݡ - ͼĻʾԶͼıpdfӡ ## ص 1. ͬʱ˵ݵcsvxlspdfʹӡݡ 2. вȫṩ̬newݺԵȸֲòýṹݣΪ㡣 3. ͬʱ֧QTableViewQTableWidgetQStandardItemModelQSqlTableModelԴ 4. ṩֱ̬ӴQTableViewQTableWidgetؼԶʶпݡ 5. ÿ鹦ܶṩʾעϸdzʺϸ׶QterԱ 6. ԭݻƣκoffice߲ϵͳȵ⣬֧Ƕʽlinux 7. ٶȳ죬9ֶ10ֻҪ2ɡ 8. ֻҪĸ輴ɿʼٵݱ100W¼Excel 9. ͬʱṩֱдݽӿںͶ߳дݽӿڣ档 10. ñ⡢⡢ 11. õݵֶп 12. ĩβԶ䣬Ĭۡ 13. ǷУݣúϹɫʾ 14. ָУСУУֵУֵ͡ 15. У֧ ȷ==>ڵ>=С<Сڵ<=!=contains 16. Уֵ֧ intfloat˫doubleĬıַ͡ 17. ɫҪɫмϡ 18. ַ֧ݣ簴豸ݣ鿴 19. csvָݷָݷָ 20. ñ߿ȡԶͣĬԶͿ 21. ǷݵԪʽĬϲԽԼ30%ļ 22. úŰ桢ֽű߾ȣ絼pdfԼӡݡ 23. ṩͼĻŵݵpdfԼӡʾԶҳֶ֧ͼ 24. ṩһӡͬʱŰʾ 25. ṩ̬ؼͼpdfļ 26. ṩ̬ͼƬתpdfļ 27. ṩ̬csvļתxlsļ֧пȲá 28. ÿпɷֱֶζʽݶʽ롢ж롢Ҷ롣 29. ԳߣɸԴö뷽ʽɫɫȡ 30. ֧excelexcel2003-2021wpsopenofficeȡ 31. Qtд֧Qt汾++ϵͳ ## ʹ÷ ### 3.1 һ - дļһ壬ֵ֧ļʹãΪܶͨõķһļУúܶ롣 - dateheadDZõͷļԼͨõݽṹ塣 - datahelperͨõУкݵӡ - datacreatڴhtmlʽݣpdfʹӡݡ - datacsvڵ뵼ļcsvʽ - dataxlsݵxlsĺġ - dataprintݵpdfʹӡݡ 1. core_dataoutĿ¼ĿļͬĿ¼ 2. Ŀproļ ```cpp INCLUDEPATH += $$PWD/../core_dataout include ($$PWD/../core_dataout/core_dataout.pri) ``` 3. ʱĿ״ṹͼпԿ core_dataout 롣 ### 3.2 ڶȡ - ǵxlspdfߴӡǰǶҪõҪݼϡ - õݵķʽ֣һݿѯQSqlTableModelһǽؼȡݱQTableViewQTableWidgetQStandardItemModel - QTableViewһԴһQSqlTableModelһQStandardItemModel - ݼϰһмӢĵ ֺ ; ͳһŵQStringListС #### 3.2.1 ʾQSqlTableModel ```cpp void frmDataOut2::on_btnLoad_clicked() { model->setTable("MsgInfo"); model->select(); ui->tableView->setModel(model); for (int i = 0; i < columnCount; i++) { model->setHeaderData(i, Qt::Horizontal, columnNames.at(i)); ui->tableView->setColumnWidth(i, columnWidths.at(i)); } } QStringList frmDataOut2::getContent() { QStringList content; QString sql = QString("select * from MsgInfo limit %1").arg(100); QSqlQuery query; if (!query.exec(sql)) { return content; } //ѭ while (query.next()) { QStringList list; for (int i = 0; i < column; i++) { list << query.value(i).toString(); } content << list.join(";"); } return content; } ``` #### 3.2.2 ʾQStandardItemModel ```cpp void frmSimple::on_btnLoad_clicked() { // model->clear(); //бп model->setColumnCount(column); for (int i = 0; i < column; ++i) { model->setHeaderData(i, Qt::Horizontal, columnNames.at(i)); ui->tableView->setColumnWidth(i, columnWidths.at(i)); } //ѭ for (int i = 0; i < row; ++i) { //ѭһе QList items; for (int j = 0; j < column; ++j) { QStandardItem *item = new QStandardItem; item->setText(QString("%1_%2").arg(i + 1).arg(j + 1)); items << item; } model->appendRow(items); } ui->tableView->setModel(model); } QStringList frmSimple::getContent() { // QTableView ȡ QStringList content; for (int i = 0; i < row; ++i) { QStringList list; for (int j = 0; j < column; ++j) { list << model->item(i, j)->text(); } //ÿΪһַָ content << list.join(";"); } return content; } ``` #### 3.2.3 ʾQTableWidget ```cpp void frmDataOut1::on_btnLoad_clicked() { QStringList list; list << "" << "" << "·" << "" << ""; ui->tableWidget->clearContents(); for (int i = 0; i < rowCount; i++) { //ɸ澯 int index = rand() % 4; QTableWidgetItem *item1 = new QTableWidgetItem(QString::number(i + 1)); QTableWidgetItem *item2 = new QTableWidgetItem("" + QString::number(i + 1)); QTableWidgetItem *item3 = new QTableWidgetItem("ϱ"); QTableWidgetItem *item4 = new QTableWidgetItem(list.at(index)); QTableWidgetItem *item5 = new QTableWidgetItem(DATETIME); item5->setTextAlignment(Qt::AlignCenter); ui->tableWidget->setItem(i, 0, item1); ui->tableWidget->setItem(i, 1, item2); ui->tableWidget->setItem(i, 2, item3); ui->tableWidget->setItem(i, 3, item4); ui->tableWidget->setItem(i, 4, item5); } } QStringList frmDataOut1::getContent() { QStringList content; for (int i = 0; i < row; i++) { QStringList list; for (int j = 0; j < column; j++) { list << ui->tableWidget->item(i, j)->text(); } content << list.join(";"); } return content; } ``` ### 3.3 - ǵxlspdfߴӡҪݽṹ壬пݼϵϢ - ǵxlsҪļ - ǵpdfҪļ - ӡҪļͱ ```cpp void frmSimple::on_btnXls_clicked() { //ýṹ DataContent dataContent; // dataContent.content = getContent(); //п dataContent.columnNames = columnNames; dataContent.columnWidths = columnWidths; //ļ dataContent.fileName = "d:/0.xls"; //ñ dataContent.sheetName = "Ϣ"; //þ̬ DataXls::saveXls(dataContent); //򿪸ղŵļ QUIHelper::openFile(dataContent.fileName, "Ϣ"); } void frmSimple::on_btnPdf_clicked() { //ýṹ DataContent dataContent; // dataContent.content = getContent(); //п dataContent.columnNames = columnNames; dataContent.columnWidths = columnWidths; //ļ dataContent.fileName = "d:/0.pdf"; //þ̬ DataPrint::savePdf(dataContent); //򿪸ղŵļ QUIHelper::openFile(dataContent.fileName, "Ϣ"); } void frmSimple::on_btnPrint_clicked() { //ýṹ DataContent dataContent; // dataContent.content = getContent(); //п dataContent.columnNames = columnNames; dataContent.columnWidths = columnWidths; //þ̬ӡ DataPrint::print(dataContent); } ``` ### 3.4 IJִв - ݵ뵼dataoutṩͳһݽṹݺ - װͳһľ̬DataXls::saveXlsxlsDataPrint::savePdfpdfDataPrint::printӡݡ - ָ֧ϸӵIJԼݵ̲߳μϸʹdemo ```cpp //þ̬浽xls DataXls::saveXls(dataContent); //þ̬浽pdf DataPrint::savePdf(dataContent); //þ̬ӡ DataPrint::print(dataContent); ``` ## ġ򷽷 Ϊ˷żQterQtԱҲֱʹãܶʱѾݼصQTableViewQTableWIgdteУֱֻӴؼģͣ͸ҵݵcsv/xls/pdfʹӡݾУԲļƣԻûѡš ```cpp void frmSimple::on_btnCsv1_clicked() { QString file = QUIHelper::appPath() + "/db/dataout_tableview.csv"; DataHelper::DataOut(ui->tableView, model, 0, file, "Ա", "Ϣ"); //򿪸ղŵļ QUIHelper::openFile(file, "Ϣ"); } void frmSimple::on_btnXls1_clicked() { //ͨúֱӴؼ #if 1 QString file = QUIHelper::appPath() + "/db/dataout_tableview.xls"; DataHelper::DataOut(ui->tableView, model, 1, file, "Ա", "Ϣ"); #else //ļᵯԻѡ񱣴ļ QString file = DataHelper::DataOut(ui->tableView, model, 1); #endif //򿪸ղŵļ QUIHelper::openFile(file, "Ϣ"); } void frmSimple::on_btnPdf1_clicked() { //ͨúֱӴؼ QString file = QUIHelper::appPath() + "/db/dataout_tableview.pdf"; DataHelper::DataOut(ui->tableView, model, 2, file, "Ա", "Ϣ"); //򿪸ղŵļ QUIHelper::openFile(file, "Ϣ"); } void frmSimple::on_btnPrint1_clicked() { //ͨúֱӴؼ DataHelper::DataOut(ui->tableView, model, 3, "", "Ա"); } ``` ================================================ FILE: snap_iottool/readme.md ================================================ ## 1、编译说明 1. 本组件封装的modbus采集支持任意Qt版本,mqtt采集最低要求Qt5.5,也就是要求存在websocket模块。 2. mqtt采集如果启用了ssl,比如mqtts://和wss://两种,还需要环境中有ssl的库,或者将ssl的库文件拷贝到可执行文件同一目录。 3. 如果编译提示 error:numeric_limits’ is not a class...,找到qglobal.h头文件,最上面加一行 #include 就行。 ## 2、代码使用 ### 2.0 基本步骤 1. 第一步,将core_iot目录拷贝到你的项目的上一级目录。 2. 第二步,打开项目的pro文件,引入物联网组件,include ($$PWD/../core_iot/core_iot.pri)。 3. 第三步,在代码文件引入对应头文件,#include "iothelper.h" #include "iotbase.h" #include "iotmodbusbase.h",使用代码。 ### 2.1 modbus - 不同协议的端口通过传入不同的端口类型字符串区分。 - 串口采集需要设置串口号和波特率,网络采集需要设置主机地址和端口。 - 各种参数都有默认值,不设置就按照默认值处理。 - 协议类型中的Web是指websocket,也就是走websocket通信。 - 无论是串口采集还是网络采集,数据处理部分完全一致。采集到的数据都是按照统一的格式信号发出来。 - 数据顺序格式在下面的文档中使用说明部分有具体说明。 #### 2.1.1 串口采集 ```cpp //实例化串口采集类 IotBase *iotBase = IotHelper::newIotBase("Modbus_Rtu_Com"); //设置串口号和波特率 iotBase->setComName("COM1"); iotBase->setBaudRate(9600); //指定采集的设备地址集合 QList addrs; addrs << 1 << 2 << 3; //指定采集的寄存器起始地址 QList indexs; indexs << 0 << 0 << 0; //指定采集的寄存器数量个数 QList numbers; numbers << 4 << 4 << 4; //指定采集的数据顺序格式 QList formats; formats << Short_BA << Short_BA << Long_DCBA; //设置采集的参数集合 iotBase->setInfo(addrs, indexs, numbers, formats); //启动采集 iotBase->start(); ``` #### 2.1.2 网络采集 ```cpp //实例化串口采集类 IotBase *iotBase = IotHelper::newIotBase("Modbus_Tcp"); //设置串口号和波特率 iotBase->setHostName("127.0.0.1"); iotBase->setHostPort(502); //指定采集的设备地址集合 QList addrs; addrs << 1 << 2 << 3; //指定采集的寄存器起始地址 QList indexs; indexs << 0 << 0 << 0; //指定采集的寄存器数量个数 QList numbers; numbers << 4 << 4 << 4; //指定采集的数据顺序格式 QList formats; formats << Short_BA << Short_BA << Long_DCBA; //设置采集的参数集合 iotBase->setInfo(addrs, indexs, numbers, formats); //启动采集 iotBase->start(); ``` #### 2.1.3 线程运行 ```cpp //取消原来的启动 //iotBase->start(); //默认不是单独的线程运行/如果需要多线程运行/需要手动指定线程 QThread *iotThread = new QThread; //关联信号槽/线程启动自动开始采集/线程结束自动销毁采集类 connect(iotThread, SIGNAL(started()), iotBase, SLOT(start())); connect(iotThread, SIGNAL(finished()), iotBase, SLOT(deleteLater())); //将采集类移动到线程执行 iotBase->moveToThread(iotThread); //启动采集线程 iotThread->start(); ``` #### 2.1.4 数据处理 ```cpp //关联采集数据信号 connect(iotBase, SIGNAL(receiveValue(QString, quint8, QList)), this, SLOT(receiveValue(QString, quint8, QList))); //采集了几个寄存器/这里的数据就有几个 //为了兼容可能的数据格式/统一采用浮点数据作为参数的数据类型 void frmIotServer::receiveValue(const QString &portName, quint8 addr, const QList &values) { } ``` ### 2.2 mqtt ```cpp //实例化mqtt采集类 IotMqttBase *mqttBase = new IotMqttBase(this); //取出mqtt通信对象 QMqttClient *mqttClient = mqttBase->getMqttClient(); //关联收到数据信号 connect(mqttClient, SIGNAL(messageReceived(QByteArray, QMqttTopicName)), this, SLOT(messageReceived(QByteArray, QMqttTopicName))); //设置通信参数/主机地址和端口参数必须设置 mqttClient->setHostname("broker.emqx.io"); mqttClient->setPort(1883); //还有其他一堆参数根据情况按需设置 ..... //连接到服务器 mqttBase->connectToHost(); //从服务器断开 mqttBase->disconnectFromHost(); //发布主题 mqttClient->publish("qtmqtt/topic", "hello"); //订阅主题 mqttClient->subscribe("qtmqtt/topic"); ``` ## 3、使用说明 1. 设备模拟中如果设备地址填0则表示应答数据的地址取收到的地址,意味着永远不判断地址是否正确。 2. 设备采集会自动过滤掉离线的设备加快采集速度,并在60s间隔重新轮询一次所有设备,以便重新读取上线的设备,可以手动单击重新读取按钮用来立即执行采集所有设备。 3. **无论是modbus还是mqtt,都只是一个协议规范,根据该规范传输数据,至于对应数据位表示何种含义,比如是温度值还是湿度值,则由具体的厂家硬件决定,需要参考对应的厂家说明。** 4. 既然是个数据规范,那就无所谓是走串口还是网络或者websocket,理论上都可以,只是拿到数据后数据解析规则一样。 5. modbus slave模拟仿真工具使用说明 [https://zhuanlan.zhihu.com/p/529828725](https://zhuanlan.zhihu.com/p/529828725) 。 ### 3.1 设备采集-Server #### 3.1.0 数据格式 - 和其他的modbus采集工具一样,本组件也支持各种数据格式,其实就是高字节低字节的顺序。 - 一般是2字节表示一个数据,后面又有4字节表示一个数据,目前好像还有8字节表示一个数据的设备。 - 不同厂家的设备对应的字节顺序可能不同,要求可以自定义顺序,以便满足各种设备的接入。 - 4字节的数据可以是长整型,也可能是浮点数,按照标准规则计算出浮点值。面对浮点数的采集,很多国内的厂商的做法是以长整型数据发出,再除以一个值比如1000进行换算,因为下位机往往是单片机,单片机对浮点数运算很鸡肋很吃力。 - 最终的数据解析还要区分有符号和无符号,一般都是有符号,因为可以表示负数,比如温度就有负数。目前本组件都是按照有符号进行运算,后面有场景需要可以增加无符号的支持。 - 如果发现数据解析不对,可以调整对应的顺序试试。 - Short_AB,短整型数据,总共2字节,正序,高字节在前,低字节在后。 - Short_BA,短整型数据,总共2字节,反序,低字节在前,高字节在后。 - Long_ABCD,长整型数据,总共4字节,大端模式正序。 - Long_DCBA,长整型数据,总共4字节,小端模式正序。 - Long_BADC,长整型数据,总共4字节,大端模式反序。 - Long_CDAB,长整型数据,总共4字节,小端模式反序。 - Float_ABCD,浮点型数据,总共4字节,大端模式正序。 - Float_DCBA,浮点型数据,总共4字节,小端模式正序。 - Float_BADC,浮点型数据,总共4字节,大端模式反序。 - Float_CDAB,浮点型数据,总共4字节,小端模式反序。 #### 3.1.1 数据采集 ![](snap/iot_server1.jpg) 1. 第一步,协议类型选择,本组件支持多种协议类型,如果选择的com,则需要填写串口号和波特率,选择的网络比如TCP,需要填写主机和端口号。 2. 第二步,选择轮询间隔,默认1s轮询一次。 3. 第三步,选择超时次数,默认3次,表示轮询3次还没有收到数据就离线。 4. 第四步,添加要采集的设备地址,起始地址和采集数量。默认会有三个地址,可以直接在表格中修改后再单击保存。 - 单击添加按钮,会生成一个默认值的新行,直接修改值,单击保存。 - 单击删除按钮,会将当前选中的行删除,删除后自动保存。 - 单击保存按钮,将表格中的所有数据保存到配置文件。 - 单击清空按钮,将表格中的所有数据清空,自动保存。 - 第一列填设备地址,1个字节,范围值1-247,切记这里是从1开始的。在RTU协议中对应在第一个字节,在TCP协议中对应在第六个字节。 - 第二列填寄存器开始地址,2个字节,范围值0-65535,切记这里是从0开始的,0表示第一个寄存器。 - 第三列填采集的数量,2个字节,和开始地址组合使用,开始地址+数量<=65535。 - 如果开始地址填0,采集数量填2,则表示采集寄存器1、寄存器2共2个寄存器的数据。 - 如果开始地址填7,采集数量填4,则表示采集寄存器8、寄存器9、寄存器10、寄存器11共4个寄存器的数据。 - 第四列填数据顺序格式,见上面数据格式说明。一般填Short_BA、Long_DCBA。 5. 单击启动服务按钮,会自动启动采集线程,如果采集设备存在则会显示对应采集到的数据。 6. 单击重新读取按钮,会立即复位设备离线状态,全部设备重新采集。为什么需要这个?因为一般在modbus采集过程中,都是按照轮询的方式采集的,但是实际场景中有些设备坏了或者根本没有,那每次都占用一次轮询时间是不是很浪费呢?这就需要自动跳过离线的设备,加快轮询速度。但是可能后面设备又接上去了,这个时候要么等待重连时间到了去采集,要么单击这个重新读取按钮立即触发。联调过程中往往喜欢单击这个按钮去立即触发一次,因为重连时间可能会比较大,傻傻的等待也不是个事。 7. 单击清空数据按钮,会清空左侧显示的数据,双击左侧的显示栏也会自动清空。 8. 所有收发的数据也会打印显示在左侧,方便查阅分析问题,也可以看到采集到的最终的值。 #### 3.1.2 数据写入 ![](snap/iot_server2.jpg) 1. 第一步,生成寄存器复选框、寄存器地址框、寄存器数值框,数量下拉框选择要生成的数量,自动一键生成。复选框的作用是用于单个写入数据,勾选了则表示该寄存器需要写入数据。寄存器地址可以更改,默认按照0-64依次递增。 2. 第二步,填写好设备地址,从1开始,也就是对哪个设备写入数据。 3. 第三步,单击写入数据/单个写入按钮,对勾选的寄存器地址写入对应微调框的值。会依次发送勾选了所在行的值。 4. 第四步,如果需要连续写入,还需要设置开始地址和写入数量,再单击连续写入按钮。比如要连续写入数据 01 10 07 d4 00 02 04 00 00 03 e8 d9 8e ,设备地址填1,开始地址填2004(07 d4=2004),写入数量填2(00 02),上面寄存器面板中随便找个行,填寄存器地址2004(程序会自动找到要写入的起始寄存器地址所在行),右侧填数据0,下面这个寄存器数值填1000,00 00 03 e8 表示两个寄存器的数据,分别数寄存器2004=0,,2005=1000。由于是连续写入,所以只需要填第一个起始寄存器的地址即可,后面的只需要填数据。 5. 单个写入对应功能码0x06,连续写入对应功能码0x10。 6. 写入动作和写入成功在左侧都有打印显示,一般是深绿色颜色显示,关键字写寄存器。 7. 寄存器地址默认最大到64,可更改,比如要写入数据 01 06 07 D1 00 00 D8 87,则勾选一个复选框,然后在后面填地址2001(07 D1=寄存器地址2001),后面填数据0,单击单个写入按钮即可。 8. 所有的勾选状态和寄存器地址和数值,都会自动保存到配置文件,下次打开自动读取并应用。 ### 3.2 设备模拟-Com ![](snap/iot_com.jpg) 1. 第一步,填写要模拟的设备地址,0表示自动处理,也就是收到什么地址就应答什么地址。 2. 第二步,填写对应的串口号和波特率。 3. 第三步,单击打开串口,成功后会变成关闭串口字样。 4. 单击清空数据会将左侧打印栏的信息清空。 5. 右侧一堆微调框用于模拟对应设备多个寄存器地址的值,默认是8个寄存器,微调框可以输入负数值。 6. 单击随机按钮,用于随机生成该范围值的数据,方便测试验证数据。 ### 3.3 设备模拟-Tcp ![](snap/iot_tcp.jpg) 1. 第一步,填写要模拟的设备地址,0表示自动处理,也就是收到什么地址就应答什么地址。 2. 第二步,填写监听的端口号,这里没有指定网卡,默认绑定所有网卡。无论连接哪个网卡的端口都能连通。 3. 第三步,选择数据模式,可选Rtu模式和Net模式。 4. 第四步,单击开始监听,监听成功会变成关闭监听字样。 5. 单击清空数据会将左侧打印栏的信息清空。 6. 右侧一堆微调框用于模拟对应设备多个寄存器地址的值,默认是8个寄存器,微调框可以输入负数值。 7. 单击随机按钮,用于随机生成该范围值的数据,方便测试验证数据。 ### 3.4 设备模拟-Udp ![](snap/iot_udp.jpg) 1. 第一步,填写要模拟的设备地址,0表示自动处理,也就是收到什么地址就应答什么地址。 2. 第二步,填写监听的端口号,这里没有指定网卡,默认绑定所有网卡。无论连接哪个网卡的端口都能连通。 3. 第三步,选择数据模式,可选Rtu模式和Net模式。 4. 第四步,单击开始监听,监听成功会变成关闭监听字样。 5. 单击清空数据会将左侧打印栏的信息清空。 6. 右侧一堆微调框用于模拟对应设备多个寄存器地址的值,默认是8个寄存器,微调框可以输入负数值。 7. 单击随机按钮,用于随机生成该范围值的数据,方便测试验证数据。 ### 3.5 设备模拟-Web ![](snap/iot_web.jpg) 1. 第一步,填写要模拟的设备地址,0表示自动处理,也就是收到什么地址就应答什么地址。 2. 第二步,填写监听的端口号,这里没有指定网卡,默认绑定所有网卡。无论连接哪个网卡的端口都能连通。 3. 第三步,选择数据模式,可选Rtu模式和Net模式。 4. 第四步,单击开始监听,监听成功会变成关闭监听字样。 5. 单击清空数据会将左侧打印栏的信息清空。 6. 右侧一堆微调框用于模拟对应设备多个寄存器地址的值,默认是8个寄存器,微调框可以输入负数值。 7. 单击随机按钮,用于随机生成该范围值的数据,方便测试验证数据。 ### 3.6 发布订阅-Mqtt ![](snap/iot_mqtt.jpg) 1. 第一步,选择协议前缀,可选mqtt://、mqtts://、ws://、wss://四种,带s结尾的是走ssl通信,ws表示走websocket通信。一般选默认的mqtt://就好。 2. 第二步,填写服务所在主机地址,可以是IP地址也可以是网址,只要真实存在的就行。 3. 第三步,填写通信所用端口号,mqtt默认端口号是1883,以实际真实端口号为准。 4. 第四步,填写资源路径,这个要websocket通信才需要填写。 5. 第五步,选择协议版本,这个要和实际的一致,比如服务器不支持5.0,而这里选择的5.0则会失败。一般服务器都会支持所有协议,因为在通信过程中会告知当前用哪个协议。 6. 第六步,填写客户端唯一编号,这个可选,如果服务器要求一定要填写则填写,不然通信会失败。 7. 第七步,单击启动服务按钮,连接成功后会变成断开服务按钮。 8. 第八步,发布主题,先要在主题文本框中输入主题字符串,再单击发布主题按钮。 9. 第九步,订阅主题,先要在主题文本框中输入主题字符串,再单击订阅主题按钮。 10. 既可以发布主题,也可以订阅主题,还可以取消订阅的主题。 11. 有些场景只需要发布主题,比如下位机采集到的数据,则只需要发布主题带上数据内容即可。 12. 有些场景只需要订阅主题,比如上位机通过订阅主题获取到最新的数据,订阅对应主题后,有新的主题数据发布,就会立即更新通知订阅过的客户端。 13. 发布的主题和订阅的主题,标识可以相同也可以不同,同一个标识的才会触发通知机制,一旦发布主题就会触发通知订阅过该主题的客户端。 14. 切换到高级参数选项卡,有一堆高级参数可以设置,比如用户验证,这样可以防止非法用户访问。一般合法的用户信息需要在mqtt服务的后台设置,相当于权限控制。 ## 4、功能特点 1. 支持多种物联网通信协议,包括modbus和mqtt。 2. 协议方式支持串口com通信、网络tcp通信、网络udp通信、网络websocket通信。 3. 数据规则支持rtu模式和网络模式,网络rtu模式也就是modbus rtu over tcp/udp/websocket。相当于modbus串口协议数据走网络方式通信。 4. 支持批量连续写入寄存器数值和单个写入寄存器数值。 5. 支持数据顺序格式的设置,比如大端小端,高字节在前低字节在前的设置。支持Short_AB、Short_BA、Long_ABCD、Long_CDAB、Long_BADC、Long_DCBA、Float_ABCD、Float_CDAB、Float_BADC、Float_DCBA等。 6. 支持数据位字节数设置,比如短整型、长整型、浮点型等。常规的一般是2字节表示一个数据位,也有设备是4字节表示一个数据位,还有4字节浮点数的形式。后期可能还有8字节一个数据位。 7. 支持mqtt协议,可设置主机地址和端口、协议版本、唯一标号、用户名称、用户密码。 8. 支持mqtt发布主题、订阅主题、取消订阅。 9. 定时自动发布主题,可设置保活时间、超时时间、过期时间。mqtt通信自动重连。 10. mqtt模拟数据收发支持多种格式,文本、json、base64、hex等。 11. mqtt同时支持websocket方式,还支持ssl方式通信。 12. 支持多种采集通讯方式,包括串口和网络等,可自由拓展其他方式。可同时采集多路。 13. 自定义采集间隔(精确到毫秒)和超时次数,超时后自动将离线的文件从轮询队列中移除,加快轮询速度。 14. 可设置最大超时重连间隔,将离线的设备重新探测一次,保证设备恢复正常后能够重新加入轮询队列。 15. 同时提供了设备模拟工具,支持各种协议,支持设定多个设备的数据值。 16. 模拟工具可随机切换模拟数据值,要正常随机数据就模拟生成正常范围的数据,要报警数据就模拟生成报警范围的数据。方便测试。 17. 多线程采集和解析数据,以信号的方式发送解析结果,不卡主线程。 18. 架构采用基类继承方式,通用处理在基类,极易拓展其他通信方式。 19. 接口友好,使用非常简单,设置要采集的地址集合、开始索引集合、采集数量集合、数据顺序格式四个参数即可。会自动组装对应协议的数据发送。 20. 采集后的数据以统一格式的信号发出来,非常简单易用。支持浮点型数据。 21. 采集指令有优先级,如果有自定义的数据需要优先执行。可以将优先级高的指令调用append方法插入即可。可批量采集也可单个采集。 22. 支持利用现有的通信链路发送自定义数据,这个数据可以不是标准的modbus协议,比如有时候需要一些私有协议数据,利用现有链路发送下去执行。 23. 多线程高并发,每个端口采集都是一个独立的线程,互不干扰,支持成千上万个设备采集。 24. 代码做了兼容,支持各种编译器,同时支持Qt4、Qt5、Qt6。 25. 跨平台,支持windows、linux、mac、嵌入式linux、android、各种国产系统和开发板等。 ## 5、协议-modbus ### 5.0 模式说明 1. 总共有三种模式,RTU模式(一般用在串口),网络模式(一般用在网络),ASCII模式(一般用在串口,现在几乎很少用)。 2. RTU模式有校验字节,网络模式没有校验字节,因为网络通信数据不会出错。 3. 校验字节计算是从所有要发送的数据一起运算,从第一位的设备地址开始的,最后发送的完整数据是带上校验数据的。 4. 写单个寄存器的应答数据就是收到的数据,写多个寄存器的应答数据是包含起始地址和寄存器个数的数据体数据,并不是原数据,相当于告知从哪个起始地址开始写入了多少个寄存器数值成功。 5. 可以这么理解,网络模式的报文,包含了RTU的报文,是在RTU报文基础上,前面加上了事务标识符+协议标识符+长度字节共计6字节,同时末尾去掉了校验字节。网络报文=事务标识符+协议标识符+后面总共有多少个字节+RTU报文去掉校验字节。 6. 参考文章 https://blog.csdn.net/m0_47136030/article/details/139128639 ### 5.1 RTU模式 #### 5.1.1 读多个寄存器-0x03 发送:01 03 00 00 00 04 44 09 - 01 设备地址。 - 03 功能码。 - 00 00 开始读取地址。 - 00 04 读取数据长度。 - 44 09 CRC校验 接收:01 03 08 00 0a 00 03 00 11 00 04 2a 11 - 01 设备地址。 - 03 功能码。 - 08 数据长度,表示后面有8字节的数据。 - 00 0a 第一个寄存器数据。 - 00 03 第二个寄存器数据。 - 00 11 第三个寄存器数据。 - 00 04 第四个寄存器数据。 - 2a 11 CRC校验 #### 5.1.2 写单个寄存器-0x06 发送:01 06 00 00 00 19 48 00 - 01 设备地址 - 06 功能码 - 00 00 寄存器地址 - 00 19 寄存器数值 - 48 00 CRC校验 接收:01 06 00 00 00 19 48 00 说明:写入的什么数据就应答什么数据 #### 5.1.3 写多个寄存器-0x10 发送:01 10 00 00 00 04 08 00 19 00 58 00 37 00 2c af 65 - 01 设备地址 - 10 功能码 - 00 00 寄存器开始地址 - 00 04 总共写入多少个寄存器 - 08 总共写入8个字节数据/永远是数量的两倍 - 00 19 写入的第1个寄存器数值 - 00 58 写入的第2个寄存器数值 - 00 37 写入的第3个寄存器数值 - 00 2c 写入的第4个寄存器数值 - af 65 CRC校验 接收:01 10 00 00 00 04 c1 ca - 01 设备地址 - 10 功能码 - 00 00 寄存器开始地址 - 00 04 总共写入多少个寄存器 ### 5.2 网络模式 #### 5.2.1 读多个寄存器-0x03 发送:00 01 00 00 00 06 01 03 00 00 00 04 - 00 01 事务标识符,一般是递增序号。 - 00 00 网络标识符,标识当前是modbus协议。 - 00 06 长度字节,表示后面有6个字节数据。 - 01 设备地址。 - 03 功能码。 - 00 00 开始读取地址。 - 00 04 读取数据长度。 接收:00 01 00 00 00 0b 01 03 08 00 0a 00 03 00 11 00 04 - 00 01 事务标识符,递增序号,和发送的一致。 - 00 00 网络标识符,标识当前是modbus协议。 - 00 0b 长度字节,表示后面有11个字节数据。 - 01 设备地址。 - 03 功能码。 - 08 数据长度,表示后面有8个字节的数据。 - 00 0a 第一个寄存器数据。 - 00 03 第二个寄存器数据。 - 00 11 第三个寄存器数据。 - 00 04 第四个寄存器数据。 #### 5.2.2 写单个寄存器-0x06 发送:00 01 00 00 00 06 01 06 00 00 00 4d - 00 01 事务标识符 - 00 00 网络标识符 - 00 06 长度字节 - 01 设备地址 - 06 功能码 - 00 00 写入寄存器地址 - 00 4d 写入寄存器数值 接收:00 01 00 00 00 06 01 06 00 00 00 4d 说明:写入的什么数据就应答什么数据 #### 5.2.3 写多个寄存器-0x10 发送:00 01 00 00 00 0f 01 10 00 00 00 04 08 00 0e 00 55 00 37 00 2c - 00 01 事务标识符 - 00 00 网络标识符 - 00 0f 长度字节 - 01 设备地址 - 10 功能码 - 00 00 寄存器开始地址 - 00 04 总共写入多少个寄存器 - 08 后面总共有多少个字节数据 - 00 0e 写入的第1个寄存器数值 - 00 55 写入的第2个寄存器数值 - 00 37 写入的第3个寄存器数值 - 00 2c 写入的第4个寄存器数值 接收:00 01 00 00 00 06 01 10 00 00 00 04 - 00 01 事务标识符 - 00 00 网络标识符 - 00 06 长度字节 - 01 设备地址 - 10 功能码 - 00 00 寄存器开始地址 - 00 04 总共写入多少个寄存器 ## 6、协议-mqtt - 参考:https://blog.csdn.net/sinat_41690014/article/details/130171072 - 编号:0123456789abcdefghijklm - 用户:admin - 密码:admin - 主题:qtmqtt/topic - 消息:hello Qt ### 6.1 版本3.1 连接:103300064d514973647003c2003c0017303132333435363738396162636465666768696a6b6c6d000561646d696e000561646d696e 应答:20 02 00 00 心跳:c0 00 应答:d0 00 断开:e0 00 发布:3016000c71746d7174742f746f70696368656c6c6f205174 应答:3016000c71746d7174742f746f70696368656c6c6f205174 (订阅后才有应答) 订阅:82110002000c71746d7174742f746f70696300 应答:9003000200 ### 6.2 版本3.1.1 连接:103100044d51545404c2003c0017303132333435363738396162636465666768696a6b6c6d000561646d696e000561646d696e 应答:20 02 00 00 心跳:c0 00 应答:d0 00 断开:e0 00 发布:3016000c71746d7174742f746f70696368656c6c6f205174 应答:3016000c71746d7174742f746f70696368656c6c6f205174 (订阅后才有应答) 订阅:82110002000c71746d7174742f746f70696300 应答:9003000200 ### 6.3 版本5.0 连接:103200044d51545405c2003c000017303132333435363738396162636465666768696a6b6c6d000561646d696e000561646d696e 应答:2013000010270010000025012a01290122ffff2801 心跳:c0 00 应答:d0 00 断开:e0 00 发布:3017000c71746d7174742f746f7069630068656c6c6f205174 应答:3017000c71746d7174742f746f7069630068656c6c6f205174 (订阅后才有应答) 订阅:8212000300000c71746d7174742f746f70696300 应答:900400030000 ### 6.4 协议拆解 ### 6.5 协议区别 - 3.1版本可变头部关键字是MQISdP,3.1.1和5.0是MQTT。 ### 6.6 编译mqtt - 参考链接 https://blog.csdn.net/luoyayun361/article/details/128339682 - 第一步:下载源码 git clone https://github.com/qt/qtmqtt - 第二步:进入目录 cd qtmqtt - 第三步:查看分支 git branch -a 看完后按 q 退出 - 第四步:切换版本 git checkout -b 5.10 origin/5.10 - 官方的qtmqtt组件最低支持5.10,代码稍微改改可以最低支持到5.3,因为5.3开始才有websocket模块。 - 客户端即可以是发布者,也可以是订阅者。 - 一般都是实现客户端,mqtt服务程序一般用第三方开源的。 - mqtt就是一套协议规范,建立在tcp长连接基础上,发布者和订阅者其实就是两个tcpsocket客户端,连接到tcpserver服务器,所有数据交互都由服务器处理,通过主题来区分消息类型。 ## 7、效果图 ### 7.1 windows ![](snap/iotsystem_win.jpg) ### 7.2 linux ![](snap/iotsystem_linux.jpg) ### 7.3 wasm ![](snap/iotsystem_wasm.jpg) - 物联网平台可以编译成wasm版本,直接网页运行,现在的浏览器基本的原生就支持wasm,并不是activex方式。 - 和wasm版本通信,选择websocket方式,这就是为什么modbus协议部分还要支持websocket的重要原因。 ### 7.4 modbus slave ![](snap/iottool_modbusslave.jpg) - 本组件支持和著名的模拟工具modbus slave/modbus poll组合使用。 - 支持slave中的各种通信模式比如串口和网络,RTU OVER xxx。 - 数据顺序可以自定义设置。 ================================================ FILE: snap_map/readme.md ================================================ ## 1、编译说明 1. 编译完成以后记得将源码下的file目录下(切记是file目录下而不是file目录)的所有文件复制到可执行文件同一目录下即可。 2. 可执行文件在源码同级目录下的bin目录,和core_map目录同级,这个目录在编译后自动生成。 3. 在线地图一般都需要对应的秘钥,本源码中内置的地图秘钥是本人的秘钥字符串,由于对应地图官方并发量有限制,建议换成自己的秘钥体验更加。可以通过函数接口setVersionKey设置秘钥字串。 4. 百度地图申请步骤可以参考 https://blog.csdn.net/wodegeCSDN/article/details/130224845 。 5. 本地图组件通过浏览器控件加载,所以依赖浏览器控件。由于不同的Qt版本提供的浏览器控件不同,甚至部分Qt版本比如mingw的Qt不提供浏览器控件,所以专门提供了core_webview组件,该组件封装了webkit/webengine/miniblink三大浏览器内核,会自动根据Qt套件识别出使用哪种浏览器内核,比如windows上mingw版本的Qt会自动采用miniblink内核,linux系统会采用webengine内核。如果有疑问那其他系统的miniblink没有怎办?这个不用考虑,因为其他系统Qt都自带了浏览器控件,不存在这个问题。就windows上的mingw版本的Qt才缺少这个组件。 6. 如果启用的是miniblink内核,则需要将miniblink的动态库拷贝到可执行文件同一目录。dll_miniblink下载链接: https://pan.baidu.com/s/13LDRu6mXC6gaADtrGprNVA 提取码: ujm7 7. 特别提示:路线规划和周边搜索相关功能只能在线使用,因为相关算法放在服务器。 8. 如果需要加载国外的地图带英文以及当地语种,最佳组合是使用天地图离线模式加上谷歌地图瓦片。 9. 如果发现地图不能在线加载瓦片,比如天地图,那很可能默认的秘钥在当前的瓦片数量已经用完,更换个秘钥即可。 ## 2、离线地图 1. 想要加载离线地图,前提是要有下载好的离线地图文件,也就是一张张的图片文件。离线地图也叫瓦片地图,这些文件可以使用本项目的离线地图下载模块下载,也可以用第三方的瓦片地图下载器下载。 2. 如果暂时没有离线地图文件,可以直接下载提供好的离线地图文件测试。离线地图网盘地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:tiles.7z是徐汇区虹漕园精简版离线地图,bin_map_tiles.tar.gz 是上海市离线地图。 3. 将离线地图文件目录拷贝到可执行文件所在的对应地图内核目录下(mapbaidu/mapgaode/maptian/mapgoogle等),和map_load.js文件同一级目录。 4. **离线地图目录可能有三个文件夹,tiles表示街道图,tiles_hybrid表示卫星图,tiles_self表示自定义图层,比如需要混合路线图等。天地图比较特殊,分6个目录,对应官网定义的6种地图。街道图底图是tiles_vec_w,街道图路网是tiles_cva_w,卫星图底图是tiles_img_w,卫星图路网是tiles_cia_w,地形图底图是tiles_ter_w,地形图路网是tiles_cta_w。天地图图层说明 http://lbs.tianditu.gov.cn/server/MapService.html 。** 5. 同一种地图内核的离线地图文件通用。网盘上提供的瓦片地图是使用本项目的离线地图下载模块下载的,也可以自己用万能地图下载器下载,对应替换就行。 6. 瓦片地图就是一张张图片文件,格式可以自定义,如果格式不一样可能会加载失败,需要手动修改。比如百度地图内核需要打开/mapbaidu/map_load.js文件,将.png改成.jpg保存即可。 7. 经过大量的测试对比发现,无论哪种地图内核,卫星图是jpg格式,其他的比如街道图和叠加图都是png格式。 8. 如果想要既有全国地图的主要部分,又要有局部精确地图,可以将全国地图下面的文件复制到局部地图中,可以同名文件替换,这样就不会说在精确地图缩小到一定级别的时候周围出现空缺难看。 9. 由于整个中国的所有层级的离线地图文件非常巨大,至少是N个TB级别的,强烈建议按需下载,比如如果你的项目只需要在上海市使用,你只需要下载整个上海市的就好,不然打包发布的时候异常的巨大。一般的做法是全国的地图下载到12级别就好,然后需要的地区的地图下载12-19级别的。 10. 如果需要天地图离线地图加载谷歌瓦片文件(为什么会有这么奇怪的组合?其实一点不奇怪,因为只有谷歌地图的国外的地图有英文和当地的语种,但是谷歌的离线api接口不全,天地图的离线api接口很全,而且天地图是地球坐标系),先要下载好谷歌的瓦片文件,然后将下载好的街道图瓦片放到天地图的目录下maptian/tiles_vec_w,还需要复制一份一模一样的拷贝到maptian/tiles_cva_w。因为天地图是每一种地图都是分开两个目录的,都有底图和叠加层。 ## 3、代码使用 1. 将core_map(地图组件)/core_webview(浏览器组件)这两个组件目录拷贝到你的项目目录,并在pro中填写引入代码加入到你的项目中。$$PWD/../表示上级目录。 ```cpp include ($$PWD/../core_map/core_map.pri) include ($$PWD/../core_webview/core_webview.pri) ``` 2. 在pro中启用地图内核,比如百度地图内核对应需要在pro文件中增加一行定义 DEFINES += baidux,天地图内核对应 DEFINES += tianx。如果是vsqt环境,添加的位置大概在属性-》配置属性-》C/C++-》预处理器。 3. 引入头文件。 ```cpp #include "webview.h" #include "maphelper.h" #include "mapobjbase.h" ``` 4. 新建一个窗体,上面放一个布局,推荐用表格布局 gridlayout,可以放多个浏览器控件。 5. 实例化浏览器类和地图类。 ```cpp //实例化浏览器控件 WebView *webView = new WebView(this); //加入到布局 webView->setLayout(ui->gridLayout); //实例化地图类/参数2表示何种地图内核/你用的哪一种就填哪一种/必须正确 MapObjBase *mapObj = MapHelper::getMapObj(this, MapCore_Tian); //传入网页控件用于执行函数 mapObj->setWebView(webView); //直接加载地图 mapObj->load(); //异步加载地图 //QMetaObject::invokeMethod(mapObj, "load", Qt::QueuedConnection); ``` 6. 所有地图相关的函数接口在MapObjBase类中,可以打开mapobjbase.h查看具体说明。 7. 地图中大部分的功能都是通过执行js函数来触发,比如添加标注、添加折线图等。这些必须严格按照提供的js函数名称和参数来执行。对应示例都提供了相关的调用方法。 8. 部分函数接口。 ```cpp QString js; //设置地图级别(值越大放大/越小缩小) js = "setZoom(9)"; //街道图卫星图切换(0-街道图/1-卫星图/2-混合图) js = "setMapType(1)"; //添加一个标记(北京那边) js = QString("addMarker('测试点', '测试地址', '', '', 100, '%1', 2)").arg("116.475836,40.251114"); //最终通过浏览器控件执行 webView->runJs(js); ``` 9. 浏览器组件使用示例。 ```cpp //实例化浏览器控件 WebView *webView = new WebView(this); //加入到布局 webView->setLayout(ui->verticalLayout); //打开网址 webView->load("https://www.baidu.com", "", ""); ``` 10. 特别强调,浏览器控件的加载,会自动根据布局的大小调整自己的大小,如果窗体还没显示就调用了load,那么很可能地图出不来,原因是因为窗体没显示的时候,Qt中所有控件的尺寸都是不确定的,只有显示后才是确定的,所以一定要显示后再去加载地图。这种现象主要发生在需要主动调整浏览器控件尺寸的浏览器控件中,比如miniblink或者cef,如果是webkit或者webengine就不需要。 ```cpp int main(int argc, char *argv[]) { QApplication a(argc, argv); frmMain w; w.show(); //下面表示异步加载地图 QMetaObject::invokeMethod(&w, "loadMap", Qt::QueuedConnection); return a.exec(); } ``` ## 4、使用说明 ### 4.1 省市区域 ![](snap/map100.jpg) ![](snap/map101.jpg) ![](snap/map102.jpg) 1. 本组件采用echart编写,离线使用。单击对应的按钮可以切换到对应的效果演示。 2. 单击仪表盘按钮切换到一个标准的仪表盘示例,可以拉动上面的滑块调整对应的值。 3. 单击闪烁点图会切换到闪烁点图示例,每个闪烁点都可以设置不同的颜色和值大小,值越大点越大。 4. 单击迁徙图会切换到迁徙图示例,多个点往一个点迁徙,有箭头指向,可以设置不同的颜色和值大小,值越大点越大。 5. 单击世界地图会切换到世界地图示例,有七大洲的轮廓图,同样的也可以设置不同经纬度值的颜色和大小。 6. 下拉选择好对应的省会以及市区,再单击区域地图会切换到对应选择的市区的轮廓图,全国的市区的轮廓图都有。 7. 单击雨量分布会切换到指定的一个区域的雨量分布图,每个县都不同颜色点以及雨量值,鼠标移上去有悬停显示。 8. 单击设置颜色按钮前,需要切换到闪烁点图或者迁徙图,通过js函数去修改某个点的颜色。清除颜色按钮同理。 9. 生成js文件按钮用于将网上下载的区域的json文件转换成echart中可以直接识别的js文件。一键生成所有按钮用于将这个json目录的文件全部批量转成成js文件。 ### 4.2 地图示例 #### 4.2.0 基础功能 ![](snap/map200.jpg) 1. 右上角可以选择地图内核,默认是百度地图,还支持高德地图、天地图、腾讯地图等。 2. 地图模式可选在线地图和离线地图,离线地图不需要联网使用。 3. 下拉切换地图内核和地图模式,会自动重新加载当前内核和模式对应的地图,也可以单击加载地图按钮手动加载。 4. 单击最简示例按钮弹出地图示例的最简单使用示例,里面的代码非常简单。 5. 有两个列表复选框,左侧是启用地图控件功能列表,右侧是动态启用操作功能列表。 6. 左侧列表框勾选后,需要单击加载地图按钮重新加载,这些功能都是在加载的时候启用。 7. 右侧列表框勾选后,立即应用,通过执行对应的js函数触发的。比如勾选地图拖曳复选框,选中后,左侧的地图可以拖曳,不选中则不可以拖曳。切换复选框立即应用。 8. 选中地图测距复选框,左侧地图进入测距模式,可以直接在地图上选中多个位置进行测距。鼠标右键结束测距,测距结果会直接显示在信息框中,也可以在界面上直接看到每一段的距离。测距结束后,复选框自动取消选中。可以选中再次进行测距。 #### 4.2.1 最简示例 ![](snap/map201.jpg) 1. 由于整个地图模块的功能众多,有时候为了直接快速的上手使用,专门写了最简示例这个模块。 2. 单击在线地图和离线地图用于切换在线离线。 3. 单击视图切换按钮用于切换街道图、卫星图、混合图。 4. 单击执行函数用于演示如何执行js函数,比如设置地图级别、添加标注点等。 #### 4.2.2 绘图示例 ![](snap/map202.jpg) 1. 本组件除了提供直接函数接口绘图,还提供了动态绘图,也就是开启了某一种绘图模式后,直接在界面上鼠标按下直接绘制。 1. 右上角选择要绘制的覆盖物类型,比如选中多边形,直接在左侧地图中鼠标按下绘制即可,一般是长按或者双击结束绘制。 1. 右上角选择取消绘制,则会结束绘制模式,此时可以拖动地图,不然在绘制模式下拖动地图他会以为你要添加覆盖物。 1. 单击开启编辑,会将当前地图中的所有覆盖物进入编辑状态,也就是可以拖动位置和调整大小。 1. 单击结束编辑,会将当前地图中的所有覆盖物结束编辑状态。 1. 单击获取图元,会将当前地图中的所有覆盖物信息打印弹出提示,用户可以拿到这些信息再进行处理。 1. 单击清空图元,清空地图中所有绘制的覆盖物。 #### 4.2.3 Mdi示例 ![](snap/map203.jpg) 1. 演示多文档模式下打开多个地图,这里子地图窗体复用的最简示例。 1. 单击打开窗体,每次新生成一个地图窗体,可以用来测试支持打开N个地图窗体对应电脑CPU占用。 1. 可以切换默认排列、多页排列、级联窗体。平铺窗体看对应效果。 #### 4.2.4 地图标注 ![](snap/map204.jpg) 1. 从左侧地图上鼠标按下选点,对应经纬度坐标会填充到坐标文本框中。填写好对应的标注的名称,单击添加标注按钮,左侧地图中会出现一个标注点,鼠标单击对应的标准点,会弹出提示单击了哪个标注点。 2. 添加标注按钮的左侧是选择图片的类型,支持各种图片和尺寸,包括动图。默认图标文件在可执行文件下的mapimage目录。 - 默认图标:不指定图片文件,每种地图都有自己内置风格的标注图片。 - 水滴图标:指定一张水滴形状的图片,底部居中对齐。 - 圆形图标:指定一张宽高一样的圆形图片,居中对齐。 - 索引图标:指定一张大图,从中取固定位置的图标,网页中常见的方式。 - 动画图标:指定一张gif动图文件,底部居中对齐。 3. 在名称文本框中输入要删除的标注点名称,单击删除标注按钮,可以删除对应的标注点。后面改成了按照经纬度文本框中的经纬度坐标来删除标注点。两种方式都支持,可以代码中调整。 4. 在名称文本框中输入要删除的标注点名称,然后左侧地图中鼠标选点,单击移动旋转按钮,会自动将标注点移动到新的位置,并设置45度旋转。 5. 单击清空按钮会清空整个地图中的标注点。 6. 单击重置标注,将随机生成一些标注点以及位置,每次都是新的位置。 7. 在地址文本框中输入地址,单击地址转坐标,会查找到当前地址最近的经纬度坐标填入坐标文本框中。 8. 在坐标文本框中输入坐标,单击坐标转地址,会查找坐标文本框中经纬度坐标最近的地址填入地址文本框中。 #### 4.2.5 绘图功能 ![](snap/map205.jpg) 1. 本模块用来演示各种绘制功能,由于地图可能被拖动以及缩放,而本模块演示的都是固定的经纬度坐标,所以提供一个移动缩放按钮,用于切换到符合当前模块演示的地图坐标中心和缩放级别,方便查看绘图结果。 2. 单击添加折线按钮,会在地图中绘制一个折线图。后面的轨迹绘制等功能,其实就是用的绘制折线图功能。 3. 单击添加多边按钮,会在地图中绘制一个多边形,演示中复用的折线图坐标点集合,所以可以看到重叠了。多边形就是在折线图基础上增加了封闭区域,这个区域的背景颜色可以设置透明度。后面的行政区划功能,用的就是绘制多边形功能。 4. 单击添加矩形按钮,会在地图中绘制一个矩形,矩形区域只需要传入两个点坐标就行。 5. 单击添加圆形按钮,会在地图中绘制一个圆形,圆形区域需要传入中心点坐标和半径大小。 6. 单击添加弧线按钮,会在地图中添加一条弧线,这里演示也是复用的折线图的坐标,弧线的点可以开启拖曳编辑。 7. 上述绘制功能,基本上都提供了边框颜色、边框粗细、背景颜色等参数,具体参考示例源码。 8. 行政区下拉框中加载的一些已经保存的离线的行政区边界点坐标集合文件,默认从mapboundary目录加载,txt格式文件。 9. 行政区下拉选择后,会自动以多边形的方式绘制该区域的轮廓图。 10. 单击搜索行政区按钮,默认搜索上海市,搜索完成后,自动定位结果区域,并绘制轮廓图。此功能必须在线使用。 11. 搜索到对应行政区后,可单击获取行政区按钮,获取对应边界点经纬度坐标集合,结果显示在弹出的文本框中。 12. 单击添加点聚合按钮,可以添加点聚合,也就是密集区域的标注点会合并到一个大的标注,该标注会显示当前大标注点下有多少个小的点,单击该大标注,会自动切换到小标注的画面,这样就不会说密密麻麻的挤一块。 13. 单击添加海量点按钮,可以添加海量点,由于marker标注还是一个比较大的实例对象,所以当点好多万的时候,用marker的方式还是性能非常糟糕,这个时候就要考虑用海量点的方式,这种方式底层就是绘制形状的,性能非常高,而且也能识别单击了哪个点。 14. 单击获取覆盖物,会将当前地图中的标注点、折线图、多边形等对象的信息,发送出去,收到后,弹出框显示。用户可以拿到这个信息进行其他处理。 15. 单击清空覆盖物,会将所有的覆盖物对象删除。 #### 4.2.6 路径规划 ![](snap/map206.jpg) 1. 路径规划也叫路线查询,可以查询公交路线、自驾路线、步行路线、骑行路线。本功能必须在线才能使用,因为路径规划方案都是存储在服务器上的。 2. 查询的前提是要选择好起点终点,单选框选中哪个,当前地图选点的结果就填在哪个文本框中。 3. 公交路线可以选择不同的策略比如推荐方案、最少时间、最少换乘、最少步行、不乘地铁等。 4. 自驾路线可以选择不同的策略比如优先高速、避开高速、避开拥堵。 5. 不同的地图内核对应有不同的策略方案,在下拉切换地图内核的时候会自动设置。 6. 单击对应的查询路线后,左侧会显示对应的结果,对应的时长和路程会信号发出来,并显示在信息框中。 7. 单击清空路线会将查询的结果清空。 8. 单击绘制路线会将当前查询的路线重新绘制,其实就是绘制的折线图。如果有多段路径则每段路径都不同颜色显示。 9. 一般的做法是通过在地图上选点,单击步行路线,然后拿到这个最优路径的坐标点集合,最后发给设备比如无人机或者机器人,让他按照这个轨迹导航运动。 #### 4.2.7 搜索信息 ![](snap/map207.jpg) 1. 搜索信息支持三种方式的搜索,单击对应按钮后,会弹出搜索结果,并自动以标注点形式添加到地图上。 2. 部分地图内核支持指定城市名称搜索,可以从地区下拉框中选择或者填入对应的城市名称比如北京市。 3. 文本框对应搜索的关键字,支持多个,可以手动输入。 4. 焦点切换到起点和终点以及中心点文本框,在左侧地图中鼠标选点,自动填入。如果两个文本框都没有焦点,则鼠标选点失效。 5. 起点和终点文本框是用于区域搜索,中心点文本框是用于周边搜索。 6. 单击标准搜索,自动以当前视野范围内或者指定的城市名称进行搜索。 7. 单击区域搜索,取起点作为左下角坐标,终点作为右上角坐标,形成矩形区域进行搜索。 8. 单击周边搜索,也就是圆形区域搜索,以中心点坐标,默认半径1000米以内,形成圆形区域进行搜索。 #### 4.2.8 其他功能 ![](snap/map208.jpg) 1. 单击获取缩放按钮,可以获取当前地图的缩放级别和中心点坐标。 2. 单击获取区域按钮,可以获取当前地图视图的区域的经纬度坐标,比如左下角的经纬度、右上角的经纬度、地图中心点的经纬度。 3. 下拉选择视图,比如街道图、卫星图、混合图,可以进行对应的视图切换。 4. 下拉选择样式,设置地图的样式风格。不同的地图内核有不同的样式风格。 5. 填写旋转角度值,单击设置可以对当前地图旋转,要对应地图以及浏览器支持。 6. 填写倾斜角度值,单击设置可以对当前地图倾斜,要对应地图以及浏览器支持。 7. 目前发现只有webengine浏览器内核支持,而且一般要gl版本的地图才有旋转和倾斜这两个功能。 ### 4.3 离线下载 ![](snap/map300.jpg) ![](snap/map301.jpg) #### 4.3.1 区域地图 1. 第一步,选择地图内核比如百度地图。 2. 第二步,选择要下载的地图类型,比如街道图。 3. 第三步,区域选择行政区域,在地名中输入要下载的省市区域比如上海市、江西省等。**注意,这里既是下拉框也是输入框,下拉框只提供了常用的几个测试的地址,你也可以直接文本框中输入你想要的地址比如武汉市。这种控件叫可输入下拉框。** 4. 第四步,单击加载按钮,会将当前填的区域自动识别并切换到合适的视图范围,同时计算对应的地图级别对应的瓦片数量,显示在右上角的表格中。 5. 第五步,选中要下载的地图的级别,比如勾选12和13则只会下载该级别的离线地图。切记按需下载,不然地图文件数量过多,不知道下载到何年何月。 6. 第六步,单击开始按钮,会自动多线程下载瓦片地图,在提示栏中会显示当前下载哪个级别哪一张图片文件。 7. 单击暂停按钮可以暂停下载,开始按钮单击后会变成停止按钮,也可以单击停止按钮来结束当前的下载。 #### 4.3.2 全国地图 1. 第一步,步骤和下载区域地图一致,唯一区别是,区域下拉框选择可视区域。 2. 第二步,左侧地图鼠标滚轮缩放到合适级别,以便显示整个中国地图的全貌。 3. 第三步,单击加载按钮,自动计算全国地图的级别瓦片数量。 4. 第四步,勾选要下载的级别。 5. 第五步,单击下载按钮。 #### 4.3.3 谷歌地图 1. 由于谷歌在线地图需要翻墙使用,而绝大部分用户电脑没有翻墙,需要离线使用谷歌地图。 2. 一开始没有瓦片地图文件的情况下,加载离线地图后是空白的页面,所以需要有个策略如何一步步下载需要的区域的地图。 3. 离线地图下载模块默认中心点是上海市,当前地图级别可以通过右下角的获取按钮查看。 4. 下面举例以下载中东国家沙特为例子,如何一步步下载沙特的离线地图。 5. 第一步,鼠标滚轮将地图级别缩放到3级,也就是最小的级别,此时一般都是能够显示整个地图。首次没有下载地图文件的情况下,地图区域应该是一片空白。 6. 第二步,右上角勾选3/4/5这几个级别,具体看有多少瓦片数量,一般到5级别的瓦片数量还是比较少的,如果不想等待太久,也可以就勾选3级别,单击开始按钮进行下载,下载后可以在左侧地图区域鼠标滚轮缩放,先到4级再到3级等,可以看到能够加载到部分地图,这个时候就能看清当前地区区域大概在哪里,这一步至关重要。 7. 第三步,左侧地图区域,拖动到中东沙特,滚轮缩放到5级别,调整合适的视图,直到整个沙特在整个区域的大致中间部分即可。单击左下角加载按钮,加载当前区域的瓦片地图下载信息到右侧的表格中,逐步勾选6/7/8级别,单击开始按钮慢慢下载。如果没有时间要求,也可以多勾选一些级别,就慢慢的等他下载就好。 8. 第四步,重复第三步的步骤,直到需要的区域全部下载完成。 9. 第五步,由于下载服务器可能有并发限制,不能保证所有瓦片地图一次性全部下载成功,尽管程序中有失败重新下载3次的机制,但是也有可能3次都下载失败,所以需要通过鼠标滚所缩放查看,哪个区域空白就表示该区域下载失败,重新下载该区域对应级别的瓦片就好。一般来说,网络正常以及服务器正常的情况下,都是能够完全正常下载成功的。 #### 4.3.4 特别提示 1. 街道图默认保存到tiles目录,卫星图保存到tiles_hybrid目录,路网图保存到tiles_self目录。可能这个目录描述不够准确,后期有可能会调整到更准确的命令,比如街道图 tiles_normal,卫星图 tiles_satellite,混合图 tiles_hybrid。 2. **天地图比较特殊,他是每一种都分底图和路网图,这样下来也就是有6种,地图类型下拉框是:街道图=矢量底图+矢量注记,卫星图=影像底图+影像注记,地形图=地形底图+地形注记。对应目录是:街道图底图是vec_w,街道图路网是cva_w,卫星图底图是img_w,卫星图路网是cia_w,地形图底图是ter_w,地形图路网是cta_w。** 3. 下载地图建议用多线程,以便最快速度下载,可以在其他设置界面中填入线程数量。 4. 选择行政区域和可视区域,唯一区别就是,行政区域当填入地名后单击加载按钮,会自动查找该区域所在地图位置,并切换显示。而可视区域是手动调整左侧地图的可视范围内的地图。 5. 如果提示瓦片数量过多,建议选择可视区域,然后左侧鼠标缩放到合适级别,让要下载的区域刚好填满整个屏幕,这样就不会下载多余的瓦片,往往这些多余的瓦片数量是不需要的。如果瓦片还是过多,建议再细分区域下载,比如一个省的可能太多,逐个按照市区来下载,一般是大级别的才会有很多瓦片。 6. 天地图每个秘钥每天的下载数量有限制。个人开发者限制在1W张,企业开发者限制在30W张,可以切换到账号后台 https://console.tianditu.gov.cn/api/traffic 查看具体限制。建议多申请几个秘钥,在MapDownHelper::initUrl()函数中加入多个秘钥,下载的时候会自动切换秘钥下载,这样可以下载的张数多一些。还有就是下载的时候尽量分区域下载,不然你都不知道下载到哪里失败了,以便下次继续下载。 7. 谷歌瓦片地图下载有可能需要翻墙才能使用。 8. 如果加载下载好的离线地图没有看到,只有一种可能,那就是当前级别当前范围的离线地图没有下载,你需要鼠标滚轮缩放和拖动到你下载的区域和级别才能看到,那问题来了,都没有图片也不知道拖到哪里了,所有就需要下载离线地图的时候,先下载大致的全国地图的轮廓,也不多,比如全国的下载到12级别,这样就可以拖动到对应的省份市区等。 9. 下载好的地图文件使用方法看本文档中的离线地图使用说明。 ### 4.4 省市轮廓 ![](snap/map400.jpg) ![](snap/map401.jpg) 1. 为什么需要这个功能?比如一些变电站系统,每个变电站可以覆盖的范围都是固定的县或者乡镇,这样就需要开启编辑,然后获取到编辑后的点坐标集合,保存到文件,最后系统中加载这个文件,形成该区域的轮廓图。 2. 部分地图内核比如百度地图和高德地图等,才有行政区划的功能。 3. 右上角表格显示的全国的省市区域,可以勾选要下载哪些。 4. 可以设置下载的间隔,默认1s就行。 5. 保存文件的类型可以选择脚本文件(js拓展名)和文本文件(txt拓展名)。 6. 有三种方式可以收到返回的边界点集合,分别保存到不同的目录。 7. 方式一,从省市县城下拉框中选择后,单击加载按钮,会自动加载选择的行政区的边界图。这种保存到mapboundary目录下,文件名以行政区域命名。 8. 方式二,单击编辑按钮,按照行政区文本框中输入的区域,自动加载该行政区划的边界,并开启边界模式,可以在左侧地图中拖动边界点,完成后,单击获取按钮。此时返回的数据保存到mapboundary目录下,文件名以填入的那个文件名为准,相当于另存为。 9. 方式三,单击开始下载按钮后,自动定时器下载选中的省份的边界点数据,这种保存到mapboundary目录下,同时按照省份名称新建子目录,在子目录中保存。 ### 4.5 动态点位 ![](snap/map500.jpg) 1. 本示例演示在地图上选点,添加主标注点和子标注点,其中主标注点有图标,每个标注点带速度值、时间等信息。 2. 第一步,左侧地图鼠标按下选点,坐标文本框显示对应的坐标,同时拆分经纬度值显示在对应文本框中。 3. 第二步,选择是否是标记点,填写速度和时间。 4. 第三步,单击添加标注按钮,将标注信息加入表格中。 ### 4.6 海量点位 ![](snap/map600.jpg) ![](snap/map601.jpg) 1. 本示例演示一键生成指定数量的标注点、点聚合、海量点,用于对比性能,比如在10000个数量下,明显海量点优于其他。项目中可以根据实际情况选择何种方式,点数量越多,性能排序依次是海量点、点聚合、标注点。但是海量点能够表示的信息比较少,就是一个绘制的图形,默认是圆形。 2. 本示例另一个主要功能是演示GPS坐标转换,由于政策原因,国内所有地图不能使用GPS坐标表示,理论上各个地图厂家提供的坐标系统都不是GPS的,要做转换。转换多少都有点误差,据说误差控制在5米。 3. 不同地图内核有不同的转换类型,比如百度地图本示例中支持GPS转百度、GPS转其他、百度转其他、其他转百度,四种方式的坐标转换。高德地图支持GPS转高德、图吧转高德、百度转高德三种方式。 4. 左侧地图鼠标按下选点后,单击一键转换,会显示对应结果,同时将两种结果以标注的形式显示在地图中,以便查看位置区别。一个是原来的位置,一个是计算后的位置。 ### 4.7 动态轨迹 动态轨迹模块中包括两个部分,一个是直接界面选择起点终点规划路径,然后按照路径移动。一个是轨迹回放,生成轨迹点或者从轨迹点文件中读取轨迹点进行回放。 #### 4.7.1 动态轨迹 ![](snap/map700.jpg) 1. 第一步:选择地图内核,下拉地图内核可以动态切换地图内核。 2. 第二步:选择移动模式,支持多种移动模式,用来控制是否重新动态绘制当前轨迹进度以及是否循环移动。 - 重新绘制-单次:标注点移动到哪里,轨迹折线就绘制到哪里,只移动一次就结束。 - 重新绘制-循环:标注点移动到哪里,轨迹折线就绘制到哪里,移动完成后再次从起始点开始移动。 - 沿线运动-单次:标注点沿着路径点移动,只移动一次就结束。 - 沿线运动-循环:标注点沿着路径点移动,移动完成后再次从起始点开始移动。 - 沿线绘制-单次:标注点沿着路径点移动,同时以不同颜色绘制走过的路径,只移动一次就结束。 - 沿线绘制-循环:标注点沿着路径点移动,同时以不同颜色绘制走过的路径,移动完成后再次从起始点开始移动。 3. 第三步:选择移动速度,一般值越大速度越快。 4. 第四步:选择起始点和结束点坐标,需要在左侧地图中鼠标按下选点,起点和终点文本框后面有个复选框,哪个勾选了,则表示在当前地图中鼠标按下选中的经纬度坐标自动填入该文本框中。 5. 第五步:单击查询轨迹,查询结果路径中的经纬度坐标集合显示在右上角表格中。 6. 第六步:筛选数据,这一步可选,不筛选则以最原始的路径点数据进行。 - 筛选数据是根据设定的点数量,从原始数据中取平均值。数量表示最终筛选后的点数。 - 比如原始数据总数量是90个,点数填30,表示依次从90个数据中每3个点取一个,这样刚好是30个点。 - 为了保证数据完整性,第一个点和末位点一定在筛选数据中。也就是按照设定的点数取值后,可能会多出两个数据点。 - 为什么需要筛选?因为查询出来的轨迹点数量可能很多,比如几千几万个,如果每次移动一个点,这样速度很慢。 - 实际使用过程中还有一种情况是,无人机或者机器人,下发给他的经纬度坐标点数量有限制,这就也需要筛选数据。 - **筛选后如果想恢复数据,数量填0重新筛选即可,0表示不筛选直接用原始数据。** 7. 第七步:保存轨迹,这一步可选,可以将当前查询的轨迹点保存到文本文件中,以便提供给轨迹回放模块使用。 8. 第八步:模拟轨迹,单击模拟轨迹按钮,会按照设定的速度模拟轨迹点移动,按钮字样变成停止模拟,再次单击则停止模拟。 9. 第九步:轨迹回放、多条轨迹、实时轨迹,这一步可选,单击按钮弹出对应界面。 #### 4.7.2 轨迹回放 ![](snap/map701.jpg) 1. 下拉地图内核可以切换所使用的地图。 2. 也可以选择在线地图还是离线地图,目前百度地图有离线地图。 3. 切换地图类型,可以切换到街道图、卫星图、混合图三种。有些用户希望在混合图中看轨迹回放效果。 4. 选择移动的速度,值越大速度越快。 5. 下拉选择数据来源,可选两点坐标、多点坐标、轨迹数据、选择文件。 6. 两点坐标用来测试给定两个点坐标,在两点个形成的直线上运动,主要是为了验证不是跳跃运动,而是匀速移动。底层会根据线条自动生成了平滑的移动点。 7. 多点坐标用来测试多个经纬度坐标点,生成的曲线运动。 8. 轨迹数据就是固定从 data/points.txt 文件读取轨迹点显示,这个文件是上一个界面单击保存轨迹按钮后生成的文件,在这里可以看到回放。由于不同地图内核使用的坐标系不一样,所以这个文件的路径轨迹未必一致。比如百度地图搜索出来的轨迹点,放到其他地图上绘制,看起来经过的路线不正确,坐标点并不在实际的路线中。高德和腾讯的通用。 9. 选择文件用来演示导入自定义的轨迹点坐标,默认文件是 data/points_gps.txt ,这是个手机上gps移动导出的模板文件,如果你的文件刚好是这个格式可以直接用。 10. 如果想用最简单的方式测试轨迹点,打开 data/points.txt 文件,填入至少两个点坐标,格式46.688610,24.773230;46.691320,24.773930 ,然后下拉框选择轨迹数据即可。 11. 在这里演示移动轨迹的时候,部分地图内核会自动设置当前地图中心点是最近的一个点,这样看起来效果更好。 12. 后期有可能会增加其他下拉框,或者更新对应的文字描述,大致功能大差不差,以实际的为准。 #### 4.7.3 多条轨迹 ![](snap/map702.jpg) 1. 演示如何回放多条轨迹,默认2条轨迹,可以自行修改支持更多条。 2. 每条估计都可以设置不同的颜色、粗细等属性。 3. 每条都可以设置不同的标注图标等参数。 #### 4.7.4 实时轨迹 ![](snap/map703.jpg) 1. 演示实时轨迹效果,传入收到的经纬度坐标,添加到轨迹中移动。并不是轨迹回放,需要提前设置好轨迹数据。 2. 里面默认从资源文件读取的几个轨迹文本数据作为演示数据,实际情况可以传入收到的一个经纬度坐标。 3. 自带平滑算法,也就是上一个点到新的点形成的直线,并不是一下子跳跃过去的,而是生成了多个线上的点,慢慢移动过去的,看起来很平滑。 4. 自带旋转角度算法,到下一个点会自动计算出来旋转角度,然后对应标注点图标旋转对应的角度移动,这样效果更逼真。 5. 默认演示是两条轨迹,可以自行修改增加更多条,每一条的颜色粗细等参数都可以设置。 ### 4.8 高级绘图 #### 4.8.1 各种示例 ![](snap/map800.jpg) 1. 为什么地图示例那边已经有通过函数接口绘制以及进入绘图模式绘制,这里还要来个高级绘制? 2. 这里着重演示如何更新覆盖物的参数比如多边形的坐标数据和圆形的半径等,也就是如何修改覆盖物信息。因为如果频繁的创建和删除覆盖物,很可能存在内存泄漏,很多时候只需要更新覆盖物信息即可,并不需要删除重新创建。 3. 根据具体的应用场景,单独提供了多个高级绘制的示例,比如雷达扫描和航迹模拟等,后续可能还会增加其他示例。 4. 图元也叫覆盖物,按钮上可能叫图元,其实就是覆盖物。 5. 类别下拉框,选择要添加的图元的类型,支持标注点、矩形、圆形等。 6. 标识文本框,填写要添加的图元的唯一标识,每添加一个后会自动+1,可以手动修改,当需要指定删除、修改、编辑的时候,需要手动填入唯一标识。 7. 添加图元,按照类别下拉框的类型,随机位置添加对应图元。 8. 删除图元,先要填写唯一标识,单击后会自动删除对应标识的覆盖物图元。如果标识不存在则不作处理。 9. 修改图元,先要填写唯一标识,单击后会自动修改图元的覆盖物属性,标注点演示的移动位置和修改图片,圆形演示的修改中心点和半径,其他覆盖物演示的修改路径点集合。 10. 清空图元,清空界面上添加的所有覆盖物。 11. 开启编辑,先要填写唯一标识,单击后会让对应覆盖物进入编辑模式,比如圆形会出现中心点和边缘手柄,拖动中心点手柄可以移动圆形到新的位置,拉动边缘手柄可以更改半径大小。标注点可以拖动到新的位置等。 12. 关闭编辑,先要填写唯一标识,单击后会让对应覆盖物结束编辑模式。 13. 开启监听,单击后对界面上的所有覆盖物添加鼠标按下事件,这里做了联动处理,在receiveDataFromJs识别到按下了哪个覆盖物,然后立即设置该覆盖物进入编辑模式,可以调整大小和拖动位置。 14. 关闭监听,结束对所有覆盖物的监听。 15. 显示所有,对界面上的所有覆盖物显示。 16. 隐藏所有,对界面上的所有覆盖物隐藏。 17. 折线箭头、直线箭头、斜线箭头、扇形区域几个是天地图独有的高级绘图接口。如果切换到其他地图内核,这里灰色不可用。 18. 添加扇形和删除扇形用来演示如何添加扇形以及指定唯一标识删除扇形。 #### 4.8.2 雷达扫描 ![](snap/map801.jpg) 1. 绘制三个固定的圆形对象,半径和颜色不一样。 2. 绘制一条直线,不断的做圆形路径移动。 3. 绘制一个扇形,不断的做圆形路径移动。 4. 绘制几个标注点对象,定义图标船形,不断的移动位置。 5. 右上角可以切换模拟的时间间隔,值越小速度越快。 6. 可以单击按钮来启动和停止模拟。 7. 支持缩放自动跟随,也就是地图变小这个图形也变小。 8. 可用来测试不断的变更覆盖物信息,会不会造成内存泄漏。 #### 4.8.3 航线模拟 ![](snap/map802.jpg) 1. 这里演示一个飞机沿着圆形的轨迹移动,方向角度跟着变化。 2. 可以切换顺时针还是逆时针旋转。 3. 可以切换每次移动的步长。 #### 4.8.4 航线规划 ![](snap/map803.jpg) 1. 自动加载和保存航线标注点。 2. 地图上单击标注点自动选择表格行。 3. 选中表格行对应标注点自动高亮。 4. 航线带方向箭头指示。 5. 支持删除指定标注点以及清空所有标注点。 6. 支持直接在地图上拖曳标注点移动位置。 7. 删除标准点后重新排序绘制航迹。 8. 标注点带序号图标显示,清晰直观。 9. 单击开始回放按钮,自动生成一个轨迹回放对象,在对应轨迹上移动。 10. 可以暂停和继续回放,看对应暂停效果。 ### 4.9 高程海拔 ![](snap/map900.jpg) 1. 本示例演示实时海拔高程数据。 2. 本地离线读取,传入的经纬度获取海拔高度值。 3. 地图比例尺1000米,可以去官网下载更详细的区域地图高程数据,比例尺精度更高。 ### 4.x 网页控件 ![](snap/map1000.jpg) 1. 本示例演示通用浏览器控件如何使用。 2. 单击打开网页,直接打开输入的网页地址。 3. 单击打开文件,选择本地网页文件加载。 4. 由于不同浏览器对js的支持程度、webgl的支持程度有限,所以可以在这里看到不同的效果,甚至有些网页未必在某些浏览器模块下能正常打开。 ## 5、函数事件 ### 5.1 函数说明 - 地图组件中封装了一些常用的函数,比如单击marker标注点弹出提示。 - 所以定义过的函数,在演示示例中都有对应用法。 - 目前发现如果使用webengine浏览器模块,天地图的dblclick双击事件有bug,会触发两次,官网的示例也是一样的bug。webkit和miniblink内核正常,应该属于天地图和浏览器底层兼容性问题。因为百度地图等其他地图在webengine下双击事件正常。 ### 5.2 函数列表 - addMarkerEvent(flag, action, event, tips, width),用于添加标注点事件/单击/双击/右键,flag表示标注点的唯一标识,action表示动作,0=不处理,1-内部弹窗,2-发信号通知。event表示事件类型,click-单击,dblclick-双击,rightclick-右键,tips用于内部处理弹出提示的内容,可以是html字符串。width表示内部弹窗最小宽度。 - 每次添加事件的时候内部会先移除同名的事件,可以添加多个事件,比如单击和右键同时存在。 ### 5.3 事件说明 - 地图组件通过各种事件通知来发送和传递数据,每个事件通知都带有唯一标识参数和具体数据内容参数,用户拿到唯一标识进行判断,再取出对应数据。 - 如果数据内容有多个内容,一般会以英文竖杠 | 隔开,可以转成字符串后再分割字符串挨个取出。 - 内容中的经纬度坐标,统一格式先经度再纬度,比如 121.424362,31.175942,因为有些地图比如谷歌地图默认是纬度经度,不统一的话不方便处理,所以整个组件中传入和传出的经纬度坐标都统一先经纬再纬度。 - 返回的参数标识全部小写。 ### 5.4 事件代码 ```cpp //关联浏览器控件信号 webView = new WebView(this); connect(webView, SIGNAL(receiveDataFromJs(QString, QVariant)), this, SLOT(receiveDataFromJs(QString, QVariant))); void frmMapDemo::receiveDataFromJs(const QString &type, const QVariant &data) { //下面演示的功能比较多/通过类型判断是什么数据回复的再进行处理 //qDebug() << TIMEMS << "frmMapDemo" << type << data; QString result = data.toString(); if (type == "rightclick") { QString pos = QString("[%1, %2]").arg(QCursor::pos().x()).arg(QCursor::pos().y()); QString info = QString("触发鼠标右键\n当前经纬度值: %1\n当前鼠标坐标: %2").arg(result).arg(pos); QtHelper::showMessageBoxInfo(info); } else if (type == "dblclick") { QString info = QString("触发鼠标双击\n当前经纬度值: %1").arg(result); QtHelper::showMessageBoxInfo(info); } else if (type == "routepoints") { datas.clear(); QStringList list = result.split("|"); foreach (QString data, list) { datas << data.split(";"); } } } ``` ### 5.5 事件列表 |事件|内容|说明| |:------ |:------|:------| |click|lnglat|鼠标单击事件,参数为对应经纬度坐标。| |dblclick|lnglat|鼠标双击事件,参数为对应经纬度坐标,一般双击地图会放大地图。| |rightclick|lnglat|鼠标右键事件,参数为对应经纬度坐标。| |zoomstart||地图缩放开始。| |zoomend||地图缩放结束。| |zoom_changed||地图缩放改变。| |bounds|lnglat1\|lnglat2\|lnglat3\|11|返回可视区域经纬度,坐标依次是左下角/右上角/中心点,末尾表示当前缩放级别。| |center|lnglat|返回当前地图中心点坐标。| |zoom|12|返回当前地图缩放级别。| |rotate|45|返回当前地图旋转角度,部分地图内核才支持。| |tilt|30|返回当前地图倾斜角度,部分地图内核才支持。| |movestep|flag\|lnglat|轨迹点每移动一步触发一次,参数是唯一标识\|经纬度坐标。| |moveend|flag|轨迹点移动结束触发一次,参数是唯一标识。| |boundary|lnglat1;lnglat2\|lnglat1;lnglat2|行政区划轮廓图边界点坐标集合,其实就是多边形区域。一个行政区可能有多个不同的区域,比如某些岛屿,一个区域的经纬度坐标用英文分号 ; 隔开,多个区域用英文竖杠 \| 隔开。| |newboundary|lnglat1;lnglat2\|lnglat1;lnglat2|同上,唯一区别就是boundary类型是通过执行searchBoundary函数返回的,newboundary类型是通过执行getBoundary函数返回的/也就是单击获取边界按钮触发的。| |geocoderresult|flag\|lnglat/address|地址转坐标对应标识AddrToPoint,坐标转地址对应标识PointToAddr。| |convertresult|lnglat1\|lnglat2\|lnglat3|坐标系转换,如果传入的是多个坐标,则返回多个坐标。| |searchresult|title;address;lnglat\|title;address;lnglat|搜索关键字返回,参数依次是标题/地址/经纬度坐标,可能有多个,中间用英文竖杠 \| 隔开。| |routeresult|duration\|distance|路径规划查询结果返回,时长和距离。| |routepoints|lnglat1;lnglat2\|lnglat1;lnglat2|路径规划查询结果返回,路线的经纬度坐标集合,可能有多条路线,中间用英文竖杠 \| 隔开。| |distance|88.8|地图测距过程中每一步触发一次,返回对应距离。| |distanceend|188|地图测距完成触发,返回对应总距离,单位米。| |overlayinfo|type\|flag\|point/points等|调用getOverlayInfo函数触发,获取覆盖物信息,对应type可以是marker/circle/polyline/polygon。对应flag是覆盖物的唯一标识,在添加覆盖物的时候指定,没有指定则为空。如果是标注点则返回经纬度坐标,如果是多边形则返回坐标集合,圆形则依次是中心点/半径/边缘坐标集合。多个覆盖物则每个都会触发一次。| |pointcollection|lnglat|单击点聚合中的某个点触发。| |marker|flag\|event|鼠标单击双击右键标注点触发,event=click/dblclick/rightclick| |polyline|flag\|event|鼠标单击双击右键折线条触发,event=click/dblclick/rightclick| |polygon|flag\|event|鼠标单击双击右键多边形触发,event=click/dblclick/rightclick| |rectangle|flag\|event|鼠标单击双击右键矩形触发,event=click/dblclick/rightclick| |circle|flag\|event|鼠标单击双击右键圆形触发,event=click/dblclick/rightclick| |||| |||| ## 6、组件框架 - core_map是整个地图组件的完整源码,可以独立使用,拷贝过去集成到自己项目中即可。 - mapdown开头的类是用于瓦片地图下载,mapobjbase是所有地图的基类,由于通用代码较多,又拆分出来mapobjfun的类专门生成通用js函数,其他mapobj开头的都是具体地图的子类实现。其余类都是辅助类。 - 如果需要自定义一种地图内核,只需要继承自mapobjbase类,实现几个具体的基类函数即可,非常方便。 - 后面为了方便管理和拓展,将地图下载相关类放到了mapdown目录,地图基类放到了mapobjbase目录,地图子类放到了mapobj目录中,后期如果拓展新的地图内核,只需要新增个子类放到mapobj目录即可。 |文件|说明| |:------ |:------| |maphead|整个地图组件的公用头文件,这里定义了地图内核枚举值、启用功能类型枚举值、地图控件类型枚举值等。| |maphelper|地图组件辅助类,这里放的一些常用的函数,比如加载地图内核到下拉框,根据不同内核获取网页基准目录,根据不同内核实例化地图类,文本经纬度与坐标互换等。| |maputil|地图组件其他辅助类,主要放的一些坐标转换和计算的函数,比如获取两个经纬度的距离,根据经纬度和距离以及角度求目标经纬度,获取两点之间的角度,获取两个点之间的中间点,给定中心点经纬度和半径求左下角和右上角经纬度坐标,各种坐标系转化等。| |mapdownhelper|瓦片地图下载辅助类,用于生成不同地图内核对应不同的瓦片地址。同时封装了下载图片文件函数。| |mapdownload|瓦片地图下载类,里面负责实例化多个下载线程,可以单张下载和批量下载,可以启动和暂停以及停止下载服务。| |mapdownthread|瓦片地图下载线程,传入经度纬度和缩放级别,自动获取到瓦片地址进行下载和保存图片文件到本地。| |mapobjbase|所有地图实现的基类,通用的函数都在这里直接实现,不同的部分需要子类自己实现。每个地图子类都继承这个基类。这个类负责生成html代码,可以直接传给浏览器控件,也可以直接保存到文件,然后再浏览器控件加载这个网页文件。| |mapobjfunbase|地图基类中拆分出来的一些基础通用函数放在这里,不然基类中的类文件太大不方便管理。这些通用函数整体框架类似,主要是不同的内核对应的一些对象名不一样,比如获取经纬度不同地图内核有不同的属性,lnglat latlng latLng等,严格区分大小写。| |mapobjfunoverlay|地图基类中拆分出来的一些覆盖物相关通用函数放在这里,比如获取覆盖物通用属性(颜色、粗细、样式、透明度等),获取边界点数据集合,添加行政区划,添加监听事件,移除监听事件等。| |mapobjbaidu|百度地图核心类,继承自mapobjbase。| |mapobjbaidugl|百度地图GL版本核心类,继承自mapobjbase。| |mapobjgaode|高德地图核心类,继承自mapobjbase。| |mapobjtian|天地图核心类,继承自mapobjbase。| |mapobjtengxun|腾讯地图核心类,继承自mapobjbase。| |mapgoogle|谷歌地图核心类,继承自mapobjbase。| |mapleaflet|leaflet地图核心类,继承自mapobjbase。| ||| ## 7、功能特点 ### 7.1 地图功能 1. 支持多种地图内核,默认采用天地图,可选百度地图、高德地图、腾讯地图、谷歌地图、通用地图等。 2. 同时支持在线地图和离线地图两种模式,离线地图方便在不联网的场景中使用。 3. 支持各种地图控件的启用,比如地图导航、地图类型、缩略图、比例尺、全景导航、实时路况、绘图工具、结果面板等。 4. 支持多种地图功能的动态启用禁用,比如地图拖曳、键盘操作、滚轮缩放、双击放大、连续缩放、地图测距等。 5. 提供众多js函数接口用于交互,参数极其丰富,能够想到的应用场景需求都有实现。 6. 统一的信号槽机制,地图中的结果统一信号发送出去,收到后根据type类型区分。 7. 支持地图交互,比如鼠标按下获取对应位置的经纬度。单击标注点弹出对应点的信息。 8. 支持添加标注、删除标注、移动标注、清空标注,支持更新标注的图片、尺寸、位置、旋转角度等。 9. 标注点可以指定图标图片和尺寸,支持gif动图,支持指定以图片中心对齐还是底部中心对齐。可以设置旋转角度,带富文本提示信息。 10. 所有覆盖物比如多边形、矩形、圆形、标注点灯,都支持动态绑定单击、双击、拖曳开始、拖曳结束等事件,对应信号发出来,可以根据对应的信号处理逻辑,比如拖曳期间更新折线的坐标点集合。 11. 标注点事件支持单击发信号通知和自己弹框显示信息,弹框信息支持html富文本。 12. 提供地址转坐标和坐标转地址接口,同时支持在线和离线两种方式。 13. 支持各种图形绘制,包括标注点、折线图、多边形、矩形、圆形、弧线等。 14. 可显示悬浮的绘图工具栏,直接在地图上划线、标注点、矩形、圆形等。 15. 支持各种区域搜索,比如矩形区域、圆形区域,可以按照关键字匹配将搜索结果显示在地图中。 16. 可动态添加离线的行政区边界点数据。可以搜索行政区划并获取该区域的边界点数据。数据可以保存到文件以便离线使用。 17. 支持点聚合功能,多个小标注点合并到一个大标注点,防止点密集导致交互不友好。 18. 可以添加海量点,每个点都可以单击获取对应坐标和信息。 19. 所有的覆盖物信息比如标注点、矩形、多边形、折线图等,都可以主动获取对应的信息比如坐标点和路径等。 20. 支持路径规划,支持公交路线、自驾路线、步行路线、骑行路线,不同查询支持不同策略,可选最少时间、最少换乘、不走高架等。 21. 路径规划结果可以显示在地图中,也可以获取到路径点坐标集合。这个数据可以保存到文件,以便发给机器人或者无人机做导航用来轨迹移动。 22. 可以设置不同的地图视图比如街道图、卫星图、混合图。 23. 可以设置不同的样式,比如午夜蓝、青草绿等样式风格。 24. 可以设置地图的旋转角度和倾斜角度,一般需要地图厂家支持以及浏览器支持webgl特性。 25. 提供经纬度坐标纠偏转换功能,比如传入的GPS坐标需要转换到百度地图坐标或者高德地图坐标。各种坐标系转换全部离线函数,支持地球坐标系WGS-84、火星坐标系GCJ-02、百度坐标系BD-09之间的互相转换,涵盖了各种地图的坐标系。 26. 提供动态轨迹点移动功能,按照给定的经纬度坐标集合平滑移动,就算是两个点之间移动也会自动计算生成多个平滑的路径点来移动。 27. 所有函数接口都提供了非常详细的使用示例,满足各个水平区间的人员学习使用。 28. 组件同时支持qwidget和qml,支持编译到安卓系统运行,安卓上也支持离线运行。 ### 7.2 其他功能 1. 地图示例中分别提供了标注点示例、覆盖物示例、路径规划示例、兴趣点搜索示例、其他示例等,非常详细。 2. 提供离线地图下载模块,可以选择不同的地图内核,比如百度地图或者谷歌地图,不同的地图类型,比如下载街道图还是卫星图,不同的地图层级,多线程批量极速下载。 3. 表格行实时显示对应的瓦片下载进度,有下载超时时间,重试次数,每个瓦片下载完成都发送信号通知,参数包括下载用时。 4. 提供省市轮廓图下载模块,自动下载各个地区的轮廓图,保存到脚本文件或者文本文件。 5. 支持手动调整不同区域的轮廓边界,调整后可以主动获取调整后的边界点集合。 6. 提供动态点位示例,手动在地图上选点并添加标注,附带自定义的信息比如速度和时间等。 7. 提供海量点位示例,批量添加标注点、点聚合、海量点。用于测试环境中支持的最大点位性能。 8. 提供动态轨迹示例,在地图上鼠标按下选择起点和终点后,查询路线,获取路径轨迹点,模拟轨迹平滑移动。可以筛选数据将过多的路径点筛选到设定的点数。 9. 提供轨迹回放示例,按照指定的轨迹点列表回放,也可以导入轨迹点数据进行回放。同时支持在街道图、卫星图、混合图中回放轨迹。 10. 提供高级绘图示例,可以动态添加、删除、修改、清空图元,可以对地图上的所有覆盖物开启编辑和关闭编辑,编辑阶段直接拖动位置和大小。可以对地图上的所有图元开启监听,监听拖动等事件,拿到对应信号进行处理。 11. 提供雷达扫描示例,演示在地图组件基础上进行二开,封装了雷达扫描类,扫描的同时有几个标注点移动。 12. 提供航线模拟示例,可以设置移动的间隔和每次移动的步长,可以设置顺时针还是逆时针旋转。一个飞机图标圆形旋转。 13. 提供航线规划示例,可以直接在地图上动态添加标注点,两个点之间直线相连,同时有箭头指示方向和角度,选中点高亮显示。 14. 提供高程海拔示例,演示传入经纬度获取对应点的高程海拔数据,无需联网离线使用,瞬间出结果。 15. 提供省市区域地图示例,采用echart组件,同时支持闪烁点图、迁徙图、区域地图、世界地图、仪表盘等。可以设置标题、提示信息、背景颜色、文字颜色、线条颜色、区域颜色等各种颜色。 16. 省市区域地图示例,内置世界地图、全国地图、省份地图、地区地图,可以精确到县,所有地图全部离线使用。可设置城市的名称、值、经纬度集合。 17. 内置通用浏览器组件,同时支持webkit/webengine/miniblink等内核。提供网页控件示例,演示打开网页和本地网页文件。 18. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。 ## 8、特别说明 1. 受限于浏览器控件对web的支持程度,不同浏览器控件加载对应的地图网页,可能会出现部分特性不支持的情况,比如miniblink内核不支持地图设置旋转角度和倾斜角度,webkit内核不支持webgl的网页。个人推荐使用最新的Qt套件对应的msvc版本,一般会自带最新webengine内核,特性比较新。 2. 不同的地图内核有不同的优缺点,一般国内使用比较多的是百度地图、高德地图、天地图三种,谷歌由于需要翻墙才能使用,导致国内用户数很低。综合使用对比下来看,高德地图在api接口和文档示例方面最丰富的,应用场景也最全;百度地图在周边附近搜索方面最详细;天地图在地图精度和完整性方面最好。 3. 高德地图和谷歌地图,在国内都是采用国家标准的GCJ-02坐标系(也叫火星坐标系),这个标准是在全球标准WGS-84坐标系(也叫地球坐标系,GPS用)基础上加密的。天地图采用的是CGCS2000坐标系(也叫大地坐标系),和WGS-84坐标系(也叫地球坐标系)几乎无偏差,一般GPS设备和谷歌国际地图使用。 百度地图用的是BD-09坐标系,是在国家标准坐标系上又加密的。一般建议用国家标准的GCJ-02坐标系。如果对接的是WGS-84坐标,则建议用天地图。 4. **常规应用场景建议用高德地图,无人机相关场景以及测绘测量行业建议用天地图,对周边生活搜索要求高的场景建议用百度地图,海外地图场景建议用谷歌地图。天地图对浏览器的兼容性最好,而且地图切换流畅度最好。天地图的卫星图最全,精度最高。而且天地图坐标系可以和地球坐标系完美兼容,无偏差。** 5. 高德地图在不同版本之间的兼容性最好,绝大部分接口通用。百度地图对应的webgl版本主要就是将原来的对象BMap改成BMapGL。天地图是旧版本TMap改成T.Map。 6. 总体感觉天地图的API结构和命名方面是借鉴的百度地图的,腾讯地图借鉴的谷歌地图的,极大的雷同,这样也好,方便程序员。 7. 无论是百度地图还是高德地图或者天地图等,都多多少少存在bug,比如不少的情况是,通过new的时候构造函数传参数是可以的,而调用setxxx接口去设置确失败,可能对应接口还没测试好。所以在使用这些类的时候,尽量用构造函数传参数,就算不填实际性的内容,也要传个空数据,避免解析失败而导致后续的功能全无。 8. 百度地图gl版本的路书js文件中,生成了3个marker标注,完全重复多余,不知道是有人故意为之还是。后面发现是github的被人恶意改了,而官方地址 http://api.map.baidu.com/library/LuShu/gl/src/LuShu_min.js 是正确的。 9. 经过大量的模拟测试发现,对覆盖物的删除和清空clearoverlay等,高德地图和谷歌地图会在合适的时机释放内存,而其他地图几乎不会去主动释放,所以如果遇到需要很多覆盖物的场景,建议生成一次后,后面只是去改变该覆盖物的属性比如坐标位置和路径,而不是清空后再次去生成。这样就可以极力避免内存泄漏,这可能也是web的缺陷,没有手动释放机制,说是内部有垃圾自动回收,但是内部很可能判断失败,导致一直无法释放。 10. 腾讯地图的坐标是纬度经度,谷歌地图也是纬度经度,而百度地图、高德地图、天地图都是经度纬度,顺序有区别。 11. 瓦片地图的格式一般的规则是,街道图png、卫星图jpg、叠加层png。 12. 通过地图示例中的指定同一个经纬度坐标来添加标注,对照各种地图内核,可以看到,高德地图、腾讯地图、谷歌地图(中国区)这三个地图位置完全一致,因为都采用了国家标准的GCJ-02坐标系,而百度地图采用的加密的坐标系不一样,天地图采用的CGCS2000坐标系(和WGS-84坐标系几乎一致)。 13. 百度地图、腾讯地图没有提供绘制矩形覆盖物,可以通过绘制多边形覆盖物来实现。 14. 天地图的矩形区域,编辑状态下可以拖动位置,谷歌地图的所有覆盖物都支持拖动位置,其他地图内核目前没有看到如何支持拖动。 15. 天地图的符号标绘,有些是通过长按结束绘制,有些是通过双击结束绘制。 ## 9、制作离线 ### 9.1 天地图 1. 下载地图核心文件 http://api.tianditu.gov.cn/api?v=4.0&tk=您的密钥 ,核心文件必须下载。 2. 下载地图部件文件 http://api.tianditu.gov.cn/v4.0/components.js ,部件文件必须下载。 3. 下载地图样式文件 http://api.tianditu.gov.cn/v4.0/css/tianditu4.0.css 。地图样式文件必须下载。 4. 下载高级绘图文件 http://api.tianditu.gov.cn/v4.0/military.js 。非必须,一般用不上。 5. 下载服务请求文件 http://api.tianditu.gov.cn/v4.0/service.js 。非必须,用于发送网络请求获取逆地址解析、路径规划查询等,既然都离线了,也用不了。 6. 打开js文件和css文件,依次下载对应的图片,可以通过搜索 .png 查找对应的图片。比如代码中是 ../img/map/cluster/cluster0.png,则完整地址是 http://api.tianditu.gov.cn/img/map/cluster/cluster0.png,也就是只需要对../替换成 http://api.tianditu.gov.cn 即可。 7. 打开核心js文件搜索 T.DomainFun(),将下面的路径改成本地的。 ```cpp E: "./", e: "./image", //E: T.Protocol.value + "api.tianditu." + T.Domain, //e: T.Protocol.value + "api.tianditu." + T.Domain + "/v4.0/image", ``` 8. 在路径这个位置下面是各种瓦片地址的在线地址,代码都是 return T.Protocol.value... ,这里可以不用改,直接在最终调用的地方更改更妥。 9. 最关键部分,将核心文件中的加载网络瓦片文件改成本地的,搜索 getTileUrl = function ,对应的函数就是返回的瓦片地址,里面总共定义了三种地图的瓦片,每一种地图都包含一个底图和一个叠加层。 ```cpp var i = new T.TileLayer("", { minZoom: 1, maxZoom: 18 }); i.getTileUrl = function(t) { return "tiles_vec_w/" + t.z + "/" + t.x + "/" + t.y + ".png" //return 0 == T.gq.EW ? T.w.t() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z : T.w.r() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z }; var n = new T.TileLayer("", { minZoom: 1, maxZoom: 18 }); n.getTileUrl = function(t) { return "tiles_cva_w/" + t.z + "/" + t.x + "/" + t.y + ".png" //return 0 == T.gq.EW ? T.w.Y() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z : T.w.T() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z }, TMAP_NORMAL_MAP = new T.MapType([i, n], "TMAP_NORMAL_MAP", { a: 1 }); var e = new T.TileLayer("", { minZoom: 1, maxZoom: 18 }); e.getTileUrl = function(t) { return "tiles_img_w/" + t.z + "/" + t.x + "/" + t.y + ".jpg" //return 0 == T.gq.EW ? T.w.I() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z : T.w.U() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z }; var o = new T.TileLayer("", { minZoom: 1, maxZoom: 18 }); o.getTileUrl = function(t) { return "tiles_cia_w/" + t.z + "/" + t.x + "/" + t.y + ".png" //return 0 == T.gq.EW ? T.w.i() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z : T.w.u() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z }, TMAP_SATELLITE_MAP = new T.MapType([e], "TMAP_SATELLITE_MAP"), TMAP_HYBRID_MAP = new T.MapType([e, o], "TMAP_HYBRID_MAP"); var s = new T.TileLayer("", { minZoom: 1, maxZoom: 18 }); s.getTileUrl = function(t) { return "tiles_ter_w/" + t.z + "/" + t.x + "/" + t.y + ".jpg" //return 0 == T.gq.EW ? T.w.P() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z : T.w.O() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z }; var r = new T.TileLayer("", { minZoom: 1, maxZoom: 18 }); r.getTileUrl = function(t) { return "tiles_cta_w/" + t.z + "/" + t.x + "/" + t.y + ".png" //return 0 == T.gq.EW ? T.w.p() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z : T.w.o() + "TILECOL=" + t.x + "&TILEROW=" + t.y + "&TILEMATRIX=" + t.z }, TMAP_TERRAIN_MAP = new T.MapType([s], "TMAP_TERRAIN_MAP"), TMAP_TERRAIN_HYBRID_MAP = new T.MapType([s, r], "TMAP_TERRAIN_HYBRID_MAP") ``` 10. 核心文件拉到末尾,注释三行代码,这三行用来网络请求获取子js文件和css样式等。不注释可能导致其他的问题。 ```cpp //for (var n = 0; n < T.w.a.length; n++) T.lR.MR("TDT_style" + n, T.w.E + T.w.a[n]); //for (var m = 0; m < T.w.A.length; m++) T.lR.nR("TDT_components" + m, T.w.E + T.w.A[m]); //T.lR.bR(T.w.E + "/qv?tk=" + T.w.TMAP_AUTHKEY + "&t=" + Math.random() + "&callback=T.qv"); ``` 11. 代码中引入js和css文件。 ```cpp ; ``` 12. 天地图官方提供的瓦片每一种类型都分经纬度投影和球面墨卡托投影 http://lbs.tianditu.gov.cn/server/MapService.html ,我们平时一般用的是球面墨卡托投影,主流也是球面墨卡托投影。 ## 10、版本说明 ### V202510 1. 将fromgps单独一个pri,增加了多条轨迹的模拟窗体以及实时估计的窗体。 2. 新增实时轨迹,演示传入实时经纬度坐标,添加,形成轨迹,支持多条轨迹。 3. 实时轨迹新增平滑插值算法,移动的时候平滑移动,如果两个点比较远,不会出现跳跃式前进,而是平滑式前进。 4. 修复天地图对应标注点文字更新位置后不显示的bug。 5. 修复轨迹规划模块,鼠标缩放地图后会自动启动移动的bug。 ### V202503 1. 新增mapdraw目录,专门存放封装的各种绘制类,方便传入参数直接使用,比如雷达模拟,飞行轨迹,轨迹回放等。 2. 新增getOverlayInfo2函数,传入类型和覆盖物对象,获取对应的信息,比如经纬度坐标,圆形还有半径,矩形则四个点等信息,方便单击或者拖曳等地方获取,发出信号通知进行处理。 3. 新增统一的事件名转换函数,比如contextmenu纠正为rightclick,drag纠正为dragging,方便统一设置和接收处理。 4. 精益求精,大幅度降低高程海拔数据占用的内存,从1.5G占用降低到0.15G,核心就是将之前的字符串存储的值改成qint16数据类型。 5. 所有覆盖物信息获取返回值,增加flag唯一标识,也就是该覆盖物添加的时候指定的唯一名字,方便获取的时候区分。 6. 天地图离线js文件,修改默认最大级别18级到21级,以便加载更高精度的离线地图,比如谷歌瓦片地图可以通用天地图。 7. 地图示例增加配置参数MapDemoZoomMax,用来控制地图缩放最大级别,用来测试加载更高精度的地图。天地图默认在线瓦片只提供到18级别。 ### V202502 1. 新增地图事件参数,默认地图会添加单击事件,如果还要添加其他事件,需要调用setMapEvent函数设置,参数是对应枚举值,支持多个组合,比如mapObj->setMapEvent(MapEvent_Click | MapEvent_RightClick)表示同时添加地图单击和右键事件。 2. 新增了地图鼠标移动事件,方便实时获取高程海拔值。 3. 新增高程海拔示例,可以直接在地图中移动,实时显示对应的高程海拔值。 ### V202412 1. 将标注点单击函数重写,改成支持单击双击右键多种事件,可以同时添加多个时间,有一些应用场景需要单击执行一个动作,双击执行其他动作。 2. 在所有覆盖物的单击事件监听基础上,增加了双击和右键的事件。 3. 所有覆盖物单击双击右键事件通知信号参数增加事件类型,之前只有一种单击,现在新增了双击和右键,需要区分。click、dblclick、rightclick。 4. 统一将右键传入传出参数改成了rightclick,遇到天地图,如果传入了rightclick,函数内部会自动转成contextmenu事件,传出自动转成rightclick事件,这样方便外部统一处理。 ### V202410 1. 地图类型下拉框动态加载,普通地图有街道图、卫星图、混合图,天地图有6种,多了地形图和地形混合图。 2. 天地图删除覆盖物函数增加删除测距留下的覆盖物,参数标识定义为 distancetool。 3. 天地图添加高级覆盖物函数addCover增加唯一标识参数,方便删除。 4. 天地图删除覆盖物函数deleteOverlay增加唯一标识参数,用于指定标识删除覆盖物。 5. 绘图示例增加添加扇形和删除扇形示例。 ### V202409 1. 新增开启编辑和停止编辑函数,用于对地图上的所有覆盖物编辑,比如标注点可以拖动位置,多边形拖拉控制点,圆形调整半径等。 2. 地图示例新增多文档示例,用于演示在mdi窗体中加载多个地图示例。 3. 地图示例新增绘图示例,用于演示如何在地图上动态添加标注点、折线、多边形、矩形、圆形覆盖物,可以开启和结束编辑,获取覆盖物信息。 4. 统一获取覆盖物信息对应矩形区域四个点坐标依次为左上/右上/右下/左下。 5. 绘图工具栏增加对应函数切换到哪一种图形绘制,比如主动设置切换到矩形框绘制。 6. 高德地图增加对覆盖物的开启编辑和停止编辑处理,包括行政区划的编辑状态切换,高德地图和其他地图比较特殊,用的是单独的编辑对象来实现编辑。 7. 废了巨大精力研究,离线地图增加天地图的支持,可以加载6种瓦片文件。 8. 离线地图下载类型增加函数判断不同地图内核有不同的瓦片文件类型。 9. 谷歌地图下载增加中文英文两个版本选择,从下拉框选择即可,现在分街道图-中文、混合图-中文、街道图-英文、混合图-英文、卫星图共5种。越来越多的用户需要在非中国区使用。 10. 地图示例中的绘图示例,增加天地图的符号标绘演示,比如绘制折线箭头、直线箭头、扇形区域等。 11. 增加了添加自定义图层函数,可以通过指定XYZ地址叠加图层。 12. 天地图行政区划增加对飞地的判断,飞地按照新的区域绘制。 ### V202408 1. 增加默认地图类型参数,比如默认加载后就是卫星图。 2. 重写轨迹回放相关函数,采用路书策略,而不是之前的跳跃移动,现在是平滑移动,哪怕路径是两个经纬度坐标点,也会自动生成一堆坐标点路径,平滑移动。 3. 路书增加伴随线条,移动轨迹到哪里就绘制到哪里。 4. 地址和坐标互转,增加唯一标识,方便多个转换给定标识取结果。 5. 修复天地图鼠标右键识别错误的bug。 6. 添加折线增加参数arrow,表示是否开启方向箭头,对应参数值是颜色值,默认为空则没有箭头。 7. 测试发现天地图中写死了路径,导致设置了baseurl后,不能正常加载瓦片文件,所以在地图基类中专门增加了处理,以便兼容天地图。 8. 新增单个折线添加数据点函数。 9. 新增地图控件配置参数。 10. 百度地图默认地图可点弹出信息改成默认关闭,比如单击一些火车站会弹出一个大面板。 11. 解决百度地图gl版本添加折线自身缺陷的bug,在添加折线的时候必须指定一个坐标点,但是在后续添加折线数据的时候,该点需要移除。 12. 完善了各种坐标系的转换离线函数,比如新增了火星坐标系转地球坐标系、百度坐标系转地球坐标系等,现在满足了三种坐标之间的互相转换,共六种情况。 13. 天地图模块增加删除CarTrack对象,官方提供的clear方法无效,查看源码得知,要用 \_Remove方法。 14. 海量点测试增加了圆形覆盖物的类型。 15. 去掉浏览器模块中的IE浏览器的支持,这个太古老了。 16. 将所有地图内核的全局对象的初始化移动到函数中处理,在需要的时候先调用 initxxx 函数初始化。 17. 修复在Qt6中跨域请求的问题,腾讯地图的路径规划等接口都是通过网络请求去获取,涉及到跨域的问题。 18. 更新新的谷歌离线地图下载地址,无需翻墙。 19. 瓦片下载地址全部换成字符串更新替换格式,更精准。 20. 瓦片地图下载的文件拓展名由请求后获取到的真实的格式决定,之前是固定的jpg,不准确,很多时候是png。 21. 修改百度地图离线js文件,增加每种图层的拓展名定义,因为可能拓展名不一样,现在是按照实际的图片拓展名来保存文件。 22. 修复地图示例中地图模式下拉框更改后不会保存配置文件的bug,修复地图示例中地图缩放复选框勾选写死的bug。 23. 地图下载模块增加地图级别的处理,不同地图模块支持的地图级别范围不一样,谷歌地图可以到20级,百度大城市到19级其他区域到18级,其他地图基本上都是到18级别。自动根据不同地图内核隐藏不需要的级别。 24. 离线地图下载新增腾讯地图下载,支持街道图、卫星图、路网图。瓦片地址找了很久,计算规则和其他的地图不一样。 25. 谷歌离线地图下载支持中英双语参数设置,比如下载俄罗斯地区默认是中文加俄语,也可以设置成英文加俄语。 26. 修复天地图获取多边形区域getLngLats后数多维数组的bug,要取出第一个后再遍历。 27. 将谷歌离线地图中的网络请求相关的全部删除,已经全部离线使用了,不需要去请求。 ### V202407 1. 新增公共函数类,全部静态方法,将多种地图通用的方法放其中,比如获取边界点数据集合getBoundary函数,所有地图内核写法都一样。那为什么不放在base基类中?因为base基类已经有不少的函数,所有公共的通用的处理函数,全部放单独的静态类文件,方便通用管理。 2. 修改浏览器内核代码,增加设置cookie目录,避免有时候默认目录没有权限导致加载失败。 3. 逐步增加了对高德地图、天地图的支持。 4. 剥离echart类,单独一个组件,方便后期拓展其他图表示例。地图就单独的map组件。 5. 统一目录,之前全部挤在config目录下很乱,现在拆分成echarts目录,mapbaidu目录专门存放离线的百度地图相关文件,mapboundary目录存放行政区域边界点文件,mapimage目录存放地图相关图片文件,cookie目录存放浏览器控件的缓存文件和webengine浏览器控件交互用的qwebchannel.js文件。 6. 新增了搜索示例,有常规关键字搜索,多边形区域搜索,周边搜索等,支持多个关键字。搜索结果以信号通知返回。 7. 搞定百度地图公交路径规划返回加过只有步行路径的bug,原来公交路线要用其他函数去获取对应的路径。 8. 重写标注点相关函数,生成的标准点对象用数组存起来,后期只需要从这个数组中遍历进行修改即可,而不需要去地图中获取,比如获取后移动位置以及设置旋转角度。 9. 新增标注点单击事件处理函数,可以动态更改单击弹出的信息以及是自己弹窗还是交给qt这边弹窗处理。 10. 简化设置是否保存文件以及保存的网页文件名称,自动根据内核设置对应的基准目录。 11. 坐标字符串转坐标数组函数getPoints增加对参数的判断,如果已经是数组则直接返回不用转换。这样大量采用此函数绘制图形的地方也支持直接传入数组。 12. 新增qml示例,采用qml中的webview组件加载,为了统一移动平台,简化交互机制,采用websocket通信接收应答数据。 13. 增加loadSuccess信号,只有加载成功才发送,而且是短时间内加载后最后一次信号,避免了频繁的信号。 14. 新增清空所有覆盖物函数clearOverlay,在清空覆盖物的同时,把一些临时对象数组也清空,比如标准对象数组。 15. 修复百度地图路径规划枚举值定义不准确的bug,比如优先高速BMAP_DRIVING_POLICY_FIRST_HIGHWAYS对应值是4,而在文档中对应顺序是1,再比如最少换乘BMAP_TRANSIT_POLICY_LEAST_TRANSFER对应值是1,而在文档中对应顺序是2。现在全部改成采用真实值而不是下拉框的索引。 16. 将qwebchannel.js文件放到webview组件的资源文件中,省的拷贝,保证编译直接就能运行。同时验证了webkit内核和webengine内核都支持资源文件的图标文件,对应写法前缀qrc:/fly.png。 17. 添加标注函数增加center参数,表示当前标注图标是在中心点还是底部居中,很多气泡箭头的图标都要以底部居中更好看,一般地图内核默认是中间居中。 ### V202406 1. 重写整个地图内核组件,改成了基类子类的模式,为后面拓展其他地图内核做准备。 2. 统一函数接口,比如添加标注、添加折线图等功能,全部换成js异步加载,而不是写在网页内容中。 3. 新增了多个js函数接口,比如加载离线文件中的行政区划地图点集合。 4. 重写整个地图示例,更加精细化模块化,非常易于阅读学习。 5. 修复动态轨迹点内存泄漏的bug,改成了复用折线图对象,通过更新点集合数据来实现。 6. 地图下载增加线程数量设置。 7. 新增了地图测距功能,支持在线离线。 8. 将之前的简易示例合并到地图示例中,通过右上角按钮最简示例打开。 9. 将之前的轨迹回放示例合并到动态估计示例中,通过右下角按钮轨迹回放打开。 10. 路线查询后的轨迹点支持保存到文件,同时在轨迹回放模块中加载。 11. ### V202401 1. 2021-12-03 统一定义多个区域坐标点集合规则 121.42721122307,31.17876546443;121.42719595187,31.178763147276|121.42721122307,31.17876546443;121.42719595187,31.178763147276,彻底解决不同浏览器内核之间坐标数组不兼容的烦恼。 2. 2022-03-20 离线地图下载模块增加自动下载失败的图片,防止一次下载失败就跳过导致没有下载成功的缺陷。将下载成功率从95%提高到100%。 3. 2022-04-24 修复离线地图不支持混合图的问题,需要先在离线地图下载器下载好路网图,放到tiles_self目录即可。 4. 离线地图下载中的网络图相当于路况图的大图,可以根据需要选择路况图还是网络图。 ================================================ FILE: snap_mapwidget/readme.md ================================================ ## 1、编译说明 1. 编译完成以后记得将源码下的file目录下(切记是file目录下而不是file目录)的所有文件复制到可执行文件同一目录下即可。 2. 可执行文件在源码同级目录下的bin目录,和core_mapwidget目录同级,这个目录在编译后自动生成。 3. 本组件支持各种地图源,内置了不同厂家对应下载瓦片的秘钥,建议换成自己的秘钥。 4. 在开启了缓存的时候,首次加载本地缓存文件会比较慢,再次加载会非常快,这是因为系统层缓存了文件,再次访问该文件的时候会非常快,这个属于正常现象。linux系统不存在这个问题,响应非常迅速。 ## 2、使用说明 ### 2.1 代码使用 1. 将core_mapwidget目录拷贝到你的项目目录,并在pro中填写引入代码加入到你的项目中。$$PWD/../表示上级目录。 ```cpp include ($$PWD/../core_mapwidget/core_mapwidget.pri) ``` 2. 引入头文件,编写代码。 ```cpp #include "mapwidget.h" //创建地图控件 MapWidget w; //设置瓦片源头 w.setTileSource(TileSource_Tian); //设置瓦片类型 w.setTileType(TileType_Satellite); //加载地图 w.load(); ``` 3. 更详细的接口参考mapwidget.h头文件。 ### 2.2 通用配置 在每个tab页面的右上角,可以选择不同地图源、地图的类型、在线离线、是否开启缓存等参数,这些都是完全一样的内容,作为通用的配置参数,在这里做下说明。 1. 地图内核,选择不同厂家的地图源。 2. 地图类型,可以选择街道图、卫星图、混合图几种。 3. 地图模式,是在线地图还是离线地图,如果是在线地图,必须联网获取瓦片数据,离线地图自动从本地获取瓦片数据。 4. 开启缓存,这个只对在线地图模式有用,也就是下载好的瓦片地图文件,是否需要同步存储到本地,这样下次直接优先从本地加载。 ### 2.3 基础示例 ![](snap/mapwidget10.jpg) 1. 本示例演示地图组件的一些基础用法。 2. 在功能列表框中,单击选中再次单击取消选中对应功能。 - 比例尺,地图左下角显示比例尺图样,在不同缩放下会显示不同的比例尺。 - 十字线,地图中心位置显示一个十字线,方便标识哪里是地图中心。 - 缩放控件,一个由放大缩小两个按钮和一个滑动条组成的控件,单击按钮可以放大或者缩小地图,拉动滑块到对应缩放级别会自动切换到对应缩放级别的地图。这个缩放控件可以摆放到左上角、右上角、左下角、右下角四个位置。这里每次勾选的时候都会设置到一个位置,可以切换复选框看到对应效果。 - 地图拖曳,开启后可以鼠标按住地图拖动,有些场景比如绘制图形的时候,可以禁用此功能,以便按下的时候开始绘图。 - 键盘操作,开启后可以和实体键盘的上下左右按键同步处理,比如按下键盘左箭头,则地图往右边移动,不断看到左侧地图区域。也支持+-按键放大和缩小地图。 - 右键移动,默认不支持右键按住地图拖曳,开启后可以,在某些场景下,可能左键按下做特殊处理,希望右键来拖曳移动地图。 - 滚轮缩放,开启后可以通过鼠标滚轮实现地图缩放。 - 双击放大,开启后双击地图自动放大。 - 鼠标追踪,开启后,不需要按住鼠标,自动识别鼠标移动,并发出对应移动处的经纬度坐标。 3. 切换风格样式下拉框,会自动应用对应的样式,要存在透明叠加图层的时候才有效果,比如天地图的街道图和混合图。 3. 单击获取中心,将当前地图的中心点坐标显示到经纬度文本框中。 3. 单击设置中心,将经纬度文本框中的数据作为中心点坐标设置到地图,可以在左侧地图控件上鼠标按下,自动填入对应的经纬度坐标。 3. 单击获取区域,获取当前地图控件显示区域的范围,弹框显示结果。 3. 单击设置区域,代码中写死的设置中国的完整区域,演示如何传入一个qrectf值设置显示对应的区域。 3. 单击获取缩放,将当前地图的缩放级别显示在微调框中。 3. 单击设置缩放,将微调框中的缩放值,设置到地图中。 3. 单击地图放大,和缩放控件的 + 按钮执行的动作一样,放大地图。 3. 单击地图缩小,和缩放控件的 - 按钮执行的动作一样,缩小地图。 ### 2.4 超级标注 ![](snap/mapwidget20.jpg) 1. 本示例演示标注点这个图形的一些参数设置,这些参数的更新都是立即应用,所见即所得。 2. 单击生成标注按钮,以右侧设置好的标注点的参数,生成一个标注点在地图上。 3. 单击删除标注按钮,自动清空地图上的标注点。 4. 在地图上单击,会自动显示经纬度值到对应文本框中。如果已经存在标注点,同时会移动标注点到对应的新的位置,也就是鼠标按下处的位置。 5. 图标类型,可以选择水滴图标,飞机图标,动态图标,就是更新标注点的图片,支持gif动图。 6. 图标位置,可以选择左边、右边、中间、左上、右上、顶部、左下、右下、底部共9个位置,表示标注点在设置的经纬度坐标的哪个位置,一般水滴图标会选择显示在坐标的顶部,飞机图标显示在中间。 7. 旋转角度,标注点根据这个角度旋转显示,支持动图旋转,一般飞行的时候需要调整角度进行显示,组件中内置了函数计算两个经纬度坐标的角度值。 8. 文本内容,每个标注点都可以显示自己附带的一些文本内容,支持换行多行文本,下拉框可以选择不同的文本样式,比如有垂直文字,就是通过换行实现的。 9. 文本颜色,对应文本文字的颜色,从颜色下拉框中选择。 10. 文字大小,对应文本文字的字体大小,也就是字号,值越大文字越大。 11. 文字对齐,这个文字相对于图标的位置,和图标位置一样,都有9种位置可以选择,默认建议显示在右上角。 12. 文字背景,文字区域的背景颜色,如果没有背景颜色,这个文字很容易和地图的颜色混在一起不好看,一般建议设置文本背景颜色用于区分。 13. 背景透明,背景颜色透明度值,纯色会遮住了下面的地图图片,一般会设置半透明效果,这样既可以看到文字信息,又能看到背后的地图。 14. 边框颜色,文字背景对应的边框的颜色,还可以设置边框的粗细,代码中支持,这里没有放出选择。 15. 边框粗细,参数支持的,目前没有放到界面上,函数接口里面都有。 ### 2.5 图形绘制 ![](snap/mapwidget30.jpg) ![](snap/mapwidget31.jpg) 1. 本示例演示各种图形绘制,也就是覆盖物。 2. 每一种图形可以添加和更新,单击添加按钮添加对应的图形,每个图形都有唯一的标识,理论上这个标识要求唯一,实际上不唯一也是可以的,代码层做了兼容,如果不唯一的话,在更新或者删除指定标识图形的时候,可能会对所有符合条件的进行处理。 3. 添加好的图形,鼠标按下的时候,能够识别到按下了哪个图形,弹框提示, 4. 单击更新按钮,需要先从标识下拉框选择要更新图形的唯一标识,选择后再单击。 5. 删除单个,隐藏图形、设置顺序这三个功能,也都是需要先选择图形标识。 6. 删除矩形,删除圆形,有时候需要统一删除一种类型的图形,比如删除所有圆形,其他保留。 7. 隐藏图层,相当于隐藏所有图形,其实是对整个图形图层进行了隐藏,也就根本不会执行该图层上的图形的绘制。 8. 设置顺序,默认图形的叠加绘制顺序按照添加的顺序来,越早绘制的越在底部显示,有时候需要将某个图形单独移到最前面显示。可以设置每个图形的叠加顺序索引,值越大越显示到前面。 9. 保存图片,这里演示瓦片拼接功能,从下拉框选择对应要保存的图片,单击保存,图片保存在mapsnap目录下。 - 可视区域,将当前地图组件可见部分截取出来保存。 - 缓存区域,将整个缓存区域的地图图片保存,默认是可视区域的2倍大小。 - 裁剪区域,如果选择了行政区划,则以行政区划的轮廓作为裁剪区域,裁剪后的图片保存。 - 北京市区,演示直接指定某个区域范围进行裁剪,这个矩形的方式,就是相当于指定一个经纬度范围,做瓦片拼接并保存到一张图片上。 10. 行政区划,一些省市区县的轮廓图,自动从mapboundary目录加载。切换下拉框自动显示对应的轮廓图。 ### 2.6 动态绘图 ![](snap/mapwidget40.jpg) ![](snap/mapwidget41.jpg) 1. 本示例演示动态绘图,在地图上按下进行各种图形的绘制,支持坐标点、文本、标注点、折线、多边形、矩形、圆形等。 2. **如何结束绘制:选中取消单选框,或者鼠标右键。** 3. 获取图形,获取地图上图形的具体信息,比如标注点的经纬度坐标,矩形的左上角右下角坐标等。 4. 清空图形,清空地图上的所有图形。 5. 开启编辑,对地图上所有图形开启编辑功能,坐标点、文本、标注点可以拖动调整位置,折线、多边形可以调整每个点的位置,也支持整体拖曳,矩形可以调整四个角拉伸变大变小,圆形可以拉动边缘点调整半径。只要鼠标在对应图形区域内,除了边缘点,其他都是可以整体拖曳。 6. 关闭编辑,开启编辑后会在相关的坐标点显示一个小矩形,比如折线多边形的每个点,矩形的四个角,圆形的中心点和边缘点。关闭编辑后,不显示这些小矩形,同时禁止拖曳和调整位置。 7. 开启监听,默认地图不对图层中的图形监听对应的事件,比如鼠标按下松开移动等,只有开启后才会响应,有时候二开需要开启拿到对应事件后进行相应的处理。 8. 关闭监听,关闭后不再监听图形的各种事件。 9. 单个处理,函数接口支持对单个图形开启和关闭编辑以及监听,这里不做演示。 10. 批量生成,先从单选框选择要批量生成的图形。然后从数量下拉框选择要批量生成的数量,一键批量生成对应的图形。可以用这个来测试地图组件的性能,比如生成10000个标注点用时多久,占用CPU和内存多少。这块相比于web的地图,性能百倍提升。 ### 2.7 轨迹移动 ![](snap/mapwidget50.jpg) ![](snap/mapwidget51.jpg) 1. 本示例演示实时轨迹和轨迹回放以及批量轨迹显示等功能。 2. 移动速度,每次移动的距离,单位米。 3. 移动间隔,多久触发一次移动,单位毫秒。 4. 轨迹模式,选择实时轨迹后,可以在地图上鼠标右键按下点,自动添加对应的点到轨迹点集合中,飞机会自动按照你设定的轨迹飞行。选择两点轨迹,演示给定两个经纬度坐标,自动生成平滑坐标点移动,用来对比跳跃移动。其他两个轨迹回放1和2是直接从轨迹点集合文件中读取对应的数据进行轨迹回放。 5. 轨迹平滑移动,默认勾选,会自动生成平滑坐标点移动。 6. 地图跟随移动,也就是最新的点作为中心点,移动的物体一直在地图的中心。 7. 显示数据轨迹,是否显示历史数据的轨迹,可以动态勾选看效果。 8. 显示移动轨迹,是否显示正在移动的轨迹,可以动态勾选看效果。 9. 可以单击按钮执行开始移动、停止移动、暂停移动、继续移动。 10. 单击模拟多条轨迹回放,自动生成两条不同颜色的轨迹,自动回放,可以自行增加多条。 ### 2.8 航线规划 ![](snap/mapwidget60.jpg) 1. 本示例演示如何在地图上动态选点添加航迹线路,核心就是markerline类的使用。默认从 data/points_marker.txt文件读取经纬度坐标,有更新变动也会自动保存到该文件。可以做成多个航迹规划文件,每个从自己的文件中读取。 2. 开启标注,开启标注后,可以在地图上单击增加点,也可以按住标注点拖动移动位置,标注点都有序号显示,按下标注点后会突出颜色显示,也就是更新了对应标注点图片。在添加和拖动后,会自动更新最新的经纬度信息到表格中。 3. 调整视野,单击后,自动调整地图到合理的区域范围以及缩放级别,保证满屏显示完所有标注点。 4. 删除标注,在地图上按下选中标注点,或者从表格中选中点,单击删除标注,将该坐标从集合中删除。 5. 清空标注,将所有标注点全部删除。 6. 开始回放,单击开始回放,会自动生成一个飞机模拟飞行,按照设置的轨迹,验证是否正确。 7. 暂停回放,在回放过程中,可以单击暂停回放,继续回放。 ## 3、功能特点 1. 支持各种地图源,包括天地图、高德地图、腾讯地图、谷歌地图、微软地图等。 2. 标准WGS-84地球坐标系,采用默卡托投影,可以拓展其他坐标系和投影规则。 3. 支持在线和离线两种场景需求,可以自定义在线瓦片地址格式和离线瓦片地址格式。 4. 多线程下载和加载瓦片图片文件,多线程绘制,自动缓存瓦片文件。 5. 在线模式下,可以开启是否缓存文件,指定缓存路径,将下载的瓦片文件存放到本地,默认优先从缓存文件查找,如果存在缓存文件则加载缓存文件,不存在则联网下载。 6. 可以拖动地图,鼠标滚轮放大和缩小地图,以鼠标所在位置作为缩放中心点,提供缩放控件手动单击进行操作。 7. 多图层机制,支持多个瓦片叠加图层和图形绘制图层,全部采用双缓冲技术,所有的图形和瓦片全部绘制到一个图片文件上,最终再将图片文件绘制到地图控件。不可见区域的图层包括覆盖物不会触发绘制,降低CPU占用。 8. 预加载机制,默认绘制的图层大小以当前区域往四周放大两倍,这样在鼠标拖动和缩放的时候,不会看到明显的加载过程,体验更佳。 9. 内置了多种图形覆盖物,包括坐标点、文本、标注点、折线、多边形、矩形、圆形等,可以设置边框颜色粗细、填充颜色和透明度等参数。 10. 标注点支持旋转角度和提示文本,其中提示文本可以设置在标注点的相对位置,标注点图片支持gif动图,可以动态切换静态图和动图。 11. 标注点和提示文本可以设置相对位置,位置包括左侧、右侧、上侧、下侧、中间、左上角、右上角、左下角、右下角。标注点默认按照底部居中对齐,一般圆形图标可以设置中心点对齐。 12. 标注点提示文本可设置背景颜色,透明度、颜色边框和粗细,支持换行和多行文字。 13. 所有的图形可以动态更新前景色、颜色粗细、背景颜色、颜色透明度等。 14. 支持删除单个图形、删除一种类型的图形、删除所有图形、隐藏单个或者所有图形等。 15. 支持动态绘制各种图形,开启后直接在地图上鼠标按下绘制,鼠标右键结束绘制,非常方便快捷。 16. 在对应图形区域鼠标按下,发出图形单击信号,精准识别单击区域,比如折线以鼠标在折线条上作为判断依据,多边形区域以鼠标在整个多边形区域内为准,而不是以矩形区域,包括圆形也是以圆形内部为准。 17. 可以动态启动禁用比例尺、十字线、缩放控件、地图拖曳、键盘操作、滚轮缩放、双击放大、鼠标追踪等特性。 18. 可以任意指定经纬度区域进行瓦片拼接保存成图片文件,也可以直接对整个可视区域或者缓存区域的地图图片文件保存。支持任意多边形轮廓保存成图片,比如某个行政区的瓦片保存。 19. 图形可以动态设置zindex层叠顺序,值越大,越显示在前面,内部维护着一个zindex表,默认按照添加的先后顺序增加,后面添加的显示在前面,主动设置后,按照设置的zindex来绘制。 20. 支持将QWidget对象作为覆盖物添加到地图控件中,跟随地图移动位置,极大提高灵活性,比如可以将自定义控件直接作为地图控件的子对象加入进去。 21. 内置MarkerMove轨迹移动类,支持历史轨迹数据回放和实时轨迹移动,可设置图标、轨迹线的颜色和粗细、移动速度、移动间隔、平滑移动等,支持多条轨迹线条同时移动。 22. 内置MarkerLine航迹规划类,支持动态添加航迹点,显示对应箭头,可以动态拖曳调整航迹点的位置,选中点高亮显示。 23. 大量使用按需绘制机制,包括内部提供合理的默认值来触发绘制,也可以手动传入参数指定是否需要立即绘制,比如删除了某个覆盖物,有些频繁的操作可以不指定立即绘制,等操作完成后再统一一起绘制,效率更高。 24. 默认开启缓存瓦片机制,所有加载过的瓦片文件都存储在内存中,下次再次绘制直接从内存取出来绘制,既不需要从联网获取,也不需要从缓存文件获取,直接内存取出来绘制,响应迅速效率最高体验最佳。 25. 支持批量添加覆盖物,比如几万个标注点和圆形,都是瞬间完成绘制,相比web网页的方式,性能提升百倍以上。 26. 支持街道图、卫星图、混合图、路网图等各种图层,可以任意叠加N个图层,甚至杂交不同地图厂家的瓦片文件。 27. 纯QWidget绘制,非qml也非web,不依赖qml或者浏览器控件,支持极低性能的嵌入式环境。 28. 原创轻量级,5000行代码,架构漂亮,注释详细,拓展方便,容易学习,适合各种初学者和进阶者,方便二次开发。 29. 支持任意Qt版本、任意系统、任意编译器,包括嵌入式linux和各种国产电脑环境。古法编程,不含任何AI代码,品质保证。 ## 4、其他说明 ### 4.1 加载流程 1. 第一步,先从内存缓存的pixmap队列中查找对应url地址对应的图片缓存,存在则取出来显示,速度最快。 2. 第二步,内存缓存没有,则判断是否开启了缓存目录,开启了,则从缓存目录找对应url地址存储的文件,找到了则加载对应文件缓存显示,速度一般,但是比从网络地址下载要快很多。 3. 第三步,内存缓存和文件缓存都没有,则直接从网络下载,下载后再显示,速度最慢,但是首次加载一般都需要这个步骤。 4. 第四步,如果是离线地图,则直接取设置的离线路径的瓦片文件,不经过内存缓存。 ================================================ FILE: snap_video_camera/readme.md ================================================ ## 1. 前言说明 1. 如果采用内核qcamera或者v4l2则不需要依赖任何第三方库。 2. 如果采用内核ffmpeg在编译好之后记得将动态库文件比如dll_ffmpeg4下面的所有文件复制到可执行文件同一目录。 3. 编译后生成的可执行文件在当前项目源码下的bin目录中。 4. 动态库区分32和64位,对应动态库目录后面有_64字样表示是64位的库。 5. 动态库下载链接: https://pan.baidu.com/s/13LDRu6mXC6gaADtrGprNVA 提取码: ujm7 6. 项目作品大全: https://qtchina.blog.csdn.net/article/details/97565652 ## 2. 其他说明 1. 如果选择 QCamera 方案的话建议用Qt6,有巨大的改进和提升,由Qt公司CTO亲自操刀编写。 2. 通用性最强可以选择 ffmpeg 方案,比如windows、linux、嵌入式linux等系统。 3. 嵌入式linux系统或者linux系统推荐用 v4l2 方案,无需任何依赖,纯v4l2框架代码实现。 4. 嵌入式linux系统如果要求流畅度推荐用ffmpeg方案,因为有缓存机制,ffmpeg方案处理的最好。 5. 采用ffmpeg方案如果官网没有提供对应的二进制包,需要自行编译,编译很简单,不要怕,就打几行命令就ok。 ## 3. 功能特点 1. 同时支持 qcamera、ffmpeg、v4l2 三种内核解析本地摄像头。 2. 提供函数 findCamera 自动搜索环境中的所有本地摄像头设备,搜索结果信号发出。 3. 支持自动搜索和指定设备两种模式,自动搜索模式下会将搜索到的第一个设备作为当前设备打开。 4. 支持同时打开多路设备,亲测4路,受限于具体的环境比如带宽。 5. 支持自动重连,默认开启,失败后会自动重新搜索和尝试打开。 6. ffmpeg方案、v4l2方案都支持回调模式(采集后转成QImage绘制)和句柄模式(采集后YUV数据GPU绘制,性能高)。 7. 视频显示位置自动调整算法,当视频分辨率超过显示控件大小则等比例缩放居中显示,不超过则原尺寸居中显示,还可设置拉伸填充显示。(自动调整、等比例缩放、拉伸填充)。 8. 可选不同的分辨率来打开摄像头,支持 160x120、320x240、640x480、800x600、1280x720、1280x960、1920x1080 等。 9. 可选不同的帧率来打开摄像头,支持 0(采用默认值)、5、、10、15、20、25、30 等。 10. 支持抓拍截图,传入文件名则自动保存截图文件,不传入则将图片数据QImage信号发出。 11. 提供函数接口 开始播放play、停止播放stop、暂停播放pause、继续播放next。 12. 支持动态热插拔加载,包括自动读取所有设备名称到下拉框。 13. 支持录像文件存储,提供开始录像recordStart、暂停录像recordPause、停止录像recordStop 等函数。 14. 提供二维码示例,自动采集画面识别二维码,支持自动将识别到的二维码重新生成大图。 15. 二维码识别支持设置热点区域,对该区域内的图片进行裁切并识别,在大分辨率图像采集的时候非常有用,提升速度和效率。 16. 支持选择图片文件解析二维码,手动输入文本内容生成二维码。 17. 提供图片传输示例,自动将打开的摄像头视频实时传输出去,服务器端接收后解析显示。此方案可以作为将本地的摄像头实时画面远程传输,比如嵌入式板子上的摄像头画面传输到PC端显示。 18. 支持等比例拉伸填充显示,画面宽高小于显示控件的宽高则以原视频大小为准,大于则按照显示控件的尺寸等比例缩放居中。 19. 视频控件悬浮条自带开始和停止录像切换、声音静音切换、抓拍截图、关闭视频等功能。 20. 音频组件支持声音波形值数据解析,可以根据该值绘制波形曲线和柱状声音条,默认提供了声音振幅信号。 21. 代码框架和结构优化到极致,性能彪悍,持续迭代更新升级。 22. 源码支持Qt4、Qt5、Qt6,兼容所有版本。 ## 4. 视频控件 1. 可动态添加任意多个osd标签信息,标签信息包括名字、是否可见、字号大小、文本文字、文本颜色、背景颜色、标签图片、标签坐标、标签格式(文本、日期、时间、日期时间、图片)、标签位置(左上角、左下角、右上角、右下角、居中、自定义坐标)。 2. 可动态添加任意多个图形信息,比如人工智能算法解析后的图形区域信息直接发给视频控件即可。图形信息支持任意形状,直接绘制在原始图片上,采用绝对坐标。 3. 图形信息包括名字、边框大小、边框颜色、背景颜色、矩形区域、路径集合、点坐标集合等。 4. 每个图形信息都可指定三种区域中的一种或者多种,指定了的都会绘制。 5. 内置悬浮条控件,悬浮条位置支持顶部、底部、左侧、右侧。 6. 悬浮条控件参数包括边距、间距、背景透明度、背景颜色、文本颜色、按下颜色、位置、按钮图标代码集合、按钮名称标识集合、按钮提示信息集合。 7. 悬浮条控件一排工具按钮可自定义,通过结构体参数设置,图标可选图形字体还是自定义图片。 8. 悬浮条按钮内部实现了录像切换、抓拍截图、静音切换、关闭视频等功能,也可以自行在源码中增加自己对应的功能。 9. 悬浮条按钮对应实现了功能的按钮,有对应图标切换处理,比如录像按钮按下后会切换到正在录像中的图标,声音按钮切换后变成静音图标,再次切换还原。 10. 悬浮条按钮单击后都用名称唯一标识作为信号发出,可以自行关联响应处理。 11. 悬浮条空白区域可以显示提示信息,默认显示当前视频分辨率大小,可以增加帧率、码流大小等信息。 12. 视频控件参数包括边框大小、边框颜色、焦点颜色、背景颜色(默认透明)、文字颜色(默认全局文字颜色)、填充颜色(视频外的空白处填充黑色)、背景文字、背景图片(如果设置了图片优先取图片)、是否拷贝图片、缩放显示模式(自动调整、等比缩放、拉伸填充)、视频显示模式(句柄、绘制、GPU)、启用悬浮条、悬浮条尺寸(横向为高度、纵向为宽度)、悬浮条位置(顶部、底部、左侧、右侧)。 ================================================ FILE: snap_video_demo/readme.md ================================================ ## 1. 前言说明 1. 编译好之后记得将动态库文件比如dll_ffmpeg4(如果是vlc内核则是dll_vlc3/举一反三其他内核都是如此)下面的所有文件复制到可执行文件同一目录。 2. 非windows系统下使用ffmpeg内核,可以参考core_videoffmpeg目录下的说明文件,linux系统和mac系统上库的用法.txt / 编译阶段linux系统ffmpeg库放置位置.jpg / 运行阶段linux系统ffmpeg库放置位置。 3. **编译后生成的可执行文件在当前项目源码下的bin目录中,和core_videobase同级目录。** 4. 动态库严格区分32和64位,对应动态库目录后面有64字样表示是64位的库。如果用的是64位的Qt套件就选择64位的库。 5. 动态库下载链接:https://pan.baidu.com/s/13LDRu6mXC6gaADtrGprNVA 提取码: ujm7 6. 如果需要滤镜支持比如文字水印还需要将源码下的 wenquanyi.ttf 字体文件复制到可执行文件同一目录。 7. 项目作品大全:https://qtchina.blog.csdn.net/article/details/97565652。 8. 代码文件说明:[https://feiyangqingyun.gitee.io/qwidgetdemo/video_system/#113313-模块-corevideoffmpeg](https://feiyangqingyun.gitee.io/qwidgetdemo/video_system/#113313-模块-corevideoffmpeg) 9. **地址格式说明:[https://feiyangqingyun.gitee.io/qwidgetdemo/video_system/#07-视频格式](https://feiyangqingyun.gitee.io/qwidgetdemo/video_system/#07-视频格式)** ## 2. 使用方法 ### 2.0 特别提示 - 每一种内核就是独立的完整的模块,可以独立拿出去使用。 - 拷贝的pri组件建议放到你的项目的同级目录,这样方便区分管理,也可以放到你的项目的目录下,但是引入的时候记得做路径调整。比如代码文件的上级目录是 $$PWD/../ ,目录下就是 $$PWD/ 。 - 使用对应的内核需要在pro中增加对应的内核定义,比如ffmpeg模块需要在pro中增加 DEFINES += ffmpeg 。 - 具体的参数含义可以参见结构体定义(最重要的就是VideoPara和WidgetPara),以及完整的使用示例。 - 模块都有动态库依赖,编译完成记得将动态库拷贝到可执行文件同一目录下即可。 - 如果是视频流,想要极低延迟,vlc内核可以将缓存大小调小到0.3s,ffmpeg内核可以选择解码策略为最快速度。 ### 2.1 内核ffmpeg 1. 将内核相关文件 core_audio、core_video、core_videobase、core_videoffmpeg 拷贝放到对应目录。 2. 在你的项目的pro文件引入上面的组件并增加对应内核定义。../表示上级目录。 ```cpp DEFINES += ffmpeg videoffmpeg ffmpeg4 include($$PWD/../core_audio/core_audio.pri) include($$PWD/../core_video/core_video.pri) include($$PWD/../core_videobase/core_videobase.pri) include($$PWD/../core_videoffmpeg/core_videoffmpeg.pri) ``` 3. 使用代码 ```cpp #include "mainwindow.h" #include "videowidgetx.h" #include int main(int argc, char *argv[]) { QApplication a(argc, argv); VideoWidget w; w.resize(800, 600); VideoPara para = w.getVideoPara(); para.videoCore = VideoCore_FFmpeg; w.setVideoPara(para); w.show(); w.open("f:/mp4/push/1.mp4"); return a.exec(); } ``` ### 2.2 内核mdk 1. 将内核相关文件 core_videobase、core_video、core_videomdk 拷贝放到对应目录。 2. 在你的项目的pro文件引入上面的组件并增加对应内核定义。 ```cpp DEFINES += mdkx include($$PWD/../core_video/core_video.pri) include($$PWD/../core_videobase/core_videobase.pri) include($$PWD/../core_videomdk/core_videomdk.pri) ``` 3. 使用代码 ```cpp #include "mainwindow.h" #include "videowidgetx.h" #include int main(int argc, char *argv[]) { QApplication a(argc, argv); VideoWidget w; w.resize(800, 600); w.show(); qApp->processEvents(); VideoPara para = w.getVideoPara(); para.videoCore = VideoCore_Mdk; para.videoUrl = "f:/mp4/1.mp4"; w.setVideoPara(para); if (w.init()) { w.play(); } return a.exec(); } ``` ## 3. 基础功能 1. 支持各种音频视频文件格式,比如mp3、wav、mp4、asf、rm、rmvb、mkv等。 2. 支持本地摄像头设备和本地桌面采集,支持多设备和多屏幕。 3. 支持各种视频流格式,比如rtp、rtsp、rtmp、http、udp等。 4. 本地音视频文件和网络音视频文件,自动识别文件长度、播放进度、音量大小、静音状态等。 5. 文件可以指定播放位置、调节音量大小、设置静音状态等。 6. 支持倍速播放文件,可选0.5倍、1.0倍、2.5倍、5.0倍等速度,相当于慢放和快放。 7. 支持开始播放、停止播放、暂停播放、继续播放。 8. 支持抓拍截图,可指定文件路径,可选抓拍完成是否自动显示预览。 9. 支持录像存储,手动开始录像、停止录像,部分内核支持暂停录像后继续录像,跳过不需要录像的部分。 10. 支持无感知切换循环播放、自动重连等机制。 11. 提供播放成功、播放完成、收到解码图片、收到抓拍图片、视频尺寸变化、录像状态变化等信号。 12. 多线程处理,一个解码一个线程,不卡主界面。 ## 4. 特色功能 1. 同时支持多种解码内核,包括qmedia内核(Qt4/Qt5/Qt6)、ffmpeg内核(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6)、vlc内核(vlc2/vlc3)、mpv内核(mpv1/mp2)、mdk内核、海康sdk、easyplayer内核等。 2. 非常完善的多重基类设计,新增一种解码内核只需要实现极少的代码量,就可以应用整套机制,极易拓展。 3. 同时支持多种画面显示策略,自动调整(原始分辨率小于显示控件尺寸则按照原始分辨率大小显示,否则等比缩放)、等比缩放(永远等比缩放)、拉伸填充(永远拉伸填充)。所有内核和所有视频显示模式下都支持三种画面显示策略。 4. 同时支持多种视频显示模式,句柄模式(传入控件句柄交给对方绘制控制)、绘制模式(回调拿到数据后转成QImage用QPainter绘制)、GPU模式(回调拿到数据后转成yuv用QOpenglWidget绘制)。 5. 支持多种硬件加速类型,ffmpeg可选dxva2、d3d11va等,vlc可选any、dxva2、d3d11va,mpv可选auto、dxva2、d3d11va,mdk可选dxva2、d3d11va、cuda、mft等。不同的系统环境有不同的类型选择,比如linux系统有vaapi、vdpau,macos系统有videotoolbox。 6. 解码线程和显示窗体分离,可指定任意解码内核挂载到任意显示窗体,动态切换。 7. 支持共享解码线程,默认开启并且自动处理,当识别到相同的视频地址,共享一个解码线程,在网络视频环境中可以大大节约网络流量以及对方设备的推流压力。国内顶尖视频厂商均采用此策略。这样只要拉一路视频流就可以共享到几十个几百个通道展示。 8. 自动识别视频旋转角度并绘制,比如手机上拍摄的视频一般是旋转了90度的,播放的时候要自动旋转处理,不然默认是倒着的。 9. 自动识别视频流播放过程中分辨率的变化,在视频控件上自动调整尺寸。比如摄像机可以在使用过程中动态配置分辨率,当分辨率改动后对应视频控件也要做出同步反应。 10. 音视频文件无感知自动切换循环播放,不会出现切换期间黑屏等肉眼可见的切换痕迹。 11. 视频控件同时支持任意解码内核、任意画面显示策略、任意视频显示模式。 12. 视频控件悬浮条同时支持句柄、绘制、GPU三种模式,非绝对坐标移来移去。 13. 本地摄像头设备支持指定设备名称、分辨率、帧率进行播放。 14. 本地桌面采集支持设定采集区域、偏移值、指定桌面索引、帧率、多个桌面同时采集等。还支持指定窗口标题采集固定窗口。 15. 录像文件同时支持打开的视频文件、本地摄像头、本地桌面、网络视频流等。 16. 瞬间响应打开和关闭,无论是打开不存在的视频或者网络流,探测设备是否存在,读取中的超时等待,收到关闭指令立即中断之前的操作并响应。 17. 支持打开各种图片文件,支持本地音视频文件拖曳播放。 18. 视频流通信方式可选tcp/udp,有些设备可能只提供了某一种协议通信比如tcp,需要指定该种协议方式打开。 19. 可设置连接超时时间(视频流探测用的超时时间)、读取超时时间(采集过程中的超时时间)。 20. 支持逐帧播放,提供上一帧/下一帧函数接口,可以逐帧查阅采集到的图像。 21. 音频文件自动提取专辑信息比如标题、艺术家、专辑、专辑封面,自动显示专辑封面。 22. 视频响应极低延迟0.2s左右,极速响应打开视频流0.5s左右,专门做了优化处理。 23. 支持H264/H265编码(现在越来越多的监控摄像头是H265视频流格式)生成视频文件,内部自动识别切换编码格式。 24. 支持用户信息中包含特殊字符(比如用户信息中包含+#@等字符)的视频流播放,内置解析转义处理。 25. 支持滤镜,各种水印及图形效果,支持多个水印和图像,可以将OSD标签信息和各种图形信息写入到MP4文件。 26. 支持视频流中的各种音频格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支持,推荐选择AAC兼容性跨平台性最好。 27. 内核ffmpeg采用纯qt+ffmpeg解码,非sdl等第三方绘制播放依赖,gpu绘制采用qopenglwidget,音频播放采用qaudiooutput。 28. 内核ffmpeg和内核mdk支持安卓,其中mdk支持安卓硬解码,性能非常凶残。 29. 可以切换音视频轨道,也就是节目通道,可能ts文件带了多个音视频节目流,可以分别设置要播放哪一个,可以播放前设置好和播放过程中动态设置。 30. 可以设置视频旋转角度,可以播放前设置好和播放过程中动态改变。 31. 视频控件悬浮条自带开始和停止录像切换、声音静音切换、抓拍截图、关闭视频等功能。 32. 音频组件支持声音波形值数据解析,可以根据该值绘制波形曲线和柱状声音条,默认提供了声音振幅信号。 33. 标签和图形信息支持三种绘制方式,绘制到遮罩层、绘制到图片、源头绘制(对应信息可以存储到文件)。 34. 通过传入一个url地址,该地址可以带上通信协议、分辨率、帧率等信息,无需其他设置。 35. 保存视频到文件支持三种策略,自动处理、仅限文件、全部转码,转码策略支持自动识别、转264、转265,编码保存支持指定分辨率缩放或者等比例缩放。比如对保存文件体积有要求可以指定缩放后再存储。 36. 支持加密保存文件和解密播放文件,可以指定秘钥文本。 37. 提供的监控布局类支持64通道同时显示,还支持各种异型布局,比如13通道,手机上6行2列布局。各种布局可以自由定义。 38. 支持电子放大,在悬浮条切换到电子放大模式,在画面上选择需要放大的区域,选取完毕后自动放大,再次切换放大模式可以复位。 39. 各组件中极其详细的打印信息提示,尤其是报错信息提示,封装的统一打印格式。针对现场复杂的设备环境测试极其方便有用,相当于精确定位到具体哪个通道哪个步骤出错。 40. 同时提供了简单示例、视频播放器、多画面视频监控、监控回放、逐帧播放、多屏渲染等单独窗体示例,专门演示对应功能如何使用。 41. 监控回放可选不同厂家类型、回放时间段、用户信息、指定通道。支持切换回放进度。 42. 可以从声卡设备下拉框选择声卡播放声音,提供对应的切换声卡函数接口。 43. 支持编译到手机app使用,提供了专门的手机app布局界面,可以作为手机上的视频监控使用。 44. 代码框架和结构优化到最优,性能强悍,注释详细,持续迭代更新升级。 45. 源码支持windows、linux、mac、android等,支持各种国产linux系统,包括但不限于统信UOS/中标麒麟/银河麒麟等。还支持嵌入式linux。 46. 源码支持Qt4、Qt5、Qt6,兼容所有版本。 ## 5. 视频控件 1. 可动态添加任意多个osd标签信息,标签信息包括名字、是否可见、字号大小、文本文字、文本颜色、背景颜色、标签图片、标签坐标、标签格式(文本、日期、时间、日期时间、图片)、标签位置(左上角、左下角、右上角、右下角、居中、自定义坐标)。 2. 可动态添加任意多个图形信息,比如人工智能算法解析后的图形区域信息直接发给视频控件即可。图形信息支持任意形状,直接绘制在原始图片上,采用绝对坐标。 3. 图形信息包括名字、边框大小、边框颜色、背景颜色、矩形区域、路径集合、点坐标集合等。 4. 每个图形信息都可指定三种区域中的一种或者多种,指定了的都会绘制。 5. 内置悬浮条控件,悬浮条位置支持顶部、底部、左侧、右侧。 6. 悬浮条控件参数包括边距、间距、背景透明度、背景颜色、文本颜色、按下颜色、位置、按钮图标代码集合、按钮名称标识集合、按钮提示信息集合。 7. 悬浮条控件一排工具按钮可自定义,通过结构体参数设置,图标可选图形字体还是自定义图片。 8. 悬浮条按钮内部实现了录像切换、抓拍截图、静音切换、关闭视频等功能,也可以自行在源码中增加自己对应的功能。 9. 悬浮条按钮对应实现了功能的按钮,有对应图标切换处理,比如录像按钮按下后会切换到正在录像中的图标,声音按钮切换后变成静音图标,再次切换还原。 10. 悬浮条按钮单击后都用名称唯一标识作为信号发出,可以自行关联响应处理。 11. 悬浮条空白区域可以显示提示信息,默认显示当前视频分辨率大小,可以增加帧率、码流大小等信息。 12. 视频控件参数包括边框大小、边框颜色、焦点颜色、背景颜色(默认透明)、文字颜色(默认全局文字颜色)、填充颜色(视频外的空白处填充黑色)、背景文字、背景图片(如果设置了图片优先取图片)、是否拷贝图片、缩放显示模式(自动调整、等比缩放、拉伸填充)、视频显示模式(句柄、绘制、GPU)、启用悬浮条、悬浮条尺寸(横向为高度、纵向为宽度)、悬浮条位置(顶部、底部、左侧、右侧)。 ## 6. 特别说明 1. 共享解码线程不支持句柄模式,必须是绘制模式或者GPU模式。目前支持共享解码线程的有qmedia内核绘制模式、ffmpeg内核绘制模式、ffmpeg内核GPU模式、vlc内核绘制模式、海康sdk内核绘制模式、海康sdk内核GPU模式等。 2. vlc和mpv内核对超过12路通道支持不友好,性能急剧暴跌。因为播放器的设计初衷是在电脑上同时播放1-8路视频,超过8路的场景不建议使用vlc和mpv内核。 3. 跨平台最好的内核是ffmpeg内核,强烈推荐此内核。 4. 如果只是当普通播放器在本地使用,推荐mpv和vlc内核。 5. 如果嵌入式板子上厂家移植好了硬解码到QMediaPlayer模块,推荐qmedia内核。 6. 视频显示模式受限于具体的解码内核支持,比如目前mpv内核只有句柄模式,vlc内核只有句柄和绘制模式,ffmpeg内核只有绘制和GPU模式,海康内核支持三种模式。 7. 视频控件支持的图形信息,只支持绘制模式,只有绘制模式才能把图形信息绘制到原始图片上。 8. Qt6.0到Qt6.2之间的版本由于缺失多媒体模块所以无法使用qmedia内核。 9. ffmpeg内核最快速度解码策略:速度优先并且不做音视频同步,打开不等待缓存,一般在特定视频流环境中需要,对响应速度有要求的。 10. vlc内核如果同时打开多路视频流建议缓存设置大一些,不然CPU占用极高容易崩溃。 11. mpv内核有内存泄漏迹象,和mpv动态库有关。 12. linux安装 apt-get install gstreamer1.0-libav。https://blog.csdn.net/MoonShapedPool/article/details/82835565 13. 画面选择拉伸填充标签效果最佳,画面选择自动调整图形效果最佳。 14. Qt4中音频组件不能调节声音大小。 15. Qt5.6以下的多媒体模块视频没有图像,QAbstractVideoSurface也获取不到图像。 16. vlc内核和mpv内核在保存阶段不建议切换播放进度,vlc内部强制不允许切换,mpv切换了会保存错乱。 17. ffmpeg内核支持保存阶段切换播放进度,因为底层是自己写的代码处理的保存,做了特殊处理。 18. ffmpeg内核32位不支持超过14路同时录制,原因未知,建议选择64位。 19. qmedia内核暂时不支持保存文件,有对应的类但是Qt底层没实现,坐等他实现。 ================================================ FILE: snap_video_gb28181/readme.md ================================================ ## 1 前言说明 1. 可执行文件在当前项目源码下的bin目录,编译后会自动生成。 2. **编译完成后记得将源码下的file目录下(切记是file目录下而不是file目录)的所有文件复制到可执行文件同一目录。** 3. 编译好之后记得将mdk的动态库文件dll_mdk_32/dll_mdk_64(如果是64位的Qt套件则选择dll_mdk_64)下面的所有文件复制到可执行文件同一目录。 4. 动态库下载: https://pan.baidu.com/s/13LDRu6mXC6gaADtrGprNVA 提取码: ujm7 5. 项目作品大全: [https://qtchina.blog.csdn.net/article/details/97565652](https://qtchina.blog.csdn.net/article/details/97565652) ## 2 代码使用 ### 2.0 基本步骤 1. 第一步,将core_gb28181server和core_gb28181rtp以及core_gb28181widget目录拷贝到你的项目的上一级目录。 2. 第二步,打开项目的pro文件,引入gb28181组件,include ($$PWD/../core_gb28181server/core_gb28181server.pri) 和 include ($$PWD/../core_gb28181rtp/core_gb28181rtp.pri)以及include ($$PWD/../core_gb28181widget/core_gb28181widget.pri) 。 3. 第三步,在代码文件引入对应头文件,#include "gb28181server.h",使用代码。 4. core_gb28181server负责信令解析和交互,core_gb28181rtp负责rtp解包,core_gb28181widget负责视频显示,解码默认使用core_videomdk组件,此组件使用ffmpeg开发。也可以切换成core_videoffmpeg组件。 5. 为了使用方便,特意封装了GB28181Widget类,专用于国标流的预览、回放、下载,不显示期间不会触发绘制,不用担心占用系统资源,此类也可用于推流。 ### 2.1 启动服务 ```cpp //实例化国标服务类 GB28181Server *server = new GB28181Server; //设置需要的参数 GB28181ServerPara para; //国标编码 para.serverId = "34020000002000000001"; //区域编码 para.serverRealm = "3402000000"; //外网地址/设备端填写的服务器地址就是这个地址/如果是本地则和监听地址相同 para.serverHost = "192.168.0.110"; //监听地址 para.serverIp = "192.168.0.110"; //监听端口 para.serverPort = 15060; //认证密码/为空的话则不启用认证 para.serverPwd = "12345678"; //查询通道信息的间隔/0表示不启用/单位秒 para.queryInterval = 0; server->setServerPara(para); //启动服务/udp和tcp同时监听 server->start(ListenMode_Both); //绑定信号接收数据 //设备上下线 connect(server, SIGNAL(deviceChanged(QString, bool)), this, SLOT(deviceChanged(QString, bool))); //通道变化 connect(server, SIGNAL(channelChanged(QString, QList)), this, SLOT(channelChanged(QString, QList))); //警情上报 connect(server, SIGNAL(receiveEvent(GB28181Event)), this, SLOT(receiveEvent(GB28181Event))); //媒体状态变化 connect(server, SIGNAL(receiveStatus(GB28181Status)), this, SLOT(receiveStatus(GB28181Status))); //返回预置位信息 connect(server, SIGNAL(receivePreset(QList)), this, SLOT(receivePreset(QList))); //返回录像文件信息 connect(server, SIGNAL(receiveRecord(QList)), this, SLOT(receiveRecord(QList))); //停止服务 server->stop(); ``` ### 2.2 获取信息 ```cpp //设备编码 QString deviceId = "34020000001320000001"; //通道编码 QString channelId = "34020000001310000001"; //获取通道信息 server->query(deviceId, "Catalog"); //获取设备信息 server->query(deviceId, "DeviceInfo"); //获取设备状态 server->query(deviceId, "DeviceStatus"); //查询设备配置 server->queryConfig(deviceId); ``` ### 2.3 视频点播 ```cpp //点播必须针对某个设备某个通道 QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; //TransmitMode支持三种 //TransmitMode_UdpServer表示udp方式 //TransmitMode_TcpServer表示tcp被动方式 //TransmitMode_TcpClient表示tcp主动方式 //具体设备支持哪些模式由厂家决定/一般早期的设备都会支持udp模式/部分厂家只支持其中一种比如tcp被动模式 //返回值用来区分是哪个点播动作/一个设备的一个通道可以有多次点播 QString ssrc = server->invite(deviceId, channelId, 6900, "", "", 0, 0, TransmitMode); //点播成功后会发出startVideo信号 //对于udp和tcp被动模式/建议先监听端口成功后再去点播 ``` ### 2.4 视频预览 ```cpp QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; ui->gb28181Widget->setPara(1, 0, TransmitMode_UdpServer, "192.168.0.110", "", PlayType_Preview); ui->gb28181Widget->openVideo(deviceId, channelId); ``` ### 2.5 录像查询 ```cpp QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; QString dateStart = "2025-04-18T00:00:00"; QString dateEnd = "2025-04-19T23:59:59"; server->queryRecord(deviceId, channelId, dateStart, dateEnd); //查询成功后会通过receiveRecord信号发出来 void frmVideoPlayback::receiveRecord(const QList &records) { foreach (GB28181Record record, records) { QString startTime = record.startTime; QString endTime = record.endTime; QString date = startTime.mid(0, 10); QString start = startTime.mid(11, 5); QString end = endTime.mid(11, 5); QListWidgetItem *item = new QListWidgetItem; item->setData(Qt::UserRole + 1, startTime); item->setData(Qt::UserRole + 2, endTime); item->setText(QString("%0 %1 - %2").arg(date).arg(start).arg(end)); ui->listWidget->addItem(item); } } ``` ### 2.6 录像回放 ```cpp //为了简化操作/特意封装了gb28181Widget播放窗体类/只需要传入参数调用openvideo即可 QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; QString startTime = "2025-04-18T00:00:00"; QString endTime = "2025-04-18T00:05:00"; //设备需要的参数/播放速度/主码流子码流/传输模式/监听地址/推流地址 ui->gb28181Widget->setPara(1, 0, TransmitMode_UdpServer, "192.168.0.110", "", PlayType_Playback); //设置开始时间和结束时间/查询录像文件返回的时候就有该文件对应的时间范围 ui->gb28181Widget->setTime(startTime, endTime); //指定设备通道开始播放 ui->gb28181Widget->openVideo(deviceId, channelId); //回放过程可以执行对应指令 //暂停播放 server->playControl(deviceId, channelId, PlayControl_Pause, 0, ssrc); //继续播放 server->playControl(deviceId, channelId, PlayControl_Play, 0, ssrc); //切换速度 server->playControl(deviceId, channelId, PlayControl_Scale, 8.0, ssrc); //切换进度/进度值是当前文件开始的时间加上多少秒 server->playControl(deviceId, channelId, PlayControl_Position, 100); ``` ### 2.7 录像下载 ```cpp //流程和回放完全一致/传过来的数据也是样一样/唯一区别就是解码后自行开启保存 QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; QString startTime = "2025-04-18T00:00:00"; QString endTime = "2025-04-18T00:05:00"; ui->gb28181Widget->setPara(8, 0, TransmitMode_UdpServer, "192.168.0.110", "", PlayType_Download); ui->gb28181Widget->setTime(startTime, endTime); ui->gb28181Widget->openVideo(deviceId, channelId); ``` ### 2.8 警情订阅 ```cpp //警情订阅/测试下来发现设备没有订阅也会主动上报警情 QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; server->subscribe(deviceId, channelId); //警情通过receiveEvent信号上报 void frmEvent::receiveEvent(GB28181Event event) { ui->tableWidget->insertRow(0); ui->tableWidget->setItem(0, 0, new QTableWidgetItem(event.deviceId)); ui->tableWidget->setItem(0, 1, new QTableWidgetItem(event.channelId)); ui->tableWidget->setItem(0, 2, new QTableWidgetItem(QString::number(event.alarmPriority))); ui->tableWidget->setItem(0, 3, new QTableWidgetItem(GB28181Helper::getAlarmMethod(event.alarmMethod))); ui->tableWidget->setItem(0, 4, new QTableWidgetItem(GB28181Helper::getAlarmType(event.alarmInfo, event.alarmMethod, event.alarmType))); ui->tableWidget->setItem(0, 5, new QTableWidgetItem(event.alarmTime)); ui->tableWidget->setItem(0, 6, new QTableWidgetItem(event.alarmDescription)); } ``` ### 2.9 语音对讲 ```cpp //一般要求先打开视频后才能语音对讲/需要在视频中接收声音 QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; //开始语音对讲 ui->gb28181Widget->openAudio(deviceId, channelId); //关闭语音对讲 ui->gb28181Widget->closeAudio(deviceId, channelId); ``` ### 2.10 点播推流 ```cpp QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; QString pushUrl = "rtsp://192.168.0.110:8554/stream/34020000001320000001_34020000001310000001"; ui->gb28181Widget->setPara(1, 0, TransmitMode_UdpServer, "192.168.0.110", pushUrl, PlayType_Preview); ui->gb28181Widget->openVideo(deviceId, channelId); ``` ### 2.11 回放推流 ```cpp QString deviceId = "34020000001320000001"; QString channelId = "34020000001310000001"; QString pushUrl = "rtsp://192.168.0.110:8554/record/34020000001320000001_34020000001310000001"; QString startTime = "2025-04-18T00:00:00"; QString endTime = "2025-04-18T00:05:00"; //设备需要的参数/播放速度/主码流子码流/传输模式/监听地址/推流地址 ui->gb28181Widget->setPara(1, 0, TransmitMode_UdpServer, "192.168.0.110", pushUrl, PlayType_Playback); //设置开始时间和结束时间/查询录像文件返回的时候就有该文件对应的时间范围 ui->gb28181Widget->setTime(startTime, endTime); //指定设备通道开始播放 ui->gb28181Widget->openVideo(deviceId, channelId); ``` ## 3 使用说明 ### 3.0 设备设置 海康IPC后台设置界面: ![](snap/video_gb28181_hkipc.jpg) 海康NVR后台设置界面: ![](snap/video_gb28181_hknvr.jpg) 大华IPC后台设置界面: ![](snap/video_gb28181_dhipc.jpg) 大华NVR后台设置界面: ![](snap/video_gb28181_dhnvr.jpg) 宇视NVR后台设置界面: ![](snap/video_gb28181_ysnvr.jpg) 华为IPC后台设置界面: ![](snap/video_gb28181_hwipc.jpg) ### 3.1 系统设置 ![](snap/video_gb28181_0.jpg) - 国标编码:对应设备端填的sip服务器编号,固定长度,具体编码规则按照国标文档约定来。 - 国标区域:对应设备端填的sip域。 - 外网地址:在云服务器上,对外提供公网访问的地址。在本地一般外网地址和监听网卡地址是相同的。 - 监听网卡:本地监听地址,可以从网卡下拉框选择不同的地址,如果选择0.0.0.0表示监听所有。 - 监听端口:本地监听的端口号,对应设备端填的sip服务器端口。 - 认证密码:默认为空表示不认证,也就是设备注册注销期间不做密码认证。对应设备端的注册密码。设置了密码后,还会对国标编码的正确性进行验证,比如设备传过来的注册指令带的国标编码不正确则会拒绝注册。 - 自动推流:下拉框可以选择手动推流还是自动推流,开启自动推流后,本程序相当于服务使用,一旦设备上线,自动点播并推流分发。手动的话需要双击对应通道后才会点播并推流。 - 预览推流:将预览的实时画面推流到流媒体服务程序,对外提供各种拉流地址包括但不限于rtsp/rtmp/http/wertc,可以在网页上拉流显示。建议和回放推流地址区分开来。对应拉流地址会在打印窗口打印出来。这里是可编辑下拉框,既可以下拉选择,也可以直接输入。 - 回放推流:将回放的实时画面推流到流媒体服务程序,对外提供各种拉流地址包括但不限于rtsp/rtmp/http/wertc,可以在网页上拉流显示。建议和预览推流地址区分开来。对应拉流地址会在打印窗口打印出来。这里是可编辑下拉框,既可以下拉选择,也可以直接输入。 - 码流类型:在预览推流和回放推流下拉框的后面,有个主码流子码流选项,这个表示对应点播拉流的时候采用何种码流,如果预览推流后面下拉框是子码流,则双击通道点播画面的时候,以子码流去拉流。 - 最小端口:收流用端口范围的最小端口号,建议大于1000,一般在云服务器上需要设置,上面一般按需开启的端口。 - 最大端口:收流用端口范围的最大端口号,不能小于最小端口号,一般在云服务器上需要设置,上面一般按需开启的端口。 - 查询间隔:默认禁用表示不会定期查询通道信息。 - 调试过滤:在软件的右上角有个地址编码下拉框,用来过滤调试打印信息的,也就是方便查看具体设备的收发数据,如果没有这个过滤,则所有收发数据都会显示,不方便查找具体哪个设备。按照编码表示下拉框是设备的国标编码,按照地址则下拉框是IP地址,有时候同一个IP会有多个设备,只不过通信端口不一样,所以此时建议选择按照编码来过滤。 - 监听模式:仅udp表示只监听udp通道,仅tcp表示只监听tcp通道,选择udp和tcp则两者都监听,默认两者都监听。 - 传输模式:对应拉流的时候数据的传输模式,可选udp、tcp被动、tcp主动三种模式。 - 解码内核:选用ffmpeg解码还是mdk解码,实时预览推荐mdk解码,录像回放推荐ffmpeg解码。 - 画面缩放:打开的视频画面按照等比例还是拉伸填充显示。 - 这些配置参数设置完都是立即应用的,国标服务器参数需要重新单击启动服务按钮应用,传输模式需要重新打开视频。 **拉流地址格式** - 具体拉流格式是由流媒体服务程序决定的,下面用mediamtx举例。 - 预览推流地址:rtsp://127.0.0.1:8554/stream - 回放推流地址:rtsp://127.0.0.1:8554/record - 通用拉流地址:前缀/设备编码\_通道编码\_ssrc,为何有个ssrc?因为同一个通道可能打开了多个流。 - 预览拉流地址:rtsp格式 rtsp://127.0.0.1:8554/stream/34020000001320000001_34020000001310000001_0000010001 - 预览拉流地址:webrtc格式 http://127.0.0.1:8889/stream/34020000001320000001_34020000001310000001_0000010001 - 回放拉流地址:rtsp格式 rtsp://127.0.0.1:8554/record/34020000001320000001_34020000001310000001_0000010001 - 预览拉流地址:webrtc格式 http://127.0.0.1:8889/record/34020000001320000001_34020000001310000001_0000010001 ### 3.2 实时预览 ![](snap/video_gb28181_1.jpg) ![](snap/video_gb28181_2.jpg) ![](snap/video_gb28181_3.jpg) - 第一步:从设备列表选中某个设备,如果是需要打开实时预览,还需要选中某个通道,默认选择设备会自动以第一个通道作为默认通道。 - 单击通道列表可以获取该设备下的所有通道信息。相当于手动获取,如果系统设置那边查询间隔非禁用,则会按照选择的间隔自动查询。 - 单击设备信息、设备状态、设备配置按钮,可以查询选中设备的信息和状态以及配置参数。 - 单击打开视频,会以新视频窗体的方式弹出视频预览,该按钮对应代码也有直接点播请求的代码,可以自行在代码中切换,以便测试。关闭按钮同理。 - 选中设备列表的某个通道执行双击,自动在实时预览画面打开该通道的视频,切记有个传输模式,有些设备可能只支持udp,所以需要在系统设置界面传输模式下拉框选择udp,有些只支持tcp被动,具体由设备端本身决定。 - 鼠标移动到预览画面窗体,会自动出现悬浮条,显示对应的音视频流信息,有音视频格式帧率等信息,也可以在悬浮条上单击右上角的关闭按钮来停止点播。 - 在悬浮条上单击语音对讲按钮,自动开启语音对讲,同一时刻只允许一个通道处于对讲状态。 - 在实时预览窗体鼠标右键,会有画面分割的菜单,执行对应的菜单会切换到对应的画面。 - 在实时预览窗体,鼠标按下选中某个通道,在云台操作面板,单击上下左右等箭头按钮可以做云台操作,包括变倍、聚焦、光圈都可以进行操作。 - 在实时预览窗体,鼠标按下选中某个通道,在预置位操作面板,可以查询预置位、添加预置位、删除预置位、更新预置位、调用预置位等操作。 ### 3.3 录像回放 ![](snap/video_gb28181_4.jpg) - 第一步:设备列表选中某个设备和通道。 - 第二步:日期时间下拉框选择开始时间和结束时间,精确到秒。 - 第三步:单击查询录像按钮,右侧列表框会显示返回的录像文件的信息。 - 第四步:双击某个文件,自动回放该文件,左下角显示对应的当前进度和总时长。 - 第五步:单击进度条可以切换播放进度,倍速下拉框可以切换播放的速度,还可以暂停和继续播放。 ### 3.4 录像下载 ![](snap/video_gb28181_5.jpg) - 第一步:设备列表选中某个设备和通道。 - 第二步:日期时间下拉框选择开始时间和结束时间,精确到秒。 - 第三步:单击查询录像按钮,表格中显示收到的录像文件的详细信息,末尾显示下载进度。 - 第四步:在要下载的文件前面选中复选框,选中的才会执行下载。左上角有个全选复选框。 - 第五步:单击开始下载按钮,自动多线程下载文件,保存在可执行文件下的video目录。 - 默认8倍速下载,如果有些不支持倍速,则可以在速度下拉框选择1倍速。 - 每个下载都在右侧有个进度条显示当前下载进度,左上角是所有下载的总进度。 - 下载过程中可以单击停止下载来强制停止,会自动生成文件。 - **受限于设备端的发流速度,一般建议如果同时下载多个,用1倍速,多倍速的情况下建议一个个去下载,而不是同时批量下载。** ### 3.5 推流分发 ![](snap/video_gb28181_6.jpg) - 这里不需要任何操作,只要有推流消息,就会显示在这里。 - 需要先在系统设置的预览推流或者回放推流下拉框中选择或者填入推流地址才会有推流。 - 推流类型一般有三种,预览推流表示双击对应通道显示后的推流,回放推流表示在录像回放中选择对应回放文件后的推流,还有个自动推流,对应配置文件有个参数autopush,默认false,可以改成true,这样只要有设备上线,就会自动点播拉流推流分发,一般用于开机后自动启动的场景,无需手动点播推流。 - 选中对应的行,会自动拷贝对应推流后的rtsp地址,可以打开vlc等播放器拉流验证下。 - 对应rtsp、rtmp等列,用来显示当前推出去的流,有多少个地方在拉取,flv的拉流可能会合并统计到rtmp类别中,hls的统计未必准确,这些数据都是实时从流媒体服务程序http获取到的。 - 无人观看列,用来显示所有推流的数据,无人观看多久,超过规定的时间还是无人观看,自动停止推流,从源头解决占用带宽浪费带宽问题。 - 推流地址这里简写的,只显示核心的设备编码和通道编码,前缀就是推流地址。 ### 3.6 警情信息 ![](snap/video_gb28181_7.jpg) - 这里不需要任何操作,只要有警情消息,就会显示在这里。 - 可以到设备的后台设置一些检测,比如入侵报警等,一旦触发,这里会显示对应警情详情。 ### 3.7 后台服务 ![](snap/video_gb28181_8.jpg) - 默认后台服务关闭的,需要在配置文件ServiceMode改成true。 - 这个服务用一套自定义的私有协议,和外部通信,可以通过tcp、http、mqtt等方式交互,比如获取设备列表、请求点播视频、回放视频、警情通知等,方便第三方根据协议进行交互接入使用。 - 单击模拟测试会弹出个框,写了简单的测试使用,用来验证每一条协议是否生效,也可以直接打开网络调试助手收发数据。 - 右侧是配置参数设置,网卡标识交互协议本地监听的网卡地址,下面是端口号,超时是指无人观看超时时间。后续还会增加其他参数。 - 在开启了后台服务后,双击通道可以预览,并不会推流,所有推流服务都是通过命令协议交互。 ## 4 通信流程 ### 4.0 代码流程 1. 第一步,准备数据头,根据不同的处理,组合不同的sip指令,其中固定有from、to、callid等字段。 2. 第二步,准备数据体,如果是获取设备信息、云台控制等,数据体是xml格式数据,如果是音视频点播、回放、下载等,是sdp格式数据,如果是播放控制比如倍速、暂停、切换进度等,是MANSRTSP格式数据。 3. 一般数据体中会带有通道编号。 4. 第三步,数据头和数据体组合成完整的数据,找到设备的地址和端口,发送数据。 5. 数据头和数据体之间必须有两个回车换行,否则无法解析。 6. 每个设备都至少有一个通道,整个层级关系只有2层,也就是设备和通道,如果是国标级联,也是将下面的设备重新定义成两层级编码再上传。 ### 4.1 注册注销 1. 设备端发送REGISTER注册或者注销指令,注册对应Expires>0,注销对应Expires=0。 2. 服务端收到后,判断是否带了Authorization鉴权信息。 3. 没带则应答401 Unauthorized,设备端收到后发送带上Authorization鉴权信息的指令。 4. 带了则取出response字段内容,用服务端的密码,加密算法做哈希运算,得到正确的response,和设备端传过来的response对比,一致则表示密码正确。 5. 密码不正确应答400 Wrong password。 6. 密码正确则发出设备上线信号,并主动去订阅目录,拉取通道等信息。 7. 注册和注销处理流程完全一致,唯一区别就是通过判断Expires字段的值,=0表示注销。 8. 注销成功后,立即执行设备下线处理。 ### 4.2 读取信息 1. 选中某个设备,读取信息都是针对单个设备,而不需要对通道。 2. 服务端主动发送MESSAGE指令,带上xml数据,其中xml数据中query节点中有个cmdtype用来指示何种操作类型。 3. cmdtype=Catalog表示目录订阅,也就是查询通道信息。 4. cmdtype=DeviceInfo表示读取设备信息,一般会取出其中的设备名称信息,作为设备的别名。 5. cmdtype=DeviceStatus表示读取设备状态,这个用的很少,意义不大。 6. cmdtype=ConfigDownload表示读取设备配置,一般会取出对应的超时时间和超时次数,用来判断设备下线。 ### 4.3 设备控制 1. 选择某个设备某个通道,控制必须是对单个通道控制,而不是对某个设备。 2. 计算控制码,云台的各个方位以及聚焦和预置位等,都对应固定的字节,具体格式规范在gb28181-2016文档的第79页。 3. 将控制码放在DeviceControl指令的xml数据的PTZCmd节点,带上通道编码,组合xml数据,用sip指令下发给对应设备。 4. 设备端收到后会应答200 OK。 5. 读取预置位信息,会返回xml数据,一堆PresetList,逐个解析就行。 6. 标准中并没有看到如何修改预置位的名称,只有编号,统一preset1-preset255这种名称。而onvif协议中就可以在添加个修改的时候传入自定义的预置位名称,可以是中文。 ### 4.4 视频点播 1. 选择某个设备某个通道,点播必须是对单个通道点播,而不是对某个设备。 2. 准备sdp数据,数据中包含了通道编码、服务器地址和端口,要点播的方式是udp、tcp主动、tcp被动。还可以拓展streamprofile字段表示主码流还是子码流等信息。 3. 发送INVITE的sip指令给设备端。 4. 设备应答带有sdp数据的200 OK指令,从中解析出来from、to、callid三个字段,这个用来唯一标识一个通信。 5. 服务端返回ack的sip指令,表示可以正常通信了。 6. 设备端将对应的音视频流数据用rtp打包发给对应的服务器端口。 7. 收到音视频数据,先要用rtp解包,解包后一般是ps流数据,再发给ffmpeg这种第三方库解码。 8. 关闭点播,服务端主动发送bye的sip指令到设备端,结束整个点播流程。 ### 4.5 文件查询 1. 选择某个设备某个通道,查询录像文件必须是对单个通道,而不是对某个设备。 2. 选择开始时间和结束时间,通过MESSAGE指令,xml数据对应cmdtype=RecordInfo,发给设备端。 3. 设备应答会将录像文件信息打包成xml格式数据返回,一个xml数据中包含多个录像文件信息,可能会返回多个xml数据,一定要注意粘包处理。 4. 查询的时间范围建议小一点,一般一两天就好,不然返回的文件数量很多。 5. 返回的录像文件信息至少包含了设备编码、通道编码、开始时间、结束时间。后期录像回放和下载的时候要用到这几个参数。 ### 4.6 录像回放 1. 回放流程和点播流程完全一致,唯一区别就是sdp中的t字段,如果是点播则对应t=0 0,如果是回放则填写的开始时间和回放时间,对应1970年经过的秒数。 2. 对应的s字段,点播是Play,回放是Playback,下载是Download。 3. 回放过程中可以进行播放控制,比如倍速,为何点播不能倍速?因为那个是实时流,不可能切换到未来的时间点。 4. 播放控制,先要准备数据体,也就是MANSRTSP格式数据,具体格式规范在gb28181-2016文档的第83页。 5. 播放控制通过发送INFO的sip指令给设备端。 ### 4.7 语音对讲 1. 打开通道视频,在视频控件悬浮条上单击语音对讲按钮开始对讲,再次单击关闭对讲,同一时刻只允许一个通道处于对讲状态。 2. 先发送语音广播通知,通过MESSAGE指令,xml数据对应cmdtype=Broadcast,信息带上SourceID和TargetID。 3. 设备端收到语音广播通知后,会主动发起INVITE点播请求到服务端,请求中带了sdp信息。 4. 服务端解析sdp信息,应答点播请求,同样带上自己这边的sdp信息。 5. 服务端绑定端口,根据sdp内容选择是被动接收还是主动发起连接到设备端。 6. 设备端应答ack,开启语音对讲,服务端采集语音pcm数据,转成pcma,再通过rtp打包发给对应设备端口。 7. 默认设备支持的是局域网对讲,如果要公网对讲,需要支持tcp主动模式,也就是设备主动连接服务端,然后服务端对对应的连接发送语音数据。 8. 测试发现海康大华的设备,音频播放那边要设置成g711a,才能正常播放语音。查阅了下资料,好像如果要支持28181语音对讲,必须设置成这个,其他28181平台也是这个规范。 9. 海康的语音对讲默认是udp模式,如果要支持广域网对讲,也就是让设备端主动连接服务端,让后语音通信,必须要新版的设备才支持,后台28181设置的地方有个启用tcp广播,这个开关必须开启。 10. 大华的语音对讲如果要支持公网,必须采用非国标的sip交互,详情见 https://zhuanlan.zhihu.com/p/649434585 。 ### 4.8 图像抓拍 1. 图像抓拍是gb28181-2022新增的功能,需要支持2022版本的设备才支持,主流厂家大概从2024年开始才可能支持这个版本。 2. 选择某个设备某个通道,图像抓拍必须是对单个通道,而不是对某个设备。 3. 发送图像抓拍通知,通过MESSAGE指令,xml数据对应cmdtype=DeviceConfig,信息带上SnapShotConfig节点,里面包含了SnapNum-抓拍张数、Interval-抓拍间隔、UploadURL-上传地址、SessionID-信息编号。 4. 服务端打开监听端口,等待设备端上传图片,通过http指令post过来,也可以选择ftp方式上传。 5. post请求数据中带了SessionID=9a7d3a146a1e420dbd00020ed01b39e7,filename="34020000001310000001022025081609220800000.jpg" 这两个关键数据。 6. 如果指定了抓拍多张,则每一张都会有一个post请求,对应SessionID相同,filename不同,收到图片后可以存储到本地,按照指定的文件名。 7. 全部抓拍上传完成,设备端发送MESSAGE指令,带xml数据,对应cmdtype=UploadSnapShotFinished,里面有SnapShotList节点指明上传了哪些图片,以便对照进行分类。 ## 5 功能特点 1. 支持设备注册、注销、心跳、校时、注册认证、注销认证等。 2. 设备上线后可以手动获取设备状态、设备信息、配置信息、预置位信息等。 3. 设备上线后自动获取设备通道信息,包括中文通道名称。识别到通道上线离线变化,会重新获取该设备的所有通道信息。 4. 支持视频点播,可以分别点播主码流和子码流,内置rtp解包线程,解包后发给视频播放组件解码播放。 5. 每个设备每个通道支持点播多个视频,通过ssrc区分,支持共用端口和不同端口收流。 6. 支持对某个设备下面所有通道、某个通道、某个通道对应的某个流分别关闭。 7. 支持录像文件查询和回放,回放控制支持暂停播放、继续播放、倍速播放、切换播放进度。 8. 支持录像文件下载,支持倍速比如8倍速下载,可同时多线程批量下载。 9. 回放和下载同时支持IPC和NVR,比如摄像头自带的SD存储卡录像文件回放,NVR上的硬盘录像文件回放。 10. 支持云台控制,向上、向下、向左、向右、左上、右上、左下、右下方位移动,镜头放大缩小,光圈放大缩小,镜头聚焦放焦。 11. 支持预置位信息的查询、调用、添加、修改、删除等操作。 12. 自动目录订阅功能,通道上线下线都有对应的信号通知。 13. 内置定时读取通道信息机制,以保证通道信息是最新的,比如有些NVR是不断更新的通道信息。 14. 内置订阅警情和位置移动功能,订阅后各种警情事件比如运动目标检测报警、入侵检测报警、徘徊检测报警等自动上报。 15. 支持语音对讲功能,可以直接在视频窗体的悬浮条上单击语音对讲按钮,再次单击关闭对讲,对讲期间悬浮条常驻显示。 16. 支持设备布防撤防,布防后警情信息会主动上报。 17. 国标服务同时支持udp和tcp方式,可选只监听一种或者两种都监听,tcp方式自动处理粘包问题。 18. 国标拉流同时支持udp、tcp被动、tcp主动三种方式,每个通道都可以自由选择何种拉流方式。 19. 内置拉流端口池,每次拉流从中取出一个,关闭流自动回收端口号,重复利用。 20. 收流端口自动纠错,自动跳过被占用的端口,不会出现端口占用导致收流失败的情况。 21. 支持三种取流方式自动检测离线重连,检测到离线后,自动重启点播拉流整个流程。 22. 录像文件回放,上一个完成后自动切换到下一个继续回放,直到所有回放完成。支持高达8倍速回放。 23. 视频播放自适应硬解码,极低资源占用,实时性极好,带悬浮条显示视频流信息,可以直接在悬浮条单击按钮保存录像文件到本地。 24. 支持几千路国标消息交互并发,实时视频流支持64路同时显示,可以拓展更多路数。 25. 支持阿里云等云服务器,可以分别设置内网监听地址和外网访问地址,一般云服务器上是监听地址用内网,对外访问用外网地址。 26. 支持视频分发,也就是推流,视频通道打开后可以自动推流到流媒体服务器,其他需要的地方拉流即可,支持rtsp、rtmp、hls、webrtc等方式拉流。 27. 视频分发也叫推流分发,表格方式展示正在推流的信息,其中包括显示统计哪些流正在被多少个地址拉取,比如有两个地方通过rtsp打开了取流,则对应推流地址行所在rtsp列显示数量2,非常直观的展示有多少个拉流。 28. 视频分发支持无人观看超时自动关闭推流和点播,提高带宽的利用率,没人观看太久的时候,没必要点播拉流和推流。在后台服务模式下,通道推流自动复用,当该通道已经存在点播推流,则复用该路流数据,不会再去点播,节约资源。 29. 提供后台服务功能,定义了一套私有协议,根据私有协议进行交互,支持tcp、http、mqtt等方式交互,方便第三方程序接入集成。通信协议非常完整,支持获取设备列表、获取指定通道视频地址、云台控制、预置位操作、录像查询、录像回放、录像下载、回放倍速等控制、警情消息通知、视频点播和关闭等。 30. 支持注册重定向,方便做负载均衡和区域化部署,这样可以支持几十万个设备连接都没问题。 31. 支持图像抓拍,可以设置抓拍最多10张图片,可设置抓拍间隔,抓拍到的图片会通过信号通知。 32. 实时预览和录像回放都支持推流,推流支持叠加文字和图片水印以及各种ffmpeg支持的滤镜效果,支持多个水印同时叠加。 33. 同时支持gb28181-2011、gb28181-2016、gb28181-2022以及后续可能的所有协议版本。 34. SIP解析和交互采用纯Qt底层代码实现,udp/tcp通信交互,祖传原创代码解析,不依赖任何第三方。 35. 代码量少,gb28181交互部分共几千行代码,注释详细,接口友好,使用极其简单,提供非常详细的使用示例。 36. 支持海康、大华、宇视、华为、天地伟业等所有国标设备,包括一些没有ssrc的设备。 37. 支持所有Qt版本和编译器以及操作系统,包括但不限于win、linux、mac、android、嵌入式linux、树莓派香橙派、国产os等。 ## 6 协议规范 ### 6.0 协议解析 ```cpp //设备端发送 REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1945388331 From: ;tag=33226951 To: Call-ID: 1455222403 CSeq: 1 REGISTER Contact: Max-Forwards: 70 User-Agent: IP Camera Expires: 3600 Content-Length: 0 //服务端应答 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1494593151 From: ;tag=33226951 To: Call-ID: 1455222403 CSeq: 2 REGISTER User-Agent: wx_feiyangqingyun Date: 2025-04-22T10:48:38.234 Content-Length: 0 ``` #### 6.0.1 消息头 - 第1行表明这条SIP消息的方法(Method)是REGISTER。 - 34020000002000000001是SIP服务器的国标编码。 - 国标编码指的是由中心编码(8位) 、行业编码(2位) 、类型编码(3位)、序号(7位)四个码段共20位十进制数字字符构成。 - 具体国标编码方法可以参考GB/T 28181—2016中的附录D。 - 3402000000指的是SIP服务器的域国标ID。 - SIP/2.0指的是SIP协议版本。 #### 6.0.2 Via头 - 第2行为Via头,Via头中包含了发送请求方的相关信息,后续需要使用这些信息进行回复。 - SIP/2.0/UDP表示使用的是2.0版本的SIP协议,使用的传输协议是UDP,也可以使用TCP协议,大部分设备默认是UDP协议,GB2016以前只支持UDP协议。 - 192.168.0.64:5060为请求发送方的IP地址和端口号。 - Via头中必须包含branch参数,具体值是一个在整个SIP通信过程中不重复的数值。 - branch是一个事务ID(Transaction ID),用于区分同一个UA所发起的不同Transaction,它不会对未来的request或者是response造成影响,对于遵循IETF RFC3261规范的实现, - **这个branch参数的值必须用 z9hG4bK 字符串打头**。 - 其它部分是对To, From, Call-ID头域和Request-URI按一定的算法加密后得到,也可以是随机数或者UUID,目前没发现有什么用途。 - rport字段表示使用rport机制路由响应,即发送的响应时,按照rport中的端口发送SIP响应。 - 也就是说IP和端口均完全遵照从哪里来的,发回哪里去的原则。 - 如果没有rport字段时,服务端的策略是IP使用UDP包中的地址,即从哪里来回哪里去,但是端口使用的是via中的端口,详情见IETF RFC35818。 #### 6.0.3 From头 - 第3行为From头,From头中包含了请求发送方的逻辑标识。 - 在GB28181协议中是发送请求的设备国标ID和域国标ID信息。 - tag参数是为了身份认证的,值为随机数字字符。 #### 6.0.4 To头 - 第4行为To头,To头在SIP协议中是为了标明请求接收方的逻辑标识的。 - 在GB28181协议中填写的是发送请求的设备国标ID和域国标ID信息。 #### 6.0.5 Call-ID头 - 第5行为Call-ID头,Call-ID头是全局唯一的,在同一个session中保持一致,在不同session中不同。 #### 6.0.6 CSeq头 - 第6行为CSeq头,CSeq头又叫Command Seqence(命令队列),用于标识命令顺序。 - 值为序号+Method,序号部分为无符号整数,最大值为2^31。 - 序号起始值是随机的,后续在同一个session中依次递增。 - 比如发1 REGISTER没返回--->再发2 REGISTER--->没返回--->再发3 REGISTER--->这时返回了2 REGISTER就知道是第2个请求得到了响应。 - 对于ACK和CANCLE中的CSeq与INVITE中的Cseq保持一致。 #### 6.0.7 其他头 - 第7行为Contact头,Contact头包含源的URI信息,用来给响应消息直接和源建立连接用。在GB28181协议中为SIP设备编码@源IP地址端口。 - 第8行为Max-Forwards头,Max-Forwards头用于设置包最大中转次数,默认是70。 - 第9行为User-Agent头,User-Agent头用于设置关于UA的信息,用户可以自定义。 - 第10行为Expires头,Expires头表示超时时间。 值=0表示注销。 - 第11行为Content-Length头,Content-Length头表示消息体的长度,因为REGISTER消息不需要消息体,因此为0。如果携带了xml或者sdp等消息体,则>0。 - **在第一行比如REGISTER sip后面,以及from和to头,@符号后面部分的内容,海康是填充的域编号,而大华填充的是IP地址和端口,国标协议没有明确规定填啥。** ### 6.1 设备注册 ```cpp //注册请求:设备端-》服务端 REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1945388331 From: ;tag=33226951 To: Call-ID: 1455222403 CSeq: 1 REGISTER Contact: Max-Forwards: 70 User-Agent: IP Camera Expires: 3600 Content-Length: 0 //注册应答:服务端-》设备端 SIP/2.0 401 Unauthorized Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1945388331 From: ;tag=33226951 To: Call-ID: 1455222403 CSeq: 1 REGISTER User-Agent: wx_feiyangqingyun Date: 2025-04-22T10:48:38.014 Content-Length: 0 WWW-Authenticate: Digest realm="3402000000", algorithm=md5, nonce="9485904448344456af5449b51759016e" //鉴权注册:设备端-》服务端 REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1494593151 From: ;tag=33226951 To: Call-ID: 1455222403 CSeq: 2 REGISTER Contact: Authorization: Digest username="34020000001320000088", realm="3402000000", nonce="9485904448344456af5449b51759016e", uri="sip:34020000002000000001@3402000000", response="b1cfb3f036fbcf7d13b8ee3bbff89906", algorithm=MD5 Max-Forwards: 70 User-Agent: IP Camera Expires: 3600 Content-Length: 0 //注册成功:服务端-》设备端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1494593151 From: ;tag=33226951 To: Call-ID: 1455222403 CSeq: 2 REGISTER User-Agent: wx_feiyangqingyun Date: 2025-04-22T10:48:38.234 Content-Length: 0 //注册重定向:服务端-》设备端 SIP/2.0 302 Moved Temporarily Via: SIP/2.0/TCP 192.168.0.65:49983;rport;branch=z9hG4bK1260508101 From: ;tag=1486467600 To: ;tag=719011525 Call-ID: 719011525 CSeq: 2 REGISTER User-Agent: wx_feiyangqingyun Date: 2025-08-19T16:08:50.501 Content-Length: 0 Expires: 3600 //下面表示重定向到192.168.0.110服务器/端口15066 Contact: ``` ### 6.2 设备注销 ```cpp //注销请求:设备端-》服务端 REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK473598701 From: ;tag=1501355611 To: Call-ID: 1128656905 CSeq: 3 REGISTER Contact: Max-Forwards: 70 User-Agent: IP Camera Expires: 0 Content-Length: 0 //注销应答:服务端-》设备端 SIP/2.0 401 Unauthorized Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK473598701 From: ;tag=1501355611 To: Call-ID: 1128656905 CSeq: 3 REGISTER User-Agent: wx_feiyangqingyun Date: 2025-04-22T10:44:41.661 Content-Length: 0 WWW-Authenticate: Digest realm="3402000000", algorithm=md5, nonce="6ebe0922efda4939ba77fb41ef50e005" //鉴权注销:设备端-》服务端 REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK2110923860 From: ;tag=1501355611 To: Call-ID: 1128656905 CSeq: 4 REGISTER Contact: Authorization: Digest username="34020000001320000088", realm="3402000000", nonce="6ebe0922efda4939ba77fb41ef50e005", uri="sip:34020000002000000001@3402000000", response="fed25d2e352370cc22bab577ac6e0d93", algorithm=MD5 Max-Forwards: 70 User-Agent: IP Camera Expires: 0 Content-Length: 0 //注销成功:服务端-》设备端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK2110923860 From: ;tag=1501355611 To: Call-ID: 1128656905 CSeq: 4 REGISTER User-Agent: wx_feiyangqingyun Date: 2025-04-22T10:44:41.768 Content-Length: 0 ``` ### 6.3 心跳保活 ```cpp //发送心跳:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK927308490 From: ;tag=287801969 To: Call-ID: 594186441 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 176 Keepalive 9 34020000001320000002 OK //心跳应答:服务端-》设备端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK2112299769 From: ;tag=1273238268 To: Call-ID: 2060526849 CSeq: 20 MESSAGE User-Agent: wx_feiyangqingyun Date: 2025-04-22T10:46:37.265 Content-Length: 0 ``` ### 6.4 信息查询 ```cpp //获取设备信息:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK9225da1aa0084d469ebc6f6694bf6dc8 From: ;tag=1019 To: Call-ID: 35d87369b8b34488867dfbfbea2e91c2 CSeq: 20 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 154 Content-Type: application/MANSCDP+xml Contact: DeviceInfo 10019 34020000001320000002 //设备信息返回:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK2061567590 From: ;tag=415404928 To: Call-ID: 541248116 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 324 DeviceInfo 10019 34020000001320000002 OK IP CAMERA Hikvision DS-2CD5024EFWD V5.5.85 1 //获取设备状态:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK326223905abd4c258ab82e726ef93616 From: ;tag=1018 To: Call-ID: f69573838e564a4c8762a3465544037b CSeq: 19 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 156 Content-Type: application/MANSCDP+xml Contact: DeviceStatus 10018 34020000001320000002 //设备状态返回:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1675009157 From: ;tag=2077654677 To: Call-ID: 1002341894 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 427 DeviceStatus 10018 34020000001320000002 OK ONLINE OK 2025-04-22T11:14:23 34020000001340000001 ONDUTY ON ON //获取设备参数:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK8fdd12678d10455faf6141abf099c783 From: ;tag=1020 To: Call-ID: 207ffb519158414a926275c01512ee0f CSeq: 21 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 197 Content-Type: application/MANSCDP+xml Contact: ConfigDownload 10020 34020000001320000002 BasicParam //设备参数返回:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK626480488 From: ;tag=1196765638 To: Call-ID: 1421185166 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 608 ConfigDownload 10020 34020000001320000002 OK IP CAMERA 34020000001320000002 34020000002000000001 192.168.0.110 15060 3402000000 3600 12345678 30 3 0 //获取通道信息:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bKca6cdd79b54b4a898a08a027df9ee74f From: ;tag=1021 To: Call-ID: 73a10bfd47944564aebc9f383d8d5dbc CSeq: 22 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 151 Content-Type: application/MANSCDP+xml Contact: Catalog 10021 34020000001320000002 //通道信息返回:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK759035473 From: ;tag=417631013 To: Call-ID: 438658409 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 963 Catalog 10021 34020000001320000002 2 34020000001310000001 hikangname Hikvision IP Camera Owner 3402000000
Address
0 34020000002000000001 0 1 0 ON
34020000001340000001 Hikvision AlarmIn Owner 3402000000
Address
0 34020000001320000002 0 1 0 ON
``` ### 6.5 云台控制 ```cpp //云台控制:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK801c1bb8b2234e2e94be74f0087e28f5 From: ;tag=1022 To: Call-ID: 6822f48163704effb5b58d7031eba9f4 CSeq: 23 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 251 Content-Type: application/MANSCDP+xml Contact: DeviceControl 10022 34020000001310000001 A50F01029600004D 5 //控制应答:设备端-》服务端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK801c1bb8b2234e2e94be74f0087e28f5 From: ;tag=1022 To: ;tag=1060605518 Call-ID: 6822f48163704effb5b58d7031eba9f4 CSeq: 23 MESSAGE User-Agent: IP Camera Content-Length: 0 //停止云台:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK3cb5f960d94549888269c12f4ed29c9e From: ;tag=1023 To: Call-ID: f3124084767d48909decda7579e3f96b CSeq: 24 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 251 Content-Type: application/MANSCDP+xml Contact: DeviceControl 10023 34020000001310000001 A50F0100000000B5 5 //停止应答:设备端-》服务端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK3cb5f960d94549888269c12f4ed29c9e From: ;tag=1023 To: ;tag=1071996623 Call-ID: f3124084767d48909decda7579e3f96b CSeq: 24 MESSAGE User-Agent: IP Camera Content-Length: 0 ``` ### 6.6 预置位置 ```cpp //查询预置位:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK5f11d64dadd7466cb06011e8971b788d From: ;tag=1024 To: Call-ID: aac3afc576894717a72ed58030479bcb CSeq: 25 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 155 Content-Type: application/MANSCDP+xml Contact: PresetQuery 10024 34020000001310000001 //返回预置位:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK1149716366 From: ;tag=2001348774 To: Call-ID: 360379917 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 258 PresetQuery 10024 34020000001310000001 1 预置点 1 //添加预置位:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK76caaff468eb43ca9818e6fb38cfd057 From: ;tag=1025 To: Call-ID: a01a7be3ba6b4214ac0755d92d0096c5 CSeq: 26 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 251 Content-Type: application/MANSCDP+xml Contact: DeviceControl 10025 34020000001310000001 A50F018100020038 5 //删除预置位:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK9259fa44edce4797a69c4c58aa408d2c From: ;tag=1027 To: Call-ID: 1bc5c4f7629b436ca66865c112ba2d47 CSeq: 28 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 251 Content-Type: application/MANSCDP+xml Contact: DeviceControl 10027 34020000001310000001 A50F01830002003A 5 //修改预置位:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bKb968612c929f488bb3f105b907a61eba From: ;tag=1031 To: Call-ID: 37bfa9cea6564707af579159d662db4a CSeq: 32 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 251 Content-Type: application/MANSCDP+xml Contact: DeviceControl 10031 34020000001310000001 A50F018100020038 5 //调用预置位:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK3a2b16dffa134a25ad7158ef07ad8b07 From: ;tag=1032 To: Call-ID: 22431fe05dec47cbb789617b38caef58 CSeq: 33 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 251 Content-Type: application/MANSCDP+xml Contact: DeviceControl 10032 34020000001310000001 A50F018200020039 5 ``` ### 6.7 警情订阅 ```cpp //警情订阅:服务端-》设备端 SUBSCRIBE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bKb7c9f520b8104e56942cb6083322c99a From: ;tag=1033 To: Call-ID: 715f605e3307409d8fb3a8617e04a0e5 CSeq: 34 SUBSCRIBE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 407 Content-Type: application/MANSCDP+xml Contact: Expires: 90 Event: presence Alarm 10033 34020000001310000001 0 0 0 all 2025-04-22T00:00:00 2025-04-22T23:59:59 //警情上报:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK797206218 From: ;tag=1532296992 To: Call-ID: 1650529689 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 283 Alarm 99 34020000001310000001 4 5 2025-04-22T11:26:02 2 ``` ### 6.8 视频点播 ```cpp //点播请求:服务端-》设备端 INVITE sip:34020000001310000001@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK4de987b1737d494d9c63bb1464c9cdcb From: ;tag=1034 To: Call-ID: 445efb2b5134487788334cd6acafe0a2 CSeq: 35 INVITE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 323 Content-Type: application/sdp Contact: Subject: 34020000001310000001:0000010000,34020000002000000001:0 v=0 o=34020000001310000001 0 0 IN IP4 192.168.0.110 s=Play u=34020000001310000001:0 c=IN IP4 192.168.0.110 t=0 0 m=video 8888 TCP/RTP/AVP 96 97 98 a=recvonly a=setup:active a=connection:new a=rtpmap:96 PS/90000 a=rtpmap:97 MPEG4/90000 a=rtpmap:98 H264/90000 a=downloadspeed:0 a=streamprofile:0 y=0000010000 //请求应答:设备端-》服务端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK4de987b1737d494d9c63bb1464c9cdcb From: ;tag=1034 To: ;tag=1561889054 Call-ID: 445efb2b5134487788334cd6acafe0a2 CSeq: 35 INVITE Contact: Content-Type: application/sdp User-Agent: IP Camera Content-Length: 206 v=0 o=34020000001320000002 2851 2851 IN IP4 192.168.0.64 s=Play c=IN IP4 192.168.0.64 t=0 0 m=video 15060 TCP/RTP/AVP 96 a=setup:passive a=sendonly a=rtpmap:96 PS/90000 a=filesize:0 y=0000010000 //开始点播:服务端-》设备端 ACK sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK4de987b1737d494d9c63bb1464c9cdcb From: ;tag=1034 To: ;tag=1561889054 Call-ID: 445efb2b5134487788334cd6acafe0a2 CSeq: 35 ACK //结束点播:服务端-》设备端 BYE sip:34020000002000000001@192.168.0.110:15060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK7fe7407471734ba087eb4092db6ba108 From: ;tag=1019 To: ;tag=6d76ea76a4370853c9313f69995c7293 Call-ID: b5b5f90ba9b044868789d16fa3d0cc36 CSeq: 22 BYE Contact: ``` ### 6.9 录像查询 ```cpp //查询文件:服务端-》设备端 MESSAGE sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bKe9591d42a3cc4518a421b62aa668428d From: ;tag=1038 To: Call-ID: a6d47d255da543e79f484475b49340de CSeq: 41 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 262 Content-Type: application/MANSCDP+xml Contact: RecordInfo 10035 34020000001310000001 2025-04-18T00:00:00 2025-04-19T23:59:59 all //文件返回:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.64:5060;rport;branch=z9hG4bK727604973 From: ;tag=593479594 To: Call-ID: 40677081 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 3161 RecordInfo 10035 34020000001310000001 hikangname 134 34020000001310000001 hikangname file_path
Address 1
2025-04-18T10:23:02 2025-04-18T10:40:20 0 time 263094272
34020000001310000001 hikangname file_path
Address 1
2025-04-18T10:40:20 2025-04-18T10:57:36 0 time 262488064
``` ### 6.10 录像回放 ```cpp //点播请求:服务端-》设备端 INVITE sip:34020000001310000001@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK3d8b174429304a9d8af106f460debf9b From: ;tag=1039 To: Call-ID: 2b3bf36cf7164ea0aac06fbd3e24680f CSeq: 42 INVITE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 342 Content-Type: application/sdp Contact: Subject: 34020000001310000001:1000010003,34020000002000000001:0 v=0 o=34020000001310000001 0 0 IN IP4 192.168.0.110 s=Download u=34020000001310000001:0 c=IN IP4 192.168.0.110 t=1744946094 1744947129 m=video 6902 RTP/AVP 96 97 98 a=recvonly a=setup:passive a=connection:new a=rtpmap:96 PS/90000 a=rtpmap:97 MPEG4/90000 a=rtpmap:98 H264/90000 a=downloadspeed:8 a=streamprofile:1 y=1000010003 //请求应答:设备端-》服务端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK3d8b174429304a9d8af106f460debf9b From: ;tag=1039 To: ;tag=723451991 Call-ID: 2b3bf36cf7164ea0aac06fbd3e24680f CSeq: 42 INVITE Contact: Content-Type: application/sdp User-Agent: IP Camera Content-Length: 211 v=0 o=34020000001320000002 539 539 IN IP4 192.168.0.64 s=Download c=IN IP4 192.168.0.64 t=0 0 m=video 15060 RTP/AVP 96 a=setup:active a=sendonly a=rtpmap:96 PS/90000 a=filesize:262209536 y=1000010003 //开始点播:服务端-》设备端 ACK sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK3d8b174429304a9d8af106f460debf9b From: ;tag=1039 To: ;tag=723451991 Call-ID: 2b3bf36cf7164ea0aac06fbd3e24680f CSeq: 42 ACK //暂停播放:服务端-》设备端 INFO sip:34020000001310000001@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK28e2fc9dac8b421f9a8450a10933210d From: ;tag=1039 To: ;tag=723451991 Call-ID: 2b3bf36cf7164ea0aac06fbd3e24680f CSeq: 43 INFO User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 41 Content-Type: application/MANSRTSP Contact: PAUSE RTSP/1.0 CSeq: 1 PauseTime: now //继续播放:服务端-》设备端 INFO sip:34020000001310000001@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK86d002cd67004d0487ed0000b652c1bd From: ;tag=1039 To: ;tag=723451991 Call-ID: 2b3bf36cf7164ea0aac06fbd3e24680f CSeq: 45 INFO User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 41 Content-Type: application/MANSRTSP Contact: PLAY RTSP/1.0 CSeq: 3 Range: npt=now- //切换速度:服务端-》设备端 INFO sip:34020000001310000001@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bKd790bfabeea74caaba1594708fb91ada From: ;tag=1039 To: ;tag=723451991 Call-ID: 2b3bf36cf7164ea0aac06fbd3e24680f CSeq: 46 INFO User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 34 Content-Type: application/MANSRTSP Contact: PLAY RTSP/1.0 CSeq: 4 Scale: 4 //切换进度:服务端-》设备端 INFO sip:34020000001310000001@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bKeb9b6621c9e446c9912f48127ecf0932 From: ;tag=1039 To: ;tag=723451991 Call-ID: 2b3bf36cf7164ea0aac06fbd3e24680f CSeq: 47 INFO User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 41 Content-Type: application/MANSRTSP Contact: PLAY RTSP/1.0 CSeq: 5 Range: npt=379- ``` ### 6.11 文件下载 ```cpp //下载请求:服务端-》设备端 INVITE sip:34020000001310000001@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK6a208da569eb44028e0d159f9f245386 From: ;tag=1041 To: Call-ID: 3bd33cd0f76c40e8b0d702b4212c74e5 CSeq: 50 INVITE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 342 Content-Type: application/sdp Contact: Subject: 34020000001310000001:1000010004,34020000002000000001:0 v=0 o=34020000001310000001 0 0 IN IP4 192.168.0.110 s=Download u=34020000001310000001:0 c=IN IP4 192.168.0.110 t=1744856617 1744856620 m=video 6902 RTP/AVP 96 97 98 a=recvonly a=setup:passive a=connection:new a=rtpmap:96 PS/90000 a=rtpmap:97 MPEG4/90000 a=rtpmap:98 H264/90000 a=downloadspeed:8 a=streamprofile:1 y=1000010004 //请求应答:设备端-》服务端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK6a208da569eb44028e0d159f9f245386 From: ;tag=1041 To: ;tag=1160726752 Call-ID: 3bd33cd0f76c40e8b0d702b4212c74e5 CSeq: 50 INVITE Contact: Content-Type: application/sdp User-Agent: IP Camera Content-Length: 211 v=0 o=34020000001320000002 2589 2589 IN IP4 192.168.0.64 s=Download c=IN IP4 192.168.0.64 t=0 0 m=video 15060 RTP/AVP 96 a=setup:active a=sendonly a=rtpmap:96 PS/90000 a=filesize:1367160 y=1000010004 //开始下载:服务端-》设备端 ACK sip:34020000001320000002@192.168.0.64:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK6a208da569eb44028e0d159f9f245386 From: ;tag=1041 To: ;tag=1160726752 Call-ID: 3bd33cd0f76c40e8b0d702b4212c74e5 CSeq: 50 ACK ``` ### 6.12 语音对讲 ```cpp //语音广播:服务端-》设备端 MESSAGE sip:34020000001320000001@192.168.0.100:5061 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bK9085c93b7778426c805f0f000f31e2b6 From: ;tag=1017 To: Call-ID: 3a10b0f450f0499a95c61c3948522546 CSeq: 18 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 200 Content-Type: application/MANSCDP+xml Contact: Broadcast 10017 34020000002000000001 34020000001310000001 //广播应答:设备端-》服务端 MESSAGE sip:34020000002000000001@192.168.0.110:15060 SIP/2.0 Call-ID: e78acda839fea196d5a4867db0ffe585 Content-Length: 180 Content-Type: Application/MANSCDP+xml CSeq: 8 MESSAGE From: ;tag=49b96ab2783ae380cd1f39848c0cf0de Max-Forwards: 70 To: User-Agent: SIP UAS V.2016.xxxx Via: SIP/2.0/UDP 192.168.0.100:5061;rport;branch=z9hG4bK09c5c9872f1eaeeb2f753672bbcac574 Broadcast1001734020000001310000001OK //对讲请求:设备端-》服务端 INVITE sip:34020000002000000001@192.168.0.110:15060 SIP/2.0 Call-ID: fd514e34c2a7e40940437ac0272afd44 Contact: Content-Length: 240 Content-Type: application/sdp CSeq: 1 INVITE Expires: 120 From: ;tag=44088339578db934ef88a6a85534964b Max-Forwards: 70 Subject: 34020000002000000001:03d7a8ef3276a8ef327,34020000001310000001:0552354c54f3354c54f To: User-Agent: SIP UAS V.2016.xxxx Via: SIP/2.0/UDP 192.168.0.100:5061;rport;branch=z9hG4bK57f191fc39457eb7ed729b95c4f8a7bf v=0 o=34020000001320000001 0 0 IN IP4 192.168.0.100 s=Play i=VCam BroadCast Session c=IN IP4 192.168.0.100 t=0 0 m=audio 9776 RTP/AVP 8 96 a=recvonly a=rtpmap:8 PCMA/8000/1 a=rtpmap:96 PS/90000 y=0022440333 f=v/0/0/0/0/0a/1/8/1 //请求应答:服务端-》设备端 SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.0.100:5061;rport;branch=z9hG4bK57f191fc39457eb7ed729b95c4f8a7bf From: ;tag=44088339578db934ef88a6a85534964b To: ;tag=fd514e34c2a7e40940437ac0272afd44 Call-ID: fd514e34c2a7e40940437ac0272afd44 CSeq: 1 INVITE User-Agent: wx_feiyangqingyun Date: 2025-05-05T17:12:49.401 Content-Length: 166 Content-Type: application/sdp v=0 o=34020000002000000001 0 0 IN IP4 192.168.0.110 s=Play c=IN IP4 192.168.0.110 t=0 0 m=audio 15060 RTP/AVP 8 a=sendonly a=rtpmap:8 PCMA/8000 y=0022440333 //开始对讲:设备端-》服务端 ACK sip:34020000002000000001@192.168.0.110:15060 SIP/2.0 Call-ID: 2136e2263aa41b1c846a318586303f8e Contact: Content-Length: 0 CSeq: 1 ACK From: ;tag=9cc9d4a2600e325f8fd545d2bd753e84 Max-Forwards: 70 To: ;tag=2136e2263aa41b1c846a318586303f8e User-Agent: SIP UAS V.2016.xxxx Via: SIP/2.0/UDP 192.168.0.100:5061;rport;branch=z9hG4bKd8aa8116cdee11c14a382f1fa6e5693e //结束对讲:服务端-》设备端 BYE sip:34020000002000000001@192.168.0.110:15060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z3hG9bKd2ed4e36330b418d80fa0873c72732f9 From: ;tag=fd514e34c2a7e40940437ac0272afd44 To: ;tag=44088339578db934ef88a6a85534964b Call-ID: fd514e34c2a7e40940437ac0272afd44 CSeq: 19 BYE Contact: //下面是tcp方式的/在对应的sdp文件的m=audio行有区别 //对讲请求:设备端-》服务端 NVITE sip:34020000002000000001@192.168.0.110:15060 SIP/2.0 Via: SIP/2.0/TCP 192.168.0.65:57527;rport;branch=z9hG4bK1273429191 From: ;tag=1812579261 To: Call-ID: 696755539 CSeq: 20 INVITE Contact: Content-Type: application/sdp Max-Forwards: 70 User-Agent: IP Camera Subject: 34020000002000000001:1,34020000001320000099:2 Content-Length: 250 v=0 o=34020000001320000099 601 601 IN IP4 192.168.0.65 s=Play c=IN IP4 192.168.0.65 t=0 0 m=audio 15066 TCP/RTP/AVP 8 96 a=setup:active a=connection:new a=recvonly a=rtpmap:8 PCMA/8000 a=rtpmap:96 PS/90000 y=0200000017 f=v/////a/1/8/1 //对讲应答:服务端-》设备端 SIP/2.0 200 OK Via: SIP/2.0/TCP 192.168.0.65:57527;rport=57527;received=192.168.0.65;branch=z9hG4bK1273429191 From: ;tag=1812579261 To: ;tag=864237224 CSeq: 20 INVITE Call-ID: 696755539 Contact: Content-Length: 226 Content-Type: APPLICATION/SDP v=0 o=34020000002000000001 601 601 IN IP4 192.168.0.110 s=Play c=IN IP4 192.168.0.110 t=0 0 m=audio 30004 TCP/RTP/AVP 8 a=sendonly a=rtpmap:8 PCMA/8000 a=setup:passive a=connection:new y=0200000017 f=v/////a/1/8/1 ``` ### 6.13 图像抓拍 ```cpp //请求抓拍:服务端-》设备端 MESSAGE sip:34020000001320000001@192.168.0.65:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.110:15060;branch=z9hG4bKf19df7a91ae147e9a6097361dfb4878a From: ;tag=1003 To: Call-ID: 90ba9bf6b96f429d879c9584c3795cca CSeq: 4 MESSAGE User-Agent: wx_feiyangqingyun Max-Forwards: 70 Content-Length: 375 Content-Type: application/MANSCDP+xml Contact: DeviceConfig 10003 34020000001310000001 1 1 http://192.168.0.110:9999/snap 3cffba1f5df14a47acb6594b2375c5c7 //请求应答:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.65:5060;rport;branch=z9hG4bK113097658 From: ;tag=2122550998 To: Call-ID: 1066186907 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 173 DeviceConfig 10003 34020000001310000001 OK //抓拍结束:设备端-》服务端 MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.65:5060;rport;branch=z9hG4bK1404287512 From: ;tag=1058173213 To: Call-ID: 21190318 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 321 UploadSnapShotFinished 10003 34020000001310000001 3cffba1f5df14a47acb6594b2375c5c7 34020000001310000001022025081815045400000 ``` ## 7 其他说明 ### 7.1 地址说明 1. 在系统设置中,有两个填IP地址的地方,一个是外网地址,一个是网卡地址。 2. 网卡地址用到的地方有,28181服务监听,包括tcp和udp两种方式,还一个是抓图服务监听。 3. 外网地址用在很多地方,几乎所有的sip交互的指令,里面带的都是外网地址。 4. 理论上来说,在局域网内,网卡地址和外网地址都是相同的。 ### 7.2 新增协议 **gb28181-2022主要新增了如下几个协议:** 1. 注册重定向,有些大的平台,下级域可能几万个设备同时连接,会导致并发不够,所以需要有个注册重定向机制,主动告知下级域重新注册到指定的上级域服务器,也就是相当于有个专门的重定向注册服务程序,专门分配注册连接。如果离线后,又需要先注册到重定向服务器,重新分配。 2. 图像抓拍,支持最多抓拍10张图片,可以设置抓拍间隔,最小1s。有些场景尤其是4G摄像头,为了省流量,平时就只需要抓拍报警图像到平台即可,平时不点播视频,视频太费流量。还有就是本来一些场景需要抓拍报警图片专门存储,之前2016版本只能去点播视频抓图,整个过程很繁琐很慢,使用非常鸡肋,而且费宽带。 3. 云台绝对控制,也叫精准控制,之前云台只能连续控制,也是最常用的方式,有些场景需要精准定位控制云台。并且云台位置变化会主动推送到服务器。 4. 增加了看守位的查询HomePositionQuery、巡航轨迹列表查询CruiseTrackListQuery、巡航轨迹查询CruiseTrackQuery、目标跟踪控制命令TargetTrack。 5. 增加了存储卡状态查询及应答命令,设备升级命令。这两个用的少一些。 ### 7.3 个人理解 1. gb2818协议是基于sip协议的一套协议框架,而sip是和http协议类似的一套基于udp/tcp载体的协议,最终底层通过udp/tcp收发数据。sip是一套多媒体通信框架,而gb28181就是在这套框架上,定义了具体的通信内容,也就是收发数据填充的内容。 2. sip协议收发通常用的第三方开源库有osip/exosip,exosip依赖osip,是对osip的二次封装,带了具体传输层,osip相当于用来构建要收发的数据,本身没有收发功能。还有个更强大开源库pjsip,带了rtp解析。具体可参考 https://blog.csdn.net/weixin_43147845/article/details/144219082 3. sip协议说复杂也复杂,说简单也简单,复杂就是涉及到的具体协议规约特别庞大,和http协议是并列的一种机制。简单就是如果只是少量的通信,可以直接用udp/tcp通信收发解析,要发送的数据每一行都回车换行,最后一起发出去即可。就是发送和解析不大方便,需要去寻找和取出关键字再处理,而开源库会给你处理好对应的数据结构,比如解析后有枚举字段直接判断。 4. onvif是国际标准协议,gb28181是国家标准协议,各有优缺点,onvif通常是通过搜索到设备再去和设备通信,而gb28181刚好相反,是让设备主动连服务器,带上校验等参数,连上后,服务器再去和设备通信。这样相当于可以跨网了,而onvif通常只能局域网。gb28181还有个优势就是组网,可以层层级联,非常适合国内各种大监控系统的建设。 5. onvif客户端是先udp组播获取到设备,然后再发送http请求到不同的地址来交互,请求中可以获取到视频流rtsp地址,然后自己用ffmpeg等框架打开这个地址播放就行。而gb28181服务端是先udp/tcp监听端口,对连上的设备进行sip协议格式的数据内容的交互,发送请求播放指令后,开启发送rtp数据包,再用ffmpeg等框架解析这个数据包就行。 6. gb28181注册的时候Expires为有效期时间,一般是3600秒,如果是0则表示注销。同理订阅也是这个机制,订阅下发的时候有个有效期,如果有效期0就表示取消订阅。 ### 7.4 要点总结 1. gb28181协议一般会选择udp通信,默认也是udp,早期国标设备都是只支持udp。 2. 服务端开启端口监听,设备端填写好对应参数后,会尝试往对应端口发数据进行连接。 3. 设备端间隔(心跳间隔默认是60s)发送REGISTER信令,服务端收到后,分析数据中是否带了鉴权信息(也就是用户认证相关信息),没有带的话则应答Unauthorized,带了的话,可以取出认证的信息,和要求的参数对比,比如国标服务端编号、认证密码、域编码信息,不一致则应答信息错误,叫客户端重新发。都没问题则表示认证通过。 4. 认证后服务端发送MESSAGE信令,带上xml数据,获取设备信息,收到设备信息后,再去查阅目录也就是获取通道列表。 5. 设备端在注册成功后,每隔一段时间发送一次心跳信息。方便服务端判断是否离线。 6. 设备端一旦注册成功,在有效期(一般是3600s)内,不会再去注册,默认就是认为已经注册成功。所以有时候服务端这边开启服务后,未必先收到REGISTER注册指令,而是可能先收到的心跳指令,所以服务端这边要做个特殊处理,收到心跳后,先判断该设备在系统中是否已经存在,不存在则先获取设备信息,再去获取通信列表。 7. 服务端支持多个设备注册,通过设备编号区分,严格要求同一个系统中设备编号不能重复,否则容易错乱。 8. 每个设备都可以有多个视频通道,一般摄像头IPC只有1个通道,录像机NVR有多个通道。如果是国标级联,相当于把服务端当做一台NVR设备,每个设备的通道都转换成唯一标识的通道。 9. 点播视频和云台控制等,都有个前提是要先获取到对应的通道列表,因为下发的数据中就要指定是哪个通道。 10. 点播视频是服务端向设备端通过发送INVITE信令,带上sdp数据(具体sdp格式规范在gb28181-2016文档的第100页),sdp数据中包含了通道编号、音视频格式、音视频数据如何交互等信息。 11. 服务端点播视频前,先要打开一个空闲的端口,这个端口号在sdp数据中带上,设备端收到点播指令后,会将音视频数据发给这个指定的端口,收到这些数据后再去用ffmpeg解码播放即可。sip这边只负责信令交互,并不负责音视频数据的通信。 12. 关闭视频很关键,因为可能开了多个点播窗口,所以需要在点播视频后应答的ACK指令数据中,记住当时信令中的from/to/callid数据,在关闭视频的时候用播放时对应的这几个数据发给设备端,才能真正停止。 13. 一个设备可能有多个通道,一个通道可能存在多个点播流,每个流都对应唯一的端口,所以需要有个队列记住这些点播流对应的ssrc/from/to/callid数据,可以指定关闭某一路流。 14. 点播流需要对应端口接收流,一般这个端口需要动态分配,也可以不同流公用一个端口,公用端口不用担心数据会冲突,里面都是rtp的数据包,通过ssrc区分是哪一个流的数据包,这个ssrc是由点播发起者下发的,在sip指令中附加在subject属性上,sdp中有个y属性专门放这个ssrc字符串。在端口数量允许的情况下,一般建议每一路流都不同的端口,方便区分管理。 15. 点播流的过程,一般第一步是先打开监听端口成功后,然后才将这个端口通过sip指令发给设备,因为端口有可能被占用,所以只有当打开监听端口成功的时候,再去点播流,这样才是通的,不然也是白搭。 16. 语音对讲和点播视频流程不一样,是反着来的,先服务端发送语音广播指令Broadcast到设备,设备返回是否支持语音对讲,如果支持,会主动发送INVITE信令,带上sdp数据,服务端搜到这个sdp数据后解析,然后服务端主动往设备对应的端口发送带了语音数据的RTP数据包,设备端的声音会通过之前的音视频流传输过来。 17. 云台控制和预置位相关处理就简单一些,因为都是单向操作。通过MESSAGE信令带上xml数据,数据中包含了要执行的通道编号和动作,这个动作的数据,是一个标准的固定长度8字节,16进制字符串数据格式,比如A5 0F 01 00 00 00 00 00,将要执行的动作替换对应的数据位即可,停止云台也是一个单独的动作。 18. **ffmpeg中并不能直接解码RTP数据包,需要解包后才是PS流才可以正确的解码,一般会用第三方开源库jrtp去实现解包,当然他也支持封包,发送语音数据的时候也要用到,jrtp直接就是带了网络通信,比如监听UDP端口收数据。** 19. 在信令交互过程中,可以多一些无关的数据,但是不能少一些必要的字段数据,比如invite信令必须带有Subject,缺少的话无法正常解析导致失败。 20. 拉流分三种模式,udp、tcp被动、tcp主动。udp是指服务端这边监听udp端口,等待设备端主动发数据;tcp被动是指服务端这边监听tcp端口,等待设备端主动连接和发数据;tcp主动是指服务端主动连接设备端,连接成功后设备端主动发数据。一般内网建议用tcp被动模式,比如公安内网一般会采用这个模式;外网如果要求实时性高于清晰度,则用udp模式,如果要求稳定性不丢包则用tcp被动模式;理论上外网也就是公网上,tcp主动模式几乎用不,因为服务端无法正常连接到设备端,除非做了端口映射。 21. 拉流中的udp和tcp被动模式,需要考虑一个细节,那就是端口占用问题,必须先监听端口成功后(不成功则端口递增直到成功),再把对应端口通过sip协议发给设备端,而不是先发端口再去监听端口。这是很多国标系统忽略的一个细节,一旦端口占用直接歇菜。 22. 有些设备对应TCP主动方式拉流会校验端口号,所以建议在连接的时候,对应socket要绑定地址和端口,如果没有绑定,则设备端收到这个连接后,会主动断开导致无法正常收流。 23. 有些运营商可能会显示TCP流量,在4G设备的场景下,UDP正常而TCP不正常的情况下,如果运行一段时间断流,需要考虑这个问题,可以换个取流方式或者换个流量卡供应商。 wvp-pro官方文档:https://doc.wvp-pro.cn/