初步实现录像功能

This commit is contained in:
hehaoyang 2024-01-21 22:22:39 +08:00
commit eb120b0fe7
24 changed files with 1793 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
debug/*
release/*
.qmake.stash
HxUtils.pro.*
Makefile*
*.Debug
*.Release
*.TMP
*.user
._*
.vscode

334
HxDevice.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

77
main.cpp Normal file
View 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
View 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