C'era una volta in dei vecchi e già abbandonati articoli, ho scritto di come sia facile e naturale trasmettere video da un canvas tramite websockets. In quell'articolo parlavo superficialmente di come catturare il video dalla telecamera e l'audio dal microfono attraverso , di come codificare il flusso ottenuto e inviarlo tramite websockets al server. Tuttavia, nella realtà, non si fa così; per le trasmissioni si utilizzano software speciali che devono essere installati e configurati: a memoria, potrebbe essere , oppure si utilizza WebRTC, che funziona direttamente senza bisogno di installare plugin come Flash Player, il quale verrà rimosso da Chromium a dicembre.
Oggi parleremo di WebRTC.
La Comunicazione Web in Tempo Reale (WebRTC) non è un singolo protocollo, ma un'intera collezione di standard, protocolli e API JavaScript che insieme consentono comunicazioni video e audio peer-to-peer in tempo reale, e possono essere utilizzati anche per la trasmissione di dati binari. Di solito, i peer sono browser, ma possono anche essere applicazioni mobili, ad esempio. Per organizzare comunicazioni p2p tra i clienti, il browser deve supportare vari tipi di codifiche video e audio, supportare una serie di protocolli di rete, e garantire l'interazione tra l'hardware e il browser (attraverso i livelli del sistema operativo): webcam, schede audio. Tutta questa varietà di tecnologie è nascosta dietro un'astrazione dell'API JavaScript per la comodità degli sviluppatori.
In definitiva, si riassume in tre API:
— ne abbiamo parlato la volta scorsa, oggi scriverò ancora qualcosa al riguardo. Serve per ottenere flussi video/audio dall'hardware.
— consente comunicazioni tra due clienti (p2p)
— serve per trasmettere dati arbitrari tra due clienti.
Preparazione dei flussi audio e video per la trasmissione
Tutto inizia con la "cattura" dei flussi audio e video della webcam e del microfono. I flussi grezzi non sono adatti per organizzare videoconferenze; ogni flusso deve essere elaborato: migliorare la qualità, sincronizzare l'audio con il video, inserire i segnalibri di sincronizzazione nel flusso video e garantire un bitrate adeguato che cambia costantemente in base alla larghezza di banda disponibile. Il browser si occupa di tutto questo, quindi lo sviluppatore non deve preoccuparsi della codifica dei flussi multimediali. All'interno di un browser moderno ci sono già strati software per la cattura, il miglioramento della qualità (eliminare l'eco e il rumore dal suono, migliorare l'immagine), e la codifica di video e audio. Lo schema degli strati è mostrato in fig. 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 la situazione non è così rosea. Esistono ancora browser che non supportano completamente , puoi seguire il link e guardare la tabella di compatibilità in fondo. In particolare, IE delude ancora.
Con i flussi acquisiti si possono fare cose molto interessanti: è possibile clonare, modificare la risoluzione video, manipolare la qualità audio, attaccare un flusso Media Stream a un tag
— ragazzi che si occupano di CV in tempo reale su Javascript. Hanno a disposizione un intero di varie librerie js per lavorare con flussi video su canvas: rilevamento volti, oggetti, applicazione di filtri (maschere, come su Instagram) e altro. Un ottimo esempio di come sia possibile elaborare video in tempo reale direttamente nel browser senza plugin aggiuntivi.
— documentazione API per la cattura di flussi video da canvas. Già supportata da Chrome, Opera e Firefox
RTCPeerConnection
E siamo arrivati al punto di come trasmettere il video a un altro utente? Qui entra in gioco . In breve, a questo punto dovrete creare un oggetto RTCPeerConnection:
const peerConnection = new RTCPeerConnection({
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
});Una delle opzioni che specifichiamo è iceServers: questo server aiuta a stabilire una connessione tra due browser che si trovano dietro a un NAT. In altre parole, qui affrontiamo il problema: come conoscere l'IP dell'interlocutore se si trova dietro al NAT del suo provider? Il protocollo ICE interviene in questo caso, anche se in realtà l'ICE non appartiene a WebRTC, ma ne parleremo più tardi.
In precedenza abbiamo ottenuto i flussi Usermedia:
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
// Flussi Usermedia, di solito video e audio
const tracks = stream.getTracks();
for (const track of tracks) {
// ogni traccia viene aggiunta a peerConnection
peerConnection.addTrack(track);
}
}).catch(console.error);Successivamente, sull'oggetto peerConnection scatta l'evento onnegotiationneeded, nel cui gestore dobbiamo creare un'offerta (in termini di SDP — Session Description Protocol) e assegnarla a peerConnection tramite il metodo setLocalDescription. Parleremo di SDP — cosa sia e dei formati offer e answer — più avanti.
Dopo la creazione di LocalDescription, il peerConnection raccoglie i candidati ICE, ovvero trova diversi percorsi per comunicare tramite NAT. Viene attivato l'evento onicegatheringstatechange. Nel gestore onicegatheringstatechange autorizziamo la connessione con il server di signaling webrtc-stream per lo scambio della 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 webrtc-signaling è necessario per facilitare lo scambio di session description tra due peer; può essere un semplice server websocket o xhr scritto 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 viene attivato l'evento ontrack nel peerConnection, nel gestore del quale i brani ricevuti possono essere assegnati a
Riferimenti e letteratura:
— documentazione
— implementazione dei protocolli WebRTC in Go
— brochure dai creatori di Pion
— libro High Performance Browser Networking. Vengono analizzati in dettaglio i temi relativi all'ottimizzazione delle performance delle web application. Alla fine si parla di WebRTC. Sebbene il libro sia piuttosto vecchio (2013), rimane comunque attuale.
Nella prossima parte voglio fornire un'altra dose di teoria e, nella pratica, discutere l'acquisizione e la gestione di un flusso video sul server utilizzando pion, il transcoding in HLS tramite ffmpeg per successivi streaming agli spettatori nel browser.
Per i più impazienti: (è solo un esperimento).
Fonte: habr.com
