Небольшое расследование: как YouTube использует WebRTC для стриминга

в 9:16, , рубрики: javascript, WebRTC, Блог компании Voximplant, Программирование, Разработка веб-сайтов, разработка мобильных приложений
Небольшое расследование: как YouTube использует WebRTC для стриминга - 1

WebRTC — это JavaScript API в современных браузерах для видеозвонков. А еще для голосовых звонков, шаринга экрана, пробития NAT, раскрытия локального адреса и других интересных штук. В последние пару лет крупные игроки начинают переходить с пропиетарных API и расширений браузеров на WebRTC: с его помощью работает Skype for Web, частично — Hangouts, а теперь и возможности YouTube по броадкасту прямо из браузера. Пока только из хрома и с пятисекундной задержкой — но велика беда начало. Под катом мы предлагаем адаптированный для Хабра перевод детективной истории, где эксперты по WebRTC разбирают код клиентской части YouTube и рассказывают нам что и как сделали разработчики из Гугла.

Прошлый Четверг. Залогинившисть в свой YouTube аккаунт, я обнаружил новую иконку камеры с подсказкой «Go Live» в правом-верхнем углу (примечание переводчика: судя по всему, пока раскатано не для всех пользователей. В комментах отметились подписчики YouTube Red, у них есть). Естественно я сразу же ее кликнул, и, похоже, теперь мы можем стримить прямо из браузера. Попахивало WebRTC, так что я привычно открыл chrome://webrtc-internals/ — и таки да, это было WebRTC. Нас как разработчиков всегда интересовали масштабные использования технологии, так что я сразу связался с мастером-реверсером Филипом «Фип» Ханкелем и попросил его покопаться во внутренностях YouTube. Дальше мы можем ознакомиться с результатами его работы.

Небольшое расследование: как YouTube использует WebRTC для стриминга - 2

Служебная страница Хрома, webrtc-internals, сослужила нам хорошую службу еще в далеком 2014 году, когда мы изучали как работает Hangouts, и ничего не мешало нам снова ей воспользоваться. Так как новая регистрация на YouTube недоступна для броадкастов в течении 24 часов, то мы воспользовались дампом, любезно предоставленным Цахи Левент-Леви (примечание переводчика: да-да, тот самый Цахи который выступал у нас на Intercom и которого мы регулярно переводим). Вы можете воспользоваться вот этой тулзой, чтобы загрузить дамп себе в Хром и посмотреть на происходящее глазами WebRTC.

Судя по тому, что мы увидели, новая фича YouTube использует WebRTC только на стороне клиента для захвата потока видеокамеры. А со стороны сервера у них что-то свое. Что это значит? Значит не realtime. Хотя наш давний и хороший знакомый Крис Кранки говорит, что задержка составляет менее пяти секунд. Очень ждем, что он вытащит наружу какие-нибудь интересные технические детали.

А пока углубимся в технические детали, которые смогли вытащить мы…

Вызовы getUserMedia

После импортирования дампа, в самом его начале мы видим вызовы JavaScript API getUserMedia, которые совершает YouTube. По вызовам видно, что сервис скромно хочет камеру в разрешении 1080p:

Небольшое расследование: как YouTube использует WebRTC для стриминга - 3

А еще они делают отдельный вызов getUserMedia для получения микрофона.

На этом скриншоте не видно самого первого вызова getUserMedia, который запрашивает сразу камеру и микрофон, чтобы пользователь увидел только одно оконо подтверждения бразуера вместо двух.

Вызовы RTCPeerConnection

Осмотрев вызовы getUserMedia, можно переходить к вызовам RTCPeerConnection. Если вы хотите узнать больше о WebRTC, рекомендую почитать результаты предыдущего исследования "Как работает Hangouts" или более общую информацию о webrtc-internals на нашем блоге TestRTC blog.

Небольшое расследование: как YouTube использует WebRTC для стриминга - 4

Сервера ICE

По логу видно, что объект RTCPeerConnection создан с пустым списокм ICE серверов (примечание переводчика: неудивительно, что это пока работает только в Хроме. Ёжик бы вообще не дал такой объект создать).

{
  iceServers: [],
  iceTransportPolicy: all, 
  bundlePolicy: balanced,
  rtcpMuxPolicy: require,
  iceCandidatePoolSize: 0
}

Далее будет ясно, почему для такого варианта использования не нужны TURN-сервера (примечание переводчика: ICE это «фреймворк», текстовая инструкция как делать peer-to-peer с печальными IP адресами 192.168..., TURN сервера в фреймворке не самое главное. Самое главное это STUN сервера, которые отвечают на фундаментальный вопрос «а какой у меня внешний IP адрес?». Без указания как минимум одного STUN сервера большинство реализаций WebRTC просто не будет работать).

Далее клиент добавляет MediaStream с помощью API addSteam. Забавно, что это API объявлено depricated. Странно, что авторы не используют новое API addTrack, которое доступно начиная с 64-й версии Google Chrome, а в более старых версиях — с помощью полифила adapter.js

Сигнализация и setLocalDescription

После создания объекта RTCPeerConnection клиент создает WebRTC «оффер» со списком всех аудио и видео кодеков, доступных Хрому. Оффер без модификаций устанавливается как описание локального эндпоинта с помощью setLocalDescription. Кстати, отсутствие модификаций означает, что simulcast (одновременное транслирование нескольких потоков с разным качеством видео, позволяет не перекодировать все на сервере, уменьшает задержки и нагрузку) не используется.

В соответствии с логикой работы WebRTC, после вызова setLocalDescription хром предлагает несколько «кандидатов» — вариантов как удаленный компьютер может попробовать подключиться к локальному. Скорее всего они не используются, так как подключаться будет клиент (Хром) к серверу (бэкенду YouTube).

Небольшое расследование: как YouTube использует WebRTC для стриминга - 5

Апдейт: Найти сервер сигналинга и используемый протокол оказалось не очень сложно. Фильтр по ключевому слову «realtimemediaservice» сетевого лога Хрома показывают нам HTTP запрос и ответ на него. Никаких сложные схем, trickle-ice оптимизаций скорости установки соединения и другой магии, все настолько просто, насколько вообще возможно.

setRemoteDescription

Следующим шагом идет вызов setRemoteDescription на основании информации, полученной от сервера. Где, как мы помним, WebRTC не используется. И здесь все становится интересным! SDP, используемый в setRemoteDescription, выглядит так, как будто на другой стороне его сделал Хром или сишная WebRTC-библиотека с полным списком кодеков наперевес. Причем мы точно знаем, что YouTube не использует «ice-lite», как это делает Hangouts.

В полученном со стороны сервера SDP пакете кодек H.264 указан как предпочтительный (число 102, см здесь, если интересно, как устроены текстовые пакеты SDP):

m=video 9 UDP/TLS/RTP/SAVPF 102 96 97 98 99 123 108 109 124

Изучение статистики (частично отображается после загрузки дампа) подтверждает, что используется кодек H.264, кому любопытно можете поискать в дампе по ключевому слову «send-googCodecName».

Кроме SDP ответа, сервер передает Хрому несколько кандидатов для установки подключения:

a=candidate:3757856892 1 udp 2113939711 2a00:1450:400c:c06::7f 19305
    typ host generation 0 network-cost 50
a=candidate:1687053168 1 tcp 2113939711 2a00:1450:400c:c06::7f 19305
    typ host tcptype passive generation 0 network-cost 50
a=candidate:1545990220 1 ssltcp 2113939711 2a00:1450:400c:c06::7f 443
    typ host generation 0 network-cost 50
a=candidate:4158478555 1 udp 2113937151 66.102.1.127 19305
    typ host generation 0 network-cost 50
a=candidate:1286562775 1 tcp 2113937151 66.102.1.127 19305
    typ host tcptype passive generation 0 network-cost 50
a=candidate:3430656991 1 ssltcp 2113937151 66.102.1.127 443
    typ host generation 0 network-cost 50

Мы можем наблюдать IPv4 и IPv6 UDP кандидаты, «ICE-TCP» кандидаты (да, в случае засухи WebRTC может ходить по TCP, хотя очень этого не любит делать) и пропиетарные для Хрома «SSL-TCP», которые мы раньше видели в Hangouts. При таком раскладе TURN сервер никак не улучшит шансы установить подключение, так как в обоих случаях это будет подключения Хрома к реальному IP адресу. Видимо, поэтому TURN сервер и не используется.

Кодеки

Симулкаста нет. Что, вообщем-то, ожидаемо: в хроме нет H264-simulcast кодека. Зато есть баг репорт с печальным отсутствием фидбека. В целом H.264 разумный вабор: кодирующая сторона может использовать видеокарту для облегчения процесса, а большинство плееров смогут воспроизвести этот формат без перекодирования.

Тем не менее, совсем без перекодирования обойтись не удастся, так как без симулкаста серверу придется создавать потоки с меньшим битрейтом и разрешением для «слабых» клиентов. Скорее всего функция перекодирования у YouTube уже есть, как часть инфраструктуры, которую они давно используют для стриминга.

Статистика WebRTC

Сама по себе статистика ничего нового не раскрывает. Самый интересный график это «picture loss indications», PLI — данные, которые присылает сервер (от переводчика: статистика WebRTC интересна тем, что на каждом конце соединения собирается как локальная статистика, так и принимается удаленная. Мы об этом писали на прошлой неделе):

image

pliCount увеличивается каждые 10 секунд и, соответственно, каждые 10 секунд клиент отсылает серверу опорный кадр (keyframe). Возможно, это сделано, чтобы облегчить серверам YouTube запись или перекодирование видео.

Итого

YouTube использует WebRTC как дружественный пользователю способ получить видеопоток с камеры для стриминга. Скорее всего это никак не повлияет на профессиональных стримеров с дорогими настроенными ригами, но вот барьер входа для новичков понизит значительно.

К сожалению, фича не работает в Firefox. Это один из примеров запуска Гуглом решений, которые работают только в Хроме. Нилс Охлмейер из Mozilla попробовал заставить его работать подделав user agent, но столкнулся с использованием в JavaScript устаревшего API registerElement. Тем не менее, с точки зрения WebRTC все должно работать, так что мы еще вернемся к этому вопросу после починки багов фронтенда.

Обновление К сожалению, дополнительное изучение показало, что JavaScript код этой фичи также использует легаси API webkitRTCPeerConnection вместо современного RTCPeerConection. С нетерпением ждем, когда в Хроме уберут префикс.

Автор: nvpushkarskiy2

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js