原始版本
This commit is contained in:
commit
90ca02f79e
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
debug/*
|
||||
release/*
|
||||
.qmake.stash
|
||||
HxUtils.pro.*
|
||||
Makefile*
|
||||
*.Debug
|
||||
*.Release
|
||||
*.TMP
|
||||
*.user
|
||||
._*
|
||||
.vscode
|
||||
HxServer/.vs
|
||||
HxServer/HxServer/bin
|
||||
HxServer/HxServer/obj
|
||||
HxVideoCaptor/debug
|
||||
HxVideoCaptor/release
|
25
HxServer/HxServer.sln
Normal file
25
HxServer/HxServer.sln
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34330.188
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HxServer", "HxServer\HxServer.csproj", "{4B2B07A3-B601-414A-9B90-E5228C3E55AD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4B2B07A3-B601-414A-9B90-E5228C3E55AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4B2B07A3-B601-414A-9B90-E5228C3E55AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B2B07A3-B601-414A-9B90-E5228C3E55AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4B2B07A3-B601-414A-9B90-E5228C3E55AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {8B6447D8-861F-4AD7-99ED-0F5B61A0397F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
30
HxServer/HxServer/HxServer.csproj
Normal file
30
HxServer/HxServer/HxServer.csproj
Normal file
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="._Program.cs" />
|
||||
<Compile Remove="._ReadMe.cs" />
|
||||
<Compile Remove="Peripheral\._DataBase.cs" />
|
||||
<Compile Remove="Peripheral\._MonitorServer.cs" />
|
||||
<Compile Remove="Utils\._FileUtils.cs" />
|
||||
<Compile Remove="Utils\._JsonUtils.cs" />
|
||||
<Compile Remove="Utils\._LogUtils.cs" />
|
||||
<Compile Remove="Utils\._SQLiteUtils.cs" />
|
||||
<Compile Remove="Utils\._TaskUtils.cs" />
|
||||
<Compile Remove="Utils\._UdpClientUtils.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
216
HxServer/HxServer/Peripheral/DataBase.cs
Normal file
216
HxServer/HxServer/Peripheral/DataBase.cs
Normal file
|
@ -0,0 +1,216 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using System.Data.SQLite;
|
||||
using HxServer.Utils;
|
||||
using System.Data.SqlTypes;
|
||||
using Newtonsoft.Json;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.Threading.Channels;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace HxServer.Peripheral
|
||||
{
|
||||
public class HxVideoRecord
|
||||
{
|
||||
[JsonProperty(PropertyName = "channel")]
|
||||
public int Channel { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "start_time")]
|
||||
public string? StartTime { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "end_time")]
|
||||
public string? EndTime { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "duration")]
|
||||
public int Duration { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "path")]
|
||||
public string? Path { get; set; }
|
||||
}
|
||||
|
||||
public class DataBase
|
||||
{
|
||||
private const string filename ="HxNvr.DataBase.db";
|
||||
|
||||
private static SQLiteConnection? Connection { get; set; }
|
||||
|
||||
private static SQLiteCommand InsertLogCommand = new("INSERT INTO [Log] ([timestamp], [type], [index], [message], [data]) VALUES (@timestamp, @type, @index, @message, @data); SELECT LAST_INSERT_ROWID();");
|
||||
|
||||
public static void Initialization()
|
||||
{
|
||||
if (!File.Exists(filename)) try { SQLiteConnection.CreateFile(filename); } catch { return; }
|
||||
|
||||
Connection = new SQLiteConnection("Data Source=" + filename);
|
||||
|
||||
try { Connection.Open(); }
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var corrupted = filename + string.Format("_[{0:yyyyMMddHHmmss}].BAK", DateTime.Now);
|
||||
File.Move(filename, corrupted);
|
||||
SQLiteConnection.CreateFile(filename);
|
||||
Connection.Open();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/* 创建程序配置表 */
|
||||
Connection.ExecuteNonQuery(
|
||||
"CREATE TABLE IF NOT EXISTS [Settings] (" +
|
||||
" [name] TEXT NOT NULL PRIMARY KEY," +
|
||||
" [value] TEXT" +
|
||||
");");
|
||||
|
||||
/* 创建日志信息表 */
|
||||
Connection.ExecuteNonQuery(
|
||||
"CREATE TABLE IF NOT EXISTS [Log] (" +
|
||||
" [id] INTEGER NOT NULL PRIMARY KEY," +
|
||||
" [timestamp] DATETIME NOT NULL," +
|
||||
" [type] INTEGER NOT NULL," +
|
||||
" [index] INTEGER," +
|
||||
" [message] NTEXT NOT NULL," +
|
||||
" [data] NTEXT" +
|
||||
");");
|
||||
|
||||
/* 创建录像信息表 */
|
||||
Connection.ExecuteNonQuery(
|
||||
"CREATE TABLE IF NOT EXISTS [Records] (" +
|
||||
" [channel] INTEGER NOT NULL," +
|
||||
" [date] DATE NOT NULL," +
|
||||
" [start_time] TIME NOT NULL," +
|
||||
" [end_time] TIME," +
|
||||
" [duration] INTEGER," +
|
||||
" [name] NTEXT NOT NULL," +
|
||||
" [path] NTEXT NOT NULL," +
|
||||
"PRIMARY KEY (name));");
|
||||
|
||||
|
||||
InsertLogCommand.Connection = Connection;
|
||||
InsertLogCommand.Parameters.Add("@timestamp", DbType.DateTime);
|
||||
InsertLogCommand.Parameters.Add("@type", DbType.Int64);
|
||||
InsertLogCommand.Parameters.Add("@index", DbType.Int64);
|
||||
InsertLogCommand.Parameters.Add("@message", DbType.String);
|
||||
InsertLogCommand.Parameters.Add("@data", DbType.String);
|
||||
InsertLogCommand.Prepare();
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> ReadSettings()
|
||||
{
|
||||
Dictionary<string, string> keyValuePairs = [];
|
||||
|
||||
using SQLiteCommand command = new("SELECT * FROM [Settings]", Connection);
|
||||
using SQLiteDataReader reader = command.ExecuteReader();
|
||||
while(reader.Read())
|
||||
{
|
||||
keyValuePairs.Add(reader.GetString(0), reader.IsDBNull(1) ? "" : reader.GetString(1));
|
||||
}
|
||||
|
||||
return keyValuePairs;
|
||||
}
|
||||
|
||||
public static void WriteLogger(DateTime timestamp, int type, int? index, string? message, string? data)
|
||||
{
|
||||
InsertLogCommand.Parameters["@timestamp"].Value = timestamp;
|
||||
InsertLogCommand.Parameters["@type"].Value = (long)type;
|
||||
if (index.HasValue && index != -1) InsertLogCommand.Parameters["@index"].Value = index.Value;
|
||||
InsertLogCommand.Parameters["@message"].Value = message;
|
||||
InsertLogCommand.Parameters["@data"].Value = data;
|
||||
try { object res = InsertLogCommand.ExecuteScalar(); } catch { }
|
||||
}
|
||||
|
||||
public static List<object> QueryFirstRecord()
|
||||
{
|
||||
List<object> records = [];
|
||||
|
||||
using SQLiteCommand command = new($"SELECT * FROM [Records]", Connection);
|
||||
using SQLiteDataReader reader = command.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
int channel = reader.GetInt32(0);
|
||||
string _date = reader.GetString(1);
|
||||
string start_time = _date + " " + reader.GetString(2);
|
||||
string end_time = reader.GetString(3) == "" ? "" : (_date + " " + reader.GetString(3));
|
||||
int duration = reader.GetInt32(4);
|
||||
string name = reader.GetString(5);
|
||||
string path = reader.GetString(6);
|
||||
|
||||
records.Add(new HxVideoRecord()
|
||||
{
|
||||
Channel = channel,
|
||||
StartTime = start_time,
|
||||
EndTime = end_time,
|
||||
Duration = duration,
|
||||
Name = name,
|
||||
Path = path,
|
||||
});
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
public static List<object> QueryRecord(string? date)
|
||||
{
|
||||
List<object> records = [];
|
||||
|
||||
using SQLiteCommand command = new($"SELECT * FROM [Records] WHERE [date] = '{date}'", Connection);
|
||||
using SQLiteDataReader reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
int channel = reader.GetInt32(0);
|
||||
string _date = reader.GetString(1);
|
||||
string start_time = _date + " " + reader.GetString(2);
|
||||
string end_time = reader.GetString(3) == "" ? "" : (_date + " " + reader.GetString(3));
|
||||
int duration = reader.GetInt32(4);
|
||||
string name = reader.GetString(5);
|
||||
string path = reader.GetString(6);
|
||||
|
||||
records.Add(new HxVideoRecord()
|
||||
{
|
||||
Channel = channel,
|
||||
StartTime = start_time,
|
||||
EndTime = end_time,
|
||||
Duration = duration,
|
||||
Name = name,
|
||||
Path = path,
|
||||
});
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
public static void ReplaceRecord(int channel, DateTime start_time, DateTime? end_time, int duration, string? name, string? path)
|
||||
{
|
||||
string commandText = string.Format("REPLACE INTO [Records] ([channel], [date], [start_time], [end_time], [duration], [name], [path]) VALUES ({0}, '{1:yyyy-MM-dd}', '{1:HH:mm:ss}', '{2:HH:mm:ss}', {3}, '{4}', '{5}');",
|
||||
channel,
|
||||
start_time,
|
||||
end_time,
|
||||
duration,
|
||||
name,
|
||||
path);
|
||||
|
||||
using SQLiteCommand command = new(commandText, Connection);
|
||||
try { command.ExecuteScalar(); } catch { }
|
||||
}
|
||||
|
||||
public static void DeleteRecord(string? key, string? value)
|
||||
{
|
||||
string commandText = string.Format("DELETE FROM Records WHERE [{0}] = '{1}'", key, value);
|
||||
|
||||
using SQLiteCommand command = new(commandText, Connection);
|
||||
try { command.ExecuteScalar(); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
197
HxServer/HxServer/Peripheral/MonitorServer.cs
Normal file
197
HxServer/HxServer/Peripheral/MonitorServer.cs
Normal file
|
@ -0,0 +1,197 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Concurrent;
|
||||
using HxServer.Utils;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace HxServer.Peripheral;
|
||||
|
||||
public class Model
|
||||
{
|
||||
[JsonProperty(PropertyName = "action_type")]
|
||||
public int ActionType { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "msgInfo")]
|
||||
public object? MsgInfo { get; set; }
|
||||
|
||||
public Model() { }
|
||||
|
||||
public Model(int action_type, object msgInfo) { ActionType = action_type; MsgInfo = msgInfo; }
|
||||
|
||||
public override string ToString() => JsonUtils.ToJson(this);
|
||||
}
|
||||
|
||||
public class AppInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Path { get; set;}
|
||||
public DateTime DateTime { get; set; } = DateTime.Now;
|
||||
|
||||
public AppInfo(string name, string version, string path) { Name = name; Version = version; Path = path; }
|
||||
|
||||
public static AppInfo Set(string name, string path) => new AppInfo(name, string.Empty, path);
|
||||
|
||||
}
|
||||
|
||||
public class MonitorServer
|
||||
{
|
||||
private static UdpClient? UdpClient;
|
||||
|
||||
private static readonly ConcurrentDictionary<string, AppInfo> AppInfos = new();
|
||||
|
||||
/// <summary>
|
||||
/// 监控服务 初始化
|
||||
/// </summary>
|
||||
/// <param name="appNames">需监控的应用程序名称</param>
|
||||
public static void Initialization(params AppInfo[] apps)
|
||||
{
|
||||
foreach (var app in apps)
|
||||
AppInfos.TryAdd(app.Name, app);
|
||||
|
||||
UdpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 5001));
|
||||
UdpClient.BeginReceive(RequestCallback, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监控服务 开始监听
|
||||
/// </summary>
|
||||
public static void Startup()
|
||||
{
|
||||
TaskUtils.Run(AppMonitorProcess, 2000, Guid.NewGuid());
|
||||
}
|
||||
|
||||
private static void RequestCallback(IAsyncResult ar)
|
||||
{
|
||||
var point = new IPEndPoint(IPAddress.Any, 0);
|
||||
|
||||
var buffer = UdpClient?.EndReceive(ar, ref point);
|
||||
|
||||
TaskUtils.Run(Process, buffer, point);
|
||||
|
||||
UdpClient?.BeginReceive(RequestCallback, null);
|
||||
}
|
||||
|
||||
private static void Process(byte[]? buffer, IPEndPoint? point)
|
||||
{
|
||||
if (buffer?.Length > 0)
|
||||
{
|
||||
var model = JsonConvert.DeserializeObject<Model>(Encoding.UTF8.GetString(buffer));
|
||||
|
||||
if (model == null)
|
||||
return;
|
||||
|
||||
var dictionary = JsonUtils.ToDictionary(model.MsgInfo);
|
||||
|
||||
switch (model.ActionType)
|
||||
{
|
||||
case 0:
|
||||
if (dictionary == null)
|
||||
return;
|
||||
|
||||
UpdateAppInfos(dictionary["name"].ToString(), dictionary["version"].ToString(), point);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
UdpClient?.Send(point, new Model(1, DataBase.ReadSettings()));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
UdpClient?.Send(point, new Model(1, DataBase.ReadSettings()));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
UdpClient?.Send(point, new Model(1, DataBase.ReadSettings()));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (dictionary == null)
|
||||
return;
|
||||
|
||||
DataBase.WriteLogger(Convert.ToDateTime(dictionary["timestamp"]),
|
||||
Convert.ToInt32(dictionary["type"]),
|
||||
Convert.ToInt32(dictionary["index"]),
|
||||
dictionary["message"].ToString(),
|
||||
dictionary["data"].ToString());
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if (dictionary == null || dictionary.Count == 0)
|
||||
{
|
||||
UdpClient?.Send(point, new Model(5, DataBase.QueryFirstRecord()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dictionary.ContainsKey("date"))
|
||||
UdpClient?.Send(point, new Model(5, DataBase.QueryRecord(dictionary["date"].ToString())));
|
||||
else
|
||||
UdpClient?.Send(point, new Model(5, DataBase.QueryRecord(dictionary["start_date"].ToString())));
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
if (dictionary == null)
|
||||
return;
|
||||
|
||||
DataBase.ReplaceRecord(Convert.ToInt32(dictionary["channel"]),
|
||||
Convert.ToDateTime(dictionary["start_time"]),
|
||||
dictionary["end_time"].ToString() == "" ? null :Convert.ToDateTime(dictionary["end_time"]),
|
||||
Convert.ToInt32(dictionary["duration"]),
|
||||
dictionary["name"].ToString(),
|
||||
dictionary["path"].ToString());
|
||||
break;
|
||||
|
||||
|
||||
case 7:
|
||||
if (dictionary == null)
|
||||
return;
|
||||
|
||||
if (dictionary.ContainsKey("path"))
|
||||
DataBase.DeleteRecord("path", dictionary["path"].ToString());
|
||||
else if (dictionary.ContainsKey("date"))
|
||||
DataBase.DeleteRecord("date", dictionary["date"].ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void UpdateAppInfos(string? name, string? version, IPEndPoint? point)
|
||||
{
|
||||
if (name == null) return;
|
||||
|
||||
if (AppInfos.TryGetValue(name, out AppInfo? app))
|
||||
{
|
||||
if (app == null) return;
|
||||
|
||||
app.DateTime = DateTime.Now;
|
||||
|
||||
AppInfos.AddOrUpdate(name, app, (k, v) => v = app);
|
||||
|
||||
Console.WriteLine($"{name}[{version}] {app.DateTime:yyyy-MM-dd HH:mm:ss}");
|
||||
}
|
||||
|
||||
UdpClient?.Send(Encoding.UTF8.GetBytes("OK " + name), 5, point);
|
||||
}
|
||||
|
||||
private static void AppMonitorProcess()
|
||||
{
|
||||
var apps = AppInfos.Values.ToArray();
|
||||
|
||||
Parallel.ForEach(apps, app =>
|
||||
{
|
||||
if (DateTime.Now - app.DateTime >= TimeSpan.FromSeconds(10))
|
||||
{
|
||||
Console.WriteLine($"{app.Name} Reboot");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
19
HxServer/HxServer/Program.cs
Normal file
19
HxServer/HxServer/Program.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
using HxServer;
|
||||
using HxServer.Peripheral;
|
||||
using System.Text;
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
Console.WriteLine($"Hello, World!");
|
||||
|
||||
/* 数据库初始化 */
|
||||
DataBase.Initialization();
|
||||
|
||||
/* 服务启动 */
|
||||
//MonitorServer.Initialization(AppInfo.Set("HxNvr", ""));
|
||||
MonitorServer.Initialization();
|
||||
MonitorServer.Startup();
|
||||
|
||||
while (true) ;
|
53
HxServer/HxServer/ReadMe.cs
Normal file
53
HxServer/HxServer/ReadMe.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HxServer;
|
||||
|
||||
/// <summary>
|
||||
/// 版本信息
|
||||
/// </summary>
|
||||
public class ReadMe
|
||||
{
|
||||
public static string AppName { get => "HxServer"; }
|
||||
|
||||
public static string VersionString { get => $"{AppName} V{Version.First().Key.Split(' ')[0]} ({Version.First().Key.Split(' ')[3]})"; }
|
||||
|
||||
static Dictionary<string, List<string>> Version = new Dictionary<string, List<string>>()
|
||||
{
|
||||
["1.00 Alpha Bulid 20240111"] = new List<string>()
|
||||
{
|
||||
"版本名称格式变换",
|
||||
},
|
||||
};
|
||||
|
||||
[RequiresAssemblyFiles()]
|
||||
public static void Bulid()
|
||||
{
|
||||
var path = Path.GetDirectoryName(typeof(ReadMe).Module.FullyQualifiedName);
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
using (FileStream fr = new(Path.Combine(path, "ReadMe.md"), FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
|
||||
{
|
||||
using (StreamWriter sw = new StreamWriter(fr, Encoding.UTF8))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"# {AppName}").AppendLine();
|
||||
foreach (var item in Version)
|
||||
{
|
||||
sb.AppendLine($"## {item.Key}");
|
||||
foreach (var value in item.Value)
|
||||
sb.AppendLine($"* {value}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
sw.Write(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
76
HxServer/HxServer/Utils/FileUtils.cs
Normal file
76
HxServer/HxServer/Utils/FileUtils.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HxServer.Utils;
|
||||
|
||||
public class FileUtils
|
||||
{
|
||||
public static long Size(string fullpath) { try { return new FileInfo(fullpath).Length; } catch { return 0; } }
|
||||
public static string GetName(string fullpath) { try { return new FileInfo(fullpath).Name; } catch { return ""; } }
|
||||
public static string ReadString(string fullpath) { try { return File.ReadAllText(fullpath); } catch { return ""; } }
|
||||
public static byte[]? ReadBytes(string fullpath) { try { return File.ReadAllBytes(fullpath); } catch { return null; } }
|
||||
|
||||
public static void Append(string fullpath, string contents)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(fullpath.Substring(0, fullpath.LastIndexOf("/")));
|
||||
|
||||
using FileStream fr = new FileStream(fullpath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
using StreamWriter sw = new StreamWriter(fr, Encoding.UTF8);
|
||||
sw.WriteLine(contents);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
public static void Append(string fullpath, byte[] contents)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(fullpath.Substring(0, fullpath.LastIndexOf("/")));
|
||||
|
||||
using FileStream fr = new FileStream(fullpath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
fr.Write(contents, 0, contents.Length);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
public static void Append(string fullpath, byte[] contents, int offset, int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(fullpath.Substring(0, fullpath.LastIndexOf("/")));
|
||||
|
||||
using FileStream fr = new FileStream(fullpath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
fr.Write(contents, offset, count);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void WriteFile(string fullpath, string contents)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(fullpath.Substring(0, fullpath.LastIndexOf("/")));
|
||||
|
||||
using FileStream fr = new FileStream(fullpath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
||||
using StreamWriter sw = new StreamWriter(fr, Encoding.UTF8);
|
||||
sw.WriteLine(contents);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
|
||||
public static void WriteFile(string fullpath, byte[] contents)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(fullpath.Substring(0, fullpath.LastIndexOf("/")));
|
||||
|
||||
using FileStream fr = new FileStream(fullpath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
||||
fr.Write(contents, 0, contents.Length);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
29
HxServer/HxServer/Utils/JsonUtils.cs
Normal file
29
HxServer/HxServer/Utils/JsonUtils.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace HxServer.Utils;
|
||||
|
||||
public class JsonUtils
|
||||
{
|
||||
public static string ToJson(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
return "";
|
||||
|
||||
return JsonConvert.SerializeObject(value);
|
||||
}
|
||||
|
||||
public static Dictionary<string, object>? ToDictionary(object? message)
|
||||
{
|
||||
var json = message?.ToString();
|
||||
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
return JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
}
|
||||
}
|
47
HxServer/HxServer/Utils/LogUtils.cs
Normal file
47
HxServer/HxServer/Utils/LogUtils.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HxServer.Utils;
|
||||
|
||||
public class LogUtils
|
||||
{
|
||||
public static string filepath { get; set; } = "Log";
|
||||
|
||||
public static void AppendLog(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
FileUtils.Append(Path.Combine(filepath, DateTime.Now.ToString("yyyyMMdd"), "log.log"), $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {message}");
|
||||
}
|
||||
|
||||
public static void AppendLog(string filename, string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
FileUtils.Append(Path.Combine(filepath, DateTime.Now.ToString("yyyyMMdd"), filename), $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {message}");
|
||||
}
|
||||
|
||||
public static void AppendError(string message)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(message);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
FileUtils.Append(Path.Combine(filepath, DateTime.Now.ToString("yyyyMMdd"), "error.log"), $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {message}");
|
||||
}
|
||||
|
||||
public static void AppendError(string filename, string message)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(message);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
FileUtils.Append(Path.Combine(filepath, DateTime.Now.ToString("yyyyMMdd"), filename), $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} {message}");
|
||||
}
|
||||
|
||||
|
||||
public static void AppendError(Exception ex) => AppendError($"exception Message:{ex.Message}, StackTrace:{ex.StackTrace}");
|
||||
|
||||
public static void AppendError(string filename, Exception ex) => AppendError(filename, $"exception Message:{ex.Message}, StackTrace:{ex.StackTrace}");
|
||||
}
|
21
HxServer/HxServer/Utils/SQLiteUtils.cs
Normal file
21
HxServer/HxServer/Utils/SQLiteUtils.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HxServer.Utils
|
||||
{
|
||||
public static class SQLiteCommandUtils
|
||||
{
|
||||
public static int ExecuteNonQuery(this SQLiteConnection connection, string commandText)
|
||||
{
|
||||
var command = new SQLiteCommand(commandText, connection);
|
||||
|
||||
return command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
190
HxServer/HxServer/Utils/TaskUtils.cs
Normal file
190
HxServer/HxServer/Utils/TaskUtils.cs
Normal file
|
@ -0,0 +1,190 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HxServer.Utils;
|
||||
|
||||
#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。
|
||||
#pragma warning disable IL2026 // Using dynamic types might cause types or members to be removed by trimmer.
|
||||
public class TaskUtils
|
||||
{
|
||||
private static ConcurrentDictionary<Guid, Task> TaskQueue = new ConcurrentDictionary<Guid, Task>();
|
||||
|
||||
private static ConcurrentDictionary<Guid, CancellationTokenSource> Dispatchers = new ConcurrentDictionary<Guid, CancellationTokenSource>();
|
||||
|
||||
public static bool TryCancel(Guid guid)
|
||||
{
|
||||
if (Dispatchers.TryRemove(guid, out CancellationTokenSource cs))
|
||||
cs.Cancel();
|
||||
|
||||
TaskQueue.TryRemove(guid, out Task t);
|
||||
|
||||
return !Dispatchers.ContainsKey(guid);
|
||||
}
|
||||
|
||||
public static void TryContinueWith(Guid guid, Action continuationAction)
|
||||
{
|
||||
if (TaskQueue.ContainsKey(guid))
|
||||
{
|
||||
if (TaskQueue.TryGetValue(guid, out Task task))
|
||||
{
|
||||
var res = TaskQueue.TryUpdate(guid, task.Status == TaskStatus.Running || task.Status == TaskStatus.WaitingForActivation ?
|
||||
task.ContinueWith((__, _) => { var o = (dynamic)_; if (o == null) return; Wait(o.continuationAction); }, new { continuationAction })
|
||||
: Task.Factory.StartNew(_ => { var o = (dynamic)_; if (o == null) return; Wait(o.continuationAction); }, new { continuationAction })
|
||||
, task);
|
||||
}
|
||||
}
|
||||
else
|
||||
TaskQueue.TryAdd(guid, Task.Factory.StartNew(_ => { var o = (dynamic)_; if (o == null) return; Wait(o.continuationAction); }, new { continuationAction }));
|
||||
}
|
||||
|
||||
public static void TryContinueWith<T>(Guid guid, Action<T> continuationAction, T t)
|
||||
{
|
||||
if (TaskQueue.ContainsKey(guid))
|
||||
{
|
||||
if (TaskQueue.TryGetValue(guid, out Task task))
|
||||
{
|
||||
var res = TaskQueue.TryUpdate(guid, task.Status == TaskStatus.Running || task.Status == TaskStatus.WaitingForActivation ?
|
||||
task.ContinueWith((__, _) => { var o = (dynamic)_; if (o == null) return; Wait(o.continuationAction, o.t); }, new { continuationAction, t })
|
||||
: Task.Factory.StartNew(_ => { var o = (dynamic)_; if (o == null) return; Wait(o.continuationAction, o.t); }, new { continuationAction, t })
|
||||
, task);
|
||||
}
|
||||
}
|
||||
else
|
||||
TaskQueue.TryAdd(guid, Task.Factory.StartNew(_ => { var o = (dynamic)_; if (o == null) return; Wait(o.continuationAction, o.t); }, new { continuationAction, t }));
|
||||
}
|
||||
|
||||
public static void Delay(int millisecondsDelay, Action continuationAction) => Task.Delay(millisecondsDelay).ContinueWith(_ => Wait(continuationAction));
|
||||
|
||||
public static void Wait(Action action)
|
||||
{
|
||||
try { action.Invoke(); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error {ex.Message}, TargetSite {ex.TargetSite}, StackTrace {ex.StackTrace}");
|
||||
|
||||
LogUtils.AppendError($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {ex.Message}, TargetSite {ex.TargetSite}, StackTrace {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Wait<T>(Action<T> action, T t)
|
||||
{
|
||||
try { action.Invoke(t); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error {ex.Message}, TargetSite {ex.TargetSite}, StackTrace {ex.StackTrace}");
|
||||
|
||||
LogUtils.AppendError($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {ex.Message}, TargetSite {ex.TargetSite}, StackTrace {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
public static void Wait<T1, T2>(Action<T1, T2> action, T1 t1, T2 t2)
|
||||
{
|
||||
try { action.Invoke(t1, t2); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error {ex.Message}, TargetSite {ex.TargetSite}, StackTrace {ex.StackTrace}");
|
||||
|
||||
LogUtils.AppendError($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {ex.Message}, TargetSite {ex.TargetSite}, StackTrace {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Run(Action action)
|
||||
{
|
||||
Task.Factory.StartNew(_ =>
|
||||
{
|
||||
var o = (dynamic)_;
|
||||
|
||||
if (o == null) return;
|
||||
|
||||
Wait(o.action);
|
||||
|
||||
}, new { action }, TaskCreationOptions.PreferFairness);
|
||||
}
|
||||
|
||||
public static void Run<T>(Action<T> action, T t)
|
||||
{
|
||||
Task.Factory.StartNew(_ =>
|
||||
{
|
||||
var o = (dynamic)_;
|
||||
|
||||
if (o == null) return;
|
||||
|
||||
Wait(o.action, o.t);
|
||||
|
||||
}, new { action, t }, TaskCreationOptions.PreferFairness);
|
||||
}
|
||||
|
||||
public static void Run<T1, T2>(Action<T1, T2> action, T1 t1, T2 t2)
|
||||
{
|
||||
Task.Factory.StartNew(_ =>
|
||||
{
|
||||
var o = (dynamic)_;
|
||||
|
||||
if (o == null) return;
|
||||
|
||||
Wait(o.action, o.t1, o.t2);
|
||||
|
||||
}, new { action, t1, t2 }, TaskCreationOptions.PreferFairness);
|
||||
}
|
||||
|
||||
|
||||
public static void Run(Action action, int millisecondsDelay, Guid guid)
|
||||
{
|
||||
if (!Dispatchers.ContainsKey(guid))
|
||||
{
|
||||
var cs = new CancellationTokenSource();
|
||||
|
||||
var t = Task.Factory.StartNew(_ =>
|
||||
{
|
||||
var o = (dynamic)_;
|
||||
|
||||
if (o == null) return;
|
||||
|
||||
while (!o.cs.IsCancellationRequested)
|
||||
{
|
||||
Wait(o.action);
|
||||
|
||||
Thread.Sleep(o.millisecondsDelay);
|
||||
}
|
||||
|
||||
}, new { action, millisecondsDelay, cs }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
|
||||
|
||||
TaskQueue.TryAdd(guid, t);
|
||||
|
||||
Dispatchers.TryAdd(guid, cs);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Run<T>(Action<T> action, int millisecondsDelay, Guid guid, T t)
|
||||
{
|
||||
if (!Dispatchers.ContainsKey(guid))
|
||||
{
|
||||
var cs = new CancellationTokenSource();
|
||||
|
||||
var task = Task.Factory.StartNew(_ =>
|
||||
{
|
||||
var o = _ as dynamic;
|
||||
|
||||
if (o == null) return;
|
||||
|
||||
while (!o.cs.IsCancellationRequested)
|
||||
{
|
||||
Wait(o.action, o.t);
|
||||
|
||||
Thread.Sleep(o.millisecondsDelay);
|
||||
}
|
||||
|
||||
}, new { action, millisecondsDelay, t, cs }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
|
||||
|
||||
TaskQueue.TryAdd(guid, task);
|
||||
|
||||
Dispatchers.TryAdd(guid, cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。
|
||||
#pragma warning restore IL2026 // Using dynamic types might cause types or members to be removed by trimmer.
|
35
HxServer/HxServer/Utils/UdpClientUtils.cs
Normal file
35
HxServer/HxServer/Utils/UdpClientUtils.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HxServer.Utils
|
||||
{
|
||||
public static class UdpClientUtils
|
||||
{
|
||||
public static void Send(this UdpClient client, object obj)
|
||||
{
|
||||
var data = obj.ToString();
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
var buffer = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
client.Send(buffer);
|
||||
}
|
||||
|
||||
public static void Send(this UdpClient client, IPEndPoint? point, object obj)
|
||||
{
|
||||
var data = obj.ToString();
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
var buffer = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
client.Send(buffer, buffer.Length, point);
|
||||
}
|
||||
}
|
||||
}
|
334
HxVideoCaptor/HxDevice.cpp
Normal file
334
HxVideoCaptor/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
HxVideoCaptor/HxDevice.h
Normal file
55
HxVideoCaptor/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
HxVideoCaptor/HxReadMe.cpp
Normal file
45
HxVideoCaptor/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
HxVideoCaptor/HxReadMe.h
Normal file
19
HxVideoCaptor/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
HxVideoCaptor/HxRecordCleanup.cpp
Normal file
172
HxVideoCaptor/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
HxVideoCaptor/HxRecordCleanup.h
Normal file
20
HxVideoCaptor/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/HxVideoCaptor.pro
Normal file
62
HxVideoCaptor/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
HxVideoCaptor/HxVideoWriter.cpp
Normal file
209
HxVideoCaptor/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
HxVideoCaptor/HxVideoWriter.h
Normal file
30
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxBroadcast.h
vendored
Normal file
50
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxDisk.h
vendored
Normal file
65
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxJson.h
vendored
Normal file
15
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxLog.h
vendored
Normal file
15
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxProcess.h
vendored
Normal file
19
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxSocket.h
vendored
Normal file
41
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxSql.h
vendored
Normal file
24
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxSystem.h
vendored
Normal file
84
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxTask.h
vendored
Normal file
228
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxThread.h
vendored
Normal file
60
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/include/HxTrace.h
vendored
Normal file
24
HxVideoCaptor/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
HxVideoCaptor/external/HxUtils/libHxUtils.a
vendored
Normal file
BIN
HxVideoCaptor/external/HxUtils/libHxUtils.a
vendored
Normal file
Binary file not shown.
75
HxVideoCaptor/main.cpp
Normal file
75
HxVideoCaptor/main.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
#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, 60000, QUuid::createUuid());
|
||||
|
||||
HxTask::run([=]()
|
||||
{
|
||||
/* 判断是否为第一次启动, 没有获取到过设置信息, 需要定时查询 */
|
||||
if (is_startup)
|
||||
HxBroadcast::publish_json(1);
|
||||
|
||||
/* 发送心跳数据 */
|
||||
HxBroadcast::publish_json(0, { {"name", "HxNvr"}, {"version", "1.00"} });
|
||||
|
||||
}, 5000, 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();
|
||||
|
||||
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;
|
||||
}
|
||||
}, root.value("msgInfo").toObject());
|
||||
break;
|
||||
|
||||
case 5:
|
||||
HxRecordCleanup::set_records_array(root.value("msgInfo").toArray());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
134
HxVideoCaptor/main.h
Normal file
134
HxVideoCaptor/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
|
BIN
通信协议说明(守护程序).docx
Normal file
BIN
通信协议说明(守护程序).docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user