基于Gstreamer与WebRTC实现Jetson Orin按需推流至React前端的技术咨询
整体实现方案拆解
一、先搞定信令服务器(FastAPI)
WebRTC必须靠信令交换SDP会话描述和ICE候选,用FastAPI做异步信令服务器最适配,核心是维护ORIN设备与React客户端的连接映射,实时转发消息:
核心逻辑:
- 用WebSocket实现双向通信,满足信令实时传输需求
- 给ORIN设备和React客户端分配唯一标识(比如ORIN端标记为
orin-stream-1/orin-stream-2,React端标记为react-ui-1),按标识转发消息 - 收到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):
- 安装coturn:
sudo apt install coturn - 修改配置文件
/etc/turnserver.conf:listening-port=3478 external-ip=你的公网IP/内网IP user=自定义用户名:自定义密码 realm=自定义域名 - 启动服务:
sudo systemctl start coturn
- 安装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
相关产品推荐
相关产品推荐

