| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 |
- using System;
- using System.IO;
- using System.Text;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- namespace IvfTl.ControlHost.Debug
- {
- /// <summary>
- /// MJPEG 推流的纯逻辑:RGB 像素 → JPEG 字节,JPEG → multipart 帧字节。
- /// 无 IO、无相机依赖,可纯单测。真正的抓帧/写流由 ControlHttpServer 推流分支驱动。
- /// 相机抓帧返回 24bpp BGR(GrabStable),WPF JpegBitmapEncoder 需 Bgr24 像素格式。
- /// </summary>
- public static class MjpegStreamWriter
- {
- public const string Boundary = "frame";
- /// <summary>把 24bpp BGR 像素(width*height*3)编码成 JPEG 字节(质量 85)。</summary>
- 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();
- }
- }
- /// <summary>把 JPEG 字节封成一个 multipart/x-mixed-replace 帧(含 boundary 头 + 帧体 + \r\n)。</summary>
- public static byte[] FrameBytes(byte[] jpeg)
- {
- 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;
- }
- /// <summary>响应头里的 Content-Type 值(供 ControlHttpServer 写头用)。</summary>
- public static string ContentType => $"multipart/x-mixed-replace; boundary={Boundary}";
- }
- }
|