(Prawie) bezużyteczne strumieniowanie kamer internetowych przez przeglądarkę. Część 2. WebRTC

Gdzieś w jednej ze starych i już zapomnianych artykułów pisałem o tym, jak łatwo i swobodnie można transmitować wideo z canvas za pomocą websockets. W tym artykule pobieżnie opisałem, jak przechwytywać wideo z kamery i dźwięk z mikrofonu za pomocą MediaStream API, jak zakodować i wysłać uzyskany strumień przez websockets na serwer. Jednak w rzeczywistości tak się nie robi, do transmisji wykorzystuje się albo specjalne oprogramowanie, które trzeba zainstalować i skonfigurować: na szybko może to być Open Broadcast Software, albo wykorzystuje się WebRTC, który działa od razu, to znaczy nie wymaga instalacji żadnych wtyczek jak flash player, którego już w grudniu usuną z przeglądarki Chromium.

Dzisiaj porozmawiamy o WebRTC.

Web Real-Time Communication (WebRTC) to nie jeden protokół, to cała kolekcja standardów, protokołów i JavaScript API, które razem zapewniają peer-to-peer komunikację wideo-audio w czasie rzeczywistym, a także mogą być używane do przesyłania wszelkich danych binarnych. Zwykle peerami są przeglądarki, ale może to być także aplikacja mobilna, na przykład. Aby zorganizować komunikację p2p między klientami, przeglądarka musi wspierać różne rodzaje kodowania wideo i audio, wspierać wiele protokołów sieciowych oraz zapewnić interakcję sprzętu z przeglądarką (przez warstwy systemu operacyjnego): kamer, kart dźwiękowych. Cała ta mieszanka technologii jest ukryta za abstrakcją JavaScript API dla wygody dewelopera.

Wszystko sprowadza się do trzech API:

  • MediaStream API — omawialiśmy w poprzednim razem, dzisiaj jeszcze trochę napiszę o nim. Służy do uzyskiwania strumieni wideo/audiowych z „sprzętu”

  • RTCPeerConnection — zapewnia komunikację między dwoma klientami (p2p)

  • RTCDataChannel — służy do przesyłania dowolnych danych między dwoma klientami

Przygotowanie strumieni audio i wideo do transmisji

Wszystko zaczyna się od "przechwytywania" strumieni wideo z kamery internetowej i mikrofonu. Surowe strumienie oczywiście nie nadają się do organizacji wideokonferencji, każdy strumień musi zostać przetworzony: poprawić jakość, zsynchronizować audio z wideo, ustawić znaczniki synchronizacji w strumieniu wideo, zapewnić odpowiedni bitrate dostosowujący się do zmieniającej się szerokości pasma. Przeglądarka zajmuje się tym wszystkim, a programista nie musi martwić się o kodowanie strumieni mediów. W nowoczesnej przeglądarce już znajdują się warstwy programowe do przechwytywania, poprawy jakości (usuwanie echa i hałasu z dźwięku, poprawa obrazu), kodowania wideo i audio. Schemat warstw przedstawiono na rys. 1:

(Prawie) bezużyteczne strumieniowanie kamer internetowych przez przeglądarkę. Część 2. WebRTCRys. 1. Warstwy przetwarzania audio i wideo w przeglądarce

Całe przetwarzanie odbywa się bezpośrednio w samej przeglądarce, nie są wymagane żadne dodatkowe wtyczki. Niemniej jednak w 2020 roku sytuacja wciąż nie wygląda tak różowo. Pozostały przeglądarki, które jeszcze nie wspierają w pełni MediaStream API, możesz przejść pod ten link i na dole zobaczyć tabelę zgodności. Szczególnie IE znowu rozczarowuje.

Z uzyskanymi strumieniami można robić bardzo ciekawe rzeczy: można je klonować, zmieniać rozdzielczość wideo, manipulować jakością audio, można załączyć strumień Media Stream do <video> i oglądać siebie na stronie html. Można również rysować strumień na canvas, wykorzystać WebGL lub CSS3, nakładać różne filtry na wideo, przechwytywać przetworzone wideo z canvas i następnie przesyłać je przez sieć na serwer, transkodować i publikować dla wszystkich chętnych (cześć bigo live, twitch i inni). Nie będę tutaj omawiać, jak takie rzeczy się robi, podam kilka przykładów znalezionych w sieci:

https://jeeliz.com/ — chłopaki zajmują się realtime CV na Javascript. Posiadają cały arsenał różnych bibliotek js do pracy ze strumieniem wideo na canvas: wykrywanie twarzy, obiektów, nakładanie filtrów (masek, jak w Instagramie) itp. Doskonały przykład tego, jak bez dodatkowych wtyczek można w przeglądarce przetwarzać wideo w czasie rzeczywistym.

API do przechwytywania Canvas captureStream — dokumentacja API dotycząca przechwytywania strumienia wideo z canvas. Już wspierane w Chrome, Operze i Firefoxie

RTCPeerConnection

Oto doszliśmy do pytania, jak właściwie przesłać wideo do innego użytkownika? Na pierwszy plan wyłania się RTCPeerConnectionMówiąc krótko, na tym etapie musisz stworzyć obiekt RTCPeerConnection:

const peerConnection = new RTCPeerConnection({
  iceServers: [{
    urls: 'stun:stun.l.google.com:19302'
  }]
});

Jedną z opcji, które podajemy, jest iceServers – to serwer, który pomaga w nawiązywaniu połączenia między dwoma przeglądarkami znajdującymi się za NAT. Oznacza to, że rozwiązujemy problem: jak poznać IP rozmówcy, jeśli znajduje się za NAT jego dostawcy? Z pomocą przychodzi protokół ICE, który w rzeczywistości nie odnosi się bezpośrednio do WebRTC, ale o tym później.

Wcześniej uzyskaliśmy strumienie Usermedia:

navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
  // Strumienie Usermedia, zazwyczaj to wideo i audio 
  const tracks = stream.getTracks();

   for (const track of tracks) {
     // każdy utwór łączymy z peerConnection
     peerConnection.addTrack(track);
   }
}).catch(console.error);

Następnie w peerConnection wywołuje się zdarzenie onnegotiationneeded, w jego obsłudze musimy stworzyć ofertę (w terminologii SDP – Session Description Protocol) i przypisać ją w peerConnection za pomocą metody setLocalDescription. O SDP – czym jest i o formatach oferty i odpowiedzi – porozmawiamy później.

Po przypisaniu LocalDescription peerConnection, przeglądarka 'zbiera' kandydatów ice, to znaczy znajduje różne trasy do komunikacji przez NAT. Wywoływane jest zdarzenie onicegatheringstatechange. W obsłudze onicegatheringstatechange zezwalamy na połączenie z serwerem sygnalizacyjnym webrtc w celu wymiany Session Description między parami:

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);
      }
    };

Serwer sygnalizacyjny webrtc - to serwer niezbędny do wymiany opisów sesji między dwoma parami, może to być najprostszy serwer websocket lub xhr na dowolnym języku programowania. Jego zadanie jest proste: przyjąć opis sesji od jednej pary i przekazać go drugiej.

Po wymianie opisów sesji obie strony są gotowe do transmisji i odbierania strumieni wideo, po stronie, która odbiera strumień wideo wywoływane jest zdarzenie ontrack w peerConnection, w obsłudze którego, otrzymane utwory można przypisać do <video> i oglądać ulubionego rozmówcę. Następnie teoria i szczegóły.

Linki i literatura:

https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection — dokumentacja RTCPeerConnection

https://github.com/pion/webrtc — implementacja protokołów WebRTC w języku go

https://webrtcforthecurious.com/ — książeczka od twórców pion

https://hpbn.co/ — książka High Performance Browser Networking. W szczegółach omawia kwestie zapewnienia wysokiej wydajności aplikacji webowych. Na koniec opisuje WebRTC. Książka jest oczywiście stara (2013), ale nie traci na aktualności.

W następnej części chcę przedstawić jeszcze trochę teorii i praktycznie omówić przyjmowanie oraz przetwarzanie strumienia wideo na serwerze za pomocą pion, transkodowanie do HLS przez ffmpeg w celu późniejszej transmisji dla widzów w przeglądarce.

Dla niecierpliwych: mój bardzo surowy prototyp transmisji wideo z kamery internetowej w react przez serwer oparty na pionie w twitchu (to po prostu eksperyment).

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster