image-20240801085121931

main.cpp

作为Qt的启动函数,通过创建XViewer对象。

然后调用show()函数在屏幕上进行显示。

接着调用exec()函数,启动Qt的循环事件,此时程序可以处理用户输入的一些请求。

1
2
3
4
5
6
7
8
9
10
int  main(int argc, char *argv[])
{
QApplication a(argc, argv);
XViewer w;
w.show();
auto re = a.exec();
// xr.Stop();
return re;
}

xviewer

xviewer.h

用于第一个Qt创建的对象,可以通过自定义函数进行一些关于界面事件的处理。

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
#pragma once

#include <QtWidgets/QWidget>
#include "ui_xviewer.h"
#include <QMenu>

class XViewer : public QWidget
{
// Q_OBJECT是Qt框架中的一个关键组件,用于Qt元对象系统。这个系统提供了一些强大的功能,如信号槽机制。
Q_OBJECT

public:
// QWidget *parent = nullptr 的作用是为构造函数提供一个默认参数,指定该窗口部件的父对象。
// 如果没有传递父对象,则默认值为 nullptr,表示这个部件没有父对象。
XViewer(QWidget *parent = nullptr);
~XViewer();

// 鼠标事件 用于拖动窗口
void mouseMoveEvent(QMouseEvent *ev) override;
void mousePressEvent(QMouseEvent *ev) override;
void mouseReleaseEvent(QMouseEvent *ev) override;

// 窗口大小发生变化
void resizeEvent(QResizeEvent *ev) override;

// 右键菜单
void contextMenuEvent(QContextMenuEvent *event) override;

// 预览视频窗口
void View(int count);

// 刷新左侧相机列表
void RefreshCams();

// 编辑摄像机
void SetCam(int index);

// 定时器渲染视频 回调函数 重载
void timerEvent(QTimerEvent *ev) override;

public slots:
void MaxWindow();
void NormalWindow();
void View1();
void View4();
void View9();
void View16();
void AddCam(); // 新增摄像机配置
void SetCam(); // 修改摄像机配置
void DelCam(); // 删除摄像机配置

void StartRecord(); // 开始全部摄像头录制
void StopRecord(); // 停止全部摄像头录制
void Preview(); // 预览界面显示
void Playback(); // 回放界面显示

void SelectCamera(QModelIndex index); // 选择摄像机
void SelectDate(QDate date); // 选择日期
void PlayVideo(QModelIndex index); // 选择时间播放视频


private:
Ui::XViewerClass ui;
QMenu left_menu_;
};

xviewer.cpp

实现xviewer中的函数

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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
#include "xviewer.h"
#include <QMouseEvent>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QResizeEvent>
#include <QDebug>
#include <QContextMenuEvent>
#include <QGridLayout>
#include <QDialog>
#include <QFormLayout>
#include <QLineEdit>
#include <QMessageBox>
#include <QDir>
#include <map>
#include <vector>
#include <sstream>
#include "xCamera_widget.h"
#include "xcamera_config.h"
#include "xcamera_record.h"
#include "xplayvideo.h"

using namespace std;

#define CAM_CONF_PATH "cams.db"

#define C(s) QString::fromLocal8Bit(s)

static XCameraWidget* cam_wids[16] = { 0 };

static vector<XCameraRecord *> records;

struct XCamVideo
{
QString filepath;
QDateTime datetime;
};

static map<QDate, vector<XCamVideo>> cam_videos;


void XViewer::SelectCamera(QModelIndex index)
{
qDebug() << "SelectCamera" << index.row();
qDebug() << "PlayVideo" << index.row();
auto conf = XCameraConfig::Instance();
auto cam = conf->GetCam(index.row());
if (cam.name[0] == '\0')
{
return;
}

stringstream ss;
ss << cam.save_path << "/" << index.row() << "/";

QDir dir(C(ss.str().c_str()));
if (!dir.exists())
{
return;
}

QStringList filters;
filters << "*.mp4" << "*.avi";
dir.setNameFilters(filters);

ui.cal->ClearDate();
cam_videos.clear();

auto files = dir.entryInfoList();
for (auto file : files)
{
QString filename = file.fileName();

auto tmp = filename.left(filename.size() - 4);
tmp = tmp.right(tmp.length() - 4);

auto dt = QDateTime::fromString(tmp, "yyyy_MM_dd_hh_mm_ss");
qDebug() << dt.date();

ui.cal->AddDate(dt.date());

XCamVideo video;
video.datetime = dt;
video.filepath = file.absoluteFilePath();
cam_videos[dt.date()].push_back(video);
}

ui.cal->showNextMonth();
ui.cal->showPreviousMonth();
}


void XViewer::SelectDate(QDate date)
{
qDebug() << "SelectDate" << date.toString();
auto dates = cam_videos[date];
ui.time_list->clear();
for (auto d : dates)
{
auto item = new QListWidgetItem(d.datetime.time().toString());

item->setData(Qt::UserRole, d.filepath);
ui.time_list->addItem(item);
}
}


void XViewer::PlayVideo(QModelIndex index)
{
qDebug() << "PlayVideo" << index.row() << endl;
auto item = ui.time_list->currentItem();
if (!item) return;
QString path = item->data(Qt::UserRole).toString();
qDebug() << path;
static XPlayVideo play;
play.Open(path.toLocal8Bit());
play.show();

}


void XViewer::View1()
{
View(1);
}
void XViewer::View4()
{
View(4);
}
void XViewer::View9()
{
View(9);
}
void XViewer::View16()
{
View(16);
}


void XViewer::View(int count)
{
qDebug() << "View: " << count << endl;

int cols = sqrt(count);

int wid_size = sizeof(cam_wids) / sizeof(QWidget*);

auto lay = (QGridLayout*)ui.cams->layout();
if (!lay)
{
lay = new QGridLayout();
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(2);
ui.cams->setLayout(lay);
}

for (int i = 0; i < count; i++)
{
if (!cam_wids[i])
{
cam_wids[i] = new XCameraWidget();
cam_wids[i]->setStyleSheet("background-color:rgb(51, 51, 51);");
}
lay->addWidget(cam_wids[i], i / cols, i % cols);
}

for (int i = count; i < wid_size; i++)
{
if (cam_wids[i])
{
delete cam_wids[i];
cam_wids[i] = nullptr;
}
}
}


void XViewer::timerEvent(QTimerEvent *ev)
{
int wid_size = sizeof(cam_wids) / sizeof(QWidget*);
for (int i = 0; i < wid_size; i++)
{
if (cam_wids[i])
{
cam_wids[i]->Draw();
}
}
}


void XViewer::StartRecord()
{
StopRecord();
qDebug() << "开始全部摄像头录制" << endl;
ui.status->setText(C("录制中..."));

auto conf = XCameraConfig::Instance();
int count = conf->GetCamCount();
for (int i = 0; i < count; i++)
{
auto cam = conf->GetCam(i);
stringstream ss;
ss << cam.save_path << "/" << i << "/";
QDir dir;
dir.mkpath(ss.str().c_str());
XCameraRecord *rec = new XCameraRecord();
rec->set_rtsp_url(cam.url);
rec->set_save_path(ss.str());
rec->set_file_sec(5);
rec->Start();
records.push_back(rec);
}
}


void XViewer::StopRecord()
{
ui.status->setText(C("监控中..."));
for (auto rec : records)
{
rec->Stop();
delete rec;
}

records.clear();
}


void XViewer::contextMenuEvent(QContextMenuEvent *event)
{
left_menu_.exec(QCursor::pos());
event->accept();
}


void XViewer::SetCam(int index)
{
auto c = XCameraConfig::Instance();

QDialog dlg(this);
dlg.resize(800, 200);

QFormLayout lay;
dlg.setLayout(&lay);
QLineEdit name_edit;
lay.addRow(C("名称"), &name_edit);

QLineEdit url_edit;
lay.addRow(C("主码流"), &url_edit);

QLineEdit sub_url_edit;
lay.addRow(C("辅码流"), &sub_url_edit);

QLineEdit save_path_edit;
lay.addRow(C("保存目录"), &save_path_edit);

QPushButton save;
save.setText(C("保存"));

connect(&save, SIGNAL(clicked()), &dlg, SLOT(accept()));
lay.addRow("", &save);

if (index >= 0)
{
auto cam = c->GetCam(index);
name_edit.setText(C(cam.name));
url_edit.setText(C(cam.url));
sub_url_edit.setText(C(cam.sub_url));
save_path_edit.setText(C(cam.save_path));
}

for (;;)
{
if (dlg.exec() == QDialog::Accepted)
{
if (name_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入名称"));
continue;
}

if (url_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入主码流"));
continue;
}

if (sub_url_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入辅码流"));
continue;
}

if (save_path_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入保存目录"));
continue;
}
break;
}
return;
}

XCameraData data;
strcpy(data.name, name_edit.text().toLocal8Bit());
strcpy(data.url, url_edit.text().toLocal8Bit());
strcpy(data.sub_url, sub_url_edit.text().toLocal8Bit());
strcpy(data.save_path, save_path_edit.text().toLocal8Bit());

if (index >= 0)
{
c->SetCam(index, data);
}
else
{
c->Push(data);
}

c->Save(CAM_CONF_PATH);
RefreshCams();
}


void XViewer::SetCam()
{
int row = ui.cam_list->currentIndex().row();
if (row < 0)
{
QMessageBox::information(this, "error", C("请选择摄像机"));
return;
}
SetCam(row);
}


void XViewer::DelCam()
{
int row = ui.cam_list->currentIndex().row();
if (row < 0)
{
QMessageBox::information(this, "error", C("请选择摄像机"));
return;
}
stringstream ss;
ss << "您确认要删除摄像机:" << ui.cam_list->currentItem()->text().toLocal8Bit().constData();
ss << "吗?";

if (
QMessageBox::information(this, "confirm", C(ss.str().c_str()), QMessageBox::Yes, QMessageBox::No)
!= QMessageBox::Yes
)
{
qDebug() << "111" << endl;
return;
}

XCameraConfig::Instance()->DelCam(row);
RefreshCams();
}


void XViewer::AddCam()
{
SetCam(-1);
}


void XViewer::RefreshCams()
{
auto c = XCameraConfig::Instance();
ui.cam_list->clear();
int count = c->GetCamCount();
for (int i = 0; i < count; i++)
{
auto cam = c->GetCam(i);
auto item = new QListWidgetItem(QIcon(":/XViewer/img/cam.png"), C(cam.name));
ui.cam_list->addItem(item);
}
c->Save(CAM_CONF_PATH);
}


void XViewer::Preview()
{
ui.cams->show();
ui.playback_wid->hide();
ui.preview->setChecked(true);
}


void XViewer::Playback()
{
ui.cams->hide();
ui.playback_wid->show();
ui.playback->setChecked(true);
}


XViewer::XViewer(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);

setWindowFlags(Qt::FramelessWindowHint);

auto vlay = new QVBoxLayout();

vlay->setContentsMargins(0, 0, 0, 0);

vlay->setSpacing(0);

vlay->addWidget(ui.head);
vlay->addWidget(ui.body);
this->setLayout(vlay);

auto hlay = new QHBoxLayout();
ui.body->setLayout(hlay);
// 边框间距
vlay->setContentsMargins(0, 0, 0, 0);
hlay->addWidget(ui.left);
hlay->addWidget(ui.cams);
hlay->addWidget(ui.playback_wid);

auto m = left_menu_.addMenu(C("视图"));
auto a = m->addAction(C("1窗口"));
connect(a, SIGNAL(triggered()), this, SLOT(View1()));

a = m->addAction(C("4窗口"));
connect(a, SIGNAL(triggered()), this, SLOT(View4()));

a = m->addAction(C("9窗口"));
connect(a, SIGNAL(triggered()), this, SLOT(View9()));

a = m->addAction(C("16窗口"));
connect(a, SIGNAL(triggered()), this, SLOT(View16()));

a = left_menu_.addAction(C("全部开始录制"));
connect(a, SIGNAL(triggered()), this, SLOT(StartRecord()));
a = left_menu_.addAction(C("全部停止录制"));
connect(a, SIGNAL(triggered()), this, SLOT(StopRecord()));

View(9);

XCameraConfig::Instance()->Load(CAM_CONF_PATH);

ui.time_list->clear();
RefreshCams();

startTimer(1);

Playback();
}


XViewer::~XViewer()
{}


void XViewer::MaxWindow()
{
ui.max->setVisible(false);
ui.normal->setVisible(true);
showMaximized();
}


void XViewer::NormalWindow()
{
ui.max->setVisible(true);
ui.normal->setVisible(false);
showNormal();
}


void XViewer::resizeEvent(QResizeEvent *ev)
{
int x = width() - ui.head_button->width();
int y = ui.head_button->y();
ui.head_button->move(x, y);
}


static bool mouse_press = false; // 鼠标是否按下
static QPoint mouse_point; // 起始的坐标


void XViewer::mouseMoveEvent(QMouseEvent *ev)
{
if (!mouse_press)
{
QWidget::mouseMoveEvent(ev);
return;
}
this->move(ev->globalPos() - mouse_point);
}


void XViewer::mousePressEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton)
{
mouse_press = true;
mouse_point = ev->pos();
}
}


void XViewer::mouseReleaseEvent(QMouseEvent *ev)
{
mouse_press = false;
}

相关自定义参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 用于存储录像机相关数据,如名称,主码流,辅码流,存储地址
#define CAM_CONF_PATH "cams.db"

// 解决中文乱码
#define C(s) QString::fromLocal8Bit(s)

// 自定义渲染窗口XCameraWidget
static XCameraWidget* cam_wids[16] = { 0 };

// 视频录制,自定义的类
static vector<XCameraRecord *> records;

// 存储视频日期时间
struct XCamVideo
{
QString filepath;
QDateTime datetime;
};

主窗口构造函数

XViewer

主窗口的构造函数,用于初始化主窗口的一些事件。

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
XViewer::XViewer(QWidget *parent) : QWidget(parent)
{
ui.setupUi(this);

// 去除原窗口边框
setWindowFlags(Qt::FramelessWindowHint);

// 布局head和body 垂直布局器
auto vlay = new QVBoxLayout();
// 边框间距
vlay->setContentsMargins(0, 0, 0, 0);
// 元素间距
vlay->setSpacing(0);

// 将head body添加到垂直布局器中
vlay->addWidget(ui.head);
vlay->addWidget(ui.body);
this->setLayout(vlay);

// 相机列表和相机预览
// 水平布局器
auto hlay = new QHBoxLayout();
ui.body->setLayout(hlay);
// 边框间距
vlay->setContentsMargins(0, 0, 0, 0);
hlay->addWidget(ui.left); // 左侧相机列表
hlay->addWidget(ui.cams); // 右侧预览窗口
hlay->addWidget(ui.playback_wid); // 右侧回放窗口

// 初始化右键菜单
auto m = left_menu_.addMenu(C("视图"));
auto a = m->addAction(C("1窗口"));

// SIGNAL(triggered()用于处理上面m->addAction(C("1窗口"))创建的Action信号
// 通过点击,从而调用View1()的槽函数,实现1/4/9/16的多路监控
connect(a, SIGNAL(triggered()), this, SLOT(View1()));

a = m->addAction(C("4窗口"));
connect(a, SIGNAL(triggered()), this, SLOT(View4()));

a = m->addAction(C("9窗口"));
connect(a, SIGNAL(triggered()), this, SLOT(View9()));

a = m->addAction(C("16窗口"));
connect(a, SIGNAL(triggered()), this, SLOT(View16()));

// 通过点击"全部开始录制",调用StartRecord()槽函数
a = left_menu_.addAction(C("全部开始录制"));
connect(a, SIGNAL(triggered()), this, SLOT(StartRecord()));
// 通过点击"全部停止录制",调用StopRecord()槽函数
a = left_menu_.addAction(C("全部停止录制"));
connect(a, SIGNAL(triggered()), this, SLOT(StopRecord()));

// 默认9窗口
View(9);

// 通过单例模式,加载录像机数据
XCameraConfig::Instance()->Load(CAM_CONF_PATH);

// 加载之后
// 刷新左侧摄像机列表
ui.time_list->clear();
RefreshCams();

// 启动定时器渲染视频
startTimer(1);

// 默认显示预览
Playback();
}

右键菜单显示

1
2
3
4
5
6
7
// 右键菜单
void XViewer::contextMenuEvent(QContextMenuEvent *event)
{
// 获取鼠标位置,显示右键菜单
left_menu_.exec(QCursor::pos());
event->accept();
}

contextMenuEvent(QContextMenuEvent *event)

  • 这是一个事件处理函数,用于处理上下文菜单事件(右键点击事件)。当用户在窗口上点击右键时,Qt 会调用这个方法

left_menu_.exec(QCursor::pos());

  • exec 方法显示上下文菜单,并使其在屏幕上的指定位置弹出。
  • QCursor::pos() 返回当前鼠标指针的位置。将这个位置传递给 exec 方法,菜单将会在用户点击右键的位置弹出。

event->accept();

  • 这行代码表示接受这个事件,通知 Qt 这个事件已经被处理。这样可以防止事件被进一步传递到其他事件处理器中。

示例图片】:

image-20240731103137618

自定义放大,缩小,关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 通过点击右上角正方形进行放大
void XViewer::MaxWindow()
{
ui.max->setVisible(false);
ui.normal->setVisible(true);
showMaximized(); // 调用Qt自定义最大化函数
}

// 当最大化后,通过点击右上角两个正方形进行缩小
void XViewer::NormalWindow()
{
ui.max->setVisible(true);
ui.normal->setVisible(false);
showNormal(); // 调用Qt自定义缩小函数
}

// 关于最小化,关闭是调用了Qt自定义函数
// 关闭: close()
// 最小化: showMinimized();

当窗口放大,缩小时候,将head_button移动到界面右上角

image-20240801084502755

1
2
3
4
5
6
7
8
9
// 窗口大小发生变化
void XViewer::resizeEvent(QResizeEvent *ev)
{
// x轴移动到最右边
int x = width() - ui.head_button->width();
// y轴不动
int y = ui.head_button->y();
ui.head_button->move(x, y);
}

鼠标拖动窗口进行移动

当鼠标左键按下并拖动时,窗口会跟随鼠标移动;当鼠标左键松开时,停止拖动。这是通过重写mouseMoveEventmousePressEventmouseReleaseEvent三个事件处理函数来实现的。

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
// 鼠标事件 用于拖动窗口
static bool mouse_press = false; // 鼠标是否按下
static QPoint mouse_point; // 起始的坐标

// 鼠标移动触发的函数
void XViewer::mouseMoveEvent(QMouseEvent *ev)
{
if (!mouse_press)
{
QWidget::mouseMoveEvent(ev);
return;
}
this->move(ev->globalPos() - mouse_point);
}

// 鼠标按下触发的函数
void XViewer::mousePressEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton)
{
mouse_press = true;
mouse_point = ev->pos();
}
}

// 鼠标离开触发的函数
void XViewer::mouseReleaseEvent(QMouseEvent *ev)
{
mouse_press = false;
}

1/4/9/16路监控设置

通过给View()传入几路参数,达到多路监控的设置

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
void XViewer::View1()
{
View(1);
}
void XViewer::View4()
{
View(4);
}
void XViewer::View9()
{
View(9);
}
void XViewer::View16()
{
View(16);
}

void XViewer::View(int count)
{
qDebug() << "View: " << count << endl;

// 2x2 3x3 4x4显示
// 确定列数
int cols = sqrt(count);
// 总窗口数量
int wid_size = sizeof(cam_wids) / sizeof(QWidget*);

// 初始化网格布局器
auto lay = (QGridLayout*)ui.cams->layout();
if (!lay)
{
lay = new QGridLayout();
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(2); // 元素间距
ui.cams->setLayout(lay);
}

// 根据传入的count,进行初始化窗口
for (int i = 0; i < count; i++)
{
// 如果当前窗口没有被初始化
if (!cam_wids[i])
{
// 给这个窗口创建一个XCameraWidget
cam_wids[i] = new XCameraWidget();
cam_wids[i]->setStyleSheet("background-color:rgb(51, 51, 51);");
}
// addWidget(QWidget *widget, int row, int column)
// 将窗口加入网格布局器中
lay->addWidget(cam_wids[i], i / cols, i % cols);
}

// 清理多余的窗体
// 从count开始,wid_size结束
for (int i = count; i < wid_size; i++)
{
if (cam_wids[i])
{
delete cam_wids[i];
cam_wids[i] = nullptr;
}
}
}

预览、回放界面

通过点击预览、来回放切换两个widget:cams,playback

image-20240801084919011

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 预览界面显示
void XViewer::Preview()
{
ui.cams->show();
ui.playback_wid->hide();
ui.preview->setChecked(true);
}

// 回放界面显示
void XViewer::Playback()
{
ui.cams->hide();
ui.playback_wid->show();
ui.playback->setChecked(true);
}

新增相机

点击左上角新增按钮,触发槽函数,进行添加相机参数

image-20240801090919691
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
void XViewer::AddCam()
{
SetCam(-1);
}

void XViewer::SetCam(int index)
{
auto c = XCameraConfig::Instance();

// 新创建一个QDialog
QDialog dlg(this);
dlg.resize(800, 200);

// 标题1 输入框1
// 标题2 输入框2

// 设置布局管理器
QFormLayout lay;
dlg.setLayout(&lay);
QLineEdit name_edit;
lay.addRow(C("名称"), &name_edit);

QLineEdit url_edit;
lay.addRow(C("主码流"), &url_edit);

QLineEdit sub_url_edit;
lay.addRow(C("辅码流"), &sub_url_edit);

QLineEdit save_path_edit;
lay.addRow(C("保存目录"), &save_path_edit);

QPushButton save;
save.setText(C("保存"));

/*
accept()函数会关闭对话框。

关闭对话框时,accept()函数会将对话框的返回状态设置为QDialog::Accepted。
这个返回状态可以在调用对话框的地方通过exec()函数的返回值获取。

一般来说
点击“确定”按钮时,调用accept()函数关闭对话框并设置返回状态为QDialog::Accepted。
点击“取消”按钮时,调用reject()函数关闭对话框并设置返回状态为QDialog::Rejected。

可以通过if(dlg.exec() == QDialog::Accepted)处理判断
*/
connect(&save, SIGNAL(clicked()), &dlg, SLOT(accept()));
lay.addRow("", &save);

// 如果是传入的参数index不是负数,代表编辑数据 读入元数据显示
if (index >= 0)
{
auto cam = c->GetCam(index);
name_edit.setText(C(cam.name));
url_edit.setText(C(cam.url));
sub_url_edit.setText(C(cam.sub_url));
save_path_edit.setText(C(cam.save_path));
}

// 处理输入格式是否有问题
for (;;)
{
// 点击了保存按钮
if (dlg.exec() == QDialog::Accepted)
{
if (name_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入名称"));
continue;
}

if (url_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入主码流"));
continue;
}

if (sub_url_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入辅码流"));
continue;
}

if (save_path_edit.text().isEmpty())
{
QMessageBox::information(0, "error", C("请输入保存目录"));
continue;
}
break;
}
return;
}

// 创建一个data,将输入的数据存储下来
XCameraData data;
strcpy(data.name, name_edit.text().toLocal8Bit());
strcpy(data.url, url_edit.text().toLocal8Bit());
strcpy(data.sub_url, sub_url_edit.text().toLocal8Bit());
strcpy(data.save_path, save_path_edit.text().toLocal8Bit());

if (index >= 0) // 表示修改
{
c->SetCam(index, data);
}
else // 表示新增
{
c->Push(data);
}

c->Save(CAM_CONF_PATH); // 保存到文件
RefreshCams(); // 刷新显示
}

修改,删除相机

当用户点击一个相机,然后点击修改、删除按钮。

不点击相机,点击修改、删除按钮无效。

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
// 修改摄像机配置
void XViewer::SetCam()
{
// 获取用户点击的相机位置
int row = ui.cam_list->currentIndex().row();
if (row < 0)
{
QMessageBox::information(this, "error", C("请选择摄像机"));
return;
}
SetCam(row);
}

// 删除摄像机配置
void XViewer::DelCam()
{
// 获取用户点击的相机位置
int row = ui.cam_list->currentIndex().row();
if (row < 0)
{
QMessageBox::information(this, "error", C("请选择摄像机"));
return;
}
stringstream ss;
ss << "您确认要删除摄像机:" << ui.cam_list->currentItem()->text().toLocal8Bit().constData();
ss << "吗?";

// 如果用户点击No,则返回
if (
QMessageBox::information(this, "confirm", C(ss.str().c_str()), QMessageBox::Yes, QMessageBox::No)
!= QMessageBox::Yes
)
{
return;
}

// 通过单例,删除数据
XCameraConfig::Instance()->DelCam(row);
RefreshCams();
}

点击相机,有录像的日期标红

点击某一个相机,在cal中有录像的日期标红。

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
// 选择摄像机
void XViewer::SelectCamera(QModelIndex index)
{
auto conf = XCameraConfig::Instance();
auto cam = conf->GetCam(index.row()); // 获取相机的参数
if (cam.name[0] == '\0')
{
return;
}

// 相机视频存储路径
stringstream ss;
ss << cam.save_path << "/" << index.row() << "/";

// 遍历此目录
QDir dir(C(ss.str().c_str()));
if (!dir.exists())
{
return;
}

// 获取目录下文件列表
QStringList filters;
// 只选择.mp4 .avi格式
filters << "*.mp4" << "*.avi";
dir.setNameFilters(filters); // 筛选

// 清理其他相机的数据
ui.cal->ClearDate();
// 存放<日期-时间>的map
cam_videos.clear();

// 所有文件列表
auto files = dir.entryInfoList();
for (auto file : files)
{
// "cam_2024_07_29_10_18_18.mp4"
QString filename = file.fileName();

// 去掉cam_ 和.mp4
auto tmp = filename.left(filename.size() - 4);
tmp = tmp.right(tmp.length() - 4);

// 剩下2024_07_29_10_18_18
auto dt = QDateTime::fromString(tmp, "yyyy_MM_dd_hh_mm_ss");

// 将数据存入cal的mdate_中,然后在cal中进行渲染。
// cal中mdate_定义:
// std::set<QDate> mdate_; // 存放有视频的日期
ui.cal->AddDate(dt.date());

// XCamVideo:自定义存储文件路径,时间的struct
XCamVideo video;
video.datetime = dt;
video.filepath = file.absoluteFilePath();
cam_videos[dt.date()].push_back(video); // 将其存储进行map中
}

// 重新显示日期
ui.cal->showNextMonth();
ui.cal->showPreviousMonth();
}

点击日期,出现录像文件

点击右侧日历中的红色日期,会在中间的time_list出现录像文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 选择日期
// 通过clicked(QDate)传入QDate进行处理
void XViewer::SelectDate(QDate date)
{
qDebug() << "SelectDate" << date.toString();
// // map<QDate, vector<XCamVideo>>
auto dates = cam_videos[date];
ui.time_list->clear();

for (auto d : dates) // 遍历vector<XCamVideo>
{
// 为每个视频创建一个新的 QListWidgetItem 对象,
// 并将视频的时间(d.datetime.time())转换为字符串并设置为该项的显示文本。
auto item = new QListWidgetItem(d.datetime.time().toString());

// item 添加自定义数据 文件路径
// 使用 setData(Qt::UserRole, value) 将每个视频文件的路径存储在 QListWidgetItem 中。
// 后续可以通过item->data(Qt::UserRole).toString();进行查询
//
// 如在void XViewer::PlayVideo(QModelIndex index)函数中
item->setData(Qt::UserRole, d.filepath);
ui.time_list->addItem(item);
}
}

相机参数设置

单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单例模式的主要好处包括以下几个方面:

  1. 控制实例数量
  • 单例模式确保一个类只有一个实例,这对于某些场景来说是非常重要的,例如:配置管理类、线程池、日志记录类等。这些场景中,一个实例足以满足需求,且多个实例可能导致问题。
  1. 全局访问点
  • 单例模式提供了一个全局访问点,可以在整个应用程序中方便地访问该实例。这简化了代码中的依赖管理,不需要到处传递实例引用。
  1. 延迟实例化
  • 单例模式可以延迟实例化,即在第一次访问实例时创建对象。这有助于优化资源的使用,避免在不需要时创建对象,从而节省内存和其他系统资源。
  1. 共享资源
  • 单例模式允许多个组件或线程共享一个资源,而不需要担心资源的并发访问问题。例如,单例模式常用于数据库连接池,这样可以确保所有数据库操作使用同一个连接池,提高资源利用率。
  1. 防止多重实例
  • 单例模式可以防止多个实例存在,从而避免可能出现的不一致性或逻辑错误。例如,在某些情况下,如果多个实例同时修改同一个文件或数据库,可能会导致数据不一致或冲突。
  1. 提高性能
  • 由于单例模式只创建一次实例,因此可以减少对象创建和销毁的开销,提高系统的性能。

xcamera_config.h

单例模式,唯一对象实例,外部只能通过Instance来调用接口函数

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
#pragma once
#include <vector>
#include <mutex>

struct XCameraData // 存储相机参数 XCameraData
{
char name[1024] = { 0 };
char url[4096] = { 0 }; // 摄像机主码流
char sub_url[4096] = { 0 }; // 摄像机辅码流
char save_path[4096] = { 0 }; // 视频录制存放目录
};

class XCameraConfig
{
public:
// 唯一对象实例 单例模式
static XCameraConfig *Instance()
{
static XCameraConfig xc;
return &xc;
}

// 插入摄像机 线程安全
void Push(const XCameraData &data);

// 获取摄像头
// index:摄像头索引 从0开始
XCameraData GetCam(int index);

// 修改摄像机数据
bool SetCam(int index, const XCameraData &data);

// 删除摄像机
bool DelCam(int index);

// 返回相机数量 线程安全
// 失败返回0
int GetCamCount();

// 存储配置文件
bool Save(const char*path);

// 读取配置文件 存入cams_
bool Load(const char*path);

private:
XCameraConfig() {}; // 构造私有 单例模式
std::vector<XCameraData> cams_; // 存储所有相机的参数 cams_
std::mutex mux_;
};


xcamera_config.cpp

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
#include "xcamera_config.h"

#include <fstream>

using namespace std;

// 插入摄像机 线程安全
void XCameraConfig::Push(const XCameraData &data)
{
unique_lock<mutex> lock(mux_);
cams_.push_back(data);
}

// 获取摄像头
// index:摄像头索引 从0开始
XCameraData XCameraConfig::GetCam(int index)
{
unique_lock<mutex> lock(mux_);
if (index < 0 || index > cams_.size())
return XCameraData();

return cams_[index];
}

// 删除摄像机
bool XCameraConfig::DelCam(int index)
{
unique_lock<mutex> lock(mux_);
if (index < 0 || index > cams_.size())
return false;

cams_.erase(cams_.begin() + index);

return true;
}

// 修改摄像机数据
bool XCameraConfig::SetCam(int index, const XCameraData &data)
{
unique_lock<mutex> lock(mux_);
if (index < 0 || index > cams_.size())
return false;
cams_[index] = data;
return true;
}

// 返回相机数量 线程安全
// 失败返回0
int XCameraConfig::GetCamCount()
{
unique_lock<mutex> lock(mux_);
return cams_.size();
}


// 读取配置文件 存入cams_
bool XCameraConfig::Load(const char*path)
{
if (!path) return false;
ifstream ifs(path, ios::binary); // 使用二进制打开文件
if (!ifs) return false;

XCameraData data;
unique_lock<mutex> lock(mux_);
cams_.clear(); // 清理数据重新读取
for (;;)
{
// 从文件中读取sizeof(data)字节的数据到data中。
ifs.read((char*)&data, sizeof(data));

// 如果读取的字节数不等于sizeof(data),说明已经读到文件末尾或发生读取错误。
if (ifs.gcount() != sizeof(data))
{
ifs.close();
return true;
}
cams_.push_back(data);
}
ifs.close();
return true;
}


// 存储配置文件
bool XCameraConfig::Save(const char*path)
{
if (!path) return false;
ofstream ofs(path, ios::binary); // 用二进制存储文件
if (!ofs) return false;

unique_lock<mutex> lock(mux_);
for (auto cam : cams_)
{
ofs.write((char*)&cam, sizeof(cam));
}
ofs.close();
return true;
}

浏览相机

将cam_list属性中的dragEnable打钩(这样就可以进行拖拽了)

重载XCameraWidget里面的一些函数,使得通过拖拽让窗口获取参数,进行播放

image-20240801092830519

】:在XViewer中的void XViewer::View(int count),在每次用户设置几路播放的时候,初始化了XCameraWidget(),使得每个窗口都是XCameraWidget,从而可以触发XCameraWidget一系列槽函数、函数。

1
2
3
4
5
6
7
8
9
10
11
// 初始化窗口
for (int i = 0; i < count; i++)
{
if (!cam_wids[i])
{
// XCameraWidget初始化
cam_wids[i] = new XCameraWidget();
cam_wids[i]->setStyleSheet("background-color:rgb(51, 51, 51);");
}
lay->addWidget(cam_wids[i], i / cols, i % cols);
}

xcamera_widget.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
#pragma once
#include <QWidget>
#include <QPaintEvent>
class XDecodeTask;
class XDemuxTask;
class XVideoView;

// 继承于QWidget
class XCameraWidget : public QWidget
{
Q_OBJECT

public:
XCameraWidget(QWidget *p = nullptr);

// 拖拽进入
void dragEnterEvent(QDragEnterEvent *e) override;

// 拖拽松开
void dropEvent(QDropEvent *e) override;

// 渲染
void paintEvent(QPaintEvent *p);

bool Open(const char *url);

// 渲染视频
void Draw();

// 清理资源
~XCameraWidget();

private:
XDecodeTask *decode_ = nullptr; // 解码器
XDemuxTask *demux_ = nullptr; // 读取数据
XVideoView *view_ = nullptr; // 初始化SDL,进行窗口渲染
};

xcamera_widget.cpp

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
#include "xCamera_widget.h"
#include <QStyleOption>
#include <QPainter>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QDebug>
#include <QListWidget>
#include "xdemuxTask.h"
#include "xdecodeTask.h"
#include "xvideo_view.h"
#include "xcamera_config.h"


// 渲染样式表
/*
对Qt::paintEvent进行了重载

paintEvent是QWidget类中的一个虚函数,当需要重新绘制控件时会被调用。
通过重载这个函数,可以自定义控件的绘制方式。

QPaintEvent *p是一个指向QPaintEvent对象的指针,包含有关绘制事件的信息。
*/
void XCameraWidget::paintEvent(QPaintEvent *p)
{
QStyleOption opt; // 描述控件绘制参数的类
opt.init(this); // 将当前控件的状态信息初始化到opt中。

QPainter painter(this); // 创建一个QPainter对象painter,并将其与当前控件相关联。
// 这意味着所有的绘制操作都将在当前控件上执行。

style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
/*
style()返回当前应用的样式(即当前使用的QStyle对象)。
drawPrimitive是QStyle类中的一个函数,用于绘制基本的元素(Primitive Element)。
QStyle::PE_Widget表示绘制的是一个普通的窗口部件(Widget)。

参数依次是绘制的元素类型(QStyle::PE_Widget)、绘制选项(QStyleOption对象opt)、
绘制设备(QPainter对象painter)和控件本身(this)。
*/
}


// 是 XCameraWidget 类的构造函数。
// 它接受一个 QWidget 类型的指针 p 作为参数,并将其传递给基类 QWidget 的构造函数。
//
// 使用初始化列表而不是在构造函数体内调用基类构造函数,更加简洁和清晰,同时也能提高性能。
XCameraWidget::XCameraWidget(QWidget *p):QWidget(p)
{
// 接收拖拽
// 设置当前小部件可以接收拖拽操作。
this->setAcceptDrops(true);
}


// 拖拽进入
void XCameraWidget::dragEnterEvent(QDragEnterEvent *e)
{
// 接收拖拽进入
// 函数调用之后,拖放操作将被认为是可行的,并且后续的 dropEvent 将会被触发。
e->acceptProposedAction();
}


// 拖拽松开
void XCameraWidget::dropEvent(QDropEvent *e)
{
// 拿到url
qDebug() << e->source()->objectName() << endl;

// 将拖拽源对象转换为 QListWidget 类型,并存储在 wid 变量中。
auto wid = (QListWidget*)e->source();
qDebug() << wid->currentRow() << endl;

// 通过 XCameraConfig 单例类的 GetCam 方法,获取当前选择行对应的摄像头配置。
auto cam = XCameraConfig::Instance()->GetCam(wid->currentRow());
Open(cam.url);
}


// 播放视频
bool XCameraWidget::Open(const char *url)
{
if (demux_)
demux_->Stop();
if (decode_)
decode_->Stop();

// 打开解封装器,线程
demux_ = new XDemuxTask();
if (!demux_->Open(url))
{
return false;
}
// 打开视频解码器,线程
decode_ = new XDecodeTask();
auto para = demux_->CopyVideoPara();

if (!decode_->Open(para->para))
{
return false;
}

// 设定解码器线程接收解封装数据
// 【责任链】
// 将解码器 decode_ 设为解封装器 demux_ 的下一个处理对象。
// 这意味着当解封装器解封装出数据包后,会传递给解码器进行解码。
demux_->set_next(decode_);

// 初始化渲染参数
view_ = XVideoView::Create();
view_->set_win_id((void*)winId());
view_->Init(para->para);

// 启动解封装和解码线程
demux_->Start();
decode_->Start();

return true;
}


// 渲染视频
// 通过在xviewer.cpp中timerEvent回调函数,每一帧进行渲染
void XCameraWidget::Draw()
{
if (!demux_ || !decode_ || !view_) return;
auto f = decode_->GetFrame();
if (!f) return;
view_->DrawFrame(f);
XFreeFrame(&f);
}


// 清理资源
XCameraWidget::~XCameraWidget()
{
if (demux_)
{
demux_->Stop();
delete demux_;
demux_ = nullptr;
}

if (decode_)
{
decode_->Stop();
delete decode_;
decode_ = nullptr;
}

if (view_)
{
view_->Close();
delete view_;
view_ = nullptr;
}
}

录制功能

xcamera_record.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma once
#include <string>
#include "xtools.h"

class XCameraRecord : public XThread
{
public:
void set_rtsp_url(std::string url) { rtsp_url_ = url; }
void set_save_path(std::string s) { save_path_ = s; }
void set_file_sec(int s) { file_sec_ = s; }

private:
void Main();
std::string rtsp_url_;
std::string save_path_; // 存储的根目录
long long file_sec_ = 5; // 多少秒创建一个新监控文件
};

xcamera_record.cpp

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
#include "xcamera_record.h"
#include "xdemuxTask.h"
#include "xmuxTask.h"
#include <chrono>
#include <iomanip>
#include <sstream>

using namespace std;
using namespace chrono;


// 生成存储的视频文件名
static std::string GetFileName(string path)
{
stringstream ss;
// 获取当前时间
auto t = system_clock::to_time_t(system_clock::now());
auto time_str = put_time(localtime(&t), "%Y_%m_%d_%H_%M_%S");
ss << path << "/" << "cam_" << time_str << ".mp4";
return ss.str();
}


void XCameraRecord::Main()
{
XDemuxTask demux;
XMuxTask mux;
if (rtsp_url_.empty())
{
LOGERROR("open rtsp url failed!");
return;
}

// 自动重连
while (!is_exit_)
{
if (demux.Open(rtsp_url_)) // 最坏情况:阻塞1秒
{
break;
}
MSleep(3000);
continue;
}

// 音视频参数
auto vpara = demux.CopyVideoPara();
if (!vpara)
{
LOGERROR("demux.CopyVideoPara failed!");
// 需要考虑demux 资源释放的问题
demux.Stop();
return;
}

// 提前启动解封装线程,防止超时
demux.Start();
auto apara = demux.CopyAudioPara();

AVCodecParameters *para = nullptr; // 音频参数
AVRational *timebase = nullptr; // 音频时间基数

if (apara)
{
para = apara->para;
timebase = apara->time_base;
}

if(!mux.Open(GetFileName(save_path_).c_str(),
vpara->para, // 视频参数
vpara->time_base,
para, // 音频参数
timebase))
{
LOGERROR("mux.Open failed!");
demux.Stop();
mux.Stop();
return;
}

// 设置 demux 的下一个处理对象为 mux,并启动 mux。
demux.set_next(&mux);
mux.Start();

// 当前时间
auto cur = NowMs();

while (!is_exit_)
{
if (NowMs() - cur > file_sec_ * 1000)
{
cur = NowMs();
mux.Stop(); // 停止存储,写入索引

if (!mux.Open(GetFileName(save_path_).c_str(),
vpara->para, // 视频参数
vpara->time_base,
para, // 音频参数
timebase))
{
LOGERROR("mux.Open failed!");
demux.Stop();
mux.Stop();
return;
}

mux.Start();
}

// 定时创建新的文件
MSleep(10);
}
mux.Stop();
demux.Stop();
}

在xviewer.cpp中的应用

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
// 开始全部摄像头录制
void XViewer::StartRecord()
{
// 首先停止之前的录制
StopRecord();
qDebug() << "开始全部摄像头录制" << endl;
ui.status->setText(C("录制中..."));
// 获取配置列表
auto conf = XCameraConfig::Instance();
int count = conf->GetCamCount();
for (int i = 0; i < count; i++)
{
auto cam = conf->GetCam(i);
stringstream ss;
ss << cam.save_path << "/" << i << "/";

// QDir 类用于处理文件和目录的操作
QDir dir;
// dir.mkpath(ss.str().c_str()); 的作用是确保指定的目录路径存在,
// 如果路径中任何部分的目录不存在,则会创建它们。
dir.mkpath(ss.str().c_str());
XCameraRecord *rec = new XCameraRecord();
rec->set_rtsp_url(cam.url);
rec->set_save_path(ss.str());
rec->set_file_sec(5);
rec->Start();
records.push_back(rec);
}

// 创建录制目录
// 分别开启录制线程
}


// 停止全部摄像头录制
void XViewer::StopRecord()
{
ui.status->setText(C("监控中..."));
for (auto rec : records)
{
rec->Stop();
delete rec;
}

records.clear();
}

自定义日历

将cal提升为自定义日历类

image-20240801145843932

xcalendar.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma once
#include <QCalendarWidget>
#include <set>
class XCalendar : public QCalendarWidget
{
public:
// XCalendar(QWidget *p):构造函数,接受一个 QWidget 指针作为父窗口。
XCalendar(QWidget *p);

// 重写 QCalendarWidget 的 paintCell 方法,以便自定义日期单元格的绘制。
void paintCell(QPainter *painter,
const QRect &rec,
const QDate &data) const override;

void AddDate(QDate d) { mdate_.insert(d); }
void ClearDate() { mdate_.clear(); }

private:
// QDate 是 Qt 框架中的一个类,用于表示和处理日期。
std::set<QDate> mdate_; // 存放有视频的日期
};

xcalendar.cpp

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
#include "xcalendar.h"
#include <QPainter>

XCalendar::XCalendar(QWidget *p):QCalendarWidget(p)
{

}

void XCalendar::paintCell(QPainter *painter,
const QRect &rec,
const QDate &date) const
{
// 有视频的日期特殊显示未红色
if (mdate_.find(date) == mdate_.end()) // 没有视频的日期
{
// 使用QCalendarWidget
QCalendarWidget::paintCell(painter, rec, date);
return;
}

auto font = painter->font();
// 设置字体
font.setPixelSize(40);

// 选中状态刷背景色
// selectedDate() 是用于获取用户在日期选择器中选定日期的一个方法
if (date == selectedDate())
{
painter->setBrush(QColor(118, 178, 224)); // 刷子颜色
painter->drawRect(rec); // 绘制背景
}

painter->setFont(font); // 设置字体和颜色
painter->setPen(QColor(255, 0, 0)); // 字体颜色
painter->drawText(rec, Qt::AlignCenter, QString::number(date.day()));
}

回放功能

image-20240801145254736

xplayvideo.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
#pragma once

#include <QWidget>
#include "ui_xplayvideo.h"
#include "xdemuxTask.h"
#include "xdecodeTask.h";
#include "xvideo_view.h"

class XPlayVideo : public QWidget
{
Q_OBJECT

public:
XPlayVideo(QWidget *parent = nullptr);
~XPlayVideo();
bool Open(const char *url);
void timerEvent(QTimerEvent *ev) override;
void Close();
void closeEvent(QCloseEvent *ev) override;

private:
Ui::XPlayVideoClass ui;
XDemuxTask demux_;
XDecodeTask decode_;
XVideoView *view_ = nullptr;
};

xplayvideo.cpp

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
#include "xplayvideo.h"


void XPlayVideo::timerEvent(QTimerEvent *ev)
{
if (!view_) return;
auto f = decode_.GetFrame();
if (!f) return;
view_->DrawFrame(f);
XFreeFrame(&f);
}


void XPlayVideo::Close()
{
// 关闭上一次数据
demux_.Stop();
decode_.Stop();
if (view_)
{
view_->Close();
delete view_;
view_ = nullptr;
}
}


void XPlayVideo::closeEvent(QCloseEvent *ev)
{
Close();
}


bool XPlayVideo::Open(const char *url)
{
if (!demux_.Open(url))
{
return false;
}

auto vp = demux_.CopyVideoPara();
if (!vp)
return false;
if (!decode_.Open(vp->para)) // 解码
{
return false;
}

demux_.set_next(&decode_);
if (!view_)
view_ = XVideoView::Create();
view_->set_win_id((void*)winId()); // SDL渲染

if (!view_->Init(vp->para))
{
return false;
}

demux_.set_syn_type(XSYN_VIDEO);
demux_.Start();
decode_.Start();

return true;
}


XPlayVideo::XPlayVideo(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
startTimer(10);
}


XPlayVideo::~XPlayVideo()
{
Close();
}

在xviewer.cpp中的应用

1
2
3
4
5
6
7
8
9
10
11
12
// 选择时间进行播放视频
void XViewer::PlayVideo(QModelIndex index)
{
qDebug() << "PlayVideo" << index.row() << endl;
auto item = ui.time_list->currentItem();
if (!item) return;
QString path = item->data(Qt::UserRole).toString();
qDebug() << path;
static XPlayVideo play;
play.Open(path.toLocal8Bit());
play.show();
}