Bir tarayıcıdan (Neredeyse) işe yaramaz web kamerası akışı. Medya Akışı ve Web Yuvaları

Bu makalede, Adobe Flash Player gibi üçüncü taraf tarayıcı eklentilerini kullanmadan websockets aracılığıyla video akışı yapma girişimlerimi paylaşmak istiyorum. Bunun ne olduğunu öğrenmek için okumaya devam edin.

Adobe Flash, eski adıyla Macromedia Flash, bir web tarayıcısında çalışan uygulamalar oluşturmaya yönelik bir platformdur. Media Stream API'nin piyasaya sürülmesinden önce, bir web kamerasından video ve ses akışının yanı sıra tarayıcıda çeşitli türde konferanslar ve sohbetler oluşturmak için pratik olarak tek platformdu. Medya bilgilerini aktarma protokolü RTMP (Gerçek Zamanlı Mesajlaşma Protokolü) aslında uzun bir süredir kapalıydı; bu şu anlama geliyordu: akış hizmetinizi güçlendirmek istiyorsanız, Adobe'nin kendi yazılımını - Adobe Media Server'ı (AMS) kullanma nezaketini gösterin.

2012'de bir süre sonra Adobe "vazgeçti ve bunu kamuoyuna tükürdü". Şartname Hatalar içeren ve esasen eksik olan RTMP protokolü. O zamana kadar geliştiriciler bu protokolün kendi uygulamalarını yapmaya başladılar ve Wowza sunucusu ortaya çıktı. Adobe, 2011 yılında RTMP ile ilgili patentlerin yasadışı kullanımı nedeniyle Wowza'ya karşı dava açtı; 4 yıl sonra anlaşmazlık dostane bir şekilde çözüldü.

Adobe Flash platformunun yaşı 20'den fazladır ve bu süre zarfında birçok kritik güvenlik açığı keşfedilmiştir. vaat 2020 yılına kadar sona erecek ve akış hizmeti için çok az alternatif kalacak.

Projem için tarayıcıda Flash kullanımını hemen tamamen bırakmaya karar verdim. Ana nedeni yukarıda belirttim; Flash mobil platformlarda da hiç desteklenmiyor ve Adobe Flash'ı Windows (wine emulator) üzerinde geliştirme için dağıtmak istemedim. Bu yüzden JavaScript'te bir istemci yazmaya başladım. Bu sadece bir prototip olacak, çünkü daha sonra akışın p2p'ye dayalı olarak çok daha verimli bir şekilde yapılabileceğini öğrendim, sadece benim için eş - sunucu - eşler olacak, ancak daha fazlası başka zaman olacak çünkü henüz hazır değil.

Başlamak için gerçek websockets sunucusuna ihtiyacımız var. Melody go paketine dayanarak en basitini yaptım:

Sunucu kodu

package main

import (
	"errors"
	"github.com/go-chi/chi"
	"gopkg.in/olahol/melody.v1"
	"log"
	"net/http"
	"time"
)

func main() {
	r := chi.NewRouter()
	m := melody.New()

	m.Config.MaxMessageSize = 204800

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "public/index.html")
	})
	r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
		m.HandleRequest(w, r)
	})

         // Бродкастим видео поток 
	m.HandleMessageBinary(func(s *melody.Session, msg []byte) {
		m.BroadcastBinary(msg)
	})

	log.Println("Starting server...")

	http.ListenAndServe(":3000", r)
}

İstemcide (akış tarafında), öncelikle kameraya erişmeniz gerekir. Bu aracılığıyla yapılır Medya Akışı API'si.

aracılığıyla kameraya/mikrofona erişim (izin) alıyoruz. Medya Cihazları API'sı. Bu API bir yöntem sağlar MediaDevices.getUserMedia(), açılır pencereyi gösterir. Kullanıcıdan kameraya ve/veya mikrofona erişim izni isteyen bir pencere. Tüm denemeleri Google Chrome'da yaptığımı belirtmek isterim ancak Firefox'ta her şeyin hemen hemen aynı şekilde çalışacağını düşünüyorum.

Daha sonra, getUserMedia() bir Promise döndürür ve bu nesneye bir video-ses verileri akışı olan MediaStream nesnesini iletir. Bu nesneyi video öğesinin src özelliğine atadık. Kod:

Yayın tarafı

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer');

// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице

if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
          // видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать 
          video.srcObject = stream;
        });
}
</script>

Bir video akışını soketler üzerinden yayınlamak için, onu bir yere kodlamanız, arabelleğe almanız ve parçalar halinde iletmeniz gerekir. Ham video akışı web soketleri aracılığıyla iletilemez. Burası bizim yardımımıza geliyor Medya Kaydedici API'si. Bu API, akışı kodlamanıza ve parçalara ayırmanıza olanak tanır. Ağ üzerinden daha az bayt göndermek amacıyla video akışını sıkıştırmak için kodlama yapıyorum. Parçalara ayırdıktan sonra her parçayı bir websocket'e gönderebilirsiniz. Kod:

Video akışını kodluyor, parçalara ayırıyoruz

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer');

// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице

if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
          // видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать 
          video.srcObject = s;
          var
            recorderOptions = {
                mimeType: 'video/webm; codecs=vp8' // будем кодировать видеопоток в формат webm кодеком vp8
              },
              mediaRecorder = new MediaRecorder(s, recorderOptions ); // объект MediaRecorder

               mediaRecorder.ondataavailable = function(e) {
                if (e.data && e.data.size > 0) {
                  // получаем кусочек видеопотока в e.data
                }
            }

            mediaRecorder.start(100); // делит поток на кусочки по 100 мс каждый

        });
}
</script>

Şimdi websocket'ler aracılığıyla iletim ekleyelim. Şaşırtıcı bir şekilde, bunun için ihtiyacınız olan tek şey bir nesne WebSocket. Gönderme ve kapatmanın yalnızca iki yöntemi vardır. İsimler kendileri için konuşur. Kod eklendi:

Video akışını sunucuya aktarıyoruz

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть себя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer');

// если доступен MediaDevices API, пытаемся получить доступ к камере (можно еще и к микрофону)
// getUserMedia вернет обещание, на которое подписываемся и полученный видеопоток в колбеке направляем в video объект на странице

if (navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia({video: true}).then(function (stream) {
          // видео поток привязываем к video тегу, чтобы клиент мог видеть себя и контролировать 
          video.srcObject = s;
          var
            recorderOptions = {
                mimeType: 'video/webm; codecs=vp8' // будем кодировать видеопоток в формат webm кодеком vp8
              },
              mediaRecorder = new MediaRecorder(s, recorderOptions ), // объект MediaRecorder
              socket = new WebSocket('ws://127.0.0.1:3000/ws');

               mediaRecorder.ondataavailable = function(e) {
                if (e.data && e.data.size > 0) {
                  // получаем кусочек видеопотока в e.data
                 socket.send(e.data);
                }
            }

            mediaRecorder.start(100); // делит поток на кусочки по 100 мс каждый

        }).catch(function (err) { console.log(err); });
}
</script>

Yayın tarafı hazır! Şimdi bir video akışı almayı ve bunu istemcide görüntülemeyi deneyelim. Bunun için neye ihtiyacımız var? Öncelikle elbette soket bağlantısı. WebSocket nesnesine bir “listener” ekleyip 'message' olayına abone oluyoruz. Bir parça ikili veri aldıktan sonra sunucumuz bunu abonelere, yani istemcilere yayınlar. Bu durumda, 'mesaj' olayının "dinleyicisi" ile ilişkili geri çağırma işlevi istemcide tetiklenir; nesnenin kendisi, vp8 tarafından kodlanan video akışının bir parçası olan işlev argümanına iletilir.

Video akışını kabul ediyoruz

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть тебя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer'),
         socket = new WebSocket('ws://127.0.0.1:3000/ws'), 
         arrayOfBlobs = [];

         socket.addEventListener('message', function (event) {
                // "кладем" полученный кусочек в массив 
                arrayOfBlobs.push(event.data);
                // здесь будем читать кусочки
                readChunk();
            });
</script>

Uzun bir süre, alınan parçaları oynatmak için video öğesine hemen göndermenin neden imkansız olduğunu anlamaya çalıştım, ancak bunun yapılamayacağı ortaya çıktı, elbette, önce parçayı özel bir ara belleğe koymanız gerekiyor. video öğesini seçin ve ancak o zaman video akışını oynatmaya başlayacaktır. Bunun için ihtiyacınız olacak Medya Kaynağı API'sı и Dosya Okuyucu API'sı.

MediaSource, medya oynatma nesnesi ile bu medya akışının kaynağı arasında bir tür aracı görevi görür. MediaSource nesnesi, video/ses akışının kaynağı için takılabilir bir arabellek içerir. Bir özelliği, arabelleğin yalnızca Uint8 verilerini tutabilmesidir, dolayısıyla böyle bir arabellek oluşturmak için bir FileReader'a ihtiyacınız olacaktır. Koda bakın ve daha net hale gelecektir:

Video akışını oynatma

<style>
  #videoObjectHtml5ApiServer { width: 320px; height: 240px; background: #666; }
</style>
</head>
<body>
<!-- Здесь в этом "окошечке" клиент будет видеть тебя -->
<video autoplay id="videoObjectHtml5ApiServer"></video>

<script type="application/javascript">
  var
        video = document.getElementById('videoObjectHtml5ApiServer'),
         socket = new WebSocket('ws://127.0.0.1:3000/ws'),
        mediaSource = new MediaSource(), // объект MediaSource
        vid2url = URL.createObjectURL(mediaSource), // создаем объект URL для связывания видеопотока с проигрывателем
        arrayOfBlobs = [],
        sourceBuffer = null; // буфер, пока нуль-объект

         socket.addEventListener('message', function (event) {
                // "кладем" полученный кусочек в массив 
                arrayOfBlobs.push(event.data);
                // здесь будем читать кусочки
                readChunk();
            });

         // как только MediaSource будет оповещен , что источник готов отдавать кусочки 
        // видео/аудио потока
        // создаем буфер , следует обратить внимание, что буфер должен знать в каком формате 
        // каким кодеком был закодирован поток, чтобы тем же способом прочитать видеопоток
         mediaSource.addEventListener('sourceopen', function() {
            var mediaSource = this;
            sourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs="vp8"");
        });

      function readChunk() {
        var reader = new FileReader();
        reader.onload = function(e) { 
          // как только FileReader будет готов, и загрузит себе кусочек видеопотока
          // мы "прицепляем" перекодированный в Uint8Array (был Blob) кусочек в буфер, связанный
          // с проигрывателем, и проигрыватель начинает воспроизводить полученный кусочек видео/аудио
          sourceBuffer.appendBuffer(new Uint8Array(e.target.result));

          reader.onload = null;
        }
        reader.readAsArrayBuffer(arrayOfBlobs.shift());
      }
</script>

Akış hizmetinin prototipi hazır. Ana dezavantaj, video oynatımının iletim tarafının 100 ms gerisinde kalmasıdır; bunu, video akışını sunucuya aktarmadan önce bölerken kendimiz ayarlıyoruz. Üstelik dizüstü bilgisayarımda kontrol ettiğimde verici ve alıcı taraflar arasındaki gecikmenin giderek biriktiği açıkça görülüyordu. Bu dezavantajı aşmanın yollarını aramaya başladım ve... RTCPeerConnection API'siBu, akışı parçalara bölmek gibi hileler olmadan bir video akışını aktarmanıza olanak tanır. Biriken gecikmenin, tarayıcının iletimden önce her parçayı webm formatına yeniden kodlamasından kaynaklandığını düşünüyorum. Daha fazla araştırmadım ve WebRTC'yi incelemeye başladım.Topluluk açısından ilgi çekici bulursam araştırmamın sonuçları hakkında ayrı bir makale yazacağımı düşünüyorum.

Kaynak: habr.com

Yorum ekle