fea: 后端添加usb摄像头功能

This commit is contained in:
SikongJueluo 2025-07-21 16:33:27 +08:00
parent 422aaa89d5
commit d1c9710afe
4 changed files with 110 additions and 61 deletions

1
.gitignore vendored
View File

@ -35,3 +35,4 @@ DebuggerCmd.md
# Generated Files # Generated Files
*.sqlite *.sqlite
components.d.ts

55
components.d.ts vendored
View File

@ -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']
}
}

View File

@ -26,6 +26,8 @@
<PackageReference Include="NLog" Version="5.4.0" /> <PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.4.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="5.4.0" />
<PackageReference Include="NSwag.AspNetCore" Version="14.3.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="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" /> <PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
</ItemGroup> </ItemGroup>

View File

@ -1,8 +1,14 @@
#define USB_CAMERA
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Peripherals.CameraClient; // 添加摄像头客户端引用 using Peripherals.CameraClient; // 添加摄像头客户端引用
#if USB_CAMERA
using OpenCvSharp;
#endif
namespace server.Services; namespace server.Services;
/// <summary> /// <summary>
@ -95,6 +101,13 @@ public class HttpVideoStreamService : BackgroundService
private int _cameraPort = 8888; // 默认端口 private int _cameraPort = 8888; // 默认端口
private readonly object _cameraLock = new object(); 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 图像数据 // 模拟 FPGA 图像数据
private int _frameCounter = 0; private int _frameCounter = 0;
private readonly List<HttpListenerResponse> _activeClients = new List<HttpListenerResponse>(); private readonly List<HttpListenerResponse> _activeClients = new List<HttpListenerResponse>();
@ -346,9 +359,16 @@ public class HttpVideoStreamService : BackgroundService
if (requestPath == "/video-stream") if (requestPath == "/video-stream")
{ {
// MJPEG 流请求 // MJPEG 流请求FPGA
_ = Task.Run(() => HandleMjpegStreamAsync(response, cancellationToken), cancellationToken); _ = 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") 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) private async Task HandleMjpegStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken)
{ {
try try