Qualche tempo fa in uno dei vecchi e già abbandonati articoli parlavo di quanto sia facile e spontaneo trasmettere video da un canvas tramite websockets. In quell'articolo ho accennato superficialmente a come catturare il video dalla camera e l'audio dal microfono tramite , come codificare il flusso ottenuto e inviarlo tramite websockets al server. Tuttavia, in realtà non si fa così, per le trasmissioni si utilizzano software speciali da installare e configurare: ad esempio, potrebbe essere , oppure si utilizza WebRTC, che funziona direttamente out-of-the-box, ossia non richiede l'installazione di plugin come flash player, che sarà rimosso da Chromium a dicembre.
Oggi parleremo di WebRTC.
Web Real-Time Communication (WebRTC) non è un singolo protocollo, ma un'intera collezione di standard, protocolli e JavaScript API che insieme forniscono comunicazioni video-audio peer-to-peer in tempo reale e possono essere utilizzati per la trasmissione di qualsiasi tipo di dati binari. Di solito i peer sono i browser, ma può essere anche un'applicazione mobile, per esempio. Per organizzare la comunicazione p2p tra i client è necessaria la compatibilità del browser con diversi formati di codifica video e audio, supporto a numerosi protocolli di rete, e l'interazione della parte hardware con il browser (tramite i layer del SO): webcam, schede audio. Tutta questa mescolanza di tecnologie è nascosta dietro l'astrazione dell'API JavaScript per la comodità dello sviluppatore.
In definitiva si riduce a tre API:
— analizzate l'ultima volta, oggi scriverò ancora un po' su di essa. Serve per ricevere flussi video/audio dall'hardware
— consente comunicazioni tra due client (p2p)
— serve per la trasmissione di dati arbitrari tra due client
Preparazione dei flussi audio e video per la trasmissione
Tutto inizia con il "catturare" i flussi video della webcam e del microfono. I flussi grezzi non sono adatti per l'organizzazione di videoconferenze, ogni flusso deve essere elaborato: migliorare la qualità, sincronizzare l'audio con il video, posizionare i marker di sincronizzazione nel flusso video, garantire che il bitrate si adatti continuamente alla larghezza di banda del canale. Il browser si occupa di tutto questo, il programmatore non deve nemmeno preoccuparsi di garantire la codifica dei flussi multimediali. All'interno di un moderno browser ci sono già strati software per la cattura, il miglioramento della qualità (eliminare l'eco e il rumore dal suono, migliorare l'immagine), la codifica video e audio. Lo schema degli strati è mostrato nella figura 1:
Fig. 1. Strati di elaborazione audio e video nel browser
Tutta l'elaborazione avviene direttamente nel browser, non sono necessari plugin aggiuntivi. Tuttavia, nel 2020 non è ancora tutto roseo. Alcuni browser non supportano ancora completamente , puoi cliccare sul link e vedere in fondo la tabella di compatibilità. In particolare, IE delude ancora una volta.
Con i flussi ottenuti si possono fare cose molto interessanti: si può clonare, cambiare la risoluzione del video, manipolare la qualità dell'audio; si può prendere e "attaccare" il flusso Media Stream a un tag <video> e osservare sé stessi sulla pagina html. E si può anche disegnare il flusso su canvas, e applicare WebGL o CSS3, e sovrapporre vari filtri al video, catturare il video elaborato dal canvas e poi inviarlo su rete al server, transcodificarlo e pubblicarlo a chiunque lo desideri (ciao bigo live, twitch e altri). Qui non esplorerò come fare tali cose, fornirò un paio di esempi trovati in rete:
— ragazzi che si occupano di CV in tempo reale su Javascript. Hanno un vero e proprio di varie librerie js per lavorare con flussi video su canvas: rilevamento di volti, oggetti, applicazione di filtri (maschere, come su Instagram) e altro. Ottimo esempio di come sia possibile elaborare video in tempo reale direttamente nel browser senza plugin aggiuntivi.
— documentazione API per la cattura dei flussi video da canvas. Già supportato in Chrome, Opera e Firefox
RTCPeerConnection
Siamo quindi giunti al punto cruciale: come trasferire effettivamente il video a un altro utente? All'orizzonte si presenta . Parlando brevemente, in questa fase è necessario creare un oggetto RTCPeerConnection:
const peerConnection = new RTCPeerConnection({
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
});Una delle opzioni è iceServers: un server che aiuta a stabilire una connessione tra due browser che si trovano dietro a un NAT. Quindi qui si risolve il problema: come scoprire l'ip dell'interlocutore se si trova dietro il NAT del suo provider? Interviene il protocollo ICE, che in realtà non è specificamente legato a WebRTC, ma ne parleremo più avanti.
In precedenza abbiamo ottenuto dei flussi Usermedia:
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
// I flussi Usermedia, di solito sono video e audio
const tracks = stream.getTracks();
for (const track of tracks) {
// ciascun tracciato viene aggiunto a peerConnection
peerConnection.addTrack(track);
}
}).catch(console.error);Successivamente, sull'oggetto peerConnection si attiva l'evento onnegotiationneeded, nel gestore del quale dobbiamo creare un'offerta (in termini di SDP — Session Description Protocol) e impostarla in peerConnection tramite il metodo setLocalDescription. Parleremo di SDP: cos'è e dei formati dell'offerta e della risposta in seguito.
Dopo aver assegnato LocalDescription a peerConnection, il browser 'raccoglie' i candidati ice, cioè trova vari percorsi per la comunicazione attraverso il NAT. Si attiva l'evento onicegatheringstatechange. Nel gestore onicegatheringstatechange consentiamo la connessione al server di segnalazione webrtc per scambiare la Session Description tra i peer:
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);
}
};Il server di segnalazione webrtc è un server necessario per facilitare lo scambio della session description tra due peer; può essere un semplice server websocket o xhr in qualsiasi linguaggio di programmazione. Il suo compito è semplice: ricevere la session description da un peer e passarla all'altro.
Dopo lo scambio delle Session descriptions, entrambe le parti sono pronte a trasmettere e ricevere flussi video; sul lato che riceve il flusso video si attiva l'evento ontrack su peerConnection, nel gestore del quale i tracciati ricevuti possono essere assegnati a
Riferimenti e letteratura:
— documentazione
— implementazione dei protocolli WebRTC in go
— un libretto dagli autori di pion
— libro High Performance Browser Networking. Vengono esaminati in dettaglio i problemi di alta prestazione delle applicazioni web. Alla fine si parla di WebRTC. Il libro è infatti vecchio (2013), ma non ha perso la sua attualità.
Nella prossima parte voglio fornire un'altra dose di teoria e analizzare praticamente la ricezione e l'elaborazione del flusso video sul server con pivot, la transcoding in HLS attraverso ffmpeg per la successiva trasmissione agli spettatori nel browser.
Per i più impazienti: (è solo un esperimento).
Fonte: habr.com
