using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.IO.Compression; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Brush = System.Drawing.Brush; using Color = System.Drawing.Color; using Pen = System.Drawing.Pen; using PixelFormat = System.Drawing.Imaging.PixelFormat; using Rectangle = System.Drawing.Rectangle; namespace Air105Camera { /// /// MainWindow.xaml 的交互逻辑 /// [PropertyChanged.AddINotifyPropertyChangedInterface] public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } //将Bitmap对象转换成bitmapImage对象 private BitmapImage ConvertBitmapToBitmapImage(Bitmap bitmap) { MemoryStream stream = new MemoryStream(); bitmap.Save(stream, ImageFormat.Bmp); BitmapImage image = new BitmapImage(); image.BeginInit(); image.StreamSource = stream; image.EndInit(); return image; } //刷新显示当前图片 private void ShowImage() { this.Dispatcher.Invoke(() => { var temp = ConvertBitmapToBitmapImage(image); CameraImage.Source = temp; }); } //缓存图片数据 Bitmap image = new Bitmap(320, 240, PixelFormat.Format16bppRgb565); Graphics imageGraphic = null; //串口对象 private SerialPort Uart = new SerialPort(); //总共收到多少包 public int totalPack { get; set; } = 0; //总共刷了多少张 public int totalPic { get; set; } = 0; //FPS数 public double fpsNow { get; set; } = 0; public bool connectEnable { get; set; } = true; public bool disconnectEnable { get; set; } = true; private void Window_Loaded(object sender, RoutedEventArgs e) { this.DataContext = this; imageGraphic = Graphics.FromImage(image); //刷白 imageGraphic.FillRectangle(new SolidBrush(Color.White), 0, 0, image.Width, image.Height); //图片显示一下 ShowImage(); // 绑定事件监听,用于监听HID设备插拔 (PresentationSource.FromVisual(this) as HwndSource)?.AddHook(WndProc); RefreshPortList(); //串口数据处理,随便写写 new Thread(Uart2Image).Start(); } private void ConnectButton_Click(object sender, RoutedEventArgs e) { if (Uart.IsOpen) { return; } if (((string)SerialComboBox.SelectedItem).Length > 0) { var portName = (string)SerialComboBox.SelectedItem; Task.Run(() => { try { Uart.PortName = portName; Uart.Open(); } catch (Exception ex) { MessageBox.Show(ex.Message); } }); } } private void DisconnectButton_Click(object sender, RoutedEventArgs e) { if (!Uart.IsOpen) { return; } try { Uart.Close(); } catch(Exception ex) { MessageBox.Show(ex.Message); } } private static int UsbPluginDeley = 0; private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == 0x219)// 监听USB设备插拔消息 { if (UsbPluginDeley == 0) { ++UsbPluginDeley; // Task启动需要准备时间,这里提前对公共变量加一 Task.Run(() => { do Task.Delay(100).Wait(); while (++UsbPluginDeley < 10); UsbPluginDeley = 0; RefreshPortList(); }); } else UsbPluginDeley = 1; handled = true; } return IntPtr.Zero; } /// /// 刷新设备列表,并在断开恢复后重连 /// /// 串口列表是否成功获取 private bool RefreshPortList() { bool result = true; var list = SerialPort.GetPortNames(); Dispatcher.Invoke(delegate { SerialComboBox.ItemsSource = list; if (SerialComboBox.Items.Count == 0) { result = false; } else if (SerialComboBox.Items.Contains(Uart.PortName)) { SerialComboBox.SelectedItem = Uart.PortName; } else SerialComboBox.SelectedIndex = 0; }); return result; } bool closed = false; private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { closed = true; //直接强关软件 Environment.Exit(0); } private void RefreshButton_Click(object sender, RoutedEventArgs e) { RefreshPortList(); } /* 16字节头+N字节数据 4字节字符串 “VCAM” + 2字节图像宽度 + 2字节图像高度 + 4字节本次数据从哪行开始刷 + 2字节未压缩数据长度 + 2字节压缩后图像数据长度 如果压缩前和压缩后数据长度是一致的,那就没有压缩;如果不一致,则后面的N字节要用zlib解压 RGB565大端格式 */ //串口数据处理 private void Uart2Image() { byte[] temp = new byte[10 * 1024 * 1024];//缓冲区 int p = 0;//上次的位置 Stopwatch sw = new Stopwatch(); sw.Start(); bool getNewData = false;//记一下是不是收到新数据了 while (true) { if (closed) return; connectEnable = !Uart.IsOpen; disconnectEnable = Uart.IsOpen; try { if (Uart.IsOpen && Uart.BytesToRead > 0) { //读数据 var toread = Uart.BytesToRead; Uart.Read(temp, p, toread); p += toread; getNewData = true; } else if (!Uart.IsOpen) { fpsNow = 0; p = 0; } if(getNewData) { getNewData = false;//收到新数据再刷 for (int i = 3; i < p; i++) { //匹配VCAM头 if (temp[i - 3] == 'V' && temp[i - 2] == 'C' && temp[i - 1] == 'A' && temp[i] == 'M' && p - i > 16) { var width = BitConverter.ToUInt16(temp, i + 1); var height = BitConverter.ToUInt16(temp, i + 3); var start = BitConverter.ToUInt32(temp, i + 5); var rawLen = BitConverter.ToUInt16(temp, i + 9); var comLen = BitConverter.ToUInt16(temp, i + 11); Debug.WriteLine($"found VCAM: {BitConverter.ToString(temp, i - 3, 16)}\r\n" + $"{width},{height},{start},{rawLen},{comLen},{i},{p}"); //获取实际数据长度 var realLength = comLen < rawLen ? comLen : rawLen; if (realLength <= p - i - 13)//一包数据够了 { totalPack++; Debug.WriteLine($"{realLength} bytes, start unpack"); //存储rgb数据 byte[] data = null; if (comLen < rawLen)//包压缩了 { //Debug.WriteLine(BitConverter.ToString(temp, i + 13, comLen)); using MemoryStream compressed = new MemoryStream(temp, i + 13, comLen); using MemoryStream decompressed = new MemoryStream(); using InflaterInputStream inputStream = new InflaterInputStream(compressed); inputStream.CopyTo(decompressed); data = decompressed.ToArray(); } else//没压缩 { Debug.WriteLine($"rawLen,{rawLen},{i + 13 + rawLen}"); data = new byte[rawLen]; for (int n = i + 13; n < i + 13 + rawLen; n++) data[n - (i + 13)] = temp[n]; } //直接把rgb565数据写入bitmap,这样快 //先把数据改成小端 for (int n = 0; n < data.Length; n += 2) { (data[n], data[n + 1]) = (data[n + 1], data[n]); } var rect = new Rectangle(0, (int)start, width, data.Length / 2 / width); BitmapData bmpData = image.LockBits(rect, ImageLockMode.ReadWrite, image.PixelFormat); IntPtr ptr = bmpData.Scan0;// Get the address of the first line. System.Runtime.InteropServices.Marshal.Copy(data, 0, ptr, data.Length); image.UnlockBits(bmpData); //刷到最后一行时再显示当前图片 if (start >= height - 16) { sw.Stop(); fpsNow = 1000.0 / sw.ElapsedMilliseconds; sw.Restart(); totalPic++; ShowImage(); } //剩下的数据全部左移到开头 for (int n = i + 13 + realLength; n < p; n++) temp[n - (i + 13 + realLength)] = temp[n]; p -= i + 13 + realLength; Debug.WriteLine($"done, p now {p}"); if (p > 3) i = 2; else break; } } } } Thread.Sleep(1); } catch (Exception e) { Debug.WriteLine(e.Message); } } } } }