From 229e6e70eda0c0561d6c31c9b7a9b961701ec757 Mon Sep 17 00:00:00 2001 From: alivender <13898766233@163.com> Date: Sun, 13 Jul 2025 10:51:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=86=E5=8C=96OV=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/Peripherals/CameraClient.cs | 980 +++++++++++++----- server/src/Services/HttpVideoStreamService.cs | 100 +- server/src/UdpClientPool.cs | 14 +- server/src/UdpServer.cs | 20 +- 4 files changed, 826 insertions(+), 288 deletions(-) diff --git a/server/src/Peripherals/CameraClient.cs b/server/src/Peripherals/CameraClient.cs index 1382dc4..382192f 100644 --- a/server/src/Peripherals/CameraClient.cs +++ b/server/src/Peripherals/CameraClient.cs @@ -40,193 +40,6 @@ class Camera const UInt32 FrameAddr = 0x00; const UInt32 FrameLength = DVPHO * DVPVO * 16 / 32; - static byte[][] InitCmdData = new byte[][] { - // Stop streaming - new byte[] { 0x30, 0x08, 0x82 }, //82是复位 - // delay 5ms - new byte[] { 0x30, 0x08, 0x42 }, //42是休眠 - new byte[] { 0x31, 0x03, 0x02 }, - new byte[] { 0x30, 0x17, 0xff }, - new byte[] { 0x30, 0x18, 0xff }, - new byte[] { 0x30, 0x37, 0x13 }, - new byte[] { 0x31, 0x08, 0x01 }, - new byte[] { 0x36, 0x30, 0x36 }, - new byte[] { 0x36, 0x31, 0x0e }, - new byte[] { 0x36, 0x32, 0xe2 }, - new byte[] { 0x36, 0x33, 0x12 }, - new byte[] { 0x36, 0x21, 0xe0 }, - new byte[] { 0x37, 0x04, 0xa0 }, - new byte[] { 0x37, 0x03, 0x5a }, - new byte[] { 0x37, 0x15, 0x78 }, - new byte[] { 0x37, 0x17, 0x01 }, - new byte[] { 0x37, 0x0b, 0x60 }, - new byte[] { 0x37, 0x05, 0x1a }, - new byte[] { 0x39, 0x05, 0x02 }, - new byte[] { 0x39, 0x06, 0x10 }, - new byte[] { 0x39, 0x01, 0x0a }, - new byte[] { 0x37, 0x31, 0x12 }, - new byte[] { 0x36, 0x00, 0x08 }, - new byte[] { 0x36, 0x01, 0x33 }, - new byte[] { 0x30, 0x2d, 0x60 }, - new byte[] { 0x36, 0x20, 0x52 }, - new byte[] { 0x37, 0x1b, 0x20 }, - new byte[] { 0x47, 0x1c, 0x50 }, - new byte[] { 0x3a, 0x13, 0x43 }, - new byte[] { 0x3a, 0x18, 0x00 }, - new byte[] { 0x3a, 0x19, 0xf8 }, - new byte[] { 0x36, 0x35, 0x13 }, - new byte[] { 0x36, 0x36, 0x03 }, - new byte[] { 0x36, 0x34, 0x40 }, - new byte[] { 0x36, 0x22, 0x01 }, - new byte[] { 0x3c, 0x01, 0x34 }, - new byte[] { 0x3c, 0x04, 0x28 }, - new byte[] { 0x3c, 0x05, 0x98 }, - new byte[] { 0x3c, 0x06, 0x00 }, - new byte[] { 0x3c, 0x07, 0x08 }, - new byte[] { 0x3c, 0x08, 0x00 }, - new byte[] { 0x3c, 0x09, 0x1c }, - new byte[] { 0x3c, 0x0a, 0x9c }, - new byte[] { 0x3c, 0x0b, 0x40 }, - // H_OFFSET/V_OFFSET - new byte[] { 0x38, 0x10, unchecked((byte)((H_OFFSET >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x11, unchecked((byte)(H_OFFSET & 0xFF)) }, //0010 - new byte[] { 0x38, 0x12, unchecked((byte)((V_OFFSET >> 8) & 0xFF)) }, - new byte[] { 0x37, 0x08, 0x64 }, - new byte[] { 0x40, 0x01, 0x02 }, - new byte[] { 0x40, 0x05, 0x1a }, - new byte[] { 0x30, 0x00, 0x00 }, - new byte[] { 0x30, 0x04, 0xff }, - new byte[] { 0x43, 0x00, 0x6F }, // RGB565:first byte:{g[2:0],b[4:0]],second byte:{r[4:0],g[5:3]} - new byte[] { 0x50, 0x1f, 0x01 }, // Format: ISP RGB - new byte[] { 0x44, 0x0e, 0x00 }, - new byte[] { 0x50, 0x00, 0xA7 }, //ISP控制 - new byte[] { 0x3a, 0x0f, 0x30 }, //AEC控制;stable range in high - new byte[] { 0x3a, 0x10, 0x28 }, //AEC控制;stable range in low - new byte[] { 0x3a, 0x1b, 0x30 }, //AEC控制;stable range out high - new byte[] { 0x3a, 0x1e, 0x26 }, //AEC控制;stable range out low - new byte[] { 0x3a, 0x11, 0x60 }, //AEC控制; fast zone high - new byte[] { 0x3a, 0x1f, 0x14 }, //AEC控制; fast zone low - // LENC - new byte[] { 0x58, 0x00, 0x23 }, new byte[] { 0x58, 0x01, 0x14 }, new byte[] { 0x58, 0x02, 0x0f }, - new byte[] { 0x58, 0x03, 0x0f }, new byte[] { 0x58, 0x04, 0x12 }, new byte[] { 0x58, 0x05, 0x26 }, - new byte[] { 0x58, 0x06, 0x0c }, new byte[] { 0x58, 0x07, 0x08 }, new byte[] { 0x58, 0x08, 0x05 }, - new byte[] { 0x58, 0x09, 0x05 }, new byte[] { 0x58, 0x0a, 0x08 }, new byte[] { 0x58, 0x0b, 0x0d }, - new byte[] { 0x58, 0x0c, 0x08 }, new byte[] { 0x58, 0x0d, 0x03 }, new byte[] { 0x58, 0x0e, 0x00 }, - new byte[] { 0x58, 0x0f, 0x00 }, new byte[] { 0x58, 0x10, 0x03 }, new byte[] { 0x58, 0x11, 0x09 }, - new byte[] { 0x58, 0x12, 0x07 }, new byte[] { 0x58, 0x13, 0x03 }, new byte[] { 0x58, 0x14, 0x00 }, - new byte[] { 0x58, 0x15, 0x01 }, new byte[] { 0x58, 0x16, 0x03 }, new byte[] { 0x58, 0x17, 0x08 }, - new byte[] { 0x58, 0x18, 0x0d }, new byte[] { 0x58, 0x19, 0x08 }, new byte[] { 0x58, 0x1a, 0x05 }, - new byte[] { 0x58, 0x1b, 0x06 }, new byte[] { 0x58, 0x1c, 0x08 }, new byte[] { 0x58, 0x1d, 0x0e }, - new byte[] { 0x58, 0x1e, 0x29 }, new byte[] { 0x58, 0x1f, 0x17 }, new byte[] { 0x58, 0x20, 0x11 }, - new byte[] { 0x58, 0x21, 0x11 }, new byte[] { 0x58, 0x22, 0x15 }, new byte[] { 0x58, 0x23, 0x28 }, - new byte[] { 0x58, 0x24, 0x46 }, new byte[] { 0x58, 0x25, 0x26 }, new byte[] { 0x58, 0x26, 0x08 }, - new byte[] { 0x58, 0x27, 0x26 }, new byte[] { 0x58, 0x28, 0x64 }, new byte[] { 0x58, 0x29, 0x26 }, - new byte[] { 0x58, 0x2a, 0x24 }, new byte[] { 0x58, 0x2b, 0x22 }, new byte[] { 0x58, 0x2c, 0x24 }, - new byte[] { 0x58, 0x2d, 0x24 }, new byte[] { 0x58, 0x2e, 0x06 }, new byte[] { 0x58, 0x2f, 0x22 }, - new byte[] { 0x58, 0x30, 0x40 }, new byte[] { 0x58, 0x31, 0x42 }, new byte[] { 0x58, 0x32, 0x24 }, - new byte[] { 0x58, 0x33, 0x26 }, new byte[] { 0x58, 0x34, 0x24 }, new byte[] { 0x58, 0x35, 0x22 }, - new byte[] { 0x58, 0x36, 0x22 }, new byte[] { 0x58, 0x37, 0x26 }, new byte[] { 0x58, 0x38, 0x44 }, - new byte[] { 0x58, 0x39, 0x24 }, new byte[] { 0x58, 0x3a, 0x26 }, new byte[] { 0x58, 0x3b, 0x28 }, - new byte[] { 0x58, 0x3c, 0x42 }, new byte[] { 0x58, 0x3d, 0xce }, - // AWB - new byte[] { 0x51, 0x80, 0xff }, new byte[] { 0x51, 0x81, 0xf2 }, new byte[] { 0x51, 0x82, 0x00 }, - new byte[] { 0x51, 0x83, 0x14 }, new byte[] { 0x51, 0x84, 0x25 }, new byte[] { 0x51, 0x85, 0x24 }, - new byte[] { 0x51, 0x86, 0x09 }, new byte[] { 0x51, 0x87, 0x09 }, new byte[] { 0x51, 0x88, 0x09 }, - new byte[] { 0x51, 0x89, 0x75 }, new byte[] { 0x51, 0x8a, 0x54 }, new byte[] { 0x51, 0x8b, 0xe0 }, - new byte[] { 0x51, 0x8c, 0xb2 }, new byte[] { 0x51, 0x8d, 0x42 }, new byte[] { 0x51, 0x8e, 0x3d }, - new byte[] { 0x51, 0x8f, 0x56 }, new byte[] { 0x51, 0x90, 0x46 }, new byte[] { 0x51, 0x91, 0xf8 }, - new byte[] { 0x51, 0x92, 0x04 }, new byte[] { 0x51, 0x93, 0x70 }, new byte[] { 0x51, 0x94, 0xf0 }, - new byte[] { 0x51, 0x95, 0xf0 }, new byte[] { 0x51, 0x96, 0x03 }, new byte[] { 0x51, 0x97, 0x01 }, - new byte[] { 0x51, 0x98, 0x04 }, new byte[] { 0x51, 0x99, 0x12 }, new byte[] { 0x51, 0x9a, 0x04 }, - new byte[] { 0x51, 0x9b, 0x00 }, new byte[] { 0x51, 0x9c, 0x06 }, new byte[] { 0x51, 0x9d, 0x82 }, - new byte[] { 0x51, 0x9e, 0x38 }, - // Gamma - new byte[] { 0x54, 0x80, 0x01 }, new byte[] { 0x54, 0x81, 0x08 }, new byte[] { 0x54, 0x82, 0x14 }, - new byte[] { 0x54, 0x83, 0x28 }, new byte[] { 0x54, 0x84, 0x51 }, new byte[] { 0x54, 0x85, 0x65 }, - new byte[] { 0x54, 0x86, 0x71 }, new byte[] { 0x54, 0x87, 0x7d }, new byte[] { 0x54, 0x88, 0x87 }, - new byte[] { 0x54, 0x89, 0x91 }, new byte[] { 0x54, 0x8a, 0x9a }, new byte[] { 0x54, 0x8b, 0xaa }, - new byte[] { 0x54, 0x8c, 0xb8 }, new byte[] { 0x54, 0x8d, 0xcd }, new byte[] { 0x54, 0x8e, 0xdd }, - new byte[] { 0x54, 0x8f, 0xea }, new byte[] { 0x54, 0x90, 0x1d }, - // CMX - new byte[] { 0x53, 0x81, 0x1e }, new byte[] { 0x53, 0x82, 0x5b }, new byte[] { 0x53, 0x83, 0x08 }, - new byte[] { 0x53, 0x84, 0x0a }, new byte[] { 0x53, 0x85, 0x7e }, new byte[] { 0x53, 0x86, 0x88 }, - new byte[] { 0x53, 0x87, 0x7c }, new byte[] { 0x53, 0x88, 0x6c }, new byte[] { 0x53, 0x89, 0x10 }, - new byte[] { 0x53, 0x8a, 0x01 }, new byte[] { 0x53, 0x8b, 0x98 }, - // SDE - new byte[] { 0x55, 0x80, 0x06 }, new byte[] { 0x55, 0x83, 0x40 }, new byte[] { 0x55, 0x84, 0x10 }, - new byte[] { 0x55, 0x89, 0x10 }, new byte[] { 0x55, 0x8a, 0x00 }, new byte[] { 0x55, 0x8b, 0xf8 }, - new byte[] { 0x50, 0x1d, 0x40 }, - // CIP - new byte[] { 0x53, 0x00, 0x08 }, new byte[] { 0x53, 0x01, 0x30 }, new byte[] { 0x53, 0x02, 0x10 }, - new byte[] { 0x53, 0x03, 0x00 }, new byte[] { 0x53, 0x04, 0x08 }, new byte[] { 0x53, 0x05, 0x30 }, - new byte[] { 0x53, 0x06, 0x08 }, new byte[] { 0x53, 0x07, 0x16 }, new byte[] { 0x53, 0x09, 0x08 }, - new byte[] { 0x53, 0x0a, 0x30 }, new byte[] { 0x53, 0x0b, 0x04 }, new byte[] { 0x53, 0x0c, 0x06 }, - new byte[] { 0x50, 0x25, 0x00 }, - // 系统时钟分频 - new byte[] { 0x30, 0x35, 0x11 }, // 30fps - new byte[] { 0x30, 0x36, PLL_MUX }, // PLL倍频 - new byte[] { 0x3c, 0x07, 0x08 }, - // 时序控制 - new byte[] { 0x38, 0x20, 0x46 }, // vflip - new byte[] { 0x38, 0x21, 0x01 }, // mirror - new byte[] { 0x38, 0x14, 0x31 }, // timing X inc - new byte[] { 0x38, 0x15, 0x31 }, // timing Y inc - // H_START/Y - new byte[] { 0x38, 0x00, unchecked((byte)((H_START >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x01, unchecked((byte)(H_START & 0xFF)) }, - new byte[] { 0x38, 0x02, unchecked((byte)((V_START >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x03, unchecked((byte)(V_START & 0xFF)) }, - // H_END/Y - new byte[] { 0x38, 0x04, unchecked((byte)((H_END >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x05, unchecked((byte)(H_END & 0xFF)) }, - new byte[] { 0x38, 0x06, unchecked((byte)((V_END >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x07, unchecked((byte)(V_END & 0xFF)) }, - // 输出像素个数 - new byte[] { 0x38, 0x08, unchecked((byte)((DVPHO >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x09, unchecked((byte)(DVPHO & 0xFF)) }, - new byte[] { 0x38, 0x0A, unchecked((byte)((DVPVO >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x0B, unchecked((byte)(DVPVO & 0xFF)) }, - // 总像素 - new byte[] { 0x38, 0x0C, unchecked((byte)((HTS >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x0D, unchecked((byte)(HTS & 0xFF)) }, - new byte[] { 0x38, 0x0E, unchecked((byte)((VTS >> 8) & 0xFF)) }, - new byte[] { 0x38, 0x0F, unchecked((byte)(VTS & 0xFF)) }, - new byte[] { 0x38, 0x13, unchecked((byte)(V_OFFSET & 0xFF)) }, // Timing Voffset - new byte[] { 0x36, 0x18, 0x00 }, - new byte[] { 0x36, 0x12, 0x29 }, - new byte[] { 0x37, 0x09, 0x52 }, - new byte[] { 0x37, 0x0c, 0x03 }, - new byte[] { 0x3a, 0x02, 0x17 }, //60Hz max exposure - new byte[] { 0x3a, 0x03, 0x10 }, //60Hz max exposure - new byte[] { 0x3a, 0x14, 0x17 }, //50Hz max exposure - new byte[] { 0x3a, 0x15, 0x10 }, //50Hz max exposure - new byte[] { 0x40, 0x04, 0x02 }, //BLC(背光) 2 lines - new byte[] { 0x47, 0x13, 0x03 }, //JPEG mode 3 - new byte[] { 0x44, 0x07, 0x04 }, //量化标度 - new byte[] { 0x46, 0x0c, 0x20 }, - new byte[] { 0x48, 0x37, 0x22 }, //DVP CLK divider - new byte[] { 0x38, 0x24, 0x02 }, //DVP CLK divider - new byte[] { 0x50, 0x01, 0xA3 }, //ISP 控制 - new byte[] { 0x3b, 0x07, 0x0a }, //帧曝光模式 - // 彩条测试使能 - new byte[] { 0x50, 0x3d, 0x00 }, - new byte[] { 0x47, 0x41, 0x00 }, - // 闪光灯禁用 - new byte[] { 0x30, 0x16, 0x02 }, - new byte[] { 0x30, 0x1c, 0x02 }, - new byte[] { 0x30, 0x19, 0x02 }, //开启闪光灯 - new byte[] { 0x30, 0x19, 0x00 }, //关闭闪光灯 - // new byte[] { 0x30, 0x2c, 0xC2 }, //控制驱动能力的 - // new byte[] { 0x46, 0x00, 0xA0 }, - // for subsample=OFF - // new byte[] { 0x36, 0x18, 0x04 }, - // new byte[] { 0x36, 0x12, 0x2b }, - // new byte[] { 0x37, 0x09, 0x12 }, - // new byte[] { 0x37, 0x0c, 0x00 }, - // Start streaming - new byte[] { 0x30, 0x08, 0x02 } - }; - /// /// 初始化摄像头客户端 @@ -246,72 +59,94 @@ class Camera public async ValueTask> Init() { - { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.STORE_ADDR, FrameAddr); - if (!ret.IsSuccessful) - { - logger.Error($"Failed to write STORE_ADDR to camera at {this.address}:{this.port}, error: {ret.Error}"); - return new(ret.Error); - } - if (!ret.Value) - { - logger.Error($"STORE_ADDR write returned false for camera at {this.address}:{this.port}"); - return new(new Exception($"STORE_ADDR write returned false for camera at {this.address}:{this.port}")); - } - } + // 步骤1: 复位 + var resetResult = await Reset(); + if (!resetResult.IsSuccessful) return resetResult; - { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.STORE_NUM, FrameLength); - if (!ret.IsSuccessful) - { - logger.Error($"Failed to write STORE_NUM to camera at {this.address}:{this.port}, error: {ret.Error}"); - return new(ret.Error); - } - if (!ret.Value) - { - logger.Error($"STORE_NUM write returned false for camera at {this.address}:{this.port}"); - return new(new Exception($"STORE_NUM write returned false for camera at {this.address}:{this.port}")); - } - } + // 步骤2: 休眠 + var sleepResult = await Sleep(); + if (!sleepResult.IsSuccessful) return sleepResult; - { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.EXPECTED_VH, (DVPVO)<<16 | (DVPHO*2)); - if (!ret.IsSuccessful) - { - logger.Error($"Failed to write EXPECTED_VH to camera at {this.address}:{this.port}, error: {ret.Error}"); - return new(ret.Error); - } - if (!ret.Value) - { - logger.Error($"EXPECTED_VH write returned false for camera at {this.address}:{this.port}"); - return new(new Exception($"EXPECTED_VH write returned false for camera at {this.address}:{this.port}")); - } - } + // 步骤3: 配置基础寄存器 + var basicResult = await ConfigureBasicRegisters(); + if (!basicResult.IsSuccessful) return basicResult; - var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout); + // 步骤4: 配置传感器控制 + var sensorResult = await ConfigureSensorControl(); + if (!sensorResult.IsSuccessful) return sensorResult; - foreach (var cmd in InitCmdData) - { - var ret = await i2c.WriteData(CAM_I2C_ADDR, cmd, CAM_PROTO); + // 步骤5: 配置模拟控制 + var analogResult = await ConfigureAnalogControl(); + if (!analogResult.IsSuccessful) return analogResult; - if (!ret.IsSuccessful) - { - logger.Error($"I2C write 0x{CAM_I2C_ADDR.ToString("X")} failed: {BitConverter.ToString(cmd)} error: {ret.Error}"); - return new(ret.Error); - } + // 步骤6: 配置时钟控制 + var clockResult = await ConfigureClockControl(); + if (!clockResult.IsSuccessful) return clockResult; - if (!ret.Value) - { - logger.Error($"I2C write 0x{CAM_I2C_ADDR.ToString("X")} returned false: {BitConverter.ToString(cmd)}"); - return false; - } - if (cmd[0] == 0x30 && cmd[1] == 0x08 && cmd[2] == 0x82) - { - // 复位命令,等待5MS - await Task.Delay(5); - } - else await Task.Delay(5); // 其他命令延时1ms - } + // 步骤7: 配置PSRAM控制 + var psramResult = await ConfigurePSRAMControl(); + if (!psramResult.IsSuccessful) return psramResult; + + // 步骤8: 配置DVP时序 + var dvpResult = await ConfigureDVPTiming(); + if (!dvpResult.IsSuccessful) return dvpResult; + + // 步骤9: 配置基础控制 + var baseResult = await ConfigureBaseControl(); + if (!baseResult.IsSuccessful) return baseResult; + + // 步骤10: 配置图像格式 + var formatResult = await ConfigureImageFormat(); + if (!formatResult.IsSuccessful) return formatResult; + + // 步骤11: 配置ISP控制 + var ispResult = await ConfigureISPControl(); + if (!ispResult.IsSuccessful) return ispResult; + + // 步骤12: 配置AEC + var aecResult = await ConfigureAEC(); + if (!aecResult.IsSuccessful) return aecResult; + + // 步骤13: 配置LENC + var lencResult = await ConfigureLENC(); + if (!lencResult.IsSuccessful) return lencResult; + + // 步骤14: 配置AWB + var awbResult = await ConfigureAWB(); + if (!awbResult.IsSuccessful) return awbResult; + + // 步骤15: 配置Gamma + var gammaResult = await ConfigureGamma(); + if (!gammaResult.IsSuccessful) return gammaResult; + + // 步骤16: 配置CMX + var cmxResult = await ConfigureCMX(); + if (!cmxResult.IsSuccessful) return cmxResult; + + // 步骤17: 配置SDE + var sdeResult = await ConfigureSDE(); + if (!sdeResult.IsSuccessful) return sdeResult; + + // 步骤18: 配置CIP + var cipResult = await ConfigureCIP(); + if (!cipResult.IsSuccessful) return cipResult; + + // 步骤19: 配置时序控制 + var timingResult = await ConfigureTimingControl(); + if (!timingResult.IsSuccessful) return timingResult; + + // 步骤20: 配置测试和闪光灯 + var testResult = await ConfigureTestAndFlash(); + if (!testResult.IsSuccessful) return testResult; + + // 步骤21: 配置分辨率(默认640x480) + // var resolutionResult = await ConfigureResolution640x480(); + var resolutionResult = await ConfigureResolution1280x720(); + if (!resolutionResult.IsSuccessful) return resolutionResult; + + // 步骤22: 开始流 + var startResult = await StartStreaming(); + if (!startResult.IsSuccessful) return startResult; return true; } @@ -338,26 +173,667 @@ class Camera /// 包含图像数据的字节数组 public async ValueTask> ReadFrame() { - // 清除UDP服务器接收缓冲区 - await MsgBus.UDPServer.ClearUDPData(this.address, this.taskID); + // 只在第一次或出错时清除UDP缓冲区,避免每帧都清除造成延迟 + // await MsgBus.UDPServer.ClearUDPData(this.address, this.taskID); - logger.Trace($"Clear up udp server {this.address} receive data"); + logger.Trace($"Reading frame from camera {this.address}"); // 使用UDPClientPool读取图像帧数据 var result = await UDPClientPool.ReadAddr4Bytes( this.ep, this.taskID, // taskID FrameAddr, - ((int)FrameLength), + // ((int)FrameLength), + 1280*720/2, this.timeout); if (!result.IsSuccessful) { logger.Error($"Failed to read frame from camera {this.address}:{this.port}, error: {result.Error}"); + // 读取失败时清除缓冲区,为下次读取做准备 + try + { + await MsgBus.UDPServer.ClearUDPData(this.address, this.taskID); + } + catch (Exception ex) + { + logger.Warn($"Failed to clear UDP data after read error: {ex.Message}"); + } return new(result.Error); } - logger.Debug($"Successfully read frame from camera {this.address}:{this.port}, data length: {result.Value.Length} bytes"); + logger.Trace($"Successfully read frame from camera {this.address}:{this.port}, data length: {result.Value.Length} bytes"); return result.Value; } + + /// + /// 批量配置I2C寄存器 + /// + /// 寄存器配置表,每个元素包含3个字节:[地址高位, 地址低位, 数据] + /// 自定义延时时间(毫秒),如果为null则使用默认延时逻辑 + /// 配置结果 + public async ValueTask> ConfigureRegisters(byte[][] registerTable, int? customDelayMs = null) + { + var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout); + + foreach (var cmd in registerTable) + { + if (cmd.Length != 3) + { + logger.Error($"Invalid register command length: {cmd.Length}, expected 3 bytes"); + return new(new ArgumentException($"Invalid register command length: {cmd.Length}, expected 3 bytes")); + } + + var ret = await i2c.WriteData(CAM_I2C_ADDR, cmd, CAM_PROTO); + + if (!ret.IsSuccessful) + { + logger.Error($"I2C write 0x{CAM_I2C_ADDR.ToString("X")} failed: {BitConverter.ToString(cmd)} error: {ret.Error}"); + return new(ret.Error); + } + + if (!ret.Value) + { + logger.Error($"I2C write 0x{CAM_I2C_ADDR.ToString("X")} returned false: {BitConverter.ToString(cmd)}"); + return false; + } + + // 处理延时逻辑 + if (customDelayMs.HasValue) + { + await Task.Delay(customDelayMs.Value); + } + else + { + // 使用默认延时逻辑 + if (cmd[0] == 0x30 && cmd[1] == 0x08 && cmd[2] == 0x82) + { + // 复位命令,等待5MS + await Task.Delay(5); + } + else + { + await Task.Delay(3); // 其他命令延时3ms + } + } + } + + return true; + } + + /// + /// 配置摄像头分辨率和相关参数 + /// + /// 水平起始位置 + /// 垂直起始位置 + /// 输出水平像素数 + /// 输出垂直像素数 + /// 水平总像素数 + /// 垂直总像素数 + /// 水平偏移 + /// 垂直偏移 + /// 水平窗口大小(默认1500) + /// 垂直窗口大小(默认1300) + /// 配置结果 + public async ValueTask> ConfigureResolution( + UInt16 hStart, UInt16 vStart, + UInt16 dvpHo, UInt16 dvpVo, + UInt16 hts, UInt16 vts, + UInt16 hOffset, UInt16 vOffset, + UInt16 hWindow = 1500, UInt16 vWindow = 1300) + { + // 计算结束位置 + UInt16 hEnd = (UInt16)(hStart + hWindow - 1); + UInt16 vEnd = (UInt16)(vStart + vWindow - 1); + + // 计算帧长度 + UInt32 frameLength = (UInt32)(dvpHo * dvpVo * 16 / 32); + + // 1. 配置UDP相关寄存器 + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.STORE_ADDR, FrameAddr); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to write STORE_ADDR: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("STORE_ADDR write returned false"); + return new(new Exception("STORE_ADDR write returned false")); + } + } + + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.STORE_NUM, frameLength); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to write STORE_NUM: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("STORE_NUM write returned false"); + return new(new Exception("STORE_NUM write returned false")); + } + } + + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.EXPECTED_VH, ((uint)dvpVo) << 16 | ((uint)dvpHo * 2)); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to write EXPECTED_VH: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("EXPECTED_VH write returned false"); + return new(new Exception("EXPECTED_VH write returned false")); + } + } + + // 2. 配置I2C寄存器 + var resolutionRegisters = new byte[][] + { + // H_OFFSET/V_OFFSET + new byte[] { 0x38, 0x10, unchecked((byte)((hOffset >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x11, unchecked((byte)(hOffset & 0xFF)) }, + new byte[] { 0x38, 0x12, unchecked((byte)((vOffset >> 8) & 0xFF)) }, + + // H_START/V_START + new byte[] { 0x38, 0x00, unchecked((byte)((hStart >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x01, unchecked((byte)(hStart & 0xFF)) }, + new byte[] { 0x38, 0x02, unchecked((byte)((vStart >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x03, unchecked((byte)(vStart & 0xFF)) }, + + // H_END/V_END + new byte[] { 0x38, 0x04, unchecked((byte)((hEnd >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x05, unchecked((byte)(hEnd & 0xFF)) }, + new byte[] { 0x38, 0x06, unchecked((byte)((vEnd >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x07, unchecked((byte)(vEnd & 0xFF)) }, + + // 输出像素个数 + new byte[] { 0x38, 0x08, unchecked((byte)((dvpHo >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x09, unchecked((byte)(dvpHo & 0xFF)) }, + new byte[] { 0x38, 0x0A, unchecked((byte)((dvpVo >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x0B, unchecked((byte)(dvpVo & 0xFF)) }, + + // 总像素 + new byte[] { 0x38, 0x0C, unchecked((byte)((hts >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x0D, unchecked((byte)(hts & 0xFF)) }, + new byte[] { 0x38, 0x0E, unchecked((byte)((vts >> 8) & 0xFF)) }, + new byte[] { 0x38, 0x0F, unchecked((byte)(vts & 0xFF)) }, + + // Timing Voffset + new byte[] { 0x38, 0x13, unchecked((byte)(vOffset & 0xFF)) } + }; + + var configResult = await ConfigureRegisters(resolutionRegisters, customDelayMs: 1); + if (!configResult.IsSuccessful) + { + logger.Error($"Failed to configure resolution registers: {configResult.Error}"); + return configResult; + } + + logger.Info($"Successfully configured resolution: {dvpHo}x{dvpVo}, HTS={hts}, VTS={vts}"); + return true; + } + + /// + /// 配置为640x480分辨率(默认配置) + /// + /// 配置结果 + public async ValueTask> ConfigureResolution640x480() + { + return await ConfigureResolution( + hStart: 0, vStart: 0, + dvpHo: 640, dvpVo: 480, + hts: 1700, vts: 1500, + hOffset: 16, vOffset: 4 + ); + } + + /// + /// 配置为320x240分辨率 + /// + /// 配置结果 + public async ValueTask> ConfigureResolution320x240() + { + return await ConfigureResolution( + hStart: 0, vStart: 0, + dvpHo: 320, dvpVo: 240, + hts: 850, vts: 750, + hOffset: 16, vOffset: 4, + hWindow: 750, vWindow: 650 + ); + } + + /// + /// 配置为1280x720分辨率 + /// + /// 配置结果 + public async ValueTask> ConfigureResolution1280x720() + { + return await ConfigureResolution( + hStart: 0, vStart: 0, + dvpHo: 1280, dvpVo: 720, + hts: 2844, vts: 1968, + hOffset: 16, vOffset: 4, + hWindow: 2592, vWindow: 1944 + ); + } + + /// + /// 复位摄像头 + /// + /// 配置结果 + public async ValueTask> Reset() + { + var resetRegisters = new byte[][] + { + new byte[] { 0x30, 0x08, 0x82 } // 复位命令 + }; + + return await ConfigureRegisters(resetRegisters, customDelayMs: 5); // 复位后等待5ms + } + + /// + /// 设置摄像头为休眠模式 + /// + /// 配置结果 + public async ValueTask> Sleep() + { + var sleepRegisters = new byte[][] + { + new byte[] { 0x30, 0x08, 0x42 } // 休眠命令 + }; + + return await ConfigureRegisters(sleepRegisters); + } + + /// + /// 配置基础寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureBasicRegisters() + { + var basicRegisters = new byte[][] + { + new byte[] { 0x31, 0x03, 0x02 }, + new byte[] { 0x30, 0x17, 0xff }, + new byte[] { 0x30, 0x18, 0xff }, + new byte[] { 0x30, 0x37, 0x13 }, + new byte[] { 0x31, 0x08, 0x01 } + }; + + return await ConfigureRegisters(basicRegisters); + } + + /// + /// 配置传感器控制寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureSensorControl() + { + var sensorRegisters = new byte[][] + { + new byte[] { 0x36, 0x30, 0x36 }, + new byte[] { 0x36, 0x31, 0x0e }, + new byte[] { 0x36, 0x32, 0xe2 }, + new byte[] { 0x36, 0x33, 0x12 }, + new byte[] { 0x36, 0x21, 0xe0 } + }; + + return await ConfigureRegisters(sensorRegisters); + } + + /// + /// 配置模拟控制寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureAnalogControl() + { + var analogRegisters = new byte[][] + { + new byte[] { 0x37, 0x04, 0xa0 }, + new byte[] { 0x37, 0x03, 0x5a }, + new byte[] { 0x37, 0x15, 0x78 }, + new byte[] { 0x37, 0x17, 0x01 }, + new byte[] { 0x37, 0x0b, 0x60 }, + new byte[] { 0x37, 0x05, 0x1a } + }; + + return await ConfigureRegisters(analogRegisters); + } + + /// + /// 配置时钟控制寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureClockControl() + { + var clockRegisters = new byte[][] + { + new byte[] { 0x39, 0x05, 0x02 }, + new byte[] { 0x39, 0x06, 0x10 }, + new byte[] { 0x39, 0x01, 0x0a }, + new byte[] { 0x30, 0x35, 0x11 }, // 30fps + new byte[] { 0x30, 0x36, PLL_MUX } // PLL倍频 + }; + + return await ConfigureRegisters(clockRegisters); + } + + /// + /// 配置PSRAM控制寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigurePSRAMControl() + { + var psramRegisters = new byte[][] + { + new byte[] { 0x37, 0x31, 0x12 }, + new byte[] { 0x36, 0x00, 0x08 }, + new byte[] { 0x36, 0x01, 0x33 }, + new byte[] { 0x30, 0x2d, 0x60 }, + new byte[] { 0x36, 0x20, 0x52 }, + new byte[] { 0x37, 0x1b, 0x20 }, + new byte[] { 0x47, 0x1c, 0x50 } + }; + + return await ConfigureRegisters(psramRegisters); + } + + /// + /// 配置DVP时序寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureDVPTiming() + { + var dvpRegisters = new byte[][] + { + new byte[] { 0x3a, 0x13, 0x43 }, + new byte[] { 0x3a, 0x18, 0x00 }, + new byte[] { 0x3a, 0x19, 0xf8 }, + new byte[] { 0x36, 0x35, 0x13 }, + new byte[] { 0x36, 0x36, 0x03 }, + new byte[] { 0x36, 0x34, 0x40 }, + new byte[] { 0x36, 0x22, 0x01 } + }; + + return await ConfigureRegisters(dvpRegisters); + } + + /// + /// 配置基础控制寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureBaseControl() + { + var baseRegisters = new byte[][] + { + new byte[] { 0x3c, 0x01, 0x34 }, + new byte[] { 0x3c, 0x04, 0x28 }, + new byte[] { 0x3c, 0x05, 0x98 }, + new byte[] { 0x3c, 0x06, 0x00 }, + new byte[] { 0x3c, 0x07, 0x08 }, + new byte[] { 0x3c, 0x08, 0x00 }, + new byte[] { 0x3c, 0x09, 0x1c }, + new byte[] { 0x3c, 0x0a, 0x9c }, + new byte[] { 0x3c, 0x0b, 0x40 }, + new byte[] { 0x37, 0x08, 0x64 } + }; + + return await ConfigureRegisters(baseRegisters); + } + + /// + /// 配置图像格式寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureImageFormat() + { + var formatRegisters = new byte[][] + { + new byte[] { 0x40, 0x01, 0x02 }, + new byte[] { 0x40, 0x05, 0x1a }, + new byte[] { 0x30, 0x00, 0x00 }, + new byte[] { 0x30, 0x04, 0xff }, + new byte[] { 0x43, 0x00, 0x6F }, // RGB565:first byte:{g[2:0],b[4:0]],second byte:{r[4:0],g[5:3]} + new byte[] { 0x50, 0x1f, 0x01 }, // Format: ISP RGB + new byte[] { 0x44, 0x0e, 0x00 } + }; + + return await ConfigureRegisters(formatRegisters); + } + + /// + /// 配置ISP控制寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureISPControl() + { + var ispRegisters = new byte[][] + { + new byte[] { 0x50, 0x00, 0xA7 }, // ISP控制 + new byte[] { 0x50, 0x01, 0xA3 } // ISP控制 + }; + + return await ConfigureRegisters(ispRegisters); + } + + /// + /// 配置AEC(自动曝光控制)寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureAEC() + { + var aecRegisters = new byte[][] + { + new byte[] { 0x3a, 0x0f, 0x30 }, // AEC控制;stable range in high + new byte[] { 0x3a, 0x10, 0x28 }, // AEC控制;stable range in low + new byte[] { 0x3a, 0x1b, 0x30 }, // AEC控制;stable range out high + new byte[] { 0x3a, 0x1e, 0x26 }, // AEC控制;stable range out low + new byte[] { 0x3a, 0x11, 0x60 }, // AEC控制; fast zone high + new byte[] { 0x3a, 0x1f, 0x14 }, // AEC控制; fast zone low + new byte[] { 0x3a, 0x02, 0x17 }, // 60Hz max exposure + new byte[] { 0x3a, 0x03, 0x10 }, // 60Hz max exposure + new byte[] { 0x3a, 0x14, 0x17 }, // 50Hz max exposure + new byte[] { 0x3a, 0x15, 0x10 }, // 50Hz max exposure + new byte[] { 0x3b, 0x07, 0x0a } // 帧曝光模式 + }; + + return await ConfigureRegisters(aecRegisters); + } + + /// + /// 配置LENC(镜头校正)寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureLENC() + { + var lencRegisters = new byte[][] + { + new byte[] { 0x58, 0x00, 0x23 }, new byte[] { 0x58, 0x01, 0x14 }, new byte[] { 0x58, 0x02, 0x0f }, + new byte[] { 0x58, 0x03, 0x0f }, new byte[] { 0x58, 0x04, 0x12 }, new byte[] { 0x58, 0x05, 0x26 }, + new byte[] { 0x58, 0x06, 0x0c }, new byte[] { 0x58, 0x07, 0x08 }, new byte[] { 0x58, 0x08, 0x05 }, + new byte[] { 0x58, 0x09, 0x05 }, new byte[] { 0x58, 0x0a, 0x08 }, new byte[] { 0x58, 0x0b, 0x0d }, + new byte[] { 0x58, 0x0c, 0x08 }, new byte[] { 0x58, 0x0d, 0x03 }, new byte[] { 0x58, 0x0e, 0x00 }, + new byte[] { 0x58, 0x0f, 0x00 }, new byte[] { 0x58, 0x10, 0x03 }, new byte[] { 0x58, 0x11, 0x09 }, + new byte[] { 0x58, 0x12, 0x07 }, new byte[] { 0x58, 0x13, 0x03 }, new byte[] { 0x58, 0x14, 0x00 }, + new byte[] { 0x58, 0x15, 0x01 }, new byte[] { 0x58, 0x16, 0x03 }, new byte[] { 0x58, 0x17, 0x08 }, + new byte[] { 0x58, 0x18, 0x0d }, new byte[] { 0x58, 0x19, 0x08 }, new byte[] { 0x58, 0x1a, 0x05 }, + new byte[] { 0x58, 0x1b, 0x06 }, new byte[] { 0x58, 0x1c, 0x08 }, new byte[] { 0x58, 0x1d, 0x0e }, + new byte[] { 0x58, 0x1e, 0x29 }, new byte[] { 0x58, 0x1f, 0x17 }, new byte[] { 0x58, 0x20, 0x11 }, + new byte[] { 0x58, 0x21, 0x11 }, new byte[] { 0x58, 0x22, 0x15 }, new byte[] { 0x58, 0x23, 0x28 }, + new byte[] { 0x58, 0x24, 0x46 }, new byte[] { 0x58, 0x25, 0x26 }, new byte[] { 0x58, 0x26, 0x08 }, + new byte[] { 0x58, 0x27, 0x26 }, new byte[] { 0x58, 0x28, 0x64 }, new byte[] { 0x58, 0x29, 0x26 }, + new byte[] { 0x58, 0x2a, 0x24 }, new byte[] { 0x58, 0x2b, 0x22 }, new byte[] { 0x58, 0x2c, 0x24 }, + new byte[] { 0x58, 0x2d, 0x24 }, new byte[] { 0x58, 0x2e, 0x06 }, new byte[] { 0x58, 0x2f, 0x22 }, + new byte[] { 0x58, 0x30, 0x40 }, new byte[] { 0x58, 0x31, 0x42 }, new byte[] { 0x58, 0x32, 0x24 }, + new byte[] { 0x58, 0x33, 0x26 }, new byte[] { 0x58, 0x34, 0x24 }, new byte[] { 0x58, 0x35, 0x22 }, + new byte[] { 0x58, 0x36, 0x22 }, new byte[] { 0x58, 0x37, 0x26 }, new byte[] { 0x58, 0x38, 0x44 }, + new byte[] { 0x58, 0x39, 0x24 }, new byte[] { 0x58, 0x3a, 0x26 }, new byte[] { 0x58, 0x3b, 0x28 }, + new byte[] { 0x58, 0x3c, 0x42 }, new byte[] { 0x58, 0x3d, 0xce } + }; + + return await ConfigureRegisters(lencRegisters); + } + + /// + /// 配置摄像头AWB(自动白平衡)寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureAWB() + { + var awbRegisters = new byte[][] + { + new byte[] { 0x51, 0x80, 0xff }, new byte[] { 0x51, 0x81, 0xf2 }, new byte[] { 0x51, 0x82, 0x00 }, + new byte[] { 0x51, 0x83, 0x14 }, new byte[] { 0x51, 0x84, 0x25 }, new byte[] { 0x51, 0x85, 0x24 }, + new byte[] { 0x51, 0x86, 0x09 }, new byte[] { 0x51, 0x87, 0x09 }, new byte[] { 0x51, 0x88, 0x09 }, + new byte[] { 0x51, 0x89, 0x75 }, new byte[] { 0x51, 0x8a, 0x54 }, new byte[] { 0x51, 0x8b, 0xe0 }, + new byte[] { 0x51, 0x8c, 0xb2 }, new byte[] { 0x51, 0x8d, 0x42 }, new byte[] { 0x51, 0x8e, 0x3d }, + new byte[] { 0x51, 0x8f, 0x56 }, new byte[] { 0x51, 0x90, 0x46 }, new byte[] { 0x51, 0x91, 0xf8 }, + new byte[] { 0x51, 0x92, 0x04 }, new byte[] { 0x51, 0x93, 0x70 }, new byte[] { 0x51, 0x94, 0xf0 }, + new byte[] { 0x51, 0x95, 0xf0 }, new byte[] { 0x51, 0x96, 0x03 }, new byte[] { 0x51, 0x97, 0x01 }, + new byte[] { 0x51, 0x98, 0x04 }, new byte[] { 0x51, 0x99, 0x12 }, new byte[] { 0x51, 0x9a, 0x04 }, + new byte[] { 0x51, 0x9b, 0x00 }, new byte[] { 0x51, 0x9c, 0x06 }, new byte[] { 0x51, 0x9d, 0x82 }, + new byte[] { 0x51, 0x9e, 0x38 } + }; + + return await ConfigureRegisters(awbRegisters, customDelayMs: 2); + } + + /// + /// 配置摄像头Gamma校正寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureGamma() + { + var gammaRegisters = new byte[][] + { + new byte[] { 0x54, 0x80, 0x01 }, new byte[] { 0x54, 0x81, 0x08 }, new byte[] { 0x54, 0x82, 0x14 }, + new byte[] { 0x54, 0x83, 0x28 }, new byte[] { 0x54, 0x84, 0x51 }, new byte[] { 0x54, 0x85, 0x65 }, + new byte[] { 0x54, 0x86, 0x71 }, new byte[] { 0x54, 0x87, 0x7d }, new byte[] { 0x54, 0x88, 0x87 }, + new byte[] { 0x54, 0x89, 0x91 }, new byte[] { 0x54, 0x8a, 0x9a }, new byte[] { 0x54, 0x8b, 0xaa }, + new byte[] { 0x54, 0x8c, 0xb8 }, new byte[] { 0x54, 0x8d, 0xcd }, new byte[] { 0x54, 0x8e, 0xdd }, + new byte[] { 0x54, 0x8f, 0xea }, new byte[] { 0x54, 0x90, 0x1d } + }; + + return await ConfigureRegisters(gammaRegisters); + } + + + /// + /// 配置CMX(色彩矩阵)寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureCMX() + { + var cmxRegisters = new byte[][] + { + new byte[] { 0x53, 0x81, 0x1e }, new byte[] { 0x53, 0x82, 0x5b }, new byte[] { 0x53, 0x83, 0x08 }, + new byte[] { 0x53, 0x84, 0x0a }, new byte[] { 0x53, 0x85, 0x7e }, new byte[] { 0x53, 0x86, 0x88 }, + new byte[] { 0x53, 0x87, 0x7c }, new byte[] { 0x53, 0x88, 0x6c }, new byte[] { 0x53, 0x89, 0x10 }, + new byte[] { 0x53, 0x8a, 0x01 }, new byte[] { 0x53, 0x8b, 0x98 } + }; + + return await ConfigureRegisters(cmxRegisters); + } + + /// + /// 配置SDE(特殊数字效果)寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureSDE() + { + var sdeRegisters = new byte[][] + { + new byte[] { 0x55, 0x80, 0x06 }, new byte[] { 0x55, 0x83, 0x40 }, new byte[] { 0x55, 0x84, 0x10 }, + new byte[] { 0x55, 0x89, 0x10 }, new byte[] { 0x55, 0x8a, 0x00 }, new byte[] { 0x55, 0x8b, 0xf8 }, + new byte[] { 0x50, 0x1d, 0x40 } + }; + + return await ConfigureRegisters(sdeRegisters); + } + + /// + /// 配置CIP(颜色插值处理)寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureCIP() + { + var cipRegisters = new byte[][] + { + new byte[] { 0x53, 0x00, 0x08 }, new byte[] { 0x53, 0x01, 0x30 }, new byte[] { 0x53, 0x02, 0x10 }, + new byte[] { 0x53, 0x03, 0x00 }, new byte[] { 0x53, 0x04, 0x08 }, new byte[] { 0x53, 0x05, 0x30 }, + new byte[] { 0x53, 0x06, 0x08 }, new byte[] { 0x53, 0x07, 0x16 }, new byte[] { 0x53, 0x09, 0x08 }, + new byte[] { 0x53, 0x0a, 0x30 }, new byte[] { 0x53, 0x0b, 0x04 }, new byte[] { 0x53, 0x0c, 0x06 }, + new byte[] { 0x50, 0x25, 0x00 } + }; + + return await ConfigureRegisters(cipRegisters); + } + + /// + /// 配置时序控制寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureTimingControl() + { + var timingRegisters = new byte[][] + { + new byte[] { 0x38, 0x20, 0x46 }, // vflip + new byte[] { 0x38, 0x21, 0x01 }, // mirror + new byte[] { 0x38, 0x14, 0x31 }, // timing X inc + new byte[] { 0x38, 0x15, 0x31 }, // timing Y inc + new byte[] { 0x36, 0x18, 0x00 }, + new byte[] { 0x36, 0x12, 0x29 }, + new byte[] { 0x37, 0x09, 0x52 }, + new byte[] { 0x37, 0x0c, 0x03 } + }; + + return await ConfigureRegisters(timingRegisters); + } + + /// + /// 配置测试模式和闪光灯寄存器 + /// + /// 配置结果 + public async ValueTask> ConfigureTestAndFlash() + { + var testRegisters = new byte[][] + { + new byte[] { 0x40, 0x04, 0x02 }, // BLC(背光) 2 lines + new byte[] { 0x47, 0x13, 0x03 }, // JPEG mode 3 + new byte[] { 0x44, 0x07, 0x04 }, // 量化标度 + new byte[] { 0x46, 0x0c, 0x20 }, + new byte[] { 0x48, 0x37, 0x22 }, // DVP CLK divider + new byte[] { 0x38, 0x24, 0x02 }, // DVP CLK divider + // 彩条测试禁用 + new byte[] { 0x50, 0x3d, 0x00 }, + new byte[] { 0x47, 0x41, 0x00 }, + // 闪光灯配置 + new byte[] { 0x30, 0x16, 0x02 }, + new byte[] { 0x30, 0x1c, 0x02 }, + new byte[] { 0x30, 0x19, 0x02 }, // 开启闪光灯 + new byte[] { 0x30, 0x19, 0x00 } // 关闭闪光灯 + }; + + return await ConfigureRegisters(testRegisters); + } + + /// + /// 开始流媒体传输 + /// + /// 配置结果 + public async ValueTask> StartStreaming() + { + var startRegisters = new byte[][] + { + new byte[] { 0x30, 0x08, 0x02 } // 开始流 + }; + + return await ConfigureRegisters(startRegisters); + } } diff --git a/server/src/Services/HttpVideoStreamService.cs b/server/src/Services/HttpVideoStreamService.cs index 4522a1c..63187bc 100644 --- a/server/src/Services/HttpVideoStreamService.cs +++ b/server/src/Services/HttpVideoStreamService.cs @@ -82,8 +82,8 @@ public class HttpVideoStreamService : BackgroundService private HttpListener? _httpListener; private readonly int _serverPort = 8080; private readonly int _frameRate = 30; // 30 FPS - private readonly int _frameWidth = 640; - private readonly int _frameHeight = 480; + private readonly int _frameWidth = 1280; + private readonly int _frameHeight = 720; // 摄像头客户端 private Camera? _camera; @@ -559,21 +559,59 @@ public class HttpVideoStreamService : BackgroundService private async Task GenerateVideoFrames(CancellationToken cancellationToken) { var frameInterval = TimeSpan.FromMilliseconds(1000.0 / _frameRate); + var lastFrameTime = DateTime.UtcNow; while (!cancellationToken.IsCancellationRequested && _cameraEnable) { try { - // 从 FPGA 获取图像数据(模拟) + var frameStartTime = DateTime.UtcNow; + + // 从 FPGA 获取图像数据 var imageData = await GetFPGAImageData(); - // 向所有连接的客户端发送帧 - await BroadcastFrameAsync(imageData, cancellationToken); + var imageAcquireTime = DateTime.UtcNow; - _frameCounter++; + // 如果有图像数据,立即开始广播(不等待) + if (imageData != null && imageData.Length > 0) + { + // 异步广播帧,不阻塞下一帧的获取 + _ = Task.Run(async () => + { + try + { + await BroadcastFrameAsync(imageData, cancellationToken); + } + catch (Exception ex) + { + logger.Error(ex, "异步广播帧时发生错误"); + } + }, cancellationToken); - // 等待下一帧 - await Task.Delay(frameInterval, cancellationToken); + _frameCounter++; + + var frameEndTime = DateTime.UtcNow; + var frameProcessingTime = (frameEndTime - frameStartTime).TotalMilliseconds; + var imageAcquireElapsed = (imageAcquireTime - frameStartTime).TotalMilliseconds; + + if (_frameCounter % 30 == 0) // 每秒记录一次性能信息 + { + logger.Debug("帧 {FrameNumber} 性能统计 - 图像获取: {AcquireTime:F1}ms, 总处理: {ProcessTime:F1}ms", + _frameCounter, imageAcquireElapsed, frameProcessingTime); + } + } + + // 动态调整延迟 - 基于实际处理时间 + var elapsed = (DateTime.UtcNow - lastFrameTime).TotalMilliseconds; + var targetInterval = frameInterval.TotalMilliseconds; + var remainingDelay = Math.Max(0, targetInterval - elapsed); + + if (remainingDelay > 0) + { + await Task.Delay(TimeSpan.FromMilliseconds(remainingDelay), cancellationToken); + } + + lastFrameTime = DateTime.UtcNow; } catch (OperationCanceledException) { @@ -582,7 +620,7 @@ public class HttpVideoStreamService : BackgroundService catch (Exception ex) { logger.Error(ex, "生成视频帧时发生错误"); - await Task.Delay(1000, cancellationToken); // 错误恢复延迟 + await Task.Delay(100, cancellationToken); // 减少错误恢复延迟 } } } @@ -593,6 +631,7 @@ public class HttpVideoStreamService : BackgroundService /// private async Task GetFPGAImageData() { + var startTime = DateTime.UtcNow; Camera? currentCamera = null; lock (_cameraLock) @@ -609,7 +648,10 @@ public class HttpVideoStreamService : BackgroundService try { // 从摄像头读取帧数据 + var readStartTime = DateTime.UtcNow; var result = await currentCamera.ReadFrame(); + var readEndTime = DateTime.UtcNow; + var readTime = (readEndTime - readStartTime).TotalMilliseconds; if (!result.IsSuccessful) { @@ -627,17 +669,23 @@ public class HttpVideoStreamService : BackgroundService } // 将 RGB565 转换为 RGB24 + var convertStartTime = DateTime.UtcNow; var rgb24Result = Common.Image.ConvertRGB565ToRGB24(rgb565Data, _frameWidth, _frameHeight, isLittleEndian: false); + var convertEndTime = DateTime.UtcNow; + var convertTime = (convertEndTime - convertStartTime).TotalMilliseconds; + if (!rgb24Result.IsSuccessful) { logger.Error("RGB565转RGB24失败: {Error}", rgb24Result.Error); return new byte[0]; } + var totalTime = (DateTime.UtcNow - startTime).TotalMilliseconds; + if (_frameCounter % 30 == 0) // 每秒更新一次日志 { - logger.Debug("成功获取第 {FrameNumber} 帧,RGB565大小: {RGB565Size} 字节, RGB24大小: {RGB24Size} 字节", - _frameCounter, rgb565Data.Length, rgb24Result.Value.Length); + logger.Debug("帧 {FrameNumber} 数据获取性能 - 读取: {ReadTime:F1}ms, 转换: {ConvertTime:F1}ms, 总计: {TotalTime:F1}ms, RGB565: {RGB565Size} 字节, RGB24: {RGB24Size} 字节", + _frameCounter, readTime, convertTime, totalTime, rgb565Data.Length, rgb24Result.Value.Length); } return rgb24Result.Value; @@ -688,8 +736,8 @@ public class HttpVideoStreamService : BackgroundService return; // 没有活跃客户端 } - // 向每个活跃的客户端发送帧 - foreach (var client in clientsToProcess) + // 向每个活跃的客户端并行发送帧 + var sendTasks = clientsToProcess.Select(async client => { try { @@ -705,19 +753,33 @@ public class HttpVideoStreamService : BackgroundService // 确保数据立即发送 await client.OutputStream.FlushAsync(cancellationToken); - if (_frameCounter % 30 == 0) // 每秒记录一次日志 - { - logger.Debug("已向客户端 {ClientId} 发送第 {FrameNumber} 帧,大小:{Size} 字节", - client.OutputStream.GetHashCode(), _frameCounter, jpegData.Length); - } + return (client, success: true, error: (Exception?)null); } catch (Exception ex) { - logger.Debug("发送帧数据时出错: {Error}", ex.Message); + return (client, success: false, error: ex); + } + }); + + // 等待所有发送任务完成 + var results = await Task.WhenAll(sendTasks); + + // 处理发送结果 + foreach (var (client, success, error) in results) + { + if (!success) + { + logger.Debug("发送帧数据时出错: {Error}", error?.Message ?? "未知错误"); clientsToRemove.Add(client); } } + if (_frameCounter % 30 == 0 && clientsToProcess.Count > 0) // 每秒记录一次日志 + { + logger.Debug("已向 {ClientCount} 个客户端发送第 {FrameNumber} 帧,大小:{Size} 字节", + clientsToProcess.Count, _frameCounter, jpegData.Length); + } + // 移除断开连接的客户端 if (clientsToRemove.Count > 0) { diff --git a/server/src/UdpClientPool.cs b/server/src/UdpClientPool.cs index 3618134..8c076f8 100644 --- a/server/src/UdpClientPool.cs +++ b/server/src/UdpClientPool.cs @@ -60,8 +60,8 @@ public class UDPClientPool var sendLen = socket.SendTo(buf, endPoint); socket.Close(); - logger.Debug($"UDP socket send bytes to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:"); - logger.Debug($" Original Data: {BitConverter.ToString(buf).Replace("-", " ")}"); + // logger.Debug($"UDP socket send bytes to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:"); + // logger.Debug($" Original Data: {BitConverter.ToString(buf).Replace("-", " ")}"); if (sendLen == buf.Length) { return true; } else { return false; } @@ -91,9 +91,9 @@ public class UDPClientPool var sendLen = socket.SendTo(sendBytes, endPoint); socket.Close(); - logger.Debug($"UDP socket send address package to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:"); - logger.Debug($" Original Data: {BitConverter.ToString(pkg.ToBytes()).Replace("-", " ")}"); - logger.Debug($" Decoded Data: {pkg.ToString()}"); + // logger.Debug($"UDP socket send address package to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:"); + // logger.Debug($" Original Data: {BitConverter.ToString(pkg.ToBytes()).Replace("-", " ")}"); + // logger.Debug($" Decoded Data: {pkg.ToString()}"); if (sendLen == sendBytes.Length) { return true; } else { return false; } @@ -124,8 +124,8 @@ public class UDPClientPool var sendLen = socket.SendTo(sendBytes, endPoint); socket.Close(); - logger.Debug($"UDP socket send data package to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:"); - logger.Debug($" Original Data: {BitConverter.ToString(pkg.ToBytes()).Replace("-", " ")}"); + // logger.Debug($"UDP socket send data package to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:"); + // logger.Debug($" Original Data: {BitConverter.ToString(pkg.ToBytes()).Replace("-", " ")}"); if (sendLen == sendBytes.Length) { return true; } else { return false; } diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs index 8b636ca..b4daab0 100644 --- a/server/src/UdpServer.cs +++ b/server/src/UdpServer.cs @@ -145,7 +145,7 @@ public class UDPServer { UDPData? data = null; - logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr}-{taskID} UDP Data"); + // logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr}-{taskID} UDP Data"); var startTime = DateTime.Now; var isTimeout = false; @@ -164,7 +164,7 @@ public class UDPServer dataQueue.Count > 0) { data = dataQueue.Dequeue(); - logger.Debug($"Find UDP Data: {data.ToString()}"); + // logger.Debug($"Find UDP Data: {data.ToString()}"); break; } } @@ -212,7 +212,7 @@ public class UDPServer dataQueue.Count > 0) { data = dataQueue.ToList(); - logger.Debug($"Find UDP Data Array: {JsonConvert.SerializeObject(data)}"); + // logger.Debug($"Find UDP Data Array: {JsonConvert.SerializeObject(data)}"); break; } } @@ -290,8 +290,8 @@ public class UDPServer // Handle RemoteEP if (remoteEP is null) { - logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:"); - logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}"); + // logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:"); + // logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}"); goto BEGIN_RECEIVE; } @@ -395,9 +395,9 @@ public class UDPServer recvData = Encoding.ASCII.GetString(bytes, 0, bytes.Length); } - logger.Debug($"Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()}:"); - logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}"); - if (recvData.Length != 0) logger.Debug($" Decoded Data : {recvData}"); + // logger.Debug($"Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()}:"); + // logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}"); + // if (recvData.Length != 0) logger.Debug($" Decoded Data : {recvData}"); } /// @@ -408,13 +408,13 @@ public class UDPServer { using (udpData.AcquireReadLock()) { - logger.Debug("Ready Data:"); + // logger.Debug("Ready Data:"); foreach (var ip in udpData) { foreach (var data in ip.Value) { - logger.Debug(data.ToString()); + // logger.Debug(data.ToString()); } } }