{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit","title":"Escalando WebRTC a 10.000 Participantes: Comparación de mediasoup, LiveKit y Janus","description":"Una comparación probada en producción de servidores de media WebRTC. Arquitectura SFU vs MCU, configuración de simulcast, negociación de codecs VP9/AV1, y los patrones de escalado que realmente funcionan cuando se necesita 10.000 participantes concurrentes.","language":"es","datePublished":"2026-02-07","dateModified":null,"contentFormat":"text/markdown","content":"Real-time\n\n# Escalando WebRTC a 10.000 Participantes: Comparación de mediasoup, LiveKit y Janus\n\n2026-02-079 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/webrtc-media-servers-mediasoup-livekit>)\n\n## SFU vs MCU: La Decisión de Arquitectura que Determina Todo\n\nAntes de comparar servers, tienen que entender la elección de arquitectura fundamental porque condiciona todo lo demás.\n\n**MCU (Multipoint Conferencing Unit):** El servidor recibe todos los streams de los participantes, los decodifica, los compone en un único stream mixeado, re-encodea, y manda un stream a cada participante. Cada participante recibe y decodifica un solo stream sin importar cuánta gente hay en la llamada.\n\n**SFU (Selective Forwarding Unit):** El servidor recibe el stream de cada participante y lo reenvía a los demás sin transcodificar. Cada participante recibe N-1 streams (donde N es el número de participantes) y los decodifica localmente.\n\n```\nArquitectura MCU:\n Participante A ──video──▶ ┌─────────┐ ──mixed──▶ Participante A\n Participante B ──video──▶ │ MCU │ ──mixed──▶ Participante B\n Participante C ──video──▶ │ Server │ ──mixed──▶ Participante C\n Participante D ──video──▶ └─────────┘ ──mixed──▶ Participante D\n CPU del server: O(n) decode + composite + encode\n Bandwidth del server: O(n) in + O(n) out\n\nArquitectura SFU:\n Participante A ──video──▶ ┌─────────┐ ──A,B,C──▶ Participante D\n Participante B ──video──▶ │ SFU │ ──A,B,D──▶ Participante C\n Participante C ──video──▶ │ Server │ ──A,C,D──▶ Participante B\n Participante D ──video──▶ └─────────┘ ──B,C,D──▶ Participante A\n CPU del server: O(1) por stream (solo forwarding)\n Bandwidth del server: O(n) in + O(n²) out (sin simulcast)\n```\n\nUn punto de partida común es un approach MCU a escala pequeña (Janus con el plugin videoroom en modo MCU) para un producto de sala de directorio de 20 personas. Funciona bien a esa escala. Pero cuando los requerimientos cambian a 500+ participantes, MCU se vuelve impracticable inmediatamente, el costo de CPU de decodificar 500 streams de video, componerlos y re-encodear es astronómico. El cambio a SFU se vuelve necesario.\n\nEl problema de bandwidth con SFUs (O(n²) de salida) se resuelve con simulcast y SVC. Más sobre eso abajo.\n\n## mediasoup: El Bisturí del Cirujano\n\n[mediasoup](<https://mediasoup.org/>) no es un servidor, es una librería C++ con un wrapper Node.js. Construís tu propio servidor alrededor. Esto te da control total y responsabilidad total.\n\nAsí se ve un setup básico de mediasoup:\n\n```javascript\nconst mediasoup = require('mediasoup');\n\n// Crear un worker (uno por core de CPU)\nconst worker = await mediasoup.createWorker({\n logLevel: 'warn',\n rtcMinPort: 40000,\n rtcMaxPort: 49999,\n});\n\n// Crear un router (uno por sala, define codecs)\nconst router = await worker.createRouter({\n mediaCodecs: [\n {\n kind: 'audio',\n mimeType: 'audio/opus',\n clockRate: 48000,\n channels: 2,\n },\n {\n kind: 'video',\n mimeType: 'video/VP9',\n clockRate: 90000,\n parameters: {\n 'profile-id': 2,\n },\n },\n {\n kind: 'video',\n mimeType: 'video/H264',\n clockRate: 90000,\n parameters: {\n 'packetization-mode': 1,\n 'profile-level-id': '42e01f',\n 'level-asymmetry-allowed': 1,\n },\n },\n ],\n});\n\n// Crear WebRTC transport para un producer (publisher)\nconst transport = await router.createWebRtcTransport({\n listenIps: [{ ip: '0.0.0.0', announcedIp: '203.0.113.1' }],\n enableUdp: true,\n enableTcp: true,\n preferUdp: true,\n initialAvailableOutgoingBitrate: 1000000,\n});\n```\n\nLa configuración crítica para escalar es simulcast. Sin esto, cada suscriptor recibe el stream en resolución completa incluso si lo está renderizando como thumbnail de 160x90:\n\n```javascript\n// Lado del producer: enviar 3 capas simulcast\nconst producer = await transport.produce({\n kind: 'video',\n rtpParameters: {\n codecs: [/* ... */],\n encodings: [\n { rid: 'r0', maxBitrate: 100000, scaleResolutionDownBy: 4 },\n { rid: 'r1', maxBitrate: 300000, scaleResolutionDownBy: 2 },\n { rid: 'r2', maxBitrate: 900000, scaleResolutionDownBy: 1 },\n ],\n },\n});\n\n// Lado del suscriptor: pedir capa específica\nconst consumer = await transport.consume({\n producerId: producer.id,\n rtpCapabilities: device.rtpCapabilities,\n preferredLayers: { spatialLayer: 1, temporalLayer: 2 },\n});\n\n// Cambiar capas dinámicamente según layout de UI\nasync function onTileResize(consumerId, width) {\n const consumer = consumers.get(consumerId);\n if (width < 200) {\n await consumer.setPreferredLayers({ spatialLayer: 0 });\n } else if (width < 600) {\n await consumer.setPreferredLayers({ spatialLayer: 1 });\n } else {\n await consumer.setPreferredLayers({ spatialLayer: 2 });\n }\n}\n```\n\nPara escalar más allá de un solo servidor, mediasoup usa pipe transports para conectar routers entre workers o máquinas:\n\n```javascript\n// Cascadear salas entre servidores\nconst { pipeProducer, pipeConsumer } = await router1.pipeToRouter({\n producerId: someProducer.id,\n router: router2,\n});\n```\n\nLas pruebas muestran que mediasoup maneja ~350 streams de video simultáneos (720p, VP9 simulcast, 3 capas) en un c6i.2xlarge (8 vCPU) antes de que la CPU pegue 80%. Impresionante para forwarding, recordar que el SFU no está transcodificando.\n\n## LiveKit: La Apuesta por Plataforma\n\n[LiveKit](<https://livekit.io/>) es lo que usás cuando no se quiere construir infraestructura desde cero. Es un SFU basado en Go con:\n\n- Escalado multi-nodo con ruteo de salas respaldado por Redis\n- Grabación y egress (RTMP, HLS, S3)\n- SDKs de cliente para toda plataforma\n- Bridging SIP\n- Framework de agentes AI\n\n```yaml\n# livekit-server.yaml\nport: 7880\nrtc:\n port_range_start: 50000\n port_range_end: 60000\n use_external_ip: true\n tcp_fallback_port: 7881\n\nredis:\n address: redis-cluster.internal:6379\n\nroom:\n max_participants: 500\n empty_timeout: 300\n\nvideo:\n dynacast: true\n codecs:\n - mime: video/av1\n fmtp: \"\"\n - mime: video/vp9\n fmtp: profile-id=0\n - mime: video/h264\n fmtp: level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\n```\n\nLa característica más destacada es ` dynacast `. Cuando un suscriptor no está mirando un stream particular (ej: está fuera de pantalla o minimizado), LiveKit le dice al publisher que deje de mandar esa capa. En una sala de 100 personas donde cada participante ve 9 tiles en pantalla, esto significa que ~91% de las capas de alta calidad están pausadas. El ahorro de bandwidth es enorme.\n\n```typescript\n// LiveKit client SDK, publicar con simulcast\nconst room = new Room({\n dynacast: true,\n adaptiveStream: true,\n videoCaptureDefaults: {\n resolution: VideoPresets.h720.resolution,\n },\n publishDefaults: {\n simulcast: true,\n videoSimulcastLayers: [\n VideoPresets.h90,\n VideoPresets.h216,\n VideoPresets.h720,\n ],\n videoCodec: 'av1',\n backupCodec: { codec: 'vp9', encoding: {} },\n },\n});\n\nawait room.connect(LIVEKIT_URL, token);\n\nconst track = await createLocalVideoTrack();\nawait room.localParticipant.publishTrack(track);\n\nroom.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {\n if (track.kind === Track.Kind.Video) {\n track.attach(document.getElementById(`video-${participant.identity}`));\n }\n});\n```\n\nPara 10K participantes, la arquitectura de LiveKit usa múltiples nodos SFU con un router respaldado por Redis:\n\n```\n ┌─────────────┐\n │ Redis │\n │ (routing) │\n └──────┬──────┘\n │\n ┌──────────────┼──────────────┐\n │ │ │\n ┌───────┴───────┐ ┌───┴──────┐ ┌────┴───────┐\n │ LiveKit │ │ LiveKit │ │ LiveKit │\n │ Nodo 1 │ │ Nodo 2 │ │ Nodo 3 │\n │ (salas 1-50) │ │(salas │ │ (salas │\n │ │ │ 51-100) │ │ 101-150) │\n └───────────────┘ └──────────┘ └────────────┘\n```\n\nCuando una sala abarca múltiples nodos (ej: 2000 participantes), LiveKit automáticamente crea conexiones relay entre nodos. Pruebas con 3 nodos manejando una sala de 3000 participantes (10 speakers activos, resto viewers), la latencia end-to-end fue 180-220ms, aceptable para eventos en vivo aunque no para llamadas interactivas.\n\n## Janus: La Navaja Suiza\n\nJanus es un gateway basado en C con arquitectura de plugins. Su plugin videoroom es el SFU, pero también tiene plugins para SIP, streaming, grabación y screen sharing. Esto importa si se necesita hacer bridge entre WebRTC e infraestructura telecom legacy.\n\nLa fortaleza de Janus es la flexibilidad. ¿Necesitás grabar un stream WebRTC a disco? Plugin Record. ¿Inyectar un stream RTSP en una sala WebRTC? Plugin Streaming. ¿Bridge SIP-to-WebRTC para un call center? Plugin SIP.\n\nPero Janus escala mal comparado con mediasoup y LiveKit. Su arquitectura de proceso único (con un thread pool) pega un techo alrededor de 200 streams de video por instancia en hardware equivalente. No tiene cascading nativo, así que escalar horizontalmente requiere una capa de ruteo externa que construís vos.\n\n## VP9 vs AV1: La Batalla de Negociación de Codecs\n\nVP9 con SVC (Scalable Video Coding) viene siendo el caballo de batalla de los SFUs WebRTC porque encodea múltiples capas de calidad en un solo stream. AV1 va más lejos con soporte SVC nativo y 30-40% mejor compresión.\n\nLos benchmarks de costo de CPU de encoding (un stream 720p30, c6i.xlarge):\n\n| Codec | Modo | Uso CPU | Bitrate | Score VMAF |\n| --- | --- | --- | --- | --- |\n| VP8 | Simulcast 3 capas | 18% | 1.3 Mbps | 82.1 |\n| VP9 | SVC L3T3 | 24% | 0.9 Mbps | 84.7 |\n| H.264 | Simulcast 3 capas | 15% | 1.5 Mbps | 81.3 |\n| AV1 | SVC L3T3 | 72% | 0.6 Mbps | 86.2 |\n\nAV1 entrega la mejor calidad al bitrate más bajo pero come 3x la CPU de VP9. El approach recomendado: VP9 SVC como codec primario con H.264 simulcast como fallback para Safari. Utilizar AV1 solo para viewers, no publishers, salvo que puedas confirmar soporte de encoder hardware.\n\n## Escalando a 10K: La Arquitectura que Realmente Deployamos\n\nEsta es una arquitectura deployada para una plataforma de eventos en vivo manejando hasta 10.000 viewers con 8 speakers activos:\n\n```\n ┌──────────────┐\n │ Signaling │\n │ Server │\n │ (WebSocket) │\n └──────┬───────┘\n │\n ┌───────────┴───────────┐\n │ │\n ┌───────┴───────┐ ┌───────┴───────┐\n │ Speaker SFU │ │ Viewer SFU │\n │ (LiveKit) │◄─────►│ Cascade │\n │ 8 publishers │ relay │ (LiveKit x4) │\n │ + 50 viewers │ │ 2500 viewers │\n └───────────────┘ │ c/u │\n └────────────────┘\n```\n\nEl throttling de PLI (Picture Loss Indication) es crítico. Sin él, la pérdida de paquetes de incluso un par de viewers dispara una cascada de pedidos de keyframe que satura el uplink del speaker. Esto se volvió evidente durante un evento de 5000 personas donde el video de un speaker se congeló para todos porque su conexión de 50Mbps quedó saturada por keyframe requests disparados por PLI.\n\nCosto total de infra para capacidad de 10K: 5 instancias c6i.2xlarge (speakers + 4 clusters de viewers) + 2 servers TURN + 1 server de signaling. Alrededor de $3,200/mes en [AWS](<https://aws.com/>), más bandwidth a ~$0.09/GB de salida. Un evento de 2 horas con 10K viewers a 1Mbps promedio = ~9TB de salida = ~$810 en bandwidth.\n\nEse es el costo real. Todos los artículos de \"escalá WebRTC a millones\" convenientemente omiten los costos de bandwidth. A escala, resulta más conveniente usar HLS/DASH para viewers no interactivos y reservar WebRTC para los participantes interactivos.\n\n## La Comparación Honesta\n\n| Feature | mediasoup | LiveKit | Janus |\n| --- | --- | --- | --- |\n| Lenguaje | C++/Node.js | Go | C |\n| Arquitectura | Librería | Server/Plataforma | Server (plugins) |\n| Max streams/nodo | ~350 video | ~500 video | ~200 video |\n| Cascading | Manual (pipe) | Built-in | Manual |\n| Grabación | Implementarlo | Built-in | Plugin |\n| Bridge SIP | No | Sí | Plugin |\n| AV1 SVC | Sí | Sí | Parcial |\n| SDKs cliente | Comunidad | Oficiales (todos) | Comunidad |\n| Curva aprendizaje | Empinada | Moderada | Moderada |\n\nPara proyectos nuevos: LiveKit para la mayoría de los proyectos, mediasoup solo si se necesita control absoluto del pipeline de media o se tiene requerimientos inusuales que las abstracciones de LiveKit no acomodan. Janus para integración con telecom legacy. Los tres son production-grade; la diferencia es cuánto código de infraestructura se quiere escribir vs. usar.\n\n## Preguntas frecuentes\n\n### ¿Qué servidor de media WebRTC debería usar?\n\nmediasoup si se necesita máximo control y se está cómodo con Node.js/C++, es una librería, no un servidor. LiveKit si se quiere una plataforma con todo incluido con grabación, egress y SDKs. Janus si se necesita extensibilidad via plugins (gateway SIP, streaming, etc.).\n\n### ¿WebRTC realmente puede manejar 10.000 participantes?\n\nNo en una sola instancia SFU. Necesitás SFUs en cascada con forwarding selectivo. El ruteo multi-nodo de LiveKit lo maneja nativamente. Con mediasoup, lo armars usted mismo usando pipe transports entre workers. 10K viewers con 5-10 speakers activos es alcanzable.\n\n### ¿Debería usar VP9 o AV1 para WebRTC?\n\nVP9 con SVC si se necesita soporte amplio de navegadores hoy. AV1 si tu audiencia es mayormente Chrome/Edge y se puede tolerar encoding más lento. AV1 entrega 30-40% mejor compresión que VP9 a la misma calidad, pero el costo de CPU del encoding es 3-5x mayor.\n\n## Tips\n\n1. **Tip:** Nunca uses arquitectura MCU para llamadas de más de 50 participantes. El costo de CPU del transcoding escala linealmente con participantes, un SFU reenviando paquetes usa ` O(n) ` bandwidth pero ` O(1) ` CPU por stream, mientras MCU usa ` O(n) ` CPU.\n2. **Tip:** Habilitar simulcast con 3 capas espaciales en mediasoup: ` encodings: [{maxBitrate: 100000}, {maxBitrate: 300000}, {maxBitrate: 900000}] `. El SFU selecciona la capa apropiada por suscriptor según su ancho de banda disponible.\n3. **Tip:** Para AV1/VP9 SVC en LiveKit, configurar ` dynacast: true ` en el room, pausa capas que ningún suscriptor está consumiendo, cortando bandwidth 40-60% en salas grandes donde la mayoría de los tiles son chicos.\n4. **Tip:** Utilizar TURN/TLS en puerto 443 como fallback, no como primario. Ejecutar ` coturn ` con ` --no-multicast-peers ` y limitá las allocations para prevenir que los servers TURN se conviertan en amplificadores DDoS.\n\n- webrtc\n- mediasoup\n- livekit\n- janus\n- sfu\n- mcu\n- simulcast\n- av1\n- vp9\n- tiempo-real\n- video-streaming\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Escalando WebRTC a 10.000 Participantes: Comparación de mediasoup, LiveKit y Janus","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#title"},{"level":2,"text":"SFU vs MCU: La Decisión de Arquitectura que Determina Todo","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#sfu-vs-mcu-la-decisin-de-arquitectura-que-determina-todo"},{"level":2,"text":"mediasoup: El Bisturí del Cirujano","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#mediasoup-el-bistur-del-cirujano"},{"level":2,"text":"LiveKit: La Apuesta por Plataforma","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#livekit-la-apuesta-por-plataforma"},{"level":2,"text":"Janus: La Navaja Suiza","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#janus-la-navaja-suiza"},{"level":2,"text":"VP9 vs AV1: La Batalla de Negociación de Codecs","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#vp9-vs-av1-la-batalla-de-negociacin-de-codecs"},{"level":2,"text":"Escalando a 10K: La Arquitectura que Realmente Deployamos","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#escalando-a-10k-la-arquitectura-que-realmente-deployamos"},{"level":2,"text":"La Comparación Honesta","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#la-comparacin-honesta"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#questions"},{"level":3,"text":"¿Qué servidor de media WebRTC debería usar?","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#question-1"},{"level":3,"text":"¿WebRTC realmente puede manejar 10.000 participantes?","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#question-2"},{"level":3,"text":"¿Debería usar VP9 o AV1 para WebRTC?","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#article-tips"}],"sections":[{"heading":"Escalando WebRTC a 10.000 Participantes: Comparación de mediasoup, LiveKit y Janus","level":1,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit","content":"Real-time"},{"heading":"Escalando WebRTC a 10.000 Participantes: Comparación de mediasoup, LiveKit y Janus","level":1,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#title","content":"2026-02-079 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/webrtc-media-servers-mediasoup-livekit>)"},{"heading":"SFU vs MCU: La Decisión de Arquitectura que Determina Todo","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#sfu-vs-mcu-la-decisin-de-arquitectura-que-determina-todo","content":"Antes de comparar servers, tienen que entender la elección de arquitectura fundamental porque condiciona todo lo demás.\n\n**MCU (Multipoint Conferencing Unit):** El servidor recibe todos los streams de los participantes, los decodifica, los compone en un único stream mixeado, re-encodea, y manda un stream a cada participante. Cada participante recibe y decodifica un solo stream sin importar cuánta gente hay en la llamada.\n\n**SFU (Selective Forwarding Unit):** El servidor recibe el stream de cada participante y lo reenvía a los demás sin transcodificar. Cada participante recibe N-1 streams (donde N es el número de participantes) y los decodifica localmente.\n\n```\nArquitectura MCU:\n Participante A ──video──▶ ┌─────────┐ ──mixed──▶ Participante A\n Participante B ──video──▶ │ MCU │ ──mixed──▶ Participante B\n Participante C ──video──▶ │ Server │ ──mixed──▶ Participante C\n Participante D ──video──▶ └─────────┘ ──mixed──▶ Participante D\n CPU del server: O(n) decode + composite + encode\n Bandwidth del server: O(n) in + O(n) out\n\nArquitectura SFU:\n Participante A ──video──▶ ┌─────────┐ ──A,B,C──▶ Participante D\n Participante B ──video──▶ │ SFU │ ──A,B,D──▶ Participante C\n Participante C ──video──▶ │ Server │ ──A,C,D──▶ Participante B\n Participante D ──video──▶ └─────────┘ ──B,C,D──▶ Participante A\n CPU del server: O(1) por stream (solo forwarding)\n Bandwidth del server: O(n) in + O(n²) out (sin simulcast)\n```\n\nUn punto de partida común es un approach MCU a escala pequeña (Janus con el plugin videoroom en modo MCU) para un producto de sala de directorio de 20 personas. Funciona bien a esa escala. Pero cuando los requerimientos cambian a 500+ participantes, MCU se vuelve impracticable inmediatamente, el costo de CPU de decodificar 500 streams de video, componerlos y re-encodear es astronómico. El cambio a SFU se vuelve necesario.\n\nEl problema de bandwidth con SFUs (O(n²) de salida) se resuelve con simulcast y SVC. Más sobre eso abajo."},{"heading":"mediasoup: El Bisturí del Cirujano","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#mediasoup-el-bistur-del-cirujano","content":"[mediasoup](<https://mediasoup.org/>) no es un servidor, es una librería C++ con un wrapper Node.js. Construís tu propio servidor alrededor. Esto te da control total y responsabilidad total.\n\nAsí se ve un setup básico de mediasoup:\n\n```javascript\nconst mediasoup = require('mediasoup');\n\n// Crear un worker (uno por core de CPU)\nconst worker = await mediasoup.createWorker({\n logLevel: 'warn',\n rtcMinPort: 40000,\n rtcMaxPort: 49999,\n});\n\n// Crear un router (uno por sala, define codecs)\nconst router = await worker.createRouter({\n mediaCodecs: [\n {\n kind: 'audio',\n mimeType: 'audio/opus',\n clockRate: 48000,\n channels: 2,\n },\n {\n kind: 'video',\n mimeType: 'video/VP9',\n clockRate: 90000,\n parameters: {\n 'profile-id': 2,\n },\n },\n {\n kind: 'video',\n mimeType: 'video/H264',\n clockRate: 90000,\n parameters: {\n 'packetization-mode': 1,\n 'profile-level-id': '42e01f',\n 'level-asymmetry-allowed': 1,\n },\n },\n ],\n});\n\n// Crear WebRTC transport para un producer (publisher)\nconst transport = await router.createWebRtcTransport({\n listenIps: [{ ip: '0.0.0.0', announcedIp: '203.0.113.1' }],\n enableUdp: true,\n enableTcp: true,\n preferUdp: true,\n initialAvailableOutgoingBitrate: 1000000,\n});\n```\n\nLa configuración crítica para escalar es simulcast. Sin esto, cada suscriptor recibe el stream en resolución completa incluso si lo está renderizando como thumbnail de 160x90:\n\n```javascript\n// Lado del producer: enviar 3 capas simulcast\nconst producer = await transport.produce({\n kind: 'video',\n rtpParameters: {\n codecs: [/* ... */],\n encodings: [\n { rid: 'r0', maxBitrate: 100000, scaleResolutionDownBy: 4 },\n { rid: 'r1', maxBitrate: 300000, scaleResolutionDownBy: 2 },\n { rid: 'r2', maxBitrate: 900000, scaleResolutionDownBy: 1 },\n ],\n },\n});\n\n// Lado del suscriptor: pedir capa específica\nconst consumer = await transport.consume({\n producerId: producer.id,\n rtpCapabilities: device.rtpCapabilities,\n preferredLayers: { spatialLayer: 1, temporalLayer: 2 },\n});\n\n// Cambiar capas dinámicamente según layout de UI\nasync function onTileResize(consumerId, width) {\n const consumer = consumers.get(consumerId);\n if (width < 200) {\n await consumer.setPreferredLayers({ spatialLayer: 0 });\n } else if (width < 600) {\n await consumer.setPreferredLayers({ spatialLayer: 1 });\n } else {\n await consumer.setPreferredLayers({ spatialLayer: 2 });\n }\n}\n```\n\nPara escalar más allá de un solo servidor, mediasoup usa pipe transports para conectar routers entre workers o máquinas:\n\n```javascript\n// Cascadear salas entre servidores\nconst { pipeProducer, pipeConsumer } = await router1.pipeToRouter({\n producerId: someProducer.id,\n router: router2,\n});\n```\n\nLas pruebas muestran que mediasoup maneja ~350 streams de video simultáneos (720p, VP9 simulcast, 3 capas) en un c6i.2xlarge (8 vCPU) antes de que la CPU pegue 80%. Impresionante para forwarding, recordar que el SFU no está transcodificando."},{"heading":"LiveKit: La Apuesta por Plataforma","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#livekit-la-apuesta-por-plataforma","content":"[LiveKit](<https://livekit.io/>) es lo que usás cuando no se quiere construir infraestructura desde cero. Es un SFU basado en Go con:\n\n- Escalado multi-nodo con ruteo de salas respaldado por Redis\n- Grabación y egress (RTMP, HLS, S3)\n- SDKs de cliente para toda plataforma\n- Bridging SIP\n- Framework de agentes AI\n\n```yaml\n# livekit-server.yaml\nport: 7880\nrtc:\n port_range_start: 50000\n port_range_end: 60000\n use_external_ip: true\n tcp_fallback_port: 7881\n\nredis:\n address: redis-cluster.internal:6379\n\nroom:\n max_participants: 500\n empty_timeout: 300\n\nvideo:\n dynacast: true\n codecs:\n - mime: video/av1\n fmtp: \"\"\n - mime: video/vp9\n fmtp: profile-id=0\n - mime: video/h264\n fmtp: level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\n```\n\nLa característica más destacada es ` dynacast `. Cuando un suscriptor no está mirando un stream particular (ej: está fuera de pantalla o minimizado), LiveKit le dice al publisher que deje de mandar esa capa. En una sala de 100 personas donde cada participante ve 9 tiles en pantalla, esto significa que ~91% de las capas de alta calidad están pausadas. El ahorro de bandwidth es enorme.\n\n```typescript\n// LiveKit client SDK, publicar con simulcast\nconst room = new Room({\n dynacast: true,\n adaptiveStream: true,\n videoCaptureDefaults: {\n resolution: VideoPresets.h720.resolution,\n },\n publishDefaults: {\n simulcast: true,\n videoSimulcastLayers: [\n VideoPresets.h90,\n VideoPresets.h216,\n VideoPresets.h720,\n ],\n videoCodec: 'av1',\n backupCodec: { codec: 'vp9', encoding: {} },\n },\n});\n\nawait room.connect(LIVEKIT_URL, token);\n\nconst track = await createLocalVideoTrack();\nawait room.localParticipant.publishTrack(track);\n\nroom.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {\n if (track.kind === Track.Kind.Video) {\n track.attach(document.getElementById(`video-${participant.identity}`));\n }\n});\n```\n\nPara 10K participantes, la arquitectura de LiveKit usa múltiples nodos SFU con un router respaldado por Redis:\n\n```\n ┌─────────────┐\n │ Redis │\n │ (routing) │\n └──────┬──────┘\n │\n ┌──────────────┼──────────────┐\n │ │ │\n ┌───────┴───────┐ ┌───┴──────┐ ┌────┴───────┐\n │ LiveKit │ │ LiveKit │ │ LiveKit │\n │ Nodo 1 │ │ Nodo 2 │ │ Nodo 3 │\n │ (salas 1-50) │ │(salas │ │ (salas │\n │ │ │ 51-100) │ │ 101-150) │\n └───────────────┘ └──────────┘ └────────────┘\n```\n\nCuando una sala abarca múltiples nodos (ej: 2000 participantes), LiveKit automáticamente crea conexiones relay entre nodos. Pruebas con 3 nodos manejando una sala de 3000 participantes (10 speakers activos, resto viewers), la latencia end-to-end fue 180-220ms, aceptable para eventos en vivo aunque no para llamadas interactivas."},{"heading":"Janus: La Navaja Suiza","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#janus-la-navaja-suiza","content":"Janus es un gateway basado en C con arquitectura de plugins. Su plugin videoroom es el SFU, pero también tiene plugins para SIP, streaming, grabación y screen sharing. Esto importa si se necesita hacer bridge entre WebRTC e infraestructura telecom legacy.\n\nLa fortaleza de Janus es la flexibilidad. ¿Necesitás grabar un stream WebRTC a disco? Plugin Record. ¿Inyectar un stream RTSP en una sala WebRTC? Plugin Streaming. ¿Bridge SIP-to-WebRTC para un call center? Plugin SIP.\n\nPero Janus escala mal comparado con mediasoup y LiveKit. Su arquitectura de proceso único (con un thread pool) pega un techo alrededor de 200 streams de video por instancia en hardware equivalente. No tiene cascading nativo, así que escalar horizontalmente requiere una capa de ruteo externa que construís vos."},{"heading":"VP9 vs AV1: La Batalla de Negociación de Codecs","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#vp9-vs-av1-la-batalla-de-negociacin-de-codecs","content":"VP9 con SVC (Scalable Video Coding) viene siendo el caballo de batalla de los SFUs WebRTC porque encodea múltiples capas de calidad en un solo stream. AV1 va más lejos con soporte SVC nativo y 30-40% mejor compresión.\n\nLos benchmarks de costo de CPU de encoding (un stream 720p30, c6i.xlarge):\n\n| Codec | Modo | Uso CPU | Bitrate | Score VMAF |\n| --- | --- | --- | --- | --- |\n| VP8 | Simulcast 3 capas | 18% | 1.3 Mbps | 82.1 |\n| VP9 | SVC L3T3 | 24% | 0.9 Mbps | 84.7 |\n| H.264 | Simulcast 3 capas | 15% | 1.5 Mbps | 81.3 |\n| AV1 | SVC L3T3 | 72% | 0.6 Mbps | 86.2 |\n\nAV1 entrega la mejor calidad al bitrate más bajo pero come 3x la CPU de VP9. El approach recomendado: VP9 SVC como codec primario con H.264 simulcast como fallback para Safari. Utilizar AV1 solo para viewers, no publishers, salvo que puedas confirmar soporte de encoder hardware."},{"heading":"Escalando a 10K: La Arquitectura que Realmente Deployamos","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#escalando-a-10k-la-arquitectura-que-realmente-deployamos","content":"Esta es una arquitectura deployada para una plataforma de eventos en vivo manejando hasta 10.000 viewers con 8 speakers activos:\n\n```\n ┌──────────────┐\n │ Signaling │\n │ Server │\n │ (WebSocket) │\n └──────┬───────┘\n │\n ┌───────────┴───────────┐\n │ │\n ┌───────┴───────┐ ┌───────┴───────┐\n │ Speaker SFU │ │ Viewer SFU │\n │ (LiveKit) │◄─────►│ Cascade │\n │ 8 publishers │ relay │ (LiveKit x4) │\n │ + 50 viewers │ │ 2500 viewers │\n └───────────────┘ │ c/u │\n └────────────────┘\n```\n\nEl throttling de PLI (Picture Loss Indication) es crítico. Sin él, la pérdida de paquetes de incluso un par de viewers dispara una cascada de pedidos de keyframe que satura el uplink del speaker. Esto se volvió evidente durante un evento de 5000 personas donde el video de un speaker se congeló para todos porque su conexión de 50Mbps quedó saturada por keyframe requests disparados por PLI.\n\nCosto total de infra para capacidad de 10K: 5 instancias c6i.2xlarge (speakers + 4 clusters de viewers) + 2 servers TURN + 1 server de signaling. Alrededor de $3,200/mes en [AWS](<https://aws.com/>), más bandwidth a ~$0.09/GB de salida. Un evento de 2 horas con 10K viewers a 1Mbps promedio = ~9TB de salida = ~$810 en bandwidth.\n\nEse es el costo real. Todos los artículos de \"escalá WebRTC a millones\" convenientemente omiten los costos de bandwidth. A escala, resulta más conveniente usar HLS/DASH para viewers no interactivos y reservar WebRTC para los participantes interactivos."},{"heading":"La Comparación Honesta","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#la-comparacin-honesta","content":"| Feature | mediasoup | LiveKit | Janus |\n| --- | --- | --- | --- |\n| Lenguaje | C++/Node.js | Go | C |\n| Arquitectura | Librería | Server/Plataforma | Server (plugins) |\n| Max streams/nodo | ~350 video | ~500 video | ~200 video |\n| Cascading | Manual (pipe) | Built-in | Manual |\n| Grabación | Implementarlo | Built-in | Plugin |\n| Bridge SIP | No | Sí | Plugin |\n| AV1 SVC | Sí | Sí | Parcial |\n| SDKs cliente | Comunidad | Oficiales (todos) | Comunidad |\n| Curva aprendizaje | Empinada | Moderada | Moderada |\n\nPara proyectos nuevos: LiveKit para la mayoría de los proyectos, mediasoup solo si se necesita control absoluto del pipeline de media o se tiene requerimientos inusuales que las abstracciones de LiveKit no acomodan. Janus para integración con telecom legacy. Los tres son production-grade; la diferencia es cuánto código de infraestructura se quiere escribir vs. usar."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#questions","content":""},{"heading":"¿Qué servidor de media WebRTC debería usar?","level":3,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#question-1","content":"mediasoup si se necesita máximo control y se está cómodo con Node.js/C++, es una librería, no un servidor. LiveKit si se quiere una plataforma con todo incluido con grabación, egress y SDKs. Janus si se necesita extensibilidad via plugins (gateway SIP, streaming, etc.)."},{"heading":"¿WebRTC realmente puede manejar 10.000 participantes?","level":3,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#question-2","content":"No en una sola instancia SFU. Necesitás SFUs en cascada con forwarding selectivo. El ruteo multi-nodo de LiveKit lo maneja nativamente. Con mediasoup, lo armars usted mismo usando pipe transports entre workers. 10K viewers con 5-10 speakers activos es alcanzable."},{"heading":"¿Debería usar VP9 o AV1 para WebRTC?","level":3,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#question-3","content":"VP9 con SVC si se necesita soporte amplio de navegadores hoy. AV1 si tu audiencia es mayormente Chrome/Edge y se puede tolerar encoding más lento. AV1 entrega 30-40% mejor compresión que VP9 a la misma calidad, pero el costo de CPU del encoding es 3-5x mayor."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#article-tips","content":"1. **Tip:** Nunca uses arquitectura MCU para llamadas de más de 50 participantes. El costo de CPU del transcoding escala linealmente con participantes, un SFU reenviando paquetes usa ` O(n) ` bandwidth pero ` O(1) ` CPU por stream, mientras MCU usa ` O(n) ` CPU.\n2. **Tip:** Habilitar simulcast con 3 capas espaciales en mediasoup: ` encodings: [{maxBitrate: 100000}, {maxBitrate: 300000}, {maxBitrate: 900000}] `. El SFU selecciona la capa apropiada por suscriptor según su ancho de banda disponible.\n3. **Tip:** Para AV1/VP9 SVC en LiveKit, configurar ` dynacast: true ` en el room, pausa capas que ningún suscriptor está consumiendo, cortando bandwidth 40-60% en salas grandes donde la mayoría de los tiles son chicos.\n4. **Tip:** Utilizar TURN/TLS en puerto 443 como fallback, no como primario. Ejecutar ` coturn ` con ` --no-multicast-peers ` y limitá las allocations para prevenir que los servers TURN se conviertan en amplificadores DDoS.\n\n- webrtc\n- mediasoup\n- livekit\n- janus\n- sfu\n- mcu\n- simulcast\n- av1\n- vp9\n- tiempo-real\n- video-streaming\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/webrtc-media-servers-mediasoup-livekit"},{"text":"mediasoup","url":"https://mediasoup.org/"},{"text":"LiveKit","url":"https://livekit.io/"},{"text":"AWS","url":"https://aws.com/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit"},"headline":"Escalando WebRTC a 10.000 Participantes: Comparación de mediasoup, LiveKit y Janus","description":"Una comparación probada en producción de servidores de media WebRTC. Arquitectura SFU vs MCU, configuración de simulcast, negociación de codecs VP9/AV1, y los patrones de escalado que realmente funcionan cuando se necesita 10.000 participantes concurrentes.","datePublished":"2026-02-07","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Real-time","keywords":"webrtc, mediasoup, livekit, janus, sfu, mcu, simulcast, av1, vp9, tiempo-real, video-streaming","wordCount":1640,"timeRequired":"PT9M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/webrtc-media-servers-mediasoup-livekit"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Escalando WebRTC a 10.000 Participantes: Comparación de mediasoup, LiveKit y Janus","item":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Qué servidor de media WebRTC debería usar?","acceptedAnswer":{"@type":"Answer","text":"mediasoup si se necesita máximo control y se está cómodo con Node.js/C++, es una librería, no un servidor. LiveKit si se quiere una plataforma con todo incluido con grabación, egress y SDKs. Janus si se necesita extensibilidad via plugins (gateway SIP, streaming, etc.)."}},{"@type":"Question","name":"¿WebRTC realmente puede manejar 10.000 participantes?","acceptedAnswer":{"@type":"Answer","text":"No en una sola instancia SFU. Necesitás SFUs en cascada con forwarding selectivo. El ruteo multi-nodo de LiveKit lo maneja nativamente. Con mediasoup, lo armars usted mismo usando pipe transports entre workers. 10K viewers con 5-10 speakers activos es alcanzable."}},{"@type":"Question","name":"¿Debería usar VP9 o AV1 para WebRTC?","acceptedAnswer":{"@type":"Answer","text":"VP9 con SVC si se necesita soporte amplio de navegadores hoy. AV1 si tu audiencia es mayormente Chrome/Edge y se puede tolerar encoding más lento. AV1 entrega 30-40% mejor compresión que VP9 a la misma calidad, pero el costo de CPU del encoding es 3-5x mayor."}}]}]}],"contentHash":"92006fd8d67b28b071136aa05905f0833180a4efaa59523879d50c204c7d883d","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/webrtc-media-servers-mediasoup-livekit","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":73326,"contentBytes":15207}}
