如何从无浏览器的无头RPi通过WebRTC推流音视频?基于Node.js适配iOS
无头树莓派基于Node.js+WebRTC推流实现方案(适配iOS)
整体技术选型
弃用uv4l方案,采用wrtc(Node.js原生WebRTC绑定库)作为核心实现,无需浏览器环境即可在无头树莓派上运行WebRTC协议栈。视频采集用树莓派官方的libcamera-vid/raspivid工具,音频采集用arecord,信令服务基于socket.io实现,完全自主可控可适配iOS兼容性要求。
树莓派环境准备
- 安装基础依赖:
sudo apt update && sudo apt install -y libcamera-dev libasound2-dev build-essential
- 安装Node.js 16/18 LTS版本,该版本对
wrtc兼容性最好 - 安装项目依赖,优先选择预编译的arm架构版本避免编译失败:
npm install @roamhq/wrtc express socket.io socket.io-client
核心实现逻辑
1. 信令服务实现
负责交换SDP offer/answer和ICE候选,可直接部署在树莓派本地:
// server.js 信令服务代码 const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: "*" } }); io.on('connection', (socket) => { socket.on('offer', (data) => socket.to(data.receiverId).emit('offer', data)); socket.on('answer', (data) => socket.to(data.senderId).emit('answer', data)); socket.on('ice-candidate', (data) => socket.to(data.targetId).emit('ice-candidate', data)); }); server.listen(8080, () => console.log('信令服务运行在8080端口'));
2. 树莓派推流逻辑
运行在树莓派后台的无头进程,直接采集音视频并推流:
// stream.js 推流端代码 const { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, MediaStream, MediaStreamTrack } = require('@roamhq/wrtc'); const { spawn } = require('child_process'); const io = require('socket.io-client'); const socket = io('http://127.0.0.1:8080'); const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); // 采集H.264视频流,树莓派旧系统(Buster及之前)替换libcamera-vid为raspivid const videoProcess = spawn('libcamera-vid', [ '--inline', '--framerate', '30', '--width', '1280', '--height', '720', '--codec', 'h264', '--profile', 'high', '--level', '4.1', '-o', '-' ]); // 采集48kHz单声道音频,适配WebRTC标准 const audioProcess = spawn('arecord', [ '-f', 'S16_LE', '-c', '1', '-r', '48000', '-D', 'default', '-' ]); // 封装音视频轨道加入PeerConnection const videoTrack = new MediaStreamTrack({ kind: 'video', codec: 'H264', stream: videoProcess.stdout }); const audioTrack = new MediaStreamTrack({ kind: 'audio', codec: 'opus', stream: audioProcess.stdout }); const stream = new MediaStream([videoTrack, audioTrack]); pc.addTrack(videoTrack, stream); pc.addTrack(audioTrack, stream); // 信令交互逻辑 socket.on('offer', async (data) => { await pc.setRemoteDescription(new RTCSessionDescription(data.offer)); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); socket.emit('answer', { senderId: data.receiverId, receiverId: data.senderId, answer }); }); pc.on('icecandidate', (candidate) => { if (candidate) socket.emit('ice-candidate', { targetId: 接收端连接ID, candidate }); }); socket.on('ice-candidate', async (candidate) => { await pc.addIceCandidate(new RTCIceCandidate(candidate)); });
iOS兼容核心要点
视频编码必须采用H.264 High Profile 4.1及以下等级,iOS Safari仅支持该等级的硬解码,编码等级过高会出现黑屏
SDP中不得包含iOS不支持的H.264扩展特性,必要时可手动过滤SDP中的冗余参数,移除FEC、SVC等特性声明
必须包含至少一个音频轨道,即便业务不需要音频也要加入静音轨道,iOS Safari不支持仅包含视频的WebRTC流
ICE候选必须存在可达地址,iOS对ICE候选的校验比Chrome严格,无有效可达候选会直接断开连接
iOS播放端video标签必须添加playsinline属性,且播放动作需要在用户交互事件(如点击)中触发,否则会被系统禁止播放
启动流程
- 启动信令服务:
node server.js - 启动推流进程:
node stream.js - 前端页面连接信令服务发送offer,收到answer后即可完成WebRTC连接播放
内容的提问来源于stack exchange,提问作者janek
相关产品推荐
相关产品推荐

