commit eb120b0fe7d93badb0fecc47739c5847f8f0b8d2 Author: hehaoyang <1109196436@qq.com> Date: Sun Jan 21 22:22:39 2024 +0800 初步实现录像功能 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..434ff86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +debug/* +release/* +.qmake.stash +HxUtils.pro.* +Makefile* +*.Debug +*.Release +*.TMP +*.user +._* +.vscode \ No newline at end of file diff --git a/HxDevice.cpp b/HxDevice.cpp new file mode 100644 index 0000000..607f18d --- /dev/null +++ b/HxDevice.cpp @@ -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(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("录像停止")); +} diff --git a/HxDevice.h b/HxDevice.h new file mode 100644 index 0000000..cd895a9 --- /dev/null +++ b/HxDevice.h @@ -0,0 +1,55 @@ +#ifndef HXDEVICE_H +#define HXDEVICE_H + +#include "main.h" +#include "HxVideoWriter.h" + +#include + +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 frames; + HxVideoWriter video_writer; + HxVideoRecord video_record; +}; + +#endif // HXDEVICE_H + diff --git a/HxReadMe.cpp b/HxReadMe.cpp new file mode 100644 index 0000000..7cce9a8 --- /dev/null +++ b/HxReadMe.cpp @@ -0,0 +1,45 @@ +#include "HxReadMe.h" + +QString HxReadMe::m_app_name = "VideoCaptor"; + +QMap> 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(); +} diff --git a/HxReadMe.h b/HxReadMe.h new file mode 100644 index 0000000..3552d88 --- /dev/null +++ b/HxReadMe.h @@ -0,0 +1,19 @@ +#ifndef HXREADME_H +#define HXREADME_H + +#include +#include +#include + +class HxReadMe +{ +public: + static void build(); + static QString version(); + +private: + static QString m_app_name; + static QMap> m_version_descriptioon; +}; + +#endif // HXREADME_H diff --git a/HxRecordCleanup.cpp b/HxRecordCleanup.cpp new file mode 100644 index 0000000..ce12217 --- /dev/null +++ b/HxRecordCleanup.cpp @@ -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 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)); +} diff --git a/HxRecordCleanup.h b/HxRecordCleanup.h new file mode 100644 index 0000000..8d80ea0 --- /dev/null +++ b/HxRecordCleanup.h @@ -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 diff --git a/HxVideoCaptor.pro b/HxVideoCaptor.pro new file mode 100644 index 0000000..4ff2918 --- /dev/null +++ b/HxVideoCaptor.pro @@ -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 +} + diff --git a/HxVideoWriter.cpp b/HxVideoWriter.cpp new file mode 100644 index 0000000..4858ec7 --- /dev/null +++ b/HxVideoWriter.cpp @@ -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(i); + + video_dec_stream = ifmt_ctx->streams[i]; + } + else if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + { + audio_index = static_cast(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; } diff --git a/HxVideoWriter.h b/HxVideoWriter.h new file mode 100644 index 0000000..e83d69e --- /dev/null +++ b/HxVideoWriter.h @@ -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 diff --git a/external/HxUtils/include/HxBroadcast.h b/external/HxUtils/include/HxBroadcast.h new file mode 100644 index 0000000..fda3396 --- /dev/null +++ b/external/HxUtils/include/HxBroadcast.h @@ -0,0 +1,50 @@ +#ifndef HXBROADCAST_H +#define HXBROADCAST_H + +#include +#include + +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> args = std::initializer_list>()); + +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 diff --git a/external/HxUtils/include/HxDisk.h b/external/HxUtils/include/HxDisk.h new file mode 100644 index 0000000..33ca507 --- /dev/null +++ b/external/HxUtils/include/HxDisk.h @@ -0,0 +1,65 @@ +#ifndef HXDISK_H +#define HXDISK_H + +#include "HxTrace.h" + +#include +#include + +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 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 diff --git a/external/HxUtils/include/HxJson.h b/external/HxUtils/include/HxJson.h new file mode 100644 index 0000000..3ee8687 --- /dev/null +++ b/external/HxUtils/include/HxJson.h @@ -0,0 +1,15 @@ +#ifndef HXJSON_H +#define HXJSON_H + +#include + +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 diff --git a/external/HxUtils/include/HxLog.h b/external/HxUtils/include/HxLog.h new file mode 100644 index 0000000..c6d7f3f --- /dev/null +++ b/external/HxUtils/include/HxLog.h @@ -0,0 +1,15 @@ +#ifndef HXLOG_H +#define HXLOG_H + +#include + +class HxLog +{ +public: + static void append(QString title, QString message); + +private: + static QMutex mutex; +}; + +#endif // HXLOG_H diff --git a/external/HxUtils/include/HxProcess.h b/external/HxUtils/include/HxProcess.h new file mode 100644 index 0000000..3944be3 --- /dev/null +++ b/external/HxUtils/include/HxProcess.h @@ -0,0 +1,19 @@ +#ifndef HXPROCESS_H +#define HXPROCESS_H + +#include +#include + +class HxProcess +{ +public: + /** + * @brief 同步启动进程 + * @param command 参数 + * @return + */ + static QString start(QString command); + +}; + +#endif // HXPROCESS_H diff --git a/external/HxUtils/include/HxSocket.h b/external/HxUtils/include/HxSocket.h new file mode 100644 index 0000000..5fe5e74 --- /dev/null +++ b/external/HxUtils/include/HxSocket.h @@ -0,0 +1,41 @@ +#ifndef HXSOCKET_H +#define HXSOCKET_H + +#include +#include + +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 diff --git a/external/HxUtils/include/HxSql.h b/external/HxUtils/include/HxSql.h new file mode 100644 index 0000000..3adfe2f --- /dev/null +++ b/external/HxUtils/include/HxSql.h @@ -0,0 +1,24 @@ +#ifndef HXSQL_H +#define HXSQL_H + +#include + +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 diff --git a/external/HxUtils/include/HxSystem.h b/external/HxUtils/include/HxSystem.h new file mode 100644 index 0000000..338df95 --- /dev/null +++ b/external/HxUtils/include/HxSystem.h @@ -0,0 +1,84 @@ +#ifndef HXSYSTEM_H +#define HXSYSTEM_H + +#include + +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 diff --git a/external/HxUtils/include/HxTask.h b/external/HxUtils/include/HxTask.h new file mode 100644 index 0000000..5ec92bf --- /dev/null +++ b/external/HxUtils/include/HxTask.h @@ -0,0 +1,228 @@ +#ifndef HXTASK_H +#define HXTASK_H + +#include "HxTrace.h" +#include + +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 + 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 + static QFuture invoke(Functor functor, const Arg1 &arg1) { return QtConcurrent::run(functor, arg1); } + + template + static QFuture invoke(Functor functor, const Arg1 &arg1, const Arg2 &arg2) { return QtConcurrent::run(functor, arg1, arg2); } + + template + static QFuture invoke(Functor functor, const Arg1 &arg1, const Arg2 &arg2, const Arg3 &arg3) { return QtConcurrent::run(functor, arg1, arg2, arg3); } + + /** + * @brief 异步执行 + * @param functor 函数 + */ + template + static QFuture invoke(Functor functor) { return QtConcurrent::run(functor); } + + /** + * @brief 异步执行 + * @param functor 函数 + * @param millisecond 函数执行间隔频率,单位毫秒 + * @param uuid 任务唯一编码 + */ + template + 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 + 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 + 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 + 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 + 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 + 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 + 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 dispatchers; +}; + +#endif // HXTASK_H diff --git a/external/HxUtils/include/HxThread.h b/external/HxUtils/include/HxThread.h new file mode 100644 index 0000000..5b742a1 --- /dev/null +++ b/external/HxUtils/include/HxThread.h @@ -0,0 +1,60 @@ +#ifndef HXTHREAD_H +#define HXTHREAD_H + +#include +#include +#include + +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 diff --git a/external/HxUtils/include/HxTrace.h b/external/HxUtils/include/HxTrace.h new file mode 100644 index 0000000..7e6a5a3 --- /dev/null +++ b/external/HxUtils/include/HxTrace.h @@ -0,0 +1,24 @@ +#ifndef HXTRACE_H +#define HXTRACE_H + +#include + +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 diff --git a/external/HxUtils/libHxUtils.a b/external/HxUtils/libHxUtils.a new file mode 100644 index 0000000..5e74344 Binary files /dev/null and b/external/HxUtils/libHxUtils.a differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..b898c8f --- /dev/null +++ b/main.cpp @@ -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; + } +} + diff --git a/main.h b/main.h new file mode 100644 index 0000000..93a3365 --- /dev/null +++ b/main.h @@ -0,0 +1,134 @@ +#ifndef MAIN_H +#define MAIN_H + +#include +#include + +#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 +} + +#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