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