Key Concept
RPi-WebRTC sirve WebRTC nativo → el browser necesita JS. El proxy consume ese stream WebRTC dentro de Node.js (vía node-datachannel) y reconvierte a MJPEG/OGG servido como HTTP puro. Así el browser del SNH recibe <img src="/video"> y <audio src="/audio"> sin JS.
Es el mismo principio que Arq. 3 (go2rtc sirve MJPEG/HLS sin JS), pero aquí la fuente es WebRTC nativo en vez de V4L2 directo.
Pipeline de Conversión
RPi-WebRTC (:8889)
│
│ WebRTC: H.264 RTP + Opus RTP
│
┌────┴──────────────────────────────── media_proxy.js ──────────┐
│ │
│ node-datachannel │
│ ├─ PeerConnection (localhost) │
│ ├─ onTrack('video') ──▷ ffmpeg -f h264 -i pipe: │
│ │ -f mjpeg -q:v 5 pipe:1 │
│ │ ──▷ HTTP chunked → GET /video │
│ │ │
│ └─ onTrack('audio') ──▷ ffmpeg -f opus -i pipe: │
│ -c:a libvorbis -f ogg pipe:1 │
│ ──▷ HTTP chunked → GET /audio │
│ │
│ onDataChannel ──▷ relay a webrtc_model.js (chat/files) │
└────────────────────────────────────────────────────────────────┘
Rutas HTTP (dentro de Oasis)
| Ruta | Método | Content-Type | Descripción |
| /video |
GET |
multipart/x-mixed-replace (MJPEG) |
Stream MJPEG continuo, ffmpeg decode H.264→MJPEG |
| /audio |
GET |
audio/ogg |
Stream OGG/Vorbis, ffmpeg decode Opus→OGG |
| /snapshot |
GET |
image/jpeg |
Último frame JPEG capturado |
Los templates EJS de Oasis renderizan HTML con:
<img src="/video" alt="cam">
<audio src="/audio" autoplay controls></audio>
Handshake: Proxy ↔ RPi-WebRTC
1. Crear PeerConnection
const { PeerConnection } = require('node-datachannel');
const pc = new PeerConnection('proxy', { iceServers: [] });
2. Intercambiar SDP con RPi-WebRTC
const offer = pc.createOffer();
pc.setLocalDescription(offer);
const resp = await fetch('http://127.0.0.1:8889/offer', {
method: 'POST',
headers: { 'Content-Type': 'application/sdp' },
body: offer.sdp
});
const answerSdp = await resp.text();
pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
3. Recibir tracks y decodificar
pc.onTrack((track) => {
if (track.kind === 'video') {
spawnFfmpeg(track, 'mjpeg', videoOutputStream);
} else if (track.kind === 'audio') {
spawnFfmpeg(track, 'ogg', audioOutputStream);
}
});
El handshake es en localhost — no necesita STUN/TURN. ICE candidates serán solo host candidates (127.0.0.1).
Comandos ffmpeg Internos
Vídeo: H.264 → MJPEG
ffmpeg -f h264 -i pipe:0 \
-f mjpeg -q:v 5 -r 15 pipe:1
-q:v 5 balancea calidad/BW. -r 15 limita fps para ahorrar CPU.
Audio: Opus → OGG/Vorbis
ffmpeg -f opus -i pipe:0 \
-c:a libvorbis -q:a 3 -f ogg pipe:1
Snapshot: H.264 → JPEG (un frame)
ffmpeg -f h264 -i pipe:0 \
-frames:v 1 -f image2 pipe:1