You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 03:33:21