MjpegStreamWriter.cs 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. using System.Windows.Media;
  5. using System.Windows.Media.Imaging;
  6. namespace IvfTl.ControlHost.Debug
  7. {
  8. /// <summary>
  9. /// MJPEG 推流的纯逻辑:RGB 像素 → JPEG 字节,JPEG → multipart 帧字节。
  10. /// 无 IO、无相机依赖,可纯单测。真正的抓帧/写流由 ControlHttpServer 推流分支驱动。
  11. /// 相机抓帧返回 24bpp BGR(GrabStable),WPF JpegBitmapEncoder 需 Bgr24 像素格式。
  12. /// </summary>
  13. public static class MjpegStreamWriter
  14. {
  15. public const string Boundary = "frame";
  16. /// <summary>把 24bpp BGR 像素(width*height*3)编码成 JPEG 字节(质量 85)。</summary>
  17. public static byte[] EncodeJpeg(byte[] bgr, int width, int height, int quality = 85)
  18. {
  19. if (bgr == null || bgr.Length < width * height * 3) return null;
  20. int stride = width * 3;
  21. var bmp = BitmapSource.Create(width, height, 96, 96,
  22. PixelFormats.Bgr24, null, bgr, stride);
  23. var encoder = new JpegBitmapEncoder { QualityLevel = quality };
  24. encoder.Frames.Add(BitmapFrame.Create(bmp));
  25. using (var ms = new MemoryStream())
  26. {
  27. encoder.Save(ms);
  28. return ms.ToArray();
  29. }
  30. }
  31. /// <summary>把 JPEG 字节封成一个 multipart/x-mixed-replace 帧(含 boundary 头 + 帧体 + \r\n)。</summary>
  32. public static byte[] FrameBytes(byte[] jpeg)
  33. {
  34. string header = $"--{Boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {jpeg.Length}\r\n\r\n";
  35. byte[] head = Encoding.ASCII.GetBytes(header);
  36. byte[] tail = Encoding.ASCII.GetBytes("\r\n");
  37. var frame = new byte[head.Length + jpeg.Length + tail.Length];
  38. Buffer.BlockCopy(head, 0, frame, 0, head.Length);
  39. Buffer.BlockCopy(jpeg, 0, frame, head.Length, jpeg.Length);
  40. Buffer.BlockCopy(tail, 0, frame, head.Length + jpeg.Length, tail.Length);
  41. return frame;
  42. }
  43. /// <summary>响应头里的 Content-Type 值(供 ControlHttpServer 写头用)。</summary>
  44. public static string ContentType => $"multipart/x-mixed-replace; boundary={Boundary}";
  45. }
  46. }