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

https://webrtchacks.com/youtube-does-webrtc-heres-how/
  • Перевод

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

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


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

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

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

Вызовы getUserMedia


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


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

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

Вызовы RTCPeerConnection


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



Серверы 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).



Апдейт: Найти сервер сигналинга и используемый протокол оказалось не очень сложно. Фильтр по ключевому слову «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. С нетерпением ждем, когда в Хроме уберут префикс.
  • +19
  • 5,5k
  • 3
Voximplant 145,28
Облачная телеком-платформа
Поделиться публикацией
Похожие публикации
Комментарии 3
    +1
    > WebRTC — это JavaScript API в современных браузерах для видеозвонков.
    > А еще для голосовых звонков, шаринга экрана, пробития NAT,
    > раскрытия локального адреса и других интересных штук.

    Одной из таких интересных штук, если я правильно понимаю, является возможность соединения браузеров между собой. Однако во всех описаниях процесс установки соединения двух браузеров описывается так, что браузерам никак не обойтись без вспомогательных серверов (STUN, а порою даже и TURN), т.к. браузеры находятся внутри NAT (часто симметричного) и этот NAT им приходится «пробивать».

    А что, если браузерам настолько сильно повезло, что они находятся внутри одной локальной сети 192.168.0.0/24 и благодаря этому никакие NATы им «пробивать» не требуется?

    Как браузеру_А передать браузеру_Б текстовое сообщение «Привет, это браузер_А»?

    Предположим, что:
    1. В локальной сети находится 192.168.0.0/24 всего 2 хоста: 192.168.0.1 и 192.168.0.2
    2. В браузер_А 1-го хоста загружена страница_А и она «знает», что браузер_Б работает на хосте 192.168.0.2. Ну, например, в её JS-коде написано что-то типа var ip_B = '192.168.0.2';
    3. В браузер_Б 2-го хоста загружена страница_Б и ожидает подключения от кого угодно, в частности от страницы_А.

    А теперь, внимание, вопрос:

    Какие действия в javascript станицы_А и какие действия в javascript страницы_Б надо выполнить, чтобы:
    1. страница_А отправила в страницу_Б текстовое сообщение «Привет из страницы_А»
    2. страница_Б приняла это сообщение и куда-то его отобразила, например в alert(s)

    Ну и дополнительно:
    Может ли страница_Б в сеансе запроса страницы_А дать ответ странице_А сразу же, пока запросное соединение страницы_А ещё не завершено (по аналогии http-запросов браузеров к веб-серверам)?

    Ну и специальные ограничения задачи, чтобы не было попыток страниц установить соединение через дополнительные серверы, а чтобы эти страницы соединялись между собой по настоящему напрямую:
    1. Браузеры из сети 192.168.0.0/24 не имеют доступа за пределы этой сети
    2. Внутри этой сети не работают ни STUN-серверы, ни TURN, ни ещё какие-либо другие серверы и помощники соединения между браузерами.
      0
      Такой фокус я показывал в Амстердаме на мастер классе по WebRTC, передавая SDP пакеты между двумя Edge через notepad :) К сожалению, многие реализации WebRTC тупо не работают без STUN сервера. Даже если она браузера в локалке и они смогут P2P напрямую.
        0
        Не получится. Нужен сигнальный сервер (не STUN и не TURN), который передаст данные между браузеромА и браузеромБ. Одного IP мало — нужно передать ключи аутентификации, порты, ну и так далее.
        Сигнальный сервер может быть любым и располагаться где угодно.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое