feat: 使用DDR读取Hdmi视频流

This commit is contained in:
SikongJueluo 2025-08-19 15:20:17 +08:00
parent 7e53b805ae
commit 3c73aa344a
No known key found for this signature in database
5 changed files with 326 additions and 157 deletions

View File

@ -7,8 +7,16 @@ namespace Peripherals.HdmiInClient;
static class HdmiInAddr static class HdmiInAddr
{ {
public const UInt32 BASE = 0xA000_0000; public const UInt32 BASE = 0xA000_0000;
public const UInt32 HdmiIn_CTRL = BASE + 0x0; //[0]: rstn, 0 is reset.
public const UInt32 HdmiIn_READFIFO = BASE + 0x1; public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0;
public const UInt32 START_WR_ADDR0 = BASE + 0x2;
public const UInt32 END_WR_ADDR0 = BASE + 0x3;
public const UInt32 HDMI_NOT_READY = BASE + 0x8;
public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x9;
public const UInt32 ADDR_HDMI_WD_START = 0x0400_0000;
} }
public class HdmiIn public class HdmiIn
@ -21,10 +29,9 @@ public class HdmiIn
readonly string address; readonly string address;
private IPEndPoint ep; private IPEndPoint ep;
// 动态分辨率参数 public int Width { get; private set; }
private UInt16 _currentWidth = 960; public int Height { get; private set; }
private UInt16 _currentHeight = 540; public int FrameLength => Width * Height * 3 / 4;
private UInt32 _currentFrameLength = 960 * 540 * 2 / 4; // RGB565格式2字节/像素按4字节对齐
/// <summary> /// <summary>
/// 初始化HDMI输入客户端 /// 初始化HDMI输入客户端
@ -44,9 +51,54 @@ public class HdmiIn
this.timeout = timeout; this.timeout = timeout;
} }
public async ValueTask<Result<bool>> EnableTrans(bool isEnable) public async ValueTask<Result<bool>> Init(bool enable = true)
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, HdmiInAddr.HdmiIn_CTRL, (isEnable ? 0x00000001u : 0x00000000u)); {
var ret = await CheckHdmiIsReady();
if (!ret.IsSuccessful)
{
logger.Error($"Failed to check HDMI ready: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("HDMI not ready");
return new(false);
}
}
int width = -1, height = -1;
{
var ret = await GetHdmiResolution();
if (!ret.IsSuccessful)
{
logger.Error($"Failed to get HDMI resolution: {ret.Error}");
return new(ret.Error);
}
(width, height) = ret.Value;
}
{
var ret = await ConnectJpeg2Hdmi(width, height);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to connect JPEG to HDMI: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("Failed to connect JPEG to HDMI");
return false;
}
}
if (enable) return await SetTransEnable(true);
else return true;
}
public async ValueTask<Result<bool>> SetTransEnable(bool isEnable)
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, HdmiInAddr.CAPTURE_RD_CTRL, (isEnable ? 0x00000001u : 0x00000000u));
if (!ret.IsSuccessful) if (!ret.IsSuccessful)
{ {
logger.Error($"Failed to write HdmiIn_CTRL to HdmiIn at {this.address}:{this.port}, error: {ret.Error}"); logger.Error($"Failed to write HdmiIn_CTRL to HdmiIn at {this.address}:{this.port}, error: {ret.Error}");
@ -75,8 +127,8 @@ public class HdmiIn
var result = await UDPClientPool.ReadAddr4BytesAsync( var result = await UDPClientPool.ReadAddr4BytesAsync(
this.ep, this.ep,
this.taskID, // taskID this.taskID, // taskID
HdmiInAddr.HdmiIn_READFIFO, HdmiInAddr.ADDR_HDMI_WD_START,
(int)_currentFrameLength, // 使用当前分辨率的动态大小 FrameLength, // 使用当前分辨率的动态大小
BurstType.FixedBurst, BurstType.FixedBurst,
this.timeout); this.timeout);
@ -99,7 +151,7 @@ public class HdmiIn
return result.Value; return result.Value;
} }
public async ValueTask<(byte[] header, byte[] data, byte[] footer)?> GetMJpegFrame() public async ValueTask<Optional<(byte[] header, byte[] data, byte[] footer)>> GetMJpegFrame()
{ {
// 从HDMI读取RGB24数据 // 从HDMI读取RGB24数据
var readStartTime = DateTime.UtcNow; var readStartTime = DateTime.UtcNow;
@ -110,13 +162,13 @@ public class HdmiIn
if (!frameResult.IsSuccessful || frameResult.Value == null) if (!frameResult.IsSuccessful || frameResult.Value == null)
{ {
logger.Warn("HDMI帧读取失败或为空"); logger.Warn("HDMI帧读取失败或为空");
return null; return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
} }
var rgb24Data = frameResult.Value; var rgb24Data = frameResult.Value;
// 验证数据长度是否正确 (RGB24为每像素2字节) // 验证数据长度是否正确 (RGB24为每像素2字节)
var expectedLength = _currentWidth * _currentHeight * 2; var expectedLength = Width * Height * 2;
if (rgb24Data.Length != expectedLength) if (rgb24Data.Length != expectedLength)
{ {
logger.Warn("HDMI数据长度不匹配期望: {Expected}, 实际: {Actual}", logger.Warn("HDMI数据长度不匹配期望: {Expected}, 实际: {Actual}",
@ -125,40 +177,105 @@ public class HdmiIn
// 将RGB24转换为JPEG参考Camera版本的处理 // 将RGB24转换为JPEG参考Camera版本的处理
var jpegStartTime = DateTime.UtcNow; var jpegStartTime = DateTime.UtcNow;
var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, _currentWidth, _currentHeight, 80); var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, Width, Height, 80);
var jpegEndTime = DateTime.UtcNow; var jpegEndTime = DateTime.UtcNow;
var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds; var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds;
if (!jpegResult.IsSuccessful) if (!jpegResult.IsSuccessful)
{ {
logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error); logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error);
return null; return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
} }
var jpegData = jpegResult.Value; var jpegData = jpegResult.Value;
// 发送MJPEG帧使用Camera版本的格式
var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length); var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length);
var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter(); var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter();
return (mjpegFrameHeader, jpegData, mjpegFrameFooter); return (mjpegFrameHeader, jpegData, mjpegFrameFooter);
} }
/// <summary> public async ValueTask<Result<bool>> CheckHdmiIsReady()
/// 获取当前分辨率
/// </summary>
/// <returns>当前分辨率(宽度, 高度)</returns>
public (int Width, int Height) GetCurrentResolution()
{ {
return (_currentWidth, _currentHeight); var ret = await UDPClientPool.ReadAddrWithWait(
this.ep, this.taskID, HdmiInAddr.HDMI_NOT_READY, 0b00, 0b01, 100, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to check HDMI status: {ret.Error}");
return new(ret.Error);
}
return ret.Value;
} }
/// <summary> public async ValueTask<Result<(int, int)>> GetHdmiResolution()
/// 获取当前帧长度
/// </summary>
/// <returns>当前帧长度</returns>
public UInt32 GetCurrentFrameLength()
{ {
return _currentFrameLength; var ret = await UDPClientPool.ReadAddrByte(
this.ep, this.taskID, HdmiInAddr.HDMI_HEIGHT_WIDTH, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to get HDMI resolution: {ret.Error}");
return new(ret.Error);
}
var data = ret.Value.Options.Data;
if (data == null || data.Length != 4)
{
logger.Error($"Invalid HDMI resolution data length: {data?.Length ?? 0}");
return new(new Exception("Invalid HDMI resolution data length"));
}
var width = data[3] | (data[2] << 8);
var height = data[1] | (data[0] << 8);
this.Width = width;
this.Height = height;
logger.Info($"HDMI resolution: {width}x{height}");
return new((width, height));
}
public async ValueTask<Result<bool>> ConnectJpeg2Hdmi(int width, int height)
{
if (width <= 0 || height <= 0)
{
logger.Error($"Invalid HDMI resolution: {width}x{height}");
return new(new ArgumentException("Invalid HDMI resolution"));
}
var frameSize = (UInt32)(width * height);
{
var ret = await UDPClientPool.WriteAddr(
this.ep, this.taskID, HdmiInAddr.START_WR_ADDR0, HdmiInAddr.ADDR_HDMI_WD_START, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to set HDMI output start address: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error($"Failed to set HDMI output start address");
return false;
} }
} }
{
var ret = await UDPClientPool.WriteAddr(
this.ep, this.taskID, HdmiInAddr.END_WR_ADDR0,
HdmiInAddr.ADDR_HDMI_WD_START + frameSize - 1, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to set HDMI output end address: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error($"Failed to set HDMI output address");
return false;
}
}
return true;
}
}

View File

@ -17,7 +17,7 @@ public class HdmiVideoStreamClient
{ {
public required HdmiIn HdmiInClient { get; set; } public required HdmiIn HdmiInClient { get; set; }
public required Jpeg JpegClient { get; set; } // public required Jpeg JpegClient { get; set; }
public required CancellationTokenSource CTS { get; set; } public required CancellationTokenSource CTS { get; set; }
@ -102,7 +102,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
var client = _clientDict[key]; var client = _clientDict[key];
client.CTS.Cancel(); client.CTS.Cancel();
var disableResult = await client.JpegClient.SetEnable(false); // var disableResult = await client.JpegClient.SetEnable(false);
var disableResult = await client.HdmiInClient.SetTransEnable(false);
if (disableResult) if (disableResult)
{ {
logger.Info("Successfully disabled HDMI transmission"); logger.Info("Successfully disabled HDMI transmission");
@ -111,6 +112,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
{ {
logger.Error($"Failed to disable HDMI transmission"); logger.Error($"Failed to disable HDMI transmission");
} }
client.CTS = new CancellationTokenSource();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -120,13 +123,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
private async Task<HdmiVideoStreamClient?> GetOrCreateClientAsync(string boardId) private async Task<HdmiVideoStreamClient?> GetOrCreateClientAsync(string boardId)
{ {
if (_clientDict.TryGetValue(boardId, out var client)) if (!_clientDict.TryGetValue(boardId, out var client))
{ {
client.Width = client.JpegClient.Width;
client.Height = client.JpegClient.Height;
return client;
}
var userManager = new Database.UserManager(); var userManager = new Database.UserManager();
var boardRet = userManager.GetBoardByID(Guid.Parse(boardId)); var boardRet = userManager.GetBoardByID(Guid.Parse(boardId));
@ -141,32 +139,35 @@ public class HttpHdmiVideoStreamService : BackgroundService
client = new HdmiVideoStreamClient() client = new HdmiVideoStreamClient()
{ {
HdmiInClient = new HdmiIn(board.IpAddr, board.Port, 1), HdmiInClient = new HdmiIn(board.IpAddr, board.Port, 1),
JpegClient = new Jpeg(board.IpAddr, board.Port, 1), // JpegClient = new Jpeg(board.IpAddr, board.Port, 1),
CTS = new CancellationTokenSource(), CTS = new CancellationTokenSource(),
Offset = 0 Offset = 0
}; };
}
// 启用HDMI传输 // 启用HDMI传输
try try
{ {
// var hdmiEnableRet = await client.JpegClient.EnableTrans(true); var hdmiEnableRet = await client.HdmiInClient.Init(true);
// if (!hdmiEnableRet.IsSuccessful) if (!hdmiEnableRet.IsSuccessful)
// {
// logger.Error($"Failed to enable HDMI transmission for board {boardId}: {hdmiEnableRet.Error}");
// return null;
// }
// logger.Info($"Successfully enabled HDMI transmission for board {boardId}");
var jpegEnableRet = await client.JpegClient.Init(true);
if (!jpegEnableRet.IsSuccessful)
{ {
logger.Error($"Failed to enable JPEG transmission for board {boardId}: {jpegEnableRet.Error}"); logger.Error($"Failed to enable HDMI transmission for board {boardId}: {hdmiEnableRet.Error}");
return null; return null;
} }
logger.Info($"Successfully enabled JPEG transmission for board {boardId}"); logger.Info($"Successfully enabled HDMI transmission for board {boardId}");
client.Width = client.JpegClient.Width; // var jpegEnableRet = await client.JpegClient.Init(true);
client.Height = client.JpegClient.Height; // if (!jpegEnableRet.IsSuccessful)
// {
// logger.Error($"Failed to enable JPEG transmission for board {boardId}: {jpegEnableRet.Error}");
// return null;
// }
// logger.Info($"Successfully enabled JPEG transmission for board {boardId}");
client.Width = client.HdmiInClient.Width;
client.Height = client.HdmiInClient.Height;
// client.Width = client.JpegClient.Width;
// client.Height = client.JpegClient.Height;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -195,15 +196,16 @@ public class HttpHdmiVideoStreamService : BackgroundService
return; return;
} }
var hdmiInToken = _clientDict[boardId].CTS.Token; var token = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken, client.CTS.Token).Token;
if (path == "/snapshot") if (path == "/snapshot")
{ {
await HandleSnapshotRequestAsync(context.Response, client, hdmiInToken); await HandleSnapshotRequestAsync(context.Response, client, token);
} }
else if (path == "/mjpeg") else if (path == "/mjpeg")
{ {
await HandleMjpegStreamAsync(context.Response, client, hdmiInToken); await HandleMjpegStreamAsync(context.Response, client, token);
} }
else if (path == "/video") else if (path == "/video")
{ {
@ -223,36 +225,47 @@ public class HttpHdmiVideoStreamService : BackgroundService
logger.Debug("处理HDMI快照请求"); logger.Debug("处理HDMI快照请求");
// 从HDMI读取RGB565数据 // 从HDMI读取RGB565数据
var frameResult = await client.JpegClient.GetMultiFrames((uint)client.Offset); // var frameResult = await client.JpegClient.GetMultiFrames((uint)client.Offset);
if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0) // if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0)
{ // {
logger.Error("HDMI快照获取失败"); // logger.Error("HDMI快照获取失败");
response.StatusCode = 500; // response.StatusCode = 500;
var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI snapshot"); // var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI snapshot");
await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); // await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken);
response.Close(); // response.Close();
return; // return;
} // }
var jpegData = frameResult.Value[0]; // var jpegData = frameResult.Value[0];
var quantTableResult = await client.JpegClient.GetQuantizationTable(); // var quantTableResult = await client.JpegClient.GetQuantizationTable();
if (!quantTableResult.IsSuccessful || quantTableResult.Value == null) // if (!quantTableResult.IsSuccessful || quantTableResult.Value == null)
{ // {
logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error); // logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error);
response.StatusCode = 500; // response.StatusCode = 500;
var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"); // var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table");
await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); // await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken);
response.Close(); // response.Close();
return; // return;
} // }
var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height, quantTableResult.Value); // var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height, quantTableResult.Value);
if (!jpegImage.IsSuccessful) // if (!jpegImage.IsSuccessful)
// {
// logger.Error("JPEG数据补全失败");
// response.StatusCode = 500;
// var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to complete JPEG data");
// await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken);
// response.Close();
// return;
// }
var jpegImage = await client.HdmiInClient.GetMJpegFrame();
if (!jpegImage.HasValue)
{ {
logger.Error("JPEG数据补全失败"); logger.Error("获取HDMI MJPEG帧失败");
response.StatusCode = 500; response.StatusCode = 500;
var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to complete JPEG data"); var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI MJPEG frame");
await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken);
response.Close(); response.Close();
return; return;
@ -260,13 +273,13 @@ public class HttpHdmiVideoStreamService : BackgroundService
// 设置响应头参考Camera版本 // 设置响应头参考Camera版本
response.ContentType = "image/jpeg"; response.ContentType = "image/jpeg";
response.ContentLength64 = jpegImage.Value.Length; response.ContentLength64 = jpegImage.Value.data.Length;
response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
await response.OutputStream.WriteAsync(jpegImage.Value, 0, jpegImage.Value.Length, cancellationToken); await response.OutputStream.WriteAsync(jpegImage.Value.data, 0, jpegImage.Value.data.Length, cancellationToken);
await response.OutputStream.FlushAsync(cancellationToken); await response.OutputStream.FlushAsync(cancellationToken);
logger.Debug("已发送HDMI快照图像大小{Size} 字节", jpegImage.Value.Length); logger.Debug("已发送HDMI快照图像大小{Size} 字节", jpegImage.Value.data.Length);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -275,6 +288,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
} }
finally finally
{ {
response.StatusCode = 200;
response.Close(); response.Close();
} }
} }
@ -292,17 +306,17 @@ public class HttpHdmiVideoStreamService : BackgroundService
logger.Debug("开始HDMI MJPEG流传输"); logger.Debug("开始HDMI MJPEG流传输");
var quantTableResult = await client.JpegClient.GetQuantizationTable(); // var quantTableResult = await client.JpegClient.GetQuantizationTable();
if (!quantTableResult.IsSuccessful || quantTableResult.Value == null) // if (!quantTableResult.IsSuccessful || quantTableResult.Value == null)
{ // {
logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error); // logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error);
response.StatusCode = 500; // response.StatusCode = 500;
await response.OutputStream.WriteAsync( // await response.OutputStream.WriteAsync(
System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"), 0, 0, cancellationToken); // System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"), 0, 0, cancellationToken);
response.Close(); // response.Close();
return; // return;
} // }
var quantTable = quantTableResult.Value; // var quantTable = quantTableResult.Value;
int frameCounter = 0; int frameCounter = 0;
@ -310,30 +324,10 @@ public class HttpHdmiVideoStreamService : BackgroundService
{ {
var frameStartTime = DateTime.UtcNow; var frameStartTime = DateTime.UtcNow;
var frameResult = var frameRet = await client.HdmiInClient.GetMJpegFrame();
await client.JpegClient.GetMultiFrames((uint)client.Offset); if (!frameRet.HasValue)
if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0)
{ {
logger.Error("获取HDMI帧失败"); logger.Error("获取HDMI帧失败");
await Task.Delay(100, cancellationToken);
continue;
}
foreach (var framebytes in frameResult.Value)
{
var jpegImage = Common.Image.CompleteJpegData(framebytes, client.Width, client.Height, quantTable);
if (!jpegImage.IsSuccessful)
{
logger.Error("JPEG数据不完整");
await Task.Delay(100, cancellationToken);
continue;
}
var frameRet = Common.Image.CreateMjpegFrameFromJpeg(jpegImage.Value);
if (!frameRet.IsSuccessful)
{
logger.Error("创建MJPEG帧失败");
await Task.Delay(100, cancellationToken);
continue; continue;
} }
var frame = frameRet.Value; var frame = frameRet.Value;
@ -354,7 +348,50 @@ public class HttpHdmiVideoStreamService : BackgroundService
frameCounter, totalTime, frame.data.Length); frameCounter, totalTime, frame.data.Length);
} }
} // var frameResult =
// await client.JpegClient.GetMultiFrames((uint)client.Offset);
// if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0)
// {
// logger.Error("获取HDMI帧失败");
// await Task.Delay(100, cancellationToken);
// continue;
// }
// foreach (var framebytes in frameResult.Value)
// {
// var jpegImage = Common.Image.CompleteJpegData(framebytes, client.Width, client.Height, quantTable);
// if (!jpegImage.IsSuccessful)
// {
// logger.Error("JPEG数据不完整");
// await Task.Delay(100, cancellationToken);
// continue;
// }
// var frameRet = Common.Image.CreateMjpegFrameFromJpeg(jpegImage.Value);
// if (!frameRet.IsSuccessful)
// {
// logger.Error("创建MJPEG帧失败");
// await Task.Delay(100, cancellationToken);
// continue;
// }
// var frame = frameRet.Value;
// await response.OutputStream.WriteAsync(frame.header, 0, frame.header.Length, cancellationToken); // await response.OutputStream.WriteAsync(frame.data, 0, frame.data.Length, cancellationToken);
// await response.OutputStream.WriteAsync(frame.footer, 0, frame.footer.Length, cancellationToken);
// await response.OutputStream.FlushAsync(cancellationToken);
// frameCounter++;
// var totalTime = (DateTime.UtcNow - frameStartTime).TotalMilliseconds;
// // 性能统计日志每30帧记录一次
// if (frameCounter % 30 == 0)
// {
// logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节",
// frameCounter, totalTime, frame.data.Length);
// }
// }
} }
} }
catch (Exception ex) catch (Exception ex)
@ -366,7 +403,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
try try
{ {
// 停止传输时禁用HDMI传输 // 停止传输时禁用HDMI传输
await client.HdmiInClient.EnableTrans(false); await client.HdmiInClient.SetTransEnable(false);
logger.Info("已禁用HDMI传输"); logger.Info("已禁用HDMI传输");
} }
catch (Exception ex) catch (Exception ex)

View File

@ -81,7 +81,12 @@
import { ref, onMounted, onUnmounted } from "vue"; import { ref, onMounted, onUnmounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { AuthManager } from "@/utils/AuthManager"; import { AuthManager } from "@/utils/AuthManager";
import { ExamClient, ResourceClient, type ExamInfo } from "@/APIClient"; import {
ExamClient,
ResourceClient,
ResourcePurpose,
type ExamInfo,
} from "@/APIClient";
// //
interface Tutorial { interface Tutorial {
@ -146,7 +151,7 @@ onMounted(async () => {
const resourceList = await resourceClient.getResourceList( const resourceList = await resourceClient.getResourceList(
exam.id, exam.id,
"cover", "cover",
"template", ResourcePurpose.Template,
); );
if (resourceList && resourceList.length > 0) { if (resourceList && resourceList.length > 0) {
// 使 // 使

View File

@ -430,13 +430,13 @@ function startStream() {
} }
// //
function stopStream() { async function stopStream() {
isPlaying.value = false; isPlaying.value = false;
currentVideoSource.value = ""; currentVideoSource.value = "";
videoStatus.value = "已停止播放"; videoStatus.value = "已停止播放";
const client = AuthManager.createClient(HdmiVideoStreamClient); const client = AuthManager.createClient(HdmiVideoStreamClient);
client.disableHdmiTransmission(); await client.disableHdmiTransmission();
addLog("info", "停止播放HDMI视频流"); addLog("info", "停止播放HDMI视频流");
alert?.info("已停止播放HDMI视频流"); alert?.info("已停止播放HDMI视频流");
@ -467,8 +467,9 @@ function handleVideoClick() {
} }
// //
function tryReconnect() { async function tryReconnect() {
hasVideoError.value = false; hasVideoError.value = false;
await stopStream();
if (endpoint.value) { if (endpoint.value) {
startStream(); startStream();
} }

View File

@ -171,7 +171,12 @@ import { useProvideComponentManager } from "@/components/LabCanvas";
import { useAlertStore } from "@/components/Alert"; import { useAlertStore } from "@/components/Alert";
import { AuthManager } from "@/utils/AuthManager"; import { AuthManager } from "@/utils/AuthManager";
import { useEquipments } from "@/stores/equipments"; import { useEquipments } from "@/stores/equipments";
import { DataClient, ResourceClient, type Board } from "@/APIClient"; import {
DataClient,
ResourceClient,
ResourcePurpose,
type Board,
} from "@/APIClient";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
@ -257,7 +262,11 @@ async function loadDocumentContent() {
const client = AuthManager.createClient(ResourceClient); const client = AuthManager.createClient(ResourceClient);
// markdown // markdown
const resources = await client.getResourceList(examId, "doc", "template"); const resources = await client.getResourceList(
examId,
"doc",
ResourcePurpose.Template,
);
if (resources && resources.length > 0) { if (resources && resources.length > 0) {
// markdown // markdown