En algún momento, en uno de mis artículos antiguos y ya olvidados, escribí sobre lo fácil y sencillo que es transmitir video desde un canvas a través de websockets. En ese artículo, hice un resumen de cómo capturar video de una cámara y audio de un micrófono usando , cómo codificar el flujo resultante y enviarlo a través de websockets a un servidor. Sin embargo, en la realidad no se hace así; para las transmisiones se utiliza software especializado que debe ser instalado y configurado: por ejemplo, se podría usar , o se puede recurrir a WebRTC, que funciona directamente sin necesidad de instalar ningún tipo de plugins como Flash Player, el cual será eliminado del navegador Chromium en diciembre.
Hoy hablaremos sobre WebRTC.
La Comunicación en Tiempo Real en la Web (WebRTC) no es un único protocolo, sino un conjunto completo de estándares, protocolos y APIs de JavaScript que en conjunto permiten comunicación de video y audio entre pares en tiempo real, así como la transmisión de cualquier tipo de datos binarios. Generalmente, los pares son los navegadores, pero también puede ser una aplicación móvil, por ejemplo. Para organizar la comunicación p2p entre clientes, se requiere que el navegador soporte varios tipos de codificación de video y audio, soporte para múltiples protocolos de red, y asegure la interacción del hardware con el navegador (a través de capas del sistema operativo): cámaras web, tarjetas de sonido. Todo este conjunto de tecnologías está oculto detrás de una abstracción en la API de JavaScript para la comodidad del desarrollador.
Todo se reduce en última instancia a tres APIs:
— que discutimos la última vez, hoy escribiré un poco más sobre ello. Sirve para obtener flujos de video/audio del 'hardware'
— facilita la comunicación entre dos clientes (p2p)
— se utiliza para la transmisión de datos arbitrarios entre dos clientes
Preparación de flujos de audio y video para la transmisión
Todo comienza con la "captura" de los flujos de medios de la cámara web y del micrófono. Los flujos en bruto, por supuesto, no son adecuados para organizar una teleconferencia; cada flujo necesita ser procesado: mejorar la calidad, sincronizar el audio con el video, colocar marcas de sincronización en el flujo de video y garantizar que el bitrate se ajuste a la variabilidad del ancho de banda. El navegador se encarga de todo esto, el desarrollador ni siquiera necesita preocuparse por proporcionar la codificación de los flujos de medios. Dentro de un navegador moderno ya existen capas de software para captura y mejora de calidad (eliminar eco y ruido del sonido, mejorar la imagen), y codificación de video y audio. El esquema de capas se muestra en la Fig. 1:
Fig. 1. Capas de procesamiento de audio y video en el navegador
Todo el procesamiento ocurre directamente en el navegador, no se requieren plugins adicionales. Sin embargo, en 2020, la situación aún no es alentadora. Existen navegadores que todavía no son totalmente compatibles , puedes seguir el enlace y al final ver la tabla de compatibilidad. En particular, IE decepciona nuevamente.
Con los flujos obtenidos se pueden hacer cosas muy interesantes: se pueden clonar, cambiar la resolución de video, manipular la calidad del audio, se puede 'adjuntar' el flujo de Media Stream a un
— los chicos se dedican a CV en tiempo real usando Javascript. Tienen un completo de diversas bibliotecas js para trabajar con flujos de video en canvas: detección de rostros, objetos, aplicación de filtros (máscaras, como en Instagram) y más. Un gran ejemplo de cómo se puede procesar video en tiempo real directamente en el navegador sin plugins adicionales.
— documentación de la API para capturar flujos de video desde canvas. Ya es compatible con Chrome, Opera y Firefox.
RTCPeerConnection
Y así llegamos a la pregunta de cómo transmitir video a otro usuario. En primer plano aparece Si hablamos brevemente, en esta etapa prácticamente necesitarás crear un objeto RTCPeerConnection:
const peerConnection = new RTCPeerConnection({
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
});Una de las opciones que indicamos es iceServers, que es un servidor que ayuda a establecer una conexión entre dos navegadores que se encuentran detrás de un NAT. Es decir, aquí se resuelve el problema: ¿cómo saber la IP del interlocutor si está detrás del NAT de su proveedor? El protocolo ICE viene al rescate; en realidad, ICE no está relacionado con WebRTC, pero de eso hablaremos más adelante.
Anteriormente, obtuvimos flujos de Usermedia:
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
// Flujos de Usermedia, generalmente esto es video y audio
const tracks = stream.getTracks();
for (const track of tracks) {
// cada pista se une a peerConnection
peerConnection.addTrack(track);
}
}).catch(console.error);A continuación, en peerConnection se activa el evento onnegotiationneeded, en el manejador de este debemos crear una oferta (en términos de SDP - Session Description Protocol) y asignarla a peerConnection a través del método setLocalDescription. Sobre SDP - qué es y sobre los formatos de oferta y respuesta - hablaremos más adelante.
Después de asignar LocalDescription a peerConnection, el navegador "recoge" a los candidatos ice, es decir, encuentra diferentes caminos para la comunicación a través del NAT. Se activa el evento onicegatheringstatechange. En el manejador onicegatheringstatechange permitimos la conexión con el servidor de señalización webrtc para intercambiar la descripción de la sesión entre los pares:
peerConnection.oniceconnectionstatechange = (event) => {
console.log('Connection state: ', peerConnection.iceConnectionState);
if (peerConnection.iceConnectionState === 'connected') {
// Можем активировать кнопку Start broadcast
setBroadcasting(true);
setBroadcastingBtnActive(true);
}
};
// Событие срабатывает сразу, как только добавился медаиапоток в peerConnection
peerConnection.onnegotiationneeded = (event) => {
// Создаем и назначаем SDP offer
peerConnection.createOffer().
then((offer) => peerConnection.setLocalDescription(offer)).
catch(console.error);
};
// Событие срабатывает каждый раз, как появляется ICE кандидат
peerConnection.onicegatheringstatechange = (ev) => {
let connection = ev.target;
// Now we can activate broadcast button
if (connection.iceGatheringState === 'complete') {
let delay = 50;
let tries = 0;
let maxTries = 3;
let timerId = setTimeout(function allowStreaming() {
if (isOnline) {
setBroadcastingBtnActive(true);
return;
}
if (tries < maxTries) {
tries += 1;
delay *= 2;
timerId = setTimeout(allowStreaming, delay);
} else {
// TODO: show user notification
console.error("Can't connect to server");
alert("Can't connect to server");
}
}, delay);
}
};El servidor de señalización webrtc es necesario para facilitar el intercambio de la descripción de la sesión entre dos pares; puede ser un websocket simple o un servidor xhr en cualquier lenguaje de programación. Su tarea es simple: recibir la descripción de la sesión de un par y pasarla al otro.
Después de intercambiar las descripciones de la sesión, ambas partes están listas para transmitir y recibir flujos de video; en el lado que recibe el flujo de video se activa el evento ontrack en peerConnection, en cuyo manejador se pueden asignar las pistas obtenidas al
Enlaces y literatura:
— documentación
— implementación de protocolos WebRTC en go
— libro de los creadores de pion
— libro High Performance Browser Networking. Detalla las cuestiones relacionadas con el rendimiento de aplicaciones web. Al final se describe WebRTC. El libro, aunque es un poco antiguo (2013), no pierde su relevancia.
En la siguiente parte, quiero ofrecer un poco más de teoría y en la práctica desglosar el método y el procesamiento de stream de video en el servidor a través de pion, la transcodificación a HLS mediante ffmpeg para la posterior transmisión a los espectadores en el navegador.
Para los impacientes: (esto es solo un experimento).
Fuente: habr.com
