343 lines
11 KiB
C#
343 lines
11 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
||
|
||
namespace SHH.CameraSdk;
|
||
|
||
[ApiController]
|
||
[Route("api/[controller]")]
|
||
public class CamerasController : ControllerBase
|
||
{
|
||
private readonly CameraManager _manager;
|
||
|
||
// 构造函数注入管理器
|
||
public CamerasController(CameraManager manager, DisplayWindowManager displayManager)
|
||
{
|
||
_manager = manager;
|
||
_displayManager = displayManager;
|
||
}
|
||
|
||
// ==========================================================================
|
||
// 区域 A: 设备全生命周期管理 (CRUD)
|
||
// ==========================================================================
|
||
|
||
/// <summary>
|
||
/// 1. 获取所有设备清单
|
||
/// </summary>
|
||
[HttpGet]
|
||
public IActionResult GetAll()
|
||
{
|
||
var devices = _manager.GetAllDevices().Select(d => new
|
||
{
|
||
d.Id,
|
||
d.Config.IpAddress,
|
||
d.Config.Name,
|
||
Status = d.Status.ToString(),
|
||
d.RealFps,
|
||
d.TotalFrames
|
||
});
|
||
return Ok(devices);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 2. 新增设备 (写入配置并初始化)
|
||
/// </summary>
|
||
[HttpPost]
|
||
public IActionResult Add([FromBody] CameraConfigDto dto)
|
||
{
|
||
if (_manager.GetDevice(dto.Id) != null)
|
||
return Conflict($"设备ID {dto.Id} 已存在");
|
||
|
||
// DTO 转 Config (实际项目中建议用 AutoMapper)
|
||
var config = MapToConfig(dto);
|
||
|
||
_manager.AddDevice(config); // 添加到内存池
|
||
// 注意:此时 IsRunning 默认为 false,等待手动 Start 或 API 控制
|
||
|
||
return CreatedAtAction(nameof(GetAll), new { id = dto.Id }, dto);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 3. 编辑设备 (自动识别冷热更新)
|
||
/// </summary>
|
||
[HttpPut("{id}")]
|
||
public async Task<IActionResult> Update(long id, [FromBody] CameraConfigDto dto)
|
||
{
|
||
try
|
||
{
|
||
if (id != dto.Id) return BadRequest("ID 不匹配");
|
||
|
||
// 调用 Manager 的智能更新逻辑 (之前实现的 UpdateDeviceConfigAsync)
|
||
await _manager.UpdateDeviceConfigAsync(id, MapToUpdateDto(dto));
|
||
|
||
return Ok(new { Success = true, Message = "配置已更新" });
|
||
}
|
||
catch (KeyNotFoundException) { return NotFound(); }
|
||
catch (System.Exception ex) { return StatusCode(500, ex.Message); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 4. 删除设备 (销毁连接)
|
||
/// </summary>
|
||
[HttpDelete("{id}")]
|
||
public async Task<IActionResult> Remove(long id)
|
||
{
|
||
var device = _manager.GetDevice(id);
|
||
if (device == null) return NotFound();
|
||
|
||
await device.StopAsync(); // 停流
|
||
_manager.RemoveDevice(id); // 从池中移除
|
||
|
||
return Ok($"设备 {id} 已移除");
|
||
}
|
||
|
||
// ==========================================================================
|
||
// 区域 B: 多进程流控订阅 (Subscription Strategy)
|
||
// ==========================================================================
|
||
|
||
|
||
// ==========================================================================
|
||
// 区域 C: 句柄动态绑定 (Handle Binding)
|
||
// ==========================================================================
|
||
|
||
/// <summary>
|
||
/// 6. 绑定显示窗口 (对应 A进程-句柄场景)
|
||
/// </summary>
|
||
[HttpPost("{id}/bind-handle")]
|
||
public IActionResult BindHandle(long id, [FromBody] BindHandleDto dto)
|
||
{
|
||
var device = _manager.GetDevice(id);
|
||
if (device == null) return NotFound();
|
||
|
||
// 构造动态选项,应用句柄
|
||
var options = new DynamicStreamOptions
|
||
{
|
||
RenderHandle = (System.IntPtr)dto.Handle
|
||
};
|
||
|
||
device.ApplyOptions(options); // 触发驱动层的 OnApplyOptions
|
||
|
||
device.AddAuditLog($"绑定新句柄: {dto.Handle} ({dto.Purpose})");
|
||
|
||
return Ok(new { Success = true });
|
||
}
|
||
|
||
// ==========================================================================
|
||
// 区域 D: 设备运行控制
|
||
// ==========================================================================
|
||
|
||
/// <summary>
|
||
/// 手动控制设备运行状态 (开关机)
|
||
/// </summary>
|
||
[HttpPost("{id}/power")]
|
||
public async Task<IActionResult> TogglePower(long id, [FromQuery] bool enabled)
|
||
{
|
||
var device = _manager.GetDevice(id);
|
||
if (device == null) return NotFound();
|
||
|
||
// 1. 更新运行意图
|
||
device.IsRunning = enabled;
|
||
|
||
// 2. 审计与执行
|
||
if (enabled)
|
||
{
|
||
device.AddAuditLog("用户指令:手动开启设备");
|
||
// 异步启动,Coordinator 也会在下个周期辅助检查
|
||
_ = device.StartAsync();
|
||
}
|
||
else
|
||
{
|
||
device.AddAuditLog("用户指令:手动关闭设备");
|
||
await device.StopAsync();
|
||
}
|
||
|
||
return Ok(new { DeviceId = id, IsRunning = enabled });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 热应用动态参数 (如切换码流)
|
||
/// </summary>
|
||
[HttpPost("{id}/options")]
|
||
public IActionResult ApplyOptions(long id, [FromBody] DynamicStreamOptions options)
|
||
{
|
||
var device = _manager.GetDevice(id);
|
||
if (device == null) return NotFound();
|
||
|
||
// 1. 如果涉及码流切换,先同步更新配置对象
|
||
if (options.StreamType.HasValue)
|
||
{
|
||
var newConfig = device.Config.DeepCopy();
|
||
newConfig.StreamType = options.StreamType.Value;
|
||
device.UpdateConfig(newConfig);
|
||
}
|
||
|
||
// 2. 应用到驱动层(触发热切换逻辑)
|
||
device.ApplyOptions(options);
|
||
|
||
return Ok(new { Message = "动态参数已发送", DeviceId = id });
|
||
}
|
||
|
||
// ==========================================================================
|
||
// 辅助方法 (Mapping)
|
||
// ==========================================================================
|
||
private VideoSourceConfig MapToConfig(CameraConfigDto dto)
|
||
{
|
||
return new VideoSourceConfig
|
||
{
|
||
Id = dto.Id,
|
||
Name = dto.Name,
|
||
Brand = (DeviceBrand)dto.Brand,
|
||
IpAddress = dto.IpAddress,
|
||
Port = dto.Port,
|
||
Username = dto.Username,
|
||
Password = dto.Password,
|
||
ChannelIndex = dto.ChannelIndex,
|
||
StreamType = dto.StreamType,
|
||
RtspPath = dto.RtspPath
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// [辅助方法] 将全量配置 DTO 转换为更新 DTO
|
||
/// </summary>
|
||
private DeviceUpdateDto MapToUpdateDto(CameraConfigDto dto)
|
||
{
|
||
return new DeviceUpdateDto
|
||
{
|
||
// ==========================================
|
||
// 1. 冷更新参数 (基础连接信息)
|
||
// ==========================================
|
||
IpAddress = dto.IpAddress,
|
||
Port = dto.Port,
|
||
Username = dto.Username,
|
||
Password = dto.Password,
|
||
ChannelIndex = dto.ChannelIndex,
|
||
Brand = dto.Brand,
|
||
RtspPath = dto.RtspPath,
|
||
MainboardIp = dto.MainboardIp,
|
||
MainboardPort = dto.MainboardPort,
|
||
|
||
// ==========================================
|
||
// 2. 热更新参数 (运行时属性)
|
||
// ==========================================
|
||
Name = dto.Name,
|
||
Location = dto.Location,
|
||
StreamType = dto.StreamType,
|
||
|
||
// 注意:通常句柄是通过 bind-handle 接口单独绑定的,
|
||
// 但如果 ConfigDto 里包含了上次保存的句柄,也可以映射
|
||
// RenderHandle = dto.RenderHandle,
|
||
|
||
// ==========================================
|
||
// 3. 图像处理参数
|
||
// ==========================================
|
||
AllowCompress = dto.AllowCompress,
|
||
AllowExpand = dto.AllowExpand,
|
||
TargetResolution = dto.TargetResolution,
|
||
EnhanceImage = dto.EnhanceImage,
|
||
UseGrayscale = dto.UseGrayscale
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// [新增] 查询某台设备的当前流控策略
|
||
/// </summary>
|
||
[HttpGet("{id}/subscriptions")]
|
||
public IActionResult GetSubscriptions(long id)
|
||
{
|
||
var device = _manager.GetDevice(id);
|
||
if (device == null) return NotFound();
|
||
|
||
// 调用刚才在 FrameController 写的方法
|
||
var subs = device.Controller.GetCurrentRequirements();
|
||
|
||
return Ok(subs);
|
||
}
|
||
|
||
private readonly DisplayWindowManager _displayManager; // [新增]
|
||
|
||
[HttpPost("{id}/subscriptions")]
|
||
public IActionResult UpdateSubscription(int id, [FromBody] SubscriptionDto dto)
|
||
{
|
||
var device = _manager.GetDevice(id);
|
||
if (device == null) return NotFound("设备不存在");
|
||
|
||
if (device is HikVideoSource hikCam)
|
||
{
|
||
// 1. 更新流控策略 (FrameController)
|
||
// 告诉底层:这个 AppId 需要多少帧
|
||
int totalFps = dto.DisplayFps + dto.AnalysisFps;
|
||
|
||
if (totalFps > 0)
|
||
{
|
||
// 情况 A: 这是一个新增或更新订阅
|
||
hikCam.Controller.Register(dto.AppId, totalFps);
|
||
|
||
// 如果是预览模式,启动窗口
|
||
if (dto.DisplayFps > 0)
|
||
{
|
||
_displayManager.StartDisplay(dto.AppId, id);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 情况 B: 这是一个停止订阅请求 (FPS 为 0)
|
||
// 1. 【核心修复】从调度中心物理删除,不再出现在列表中
|
||
hikCam.Controller.Unregister(dto.AppId);
|
||
|
||
// 2. 关闭可能存在的本地窗口
|
||
_displayManager.StopDisplay(dto.AppId);
|
||
}
|
||
|
||
return Ok(new { message = "Policy updated", currentConfig = hikCam.Controller.GetCurrentRequirements() });
|
||
}
|
||
|
||
return BadRequest("Device implies no controller");
|
||
}
|
||
|
||
|
||
///// <summary>
|
||
///// 5. 注册/更新进程的流需求 (A/B/C/D 场景核心)
|
||
///// </summary>
|
||
///// <remarks>
|
||
///// 示例场景:
|
||
///// - 主进程配置(B): { "appId": "Main_Config", "displayFps": 25, "analysisFps": 0 }
|
||
///// - AI进程(C): { "appId": "AI_Core", "displayFps": 0, "analysisFps": 5 }
|
||
///// </remarks>
|
||
//[HttpPost("{id}/subscriptions")]
|
||
//public IActionResult UpdateSubscription(long id, [FromBody] SubscriptionDto sub)
|
||
//{
|
||
// var device = _manager.GetDevice(id);
|
||
// if (device == null) return NotFound();
|
||
|
||
// // 逻辑转换:将 "显示帧" 和 "分析帧" 映射到底层控制器的注册表
|
||
|
||
// // 1. 处理显示需求
|
||
// string displayKey = $"{sub.AppId}_Display";
|
||
// if (sub.DisplayFps > 0)
|
||
// {
|
||
// // 告诉控制器:这个 App 需要 X 帧用于显示
|
||
// device.Controller.Register(displayKey, sub.DisplayFps);
|
||
// }
|
||
// else
|
||
// {
|
||
// // 如果不需要,移除注册
|
||
// device.Controller.Unregister(displayKey);
|
||
// }
|
||
|
||
// // 2. 处理分析需求
|
||
// string analysisKey = $"{sub.AppId}_Analysis";
|
||
// if (sub.AnalysisFps > 0)
|
||
// {
|
||
// // 告诉控制器:这个 App 需要 Y 帧用于分析
|
||
// device.Controller.Register(analysisKey, sub.AnalysisFps);
|
||
// }
|
||
// else
|
||
// {
|
||
// device.Controller.Unregister(analysisKey);
|
||
// }
|
||
|
||
// // 运维审计
|
||
// device.AddAuditLog($"更新订阅策略 [{sub.AppId}]: Display={sub.DisplayFps}, Analysis={sub.AnalysisFps}");
|
||
|
||
// return Ok(new { Message = "订阅策略已更新", DeviceId = id });
|
||
//}
|
||
} |