fea: 后端添加usb摄像头功能
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -35,3 +35,4 @@ DebuggerCmd.md
 | 
			
		||||
 | 
			
		||||
# Generated Files
 | 
			
		||||
*.sqlite
 | 
			
		||||
components.d.ts
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										55
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -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']
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -26,6 +26,8 @@
 | 
			
		||||
    <PackageReference Include="NLog" Version="5.4.0" />    
 | 
			
		||||
    <PackageReference Include="NLog.Web.AspNetCore" Version="5.4.0" />
 | 
			
		||||
    <PackageReference Include="NSwag.AspNetCore" Version="14.3.0" />
 | 
			
		||||
    <PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
 | 
			
		||||
    <PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
 | 
			
		||||
    <PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
 | 
			
		||||
    <PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
@@ -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<HttpListenerResponse> _activeClients = new List<HttpListenerResponse>();
 | 
			
		||||
@@ -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("摄像头自动对焦执行成功");
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user