如何用Java搭建持续向网页推送视频二进制数据的Web服务
核心问题定位
你的代码目前存在两个根本性错误导致无法正常播放:
- 数据格式不匹配:你返回的是OpenCV Mat的原始像素数组,既不是JPG/PNG这类图片编码格式,也不是H264这类视频编码格式,
<video>标签无法直接解析原始像素数据,你手动指定的type: "video/H264"和实际返回的内容完全不符。 - 服务端逻辑错误:普通Spring MVC接口的
return语句会直接结束当前HTTP请求,你写的while循环只会执行一次就终止,永远只能返回第一帧数据,不可能实现持续流传输。
疑问解答
你提到的两种拉流模式都有成熟的实现方案,对应不同的技术选型:
方案1:客户端持续发请求拉取单帧(MJPG短轮询,实现最简单)
这是入门级实时流的最优选择,不需要使用<video>标签,直接用<img>标签即可实现,开发成本极低:
- 服务端改造:删除原来的while循环,每次请求返回当前最新一帧的JPG编码数据即可,核心代码修改如下:
import org.springframework.http.MediaType; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.core.MatOfByte; @RestController public class GreetingController { // 原有成员变量和构造方法保持不变 @GetMapping(value = "/streaming", produces = MediaType.IMAGE_JPEG_VALUE) public byte[] streaming() { if (videoCapture.isOpened() && videoCapture.read(m)) { MatOfByte jpgBuffer = new MatOfByte(); // 将Mat直接编码为JPG格式字节数组 Imgcodecs.imencode(".jpg", m, jpgBuffer); return jpgBuffer.toArray(); } return new byte[0]; } } - 客户端改造:不需要AJAX,直接通过定时器刷新
<img>标签的src地址即可,代码如下:<!DOCTYPE html> <html> <head> <title>实时流</title> <meta charset="UTF-8"> </head> <body> <img id="videoFrame" alt="实时画面"> </body> <script> const img = document.getElementById('videoFrame'); // 每40ms刷新一次,对应25帧/秒的帧率 setInterval(() => { // 加时间戳避免浏览器缓存图片 img.src = 'http://localhost:8080/streaming?' + Date.now(); }, 40); </script> </html>
方案2:客户端仅发一次请求,服务端持续返回流(H264长连接方案)
如果你需要用<video>标签实现更低带宽的传输,就选择这种方案,需要改造服务端为长连接响应模式:
- 服务端改造:不能用普通接口的return返回数据,要使用
StreamingResponseBody保持HTTP长连接,循环编码H264帧写入响应流,不会主动断开连接,核心代码示例:import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; @GetMapping("/streaming") public StreamingResponseBody streaming() { return outputStream -> { MatOfByte frameBuffer = new MatOfByte(); while (videoCapture.isOpened() && videoCapture.read(m)) { // 这里需要将Mat编码为H264格式的切片,推荐集成FFmpeg实现编码 // 测试阶段可以先用JPG格式验证流传输逻辑 Imgcodecs.imencode(".jpg", m, frameBuffer); byte[] frameBytes = frameBuffer.toArray(); // 写入帧长度标识,方便前端切分不同的帧 outputStream.write(intToBytes(frameBytes.length)); // 写入帧数据 outputStream.write(frameBytes); outputStream.flush(); // 控制帧率为25帧/秒 Thread.sleep(40); } }; } // 辅助方法:int转4字节数组 private byte[] intToBytes(int value) { return new byte[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value }; } - 客户端改造:不能用普通AJAX一次性拉取数据,要使用Fetch API的可读流持续读取响应,切分帧后喂给MediaSource对象,再绑定到
<video>标签播放。
实现建议
优先选择方案1实现,不需要处理复杂的视频编码逻辑,代码改动最小,延迟也能满足大多数普通场景的需求。跑通基础功能后,如果需要降低带宽占用,再升级为方案2的H264流方案即可。
内容的提问来源于stack exchange,提问作者O Connor
相关产品推荐
相关产品推荐

