Next.js前端向FastAPI推流,OpenCV解析帧始终为None问题排查
问题:FastAPI后端无法解析WebSocket传来的视频帧(frame始终为None)
我正在开发一款视频流应用:前端基于Next.js构建,后端用FastAPI实现,目标是通过WebSocket传输视频流,并用OpenCV处理显示。目前WebSocket连接正常且能发送数据,但后端的frame变量始终为None,无法解析出视频帧。
现有代码
后端FastAPI代码
@app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() try: while True: data = await websocket.receive_bytes() nparr = np.frombuffer(data, np.uint8) frame = cv.imdecode(nparr, cv.IMREAD_COLOR) if frame is not None: # OpenCV处理逻辑 cv.imshow("frame", frame) cv.waitKey(1) except WebSocketDisconnect: print("WebSocket disconnected") cv.destroyAllWindows() await websocket.close()
前端Next.js核心代码
import React, { useRef, useEffect } from 'react'; import { Button, Card, Row} from 'antd'; const Webcam = () => { const videoRef = useRef(null); const mediaStreamRef = useRef(null); const mediaRecorderRef = useRef(null); const websocket = useRef(null); const startCamera = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); mediaStreamRef.current = stream; videoRef.current.srcObject = stream; videoRef.current.play(); // 建立WebSocket连接 websocket.current = new WebSocket('ws://localhost:3010/ws'); websocket.current.onopen = () => { console.log('WebSocket connected'); }; mediaRecorderRef.current = new MediaRecorder(stream); mediaRecorderRef.current.ondataavailable = (e) => { if (e.data.size > 0) { websocket.current.send(e.data); // 发送视频数据 } }; mediaRecorderRef.current.start(1000); // 每秒录制一次 } catch (error) { console.error('获取摄像头流失败: ', error); } }; const stopCamera = () => { if (mediaStreamRef.current) { const tracks = mediaStreamRef.current.getTracks(); tracks.forEach((track) => { track.stop(); }); mediaStreamRef.current = null; videoRef.current.srcObject = null; } if (websocket.current) { websocket.current.close(); } if (mediaRecorderRef.current) { mediaRecorderRef.current.stop(); } }; useEffect(() => { return () => { stopCamera(); }; }, []); return ( <Card title="Stream"> <video ref={videoRef} style={{ width: '100%', height: 'auto' }} autoPlay playsInline muted /> <Row justify="center" style={{ marginTop: 'auto' }}> <Button type="primary" onClick={() => startCamera()} style={{ margin: '1rem' }} > 开始会话 </Button> <Button style={{ margin: '1rem' }} type="danger" onClick={() => stopCamera()}> 结束会话 </Button> </Row> </Card> ); }; export default Webcam;
问题原因
前端使用MediaRecorder发送的是媒体容器格式(默认是WebM)的数据,这是封装了多帧视频的流格式,而OpenCV的cv.imdecode只能解析单帧图像的二进制数据(如JPEG、PNG),无法直接解码媒体容器流,因此解析失败返回None。
解决方案
方案1:前端直接捕获单帧图像并发送(推荐)
放弃MediaRecorder,改用Canvas捕获视频的每一帧,编码为JPEG/PNG格式后再通过WebSocket发送,这样后端就能直接用cv.imdecode解析。
修改后的前端startCamera函数:
const startCamera = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); mediaStreamRef.current = stream; videoRef.current.srcObject = stream; videoRef.current.play(); // 建立WebSocket连接 websocket.current = new WebSocket('ws://localhost:3010/ws'); websocket.current.onopen = () => { console.log('WebSocket connected'); // 循环捕获视频帧 const captureFrame = () => { // 检查WebSocket状态和视频元素是否可用 if (!videoRef.current || !websocket.current || websocket.current.readyState !== WebSocket.OPEN) { return; } // 创建Canvas用于绘制帧 const canvas = document.createElement('canvas'); canvas.width = videoRef.current.videoWidth; canvas.height = videoRef.current.videoHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height); // 将Canvas内容转为JPEG Blob并发送 canvas.toBlob((blob) => { if (blob) { websocket.current.send(blob); } }, 'image/jpeg', 0.8); // 0.8是JPEG质量参数 // 下一帧继续捕获 requestAnimationFrame(captureFrame); }; captureFrame(); }; } catch (error) { console.error('获取摄像头流失败: ', error); } };
方案2:后端解码媒体容器流(复杂度高)
如果坚持使用MediaRecorder发送WebM流,后端需要使用FFmpeg或专门的媒体解码库(如pyav)先解码WebM流,提取出单帧图像后再交给OpenCV处理。这种方式需要额外依赖,且处理逻辑更复杂,不推荐用于简单场景。
验证
修改前端代码后,后端的frame变量将能正常解析出视频帧,cv.imshow可以正常显示画面。
内容的提问来源于stack exchange,提问作者Marcelo_M
相关产品推荐
相关产品推荐

