WebAPI 支持摄像头启停控制、码流切换、审计日志的提供
This commit is contained in:
@@ -72,6 +72,37 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
public BaseVideoSource? GetDevice(long id)
|
||||
=> _cameraPool.TryGetValue(id, out var source) ? source : null;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前管理的所有相机设备
|
||||
/// </summary>
|
||||
/// <returns>设备实例集合</returns>
|
||||
public IEnumerable<BaseVideoSource> GetAllDevices()
|
||||
{
|
||||
return _cameraPool.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从管理池中移除指定设备并释放资源
|
||||
/// </summary>
|
||||
/// <param name="id">设备唯一标识</param>
|
||||
public void RemoveDevice(long id)
|
||||
{
|
||||
if (_cameraPool.TryRemove(id, out var device))
|
||||
{
|
||||
// 记录日志
|
||||
System.Console.WriteLine($"[Manager] 正在移除设备 {id}...");
|
||||
|
||||
// 1. 停止物理连接 (异步转同步等待,防止资源未释放)
|
||||
// 在实际高并发场景建议改为 RemoveDeviceAsync
|
||||
device.StopAsync().GetAwaiter().GetResult();
|
||||
|
||||
// 2. 释放资源 (销毁非托管句柄)
|
||||
device.Dispose();
|
||||
|
||||
System.Console.WriteLine($"[Manager] 设备 {id} 已彻底移除");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 3. 生命周期控制 (Engine Lifecycle) ---
|
||||
@@ -82,13 +113,14 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
public async Task StartAsync()
|
||||
{
|
||||
// 防护:已销毁则抛出异常
|
||||
if (_isDisposed) throw new ObjectDisposedException(nameof(CameraManager));
|
||||
if (_isDisposed) throw new System.ObjectDisposedException(nameof(CameraManager));
|
||||
// 防护:避免重复启动
|
||||
if (_isEngineStarted) return;
|
||||
|
||||
// 1. 全局驱动环境预初始化:初始化厂商 SDK 运行环境
|
||||
HikSdkManager.Initialize();
|
||||
|
||||
// 不要运行,手动运行
|
||||
//// 2. 激活现有设备池中所有设备的“运行意图”,触发设备连接流程
|
||||
//foreach (var source in _cameraPool.Values)
|
||||
//{
|
||||
@@ -105,7 +137,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
|
||||
Console.WriteLine($"[CameraManager] 引擎启动成功,当前管理 {_cameraPool.Count} 路相机设备。");
|
||||
System.Console.WriteLine($"[CameraManager] 引擎启动成功,当前管理 {_cameraPool.Count} 路相机设备。");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -168,14 +200,93 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
TotalFrames = cam.TotalFrames,
|
||||
HealthScore = healthScore,
|
||||
LastErrorMessage = cam.Status == VideoSourceStatus.Faulted ? "设备故障或网络中断" : null,
|
||||
Timestamp = DateTime.Now
|
||||
Timestamp = System.DateTime.Now
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 5. 资源清理 (Disposal) ---
|
||||
#region --- 5. 配置热更新 (Config Hot Update) ---
|
||||
|
||||
/// <summary>
|
||||
/// 智能更新设备配置 (含冷热分离逻辑)
|
||||
/// </summary>
|
||||
/// <param name="deviceId">设备唯一标识</param>
|
||||
/// <param name="dto">配置更新传输对象</param>
|
||||
/// <exception cref="KeyNotFoundException">设备不存在时抛出</exception>
|
||||
public async Task UpdateDeviceConfigAsync(long deviceId, DeviceUpdateDto dto)
|
||||
{
|
||||
if (!_cameraPool.TryGetValue(deviceId, out var device))
|
||||
throw new KeyNotFoundException($"设备 {deviceId} 不存在");
|
||||
|
||||
// 1. 审计
|
||||
device.AddAuditLog("收到配置更新请求");
|
||||
|
||||
// 2. 创建副本进行对比
|
||||
var oldConfig = device.Config;
|
||||
var newConfig = oldConfig.DeepCopy();
|
||||
|
||||
// 3. 映射 DTO 值 (仅当不为空时修改)
|
||||
if (dto.IpAddress != null) newConfig.IpAddress = dto.IpAddress;
|
||||
if (dto.Port != null) newConfig.Port = dto.Port.Value;
|
||||
if (dto.Username != null) newConfig.Username = dto.Username;
|
||||
if (dto.Password != null) newConfig.Password = dto.Password;
|
||||
if (dto.ChannelIndex != null) newConfig.ChannelIndex = dto.ChannelIndex.Value;
|
||||
if (dto.StreamType != null) newConfig.StreamType = dto.StreamType.Value;
|
||||
if (dto.Name != null) newConfig.Name = dto.Name;
|
||||
if (dto.RenderHandle != null) newConfig.RenderHandle = (System.IntPtr)dto.RenderHandle.Value;
|
||||
|
||||
// 4. 判定冷热更新
|
||||
// 核心参数变更 -> 冷重启
|
||||
bool needColdRestart =
|
||||
newConfig.IpAddress != oldConfig.IpAddress ||
|
||||
newConfig.Port != oldConfig.Port ||
|
||||
newConfig.Username != oldConfig.Username ||
|
||||
newConfig.Password != oldConfig.Password ||
|
||||
newConfig.ChannelIndex != oldConfig.ChannelIndex ||
|
||||
newConfig.Brand != oldConfig.Brand;
|
||||
|
||||
if (needColdRestart)
|
||||
{
|
||||
device.AddAuditLog($"检测到核心参数变更,执行冷重启 (Reboot)");
|
||||
|
||||
// 记录之前的运行状态
|
||||
bool wasRunning = device.IsRunning;
|
||||
|
||||
// A. 彻底停止
|
||||
if (device.IsOnline) await device.StopAsync();
|
||||
|
||||
// B. 写入新配置
|
||||
device.UpdateConfig(newConfig);
|
||||
|
||||
// C. 如果之前是运行意图,则自动重启连接
|
||||
if (wasRunning) await device.StartAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
device.AddAuditLog($"检测到运行时参数变更,执行热更新 (HotSwap)");
|
||||
|
||||
// A. 更新配置数据
|
||||
device.UpdateConfig(newConfig);
|
||||
|
||||
// B. 在线应用策略 (无需断线)
|
||||
if (device.IsOnline)
|
||||
{
|
||||
var options = new DynamicStreamOptions
|
||||
{
|
||||
StreamType = dto.StreamType,
|
||||
RenderHandle = dto.RenderHandle.HasValue ? (System.IntPtr)dto.RenderHandle : null
|
||||
};
|
||||
// 触发驱动层的 OnApplyOptions
|
||||
device.ApplyOptions(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 6. 资源清理 (Disposal) ---
|
||||
|
||||
/// <summary>
|
||||
/// 同步销毁:内部调用异步销毁逻辑,等待销毁完成
|
||||
|
||||
@@ -38,6 +38,18 @@ public class FrameController
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [新增] 注销订阅者的帧需求
|
||||
/// 功能:移除指定订阅者的配置,该订阅者将不再收到任何分发帧
|
||||
/// </summary>
|
||||
/// <param name="appId">订阅者唯一标识</param>
|
||||
public void Unregister(string appId)
|
||||
{
|
||||
// ConcurrentDictionary.TryRemove 是原子的、线程安全的
|
||||
// out _ 表示我们要丢弃移除出的对象,因为我们只关心移除动作本身
|
||||
_requirements.TryRemove(appId, out _);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region --- 帧决策生成 (Frame Decision Generation) ---
|
||||
|
||||
Reference in New Issue
Block a user