fix: 适配usb摄像头,当似乎没有正常工作

This commit is contained in:
2025-08-18 15:15:41 +08:00
parent 3c52110a2f
commit 283bf2a956
4 changed files with 326 additions and 98 deletions

View File

@@ -3,10 +3,7 @@ using System.Text;
using System.Collections.Concurrent;
using DotNext;
using DotNext.Threading;
#if USB_CAMERA
using OpenCvSharp;
#endif
using FlashCap;
namespace server.Services;
@@ -17,17 +14,17 @@ public class VideoStreamClient
public int FrameWidth { get; set; }
public int FrameHeight { get; set; }
public int FrameRate { get; set; }
public Peripherals.CameraClient.Camera Camera { get; set; }
public AsyncLazy<Peripherals.CameraClient.Camera> Camera { get; set; }
public CancellationTokenSource CTS { get; set; }
public readonly AsyncReaderWriterLock Lock = new();
public VideoStreamClient(
string clientId, int width, int height, Peripherals.CameraClient.Camera camera)
string clientId, int width, int height, AsyncLazy<Peripherals.CameraClient.Camera> camera)
{
ClientId = clientId;
FrameWidth = width;
FrameHeight = height;
FrameRate = 0;
FrameRate = 30;
Camera = camera;
CTS = new CancellationTokenSource();
}
@@ -101,11 +98,25 @@ public class HttpVideoStreamService : BackgroundService
private readonly ConcurrentDictionary<string, VideoStreamClient> _clientDict = new();
// USB Camera 相关
#if USB_CAMERA
private VideoCapture? _usbCamera;
private bool _usbCameraEnable = false;
private readonly object _usbCameraLock = new object();
#endif
private AsyncLazy<UsbCameraCapture> _usbCamera = new(async token => await InitializeUsbCamera(token));
private static async Task<UsbCameraCapture> InitializeUsbCamera(CancellationToken token)
{
try
{
var camera = new UsbCameraCapture();
var devices = camera.GetDevices();
for (int i = 0; i < devices.Count; i++)
logger.Info($"Device[{i}]: {devices[i].Name}");
await camera.StartAsync(1, 3840, 2160, 30);
return camera;
}
catch (Exception ex)
{
logger.Error(ex, "Failed to start USB camera");
throw;
}
}
private Optional<VideoStreamClient> TryGetClient(string boardId)
{
@@ -116,7 +127,7 @@ public class HttpVideoStreamService : BackgroundService
return null;
}
private async Task<VideoStreamClient?> GetOrCreateClientAsync(string boardId, int initWidth, int initHeight)
private Optional<VideoStreamClient> GetOrCreateClient(string boardId, int initWidth, int initHeight)
{
if (_clientDict.TryGetValue(boardId, out var client))
{
@@ -135,13 +146,17 @@ public class HttpVideoStreamService : BackgroundService
var board = boardRet.Value.Value;
var camera = new Peripherals.CameraClient.Camera(board.IpAddr, board.Port);
var ret = await camera.Init();
if (!ret.IsSuccessful || !ret.Value)
var camera = new AsyncLazy<Peripherals.CameraClient.Camera>(async (_) =>
{
logger.Error("Camera Init Failed!");
return null;
}
var camera = new Peripherals.CameraClient.Camera(board.IpAddr, board.Port);
var ret = await camera.Init();
if (!ret.IsSuccessful || !ret.Value)
{
logger.Error("Camera Init Failed!");
throw new Exception("Camera Init Failed!");
}
return camera;
});
client = new VideoStreamClient(boardId, initWidth, initHeight, camera);
_clientDict[boardId] = client;
@@ -172,7 +187,8 @@ public class HttpVideoStreamService : BackgroundService
client.CTS.Cancel();
using (await client.Lock.AcquireWriteLockAsync(cancellationToken))
{
await client.Camera.EnableHardwareTrans(false);
var camera = await client.Camera.WithCancellation(cancellationToken);
await camera.EnableHardwareTrans(false);
}
}
_clientDict.Clear();
@@ -217,40 +233,40 @@ public class HttpVideoStreamService : BackgroundService
private async Task HandleRequestAsync(HttpListenerContext context, CancellationToken cancellationToken)
{
var path = context.Request.Url?.AbsolutePath ?? "/";
var boardId = context.Request.QueryString["board"];
var width = int.TryParse(context.Request.QueryString["width"], out var w) ? w : 640;
var height = int.TryParse(context.Request.QueryString["height"], out var h) ? h : 480;
var boardId = context.Request.QueryString["boardId"];
if (string.IsNullOrEmpty(boardId))
{
await SendErrorAsync(context.Response, "Missing clientId");
return;
}
var client = await GetOrCreateClientAsync(boardId, width, height);
if (client == null)
var width = int.TryParse(context.Request.QueryString["width"], out var w) ? w : 640;
var height = int.TryParse(context.Request.QueryString["height"], out var h) ? h : 480;
var clientOpt = GetOrCreateClient(boardId, width, height);
if (!clientOpt.HasValue)
{
await SendErrorAsync(context.Response, "Invalid clientId or camera not available");
return;
}
var client = clientOpt.Value;
var clientToken = client.CTS.Token;
try
{
logger.Info("新HTTP客户端连接: {RemoteEndPoint}", context.Request.RemoteEndPoint);
if (path == "/video-stream")
if (path == "/video")
{
// MJPEG 流请求FPGA
await HandleMjpegStreamAsync(context.Response, client, cancellationToken);
}
#if USB_CAMERA
else if (requestPath == "/usb-camera")
else if (path == "/usbCamera")
{
// USB Camera MJPEG流请求
await HandleUsbCameraStreamAsync(response, cancellationToken);
await HandleUsbCameraStreamAsync(context.Response, client, cancellationToken);
}
#endif
else if (path == "/snapshot")
{
// 单帧图像请求
@@ -281,24 +297,16 @@ public class HttpVideoStreamService : BackgroundService
}
// USB Camera MJPEG流处理
#if USB_CAMERA
private async Task HandleUsbCameraStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken)
private async Task HandleUsbCameraStreamAsync(
HttpListenerResponse response, VideoStreamClient client, CancellationToken cancellationToken)
{
try
{
lock (_usbCameraLock)
{
if (_usbCamera == null)
{
_usbCamera = new VideoCapture(1);
_usbCamera.Fps = _frameRate;
_usbCamera.FrameWidth = _frameWidth;
_usbCamera.FrameHeight = _frameHeight;
_usbCameraEnable = _usbCamera.IsOpened();
}
}
if (!_usbCameraEnable || _usbCamera == null || !_usbCamera.IsOpened())
var camera = await _usbCamera.WithCancellation(cancellationToken);
if (!camera.IsCapturing)
{
logger.Error("USB Camera is not capturing");
response.StatusCode = 500;
await response.OutputStream.FlushAsync(cancellationToken);
response.Close();
@@ -310,61 +318,52 @@ public class HttpVideoStreamService : BackgroundService
response.Headers.Add("Pragma", "no-cache");
response.Headers.Add("Expires", "0");
using (var mat = new Mat())
logger.Info("Start USB Camera MJPEG Stream");
while (true)
{
while (!cancellationToken.IsCancellationRequested)
cancellationToken.ThrowIfCancellationRequested();
var jpegData = camera.GetLatestFrame();
if (jpegData == null)
{
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);
logger.Warn("USB Camera MJPEG帧获取失败");
await Task.Delay(1000 / client.FrameRate, cancellationToken);
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 / client.FrameRate, cancellationToken);
logger.Info("USB Camera MJPEG帧发送成功");
}
}
catch (OperationCanceledException ex)
{
logger.Info(ex, "USB Camera MJPEG 串流取消");
}
catch (Exception ex)
{
logger.Error(ex, "USB Camera MJPEG流处理异常");
}
finally
{
try { response.Close(); } catch { }
}
}
#endif
private async Task HandleSnapshotRequestAsync(HttpListenerResponse response, VideoStreamClient client, CancellationToken cancellationToken)
private async Task HandleSnapshotRequestAsync(
HttpListenerResponse response, VideoStreamClient client, CancellationToken cancellationToken)
{
// 读取 Camera 快照,返回 JPEG
var frameResult = await client.Camera.ReadFrame();
var camera = await client.Camera.WithCancellation(cancellationToken);
var frameResult = await camera.ReadFrame();
if (!frameResult.IsSuccessful || frameResult.Value == null)
{
response.StatusCode = 500;
@@ -386,16 +385,18 @@ public class HttpVideoStreamService : BackgroundService
response.Close();
}
private async Task HandleMjpegStreamAsync(HttpListenerResponse response, VideoStreamClient client, CancellationToken cancellationToken)
private async Task HandleMjpegStreamAsync(
HttpListenerResponse response, VideoStreamClient client, CancellationToken cancellationToken)
{
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");
var camera = await client.Camera.WithCancellation(cancellationToken);
while (!cancellationToken.IsCancellationRequested)
{
var frameResult = await client.Camera.ReadFrame();
var frameResult = await camera.ReadFrame();
if (!frameResult.IsSuccessful || frameResult.Value == null) continue;
var jpegResult = Common.Image.ConvertRGB24ToJpeg(frameResult.Value, client.FrameWidth, client.FrameHeight, 80);
if (!jpegResult.IsSuccessful) continue;
@@ -508,7 +509,8 @@ public class HttpVideoStreamService : BackgroundService
{
// 从摄像头读取帧数据
var readStartTime = DateTime.UtcNow;
var result = await client.Camera.ReadFrame();
var camera = await client.Camera.WithCancellation(cancellationToken);
var result = await camera.ReadFrame();
var readEndTime = DateTime.UtcNow;
var readTime = (readEndTime - readStartTime).TotalMilliseconds;
@@ -568,7 +570,7 @@ public class HttpVideoStreamService : BackgroundService
using (await client.Lock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(timeout), cancellationToken))
{
var currentCamera = client.Camera;
var currentCamera = await client.Camera.WithCancellation(cancellationToken);
if (currentCamera == null)
{
var message = $"获取摄像头失败";
@@ -621,7 +623,8 @@ public class HttpVideoStreamService : BackgroundService
using (await client.Lock.AcquireWriteLockAsync(
TimeSpan.FromMilliseconds(timeout), cancellationToken))
{
var result = await client.Camera.InitAutoFocus();
var camera = await client.Camera.WithCancellation(cancellationToken);
var result = await camera.InitAutoFocus();
if (result.IsSuccessful && result.Value)
{
@@ -655,7 +658,8 @@ public class HttpVideoStreamService : BackgroundService
logger.Info($"Board{boardId}开始执行摄像头自动对焦");
var result = await client.Camera.PerformAutoFocus();
var camera = await client.Camera.WithCancellation(cancellationToken);
var result = await camera.PerformAutoFocus();
if (result.IsSuccessful && result.Value)
{
@@ -679,16 +683,18 @@ public class HttpVideoStreamService : BackgroundService
/// 配置摄像头连接参数
/// </summary>
/// <param name="boardId">板卡ID</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>配置是否成功</returns>
public async Task<bool> ConfigureCameraAsync(string boardId)
public async Task<bool> ConfigureCameraAsync(string boardId, CancellationToken cancellationToken = default)
{
try
{
var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
var camera = await client.Camera.WithCancellation(cancellationToken);
using (await client.Lock.AcquireWriteLockAsync())
using (await client.Lock.AcquireWriteLockAsync(cancellationToken))
{
var ret = await client.Camera.Init();
var ret = await camera.Init();
if (!ret.IsSuccessful)
{
logger.Error(ret.Error);
@@ -702,9 +708,9 @@ public class HttpVideoStreamService : BackgroundService
}
}
using (await client.Lock.AcquireWriteLockAsync())
using (await client.Lock.AcquireWriteLockAsync(cancellationToken))
{
var ret = await client.Camera.ChangeResolution(client.FrameWidth, client.FrameHeight);
var ret = await camera.ChangeResolution(client.FrameWidth, client.FrameHeight);
if (!ret.IsSuccessful)
{
logger.Error(ret.Error);
@@ -747,7 +753,7 @@ public class HttpVideoStreamService : BackgroundService
client.CTS.Cancel();
}
var camera = client.Camera;
var camera = await client.Camera.WithCancellation(client.CTS.Token);
var disableResult = await camera.EnableHardwareTrans(enable);
if (disableResult.IsSuccessful && disableResult.Value)
logger.Info($"Successfully disabled camera {boardId} hardware transmission");
@@ -782,7 +788,7 @@ public class HttpVideoStreamService : BackgroundService
public VideoStreamEndpoint GetVideoEndpoint(string boardId)
{
var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
var client = GetOrCreateClient(boardId, 640, 480).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
return new VideoStreamEndpoint
{