911 lines
34 KiB
C#
911 lines
34 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Contracts;
|
||
using OrpaonVision.SiteApp.Runtime.Services;
|
||
using System.Collections.Concurrent;
|
||
using System.Runtime.InteropServices;
|
||
using MvCamCtrl.NET;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 海康相机服务实现(支持真实和模拟模式)。
|
||
/// </summary>
|
||
public sealed class RealHikCameraService : IHikCameraService, IDisposable
|
||
{
|
||
private readonly ILogger<RealHikCameraService> _logger;
|
||
private readonly HikCameraOptions _options;
|
||
private readonly object _lockObject = new();
|
||
private HikCameraDevice? _currentDevice;
|
||
private bool _isConnected;
|
||
private bool _isGrabbing;
|
||
private Timer? _frameTimer;
|
||
private readonly ConcurrentQueue<byte[]> _frameQueue = new();
|
||
private MyCamera? _camera;
|
||
private bool _sdkInitialized;
|
||
private IntPtr _deviceHandle = IntPtr.Zero;
|
||
private MyCamera.MV_CC_DEVICE_INFO? _deviceInfo;
|
||
private int _reconnectAttempts = 0;
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public RealHikCameraService(ILogger<RealHikCameraService> logger, IOptions<HikCameraOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
_logger.LogInformation("海康相机服务已初始化(模式: {RunMode})", _options.RunMode);
|
||
|
||
if (_options.RunMode == "Real")
|
||
{
|
||
InitializeSdk();
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public bool IsConnected => _isConnected;
|
||
|
||
/// <inheritdoc />
|
||
public HikCameraDevice? CurrentDevice => _currentDevice;
|
||
|
||
/// <inheritdoc />
|
||
public event Action<byte[], DateTime>? OnImageReceived;
|
||
|
||
/// <inheritdoc />
|
||
public event Action<bool, string>? OnConnectionStateChanged;
|
||
|
||
/// <summary>
|
||
/// 初始化海康SDK。
|
||
/// </summary>
|
||
private void InitializeSdk()
|
||
{
|
||
try
|
||
{
|
||
if (_sdkInitialized)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_logger.LogInformation("正在初始化海康SDK...");
|
||
|
||
// 初始化海康SDK
|
||
var result = MyCamera.MV_CC_Initialize_NET();
|
||
if (result != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogError("海康SDK初始化失败,错误代码: {ErrorCode}", result);
|
||
return;
|
||
}
|
||
|
||
_camera = new MyCamera();
|
||
_sdkInitialized = true;
|
||
_logger.LogInformation("海康SDK初始化成功");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "初始化海康SDK失败,将回退到模拟模式");
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<IReadOnlyList<HikCameraDevice>> EnumerateDevices()
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("正在枚举海康相机设备...");
|
||
|
||
if (_options.RunMode != "Real" || !_sdkInitialized || _camera == null)
|
||
{
|
||
_logger.LogWarning("真实模式未启用或SDK未初始化,返回模拟设备");
|
||
return GetMockDevices();
|
||
}
|
||
|
||
// 使用海康SDK枚举设备
|
||
var deviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
|
||
var result = _camera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref deviceList);
|
||
|
||
if (result != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogError("枚举海康设备失败,错误代码: {ErrorCode}", result);
|
||
return GetMockDevices();
|
||
}
|
||
|
||
var devices = new List<HikCameraDevice>();
|
||
|
||
for (uint i = 0; i < deviceList.nDeviceNum; i++)
|
||
{
|
||
var deviceInfo = deviceList.MvDeviceInfo[i];
|
||
var device = new HikCameraDevice
|
||
{
|
||
SerialNumber = Marshal.PtrToStringAnsi(deviceInfo.SpecialInfo.stGigEInfo.chSerialNumber) ?? $"HK_DEVICE_{i}",
|
||
DeviceName = Marshal.PtrToStringAnsi(deviceInfo.SpecialInfo.stGigEInfo.chUserDefinedName) ?? $"海康相机_{i}",
|
||
IpAddress = Marshal.PtrToStringAnsi(deviceInfo.SpecialInfo.stGigEInfo.nCurrentIp) ?? "Unknown",
|
||
MacAddress = Marshal.PtrToStringAnsi(deviceInfo.SpecialInfo.stGigEInfo.nCurrentMac) ?? "Unknown",
|
||
IsConnected = true
|
||
};
|
||
|
||
devices.Add(device);
|
||
_logger.LogInformation("发现海康设备: {DeviceName} ({SerialNumber})", device.DeviceName, device.SerialNumber);
|
||
}
|
||
|
||
if (devices.Count == 0)
|
||
{
|
||
_logger.LogInformation("未发现海康设备,返回模拟设备");
|
||
return GetMockDevices();
|
||
}
|
||
|
||
_logger.LogInformation("成功枚举到 {Count} 个海康设备", devices.Count);
|
||
return Result.Success<IReadOnlyList<HikCameraDevice>>(devices);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "枚举海康设备失败");
|
||
return Result.Fail<IReadOnlyList<HikCameraDevice>>("HIK_ENUM_FAILED", "枚举海康设备失败");
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result ConnectToDevice(HikCameraDevice device)
|
||
{
|
||
if (device == null)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_DEVICE_NULL", "相机设备不能为空。");
|
||
}
|
||
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_ALREADY_CONNECTED", "相机已连接,请先断开。");
|
||
}
|
||
|
||
_logger.LogInformation("正在连接相机设备: {SerialNumber}", device.SerialNumber);
|
||
|
||
if (_options.RunMode != "Real" || !_sdkInitialized)
|
||
{
|
||
_logger.LogWarning("真实模式未启用或SDK未初始化,使用模拟连接");
|
||
return MockConnect(device);
|
||
}
|
||
|
||
// 查找目标设备信息
|
||
MyCamera.MV_CC_DEVICE_INFO targetDeviceInfo = default;
|
||
bool foundDevice = false;
|
||
|
||
var deviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
|
||
var enumResult = _camera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref deviceList);
|
||
|
||
if (enumResult == MyCamera.MV_OK)
|
||
{
|
||
for (uint i = 0; i < deviceList.nDeviceNum; i++)
|
||
{
|
||
var deviceInfo = deviceList.MvDeviceInfo[i];
|
||
var serialNumber = Marshal.PtrToStringAnsi(deviceInfo.SpecialInfo.stGigEInfo.chSerialNumber) ?? "";
|
||
var ipAddress = Marshal.PtrToStringAnsi(deviceInfo.SpecialInfo.stGigEInfo.nCurrentIp) ?? "";
|
||
|
||
if (serialNumber.Equals(device.SerialNumber, StringComparison.OrdinalIgnoreCase) ||
|
||
ipAddress.Equals(device.IpAddress, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
targetDeviceInfo = deviceInfo;
|
||
foundDevice = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!foundDevice)
|
||
{
|
||
_logger.LogError("未找到指定的相机设备: {SerialNumber}", device.SerialNumber);
|
||
return Result.Fail("HIK_DEVICE_NOT_FOUND", $"未找到指定的相机设备: {device.SerialNumber}");
|
||
}
|
||
|
||
// 创建设备句柄
|
||
var createResult = _camera.MV_CC_CreateHandle_NET(ref targetDeviceInfo, out _deviceHandle);
|
||
if (createResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogError("创建相机句柄失败,错误代码: {ErrorCode}", createResult);
|
||
return Result.Fail("HIK_CREATE_HANDLE_FAILED", $"创建相机句柄失败,错误代码: {createResult}");
|
||
}
|
||
|
||
// 打开设备
|
||
var openResult = _camera.MV_CC_OpenDevice_NET(_deviceHandle, MyCamera.MV_ACCESS_Exclusive, 0);
|
||
if (openResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogError("打开相机设备失败,错误代码: {ErrorCode}", openResult);
|
||
_camera.MV_CC_DestroyHandle_NET(_deviceHandle);
|
||
_deviceHandle = IntPtr.Zero;
|
||
return Result.Fail("HIK_OPEN_DEVICE_FAILED", $"打开相机设备失败,错误代码: {openResult}");
|
||
}
|
||
|
||
// 获取设备信息
|
||
_deviceInfo = targetDeviceInfo;
|
||
var deviceInfoResult = GetDeviceInfo();
|
||
if (!deviceInfoResult.Succeeded)
|
||
{
|
||
_camera.MV_CC_CloseDevice_NET(_deviceHandle);
|
||
_camera.MV_CC_DestroyHandle_NET(_deviceHandle);
|
||
_deviceHandle = IntPtr.Zero;
|
||
_deviceInfo = null;
|
||
return Result.Fail(deviceInfoResult.Code, deviceInfoResult.Message, deviceInfoResult.Errors.ToArray());
|
||
}
|
||
|
||
_currentDevice = new HikCameraDevice
|
||
{
|
||
SerialNumber = device.SerialNumber,
|
||
DeviceName = device.DeviceName,
|
||
IpAddress = device.IpAddress,
|
||
SubnetMask = device.SubnetMask,
|
||
DefaultGateway = device.DefaultGateway,
|
||
MacAddress = device.MacAddress,
|
||
FirmwareVersion = device.FirmwareVersion,
|
||
IsConnected = true,
|
||
ConnectedAt = DateTime.UtcNow
|
||
};
|
||
_isConnected = true;
|
||
_reconnectAttempts = 0;
|
||
|
||
_logger.LogInformation("相机连接成功: {SerialNumber}", device.SerialNumber);
|
||
OnConnectionStateChanged?.Invoke(true, $"已连接到 {device.DeviceName}");
|
||
|
||
return Result.Success(message: $"相机 {device.DeviceName} 连接成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "连接相机失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_CONNECT_FAILED", "连接相机失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result ConnectToDevice(string serialNumberOrIp)
|
||
{
|
||
var devicesResult = EnumerateDevices();
|
||
if (!devicesResult.Succeeded)
|
||
{
|
||
return Result.Fail(devicesResult.Code, devicesResult.Message, devicesResult.Errors.ToArray());
|
||
}
|
||
|
||
var targetDevice = devicesResult.Data.FirstOrDefault(d =>
|
||
d.SerialNumber.Equals(serialNumberOrIp, StringComparison.OrdinalIgnoreCase) ||
|
||
d.IpAddress.Equals(serialNumberOrIp, StringComparison.OrdinalIgnoreCase));
|
||
|
||
if (targetDevice == null)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_FOUND", $"未找到序列号或IP为 {serialNumberOrIp} 的相机设备。");
|
||
}
|
||
|
||
return ConnectToDevice(targetDevice);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result Disconnect()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Success(message: "相机未连接。");
|
||
}
|
||
|
||
_logger.LogInformation("正在断开相机连接...");
|
||
|
||
// 停止采集
|
||
if (_isGrabbing)
|
||
{
|
||
StopGrabbing();
|
||
}
|
||
|
||
// 实际实现需要调用海康SDK的MV_CC_CloseDevice、MV_CC_DestroyHandle等函数
|
||
if (_options.RunMode == "Real" && _deviceHandle != IntPtr.Zero && _camera != null)
|
||
{
|
||
// 关闭设备
|
||
var closeResult = _camera.MV_CC_CloseDevice_NET(_deviceHandle);
|
||
if (closeResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("关闭相机设备失败,错误代码: {ErrorCode}", closeResult);
|
||
}
|
||
|
||
// 销毁句柄
|
||
var destroyResult = _camera.MV_CC_DestroyHandle_NET(_deviceHandle);
|
||
if (destroyResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("销毁相机句柄失败,错误代码: {ErrorCode}", destroyResult);
|
||
}
|
||
|
||
_deviceHandle = IntPtr.Zero;
|
||
_deviceInfo = null;
|
||
}
|
||
|
||
Thread.Sleep(100);
|
||
|
||
var deviceName = _currentDevice?.DeviceName ?? "未知设备";
|
||
_currentDevice = null;
|
||
_isConnected = false;
|
||
|
||
_logger.LogInformation("相机断开连接成功");
|
||
OnConnectionStateChanged?.Invoke(false, $"已断开与 {deviceName} 的连接");
|
||
|
||
return Result.Success(message: "相机断开连接成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "断开相机连接失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_DISCONNECT_FAILED", "断开相机连接失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result SetCameraParameters(int width, int height, string pixelFormat, int triggerMode = 0)
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_CONNECTED", "相机未连接。");
|
||
}
|
||
|
||
_logger.LogInformation("正在设置相机参数: {Width}x{Height}, {PixelFormat}, TriggerMode: {TriggerMode}",
|
||
width, height, pixelFormat, triggerMode);
|
||
|
||
if (_options.RunMode != "Real" || _deviceHandle == IntPtr.Zero || _camera == null)
|
||
{
|
||
_logger.LogDebug("模拟模式:设置相机参数");
|
||
return Result.Success(message: "相机参数设置成功(模拟模式)。");
|
||
}
|
||
|
||
// 设置图像宽度
|
||
var widthResult = _camera.MV_CC_SetIntValue_NET(_deviceHandle, "Width", width);
|
||
if (widthResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("设置图像宽度失败,错误代码: {ErrorCode}", widthResult);
|
||
}
|
||
|
||
// 设置图像高度
|
||
var heightResult = _camera.MV_CC_SetIntValue_NET(_deviceHandle, "Height", height);
|
||
if (heightResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("设置图像高度失败,错误代码: {ErrorCode}", heightResult);
|
||
}
|
||
|
||
// 设置像素格式
|
||
var pixelFormatResult = _camera.MV_CC_SetEnumValue_NET(_deviceHandle, "PixelFormat", GetPixelFormatValue(pixelFormat));
|
||
if (pixelFormatResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("设置像素格式失败,错误代码: {ErrorCode}", pixelFormatResult);
|
||
}
|
||
|
||
// 设置触发模式
|
||
var triggerModeResult = _camera.MV_CC_SetEnumValue_NET(_deviceHandle, "TriggerMode", triggerMode);
|
||
if (triggerModeResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("设置触发模式失败,错误代码: {ErrorCode}", triggerModeResult);
|
||
}
|
||
|
||
Thread.Sleep(100); // 等待参数生效
|
||
|
||
_logger.LogInformation("相机参数设置成功");
|
||
return Result.Success(message: "相机参数设置成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "设置相机参数失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_SET_PARAMS_FAILED", "设置相机参数失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result StartGrabbing()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_CONNECTED", "相机未连接。");
|
||
}
|
||
|
||
if (_isGrabbing)
|
||
{
|
||
return Result.Success(message: "相机已在采集中。");
|
||
}
|
||
|
||
_logger.LogInformation("正在开始图像采集...");
|
||
|
||
if (_options.RunMode != "Real" || _deviceHandle == IntPtr.Zero || _camera == null)
|
||
{
|
||
_logger.LogDebug("模拟模式:开始图像采集");
|
||
_isGrabbing = true;
|
||
_frameQueue.Clear();
|
||
|
||
// 启动定时器生成模拟帧
|
||
_frameTimer = new Timer(GenerateMockFrame, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(33)); // 约30fps
|
||
|
||
return Result.Success(message: "图像采集启动成功(模拟模式)。");
|
||
}
|
||
|
||
// 开始采集
|
||
var startResult = _camera.MV_CC_StartGrabbing_NET(_deviceHandle);
|
||
if (startResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogError("启动图像采集失败,错误代码: {ErrorCode}", startResult);
|
||
return Result.Fail("HIK_START_GRABBING_FAILED", $"启动图像采集失败,错误代码: {startResult}");
|
||
}
|
||
|
||
Thread.Sleep(100); // 等待采集启动
|
||
|
||
_isGrabbing = true;
|
||
_frameQueue.Clear();
|
||
|
||
// 启动定时器获取图像帧
|
||
_frameTimer = new Timer(GetFrameFromCamera, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(33)); // 约30fps
|
||
|
||
_logger.LogInformation("图像采集已启动");
|
||
return Result.Success(message: "图像采集启动成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "启动图像采集失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_START_GRAB_FAILED", "启动图像采集失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result StopGrabbing()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isGrabbing)
|
||
{
|
||
return Result.Success(message: "相机未在采集状态。");
|
||
}
|
||
|
||
_logger.LogInformation("正在停止图像采集...");
|
||
|
||
// 停止定时器
|
||
_frameTimer?.Dispose();
|
||
_frameTimer = null;
|
||
|
||
if (_options.RunMode == "Real" && _deviceHandle != IntPtr.Zero && _camera != null)
|
||
{
|
||
var stopResult = _camera.MV_CC_StopGrabbing_NET(_deviceHandle);
|
||
if (stopResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("停止图像采集失败,错误代码: {ErrorCode}", stopResult);
|
||
}
|
||
}
|
||
|
||
Thread.Sleep(50); // 等待采集停止
|
||
|
||
_isGrabbing = false;
|
||
_frameQueue.Clear();
|
||
|
||
_logger.LogInformation("图像采集已停止");
|
||
return Result.Success(message: "图像采集停止成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "停止图像采集失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_STOP_GRAB_FAILED", "停止图像采集失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result TriggerSoftware()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
try
|
||
{
|
||
if (!_isConnected)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_CONNECTED", "相机未连接。");
|
||
}
|
||
|
||
if (!_isGrabbing)
|
||
{
|
||
return Result.Fail("HIK_CAMERA_NOT_GRABBING", "相机未在采集状态。");
|
||
}
|
||
|
||
if (_options.RunMode == "Real" && _camera != null)
|
||
{
|
||
_logger.LogDebug("执行软件触发");
|
||
var triggerResult = _camera.MV_CC_SetCommandValue_NET(_deviceHandle, "TriggerSoftware");
|
||
if (triggerResult != MyCamera.MV_OK)
|
||
{
|
||
_logger.LogWarning("软件触发失败,错误代码: {ErrorCode}", triggerResult);
|
||
return Result.Fail("HIK_CAMERA_TRIGGER_FAILED", $"软件触发失败,错误代码: {triggerResult}");
|
||
}
|
||
}
|
||
|
||
return Result.Success(message: "软件触发执行成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "软件触发失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_TRIGGER_FAILED", "软件触发失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<byte[]> GetLatestFrame()
|
||
{
|
||
try
|
||
{
|
||
if (!_isGrabbing)
|
||
{
|
||
return Result<byte[]>.Fail("HIK_CAMERA_NOT_GRABBING", "相机未在采集状态。");
|
||
}
|
||
|
||
if (_frameQueue.TryDequeue(out var frame))
|
||
{
|
||
return Result<byte[]>.Success(frame, message: "获取图像帧成功。");
|
||
}
|
||
|
||
return Result<byte[]>.Fail("HIK_CAMERA_NO_FRAME", "暂无可用图像帧。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取图像帧失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_CAMERA_GET_FRAME_FAILED", "获取图像帧失败。", traceId);
|
||
return Result<byte[]>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public Result<CameraFrameDto> GetLatestFrameWithTimestamp()
|
||
{
|
||
var frameResult = GetLatestFrame();
|
||
if (!frameResult.Succeeded)
|
||
{
|
||
return Result<CameraFrameDto>.Fail(frameResult.Code, frameResult.Message, frameResult.Errors.ToArray());
|
||
}
|
||
|
||
var now = DateTime.UtcNow;
|
||
return Result<CameraFrameDto>.Success(new CameraFrameDto
|
||
{
|
||
FrameId = Guid.NewGuid(),
|
||
CapturedAtUtc = now,
|
||
CameraId = _currentDevice?.SerialNumber ?? string.Empty,
|
||
ImageData = frameResult.Data,
|
||
Timestamp = now,
|
||
Width = _options.ImageWidth,
|
||
Height = _options.ImageHeight,
|
||
PixelFormat = _options.PixelFormat
|
||
}, message: "获取图像帧成功。");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从相机获取图像帧。
|
||
/// </summary>
|
||
private void GetFrameFromCamera(object? state)
|
||
{
|
||
try
|
||
{
|
||
if (!_isGrabbing || _options.RunMode != "Real" || !_sdkInitialized)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (_deviceHandle == IntPtr.Zero || _camera == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 获取一帧图像数据
|
||
var frame = new MyCamera.MV_FRAME_OUT_INFO_EX();
|
||
var imageBuffer = IntPtr.Zero;
|
||
var getFrameResult = _camera.MV_CC_GetOneFrameTimeout_NET(_deviceHandle, ref frame, 1000);
|
||
|
||
if (getFrameResult == MyCamera.MV_OK)
|
||
{
|
||
try
|
||
{
|
||
// 转换图像数据为BGR格式
|
||
var convertResult = _camera.MV_CC_ConvertPixelType_NET(_deviceHandle, frame, MyCamera.MV_Gvsp_RGB8_Packed, ref imageBuffer);
|
||
|
||
if (convertResult == MyCamera.MV_OK && imageBuffer != IntPtr.Zero)
|
||
{
|
||
var frameSize = frame.nWidth * frame.nHeight * 3; // BGR8Packed
|
||
var frameData = new byte[frameSize];
|
||
Marshal.Copy(imageBuffer, frameData, 0, frameSize);
|
||
|
||
var timestamp = DateTime.UtcNow;
|
||
|
||
// 添加到队列
|
||
if (_frameQueue.Count < 10) // 限制队列大小防止内存溢出
|
||
{
|
||
_frameQueue.Enqueue(frameData);
|
||
}
|
||
|
||
// 触发事件
|
||
OnImageReceived?.Invoke(frameData, timestamp);
|
||
|
||
// 释放图像缓冲区
|
||
_camera.MV_CC_FreeImageBuffer_NET(_deviceHandle, ref frame);
|
||
}
|
||
else
|
||
{
|
||
// 转换失败,使用模拟数据
|
||
GenerateMockFrame(null);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理图像帧数据时发生异常");
|
||
}
|
||
finally
|
||
{
|
||
// 确保释放缓冲区
|
||
if (imageBuffer != IntPtr.Zero)
|
||
{
|
||
_camera.MV_CC_FreeImageBuffer_NET(_deviceHandle, ref frame);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 获取图像失败,使用模拟数据
|
||
GenerateMockFrame(null);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "从相机获取图像帧失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成模拟图像帧。
|
||
/// </summary>
|
||
private void GenerateMockFrame(object? state)
|
||
{
|
||
try
|
||
{
|
||
var width = _options.ImageWidth;
|
||
var height = _options.ImageHeight;
|
||
var frameSize = width * height * 3; // BGR8Packed
|
||
var frameData = new byte[frameSize];
|
||
|
||
// 填充模拟图像数据(简单的渐变图案)
|
||
var random = new Random();
|
||
for (int i = 0; i < frameSize; i += 3)
|
||
{
|
||
frameData[i] = (byte)(i % 256); // B
|
||
frameData[i + 1] = (byte)((i / 3) % 256); // G
|
||
frameData[i + 2] = (byte)random.Next(256); // R
|
||
}
|
||
|
||
var timestamp = DateTime.UtcNow;
|
||
|
||
// 添加到队列
|
||
if (_frameQueue.Count < 10)
|
||
{
|
||
_frameQueue.Enqueue(frameData);
|
||
}
|
||
|
||
// 触发事件
|
||
OnImageReceived?.Invoke(frameData, timestamp);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "生成模拟图像帧失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取设备详细信息。
|
||
/// </summary>
|
||
private Result GetDeviceInfo()
|
||
{
|
||
try
|
||
{
|
||
if (_deviceHandle == IntPtr.Zero || !_deviceInfo.HasValue)
|
||
{
|
||
return Result.Fail("HIK_DEVICE_NOT_CONNECTED", "设备未连接");
|
||
}
|
||
|
||
var info = _deviceInfo.Value;
|
||
|
||
// 获取设备型号名称
|
||
var modelName = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.chModelName) ?? "Unknown";
|
||
|
||
// 获取设备序列号
|
||
var serialNumber = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.chSerialNumber) ?? "Unknown";
|
||
|
||
// 获取设备用户定义名称
|
||
var userDefinedName = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.chUserDefinedName) ?? "Unknown";
|
||
|
||
// 获取固件版本
|
||
var firmwareVersion = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.chFirmwareVersion) ?? "Unknown";
|
||
|
||
// 获取IP地址
|
||
var ipAddress = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.nCurrentIp) ?? "Unknown";
|
||
|
||
// 获取子网掩码
|
||
var subnetMask = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.nCurrentSubNet) ?? "Unknown";
|
||
|
||
// 获取默认网关
|
||
var defaultGateway = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.nDefaultGate) ?? "Unknown";
|
||
|
||
// 获取MAC地址
|
||
var macAddress = Marshal.PtrToStringAnsi(info.SpecialInfo.stGigEInfo.nCurrentMac) ?? "Unknown";
|
||
|
||
// 更新当前设备信息
|
||
if (_currentDevice != null)
|
||
{
|
||
_currentDevice.DeviceName = userDefinedName;
|
||
_currentDevice.FirmwareVersion = firmwareVersion;
|
||
_currentDevice.SubnetMask = subnetMask;
|
||
_currentDevice.DefaultGateway = defaultGateway;
|
||
_currentDevice.MacAddress = macAddress;
|
||
}
|
||
|
||
_logger.LogDebug("设备信息: {ModelName}, {SerialNumber}, {IpAddress}", modelName, serialNumber, ipAddress);
|
||
return Result.Success(message: "获取设备信息成功。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取设备信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "HIK_GET_DEVICE_INFO_FAILED", "获取设备信息失败。", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 模拟连接。
|
||
/// </summary>
|
||
private Result MockConnect(HikCameraDevice device)
|
||
{
|
||
_currentDevice = new HikCameraDevice
|
||
{
|
||
SerialNumber = device.SerialNumber,
|
||
DeviceName = device.DeviceName,
|
||
IpAddress = device.IpAddress,
|
||
MacAddress = device.MacAddress,
|
||
IsConnected = true,
|
||
ConnectedAt = DateTime.UtcNow
|
||
};
|
||
_isConnected = true;
|
||
|
||
_logger.LogInformation("模拟连接成功: {SerialNumber}", device.SerialNumber);
|
||
OnConnectionStateChanged?.Invoke(true, $"模拟连接到 {device.DeviceName}");
|
||
|
||
return Result.Success(message: $"相机 {device.DeviceName} 模拟连接成功。");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取模拟设备。
|
||
/// </summary>
|
||
private Result<IReadOnlyList<HikCameraDevice>> GetMockDevices()
|
||
{
|
||
var mockDevices = new List<HikCameraDevice>
|
||
{
|
||
new()
|
||
{
|
||
SerialNumber = "MOCK_HIK_001",
|
||
DeviceName = "模拟海康相机_001",
|
||
IpAddress = "192.168.1.100",
|
||
MacAddress = "00:11:22:33:44:55",
|
||
IsConnected = true
|
||
},
|
||
new()
|
||
{
|
||
SerialNumber = "MOCK_HIK_002",
|
||
DeviceName = "模拟海康相机_002",
|
||
IpAddress = "192.168.1.101",
|
||
MacAddress = "00:11:22:33:44:56",
|
||
IsConnected = true
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("返回 {Count} 个模拟设备", mockDevices.Count);
|
||
return Result.Success<IReadOnlyList<HikCameraDevice>>(mockDevices);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取像素格式值。
|
||
/// </summary>
|
||
private uint GetPixelFormatValue(string pixelFormat)
|
||
{
|
||
return pixelFormat.ToUpperInvariant() switch
|
||
{
|
||
"BGR8PACKED" => MyCamera.MV_Gvsp_RGB8_Packed,
|
||
"RGB8PACKED" => MyCamera.MV_Gvsp_RGB8_Packed,
|
||
"MONO8" => MyCamera.MV_Gvsp_Mono8,
|
||
"MONO16" => MyCamera.MV_Gvsp_Mono16,
|
||
_ => MyCamera.MV_Gvsp_RGB8_Packed
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动重连。
|
||
/// </summary>
|
||
private async Task AutoReconnectAsync()
|
||
{
|
||
if (_reconnectAttempts >= _options.AutoReconnectCount)
|
||
{
|
||
_logger.LogWarning("已达到最大重连次数,停止重连");
|
||
return;
|
||
}
|
||
|
||
_reconnectAttempts++;
|
||
_logger.LogInformation("执行第 {Attempt} 次自动重连", _reconnectAttempts);
|
||
|
||
await Task.Delay(_options.AutoReconnectIntervalMs);
|
||
|
||
if (_currentDevice != null)
|
||
{
|
||
var connectResult = ConnectToDevice(_currentDevice.SerialNumber);
|
||
if (connectResult.Succeeded)
|
||
{
|
||
_logger.LogInformation("自动重连成功");
|
||
_reconnectAttempts = 0;
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("自动重连失败: {Error}", connectResult.Message);
|
||
await AutoReconnectAsync();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源。
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
// 停止定时器
|
||
_frameTimer?.Dispose();
|
||
_frameTimer = null;
|
||
|
||
// 断开连接
|
||
Disconnect();
|
||
|
||
// 释放SDK
|
||
if (_sdkInitialized && _options.RunMode == "Real")
|
||
{
|
||
_camera?.MV_CC_Finalize_NET();
|
||
_camera?.Dispose();
|
||
_camera = null;
|
||
_sdkInitialized = false;
|
||
}
|
||
|
||
_logger.LogInformation("海康相机服务已释放");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "释放海康相机服务资源时发生错误");
|
||
}
|
||
}
|
||
}
|