初步实现录像功能
This commit is contained in:
commit
eb120b0fe7
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
debug/*
|
||||
release/*
|
||||
.qmake.stash
|
||||
HxUtils.pro.*
|
||||
Makefile*
|
||||
*.Debug
|
||||
*.Release
|
||||
*.TMP
|
||||
*.user
|
||||
._*
|
||||
.vscode
|
334
HxDevice.cpp
Normal file
334
HxDevice.cpp
Normal file
|
@ -0,0 +1,334 @@
|
|||
#include "HxDevice.h"
|
||||
#include "HxTrace.h"
|
||||
|
||||
#include "HxDisk.h"
|
||||
#include "HxBroadcast.h"
|
||||
|
||||
HxDevice::HxDevice(int channel, bool secondary, int millisecond) : HxThread(millisecond)
|
||||
{
|
||||
this->channel = channel;
|
||||
this->secondary = secondary;
|
||||
this->uuid = QUuid::createUuid();
|
||||
}
|
||||
|
||||
void HxDevice::startup(QJsonObject object)
|
||||
{
|
||||
code = HxJson::to_string(object, "code");
|
||||
drive_threshold = HxJson::to_int(object, "drive_threshold");
|
||||
recording_split_time = HxJson::to_int(object, "recording_split_time");
|
||||
|
||||
disks.clear();
|
||||
foreach(QString d, HxJson::to_string_list(object, "disks", ";"))
|
||||
{
|
||||
if (HxDisk::exist(d))
|
||||
{
|
||||
disks.append(d);
|
||||
}
|
||||
}
|
||||
|
||||
auto array = HxJson::to_string_list(object, QString("channel_%1").arg(channel, 2, 10, QLatin1Char('0')), ";");
|
||||
auto type = array[2].toUpper();
|
||||
auto address = array[3];
|
||||
auto port = array[4].toInt();
|
||||
auto enable = QVariant(array[7]).toBool();
|
||||
auto record = QVariant(array[8]).toBool();
|
||||
|
||||
if (enable && record)
|
||||
{
|
||||
if (type == "LION" || type == "HIK")
|
||||
rtspurl = QString("rtsp://admin:hik12345@%1:%2/h264/ch1/%3/av_stream").arg(address).arg(port).arg(!secondary ? "main" : "sub");
|
||||
else if (type == "XM")
|
||||
rtspurl = QString("rtsp://%1:%2/user=admin&password=&channel=0&stream=%3.sdp?").arg(address).arg(port).arg(!secondary ? 0 : 1);
|
||||
else if (type == "XIANGFEI")
|
||||
rtspurl = QString("rtsp://%1:%2/stander/livestream/0/%3").arg(address).arg(port).arg(!secondary ? 0 : 1);
|
||||
else if (type == "BSM")
|
||||
rtspurl = QString("rtsp://%1:%2/%3").arg(address).arg(port).arg(!secondary ? 11 : 12);
|
||||
|
||||
/* 创建 视频读取线程 */
|
||||
HxTask::run(this, &HxDevice::frame_read_process, 5000, uuid);
|
||||
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
void HxDevice::frame_read_process()
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
AVDictionary* avdic = nullptr;
|
||||
|
||||
/* 减少卡顿或者花屏现象,相当于增加或扩大了缓冲区,给予编码和发送足够的时间 */
|
||||
av_dict_set(&avdic, "buffer_size", "1024000", 0);
|
||||
// /* 减少采集缓存 */
|
||||
// av_dict_set(&avdic, "preset", "fast", 0);
|
||||
// av_dict_set(&avdic, "tune", "zerolatency", 0);
|
||||
// /* 减少音频采集sampels数量 */
|
||||
// av_dict_set(&avdic, "audio_buffer_size","30", 0);
|
||||
av_dict_set(&avdic, "stimeout", "500000", 0);
|
||||
av_dict_set(&avdic, "max_delay", "500000", 0);
|
||||
av_dict_set(&avdic, "rtsp_transport", "tcp", 0);
|
||||
|
||||
avformat_network_init();
|
||||
|
||||
ifmt_ctx = avformat_alloc_context();
|
||||
|
||||
error = avformat_open_input(&ifmt_ctx, rtspurl.toUtf8().data(), nullptr, &avdic);
|
||||
|
||||
av_dict_free(&avdic);
|
||||
|
||||
if (error != 0)
|
||||
{
|
||||
avformat_free_context(ifmt_ctx);
|
||||
|
||||
HxTrace::debug_write_line("videolivestream", QString("address=%1, avformat_open_input failed, errorcode=%2").arg(rtspurl).arg(error));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(ifmt_ctx, nullptr) < 0)
|
||||
{
|
||||
avformat_close_input(&ifmt_ctx);
|
||||
|
||||
avformat_free_context(ifmt_ctx);
|
||||
|
||||
HxTrace::debug_write_line("videolivestream", QString("address=%1, avformat_find_stream_info failed").arg(rtspurl));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
av_dump_format(ifmt_ctx, 0, rtspurl.toUtf8().data(), 0);
|
||||
|
||||
auto video_stream_index = -1;
|
||||
for (uint i = 0; i < ifmt_ctx->nb_streams; i++)
|
||||
{
|
||||
if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||
{
|
||||
// video_fps = ifmt_ctx->streams[i]->avg_frame_rate.num;
|
||||
// video_width = ifmt_ctx->streams[i]->codecpar->width;
|
||||
// video_height = ifmt_ctx->streams[i]->codecpar->height;
|
||||
video_stream_index = static_cast<int>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (video_stream_index == -1)
|
||||
{
|
||||
avformat_close_input(&ifmt_ctx);
|
||||
avformat_free_context(ifmt_ctx);
|
||||
|
||||
HxTrace::debug_write_line("videolivestream", QString("address=%1, not found video stream").arg(rtspurl));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
AVPacket packet;
|
||||
|
||||
if (av_read_frame(ifmt_ctx, &packet) < 0)
|
||||
{
|
||||
stream_disconnect = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
mutex.lock();
|
||||
frames.append(HxVideoFrame(QDateTime::currentDateTime(), &packet));
|
||||
mutex.unlock();
|
||||
|
||||
av_packet_unref(&packet);
|
||||
|
||||
msleep(10);
|
||||
}
|
||||
|
||||
if (ifmt_ctx != nullptr)
|
||||
{
|
||||
avformat_close_input(&ifmt_ctx);
|
||||
avformat_free_context(ifmt_ctx);
|
||||
ifmt_ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HxDevice::create_recording_file(HxVideoFrame frame)
|
||||
{
|
||||
/* 查找存储介质 */
|
||||
QString root_path;
|
||||
|
||||
foreach(QString path, disks)
|
||||
{
|
||||
auto freeSize = HxDisk::free_size(path);
|
||||
|
||||
if (freeSize > drive_threshold)
|
||||
{
|
||||
root_path = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (root_path.isNull())
|
||||
{
|
||||
build_video_log(tr("录像中断"), tr("存储介质异常"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
video_record.reset();
|
||||
|
||||
video_record.build(channel, frame.time, root_path, code);
|
||||
|
||||
//需要写日志
|
||||
if (!video_writer.open(ifmt_ctx, video_record.path))
|
||||
{
|
||||
video_writer.close();
|
||||
|
||||
build_video_log(tr("录像中断"), video_writer.error());
|
||||
}
|
||||
else
|
||||
{
|
||||
//设置录像标志位
|
||||
video_record.status = RecordStatus::Recording;
|
||||
|
||||
/* 发送录像创建通知 */
|
||||
REPLACE_RECORD_LOG(video_record);
|
||||
|
||||
build_video_log(tr("录像启动"));
|
||||
}
|
||||
}
|
||||
|
||||
void HxDevice::close_recording_file(void)
|
||||
{
|
||||
if (video_record.status == RecordStatus::Recording)
|
||||
{
|
||||
video_writer.close();
|
||||
|
||||
video_record.status = RecordStatus::Recorded;
|
||||
|
||||
video_record.duration = get_video_duration(video_record.path);
|
||||
|
||||
video_record.end_time = QDateTime::currentDateTime();
|
||||
|
||||
REPLACE_RECORD_LOG(video_record);
|
||||
}
|
||||
}
|
||||
|
||||
void HxDevice::write_recording_data(HxVideoFrame frame)
|
||||
{
|
||||
/* 判断是否是I帧 */
|
||||
if (frame.packet->flags & AV_PKT_FLAG_KEY)
|
||||
{
|
||||
/* 当前状态: 未录像 */
|
||||
if (video_record.status != RecordStatus::Recording)
|
||||
{
|
||||
create_recording_file(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 判断录像时长 */
|
||||
if (frame.time >= video_record.start_time.addSecs((int)recording_split_time * 60 + 1))
|
||||
{
|
||||
HxTrace::debug_write_line("HxVideoCaptor", QString("Record PackTime fileName = %1 , %2 - %3").arg(video_record.name, video_record.start_time.toString("yyyy-MM-dd HH:mm:ss"), frame.time.toString("yyyy-MM-dd HH:mm:ss")));
|
||||
|
||||
/* 关闭录像 */
|
||||
close_recording_file();
|
||||
|
||||
/* 重新打开录像 */
|
||||
create_recording_file(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 当前状态: 正在录像 */
|
||||
if (video_record.status == RecordStatus::Recording)
|
||||
{
|
||||
video_writer.send(frame.packet);
|
||||
|
||||
/* 特殊情况: 1. 判断文件是否被删除 */
|
||||
if(!QFile::exists(video_record.path))
|
||||
{
|
||||
/* 关闭录像 */
|
||||
close_recording_file();
|
||||
|
||||
/* 将错误写日志 */
|
||||
build_video_log(tr("录像中断"), tr("文件异常"));
|
||||
|
||||
/* 重新开始录像 */
|
||||
video_record.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int HxDevice::get_video_duration(QString path)
|
||||
{
|
||||
/* 始化网络设备 */
|
||||
avformat_network_init();
|
||||
|
||||
/* 初始化 */
|
||||
AVFormatContext* ifmt_ctx = avformat_alloc_context();
|
||||
|
||||
/* 打开输入文件 */
|
||||
if (avformat_open_input(&ifmt_ctx, path.toUtf8().data(), nullptr, nullptr) < 0)
|
||||
return 0;
|
||||
|
||||
/* 获取流信息 */
|
||||
if (avformat_find_stream_info(ifmt_ctx, nullptr) < 0)
|
||||
{
|
||||
avformat_close_input(&ifmt_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 这里获取的是微秒,需要转成秒 */
|
||||
int64_t tduration = ifmt_ctx->duration;
|
||||
|
||||
/* 打开文件流后,需要关闭,否则会一直占用视频文件,无法进行视频文件的后续操作 */
|
||||
avformat_close_input(&ifmt_ctx);
|
||||
|
||||
/* 销毁 */
|
||||
avformat_free_context(ifmt_ctx);
|
||||
|
||||
return tduration / 1000000;
|
||||
}
|
||||
|
||||
void HxDevice::build_video_log(QString title, QString data)
|
||||
{
|
||||
auto message = title + data;
|
||||
|
||||
if (status_message != message)
|
||||
{
|
||||
status_message = message;
|
||||
|
||||
WRITE_VIDEO_LOG(channel, secondary ? tr("副盘") : tr("主盘") + title, data);
|
||||
}
|
||||
}
|
||||
|
||||
void HxDevice::action()
|
||||
{
|
||||
while (!frames.isEmpty())
|
||||
{
|
||||
mutex.lock();
|
||||
auto frame = frames.dequeue();
|
||||
mutex.unlock();
|
||||
|
||||
write_recording_data(frame);
|
||||
|
||||
frame.free();
|
||||
|
||||
msleep(0);
|
||||
}
|
||||
|
||||
if (stream_disconnect && frames.isEmpty())
|
||||
{
|
||||
close_recording_file();
|
||||
|
||||
build_video_log(tr("录像中断"), tr("摄像头断开连接"));
|
||||
|
||||
stream_disconnect = false;
|
||||
}
|
||||
}
|
||||
|
||||
void HxDevice::continue_with()
|
||||
{
|
||||
close_recording_file();
|
||||
|
||||
build_video_log(tr("录像停止"));
|
||||
}
|
55
HxDevice.h
Normal file
55
HxDevice.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#ifndef HXDEVICE_H
|
||||
#define HXDEVICE_H
|
||||
|
||||
#include "main.h"
|
||||
#include "HxVideoWriter.h"
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
class HxDevice : public HxThread
|
||||
{
|
||||
public:
|
||||
HxDevice(int channel, bool secondary = false, int millisecond = 10);
|
||||
void startup(QJsonObject object);
|
||||
|
||||
private:
|
||||
void frame_read_process(void);
|
||||
void create_recording_file(HxVideoFrame frame);
|
||||
void close_recording_file(void);
|
||||
void write_recording_data(HxVideoFrame frame);
|
||||
int get_video_duration(QString path);
|
||||
void build_video_log(QString title, QString data = "");
|
||||
|
||||
protected:
|
||||
void action() override;
|
||||
void continue_with() override;
|
||||
|
||||
private:
|
||||
QUuid uuid;
|
||||
QString code;
|
||||
/* 每盘保留 GB */
|
||||
int drive_threshold;
|
||||
/* 分包间隔 */
|
||||
int recording_split_time;
|
||||
/* 存储硬盘 */
|
||||
QStringList disks;
|
||||
/* 通道号 */
|
||||
int channel;
|
||||
/* 副盘录像 */
|
||||
int secondary;
|
||||
/* 状态信息 */
|
||||
QString status_message;
|
||||
/* 流断开状态 */
|
||||
bool stream_disconnect = false;
|
||||
/* RTSP流地址 */
|
||||
QString rtspurl;
|
||||
AVFormatContext *ifmt_ctx;
|
||||
|
||||
QMutex mutex;
|
||||
QQueue<HxVideoFrame> frames;
|
||||
HxVideoWriter video_writer;
|
||||
HxVideoRecord video_record;
|
||||
};
|
||||
|
||||
#endif // HXDEVICE_H
|
||||
|
45
HxReadMe.cpp
Normal file
45
HxReadMe.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "HxReadMe.h"
|
||||
|
||||
QString HxReadMe::m_app_name = "VideoCaptor";
|
||||
|
||||
QMap<QString, QList<QString>> HxReadMe::m_version_descriptioon
|
||||
{
|
||||
{"1.00",
|
||||
{
|
||||
"系统重构",
|
||||
}},
|
||||
};
|
||||
|
||||
void HxReadMe::build()
|
||||
{
|
||||
QFile file("ReadMe.md");
|
||||
|
||||
if (file.open(QFile::WriteOnly | QIODevice::Truncate))
|
||||
{
|
||||
QTextStream stream(&file);
|
||||
|
||||
stream << QString("# %1 版本历史介绍").arg(m_app_name) << "\r\n\r\n";
|
||||
|
||||
auto item = m_version_descriptioon.begin();
|
||||
while (item != m_version_descriptioon.end())
|
||||
{
|
||||
stream << QString("## %1").arg(item.key()) << "\r\n";
|
||||
|
||||
foreach(auto str, item.value())
|
||||
{
|
||||
stream << QString("* %1").arg(str) << "\r\n";
|
||||
}
|
||||
|
||||
stream << "\r\n";
|
||||
|
||||
item++;
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
QString HxReadMe::version()
|
||||
{
|
||||
return m_version_descriptioon.firstKey();
|
||||
}
|
19
HxReadMe.h
Normal file
19
HxReadMe.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef HXREADME_H
|
||||
#define HXREADME_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QMap>
|
||||
|
||||
class HxReadMe
|
||||
{
|
||||
public:
|
||||
static void build();
|
||||
static QString version();
|
||||
|
||||
private:
|
||||
static QString m_app_name;
|
||||
static QMap<QString, QList<QString>> m_version_descriptioon;
|
||||
};
|
||||
|
||||
#endif // HXREADME_H
|
172
HxRecordCleanup.cpp
Normal file
172
HxRecordCleanup.cpp
Normal file
|
@ -0,0 +1,172 @@
|
|||
#include "HxRecordCleanup.h"
|
||||
|
||||
#include "HxDisk.h"
|
||||
#include "HxJson.h"
|
||||
#include "HxTrace.h"
|
||||
|
||||
/* 每盘保留 GB */
|
||||
int drive_threshold;
|
||||
/* 录像保留时间 */
|
||||
int recording_reserver_time;
|
||||
/* 磁盘空间若不足时自动覆盖 */
|
||||
bool is_cleanup_enabled;
|
||||
/* 保留期限若超过时强制删除 */
|
||||
bool is_cleanup_forced_for_reserve_time;
|
||||
/* 存储硬盘 */
|
||||
QStringList disks;
|
||||
|
||||
QList<HxVideoRecord> records;
|
||||
static bool query_record_errorcode = false;
|
||||
|
||||
void HxRecordCleanup::set(QJsonObject object)
|
||||
{
|
||||
drive_threshold = HxJson::to_int(object, "drive_threshold");
|
||||
recording_reserver_time = HxJson::to_int(object, "recording_reserver_time");
|
||||
is_cleanup_enabled = HxJson::to_boolean(object, "is_cleanup_enabled");
|
||||
is_cleanup_forced_for_reserve_time = HxJson::to_boolean(object, "is_cleanup_forced_for_reserve_time");
|
||||
|
||||
disks.clear();
|
||||
foreach(QString d, HxJson::to_string_list(object, "disks", ";"))
|
||||
{
|
||||
if (HxDisk::exist(d))
|
||||
{
|
||||
disks.append(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HxRecordCleanup::set_records_array(QJsonArray array)
|
||||
{
|
||||
records.clear();
|
||||
|
||||
for (int i = 0; i < array.size(); i++)
|
||||
{
|
||||
auto object = array.at(i).toObject();
|
||||
|
||||
HxVideoRecord record;
|
||||
|
||||
record.channel = object.value("channel").toInt();
|
||||
record.start_time = QDateTime::fromString(object.value("start_time").toString(), "yyyy-MM-dd HH:mm:ss");
|
||||
record.end_time = object.value("end_time").toString() == "" ? QDateTime() : QDateTime::fromString(object.value("end_time").toString(), "yyyy-MM-dd HH:mm:ss");
|
||||
record.duration = object.value("duration").toInt();
|
||||
record.name = object.value("name").toString();
|
||||
record.path = object.value("path").toString();
|
||||
|
||||
records.append(record);
|
||||
}
|
||||
|
||||
query_record_errorcode = records.count() > 0;
|
||||
}
|
||||
|
||||
void HxRecordCleanup::process(void)
|
||||
{
|
||||
/* 保留期限若超过时强制删除 */
|
||||
if (is_cleanup_forced_for_reserve_time)
|
||||
{
|
||||
/* 存储期限判断 */
|
||||
for (int i = 0; i < CHANNEL_MAX; i++)
|
||||
{
|
||||
if (query_record(i, QDate::currentDate().addDays((recording_reserver_time + 1) * -1), QDate::currentDate().addDays(-1)))
|
||||
{
|
||||
/* delete */
|
||||
for (int i = 0; i < records.size(); i++)
|
||||
remove(records.at(i));
|
||||
}
|
||||
|
||||
QThread::msleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
/* 保留期限若超过时强制删除 */
|
||||
if (is_cleanup_enabled)
|
||||
{
|
||||
int needCleanNum = 0;
|
||||
|
||||
if (disks.count() == 0)
|
||||
{
|
||||
/* 存储介质丢失 */
|
||||
HxTrace::debug_write_line("delete", "存储介质丢失");
|
||||
return;
|
||||
}
|
||||
|
||||
/* */
|
||||
foreach(QString drive, disks)
|
||||
{
|
||||
auto size = HxDisk::free_size(drive);
|
||||
|
||||
/* 判断磁盘可用空间 */
|
||||
if (size <= (drive_threshold + 2))
|
||||
{
|
||||
needCleanNum++;
|
||||
|
||||
HxTrace::debug_write_line("delete", QString("need clean num = %1/%2").arg(needCleanNum).arg(disks.count()));
|
||||
}
|
||||
}
|
||||
|
||||
/* 所有磁盘都满了的情况下, 删除最早的录像 */
|
||||
if (needCleanNum == disks.count())
|
||||
{
|
||||
if (query_first_record())
|
||||
{
|
||||
auto date = records[0].start_time.date();
|
||||
|
||||
if (!query_record(date))
|
||||
return;
|
||||
|
||||
HxTrace::debug_write_line("delete", QString("need clean record count = %1").arg(records.size()));
|
||||
|
||||
/* delete */
|
||||
for (int i = 0; i < records.size(); i++)
|
||||
{
|
||||
remove(records.at(i));
|
||||
|
||||
HxTrace::debug_write_line("delete", QString("delete file = %1/%2").arg(i + 1).arg(records.size()));
|
||||
}
|
||||
|
||||
HxBroadcast::publish_json(7, { {"date", date.toString("yyyy-MM-dd")} });
|
||||
|
||||
HxTrace::debug_write_line("delete", "finished");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HxRecordCleanup::query_first_record()
|
||||
{
|
||||
query_record_errorcode = false;
|
||||
|
||||
HxBroadcast::publish_json(5);
|
||||
|
||||
return HxTask::wait([=]() -> bool { return query_record_errorcode; }, 3000);
|
||||
}
|
||||
|
||||
bool HxRecordCleanup::query_record(QDate date)
|
||||
{
|
||||
query_record_errorcode = false;
|
||||
|
||||
HxBroadcast::publish_json(5, { {"date", date.toString("yyyy-MM-dd")} });
|
||||
|
||||
return HxTask::wait([=]() -> bool { return query_record_errorcode; }, 3000);
|
||||
}
|
||||
|
||||
bool HxRecordCleanup::query_record(int channel, QDate start_date, QDate end_date)
|
||||
{
|
||||
query_record_errorcode = false;
|
||||
|
||||
HxBroadcast::publish_json(5, { {"channel", channel},
|
||||
{"start_date", start_date.toString("yyyy-MM-dd")},
|
||||
{"end_date", end_date.toString("yyyy-MM-dd")} });
|
||||
|
||||
return HxTask::wait([=]() -> bool { return query_record_errorcode; }, 3000);
|
||||
}
|
||||
|
||||
void HxRecordCleanup::remove(HxVideoRecord record)
|
||||
{
|
||||
/* delete file */
|
||||
HxDisk::remove(record.path);
|
||||
|
||||
/* delete database */
|
||||
HxBroadcast::publish_json(7, { {"path", record.path} });
|
||||
|
||||
HxTrace::debug_write_line("delete", QString("delete record %1").arg(record.name));
|
||||
}
|
20
HxRecordCleanup.h
Normal file
20
HxRecordCleanup.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#ifndef HXRECORDCLEANUP_H
|
||||
#define HXRECORDCLEANUP_H
|
||||
|
||||
#include "main.h"
|
||||
|
||||
class HxRecordCleanup
|
||||
{
|
||||
public:
|
||||
static void set(QJsonObject object);
|
||||
static void set_records_array(QJsonArray array);
|
||||
static void process(void);
|
||||
|
||||
private:
|
||||
static bool query_first_record();
|
||||
static bool query_record(QDate date);
|
||||
static bool query_record(int channel, QDate start_date, QDate end_date);
|
||||
static void remove(HxVideoRecord record);
|
||||
};
|
||||
|
||||
#endif // HXRECORDCLEANUP_H
|
62
HxVideoCaptor.pro
Normal file
62
HxVideoCaptor.pro
Normal file
|
@ -0,0 +1,62 @@
|
|||
QT -= gui
|
||||
QT += sql
|
||||
QT += concurrent
|
||||
QT += network
|
||||
|
||||
CONFIG += c++11 console
|
||||
CONFIG -= app_bundle
|
||||
|
||||
CONFIG += debug_and_release
|
||||
|
||||
unix {
|
||||
CONFIG(debug, debug|release){
|
||||
TARGET = debug/HxVideoCaptor
|
||||
} else {
|
||||
TARGET = release/HxVideoCaptor
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
HEADERS += \
|
||||
HxDevice.h \
|
||||
HxReadMe.h \
|
||||
HxRecordCleanup.h \
|
||||
HxVideoWriter.h \
|
||||
main.h
|
||||
|
||||
SOURCES += \
|
||||
HxDevice.cpp \
|
||||
HxReadMe.cpp \
|
||||
HxRecordCleanup.cpp \
|
||||
HxVideoWriter.cpp \
|
||||
main.cpp
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
INCLUDEPATH += $$PWD/external/HxUtils/include
|
||||
LIBS += -L$$PWD/external/HxUtils -lHxUtils
|
||||
PRE_TARGETDEPS += $$PWD/external/HxUtils/libHxUtils.a
|
||||
|
||||
unix {
|
||||
INCLUDEPATH += /usr/local/ffmpeg/include
|
||||
LIBS += -L/usr/local/ffmpeg/lib -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
|
||||
}
|
||||
|
||||
win32 {
|
||||
INCLUDEPATH += D:/Library/ffmpeg/4.4.4/include
|
||||
LIBS += -LD:/Library/ffmpeg/4.4.4/lib -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
|
||||
}
|
||||
|
209
HxVideoWriter.cpp
Normal file
209
HxVideoWriter.cpp
Normal file
|
@ -0,0 +1,209 @@
|
|||
#include "HxVideoWriter.h"
|
||||
|
||||
#include "HxTask.h"
|
||||
|
||||
HxVideoWriter::HxVideoWriter() : m_status(false) {}
|
||||
|
||||
bool HxVideoWriter::open(AVFormatContext *ifmt_ctx, QString filename)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
video_index = -1;
|
||||
audio_index = -1;
|
||||
|
||||
video_frame_index = 0;
|
||||
audio_frame_index = 0;
|
||||
|
||||
ofmt_ctx = nullptr;
|
||||
|
||||
video_dec_stream = nullptr;
|
||||
audio_dec_stream = nullptr;
|
||||
|
||||
/* 查找流中视频与音频位置 */
|
||||
if(ifmt_ctx->nb_streams > 2)
|
||||
{
|
||||
m_error = QObject::tr("数据流异常");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < ifmt_ctx->nb_streams; i++)
|
||||
{
|
||||
if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||
{
|
||||
video_index = static_cast<int>(i);
|
||||
|
||||
video_dec_stream = ifmt_ctx->streams[i];
|
||||
}
|
||||
else if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
{
|
||||
audio_index = static_cast<int>(i);
|
||||
|
||||
audio_dec_stream = ifmt_ctx->streams[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (video_index == -1)
|
||||
{
|
||||
m_error = QObject::tr("未找到视频流");
|
||||
return false;
|
||||
}
|
||||
|
||||
avformat_alloc_output_context2(&ofmt_ctx, nullptr, nullptr, filename.toStdString().data());
|
||||
|
||||
if (!ofmt_ctx)
|
||||
{
|
||||
m_error = QObject::tr("无法打开输出文件");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < ifmt_ctx->nb_streams; i++)
|
||||
{
|
||||
/* 根据输入流创建输出流 */
|
||||
AVCodec *codec = avcodec_find_decoder(ifmt_ctx->streams[i]->codecpar->codec_id);
|
||||
|
||||
AVStream *out_stream = avformat_new_stream(ofmt_ctx, codec);
|
||||
if (!out_stream)
|
||||
{
|
||||
m_error = QObject::tr("无法创建输出流");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 复制AVCodecContext的设置 */
|
||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||
ret = avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[i]->codecpar);
|
||||
if (ret < 0)
|
||||
{
|
||||
m_error = QObject::tr("未能将输入流中的codepar复制到编解码器上下文");
|
||||
avcodec_free_context(&codec_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
codec_ctx->codec_tag = 0;
|
||||
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
|
||||
if (ret < 0)
|
||||
{
|
||||
m_error = QObject::tr("无法将编解码器上下文复制到输出流codepar上下文");
|
||||
avcodec_free_context(&codec_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
avcodec_free_context(&codec_ctx);
|
||||
}
|
||||
|
||||
ofmt_ctx->streams[video_index]->time_base = {1, 25};
|
||||
|
||||
av_dump_format(ofmt_ctx, 0, filename.toStdString().data(), 1);
|
||||
|
||||
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
|
||||
{
|
||||
ret = avio_open(&ofmt_ctx->pb, filename.toStdString().data(), AVIO_FLAG_WRITE);
|
||||
if (ret < 0)
|
||||
{
|
||||
m_error = QObject::tr("无法打开输出文件");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ret = avformat_write_header(ofmt_ctx, nullptr);
|
||||
if (ret < 0)
|
||||
{
|
||||
m_error = QObject::tr("无法写入文件头信息");
|
||||
return false;
|
||||
}
|
||||
|
||||
return (m_status = true);
|
||||
}
|
||||
|
||||
void HxVideoWriter::send(AVPacket *packet)
|
||||
{
|
||||
auto out_stream = ofmt_ctx->streams[packet->stream_index];
|
||||
|
||||
if (packet->stream_index == video_index)
|
||||
{
|
||||
auto pts = av_rescale_q_rnd(packet->pts, video_dec_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
auto dts = av_rescale_q_rnd(packet->dts, video_dec_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
|
||||
if (video_frame_index == 0)
|
||||
{
|
||||
video_pts = pts < 0 ? 0 : pts;
|
||||
video_dts = dts < 0 ? 0 : dts;
|
||||
}
|
||||
else if (video_frame_index == 1)
|
||||
{
|
||||
if (video_pts > pts)
|
||||
video_pts = pts;
|
||||
else
|
||||
video_pts = pts - video_pts;
|
||||
|
||||
if (video_dts > dts)
|
||||
video_dts = dts;
|
||||
else
|
||||
video_dts = dts - video_dts;
|
||||
}
|
||||
|
||||
packet->pts = video_pts * video_frame_index;
|
||||
packet->dts = video_dts * video_frame_index;
|
||||
packet->duration = 0;
|
||||
|
||||
video_frame_index++;
|
||||
}
|
||||
else if (packet->stream_index == audio_index)
|
||||
{
|
||||
auto pts = av_rescale_q_rnd(packet->pts, audio_dec_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
auto dts = av_rescale_q_rnd(packet->dts, audio_dec_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
||||
|
||||
if (audio_frame_index == 1 && pts > 0)
|
||||
{
|
||||
audio_pts = pts - audio_pts;
|
||||
audio_dts = dts - audio_dts;
|
||||
}
|
||||
else if (audio_frame_index == 0)
|
||||
{
|
||||
audio_pts = pts < 0 ? 0 : pts;
|
||||
audio_dts = dts < 0 ? 0 : dts;
|
||||
}
|
||||
|
||||
packet->pts = audio_pts * audio_frame_index;
|
||||
packet->dts = audio_dts * audio_frame_index;
|
||||
packet->duration = 0;
|
||||
|
||||
audio_frame_index++;
|
||||
}
|
||||
|
||||
auto ret = av_interleaved_write_frame(ofmt_ctx, packet);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
HxTrace::debug_write_line("VideoWrite", QString("Error muxing packet, ret = %1").arg(ret));
|
||||
|
||||
//这里是否需要做处理 ???
|
||||
}
|
||||
}
|
||||
|
||||
void HxVideoWriter::close()
|
||||
{
|
||||
//Write file trailer
|
||||
if (m_status)
|
||||
av_write_trailer(ofmt_ctx);
|
||||
|
||||
video_pts = 0;
|
||||
video_dts = 0;
|
||||
audio_pts = 0;
|
||||
audio_dts = 0;
|
||||
|
||||
video_frame_index = 0;
|
||||
audio_frame_index = 0;
|
||||
|
||||
/* close output */
|
||||
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
|
||||
avio_close(ofmt_ctx->pb);
|
||||
|
||||
avformat_free_context(ofmt_ctx);
|
||||
|
||||
m_status = false;
|
||||
}
|
||||
|
||||
QString HxVideoWriter::error() { return m_error; }
|
30
HxVideoWriter.h
Normal file
30
HxVideoWriter.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#ifndef HXVIDEOWRITER_H
|
||||
#define HXVIDEOWRITER_H
|
||||
|
||||
#include "main.h"
|
||||
|
||||
|
||||
class HxVideoWriter
|
||||
{
|
||||
public:
|
||||
HxVideoWriter();
|
||||
|
||||
bool open(AVFormatContext *ifmt_ctx, QString filename);
|
||||
void send(AVPacket *packet);
|
||||
void close();
|
||||
|
||||
QString error();
|
||||
|
||||
private:
|
||||
bool m_status;
|
||||
QString m_error;
|
||||
|
||||
int video_index, audio_index;
|
||||
int video_frame_index, audio_frame_index;
|
||||
long long video_pts, video_dts, audio_pts, audio_dts;
|
||||
|
||||
AVFormatContext *ofmt_ctx;
|
||||
AVStream *video_dec_stream, *audio_dec_stream;
|
||||
};
|
||||
|
||||
#endif // HXVIDEOWRITER_H
|
50
external/HxUtils/include/HxBroadcast.h
vendored
Normal file
50
external/HxUtils/include/HxBroadcast.h
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef HXBROADCAST_H
|
||||
#define HXBROADCAST_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
|
||||
class HxBroadcast : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static HxBroadcast* context();
|
||||
|
||||
/**
|
||||
* @brief 初始化
|
||||
*
|
||||
* @param port 广播端口
|
||||
*/
|
||||
static void initialization(int port);
|
||||
|
||||
/**
|
||||
* @brief 发布消息
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
static void publish(QString message);
|
||||
|
||||
/**
|
||||
* @brief 发布消息 (JSON格式)
|
||||
*
|
||||
* @param action_type 操作类型
|
||||
* @param args msgInfo 参数
|
||||
*/
|
||||
static void publish_json(int action_type, std::initializer_list<QPair<QString, QJsonValue>> args = std::initializer_list<QPair<QString, QJsonValue>>());
|
||||
|
||||
private:
|
||||
void publish_achieve(QString message);
|
||||
|
||||
signals:
|
||||
void publish_event(QString message);
|
||||
void receive_event(QByteArray data);
|
||||
|
||||
private slots:
|
||||
void receive_ready_read();
|
||||
|
||||
private:
|
||||
int port;
|
||||
QUdpSocket *socket;
|
||||
};
|
||||
|
||||
#endif // HXBROADCAST_H
|
65
external/HxUtils/include/HxDisk.h
vendored
Normal file
65
external/HxUtils/include/HxDisk.h
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
#ifndef HXDISK_H
|
||||
#define HXDISK_H
|
||||
|
||||
#include "HxTrace.h"
|
||||
|
||||
#include <QStorageInfo>
|
||||
#include <QtGlobal>
|
||||
|
||||
class HxDisk
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief 创建文件夹
|
||||
* @param path 路径
|
||||
*/
|
||||
static void mkpath(QString path);
|
||||
|
||||
|
||||
static void mkpath(QStringList names);
|
||||
|
||||
/**
|
||||
* @brief 判断盘符是否存在
|
||||
* @param rootPath 路径
|
||||
* @return
|
||||
*/
|
||||
static bool exist(QString rootPath);
|
||||
|
||||
|
||||
static bool empty(QString path);
|
||||
|
||||
/**
|
||||
* @brief 遍历盘符信息
|
||||
* @return 返回磁盘信息
|
||||
*/
|
||||
static QList<QStorageInfo> mounted_volumes();
|
||||
|
||||
/**
|
||||
* @brief 获取磁盘总空间大小
|
||||
* @param rootPath 路径
|
||||
* @return 总空间, 单位: G
|
||||
*/
|
||||
static double total_size(QString rootPath);
|
||||
|
||||
/**
|
||||
* @brief 获取磁盘剩余空间大小
|
||||
* @param rootPath 路径
|
||||
* @return 剩余空间, 单位: G
|
||||
*/
|
||||
static double free_size(QString rootPath);
|
||||
|
||||
/**
|
||||
* @brief 检索目录信息总数
|
||||
* @param path 目录路径
|
||||
* @return 文件总数
|
||||
*/
|
||||
static int entry_info_list(QString path);
|
||||
|
||||
/**
|
||||
* @brief 删除文件并判断目录是否为空, 为空时删除目录
|
||||
* @param filepath 文件路径
|
||||
*/
|
||||
static void remove(QString filepath);
|
||||
};
|
||||
|
||||
#endif
|
15
external/HxUtils/include/HxJson.h
vendored
Normal file
15
external/HxUtils/include/HxJson.h
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef HXJSON_H
|
||||
#define HXJSON_H
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
class HxJson
|
||||
{
|
||||
public:
|
||||
static int to_int(QJsonObject object, QString key);
|
||||
static bool to_boolean(QJsonObject object, QString key);
|
||||
static QString to_string(QJsonObject object, QString key);
|
||||
static QStringList to_string_list(QJsonObject object, QString key, QString split);
|
||||
};
|
||||
|
||||
#endif // HXJSON_H
|
15
external/HxUtils/include/HxLog.h
vendored
Normal file
15
external/HxUtils/include/HxLog.h
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef HXLOG_H
|
||||
#define HXLOG_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
class HxLog
|
||||
{
|
||||
public:
|
||||
static void append(QString title, QString message);
|
||||
|
||||
private:
|
||||
static QMutex mutex;
|
||||
};
|
||||
|
||||
#endif // HXLOG_H
|
19
external/HxUtils/include/HxProcess.h
vendored
Normal file
19
external/HxUtils/include/HxProcess.h
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef HXPROCESS_H
|
||||
#define HXPROCESS_H
|
||||
|
||||
#include <QProcess>
|
||||
#include <HxTrace.h>
|
||||
|
||||
class HxProcess
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief 同步启动进程
|
||||
* @param command 参数
|
||||
* @return
|
||||
*/
|
||||
static QString start(QString command);
|
||||
|
||||
};
|
||||
|
||||
#endif // HXPROCESS_H
|
41
external/HxUtils/include/HxSocket.h
vendored
Normal file
41
external/HxUtils/include/HxSocket.h
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef HXSOCKET_H
|
||||
#define HXSOCKET_H
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QTcpServer>
|
||||
|
||||
class HxSocket: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
HxSocket(quint16 port);
|
||||
|
||||
HxSocket(QString address, int port);
|
||||
|
||||
signals:
|
||||
void data_receive_event(QByteArray data);
|
||||
void reconnection_event(void);
|
||||
|
||||
public slots:
|
||||
void new_connection();
|
||||
|
||||
void write(QByteArray data);
|
||||
|
||||
void ready_read();
|
||||
|
||||
void disconnected();
|
||||
|
||||
/**
|
||||
* @brief 重连
|
||||
*/
|
||||
void reconnection();
|
||||
|
||||
private:
|
||||
int port;
|
||||
QString address;
|
||||
bool is_reconnect = false;
|
||||
QTcpServer server;
|
||||
QTcpSocket *socket = nullptr;
|
||||
};
|
||||
|
||||
#endif // HXSOCKET_H
|
24
external/HxUtils/include/HxSql.h
vendored
Normal file
24
external/HxUtils/include/HxSql.h
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef HXSQL_H
|
||||
#define HXSQL_H
|
||||
|
||||
#include <QSqlDatabase>
|
||||
|
||||
class HxSql
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief 打开数据库
|
||||
* @param filepath 数据库文件路径
|
||||
* @param connectionName 连接名
|
||||
* @return QSqlDatabase 数据库对象
|
||||
*/
|
||||
static QSqlDatabase open(QString filepath, QString connectionName);
|
||||
|
||||
/**
|
||||
* @brief 关闭数据库
|
||||
* @param connectionName 连接名
|
||||
*/
|
||||
static void close(QString connectionName);
|
||||
};
|
||||
|
||||
#endif // HXSQL_H
|
84
external/HxUtils/include/HxSystem.h
vendored
Normal file
84
external/HxUtils/include/HxSystem.h
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
#ifndef HXSYSTEM_H
|
||||
#define HXSYSTEM_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class HxSystem
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief 获取系统cpu型号
|
||||
* @return 系统cpu型号
|
||||
*/
|
||||
static QString get_cpu_model();
|
||||
|
||||
/**
|
||||
* @brief 获取系统逻辑CPU的个数
|
||||
* @return 系统逻辑CPU的个数
|
||||
*/
|
||||
static int get_logical_cpu_number();
|
||||
|
||||
/**
|
||||
* @brief 获取系统cpu温度
|
||||
* @param temp cpu温度
|
||||
* @return true: 成功; false 失败;
|
||||
*/
|
||||
static bool get_cpu_temp(double *temp);
|
||||
|
||||
/**
|
||||
* @brief 获取系统cpu状态
|
||||
* @param cpu_rate cpu使用率
|
||||
* @return true: 成功; false 失败;
|
||||
*/
|
||||
static bool get_cpu_status(double *cpu_rate);
|
||||
|
||||
/**
|
||||
* @brief 获取系统内存状态
|
||||
* @param memory_use 已用内存
|
||||
* @param memory_total 内存总大小
|
||||
* @param swap_use 已用交换空间
|
||||
* @param swap_total 交换空间总大小
|
||||
* @return true: 成功; false 失败;
|
||||
*/
|
||||
static bool get_memory_status(double *memory_use, double *memory_total, double *swap_use, double *swap_total);
|
||||
|
||||
/**
|
||||
* @brief 获取程序状态
|
||||
* @param cpu_usage CPU使用率
|
||||
* @param virtual_memory 虚拟内存使用量
|
||||
* @param resident_memory 物理内存使用量
|
||||
* @return true: 成功; false 失败;
|
||||
*/
|
||||
static bool get_program_status(double *cpu_usage, double *virtual_memory, double *resident_memory);
|
||||
|
||||
/**
|
||||
* @brief 获取硬盘状态
|
||||
* @param disk 路径
|
||||
* @param file_system 文件系统(磁盘路径)
|
||||
* @param size 总大小
|
||||
* @param use 已用大小
|
||||
* @param read_speed 读速度
|
||||
* @param write_speed 写速度
|
||||
* @return true: 成功; false 失败;
|
||||
*/
|
||||
static bool get_harddisk_status(QString disk, QString *file_system, QString *size, QString *use, double *read_speed, double *write_speed);
|
||||
|
||||
/**
|
||||
* @brief 获取硬盘温度
|
||||
* @param file_system 文件系统(磁盘路径)
|
||||
* @param temperature 温度
|
||||
* @return true: 成功; false 失败;
|
||||
*/
|
||||
static bool get_harddisk_temperature(QString file_system, QString *temperature);
|
||||
|
||||
/**
|
||||
* @brief 获取硬盘健康状态
|
||||
* @param file_system 文件系统(磁盘路径)
|
||||
* @param smart 健康状态
|
||||
* @return true: 成功; false 失败;
|
||||
*/
|
||||
static bool get_harddisk_smart(QString file_system, QString *smart);
|
||||
|
||||
};
|
||||
|
||||
#endif // HXSYSTEM_H
|
228
external/HxUtils/include/HxTask.h
vendored
Normal file
228
external/HxUtils/include/HxTask.h
vendored
Normal file
|
@ -0,0 +1,228 @@
|
|||
#ifndef HXTASK_H
|
||||
#define HXTASK_H
|
||||
|
||||
#include "HxTrace.h"
|
||||
#include <QtConcurrent>
|
||||
|
||||
class HxTask
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief 停止
|
||||
* @param uuid 任务唯一编码
|
||||
*/
|
||||
static void stop(QUuid uuid)
|
||||
{
|
||||
if (dispatchers.contains(uuid))
|
||||
{
|
||||
dispatchers[uuid] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 等待结果
|
||||
* @param functor 函数, 返回值为 bool
|
||||
* @param millisecond 最大等待时长
|
||||
*/
|
||||
template <typename Functor>
|
||||
static bool wait(Functor functor, int millisecond)
|
||||
{
|
||||
auto timestamp = QDateTime::currentDateTime();
|
||||
|
||||
while(!functor())
|
||||
{
|
||||
if (QDateTime::currentDateTime() > timestamp.addMSecs(millisecond))
|
||||
return false;
|
||||
|
||||
QThread::msleep(10);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Functor, typename Arg1>
|
||||
static QFuture<void> invoke(Functor functor, const Arg1 &arg1) { return QtConcurrent::run(functor, arg1); }
|
||||
|
||||
template <typename Functor, typename Arg1, typename Arg2>
|
||||
static QFuture<void> invoke(Functor functor, const Arg1 &arg1, const Arg2 &arg2) { return QtConcurrent::run(functor, arg1, arg2); }
|
||||
|
||||
template <typename Functor, typename Arg1, typename Arg2, typename Arg3>
|
||||
static QFuture<void> invoke(Functor functor, const Arg1 &arg1, const Arg2 &arg2, const Arg3 &arg3) { return QtConcurrent::run(functor, arg1, arg2, arg3); }
|
||||
|
||||
/**
|
||||
* @brief 异步执行
|
||||
* @param functor 函数
|
||||
*/
|
||||
template <typename Functor>
|
||||
static QFuture<void> invoke(Functor functor) { return QtConcurrent::run(functor); }
|
||||
|
||||
/**
|
||||
* @brief 异步执行
|
||||
* @param functor 函数
|
||||
* @param millisecond 函数执行间隔频率,单位毫秒
|
||||
* @param uuid 任务唯一编码
|
||||
*/
|
||||
template <typename Functor>
|
||||
static void run(Functor functor, int millisecond, QUuid uuid)
|
||||
{
|
||||
dispatchers.insert(uuid, true);
|
||||
|
||||
QtConcurrent::run(
|
||||
[=](Functor functor, int _millisecond, QUuid _uuid)
|
||||
{
|
||||
HxTrace::debug_write_line("HxTask", QString("Thread: %1, start").arg(_uuid.toString()));
|
||||
|
||||
while (dispatchers[_uuid])
|
||||
{
|
||||
functor();
|
||||
|
||||
QThread::msleep(_millisecond);
|
||||
}
|
||||
|
||||
HxTrace::debug_write_line("HxTask", QString("Thread: %1, stop").arg(_uuid.toString()));
|
||||
|
||||
dispatchers.remove(_uuid);
|
||||
},
|
||||
functor, millisecond, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 异步执行
|
||||
* @param object 操作对象类
|
||||
* @param fn 操作对象类中的成员函数
|
||||
*/
|
||||
template <typename T, typename Class>
|
||||
static void run(Class *object, T (Class::*fn)())
|
||||
{
|
||||
QtConcurrent::run(
|
||||
[=](Class *_object, T (Class::*_fn)())
|
||||
{
|
||||
(_object->*_fn)();
|
||||
},
|
||||
object, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 异步执行线程
|
||||
* @param object 操作对象类
|
||||
* @param fn 操作对象类中的成员函数
|
||||
* @param millisecond 函数执行间隔频率,单位毫秒
|
||||
* @param uuid 任务唯一编码
|
||||
*/
|
||||
template <typename T, typename Class>
|
||||
static void run(Class *object, T (Class::*fn)(), int millisecond, QUuid uuid)
|
||||
{
|
||||
dispatchers.insert(uuid, true);
|
||||
|
||||
QtConcurrent::run(
|
||||
[=](Class *_object, T (Class::*_fn)(), int _millisecond, QUuid _uuid)
|
||||
{
|
||||
HxTrace::debug_write_line("HxTask", QString("Thread: %1, start").arg(_uuid.toString()));
|
||||
|
||||
while (dispatchers[_uuid])
|
||||
{
|
||||
(_object->*_fn)();
|
||||
|
||||
QThread::msleep(_millisecond);
|
||||
}
|
||||
|
||||
HxTrace::debug_write_line("HxTask", QString("Thread: %1, stop").arg(_uuid.toString()));
|
||||
|
||||
dispatchers.remove(_uuid);
|
||||
},
|
||||
object, fn, millisecond, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 任务并行
|
||||
* @param object 操作对象类
|
||||
* @param fn1 操作对象类中的成员函数1
|
||||
* @param millisecond1 函数1执行间隔频率,单位毫秒
|
||||
* @param fn2 操作对象类中的成员函数2
|
||||
* @param millisecond2 函数2执行间隔频率,单位毫秒
|
||||
* @return 任务唯一编码
|
||||
*/
|
||||
template <typename T, typename Class>
|
||||
static QUuid invoke(Class *object, T (Class::*fn1)(), int millisecond1, T (Class::*fn2)(), int millisecond2)
|
||||
{
|
||||
QUuid uuid = QUuid::createUuid();
|
||||
|
||||
HxTask::run(uuid, object, fn1, millisecond1);
|
||||
HxTask::run(uuid, object, fn2, millisecond2);
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 任务并行
|
||||
* @param uuid 任务唯一编码
|
||||
* @param object 操作对象类
|
||||
* @param fn1 操作对象类中的成员函数1
|
||||
* @param millisecond1 函数1执行间隔频率,单位毫秒
|
||||
* @param fn2 操作对象类中的成员函数2
|
||||
* @param millisecond2 函数2执行间隔频率,单位毫秒
|
||||
* @return 任务唯一编码
|
||||
*/
|
||||
template <typename T, typename Class>
|
||||
static void invoke(QUuid uuid, Class *object, T (Class::*fn1)(), int millisecond1, T (Class::*fn2)(), int millisecond2)
|
||||
{
|
||||
HxTask::run(uuid, object, fn1, millisecond1);
|
||||
HxTask::run(uuid, object, fn2, millisecond2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 任务并行
|
||||
* @param object 操作对象类
|
||||
* @param fn1 操作对象类中的成员函数1
|
||||
* @param millisecond1 函数1执行间隔频率,单位毫秒
|
||||
* @param fn2 操作对象类中的成员函数2
|
||||
* @param millisecond2 函数2执行间隔频率,单位毫秒
|
||||
* @param fn3 操作对象类中的成员函数3
|
||||
* @param millisecond3 函数3执行间隔频率,单位毫秒
|
||||
* @return 任务唯一编码
|
||||
*/
|
||||
template <typename T, typename Class>
|
||||
static QUuid invoke(Class *object, T (Class::*fn1)(), int millisecond1, T (Class::*fn2)(), int millisecond2, T (Class::*fn3)(), int millisecond3)
|
||||
{
|
||||
QUuid uuid = QUuid::createUuid();
|
||||
|
||||
HxTask::run(uuid, object, fn1, millisecond1);
|
||||
HxTask::run(uuid, object, fn2, millisecond2);
|
||||
HxTask::run(uuid, object, fn3, millisecond3);
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 任务并行
|
||||
* @param uuid 任务唯一编码
|
||||
* @param object 操作对象类
|
||||
* @param fn1 操作对象类中的成员函数1
|
||||
* @param millisecond1 函数1执行间隔频率,单位毫秒
|
||||
* @param fn2 操作对象类中的成员函数2
|
||||
* @param millisecond2 函数2执行间隔频率,单位毫秒
|
||||
* @param fn3 操作对象类中的成员函数3
|
||||
* @param millisecond3 函数3执行间隔频率,单位毫秒
|
||||
* @return 任务唯一编码
|
||||
*/
|
||||
template <typename T, typename Class>
|
||||
static void invoke(QUuid uuid, Class *object, T (Class::*fn1)(), int millisecond1, T (Class::*fn2)(), int millisecond2, T (Class::*fn3)(), int millisecond3)
|
||||
{
|
||||
HxTask::run(uuid, object, fn1, millisecond1);
|
||||
HxTask::run(uuid, object, fn2, millisecond2);
|
||||
HxTask::run(uuid, object, fn3, millisecond3);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief mutex
|
||||
*/
|
||||
static QMutex mutex;
|
||||
|
||||
/**
|
||||
* @brief dispatchers
|
||||
*/
|
||||
static QMap<QUuid, bool> dispatchers;
|
||||
};
|
||||
|
||||
#endif // HXTASK_H
|
60
external/HxUtils/include/HxThread.h
vendored
Normal file
60
external/HxUtils/include/HxThread.h
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef HXTHREAD_H
|
||||
#define HXTHREAD_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QtConcurrent>
|
||||
|
||||
class HxThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief 线程类初始化
|
||||
* @param millisecond
|
||||
*/
|
||||
HxThread(int millisecond);
|
||||
|
||||
/**
|
||||
* @brief 停止
|
||||
*/
|
||||
void stop();
|
||||
|
||||
static void sleep(int millisecond);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 线程任务
|
||||
*/
|
||||
virtual void action();
|
||||
|
||||
/**
|
||||
* @brief 线程任务结束后的处理任务
|
||||
*/
|
||||
virtual void continue_with();
|
||||
|
||||
/**
|
||||
* @brief 线程
|
||||
*/
|
||||
virtual void run();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 线程状态. true: 运行; false: 结束;
|
||||
*/
|
||||
bool m_thread_status = false;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 线程轮询间隔, 单位毫秒
|
||||
* @
|
||||
*/
|
||||
int m_wait_time;
|
||||
|
||||
/**
|
||||
* @brief 线程停止标志
|
||||
*/
|
||||
bool m_stop_flags = true;
|
||||
};
|
||||
|
||||
#endif // HXTHREAD_H
|
24
external/HxUtils/include/HxTrace.h
vendored
Normal file
24
external/HxUtils/include/HxTrace.h
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef HXTRACE_H
|
||||
#define HXTRACE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class HxTrace
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief 调试信息输出
|
||||
* @param title 标题
|
||||
* @param message 信息
|
||||
*/
|
||||
static void debug_write_line(QString title, QString message);
|
||||
|
||||
/**
|
||||
* @brief 调试信息输出
|
||||
* @param title 标题
|
||||
* @param format 信息
|
||||
*/
|
||||
static void debug_write_line(QString title, const char *format, ...);
|
||||
};
|
||||
|
||||
#endif // HXTRACE_H
|
BIN
external/HxUtils/libHxUtils.a
vendored
Normal file
BIN
external/HxUtils/libHxUtils.a
vendored
Normal file
Binary file not shown.
77
main.cpp
Normal file
77
main.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#include "main.h"
|
||||
|
||||
#include "HxDevice.h"
|
||||
#include "HxRecordCleanup.h"
|
||||
|
||||
/* 是否为初次启动 */
|
||||
static bool is_startup = true;
|
||||
/* 视频设备 */
|
||||
static HxDevice* main_device[16];
|
||||
|
||||
static void broadcast_receive_event(QByteArray data);
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
/* UDP广播 初始化 */
|
||||
HxBroadcast::initialization(BROADCAST_PORT);
|
||||
QObject::connect(HxBroadcast::context(), &HxBroadcast::receive_event, [=](QByteArray data) { broadcast_receive_event(data); });
|
||||
|
||||
// WRITE_SYSTEM_LOG("系统启动", HxReadMe::version());
|
||||
|
||||
/* 启动录像清理线程 */
|
||||
HxTask::run(&HxRecordCleanup::process, 1000, QUuid::createUuid());
|
||||
|
||||
HxTask::run([=]()
|
||||
{
|
||||
/* 判断是否为第一次启动, 没有获取到过设置信息, 需要定时查询 */
|
||||
if (is_startup)
|
||||
HxBroadcast::publish_json(1);
|
||||
|
||||
/* 发送心跳数据 */
|
||||
HxBroadcast::publish_json(0, { {"name", "HxNvr"}, {"version", "1.00"} });
|
||||
|
||||
}, 1000, QUuid::createUuid());
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
void broadcast_receive_event(QByteArray data)
|
||||
{
|
||||
int action_type = -1;
|
||||
|
||||
auto document = QJsonDocument::fromJson(data);
|
||||
auto root = document.object();
|
||||
if (!root.contains("action_type"))
|
||||
return;
|
||||
|
||||
action_type = root.value("action_type").toInt();
|
||||
|
||||
auto msginfo = root.value("msgInfo").toObject();
|
||||
|
||||
switch (action_type)
|
||||
{
|
||||
case 1:
|
||||
HxTask::invoke([=](QJsonObject _msginfo) {
|
||||
HxRecordCleanup::set(_msginfo);
|
||||
|
||||
if (is_startup)
|
||||
{
|
||||
for (int i = 0; i < CHANNEL_MAX; i++)
|
||||
{
|
||||
main_device[i] = new HxDevice(i);
|
||||
main_device[i]->startup(_msginfo);
|
||||
}
|
||||
|
||||
is_startup = false;
|
||||
}
|
||||
}, msginfo);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
HxRecordCleanup::set_records_array(root.value("msgInfo").toArray());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
134
main.h
Normal file
134
main.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
|
||||
#include "HxDisk.h"
|
||||
#include "HxTask.h"
|
||||
#include "HxJson.h"
|
||||
#include "HxThread.h"
|
||||
#include "HxReadMe.h"
|
||||
#include "HxBroadcast.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/time.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libswresample/swresample.h"
|
||||
#include <libavutil/audio_fifo.h>
|
||||
}
|
||||
|
||||
#define CHANNEL_MAX 16
|
||||
#define BROADCAST_PORT 5001
|
||||
|
||||
|
||||
#define WRITE_LOG(type, index, message, data) HxBroadcast::publish_json(4, {{"timestamp", QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")}, \
|
||||
{"type", type}, \
|
||||
{"index", index}, \
|
||||
{"message", message}, \
|
||||
{"data", data}})
|
||||
|
||||
#define WRITE_SYSTEM_LOG(message, data) WRITE_LOG(0, -1, message, data);
|
||||
|
||||
#define WRITE_VIDEO_LOG(channel, message, data) WRITE_LOG(2, channel, message, data);
|
||||
|
||||
#define REPLACE_RECORD_LOG(record) HxBroadcast::publish_json(6, {{"channel", record.channel}, \
|
||||
{"start_time", record.start_time.toString("yyyy-MM-dd HH:mm:ss")}, \
|
||||
{"end_time", record.end_time.toString("yyyy-MM-dd HH:mm:ss")}, \
|
||||
{"duration", record.duration}, \
|
||||
{"name", record.name}, \
|
||||
{"path", record.path}})
|
||||
|
||||
typedef enum
|
||||
{
|
||||
Null, /* 无状态 */
|
||||
Recording, /* 录像中 */
|
||||
Recorded, /* 录像完成 */
|
||||
} RecordStatus;
|
||||
|
||||
typedef struct __HxVideoRecord
|
||||
{
|
||||
/* 通道号 */
|
||||
int channel;
|
||||
/* 开始时间 */
|
||||
QDateTime start_time;
|
||||
/* 结束时间 */
|
||||
QDateTime end_time;
|
||||
/* 时长 */
|
||||
int duration;
|
||||
/* 名称 */
|
||||
QString name;
|
||||
/* 完整路径 */
|
||||
QString path;
|
||||
|
||||
/* 录像状态 */
|
||||
RecordStatus status = Null;
|
||||
|
||||
__HxVideoRecord() {}
|
||||
|
||||
void build(int channel, QDateTime startTime, QString rootPath, QString code)
|
||||
{
|
||||
this->channel = channel;
|
||||
this->start_time = startTime;
|
||||
this->end_time = QDateTime();
|
||||
this->duration = 0;
|
||||
this->name = QString("[%1][%2][%3][%4][Scheduled].avi")
|
||||
.arg(code)
|
||||
.arg(channel + 1, 2, 10, QChar('0'))
|
||||
.arg(startTime.toString("yyyyMMdd"),
|
||||
startTime.toString("HHmmsszzz"));
|
||||
|
||||
this->path = QString("%1HVNVR_RECORD/%2/%3/")
|
||||
#ifdef Q_OS_WIN32
|
||||
.arg(rootPath)
|
||||
#else
|
||||
.arg(rootPath + "/")
|
||||
#endif
|
||||
.arg(this->start_time.toString("yyyyMMdd"))
|
||||
.arg(this->channel + 1, 2, 10, QChar('0'));
|
||||
|
||||
HxDisk::mkpath(path);
|
||||
|
||||
this->path += name;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
this->start_time = QDateTime();
|
||||
this->duration = 0;
|
||||
this->name = nullptr;
|
||||
this->status = RecordStatus::Null;
|
||||
}
|
||||
} HxVideoRecord;
|
||||
|
||||
typedef struct __HxVideoFrame
|
||||
{
|
||||
QDateTime time;
|
||||
|
||||
AVPacket *packet;
|
||||
|
||||
__HxVideoFrame() {}
|
||||
|
||||
__HxVideoFrame(QDateTime time, AVPacket *packet)
|
||||
{
|
||||
this->time = time;
|
||||
this->packet = av_packet_clone(packet);
|
||||
}
|
||||
|
||||
__HxVideoFrame copy() { return __HxVideoFrame(this->time, this->packet); }
|
||||
|
||||
void free()
|
||||
{
|
||||
if (packet)
|
||||
{
|
||||
av_packet_unref(packet);
|
||||
av_packet_free(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
} HxVideoFrame;
|
||||
|
||||
#endif // MAIN_H
|
Loading…
Reference in New Issue
Block a user