diff --git a/.gitignore b/.gitignore index 50c73fa..1d74a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ DebuggerCmd.md # Generated Files *.sqlite +components.d.ts diff --git a/components.d.ts b/components.d.ts deleted file mode 100644 index 6ba3abd..0000000 --- a/components.d.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable */ -// @ts-nocheck -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 -// biome-ignore lint: disable -export {} - -/* prettier-ignore */ -declare module 'vue' { - export interface GlobalComponents { - Alert: typeof import('./src/components/Alert/Alert.vue')['default'] - BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default'] - BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default'] - CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default'] - ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default'] - DDR: typeof import('./src/components/equipments/DDR.vue')['default'] - DDS: typeof import('./src/components/equipments/DDS.vue')['default'] - DDSPropertyEditor: typeof import('./src/components/equipments/DDSPropertyEditor.vue')['default'] - DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default'] - Dialog: typeof import('./src/components/Dialog.vue')['default'] - ETH: typeof import('./src/components/equipments/ETH.vue')['default'] - HDMI: typeof import('./src/components/equipments/HDMI.vue')['default'] - IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default'] - ItemList: typeof import('./src/components/LabCanvas/ItemList.vue')['default'] - LogicalWaveFormDisplay: typeof import('./src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue')['default'] - MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default'] - MechanicalButton: typeof import('./src/components/equipments/MechanicalButton.vue')['default'] - MotherBoard: typeof import('./src/components/equipments/MotherBoard.vue')['default'] - MotherBoardCaps: typeof import('./src/components/equipments/MotherBoardCaps.vue')['default'] - Navbar: typeof import('./src/components/Navbar.vue')['default'] - OscilloscopeWaveformDisplay: typeof import('./src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue')['default'] - PG2L100H_FBG676: typeof import('./src/components/equipments/PG2L100H_FBG676.vue')['default'] - Pin: typeof import('./src/components/equipments/Pin.vue')['default'] - PopButton: typeof import('./src/components/PopButton.vue')['default'] - PortInputField: typeof import('./src/components/InputField/PortInputField.vue')['default'] - PropertyEditor: typeof import('./src/components/PropertyEditor.vue')['default'] - PropertyPanel: typeof import('./src/components/PropertyPanel.vue')['default'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - SD: typeof import('./src/components/equipments/SD.vue')['default'] - SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default'] - SFP: typeof import('./src/components/equipments/SFP.vue')['default'] - Sidebar: typeof import('./src/components/Sidebar.vue')['default'] - SMA: typeof import('./src/components/equipments/SMA.vue')['default'] - SMT_LED: typeof import('./src/components/equipments/SMT_LED.vue')['default'] - Switch: typeof import('./src/components/equipments/Switch.vue')['default'] - ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default'] - ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default'] - TriggerSettings: typeof import('./src/components/LogicAnalyzer/TriggerSettings.vue')['default'] - TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default'] - UploadCard: typeof import('./src/components/UploadCard.vue')['default'] - WaveformDisplay: typeof import('./src/components/WaveformDisplay/WaveformDisplay.vue')['default'] - Wire: typeof import('./src/components/equipments/Wire.vue')['default'] - } -} diff --git a/server/server.csproj b/server/server.csproj index 59e8975..f138d5c 100644 --- a/server/server.csproj +++ b/server/server.csproj @@ -26,6 +26,8 @@ + + diff --git a/server/src/Services/HttpVideoStreamService.cs b/server/src/Services/HttpVideoStreamService.cs index 3fec4ad..88b1fe2 100644 --- a/server/src/Services/HttpVideoStreamService.cs +++ b/server/src/Services/HttpVideoStreamService.cs @@ -1,8 +1,14 @@ +#define USB_CAMERA + using System.Net; using System.Text; using System.Threading.Tasks; using Peripherals.CameraClient; // 添加摄像头客户端引用 +#if USB_CAMERA +using OpenCvSharp; +#endif + namespace server.Services; /// @@ -82,7 +88,7 @@ public class HttpVideoStreamService : BackgroundService private HttpListener? _httpListener; private readonly int _serverPort = 8080; private readonly int _frameRate = 30; // 30 FPS - + // 动态分辨率配置 private int _frameWidth = 640; // 默认640x480 private int _frameHeight = 480; @@ -95,6 +101,13 @@ public class HttpVideoStreamService : BackgroundService private int _cameraPort = 8888; // 默认端口 private readonly object _cameraLock = new object(); + // USB Camera 相关 +#if USB_CAMERA + private VideoCapture? _usbCamera; + private bool _usbCameraEnable = false; + private readonly object _usbCameraLock = new object(); +#endif + // 模拟 FPGA 图像数据 private int _frameCounter = 0; private readonly List _activeClients = new List(); @@ -346,9 +359,16 @@ public class HttpVideoStreamService : BackgroundService if (requestPath == "/video-stream") { - // MJPEG 流请求 + // MJPEG 流请求(FPGA) _ = Task.Run(() => HandleMjpegStreamAsync(response, cancellationToken), cancellationToken); } +#if USB_CAMERA + else if (requestPath == "/usb-camera") + { + // USB Camera MJPEG流请求 + _ = Task.Run(() => HandleUsbCameraStreamAsync(response, cancellationToken), cancellationToken); + } +#endif else if (requestPath == "/snapshot") { // 单帧图像请求 @@ -382,6 +402,87 @@ public class HttpVideoStreamService : BackgroundService } } + // USB Camera MJPEG流处理 +#if USB_CAMERA + private async Task HandleUsbCameraStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken) + { + try + { + lock (_usbCameraLock) + { + if (_usbCamera == null) + { + _usbCamera = new VideoCapture(0); + _usbCamera.Fps = _frameRate; + _usbCamera.FrameWidth = _frameWidth; + _usbCamera.FrameHeight = _frameHeight; + _usbCameraEnable = _usbCamera.IsOpened(); + } + } + if (!_usbCameraEnable || _usbCamera == null || !_usbCamera.IsOpened()) + { + response.StatusCode = 500; + await response.OutputStream.FlushAsync(cancellationToken); + response.Close(); + return; + } + + response.ContentType = "multipart/x-mixed-replace; boundary=--boundary"; + response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); + response.Headers.Add("Pragma", "no-cache"); + response.Headers.Add("Expires", "0"); + + using (var mat = new Mat()) + { + while (!cancellationToken.IsCancellationRequested) + { + bool grabbed; + lock (_usbCameraLock) + { + grabbed = _usbCamera.Read(mat); + } + if (!grabbed || mat.Empty()) + { + await Task.Delay(50, cancellationToken); + continue; + } + + // 编码为JPEG + byte[]? jpegData = null; + try + { + jpegData = mat.ToBytes(".jpg", new int[] { (int)ImwriteFlags.JpegQuality, 80 }); + } + catch (Exception ex) + { + logger.Error(ex, "USB Camera帧编码JPEG失败"); + continue; + } + if (jpegData == null) + continue; + + // MJPEG帧头 + var header = Encoding.ASCII.GetBytes("--boundary\r\nContent-Type: image/jpeg\r\nContent-Length: " + jpegData.Length + "\r\n\r\n"); + await response.OutputStream.WriteAsync(header, 0, header.Length, cancellationToken); + await response.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken); + await response.OutputStream.WriteAsync(new byte[] { 0x0D, 0x0A }, 0, 2, cancellationToken); // \r\n + await response.OutputStream.FlushAsync(cancellationToken); + + await Task.Delay(1000 / _frameRate, cancellationToken); + } + } + } + catch (Exception ex) + { + logger.Error(ex, "USB Camera MJPEG流处理异常"); + } + finally + { + try { response.Close(); } catch { } + } + } +#endif + private async Task HandleMjpegStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken) { try @@ -1046,9 +1147,9 @@ public class HttpVideoStreamService : BackgroundService } logger.Info("开始初始化摄像头自动对焦功能"); - + var result = await _camera!.InitAutoFocus(); - + if (result.IsSuccessful && result.Value) { logger.Info("摄像头自动对焦功能初始化成功"); @@ -1085,9 +1186,9 @@ public class HttpVideoStreamService : BackgroundService } logger.Info("开始执行摄像头自动对焦"); - + var result = await _camera!.PerformAutoFocus(); - + if (result.IsSuccessful && result.Value) { logger.Info("摄像头自动对焦执行成功");