SOLAR NET HUB

Arq. 3

go2rtc + Mumble + node-datachannel
Software Specification — RPi 3B — LAN + Remoto, Cero JS, Tor Ready
web-rtc-ext.md · EXT-5C / EXT-7 · Camino C

☞ Deep-Dive Specs

go2rtc — YAML config, endpoints, protocolos, WHEP
Mumble Bridge — audio pipeline, latencia, murmurd config
Oasis Proxy — routes, templates, HLS + MJPEG, CSP
node-datachannel — signaling SDP/ICE via SSB, file transfer
Deploy & Ops — systemd, Docker, monitoring, pre-flight
~20 MBRAM go2rtc
+0 MBMumble (ya corre)
~65 MBTotal stack
1 GBLPDDR2 total
0JS en browser

☞ Esquema de Arquitectura

 ┌──────────────────────────────────────────────────────────────────┐
 │              RASPBERRY PI 3B  ·  SOLAR NET HUB                  │
 │              BCM2837 · Cortex-A53 · VideoCore IV                │
 │                                                                  │
 │  ┌──────────┐  V4L2   ┌─────────────────────────┐              │
 │  │ 🎥 CAM   │────────▷│ go2rtc          :1984   │              │
 │  │ CSI/USB  │         │ ┌───────────────────────┐│              │
 │  └──────────┘         │ │MJPEG · HLS · RTSP     ││              │
 │                       │ │WebRTC (WHEP) · snap    ││              │
 │  ┌──────────┐  ALSA   │ │ALSA direct capture    ││              │
 │  │ 🎤 MIC   │────────▷│ └───────────────────────┘│              │
 │  └──────────┘         └──────────┬──────────────┘               │
 │                                  │ localhost                     │
 │  ┌──────────┐  ALSA   ┌─────────┼──────────┐                   │
 │  │ 🎤 MIC   │────────▷│ murmurd │  :64738  │                   │
 │  └──────────┘         │ (Mumble,│ya activo) │                   │
 │                       │ audio   │bidirecc.  │                   │
 │                       └─────────┼──────────┘                    │
 │                                 │ localhost                      │
 │  ┌──────────────────────────────┴────────────────────────────┐  │
 │  │  OASIS  :3000   (Node.js · SSB · cero JS en browser)     │  │
 │  │                                                           │  │
 │  │   GET /video     ──▷ proxy go2rtc MJPEG                  │  │
 │  │   GET /video.m3u8 ─▷ proxy go2rtc HLS (remoto)           │  │
 │  │   GET /snapshot  ──▷ proxy go2rtc JPEG                   │  │
 │  │   GET /audio     ──▷ bridge Mumble Opus→OGG              │  │
 │  │                                                           │  │
 │  │   HTML: <img src="/video"> + <audio src="/audio">        │  │
 │  │   Remote: <video src="/video.m3u8"> (HLS, sin JS)        │  │
 │  │                                                           │  │
 │  │   ┌─────────────────────────────────────┐                 │  │
 │  │   │ node-datachannel (P2P DataChannels) │                 │  │
 │  │   │ texto · archivos · señalización     │                 │  │
 │  │   └─────────────────────────────────────┘                 │  │
 │  └───────────────────────────────────────────────────────────┘  │
 └──────────────────────────────────────────────────────────────────┘
          │                │                │                │
          ▼                ▼                ▼                ▼
   ┌────────────┐  ┌─────────────┐  ┌────────────┐  ┌────────────┐
   │ 📺 BROWSER │  │ 🔊 MUMBLE  │  │ 📡 PEER   │  │ 🌐 REMOTE │
   │ LAN (no JS)│  │ (cliente    │  │ P2P data  │  │ HLS viewer │
   │ <img>MJPEG │  │  nativo)    │  │ node-dc   │  │ <video>HLS │
   │ <audio>OGG │  │  RTT ~20ms  │  │ text/file │  │ via Tor OK │
   └────────────┘  └─────────────┘  └────────────┘  └────────────┘
la navaja suiza: MJPEG local + HLS remoto + Tor, un solo binario Go

Key Concepts

¿Por qué go2rtc y no ffmpeg directo?
go2rtc captura directamente de V4L2 (cámara) y ALSA (mic) sin lanzar ffmpeg como subproceso. Un solo binario Go sirve MJPEG + HLS + RTSP + WebRTC. ffmpeg solo entra si se necesita transcodificación especial.
go2rtc usa ffmpeg internamente bajo demanda solo para resampling WebRTC o formatos exóticos. Para MJPEG/HLS básico no lo necesita. → Detalle completo de go2rtc en poster Arq. 1
¿Por qué Mumble para audio y no go2rtc?
Audio bidireccional: go2rtc solo envía audio (downstream). Mumble es full-duplex nativo. Además murmurd ya está corriendo → +0 MB de RAM nueva. Y funciona sobre Tor (TCP tunnel), mientras que go2rtc ALSA solo sirve por HTTP local.
⚠️ Nota de implementación: el full-duplex Mumble es nativo entre clientes Mumble. Para retorno de audio desde browser SNH sin JS nativo, se necesita bridge/inyección (estrategias 3A/3B/3C en web-rtc.md §3). La complejidad del bridge depende del camino elegido.
HLS: cómo funciona sin JS
HLS usa <video src="/video.m3u8">. En 2026 hay soporte nativo sin JS en Safari y en navegadores modernos compatibles (segun version). En el browser local del SNH seguimos usando MJPEG (sin JS) como opcion universal.
Estrategia recomendada: HLS nativo donde aplique + fallback MJPEG para Firefox desktop u otros casos sin soporte nativo. Evitar frases absolutas por browser sin contexto de version.
Doble salida: LAN (MJPEG) vs Remoto (HLS)
go2rtc sirve ambos formatos en paralelo desde la misma fuente V4L2:
GET /api/frame.mjpeg?src=solar-cam → MJPEG continuo (LAN, <img>) GET /api/stream.m3u8?src=solar-cam → HLS playlist (remoto, <video>) GET /api/frame.jpeg?src=solar-cam → Snapshot JPEG (thumbnails, SSB blobs)
BW savings: HLS usa H.264, ×10 menos BW que MJPEG. Para peers remotos sobre Tor (ancho de banda limitado), HLS es esencial.

Componentes

go2rtc
:1984
Lenguaje: Go (binario estático ~15 MB)
Repo: AlexxIT/go2rtc
RAM: ~20–40 MB
Entrada: V4L2, ALSA, RTSP, ffmpeg exec
Salida: MJPEG, HLS, RTSP, WebRTC (WHEP)
Config: go2rtc.yaml
go2rtc.yaml — config mínima
# /etc/go2rtc/go2rtc.yaml streams: solar-cam: - v4l2:///dev/video0 solar-mic: - alsa:///default api: listen: "127.0.0.1:1984"
Si la cámara CSI necesita libcamera: usar exec:libcamerify... en vez de v4l2 directo.
murmurd (Mumble)
:64738
Audio bidireccional · Opus codec
Ya instalado y corriendo → +0 MB
Compatible Tor (TCP tunnel)
Latencia: ~20 ms (nativo) / ~200 ms (bridge)
node-datachannel
P2P
DataChannels WebRTC sin media
Texto, archivos, señalización
Corre dentro de Oasis (Node.js)
No transporta audio ni vídeo

Ficha Rápida

PropiedadEstado
Cero JS en browser LAN
Cámara CSI
Cámara USB
Audio bidireccional
Funciona sobre Tor
Escritura a microSD✘ (no)
MJPEG streaming
HLS para remoto
WebRTC WHEP (media)
RTSP output
DataChannels P2P (data)
RAM nuevos procesos~65 MB

¿Cuándo Elegir Arq. 3?

✔ SÍ cuando:
· Necesitas LAN y remoto (HLS)
· Peers acceden vía Tor
· Quieres multi-protocolo (MJPEG+HLS+RTSP)
· Audio bidireccional es requisito
· Cámara puede ser CSI o USB (flexibilidad)

✘ NO cuando:
· Solo LAN + RAM ultra-crítica
→ Usa Arq. 6 (µStreamer, 10 MB menos)

☞ Setup Técnico

1. INSTALAR go2rtc
# Descargar binario ARM (no hay paquete Debian) wget -O /usr/local/bin/go2rtc \ https://github.com/AlexxIT/go2rtc/releases/latest/download/go2rtc_linux_arm chmod +x /usr/local/bin/go2rtc
Alt: compile from source
git clone https://github.com/AlexxIT/go2rtc cd go2rtc && go build -o go2rtc sudo mv go2rtc /usr/local/bin/
Requiere Go 1.21+. En Bookworm ARM: apt install golang o usar binario precompilado.
2. CONFIGURAR go2rtc
# /etc/go2rtc/go2rtc.yaml streams: solar-cam: - v4l2:///dev/video0 # USB solar-cam-csi: - "exec:libcamerify v4l2-ctl --stream" # CSI alt api: listen: "127.0.0.1:1984" # solo local
Config avanzada: ALSA, RTSP in, multi-stream
streams: solar-cam: - v4l2:///dev/video0 solar-mic: - alsa:///default ext-cam: - rtsp://192.168.1.50/stream1 # cámara IP externa api: listen: "127.0.0.1:1984" webrtc: candidates: - 192.168.1.10:8555 # IP LAN fija para WHEP
go2rtc puede agregar múltiples fuentes (V4L2 + RTSP + ALSA) y servirlas todas desde los mismos endpoints HTTP.
3. INICIAR go2rtc
go2rtc -config /etc/go2rtc/go2rtc.yaml
4. MUMBLE (ya corriendo)
# murmurd ya activo en :64738 # bridge audio para <audio> en browser: ffmpeg -i mumble://localhost:64738 \ -c:a libvorbis -f ogg pipe:1 | \ node oasis-audio-bridge.js
5. HTML EN OASIS (cero JS)
<!-- LAN: MJPEG + OGG audio --> <img src="/video" alt="stream"> <audio src="/audio" controls autoplay></audio> <!-- Remoto: HLS (para browsers con soporte nativo) --> <video src="/video.m3u8" controls autoplay></video>
6. PROXY EN OASIS (Node.js)
// routes — proxy go2rtc const GO2RTC = 'http://127.0.0.1:1984'; app.get('/video', (req, res) => { const up = http.get( `${GO2RTC}/api/frame.mjpeg?src=solar-cam`, (mjpeg) => { res.writeHead(200, { 'Content-Type': mjpeg.headers['content-type'], 'Cache-Control': 'no-store' }); mjpeg.pipe(res); } ); req.on('close', () => up.destroy()); }); app.get('/video.m3u8', (req, res) => { const up = http.get( `${GO2RTC}/api/stream.m3u8?src=solar-cam`, (hls) => { res.writeHead(200, hls.headers); hls.pipe(res); } ); req.on('close', () => up.destroy()); }); app.get('/snapshot', (req, res) => { const up = http.get( `${GO2RTC}/api/frame.jpeg?src=solar-cam`, (snap) => { res.writeHead(200, { 'Content-Type': 'image/jpeg', 'Cache-Control': 'no-store' }); snap.pipe(res); } ); req.on('close', () => up.destroy()); });
Security headers middleware
app.use((req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'SAMEORIGIN'); res.setHeader('Referrer-Policy', 'no-referrer'); res.setHeader('Content-Security-Policy', "default-src 'self'; img-src 'self'; " + "media-src 'self'; style-src 'self' 'unsafe-inline'; " + "script-src 'none'" ); next(); });
script-src 'none': refuerza cero JS a nivel CSP. Si el remote necesita hls.js, crear una ruta /remote con CSP más permisiva. → Ver poster de seguridad

Budget de Recursos

ComponenteRAMCPU idleCPU streamingNotas
murmurd (ya corre)~10 MB~0%~2–5%Opus C++, ya existente
gumble bridge~5 MB~0%~1%Go estático, forwarding
ffmpeg (audio bridge)~15 MB~0%~3%PCM→OGG, un stream
go2rtc~20 MB~0%~5–10%V4L2→MJPEG/HLS
node-datachannel~15 MB~0%~1%Solo DataChannel, sin media
TOTAL Arq. 3~65 MB~12–20%
Comparación con otras arquitecturas
RAM totalCPU streamingHLSTorBidireccional
Arq. 3 (esta)~65 MB~12–20%
Arq. 6 (µStreamer)~10–20 MB~5–15%
Arq. 1 (go2rtc integral)~70 MB~10–15%⚠️
Camino A (todo Node.js)~80 MB~20–30%
Arq. 3 = mejor equilibrio versatilidad / recursos. Solo Arq. 6 gasta menos RAM, pero pierde HLS y multi-protocolo.

go2rtc Endpoints — Cheatsheet

EndpointContent-TypeUso
/api/frame.mjpeg?src=multipart/x-mixed-replaceMJPEG continuo → <img>
/api/stream.m3u8?src=application/vnd.apple.mpegurlHLS playlist → <video>
/api/frame.jpeg?src=image/jpegSnapshot único → SSB blob
/api/stream.mp4?src=video/mp4MP4 progressive download
/api/webrtc?src=application/sdp (WHEP)WebRTC P2P (necesita JS)
/api/streamsapplication/jsonLista streams configurados
Verificar endpoints con curl
# Snapshot rápido curl -o snap.jpg http://127.0.0.1:1984/api/frame.jpeg?src=solar-cam # Verificar MJPEG headers curl -I http://127.0.0.1:1984/api/frame.mjpeg?src=solar-cam # Ver streams configurados curl -s http://127.0.0.1:1984/api/streams | python3 -m json.tool # HLS: verificar playlist curl -s http://127.0.0.1:1984/api/stream.m3u8?src=solar-cam | head

Arq. 3 vs Arq. 6 — Trade-offs

Arq. 3 (go2rtc + Mumble) Arq. 6 (µStreamer + Mumble)
RAM nuevos ~20 MB (go2rtc) ~10 MB (µStreamer) ★
Formatos vídeo MJPEG, HLS, RTSP, WebRTC ★ MJPEG solamente
Audio Igual (Mumble) Igual (Mumble)
HW JPEG encode ✘ (go2rtc no usa M2M) ✔ (V4L2 M2M nativo) ★
HLS para remoto ✔ ★ ✘ — solo LAN
Remoto Tor ✔ MJPEG + HLS ★ ✔ solo MJPEG (BW alto)
WebRTC WHEP ✔ integrado ★ ✘ (necesitaría Janus)
Install Binario manual apt install ★
★ = ventaja. Arq. 3 gana en versatilidad; Arq. 6 gana en ligereza.

DRY Rules — Arq. 3

1. go2rtc escucha en 127.0.0.1:1984 — nunca en 0.0.0.0. Oasis es la puerta única (:3000).
2. Audio siempre por Mumble — no por go2rtc ALSA directo (Mumble = bidireccional + Tor).
3. Un solo go2rtc.yaml — todas las fuentes declaradas ahí. No flags CLI sueltos.
4. Proxy con upstream.destroy() on req.close — evitar socket leaks.
5. Cache-Control: no-store en todas las rutas de streaming.
6. CSP script-src 'none' en rutas LAN. Si remoto necesita hls.js → ruta /remote con CSP separada. → poster de seguridad
7. Snapshots → guardar como SSB blob (inmutable, content-addressed).
8. node-datachannel solo para datos P2P, nunca para media.

Matriz de Fases × Cámara

CámaraFase 1 (LAN)Fase 2 (LAN+)Fase 3 (Internet)Fase 3 + Tor
CSIArq. 6 o Arq. 1Arq. 1Arq. 3 ★Arq. 3 ★
USBArq. 6 o Arq. 1Arq. 1Arq. 3 ★Arq. 3 ★
AmbasArq. 6/1 + detectArq. 1 + detectArq. 3 + detect ★Arq. 3 + detect ★
Arq. 3 domina toda la columna Fase 3 + Tor. Es la arquitectura de convergencia: la que necesitas cuando el SNH se abre al exterior.

Keywords

go2rtc MJPEG HLS .m3u8 WHEP V4L2 ALSA Mumble Opus OGG Tor node-datachannel SCTP SDP SSB blob :1984 :64738 :3000 go2rtc.yaml proxy pipe