使用WebAPI流式传输MJPEG时始终出现缓冲问题
解决MJPEG流式传输的核心思路
MJPEG的本质是multipart/x-mixed-replace类型的响应,它完全不需要提前知道总帧数或总长度——每个视频帧作为独立的部分,用自定义分隔符(boundary)分开,浏览器会自动持续接收并刷新画面。关键是要正确配置响应头,然后持续向输出流写入帧数据。
1. 配置WebAPI路由
首先确保MJPEG流路由和其他路由(配置、Web界面)互不干扰,推荐用属性路由明确区分:
public class CameraController : ApiController { // MJPEG流专属路由 [Route("api/camera/stream")] [HttpGet] public HttpResponseMessage GetCameraStream() { // 流逻辑实现放在这里 } // 示例:配置查询路由 [Route("api/camera/config")] [HttpGet] public IHttpActionResult GetCameraConfig() { return Ok(new { Resolution = "1920x1080", FrameRate = 25 }); } // 示例:Web界面路由(如果是返回静态页面,可结合Hosted静态文件处理) [Route("camera/dashboard")] [HttpGet] public HttpResponseMessage GetDashboard() { var content = new StringContent(File.ReadAllText("dashboard.html")); content.Headers.ContentType = new MediaTypeHeaderValue("text/html"); return new HttpResponseMessage(HttpStatusCode.OK) { Content = content }; } }
2. 实现MJPEG流的核心逻辑
核心是手动构建HttpResponseMessage,启用分块传输,然后循环写入每个帧:
[Route("api/camera/stream")] [HttpGet] public HttpResponseMessage GetCameraStream() { // 定义分块分隔符(选一个不会在帧数据中出现的唯一字符串) var boundary = "camera-stream-boundary-7890"; // 创建响应,使用PushStreamContent主动控制输出流 var response = new HttpResponseMessage(HttpStatusCode.OK); response.Content = new PushStreamContent((stream, content, context) => { using var writer = new StreamWriter(stream); using var binaryWriter = new BinaryWriter(stream); try { // 持续获取监控帧(替换成你的安防摄像头帧获取逻辑) while (true) { // 1. 获取当前帧的字节数组(示例:调用摄像头SDK/本地帧源) byte[] frameBytes = FetchCurrentCameraFrame(); if (frameBytes == null || frameBytes.Length == 0) { Thread.Sleep(50); // 无帧时短暂休眠,避免空循环占用资源 continue; } // 2. 写入分块边界与帧头 writer.WriteLine($"--{boundary}"); writer.WriteLine("Content-Type: image/jpeg"); writer.WriteLine($"Content-Length: {frameBytes.Length}"); writer.WriteLine(); // 空行分隔头信息与帧内容 writer.Flush(); // 确保头信息立即写入流 // 3. 写入帧数据 binaryWriter.Write(frameBytes); binaryWriter.Flush(); // 控制帧率(比如25帧/秒,根据实际需求调整) Thread.Sleep(40); } } catch (IOException) { // 客户端断开连接时会抛出此异常,清理并结束流 writer.WriteLine($"--{boundary}--"); writer.Flush(); } finally { stream.Close(); } }); // 设置响应的多部分类型与分隔符 response.Content.Headers.ContentType = new MediaTypeHeaderValue("multipart/x-mixed-replace"); response.Content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("boundary", boundary)); // 禁用缓存,避免浏览器缓存旧帧不刷新 response.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true, NoStore = true, MustRevalidate = true }; return response; } // 示例:替换为你的实际帧获取逻辑(比如调用安防摄像头SDK) private byte[] FetchCurrentCameraFrame() { // 测试用:读取本地图片模拟帧 // return File.ReadAllBytes("test-frame.jpg"); // 实际场景:调用摄像头SDK的帧抓取方法 throw new NotImplementedException("请替换为你的安防摄像头帧获取逻辑"); }
3. 关键注意事项
- 分块传输自动启用:使用
PushStreamContent且不设置总Content-Length时,WebAPI会自动启用Transfer-Encoding: chunked,这正是MJPEG流式传输需要的。 - 边界格式严格遵守:每个帧的开头必须是
--{boundary},客户端断开时可以用--{boundary}--结束流。 - 处理客户端断开:一定要捕获
IOException,当用户关闭页面/刷新时,流会断开,此时需清理资源并退出循环。 - 禁用缓存是必须的:如果不设置
Cache-Control头,浏览器可能会缓存第一帧,导致后续画面不刷新。 - 帧率控制要合理:根据摄像头实际帧率和客户端性能调整
Thread.Sleep时长,避免服务器和客户端过载。
4. 验证其他路由可用性
你的配置路由、Web界面路由完全不受影响——WebAPI会根据路由匹配规则分发请求,MJPEG流的Action只会处理匹配/api/camera/stream的请求,其他请求正常流向对应路由。
内容的提问来源于stack exchange,提问作者kidmosey
相关产品推荐
相关产品推荐

