feat: 完成jpeg后端
This commit is contained in:
		@@ -12,7 +12,7 @@ static class HdmiInAddr
 | 
			
		||||
    public const UInt32 HdmiIn_READFIFO = BASE + 0x1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class HdmiIn
 | 
			
		||||
public class HdmiIn
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,14 +7,28 @@ namespace Peripherals.JpegClient;
 | 
			
		||||
static class JpegAddr
 | 
			
		||||
{
 | 
			
		||||
    const UInt32 BASE = 0x0000_0000;
 | 
			
		||||
    public const UInt32 ENABLE = BASE + 0x0;
 | 
			
		||||
    public const UInt32 FRAME_NUM = BASE + 0x1;
 | 
			
		||||
    public const UInt32 FRAME_INFO = BASE + 0x2;
 | 
			
		||||
    public const UInt32 FRAME_SAMPLE_RATE = BASE + 0x3;
 | 
			
		||||
    public const UInt32 FRAME_DATA_MAX_POINTER = BASE + 0x4;
 | 
			
		||||
 | 
			
		||||
    public const UInt32 DDR_FRAME_DATA_ADDR = 0x0000_0000;
 | 
			
		||||
    public const UInt32 DDR_FRAME_DATA_MAX_ADDR = 0x8000_0000;
 | 
			
		||||
    public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0;
 | 
			
		||||
    public const UInt32 CAPTURE_WR_CTRL = BASE + 0x1;
 | 
			
		||||
 | 
			
		||||
    public const UInt32 START_WR_ADDR0 = BASE + 0x2;
 | 
			
		||||
    public const UInt32 END_WR_ADDR0 = BASE + 0x3;
 | 
			
		||||
    public const UInt32 START_WR_ADDR1 = BASE + 0x4;
 | 
			
		||||
    public const UInt32 END_WR_ADDR1 = BASE + 0x5;
 | 
			
		||||
    public const UInt32 START_RD_ADDR0 = BASE + 0x6;
 | 
			
		||||
    public const UInt32 END_RD_ADDR0 = BASE + 0x7;
 | 
			
		||||
 | 
			
		||||
    public const UInt32 HDMI_NOT_READY = BASE + 0x8;
 | 
			
		||||
    public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x9;
 | 
			
		||||
 | 
			
		||||
    public const UInt32 JPEG_HEIGHT_WIDTH = BASE + 0xA;
 | 
			
		||||
    public const UInt32 JPEG_ADD_NEED_FRAME_NUM = BASE + 0xB;
 | 
			
		||||
    public const UInt32 JPEG_FRAME_SAVE_NUM = BASE + 0xC;
 | 
			
		||||
    public const UInt32 JPEG_FIFO_FRAME_INFO = BASE + 0xD;
 | 
			
		||||
 | 
			
		||||
    public const UInt32 ADDR_HDMI_WD_START = 0x4000_0000;
 | 
			
		||||
    public const UInt32 ADDR_JPEG_START = 0x8000_0000;
 | 
			
		||||
    public const UInt32 ADDR_JPEG_END = 0xA000_0000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class JpegInfo
 | 
			
		||||
@@ -79,39 +93,248 @@ public class Jpeg
 | 
			
		||||
        this.timeout = timeout;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<Result<bool>> Init(bool enable = true)
 | 
			
		||||
    {
 | 
			
		||||
        {
 | 
			
		||||
            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 SetEnable(true);
 | 
			
		||||
        else return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<bool> SetEnable(bool enable)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.ENABLE, Convert.ToUInt32(enable), this.timeout);
 | 
			
		||||
        if (enable)
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddrSeq(
 | 
			
		||||
                this.ep,
 | 
			
		||||
                this.taskID,
 | 
			
		||||
                [JpegAddr.CAPTURE_RD_CTRL, JpegAddr.CAPTURE_WR_CTRL],
 | 
			
		||||
                [0b11, 0b01],
 | 
			
		||||
                this.timeout
 | 
			
		||||
            );
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set JPEG enable: {ret.Error}");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return ret.Value;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddrSeq(
 | 
			
		||||
                this.ep,
 | 
			
		||||
                this.taskID,
 | 
			
		||||
                [JpegAddr.CAPTURE_RD_CTRL, JpegAddr.CAPTURE_WR_CTRL],
 | 
			
		||||
                [0b00, 0b00],
 | 
			
		||||
                this.timeout
 | 
			
		||||
            );
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set JPEG disable: {ret.Error}");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return ret.Value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<Result<bool>> CheckHdmiIsReady()
 | 
			
		||||
    {
 | 
			
		||||
        var ret = await UDPClientPool.ReadAddrWithWait(
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.HDMI_NOT_READY, 0b01, 0b01, 100, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to set JPEG enable: {ret.Error}");
 | 
			
		||||
            return false;
 | 
			
		||||
            logger.Error($"Failed to check HDMI status: {ret.Error}");
 | 
			
		||||
            return new(ret.Error);
 | 
			
		||||
        }
 | 
			
		||||
        return ret.Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<bool> SetSampleRate(uint rate)
 | 
			
		||||
    public async ValueTask<Result<(int, int)>> GetHdmiResolution()
 | 
			
		||||
    {
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.FRAME_SAMPLE_RATE, rate, this.timeout);
 | 
			
		||||
        var ret = await UDPClientPool.ReadAddr(
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.HDMI_HEIGHT_WIDTH, 0, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to set JPEG sample rate: {ret.Error}");
 | 
			
		||||
            return false;
 | 
			
		||||
            logger.Error($"Failed to get HDMI resolution: {ret.Error}");
 | 
			
		||||
            return new(ret.Error);
 | 
			
		||||
        }
 | 
			
		||||
        return ret.Value;
 | 
			
		||||
 | 
			
		||||
        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[0] | (data[1] << 8);
 | 
			
		||||
        var height = data[2] | (data[3] << 8);
 | 
			
		||||
        return new((width, height));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<bool> SetSampleRate(JpegSampleRate rate)
 | 
			
		||||
    public async ValueTask<Result<bool>> ConnectJpeg2Hdmi(int width, int height)
 | 
			
		||||
    {
 | 
			
		||||
        return await SetSampleRate((uint)rate);
 | 
			
		||||
        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 / 4);
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.START_WR_ADDR0, JpegAddr.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, JpegAddr.END_WR_ADDR0,
 | 
			
		||||
                JpegAddr.ADDR_HDMI_WD_START + frameSize, 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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.START_RD_ADDR0, JpegAddr.ADDR_HDMI_WD_START, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg input start address: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg input address");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.END_RD_ADDR0,
 | 
			
		||||
                JpegAddr.ADDR_HDMI_WD_START + frameSize, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg input end address: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg input end address");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.START_WR_ADDR1, JpegAddr.ADDR_JPEG_START, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg output start address: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg output start address");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.END_WR_ADDR1, JpegAddr.ADDR_JPEG_END, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg output end address: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set jpeg output end address");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // public async ValueTask<bool> SetSampleRate(uint rate)
 | 
			
		||||
    // {
 | 
			
		||||
    //     var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
    //         this.ep, this.taskID, JpegAddr.FRAME_SAMPLE_RATE, rate, this.timeout);
 | 
			
		||||
    //     if (!ret.IsSuccessful)
 | 
			
		||||
    //     {
 | 
			
		||||
    //         logger.Error($"Failed to set JPEG sample rate: {ret.Error}");
 | 
			
		||||
    //         return false;
 | 
			
		||||
    //     }
 | 
			
		||||
    //     return ret.Value;
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // public async ValueTask<bool> SetSampleRate(JpegSampleRate rate)
 | 
			
		||||
    // {
 | 
			
		||||
    //     return await SetSampleRate((uint)rate);
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<uint> GetFrameNumber()
 | 
			
		||||
    {
 | 
			
		||||
        var ret = await UDPClientPool.ReadAddrByte(
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.FRAME_NUM, this.timeout);
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.JPEG_FRAME_SAVE_NUM, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to get JPEG frame number: {ret.Error}");
 | 
			
		||||
@@ -122,7 +345,7 @@ public class Jpeg
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<Optional<List<JpegInfo>>> GetFrameInfo(int num)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = await UDPClientPool.ReadAddr(this.ep, this.taskID, JpegAddr.FRAME_INFO, num, this.timeout);
 | 
			
		||||
        var ret = await UDPClientPool.ReadAddr(this.ep, this.taskID, JpegAddr.JPEG_FIFO_FRAME_INFO, num, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to get JPEG frame info: {ret.Error}");
 | 
			
		||||
@@ -150,10 +373,10 @@ public class Jpeg
 | 
			
		||||
        return new(infos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask<bool> UpdatePointer(uint cnt)
 | 
			
		||||
    public async ValueTask<bool> AddFrameNum2Process(uint cnt)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.FRAME_DATA_MAX_POINTER, cnt, this.timeout);
 | 
			
		||||
            this.ep, this.taskID, JpegAddr.JPEG_ADD_NEED_FRAME_NUM, cnt, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to update pointer: {ret.Error}");
 | 
			
		||||
@@ -171,13 +394,16 @@ public class Jpeg
 | 
			
		||||
        }
 | 
			
		||||
        MsgBus.UDPServer.ClearUDPData(this.ep.Address.ToString(), this.ep.Port);
 | 
			
		||||
 | 
			
		||||
        var firstReadLength = (int)(Math.Min(length, JpegAddr.DDR_FRAME_DATA_MAX_ADDR - offset));
 | 
			
		||||
        var firstReadLength = (int)(Math.Min(
 | 
			
		||||
            length,
 | 
			
		||||
            JpegAddr.ADDR_JPEG_END - JpegAddr.ADDR_JPEG_START - offset
 | 
			
		||||
        ));
 | 
			
		||||
        var secondReadLength = (int)(length - firstReadLength);
 | 
			
		||||
        var dataBytes = new byte[length];
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.ReadAddr4Bytes(
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.DDR_FRAME_DATA_ADDR + offset, firstReadLength, this.timeout);
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.ADDR_JPEG_START + offset, firstReadLength, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to get JPEG frame data: {ret.Error}");
 | 
			
		||||
@@ -194,7 +420,7 @@ public class Jpeg
 | 
			
		||||
        if (secondReadLength > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.ReadAddr4Bytes(
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.DDR_FRAME_DATA_ADDR, secondReadLength, this.timeout);
 | 
			
		||||
                this.ep, this.taskID, JpegAddr.ADDR_JPEG_START, secondReadLength, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to get JPEG frame data: {ret.Error}");
 | 
			
		||||
@@ -239,7 +465,7 @@ public class Jpeg
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UpdatePointer((uint)sizes.Length);
 | 
			
		||||
            var ret = await AddFrameNum2Process((uint)sizes.Length);
 | 
			
		||||
            if (!ret) logger.Error($"Failed to update pointer");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using Peripherals.HdmiInClient;
 | 
			
		||||
using Peripherals.JpegClient;
 | 
			
		||||
 | 
			
		||||
namespace server.Services;
 | 
			
		||||
 | 
			
		||||
@@ -12,6 +13,15 @@ public class HdmiVideoStreamEndpoint
 | 
			
		||||
    public string SnapshotUrl { get; set; } = "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class HdmiVideoStreamClient
 | 
			
		||||
{
 | 
			
		||||
    public required HdmiIn HdmiInClient { get; set; }
 | 
			
		||||
 | 
			
		||||
    public required Jpeg JpegClient { get; set; }
 | 
			
		||||
 | 
			
		||||
    public required CancellationTokenSource CTS { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
{
 | 
			
		||||
    private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
@@ -20,8 +30,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
 | 
			
		||||
    private HttpListener? _httpListener;
 | 
			
		||||
    private readonly int _serverPort = 4322;
 | 
			
		||||
    private readonly ConcurrentDictionary<string, HdmiIn> _hdmiInDict = new();
 | 
			
		||||
    private readonly ConcurrentDictionary<string, CancellationTokenSource> _hdmiInCtsDict = new();
 | 
			
		||||
    private readonly ConcurrentDictionary<string, HdmiVideoStreamClient> _clientDict = new();
 | 
			
		||||
 | 
			
		||||
    public HttpHdmiVideoStreamService(IServiceProvider serviceProvider)
 | 
			
		||||
    {
 | 
			
		||||
@@ -75,7 +84,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
 | 
			
		||||
        // 禁用所有活跃的HDMI传输
 | 
			
		||||
        var disableTasks = new List<Task>();
 | 
			
		||||
        foreach (var hdmiKey in _hdmiInDict.Keys)
 | 
			
		||||
        foreach (var hdmiKey in _clientDict.Keys)
 | 
			
		||||
        {
 | 
			
		||||
            disableTasks.Add(DisableHdmiTransmissionAsync(hdmiKey));
 | 
			
		||||
        }
 | 
			
		||||
@@ -84,8 +93,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
        await Task.WhenAll(disableTasks);
 | 
			
		||||
 | 
			
		||||
        // 清空字典
 | 
			
		||||
        _hdmiInDict.Clear();
 | 
			
		||||
        _hdmiInCtsDict.Clear();
 | 
			
		||||
        _clientDict.Clear();
 | 
			
		||||
 | 
			
		||||
        _httpListener?.Close(); // 立即关闭监听器,唤醒阻塞
 | 
			
		||||
        await base.StopAsync(cancellationToken);
 | 
			
		||||
@@ -95,11 +103,10 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var cts = _hdmiInCtsDict[key];
 | 
			
		||||
            cts.Cancel();
 | 
			
		||||
            var client = _clientDict[key];
 | 
			
		||||
            client.CTS.Cancel();
 | 
			
		||||
 | 
			
		||||
            var hdmiIn = _hdmiInDict[key];
 | 
			
		||||
            var disableResult = await hdmiIn.EnableTrans(false);
 | 
			
		||||
            var disableResult = await client.HdmiInClient.EnableTrans(false);
 | 
			
		||||
            if (disableResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Info("Successfully disabled HDMI transmission");
 | 
			
		||||
@@ -115,31 +122,9 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取/创建 HdmiIn 实例
 | 
			
		||||
    private async Task<HdmiIn?> GetOrCreateHdmiInAsync(string boardId)
 | 
			
		||||
    private async Task<HdmiVideoStreamClient?> GetOrCreateClientAsync(string boardId)
 | 
			
		||||
    {
 | 
			
		||||
        if (_hdmiInDict.TryGetValue(boardId, out var hdmiIn))
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var enableResult = await hdmiIn.EnableTrans(true);
 | 
			
		||||
                if (!enableResult.IsSuccessful)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.Error($"Failed to enable HDMI transmission for board {boardId}: {enableResult.Error}");
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
                logger.Info($"Successfully enabled HDMI transmission for board {boardId}");
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error(ex, $"Exception occurred while enabling HDMI transmission for board {boardId}");
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _hdmiInDict[boardId] = hdmiIn;
 | 
			
		||||
            _hdmiInCtsDict[boardId] = new CancellationTokenSource();
 | 
			
		||||
            return hdmiIn;
 | 
			
		||||
        }
 | 
			
		||||
        if (_clientDict.TryGetValue(boardId, out var client)) return client;
 | 
			
		||||
 | 
			
		||||
        using var scope = _serviceProvider.CreateScope();
 | 
			
		||||
        var userManager = scope.ServiceProvider.GetRequiredService<Database.UserManager>();
 | 
			
		||||
@@ -153,18 +138,31 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
 | 
			
		||||
        var board = boardRet.Value.Value;
 | 
			
		||||
 | 
			
		||||
        hdmiIn = new HdmiIn(board.IpAddr, board.Port, 0); // taskID 可根据实际需求调整
 | 
			
		||||
        client = new HdmiVideoStreamClient()
 | 
			
		||||
        {
 | 
			
		||||
            HdmiInClient = new HdmiIn(board.IpAddr, board.Port, 1),
 | 
			
		||||
            JpegClient = new Jpeg(board.IpAddr, board.Port, 1),
 | 
			
		||||
            CTS = new CancellationTokenSource()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // 启用HDMI传输
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var enableResult = await hdmiIn.EnableTrans(true);
 | 
			
		||||
            if (!enableResult.IsSuccessful)
 | 
			
		||||
            var hdmiEnableRet = await client.HdmiInClient.EnableTrans(true);
 | 
			
		||||
            if (!hdmiEnableRet.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to enable HDMI transmission for board {boardId}: {enableResult.Error}");
 | 
			
		||||
                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}");
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            logger.Info($"Successfully enabled JPEG transmission for board {boardId}");
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -172,9 +170,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _hdmiInDict[boardId] = hdmiIn;
 | 
			
		||||
        _hdmiInCtsDict[boardId] = new CancellationTokenSource();
 | 
			
		||||
        return hdmiIn;
 | 
			
		||||
        _clientDict[boardId] = client;
 | 
			
		||||
        return client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task HandleRequestAsync(HttpListenerContext context, CancellationToken cancellationToken)
 | 
			
		||||
@@ -187,14 +184,14 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var hdmiIn = await GetOrCreateHdmiInAsync(boardId);
 | 
			
		||||
        if (hdmiIn == null)
 | 
			
		||||
        var client = await GetOrCreateClientAsync(boardId);
 | 
			
		||||
        if (client == null)
 | 
			
		||||
        {
 | 
			
		||||
            await SendErrorAsync(context.Response, "Invalid boardId or board not available");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var hdmiInToken = _hdmiInCtsDict[boardId].Token;
 | 
			
		||||
        var hdmiInToken = _clientDict[boardId].CTS.Token;
 | 
			
		||||
        if (hdmiInToken == null)
 | 
			
		||||
        {
 | 
			
		||||
            await SendErrorAsync(context.Response, "HDMI input is not available");
 | 
			
		||||
@@ -203,11 +200,11 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
 | 
			
		||||
        if (path == "/snapshot")
 | 
			
		||||
        {
 | 
			
		||||
            await HandleSnapshotRequestAsync(context.Response, hdmiIn, hdmiInToken);
 | 
			
		||||
            await HandleSnapshotRequestAsync(context.Response, client, hdmiInToken);
 | 
			
		||||
        }
 | 
			
		||||
        else if (path == "/mjpeg")
 | 
			
		||||
        {
 | 
			
		||||
            await HandleMjpegStreamAsync(context.Response, hdmiIn, hdmiInToken);
 | 
			
		||||
            await HandleMjpegStreamAsync(context.Response, client, hdmiInToken);
 | 
			
		||||
        }
 | 
			
		||||
        else if (path == "/video")
 | 
			
		||||
        {
 | 
			
		||||
@@ -219,14 +216,15 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task HandleSnapshotRequestAsync(HttpListenerResponse response, HdmiIn hdmiIn, CancellationToken cancellationToken)
 | 
			
		||||
    private async Task HandleSnapshotRequestAsync(
 | 
			
		||||
        HttpListenerResponse response, HdmiVideoStreamClient client, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            logger.Debug("处理HDMI快照请求");
 | 
			
		||||
 | 
			
		||||
            // 从HDMI读取RGB565数据
 | 
			
		||||
            var frameResult = await hdmiIn.ReadFrame();
 | 
			
		||||
            var frameResult = await client.HdmiInClient.ReadFrame();
 | 
			
		||||
            if (!frameResult.IsSuccessful || frameResult.Value == null)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error("HDMI快照获取失败");
 | 
			
		||||
@@ -260,7 +258,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task HandleMjpegStreamAsync(HttpListenerResponse response, HdmiIn hdmiIn, CancellationToken cancellationToken)
 | 
			
		||||
    private async Task HandleMjpegStreamAsync(
 | 
			
		||||
        HttpListenerResponse response, HdmiVideoStreamClient client, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -280,7 +279,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
                {
 | 
			
		||||
                    var frameStartTime = DateTime.UtcNow;
 | 
			
		||||
 | 
			
		||||
                    var ret = await hdmiIn.GetMJpegFrame();
 | 
			
		||||
                    var ret = await client.HdmiInClient.GetMJpegFrame();
 | 
			
		||||
                    if (ret == null) continue;
 | 
			
		||||
                    var frame = ret.Value;
 | 
			
		||||
 | 
			
		||||
@@ -315,7 +314,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                // 停止传输时禁用HDMI传输
 | 
			
		||||
                await hdmiIn.EnableTrans(false);
 | 
			
		||||
                await client.HdmiInClient.EnableTrans(false);
 | 
			
		||||
                logger.Info("已禁用HDMI传输");
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
 
 | 
			
		||||
@@ -331,7 +331,9 @@ public class UDPClientPool
 | 
			
		||||
    /// <param name="timeout">超时时间(毫秒)</param>
 | 
			
		||||
    /// <returns>校验结果,true表示在超时前数据匹配期望值</returns>
 | 
			
		||||
    public static async ValueTask<Result<bool>> ReadAddrWithWait(
 | 
			
		||||
            IPEndPoint endPoint, int taskID, uint devAddr, UInt32 result, UInt32 resultMask, int waittime = 100, int timeout = 1000)
 | 
			
		||||
            IPEndPoint endPoint, int taskID, uint devAddr,
 | 
			
		||||
            UInt32 result, UInt32 resultMask,
 | 
			
		||||
            int waittime = 100, int timeout = 1000)
 | 
			
		||||
    {
 | 
			
		||||
        var address = endPoint.Address.ToString();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user