using System; using System.IO; using System.Text; using System.Windows.Media; using System.Windows.Media.Imaging; namespace IvfTl.ControlHost.Debug { /// /// MJPEG 推流的纯逻辑:RGB 像素 → JPEG 字节,JPEG → multipart 帧字节。 /// 无 IO、无相机依赖,可纯单测。真正的抓帧/写流由 ControlHttpServer 推流分支驱动。 /// 相机抓帧返回 24bpp BGR(GrabStable),WPF JpegBitmapEncoder 需 Bgr24 像素格式。 /// public static class MjpegStreamWriter { public const string Boundary = "frame"; /// 把 24bpp BGR 像素(width*height*3)编码成 JPEG 字节(质量 85)。 public static byte[] EncodeJpeg(byte[] bgr, int width, int height, int quality = 85) { if (bgr == null || bgr.Length < width * height * 3) return null; int stride = width * 3; var bmp = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, bgr, stride); var encoder = new JpegBitmapEncoder { QualityLevel = quality }; encoder.Frames.Add(BitmapFrame.Create(bmp)); using (var ms = new MemoryStream()) { encoder.Save(ms); return ms.ToArray(); } } /// 把 JPEG 字节封成一个 multipart/x-mixed-replace 帧(含 boundary 头 + 帧体 + \r\n)。 public static byte[] FrameBytes(byte[] jpeg) { if (jpeg == null) return null; string header = $"--{Boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {jpeg.Length}\r\n\r\n"; byte[] head = Encoding.ASCII.GetBytes(header); byte[] tail = Encoding.ASCII.GetBytes("\r\n"); var frame = new byte[head.Length + jpeg.Length + tail.Length]; Buffer.BlockCopy(head, 0, frame, 0, head.Length); Buffer.BlockCopy(jpeg, 0, frame, head.Length, jpeg.Length); Buffer.BlockCopy(tail, 0, frame, head.Length + jpeg.Length, tail.Length); return frame; } /// 响应头里的 Content-Type 值(供 ControlHttpServer 写头用)。 public static string ContentType => $"multipart/x-mixed-replace; boundary={Boundary}"; } }