原始版本

This commit is contained in:
hehaoyang 2024-01-22 15:23:50 +08:00
commit 90ca02f79e
37 changed files with 2734 additions and 0 deletions

16
.gitignore vendored Normal file
View 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
View 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

View 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>

View 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 { }
}
}
}

View 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");
}
});
}
}

View 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) ;

View 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);
}
}
}
}

View 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 { }
}
}

View 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);
}
}

View 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}");
}

View 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();
}
}
}

View 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.

View 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
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
HxVideoCaptor/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

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
HxVideoCaptor/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

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));
}

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

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
}

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; }

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

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

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

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

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

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

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

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

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

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

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

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

Binary file not shown.

75
HxVideoCaptor/main.cpp Normal file
View 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
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

Binary file not shown.