摘要

在流媒体技术的发展中,视频技术的发展越来越迅速,大量应用于安防与教育事业,比如在远程视频教育、在线远程视频会议、医疗、各种场所的视频监控以及危险区域的探测等方面。Linux操作系统在发展的过程具备了很好的网络性能。Linux系统下提供了非常丰富的开源软件和视频支持,支持各种硬件平台,因此 Linux平台成为了视频采集的最佳平台。Linux系统上的软件开发具有高可靠性、低成本、高度自动化、高可配置性、及低耗费等一些优点,同时能够很好的完成多任务和实时性的设计需求。
我国资深嵌入式系统专家——沈绪榜院士曾预言:“未来十年将会产生针头大小,具有超过一亿次运算能力的嵌入式智能芯片”。这些将为我们的开发提供了相当大的创造空间。中国应当注意发展这一智力密集型产业”。
综上所述,嵌入式Linux操作系统在广泛和深入的应用于各个领域,应用的技术也越来越复杂。针对基于网络传输的视频监控系统的实际需求与应用,结合视频的图像采集技术与编码技术,嵌入式Linux操作系统和网络编程技术等多方面的新兴技术,设计的一套Linux 网络视频监控系统,用来进行视频数据的采集、视频数据的压缩编码与解码以及视频的网络传输,该系统基于Linux 操作系统进行开发的,从OV5640捕捉视频数据,QT实现客户端和服务器端,即可查看远程视频影像。
关键词OV5640、网络传输、QT实现、远程视频影像

一、目的及意义

视频监控系统的设计主要说明视频监控系统的详细设计与实现。介绍了系统中各个模块的设计与模块功能的实现以及应用的相关技术。我将会对每一个模块的设计以及实现方法给出简单的文字说明以及设计的流程图。
课程设计的主要目的是学习熟悉基于TCP/IP网络的远程视频监控系统的基本知识和相关的多媒体开发技术。随着5G通讯时代的到来,我们更可以将视频监控客户端发展到移动终端,这将使我们更加方便。
纵观视频监控的研究进展,从闭路电视系统构建的模拟系统、经历了数字信号控制的模拟视频监控系统、过渡到数字硬盘录像设备为核心的视频监控系统和当代的数字网络视频监控系统这几大重要阶段。
数字网络视频监控系统与其它监控系统的优点:
a、布控区域广阔
数字网络视频监控系统是可以超越地域的限制的,可以延伸整个需要布控区域,这是因为它将网络视频信号采集终端直接连入网络,并且能够解除信号的衰减和缆线长度的局限性,而且网络是不受距离长短的影响。
b、系统具有几乎无限的无缝扩展能力
需要增加设备时只要将IP地址进行扩充操作就行了,因为所有的IP地址都在系统中进行了标识。
c、可组成非常复杂的监控网络
监控系统是以基于网络视频信号采集终端为核心,在组网方式上与基于PC平台的监控和传统的模拟监控方式有了质的飞跃,视频信号采集终端输出已完成了从模拟到数字的转换并压缩,在网络上传输时采用统一的协议,能够实现跨路由器、跨网关等的远程视频传输。
d、性能稳定可靠,无需专人管理
视频监控系统中采集视频信号的终端应用了嵌入式Linux技术,主要采用嵌入式多任务实时操作系统,又能够将网络功能和视频压缩功能集中到一个极小体积的设备内,同时可以直接连入广域网络或者局域网络,即插即看,系统的稳定性、可靠性、实时性得到了极大的提升,能够运用于无人值守的环境中。
e、我们可以从视频监控中心同时观看多个USB 网络摄像头视频,但是对网络带宽是有一定的要求。

二、视频监控系统的组成

1、总体设计

视频监控系统大致可分为三大模块:视频数据信号的采集部分、网络传输部分以及远程客户端视频的渲染部分。
总体框图如图所示:
总体框图

2、功能分析

服务器(server)端负责视频数据的采集,采集可以通过v4l2编程实现,有详细的API开发文档,以及v4l2编程的相关资料可以参考。
客户端(client)主要负责的是将接收到的视频数据通过QT编程将其渲染出来。主要用到了QT中的信号(signal)和槽(slot)机制、以及绘图事件,QT是利用C++编程,而且它封装了一套自己的库可以实现跨平台,这也就解决了前面提到的客户端的跨平台特性。
还有连接Server 与Client的就是采用Socket网络服务器的搭建,这主要采用的是Linux环境网络编程。该网络的搭建主要是利用基于TCP/IP协议的HTTP协议,将服务器( server)端采集到的视频数据封装成http 数据帧的形式,然后利用TCP 协议将视频数据帧发送到客户端(client)。由于是实时传输,所以还用到多线程,使得每个客户端(client)相互独立,各个线程都将执行一个死循环,源源不断的给客户端发送视频数据。

3、何采集视频数据逻辑概要

这一节主要介绍了服务器(server)端的如何采集视频数据。
如何采集视频数据
UDP 或者 TCP 传输,使用 Qt 封装好 TCP/IP 协议 Socket 抽象层接口来通信。那么我们也可以 使用它们来发送图像数据啊。这样,服务器和客户端我们完全可以使用 Qt 来写了。实际上 RTSP 协议和 RTMP 协议都使用 TCP/IP 协议,在传输层使用了 UDP 或 TCP。
项目流程如下:
(1) 服务器采集摄像头的数据。
(2) 处理视频数据转交给 Socket,由 TCP/UDP 传输。
(3) 客户端接收视频数据。
采集视频数据并发送

三、视频监控系统的组成

1.系统测试过程

视频监控系统的测试将分别对服务端和客户端进行,我们可以下执行步骤。 1)连接好OV5640摄像头,在 Linux终端运行服务端程序,获取摄像头设备的数据,如果摄像头有不匹配或者连接问题,则会报错。
2)如上一步正常则运行客户端程序,也就是采用QT设计的图形化界面,其实在没有开启服务端程序时,也可以开启客户端,但是不能获取到视频数据。
3)在客户端输入服务端的IP地址(如本地摄像头IP为127.0.0.1),设置想要查看的窗口,按照自己的要求进行监控,通过服务端可以设计客户端相关信息在图形化界面上显示出来,以便用户查看。
4)通过客户端查看各个OV5640拍摄到的视频。
系统调试及运行的效果图下所示。
串口传输
串口传输
串口传输
Ov5640连接正常,通过串口进行程序的烧录
客户端:
客户端测试
客户端测试
客户端测试
服务端:
服务端测试
服务端测试
服务端测试
测试完毕,客户端和服务端功能均正常

四、总结

视频监控系统课题的所设计和实现中,充分利用了Linux操作系统作为嵌入式操作系统的诸多优势,嵌入式Linux系统下的视频网络监控系统是计算机软硬件、电工电子装置以及网络通信等多方面的有机结合而形成的,它主要以网络化、交互性、智能化为特征,结构较为复杂。如果采用OSI七层模型的内容和形式,把相应的控制模块硬件、视频数据采集、视频数据的编码、视频数据的解码和视频监控软件以及开发环境的搭建等进行有机结合,可以形成一个统一完整的系统框架。在 Linux操作系统中调用v4l2编程中的API接口和底层设备驱动程序来完成视频捕获操作。v4L2是Linux操作系统中的内核驱动,主要是关于视频的开发。它为Linux操作系统中的各种视频设备提供了统一的API编程接口,应用程序可以通过这些接口函数进行操纵各种不同的设备。结合了QT技术构建了具有图形化界面支持的嵌入式系统开发平台,这种系统结构模式是目前在 PC上进行视频监控系统开发的热点。
对未来视频监控的展望:未来的视频监控系统应该更加趋向智能化、人性化、同时能够自行的对异常情况分析判断。另外在不久的未来,视频监控系统还可具有安防互动功能,与各种传感器,控制器链接,实现自动目标跟踪等功能。而且随着5G通信技术的发展和成熟,视频监控系统的成本也应该进一步降低,并且能够将其移植到移动终端,以适应更广泛领域的需要。
由于我还是个初学者,虽然最终能够实现了一些功能,但是对中间相关的一些模块开发,比如驱动、QTSocket原理等等还不是很熟悉,还有待进一步学习和掌握。

附录:源程序代码

服务端:
video_server.pro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

#修改生成的应用程序的名字
TARGET = 视频监控服务端v0.1-by-JASand炳川
#指定生成的makefile的类型 lib
TEMPLATE = app

SOURCES += \
capture_thread.cpp \
main.cpp \
mainwindow.cpp

HEADERS += \
capture_thread.h \
mainwindow.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

Capture_thread.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#ifndef CAPTURE_THREAD_H
#define CAPTURE_THREAD_H

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#ifdef linux
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <linux/input.h>
#endif

#include <QThread>
#include <QDebug>
#include <QPushButton>
#include <QImage>
#include <QByteArray>
#include <QBuffer>
#include <QTime>
#include <QUdpSocket>

#define VIDEO_DEV "/dev/video1"//摄像头设备节点
#define FB_DEV "/dev/fb0" //LCD设备节点
#define VIDEO_BUFFER_COUNT 3//帧缓冲数量

struct buffer_info {
void *start;
unsigned int length;
};

class CaptureThread : public QThread
{
Q_OBJECT


signals:
/* 准备图片 */
void imageReady(QImage);
void sendImage(QImage);

private:
/* 线程开启flag */
bool startFlag = false;

/* 开启广播flag */
bool startBroadcast = false;

/* 本地显示flag */
bool startLocalDisplay = false;
void run() override;

public:
CaptureThread(QObject *parent = nullptr)
{
Q_UNUSED(parent);
}

public slots:
/*在这个区内声明的槽意味着任何对象都可将信号与之相连接。这对于组件编程非常有用,
* 你可以创建彼此互不了解的对象,将它们的信号与槽进行连接以便信息正确传递*/
/* 设置线程 */
void setThreadStart(bool start)
{
startFlag = start;
if (start) {
if (!this->isRunning())
this->start();
} else {
this->quit();
}
}

/* 设置广播 */
void setBroadcast(bool start) {
startBroadcast = start;
}

/* 设置本地显示 */
void setLocalDisplay(bool start) {
startLocalDisplay = start;
}
};

#endif // CAPTURE_THREAD_H

capture_thread.cpp:
#include "capture_thread.h"

void CaptureThread::run()
{
/* 下面的代码参考了正点原子C应用编程V4L2章节的摄像头编程*/
#ifdef linux
#ifndef __arm__

return;
#endif
int video_fd = -1;
struct v4l2_format fmt;//https://blog.csdn.net/xxxxxlllllxl/article/details/22074151
struct v4l2_requestbuffers req_bufs;
static struct v4l2_buffer buf;
int n_buf;
struct buffer_info bufs_info[VIDEO_BUFFER_COUNT];
enum v4l2_buf_type type;

//文件描述符:本质是一个正整数 open函数 0-OPEN-MAX
video_fd = open(VIDEO_DEV, O_RDWR);
if (0 > video_fd)
{
printf("ERROR: failed to open video device %s\n", VIDEO_DEV);
return ;
}

/* 设置帧格式 */
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type类型
fmt.fmt.pix.width = 640;//视频帧宽度
fmt.fmt.pix.height = 480;//视频帧高度
fmt.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;//像素格式

if (0 > ioctl(video_fd, VIDIOC_S_FMT, &fmt))
{
printf("ERROR: failed to VIDIOC_S_FMT\n");
close(video_fd);
return ;
}

/* 申请帧缓冲 */
req_bufs.count = VIDEO_BUFFER_COUNT;//帧缓冲的数量
req_bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_bufs.memory = V4L2_MEMORY_MMAP;

if (0 > ioctl(video_fd, VIDIOC_REQBUFS, &req_bufs))
{
printf("ERROR: failed to VIDIOC_REQBUFS\n");
return ;
}
/* 建立内存映射 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++)
{
buf.index = n_buf;
if (0 > ioctl(video_fd, VIDIOC_QUERYBUF, &buf))
{
printf("ERROR: failed to VIDIOC_QUERYBUF\n");
return ;
}
/*void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
在这个函数原型中:

参数addr:指定映射的起始地址,通常设为NULL,由内核来分配

参数length:代表将文件中映射到内存的部分的长度。

参数prot:映射区域的保护方式。可以为以下几种方式的组合:

PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能存取

参数flags:映射区的特性标志位,常用的两个选项是:

MAP_SHARD:写入映射区的数据会复制回文件,且运行其他映射文件的进程共享
MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制,对此区域的修改不会写会原文件

参数fd:要映射到内存中的文件描述符,有open函数打开文件时返回的值。

参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

函数返回值:实际分配的内存的起始地址。
*/
bufs_info[n_buf].length = buf.length;
bufs_info[n_buf].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED,
video_fd, buf.m.offset);
if (MAP_FAILED == bufs_info[n_buf].start)
{
printf("ERROR: failed to mmap video buffer, size 0x%x\n", buf.length);
return ;
}
}
/* 入队 */
for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++)
{
buf.index = n_buf;
if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf))
{
printf("ERROR: failed to VIDIOC_QBUF\n");
return ;
}
}

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 > ioctl(video_fd, VIDIOC_STREAMON, &type))
{
printf("ERROR: failed to VIDIOC_STREAMON\n");
return ;
}

while (startFlag)
{
for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++)
{
buf.index = n_buf;
if (0 > ioctl(video_fd, VIDIOC_DQBUF, &buf))
{
printf("ERROR: failed to VIDIOC_DQBUF\n");
return;
}

QImage qImage((unsigned char*)bufs_info[n_buf].start, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16);

/* 是否开启本地显示,开启本地显示可能会导致开启广播卡顿,它们互相制约 */
if (startLocalDisplay)
emit imageReady(qImage);//信号释放

/* 是否开启广播,开启广播会导致本地显示卡顿,它们互相制约 */
if (startBroadcast)
{
/* udp套接字 */
QUdpSocket udpSocket;

/* QByteArray类型 */
QByteArray byte;

/* 建立一个用于IO读写的缓冲区 */
QBuffer buff(&byte);

/* image转为byte的类型,再存入buff */
qImage.save(&buff, "JPEG", -1);

/* 转换为base64Byte类型 */
QByteArray base64Byte = byte.toBase64();

/* 由udpSocket以广播的形式传输数据,端口号为8888 */
udpSocket.writeDatagram(base64Byte.data(), base64Byte.size(), QHostAddress::Broadcast, 8888);
}

if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf))
{
printf("ERROR: failed to VIDIOC_QBUF\n");
return;
}
}
}

msleep(800);//at lease 650
/*与mmap函数成对使用的是munmap函数,它是用来解除映射的函数,原型如下:

int munmap(void *start, size_t length)

参数start:映射的起始地址
参数length:文件中映射到内存的部分的长度*/
for (int i = 0; i < VIDEO_BUFFER_COUNT; i++)
{
munmap(bufs_info[i].start, buf.length);
}

close(video_fd);
#endif
}

Mainwindow.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName video_server
* @brief mainwindow.h
* @author Deng Zhimao & JAS
* @email dengzhimao@alientek.com $1175595406@qq.com
* @link www.openedv.com jasblog.top
* @date 2022-6-19
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QLabel>
#include <QImage>
#include <QPushButton>
#include <QHBoxLayout>
#include <QCheckBox>

#include "capture_thread.h"

class MainWindow : public QMainWindow
{
Q_OBJECT


public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private:
/* 用于显示捕获到的图像 */
QLabel *videoLabel;

/* 摄像头线程 */
CaptureThread *captureThread;

/* 开始捕获图像按钮 */
QPushButton *startCaptureButton;
QPushButton *startCaptureButton2;

/* 用于开启本地图像显示 */
QCheckBox *checkBox1;

/* 用于开启网络广播 */
QCheckBox *checkBox2;

/* 重写大小事件 */
void resizeEvent(QResizeEvent *event) override;

private slots: //在这个区内声明的槽意味着只有类自己可以将信号与之相连接。
/* 显示图像 */
void showImage(QImage);

/* 开始采集按钮被点击 */
void startCaptureButtonClicked(bool);
void startCaptureButton2Clicked(bool);
};
#endif // MAINWINDOW_H

Mainwindow.cpp:
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0, 0, 800, 480);//从屏幕上(0,0)位置开始(即为最左上角的点),显示一个800*480的界面。

videoLabel = new QLabel(this);
videoLabel->setText("未获取到图像数据或未开启服务端显示");
videoLabel->setStyleSheet("QWidget {color: white;}");
videoLabel->setAlignment(Qt::AlignCenter);//setAlignment()主要将是消除布局中的空隙,让两个控件紧紧挨在一起。
videoLabel->resize(640, 480);

checkBox1 = new QCheckBox(this);
checkBox2 = new QCheckBox(this);
checkBox1->resize(150, 50);
checkBox2->resize(150, 50);
checkBox1->setText("服务端显示");
checkBox2->setText("开启广播");

//Style Sheets是文字性的设定,对于整个应用程序可以使用QApplication::setStyleSheet() 或者对应一个窗口可以使用QWidget::setStyleSheet()
checkBox1->setStyleSheet("QCheckBox {color: red;}"
"QCheckBox:indicator {width: 40; height: 40;}");
checkBox2->setStyleSheet("QCheckBox {color: red;}"
"QCheckBox:indicator {width: 40; height: 40;}");

/* 按钮 */
startCaptureButton = new QPushButton(this);
startCaptureButton->setCheckable(true);
startCaptureButton->setText("开始采集摄像头数据");

startCaptureButton2 = new QPushButton(this);
startCaptureButton2->setCheckable(true);
startCaptureButton2->setText("防伪按钮");

/* 设置背景颜色为黑色 */
QColor color = QColor(Qt::black);
QPalette p;
p.setColor(QPalette::Window, color);
this->setPalette(p);

/* 样式表 */
startCaptureButton->setStyleSheet("QPushButton {background-color: white; border-radius: 30}"
"QPushButton:pressed {background-color: red;}");

captureThread = new CaptureThread(this);

/*信号与槽机制是QT非常核心的东西,通过信号与槽将不同的部分结合起来,信号与槽像是设计模式中的观察者模式(我自己觉得是这样),
* 只关心信号何时发来,以及做如何相应,组件之间可以异步或者同步去处理事情。
* 信号(Signal):就是在特定情况下被发射的事件,例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号,
* 一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号。GUI 程序设计的主要内容就是对界面上各组件的信号的响应,
* 只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了。
* 槽(Slot):就是对信号响应的函数。
* 槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),
* 可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:
* 槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。 */

connect(startCaptureButton, SIGNAL(clicked(bool)), captureThread, SLOT(setThreadStart(bool)));
connect(startCaptureButton, SIGNAL(clicked(bool)), this, SLOT(startCaptureButtonClicked(bool)));
connect(startCaptureButton2, SIGNAL(clicked(bool)), this, SLOT(startCaptureButton2Clicked(bool)));
connect(captureThread, SIGNAL(imageReady(QImage)), this, SLOT(showImage(QImage)));
connect(checkBox1, SIGNAL(clicked(bool)), captureThread, SLOT(setLocalDisplay(bool)));
connect(checkBox2, SIGNAL(clicked(bool)), captureThread, SLOT(setBroadcast(bool)));

}

MainWindow::~MainWindow()
{

}

void MainWindow::showImage(QImage image)
{
videoLabel->setPixmap(QPixmap::fromImage(image));
}

void MainWindow::resizeEvent(QResizeEvent *event)//作为QT的事件函数在窗口控件大小获取,重置按钮位置用
{
Q_UNUSED(event)//用来避免编译器警告
startCaptureButton->move((this->width() - 200) / 2, this->height() - 80);
startCaptureButton->resize(200, 60);
startCaptureButton2->move(((this->width() - 200) / 2) -300, this->height()- 40);
startCaptureButton2->resize(100, 40);
videoLabel->move((this->width() - 640) / 2, (this->height() - 480) / 2);
checkBox1->move(this->width() - 130, this->height() / 2 + 145);
checkBox2->move(this->width() - 130, this->height() / 2 + 190);
}

void MainWindow::startCaptureButtonClicked(bool start)
{
if (start)
startCaptureButton->setText("停止采集摄像头数据");
else
startCaptureButton->setText("开始采集摄像头数据");
}
void MainWindow::startCaptureButton2Clicked(bool start)
{
if (start)
startCaptureButton2->setText("by JAS & LBC");
else
startCaptureButton2->setText("防伪按钮");
}

Main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "mainwindow.h"
#include <QApplication>

//程序入口
int main(int argc, char *argv[])
{
//每一个qt程序只有一个,应用程序类
QApplication a(argc, argv);
//窗口类,默认不显示
MainWindow w;
w.show();
return a.exec();//a.exec();实际上是死循环,所以main函数实际上还没结束
}

客户端:
Video_Client.pro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#qt程序用到的模块
QT += core gui network
#兼容以前的版本
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

#修改生成的应用程序的名字
TARGET = 视频监控客户端v0.1-byJAS-炳川
#指定生成的makefile的类型 lib
TEMPLATE = app

SOURCES += \
main.cpp \
mainwindow.cpp

HEADERS += \
mainwindow.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

Mainwindow.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName video_client
* @brief mainwindow.h
* @author Deng Zhimao & JAS
* @email dengzhimao@alientek.com $1175595406@qq.com
* @link www.openedv.com jasblog.top
* @date 2022-6-17
*******************************************************************/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QUdpSocket>
#include <QLabel>

class MainWindow : public QMainWindow
{
Q_OBJECT //如果要使用信号和槽,必须加这个宏

public:
MainWindow(QWidget *parent = nullptr);//构造与析构
~MainWindow();

private:
/* 用于接收数据 */
QUdpSocket *udpSocket;

/* 显示接收的图像数据 */
QLabel *videoLabel;

void resizeEvent(QResizeEvent *event) override;

private slots:
/* 图像更新 */
void videoUpdate();
};
#endif // MAINWINDOW_H

Mainwindow.cpp:
#include "mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置背景颜色为黑色 */
QColor color = QColor(Qt::black);
QPalette p;
p.setColor(QPalette::Window, color);
this->setPalette(p);

udpSocket = new QUdpSocket(this);
/* 绑定端口号 */
udpSocket->bind(QHostAddress::Any, 8888);

videoLabel = new QLabel(this);
videoLabel->resize(640, 480);
videoLabel->setText("未获取到图像数据");
videoLabel->setStyleSheet("QWidget {color: white;}");
videoLabel->setAlignment(Qt::AlignCenter);//函数setAlignment()主要将是消除布局中的空隙,让两个控件紧紧挨在一起。
connect(udpSocket, SIGNAL(readyRead()), this,SLOT(videoUpdate()));
this->setGeometry(0, 0, 800, 480);
}

MainWindow::~MainWindow()
{
}
void MainWindow::videoUpdate()
{
QByteArray datagram;

/* 数据大小重置 */
datagram.resize(udpSocket->pendingDatagramSize());

/* 数据存放到datagram中 */
udpSocket->readDatagram(datagram.data(), datagram.size());

QByteArray decryptedByte;
decryptedByte = QByteArray::fromBase64(datagram.data());

QImage image;
image.loadFromData(decryptedByte);

/* 显示图像 */
videoLabel->setPixmap(QPixmap::fromImage(image));
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
videoLabel->move((this->width() - 640) / 2, (this->height() - 480) / 2);
}

Main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "mainwindow.h"
#include <QApplication>

//程序入口
int main(int argc, char *argv[])
{
//每一个qt程序只有一个,应用程序类
QApplication a(argc, argv);
//窗口类,默认不显示
MainWindow w;
w.show();
return a.exec();//a.exec();}实际上是死循环,所以main函数实际上还没结束
}