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

基于Gstreamer与WebRTC实现Jetson Orin按需推流至React前端的技术咨询

整体实现方案拆解

一、先搞定信令服务器(FastAPI)

WebRTC必须靠信令交换SDP会话描述和ICE候选,用FastAPI做异步信令服务器最适配,核心是维护ORIN设备与React客户端的连接映射,实时转发消息:

  • 核心逻辑:

    1. 用WebSocket实现双向通信,满足信令实时传输需求
    2. 给ORIN设备和React客户端分配唯一标识(比如ORIN端标记为orin-stream-1/orin-stream-2,React端标记为react-ui-1),按标识转发消息
    3. 收到ORIN的SDP Answer/ICE候选,直接转发给对应React客户端;收到React的SDP Offer/ICE候选,转发给目标ORIN设备
  • 极简代码示例:

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import Dict
import json

app = FastAPI()
connections: Dict[str, WebSocket] = {}

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await websocket.accept()
    connections[client_id] = websocket
    try:
        while True:
            data = await websocket.receive_text()
            msg = json.loads(data)
            target_ws = connections.get(msg["target"])
            if target_ws:
                await target_ws.send_text(msg["data"])
    except WebSocketDisconnect:
        del connections[client_id]

二、ORIN端的GStreamer WebRTC管道(从videotestsrc入手)

先跑通videotestsrc的WebRTC推流,再替换为arravisrc。核心用webrtcbin元素处理WebRTC会话协商与媒体传输,搭配ORIN硬件编码提升性能:

1. 核心管道结构(命令行测试用)

gst-launch-1.0 videotestsrc ! videoconvert ! nvh264enc bitrate=2000000 ! h264parse ! rtph264pay config-interval=1 ! webrtcbin name=webrtcbin stun-server=stun://你的STUN服务器地址:3478
  • nvh264enc:ORIN硬件H.264编码器,远快于软件编码
  • rtph264pay:将H.264裸包封装为RTP格式,适配WebRTC传输要求
  • webrtcbin:必须指定STUN服务器解决NAT穿透;若ORIN在严格NAT环境,需追加turn-server=turn://用户名:密码@你的TURN服务器地址:3478

2. 信令交互脚本(Python控制GStreamer)

命令行无法处理信令,需用Python的Gst库写脚本,让webrtcbin与FastAPI信令服务器交互:

  • 极简脚本片段:
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstWebRTC', '1.0')
gi.require_version('GstSdp', '1.0')
from gi.repository import Gst, GstWebRTC, GstSdp
import websockets
import json
import asyncio

Gst.init(None)

async def handle_webrtc_stream():
    async with websockets.connect("ws://你的FastAPI服务器地址:8000/ws/orin-stream-1") as ws:
        # 构建GStreamer管道
        pipeline = Gst.parse_launch("""
            videotestsrc ! videoconvert ! nvh264enc bitrate=2000000 ! h264parse ! rtph264pay config-interval=1 ! webrtcbin name=webrtcbin stun-server=stun://你的STUN服务器地址:3478
        """)
        webrtcbin = pipeline.get_by_name("webrtcbin")

        # 处理SDP Offer,生成并发送Answer
        def on_offer_received(_, offer):
            webrtcbin.emit('set-remote-description', offer)
            promise = Gst.Promise.new()
            webrtcbin.emit('create-answer', None, promise)
            promise.interrupt()
            reply = promise.get_reply()
            answer = reply.get_value('answer')
            asyncio.create_task(ws.send(json.dumps({
                "target": "react-ui-1",
                "data": json.dumps({
                    "type": "answer",
                    "sdp": answer.sdp.as_text()
                })
            })))

        # 转发ICE候选
        def on_ice_candidate(_, mlineindex, candidate):
            asyncio.create_task(ws.send(json.dumps({
                "target": "react-ui-1",
                "data": json.dumps({
                    "type": "ice-candidate",
                    "candidate": candidate,
                    "sdpMLineIndex": mlineindex
                })
            })))

        # 绑定信号监听
        webrtcbin.connect('on-offer-received', on_offer_received)
        webrtcbin.connect('on-ice-candidate', on_ice_candidate)

        # 监听信令消息
        async for msg in ws:
            msg_data = json.loads(msg)
            if msg_data["type"] == "offer":
                # 解析SDP Offer并传递给webrtcbin
                sdp = GstSdp.SDPMessage.new()
                GstSdp.sdp_message_parse_buffer(bytes(msg_data["sdp"], 'utf-8'), sdp)
                offer = GstWebRTC.WebRTCSessionDescription.new(GstWebRTC.WebRTCSDPType.OFFER, sdp)
                on_offer_received(webrtcbin, offer)
            elif msg_data["type"] == "ice-candidate":
                webrtcbin.emit('add-ice-candidate', msg_data["sdpMLineIndex"], msg_data["candidate"])
            elif msg_data["type"] == "start-stream":
                # 启动管道推流
                pipeline.set_state(Gst.State.PLAYING)

asyncio.run(handle_webrtc_stream())

三、React端的WebRTC接收逻辑

用浏览器原生RTCPeerConnection连接信令服务器,发起SDP Offer,接收流并播放:

  • 核心代码片段:
import { useEffect, useRef } from 'react';

const WebRTCPlayer = () => {
  const videoRef = useRef(null);
  let pc;
  let ws;

  useEffect(() => {
    // 初始化RTCPeerConnection,配置STUN/TURN
    pc = new RTCPeerConnection({
      iceServers: [
        { urls: 'stun://你的STUN服务器地址:3478' },
        { urls: 'turn://用户名:密码@你的TURN服务器地址:3478' }
      ]
    });

    // 绑定远程流到video元素
    pc.ontrack = (event) => {
      if (videoRef.current) {
        videoRef.current.srcObject = event.streams[0];
      }
    };

    // 连接信令服务器
    ws = new WebSocket('ws://你的FastAPI服务器地址:8000/ws/react-ui-1');
    ws.onmessage = async (event) => {
      const msg = JSON.parse(event.data);
      if (msg.type === 'answer') {
        await pc.setRemoteDescription(new RTCSessionDescription(msg));
      } else if (msg.type === 'ice-candidate') {
        await pc.addIceCandidate(new RTCIceCandidate(msg));
      }
    };

    // 发起推流请求
    const startStreamRequest = async () => {
      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);
      // 发送SDP Offer给ORIN
      ws.send(JSON.stringify({
        target: 'orin-stream-1',
        data: JSON.stringify({
          type: 'offer',
          sdp: pc.localDescription.sdp
        })
      }));
      // 发送启动流指令
      ws.send(JSON.stringify({
        target: 'orin-stream-1',
        data: JSON.stringify({ type: 'start-stream' })
      }));
    };

    startStreamRequest();

    // 组件卸载时清理资源
    return () => {
      pc.close();
      ws.close();
    };
  }, []);

  return <video ref={videoRef} autoPlay playsInline />;
};

export default WebRTCPlayer;

四、STUN/TURN服务器搭建(解决NAT问题)

ORIN处于NAT后,必须用STUN获取公网ICE候选;严格NAT环境下需用TURN中转媒体流:

  • 用coturn搭建TURN服务器(可部署在ORIN或AWS EC2):
    1. 安装coturn:sudo apt install coturn
    2. 修改配置文件/etc/turnserver.conf:
      listening-port=3478
      external-ip=你的公网IP/内网IP
      user=自定义用户名:自定义密码
      realm=自定义域名
      
    3. 启动服务:sudo systemctl start coturn

五、替换为arravisrc相机流

等videotestsrc跑通后,直接替换管道中的源即可,注意配置相机参数:

gst-launch-1.0 arravisrc camera-name="你的相机名称" ! videoconvert ! nvh264enc bitrate=2000000 ! h264parse ! rtph264pay config-interval=1 ! webrtcbin name=webrtcbin stun-server=stun://你的STUN服务器地址:3478
  • 用arv-tool-0.8可扫描ORIN上的arravis相机,获取设备名称与参数

关键注意事项

  • ORIN需安装依赖插件:gst-plugins-good(含webrtcbin)、gst-plugins-bad(含nvh264enc)、aravis
  • 信令服务器需部署在公网(如AWS EC2),确保ORIN与React客户端均可访问
  • 防火墙需开放WebSocket端口(如8000)、STUN/TURN端口(3478,UDP/TCP)

内容的提问来源于stack exchange,提问作者Suryaa K S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:07:04