新增 Mjpegplayer 用来播放 Web 流

This commit is contained in:
2026-01-21 19:03:59 +08:00
parent f79cb6e74d
commit c438edfa0d
71 changed files with 4538 additions and 452 deletions

View File

@@ -0,0 +1,105 @@
using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations;
namespace SHH.Contracts
{
// ==============================================================================
// 1. 物理与运行配置 DTO (对应 CRUD 操作)
// 用于设备新增/全量配置查询,包含基础身份、连接信息、运行参数等全量字段
// ==============================================================================
public class CameraConfigDto
{
// --- 基础身份 (Identity) ---
/// <summary>设备唯一标识</summary>
[Required(ErrorMessage = "设备ID不能为空")]
[Range(1, long.MaxValue, ErrorMessage = "设备ID必须为正整数")]
public long Id { get; set; }
/// <summary>设备友好名称</summary>
[MaxLength(64, ErrorMessage = "设备名称长度不能超过64个字符")]
public string Name { get; set; } = string.Empty;
/// <summary>摄像头品牌类型 (0:HikVision, 1:Dahua, 2:RTSP...)</summary>
[Range(0, 10, ErrorMessage = "品牌类型值必须在0-10范围内")]
public int Brand { get; set; }
/// <summary>设备安装位置描述</summary>
[MaxLength(128, ErrorMessage = "安装位置长度不能超过128个字符")]
public string Location { get; set; } = string.Empty;
// --- 主板关联信息 (Metadata) ---
/// <summary>关联主板IP地址</summary>
[RegularExpression(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)?$",
ErrorMessage = "IPv4地址")]
public string MainboardIp { get; set; } = string.Empty;
/// <summary>关联主板端口</summary>
[Range(0, 65535, ErrorMessage = "主板端口号必须在1-65535范围内")]
public int MainboardPort { get; set; } = 0;
// --- 核心连接 (Connectivity) - 修改此类参数触发冷重启 ---
/// <summary>摄像头IP地址</summary>
[Required(ErrorMessage = "IP地址不能为空")]
[RegularExpression(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$",
ErrorMessage = "IPv4地址")]
public string IpAddress { get; set; } = string.Empty;
/// <summary>登录用户名</summary>
[MaxLength(32, ErrorMessage = "用户名长度不能超过32个字符")]
public string Username { get; set; } = string.Empty;
/// <summary>登录密码</summary>
[MaxLength(64, ErrorMessage = "密码长度不能超过64个字符")]
public string Password { get; set; } = string.Empty;
/// <summary>SDK端口 (如海康默认8000)</summary>
[Range(1, 65535, ErrorMessage = "端口号必须在1-65535范围内")]
public ushort Port { get; set; }
/// <summary>通道号 (通常为1)</summary>
[Range(0, 256, ErrorMessage = "通道号必须在0-256范围内")]
public int ChannelIndex { get; set; }
/// <summary>码流类型 (0:主码流, 1:子码流)</summary>
[Range(0, 1, ErrorMessage = "码流类型只能是0(主码流)或1(子码流)")]
public int StreamType { get; set; }
/// <summary>渲染句柄 (通常下发时为0由本地窗口绑定时再指定或者此处仅作占位)</summary>
public long RenderHandle { get; set; }
/// <summary>RTSP流路径 (备用或非SDK模式使用)</summary>
[MaxLength(256, ErrorMessage = "RTSP地址长度不能超过256个字符")]
public string RtspPath { get; set; } = string.Empty;
// --- 运行时参数 (Runtime Options) - 支持热更新 ---
/// <summary>是否使用灰度图 (用于AI分析场景加速)</summary>
public bool UseGrayscale { get; set; } = false;
/// <summary>是否启用图像增强 (去噪/锐化等)</summary>
public bool EnhanceImage { get; set; } = true;
// --- 画面变换 (Transform) - 支持热更新 ---
/// <summary>是否允许图像压缩 (降低带宽占用)</summary>
public bool AllowCompress { get; set; } = true;
/// <summary>是否允许图像放大 (提升渲染质量)</summary>
public bool AllowExpand { get; set; } = false;
/// <summary>目标分辨率 (格式如 1920x1080空则保持原图)</summary>
[RegularExpression(@"^\d+x\d+$", ErrorMessage = "分辨率格式必须为 宽度x高度 (如 1920x1080)")]
public string TargetResolution { get; set; } = string.Empty;
/// <summary>随配置一并下发的自动订阅请求</summary>
public List<CameraConfigSubscribeDto> AutoSubscriptions { get; set; }
= new List<CameraConfigSubscribeDto>();
/// <summary>是否立即执行</summary>
[JsonProperty("ImmediateExecution")] // 确保 JSON 里的这个 key 能精准对应到这个属性
public bool ImmediateExecution { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
namespace SHH.Contracts
{
/// <summary>订阅项</summary>
public class CameraConfigSubscribeDto
{
/// <summary>订阅标识 例如: "UI_Display" (界面显示), "AI_Analysis" (算法分析)</summary>
public string AppId { get; set; } = string.Empty;
/// <summary>订阅业务类型 对应枚举 SubscriptionType 的整型值</summary>
public int Type { get; set; }
/// <summary>要求的传输帧率 要求的帧率8 帧或 1 帧</summary>
public int TargetFps { get; set; }
/// <summary>是否需要高清晰度流(主码流) true: 请求高分辨率主码流; false: 请求低分辨率子码流(默认)</summary>
public string Memo { get; set; } = string.Empty;
/// <summary>是否需要高清晰度</summary>
public bool NeedHighDefinition { get; set; }
= false;
}
}

View File

@@ -0,0 +1,81 @@
namespace SHH.Contracts
{
/// <summary>
/// 通用指令请求载体 (Request)
/// <para>用于 NetMQ 的 Request-Reply 或 Router-Dealer 模式</para>
/// </summary>
public class CommandPayload
{
#region --- 0. ---
/// <summary>
/// 协议类型标识
/// <para>建议值: "COMMAND" 或 "指令包"</para>
/// </summary>
public string Protocol { get; set; } = "COMMAND";
#endregion
#region --- ---
/// <summary>
/// 指令代码 (路由键)
/// <para>示例: "PTZ", "RECORD_START", "SERVER_REGISTER"</para>
/// </summary>
public string CmdCode { get; set; } = string.Empty;
/// <summary>
/// 目标对象 ID
/// <para>示例: 摄像头ID "101",或者系统级指令填 "SYSTEM"</para>
/// </summary>
public string TargetId { get; set; } = string.Empty;
/// <summary>
/// 业务参数 (JSON 字符串)
/// <para>根据 CmdCode 的不同,反序列化为不同的 DTO (如 PtzControlDto)</para>
/// </summary>
public string JsonParams { get; set; } = string.Empty;
#endregion
#region --- ---
/// <summary>
/// 请求追踪 ID (UUID)
/// <para>核心字段:用于实现异步等待 (await)。回执包必须携带此 ID。</para>
/// </summary>
public string RequestId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 发送时间戳
/// </summary>
public DateTime Timestamp { get; set; } = DateTime.Now;
#endregion
#region --- (QoS) ---
/// <summary>
/// 是否需要回执 (ACK)
/// <para>true: 发送端会 await 等待结果 (默认)</para>
/// <para>false: 发后即忘 (Fire-and-Forget),服务端收到后不回发任何消息,减少带宽</para>
/// </summary>
public bool RequireAck { get; set; } = true;
/// <summary>
/// 重试计数器
/// <para>0: 首次发送</para>
/// <para>1, 2...: 第N次重试</para>
/// <para>服务端据此判断是否需要查重 (幂等性处理)</para>
/// </summary>
public int RetryCount { get; set; } = 0;
/// <summary>
/// 消息过期时间 (Unix时间戳)
/// <para>如果接收端收到时已经超过此时间,直接丢弃,不处理也不回复</para>
/// </summary>
public long ExpireTime { get; set; }
#endregion
}
}

View File

@@ -0,0 +1,92 @@
using Newtonsoft.Json;
// 注意:如果不想依赖 Newtonsoft也可以用 System.Text.Json但 Newtonsoft 在 Std 2.0 中兼容性更好
namespace SHH.Contracts
{
/// <summary>
/// 视频数据传输契约(纯净版 POCO
/// </summary>
public class VideoPayload
{
/// <summary>
/// 构造函数
/// </summary>
public VideoPayload()
{
SubscriberIds = new List<string>(16);
Diagnostics = new Dictionary<string, object>(4);
}
#region --- 1. (Metadata) ---
public string CameraId { get; set; } = string.Empty;
/// <summary>采集时间戳 (Unix 毫秒)</summary>
public long CaptureTimestamp { get; set; }
/// <summary>分发时间戳 (Unix 毫秒)</summary>
public long DispatchTimestamp { get; set; }
/// <summary>原始图像宽度</summary>
public int OriginalWidth { get; set; }
/// <summary>原始图像高度</summary>
public int OriginalHeight { get; set; }
/// <summary>目标图像宽度</summary>
public int TargetWidth { get; set; }
/// <summary>目标图像高度</summary>
public int TargetHeight { get; set; }
/// <summary>订阅Ids</summary>
public List<string> SubscriberIds { get; set; }
/// <summary>诊断信息</summary>
public Dictionary<string, object> Diagnostics { get; set; }
/// <summary>
/// 指示标志:是否存在原始图
/// </summary>
public bool HasOriginalImage { get; set; }
/// <summary>
/// 指示标志:是否存在处理图
/// </summary>
public bool HasTargetImage { get; set; }
#endregion
#region --- 2. (Binary) ---
// 标记 JsonIgnore防止被错误序列化
[JsonIgnore]
public byte[]? OriginalImageBytes { get; set; }
[JsonIgnore]
public byte[]? TargetImageBytes { get; set; }
#endregion
#region --- 3. ( JSON ) ---
/// <summary>
/// 获取纯元数据的 JSON 字符串
/// </summary>
public string GetMetadataJson()
{
// 在序列化前自动更新标志位,防止逻辑不同步
HasOriginalImage = (OriginalImageBytes != null && OriginalImageBytes.Length > 0);
HasTargetImage = (TargetImageBytes != null && TargetImageBytes.Length > 0);
return JsonConvert.SerializeObject(this);
}
public static VideoPayload? FromMetadataJson(string json)
{
return JsonConvert.DeserializeObject<VideoPayload>(json);
}
#endregion
}
}

View File

@@ -0,0 +1,47 @@
namespace SHH.Contracts
{
/// <summary>
/// 协议代码定义常量类
/// <para>职责:统一管理 gRpc 通讯中所涉及的协议大类 (Protocol) 与具体业务指令码 (CmdCode)</para>
/// </summary>
public static class ProtocolCodes
{
#region --- 1. ( Protocol ) ---
/// <summary>
/// 基础指令协议头
/// <para>用于标记该消息是一个业务控制指令</para>
/// </summary>
public const string Command = "Command";
/// <summary>
/// 指令执行结果反馈协议头
/// <para>用于分析节点执行完指令后,向主控端回执操作结果</para>
/// </summary>
public const string Command_Result = "Command_Result";
#endregion
#region --- 2. ( CmdCode ) ---
/// <summary>
/// 服务器注册指令
/// <para>触发场景:节点启动时向主控端注册自身信息</para>
/// </summary>
public const string ServerRegister = "SERVER_REGISTER";
/// <summary>
/// 同步摄像头配置指令
/// <para>触发场景:节点上线全量同步、数据库摄像头信息变更增量同步</para>
/// </summary>
public static string Sync_Camera { get; } = "Sync_Camera";
/// <summary>
/// 移除摄像头指令
/// <para>触发场景:本地数据库删除摄像头后,通知远程节点停止相关流采集与分析</para>
/// </summary>
public static string Remove_Camera { get; } = "Remove_Camera";
#endregion
}
}

View File

@@ -0,0 +1,64 @@
namespace SHH.Contracts
{
/// <summary>
/// 服务端身份注册信息 (DTO)
/// <para>用于服务端主动连上客户端后,上报自身的端口和身份信息</para>
/// </summary>
public class RegisterPayload
{
#region --- 0. ---
/// <summary>协议类型标识 (人工可读)</summary>
public string Protocol { get; set; } = ProtocolCodes.ServerRegister;
#endregion
#region --- 1. ---
/// <summary>进程 ID (用于区分同一台机器上的多个实例)</summary>
public int ProcessId { get; set; }
/// <summary>调用进程 ID (用于区分同一台机器上的多个实例)</summary>
public int InvokeProcId { get; set; }
/// <summary>
/// 实例唯一标识符
/// <para>启动时通过命令行传入,例如 "Gateway_Factory_A"</para>
/// </summary>
public string InstanceId { get; set; } = string.Empty;
/// <summary>服务端版本号</summary>
public string Version { get; set; } = "1.0.0";
#endregion
#region --- 2. () ---
/// <summary>
/// 服务端所在的局域网 IP
/// <para>客户端无法直接连接此IP(因为可能是内网),但运维人员需要知道</para>
/// </summary>
public string ServerIp { get; set; } = string.Empty;
/// <summary>
/// WebAPI 监听端口 (HTTP)
/// <para>用于运维人员打开 Swagger 进行调试</para>
/// </summary>
public int WebApiPort { get; set; }
/// <summary>Grpc通讯端口</summary>
public int GrpcPort { get; set; }
#endregion
#region --- 3. ---
/// <summary>启动时间</summary>
public DateTime StartTime { get; set; }
/// <summary>描述信息 (可选)</summary>
public string Description { get; set; } = string.Empty;
#endregion
}
}

View File

@@ -0,0 +1,23 @@
namespace SHH.Contracts
{
/// <summary>
/// [控制面] 设备状态变更通知包
/// </summary>
public class StatusEventPayload
{
/// <summary>摄像头ID</summary>
public string CameraId { get; set; } = string.Empty;
/// <summary>IP地址</summary>
public string IpAddress { get; set; } = string.Empty;
/// <summary>true: 上线/活跃, false: 离线/超时</summary>
public bool IsOnline { get; set; }
/// <summary>变更原因 (e.g. "Ping Success", "Frame Timeout")</summary>
public string Reason { get; set; } = string.Empty;
/// <summary>时间戳</summary>
public long Timestamp { get; set; }
}
}

View File

@@ -23,6 +23,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>