feat: 更新api,并更新了串流页面

This commit is contained in:
2025-07-09 13:39:03 +08:00
parent 67bdec8570
commit 443aea5e3e
7 changed files with 311 additions and 164 deletions

View File

@@ -14,6 +14,11 @@ public class TutorialController : ControllerBase
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly IWebHostEnvironment _environment;
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="environment">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
public TutorialController(IWebHostEnvironment environment)
{
_environment = environment;

View File

@@ -109,6 +109,7 @@ public class UDPController : ControllerBase
/// 获取指定IP地址接收的数据列表
/// </summary>
/// <param name="address">IP地址</param>
/// <param name="taskID">任务ID</param>
[HttpGet("GetRecvDataArray")]
[ProducesResponseType(typeof(List<UDPData>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]

View File

@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
/// <summary>
/// 视频流控制器,支持动态配置摄像头连接
@@ -17,10 +17,16 @@ public class VideoStreamController : ControllerBase
/// </summary>
public class CameraConfigRequest
{
/// <summary>
/// 摄像头地址
/// </summary>
[Required]
[RegularExpression(@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", ErrorMessage = "IP地址")]
public string Address { get; set; } = "";
/// <summary>
/// 摄像头端口
/// </summary>
[Required]
[Range(1, 65535, ErrorMessage = "端口必须在1-65535范围内")]
public int Port { get; set; }
@@ -54,21 +60,11 @@ public class VideoStreamController : ControllerBase
var status = _videoStreamService.GetServiceStatus();
// 转换为小写首字母的JSON属性符合前端惯例
return TypedResults.Ok(new
{
isRunning = true, // HTTP视频流服务作为后台服务始终运行
serverPort = _videoStreamService.ServerPort,
streamUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-feed.html",
mjpegUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-stream",
snapshotUrl = $"http://localhost:{_videoStreamService.ServerPort}/snapshot",
connectedClients = _videoStreamService.ConnectedClientsCount,
clientEndpoints = _videoStreamService.GetConnectedClientEndpoints(),
cameraStatus = _videoStreamService.GetCameraStatus()
});
return TypedResults.Ok(status);
}
catch (Exception ex)
{
logger.Error(ex, "获取 HTTP 视频流服务状态失败");
logger.Error(ex, "获取 HTTP 视频流服务状态失败");
return TypedResults.InternalServerError(ex.Message);
}
}
@@ -95,13 +91,11 @@ public class VideoStreamController : ControllerBase
htmlUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-feed.html",
mjpegUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-stream",
snapshotUrl = $"http://localhost:{_videoStreamService.ServerPort}/snapshot",
cameraAddress = _videoStreamService.CameraAddress,
cameraPort = _videoStreamService.CameraPort
});
}
catch (Exception ex)
{
logger.Error(ex, "获取 HTTP 视频流信息失败");
logger.Error(ex, "获取 HTTP 视频流信息失败");
return TypedResults.InternalServerError(ex.Message);
}
}
@@ -123,7 +117,7 @@ public class VideoStreamController : ControllerBase
logger.Info("配置摄像头连接: {Address}:{Port}", config.Address, config.Port);
var success = await _videoStreamService.ConfigureCameraAsync(config.Address, config.Port);
if (success)
{
return TypedResults.Ok(new
@@ -166,7 +160,7 @@ public class VideoStreamController : ControllerBase
{
logger.Info("获取摄像头配置");
var cameraStatus = _videoStreamService.GetCameraStatus();
return TypedResults.Ok(new
{
address = _videoStreamService.CameraAddress,
@@ -183,35 +177,23 @@ public class VideoStreamController : ControllerBase
}
/// <summary>
/// 测试摄像头连接
/// 控制 HTTP 视频流服务开关
/// </summary>
/// <returns>连接测试结果</returns>
[HttpPost("TestCameraConnection")]
/// <param name="enabled">是否启用服务</param>
/// <returns>操作结果</returns>
[HttpPost("SetEnabled")]
[EnableCors("Users")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async Task<IResult> TestCameraConnection()
public IResult SetEnabled([FromQuery] bool enabled)
{
try
logger.Info("设置视频流服务开关: {Enabled}", enabled);
_videoStreamService.Enabled = enabled;
return TypedResults.Ok(new
{
logger.Info("测试摄像头连接");
var (isSuccess, message) = await _videoStreamService.TestCameraConnectionAsync();
return TypedResults.Ok(new
{
success = isSuccess,
message = message,
cameraAddress = _videoStreamService.CameraAddress,
cameraPort = _videoStreamService.CameraPort,
timestamp = DateTime.Now
});
}
catch (Exception ex)
{
logger.Error(ex, "测试摄像头连接失败");
return TypedResults.InternalServerError(ex.Message);
}
success = true,
enabled = _videoStreamService.Enabled
});
}
/// <summary>
@@ -229,16 +211,29 @@ public class VideoStreamController : ControllerBase
logger.Info("测试 HTTP 视频流连接");
// 尝试通过HTTP请求检查视频流服务是否可访问
bool isConnected = false;
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(2); // 设置较短的超时时间
var response = await httpClient.GetAsync($"http://localhost:{_videoStreamService.ServerPort}/");
// 只要能连接上就认为成功,不管返回状态
bool isConnected = response.IsSuccessStatusCode;
return TypedResults.Ok(isConnected);
isConnected = response.IsSuccessStatusCode;
}
logger.Info("测试摄像头连接");
var (isSuccess, message) = await _videoStreamService.TestCameraConnectionAsync();
return TypedResults.Ok(new
{
isConnected = isConnected,
success = isSuccess,
message = message,
cameraAddress = _videoStreamService.CameraAddress,
cameraPort = _videoStreamService.CameraPort,
timestamp = DateTime.Now
});
}
catch (Exception ex)
{

View File

@@ -38,7 +38,7 @@ class Camera
public async ValueTask<Result<bool>> Init()
{
var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.timeout);
var ret = await i2c.WriteData(0x78, new byte[] { 0x08, 0x30, 0x02 }, Peripherals.I2cClient.I2cProtocol.I2c);
var ret = await i2c.WriteData(0x78, new byte[] { 0x30, 0x08, 0x02 }, Peripherals.I2cClient.I2cProtocol.I2c);
if (!ret.IsSuccessful)
{
logger.Error($"I2C write failed during camera initialization for {this.address}:{this.port}, error: {ret.Error}");

View File

@@ -4,6 +4,73 @@ using Peripherals.CameraClient; // 添加摄像头客户端引用
namespace server.Services;
/// <summary>
/// 表示摄像头连接状态信息
/// </summary>
public class CameraStatus
{
/// <summary>
/// 摄像头的IP地址
/// </summary>
public string Address { get; set; } = string.Empty;
/// <summary>
/// 摄像头的端口号
/// </summary>
public int Port { get; set; }
/// <summary>
/// 是否已配置摄像头
/// </summary>
public bool IsConfigured { get; set; }
/// <summary>
/// 摄像头连接字符串IP:端口)
/// </summary>
public string ConnectionString { get; set; } = string.Empty;
}
/// <summary>
/// 表示视频流服务的运行状态
/// </summary>
public class ServiceStatus
{
/// <summary>
/// 服务是否正在运行
/// </summary>
public bool IsRunning { get; set; }
/// <summary>
/// 服务监听的端口号
/// </summary>
public int ServerPort { get; set; }
/// <summary>
/// 视频流的帧率FPS
/// </summary>
public int FrameRate { get; set; }
/// <summary>
/// 视频分辨率(如 640x480
/// </summary>
public string Resolution { get; set; } = string.Empty;
/// <summary>
/// 当前连接的客户端数量
/// </summary>
public int ConnectedClients { get; set; }
/// <summary>
/// 当前连接的客户端端点列表
/// </summary>
public List<string> ClientEndpoints { get; set; } = new();
/// <summary>
/// 摄像头连接状态信息
/// </summary>
public CameraStatus CameraStatus { get; set; } = new();
}
/// <summary>
/// HTTP 视频流服务,用于从 FPGA 获取图像数据并推送到前端网页
/// 支持动态配置摄像头地址和端口
@@ -28,6 +95,11 @@ public class HttpVideoStreamService : BackgroundService
private readonly List<HttpListenerResponse> _activeClients = new List<HttpListenerResponse>();
private readonly object _clientsLock = new object();
/// <summary>
/// 获取 / 设置视频流服务是否启用
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// 获取当前连接的客户端数量
/// </summary>
@@ -94,7 +166,7 @@ public class HttpVideoStreamService : BackgroundService
try
{
await Task.Run(() =>
await Task.Run(async () =>
{
lock (_cameraLock)
{
@@ -122,6 +194,22 @@ public class HttpVideoStreamService : BackgroundService
logger.Info("摄像头配置已更新: {Address}:{Port}", _cameraAddress, _cameraPort);
}
// Init Camera
{
var ret = await _camera.Init();
if (!ret.IsSuccessful)
{
logger.Error(ret.Error);
throw ret.Error;
}
if (!ret.Value)
{
logger.Error($"Camera Init Failed!");
throw new Exception($"Camera Init Failed!");
}
}
});
return true;
}
@@ -176,18 +264,15 @@ public class HttpVideoStreamService : BackgroundService
/// 获取摄像头连接状态
/// </summary>
/// <returns>连接状态信息</returns>
public object GetCameraStatus()
public CameraStatus GetCameraStatus()
{
lock (_cameraLock)
return new CameraStatus
{
return new
{
Address = _cameraAddress,
Port = _cameraPort,
IsConfigured = _camera != null,
ConnectionString = $"{_cameraAddress}:{_cameraPort}"
};
}
Address = _cameraAddress,
Port = _cameraPort,
IsConfigured = _camera != null,
ConnectionString = $"{_cameraAddress}:{_cameraPort}"
};
}
/// <summary>
@@ -215,7 +300,17 @@ public class HttpVideoStreamService : BackgroundService
_ = Task.Run(() => AcceptClientsAsync(stoppingToken), stoppingToken);
// 开始生成视频帧
await GenerateVideoFrames(stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
if (Enabled)
{
await GenerateVideoFrames(stoppingToken);
}
else
{
await Task.Delay(500, stoppingToken);
}
}
}
catch (HttpListenerException ex)
{
@@ -660,13 +755,13 @@ public class HttpVideoStreamService : BackgroundService
/// <summary>
/// 获取服务状态信息
/// </summary>
public object GetServiceStatus()
public ServiceStatus GetServiceStatus()
{
var cameraStatus = GetCameraStatus();
return new
return new ServiceStatus
{
IsRunning = _httpListener?.IsListening ?? false,
IsRunning = (_httpListener?.IsListening ?? false) && Enabled,
ServerPort = _serverPort,
FrameRate = _frameRate,
Resolution = $"{_frameWidth}x{_frameHeight}",
@@ -683,6 +778,8 @@ public class HttpVideoStreamService : BackgroundService
{
logger.Info("正在停止 HTTP 视频流服务...");
Enabled = false;
if (_httpListener != null && _httpListener.IsListening)
{
_httpListener.Stop();