(Бараг) вэбкамераас хөтчөөс дамжуулж байгаа хэрэггүй. Media Stream болон Websockets

Энэ нийтлэлд би Adobe Flash Player гэх мэт гуравдагч талын хөтчийн залгаасуудыг ашиглахгүйгээр вэб залгуураар дамжуулан видео дамжуулах оролдлогоо хуваалцахыг хүсч байна. Үүнээс юу гарсныг олж мэдэхийн тулд уншина уу.

Adobe Flash, өмнө нь Macromedia Flash нь вэб хөтөч дээр ажилладаг програмуудыг үүсгэх платформ юм. Media Stream API-г нэвтрүүлэхээс өмнө энэ нь вэбкамераас видео болон дуу хоолойг дамжуулах, мөн хөтөч дээр төрөл бүрийн хурал, чат үүсгэх бараг цорын ганц платформ байсан юм. Хэвлэл мэдээллийн мэдээллийг дамжуулах протокол RTMP (Бодит цагийн мессежийн протокол) удаан хугацаанд хаалттай байсан бөгөөд энэ нь: Хэрэв та стриминг үйлчилгээгээ сайжруулахыг хүсч байвал Adobe-ийн програм хангамжийг ашиглахад хангалттай эелдэг хандаарай - Adobe Media Server (AMS).

2012 онд хэсэг хугацааны дараа Adobe олон нийтэд "бууж өгч, нулимав". тодорхойлолт RTMP протокол нь алдаа агуулсан бөгөөд үндсэндээ бүрэн бус байсан. Тэр үед хөгжүүлэгчид энэ протоколын хэрэгжилтийг өөрсдөө хийж эхэлсэн бөгөөд Wowza сервер гарч ирэв. 2011 онд Adobe RTMP-тэй холбоотой патентыг хууль бусаар ашигласан хэргээр Wowza-ийн эсрэг шүүхэд нэхэмжлэл гаргасан бөгөөд 4 жилийн дараа зөрчилдөөнийг эв зүйгээр шийдвэрлэсэн.

Adobe Flash платформ нь 20 гаруй жилийн настай бөгөөд энэ хугацаанд олон чухал сул талууд илэрсэн. амласан 2020 он гэхэд дуусч, стриминг үйлчилгээнд цөөн хэдэн хувилбар үлдээх болно.

Төслийн хувьд би тэр даруй хөтөч дээр Flash ашиглахаас бүрэн татгалзахаар шийдсэн. Би дээр дурдсан гол шалтгааныг дурьдсан; Flash нь гар утасны платформ дээр огт дэмжигдээгүй бөгөөд би Adobe Flash-ийг Windows (дарсны эмулятор) дээр хөгжүүлэхийг хүсээгүй. Тиймээс би JavaScript дээр үйлчлүүлэгч бичихээр шийдсэн. Энэ бол зүгээр л прототип байх болно, учир нь би стримингийг p2p дээр үндэслэн илүү үр дүнтэй хийх боломжтой гэдгийг хожим мэдсэн, зөвхөн миний хувьд энэ нь үе тэнгийн - сервер - үе тэнгийнхэн байх болно, гэхдээ энэ нь хараахан бэлэн болоогүй байгаа тул өөр нэг удаа илүү их байх болно.

Эхлэхийн тулд бидэнд бодит websockets сервер хэрэгтэй. Би melody go багц дээр үндэслэн хамгийн энгийнийг хийсэн:

Серверийн код

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

Үйлчлүүлэгч (урсалтын тал) дээр та эхлээд камерт хандах хэрэгтэй. Үүнийг дамжуулан хийдэг MediaStream API.

Бид камер/микрофон руу нэвтрэх эрх (зөвшөөрөл) авдаг Media Devices API. Энэ API нь аргыг өгдөг MediaDevices.getUserMedia(), гарч ирэх цонхыг харуулдаг. Хэрэглэгчээс камер болон/эсвэл микрофон руу нэвтрэх зөвшөөрөл хүссэн цонх. Би бүх туршилтыг Google Chrome дээр хийсэн гэдгээ тэмдэглэхийг хүсч байна, гэхдээ Firefox дээр бүх зүйл ижилхэн ажиллана гэж бодож байна.

Дараа нь getUserMedia() нь MediaStream объект буюу видео-аудио өгөгдлийн урсгалыг дамжуулдаг Амлалт буцаана. Бид энэ объектыг видео элементийн src шинж чанарт оноодог. Код:

Нэвтрүүлгийн тал

<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>

Видео урсгалыг залгуураар дамжуулахын тулд та үүнийг хаа нэгтээ кодлож, буфер хийж, хэсэг хэсгээр нь дамжуулах хэрэгтэй. Түүхий видео урсгалыг вэб залгуураар дамжуулах боломжгүй. Эндээс л бидний тусламж ирдэг MediaRecorder API. Энэхүү API нь дамжуулалтыг хэсэг болгон кодлох, хуваах боломжийг танд олгоно. Би сүлжээгээр цөөн байт илгээхийн тулд видеоны урсгалыг шахахын тулд кодчилол хийдэг. Үүнийг хэсэг болгон хуваагаад хэсэг бүрийг вэб залгуур руу илгээж болно. Код:

Бид видеоны урсгалыг кодлож, хэсэг болгон хуваадаг

<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>

Одоо вэбсокетуудаар дамжуулалтыг нэмье. Гайхалтай нь, үүнд танд зөвхөн объект л хэрэгтэй WebSockets. Энэ нь илгээх, хаах хоёр л аргатай. Нэр нь өөрсдөө ярьдаг. Нэмэгдсэн код:

Бид видео урсгалыг сервер рүү дамжуулдаг

<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>

Нэвтрүүлгийн тал бэлэн боллоо! Одоо видео урсгалыг хүлээн авч, үйлчлүүлэгч дээр харуулахыг хичээцгээе. Үүнд бидэнд юу хэрэгтэй вэ? Нэгдүгээрт, мэдээжийн хэрэг сокет холболт. Бид WebSocket объектод "сонсогч" хавсаргаж, "мессеж" үйл явдалд бүртгүүлнэ. Хоёртын өгөгдлийг хүлээн авсны дараа манай сервер үүнийг захиалагчдад, өөрөөр хэлбэл үйлчлүүлэгчдэд дамжуулдаг. Энэ тохиолдолд "мессеж" үйл явдлын "сонсогч"-той холбоотой буцаан дуудлагын функц нь үйлчлүүлэгч дээр идэвхждэг бөгөөд объект өөрөө функцийн аргумент руу шилждэг - vp8-ээр кодлогдсон видео урсгалын хэсэг.

Бид видео дамжуулалтыг хүлээн авдаг

<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>

Хүлээн авсан хэсгүүдийг яагаад шууд тоглуулахаар видео элемент рүү илгээх боломжгүй байгааг би удаан хугацааны турш ойлгохыг хичээсэн боловч үүнийг хийх боломжгүй болсон, мэдээжийн хэрэг та эхлээд уг хэсгийг тусгай буферт хийх хэрэгтэй. видео элемент, зөвхөн дараа нь видео урсгалыг тоглуулж эхэлнэ. Үүний тулд танд хэрэгтэй болно MediaSource API и FileReader API.

MediaSource нь медиа тоглуулах объект болон энэ медиа урсгалын эх үүсвэрийн хооронд зуучлагчийн үүрэг гүйцэтгэдэг. MediaSource объект нь видео/аудио урсгалын эх сурвалжийн залгах боломжтой буферийг агуулна. Нэг онцлог нь буфер нь зөвхөн Uint8 өгөгдлийг хадгалах боломжтой тул ийм буфер үүсгэхийн тулд танд FileReader хэрэгтэй болно. Кодыг харвал илүү тодорхой болно:

Видео урсгалыг тоглуулж байна

<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>

Стрийминг үйлчилгээний прототип бэлэн боллоо. Гол сул тал нь видеог тоглуулах нь дамжуулагч талаас 100 мс-ээр хоцорч, сервер рүү дамжуулахаасаа өмнө видеоны урсгалыг хуваахдаа бид өөрсдөө үүнийг тохируулдаг. Түүгээр ч барахгүй зөөврийн компьютерээ шалгахад дамжуулагч болон хүлээн авагч талуудын хоорондох хоцрогдол аажмаар хуримтлагдаж, энэ нь тодорхой харагдаж байв. Би энэ сул талыг даван туулах арга замыг хайж эхэлсэн бөгөөд ... таарав RTCPeerConnection API, энэ нь видео урсгалыг хэсэг болгон хуваах гэх мэт заль мэхгүйгээр дамжуулах боломжийг олгодог. Хуримтлагдах хоцрогдол нь хөтөч дамжуулахын өмнө хэсэг бүрийг webm формат руу дахин кодлодогтой холбоотой гэж би бодож байна. Би цааш ухаагүй, гэхдээ WebRTC-ийг судалж эхэлсэн. Хэрэв би судалгааныхаа үр дүнг нийгэмд сонирхолтой гэж үзвэл тусдаа нийтлэл бичнэ гэж бодож байна.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх