Go2rtc — это open source приложение для стриминга видео в реальном времени. Оно весит всего 5 мегабайт, не имеет внешних зависимостей и работает на всех популярных ОС. Поддерживает технологии RTSP, RTMP, DVRIP, HTTP-FLV, WebRTC, MSE, HLS, MJPEG, HomeKit и многие другие. Последний релиз получился настолько масштабным, что все нововведения пришлось оформлять в статью на Хабре.
Введение
Моё знакомство с технологией WebRTC началось в далёком 2020 году с серии стриминговых проектов Андрея @deepch - RTSPtoWebRTC, RTSPtoWSMP4f, RTSPtoWeb.
В апреле 2022-го я начал собирать все идеи для развития в собственном проекте go2rtc, первая альфа-версия вышла в последние дни лета, а первый релиз вышел в конце января 2023-го.
Концепция проекта вышла настолько удачной, что разработчики различных open source проектов начали встраивать go2rtc в свои решения там, где требовался стриминг с камер.
В версии 1.2.0 от февраля 2023-го go2rtc поддерживал передачу потокового видео с камер в браузер в формате WebRTC с задержкой менее 0.5 секунды.
Поддерживались и дополнительные особенности.
1. Синхронное и асинхронное WebRTC-подключение
Сам по себе процесс подключения по технологии WebRTC выглядит очень просто: один из клиентов отправляет offer (пожелания о подключении), второй возвращает ему answer (согласие о подключении). В offer и answer находятся media-элементы с типом (video, audio), направлением (recvonly, sendonly, sendrecv) и перечнем кодеков (H264, VP8, OPUS, PCMA и т.д.).
Например, если браузер хочет только принимать видео и аудио, он может создать offer с двумя media направления recvonly. По умолчанию туда подадут все кодеки, которые браузер поддерживает для видео и аудио.
В теории answer не может быть шире offer, то есть туда нельзя добавлять новые media или новые кодеки. Но можно отказаться от ненужных медиа и неудобных кодеков.
Чтоб подключение могло установиться, участники должны обменяться IP-адресами (candidates). Это можно сделать либо внутри offer/answer (синхронное подключение), либо уже после отправки offer/answer (асинхронное подключение). В список IP-адресов должны попасть адреса всех локальных сетевых интерфейсов и внешние адреса — на случай, если второй участник находится за пределами локальной сети.
В зависимости от настроек сети на компьютере, домашнем роутере, провайдере, да и вообще в стране, поиск списка адресов может затянуться. Потому синхронное подключение будет устанавливаться очень долго. В этом случае выгодно применять асинхронное подключение. Подход также называют Trickle ICE.
Go2rtc поддерживает оба варианта и внутри своих веб-интерфейсов использует именно асинхронный. Но сторонние проекты, например стандартная интеграция Home Assistant, часто умеют только синхронный вариант.
2. Настройка внешнего доступа
Важно понимать, что технология WebRTC в первую очередь нацелена на прямое подключение (peer-to-peer) между двумя участниками соединения. Данные передаются не по HTTP-протоколу. И web/proxy-сервер вроде Nginx участвует только на момент установки соединения.
Для установки прямого подключения между клиентами из разных закрытых сетей используется технология STUN и один из публичных STUN-серверов (в открытых проектах часто используют сервер Google). Такой сервер не участвует в передаче медиаданных, а лишь помогает клиентам установить соединение.
В некоторых случаях, когда клиенты находятся за Symmetric NAT, технология STUN не сработает. Коммерческие проекты в этом случае используют TURN-сервера для проксирования медиа трафика. Бесплатных TURN-серверов нет. Поэтому обычным пользователям приходиться искать другие варианты.
Go2rtc поддерживает несколько вариантов настройки стабильного внешнего доступа:
Если у пользователя статический или динамический публичный (белый) IP-адрес, он может настроить форвардинг специального TCP-порта для передачи медиаданных и добавить свой внешний IP-адрес в качестве кандидата на подключение.
Такой кандидат будет менее приоритетный, чем стандартные UDP-кандидаты. Но если у стандартных кандидатов не удастся подключиться, TCP-кандидат подключится гарантированно.
В случае серого IP-адреса (за NAT-провайдера) можно использовать автоматический туннель сервиса Ngrok. В бесплатном варианте сервис будет постоянно выдавать случайный внешний адрес. Но go2rtc умеет его определять и автоматически добавлять в кандидаты на подключение. Опять же с меньшим приоритетом.
Если у пользователя есть собственный VPS и соответствующие навыки, он может либо поднять свой SSH-туннель и использовать его вместо Ngrok-туннеля, либо установить свой TURN-сервер.
3. Двухсторонняя аудио-связь
Одной из важных фич, которая была заложена в архитектуру проекта, была поддержка двусторонней аудиосвязи с камерой.
Сейчас всё больше камер выпускается с динамиком. Часто для этой функции компании используют свои проприетарные протоколы. Но приличные производители недавно начали добавлять обратное аудио по протоколу ONVIF backchannel.
Поскольку технология WebRTC изначально создавалась для двухсторонней аудио- и видеосвязи, поддержка backchannel была лишь вопросом времени.
Один важный момент: для доступа к микрофону современные браузеры требуют наличия HTTPS на сайте.
4. Кодек H265 в браузерах Apple Safari
Похоже, go2rtc — это первый проект в мире, который начал поддерживать этот кодек в настоящем браузере для технологии WebRTC.
Для упаковки HEVC кодека в RTP-пакеты существует стандарт RFC 7798, которым успешно пользуются разработчики современных камер. Но инженеры Apple, как обычно у них принято, проигнорировали мировые стандарты и изобрели свой велосипед.
Написав небольшой прототип по получению видео с камеры iPhone по протоколу WebRTC, я нашёл закономерность формирования пакетов, описал её в посте на GitHub и реализовал поддержку в go2rtc.
Roborock Vacuum
Сразу после выхода релиза 1.2.0 мне написал один из пользователей с предложением рассмотреть поддержку стриминга видео с пылесосов Roborock и прислал данные своего аккаунта. Это отдельная забавная история, как одни пользователи готовы поделится доступом к мобильной роботизированной камере с двухсторонней аудиосвязью, а другие замазывают локальные IP-адреса в пересылаемых логах.
Спустя сутки весь протокол Roborock был как на ладони:
Для общения с умными устройствами используется обычный MQTT поверх SSL.
Все логины, пароли и топики хешируются.
Для каждого устройства используется один топик для передачи данных и один топик для приёма данных.
Сообщения передаются в формате JSON-RPC и также шифруются ключом устройства.
Для запуска видеопотока можно установить графический пин-код.
Видео передается по современному WebRTC (наконец-то в Китае начинают отказываться от P2P).
Так получилось, что ранее я имел опыт со всеми этими технологиями из перечня:
С шифрованием впервые столкнулся в проекте SonoffLAN. Однажды даже пришлось изучать реализацию AES-CBC на чистом python, потому что процессор Apple M1, работающий на архитектуре ARM в режиме эмуляции x86, выдаёт неправильные результаты шифрования при использовании собранных wheels-библиотек (кто бы мог подумать).
С JSON-RPC и MQTT был опыт в проекте XiaomiGateway3. Довольно популярный протокол MiOT от Xiaomi передаёт сообщения как раз в формате JSON-RPC. А MQTT активно используется последние несколько лет практически во всех шлюзах, разработанных совместно с компанией Lumi.
Первое знакомство с WebRTC началось ещё в 2020 году с проекта RTSP2WebRTC, а затем WebRTC Camera.
Где-то два дня ушло на реализацию Roborock API на языке Go. И ещё полдня на реализацию WebRTC-клиента.
Одним из плюсов было то, что пылесос находился за тысячи километров от меня и можно было качественно отладить удалённое подключение.
Первая проблема была в обмене ICE-кандидатами. Если перестараться с асинхронным подключением WebRTC, кандидаты начнут прилетать ещё до того, как участники инициализируют соединение. Такие кандидаты будут проигнорированы.
Вторая проблема была интереснее. В сообществе разработчиков WebRTC начали замечать, что в некоторых регионах России мифическим образом нарушается процесс установки безопасного DTLS-соединения. Забавно, что первым проблему обнародовал и починил разработчик из Китая.
WebRTC Client
Расположение пылесоса за несколько тысяч километров было как плюсом, так и минусом. Робот постоянно издавал звуковой сигнал о начале видеосвязи. А для тестирования стабильной работы таких подключений приходилось делать очень много. Плюс владелец пылесоса находился в совершенно другом часовом поясе.
В поисках удобных вариантов отладки мне в голову пришла довольно банальная идея — реализовать возможность подключения из одного go2rtc к другому go2rtc по протоколу WebRTC. Тут я решил не изобретать велосипеды и поискать готовые стандарты.
В сообществе разработчиков WebRTC подсказали варианты:
WHEP — стандарт для видеоплееров (потребителей медиа);
WHIP — стандарт для Broadcaster Software (поставщиков медиа).
К сожалению, стандарты ещё не приняты. Но популярные стриминговые проекты уже начинают их внедрять.
В итоге в go2rtc версии 1.3.0 попали функции:
Возможность забирать поток с стороннего ПО по стандарту WebRTC/WHEP;
Возможность принимать входящий поток с стороннего ПО по стандарту WebRTC/WHIP. Её я тестировал на OBS Studio. К сожалению, ментейнеры проекта не хотят выпускать релиз с этой функцией до принятия WHIP-стандарта. Но благодаря функции автоматической сборки релизов на GitHub уже можно скачать готовую сборку OBS Studio с поддержкой WebRTC.
WebTorrent
В процессе изучения всего вышеописанного я наткнулся на ещё одно ответвление WebRTC. Это технология WebTorrent.
Классические Torrent-трекеры помогают Torrent-клиентам обмениваться своими IP-адресами. После чего участники подключаются друг к другу напрямую для передачи полезной информации.
WebTorrent-трекер работает по той же логике. Только клиентами являются обычные браузеры. Для подключения к трекеру используется протокол WebSocket. Для подключения к другим клиентам используется протокол WebRTC, для передачи данных — RTCDataChannel.
Обмениваться файлами по этому протоколу могут только браузеры и некоторые настольные клиенты с поддержкой WebTorrent. Их ещё называют гибридными клиентами, потому что они, как правило, поддерживают BitTorrent-протокол и могут работать одновременно в обоих сетях.
Так ко мне пришла идея использовать открытые WebTorrent-трекеры для реализации функции внешнего доступа к камерам.
Для доступа к потоку требуется знать логин и пароль. Логин используется, чтоб найти именно вашу камеру среди всех торрентов на трекере. Пароль используется для шифрованного обмена информацией о подключении с сервером go2rtc. Так, чтоб даже владельцы трекера не могли получить к ней доступ.
Клиент для подключения написан в виде небольшой HTML-странички с использованием GitHub Pages. Ссылки на трансляцию выглядят примерно так:
https://alexxit.github.io/go2rtc/#share=hmcfzyls3aa7ofsi&pwd=v4j3h0l4guetk1zr
Современные браузеры не отправляют информацию после знака якоря в ссылке, так что логин и пароль не достанутся Гитхабу.
Go2rtc версии 1.3.0 позволяет быстро создавать и удалять случайные ссылки на трансляции. Такие ссылку будут работать до момента перезагрузки go2rtc. При желании можно прописать статические потоки в конфиге вручную.
Но и это ещё не всё. Ведь в новой версии go2rtc появился WebRTC-клиент. А значит, технологию WebTorrent можно использовать не только чтобы отдать поток, но и чтобы забрать удаленный поток назад в go2rtc.
Таким образом можно организовать подключение к камерам в закрытой удаленной сети. Бесплатно и безопасно.
Двухсторонний обмен
Хотя получение видео с пылесоса Roborock было написано всего за неделю, выпуск всего релиза занял немногим более месяца. Модель Roborock S7 MaxV поддерживала двустороннюю аудиосвязь. Никак нельзя было выпускать новый релиз без этой фичи.
Механизм стыковки медиапотоков внутри go2rtc является одной из самых сложных частей ядра проекта.
Когда у вас есть камера, которая только передает данные, тут нет ничего сложного. Вы просто читаете с неё пакеты в цикле и отправляете их тому, кто хочет получать эти данные. Например, браузеру. В этом случае камера является Producer (поставщиком данных), а браузер — Consumer (потребителем данных).
Но когда в камере появляется обратный канал (backchannel по терминологии ONVIF-стандарта) для передачи данных на динамик камеры, всё становится немного запутанней. Теперь браузер становится немного Producer и начинает слать данные с микрофона. А камера становится немного Consumer.
Начиная с версии 1.2.0, в go2rtc появилась функция стримить файлы и онлайн-аудио на устройства с поддержкой two way audio. При таком подключении камера вообще не будет являтся Producer, а будет лишь Consumer (потребителем).
Получается, в любой момент код, отвечающий за протоколы RTSP или WebRTC, может выступать как в роли получателя данных, так и в роли отправителя данных.
А ещё каждый из этих протоколов может быть как активным участником соединения (клиентом), так и пассивным участником соединения (сервером).
А ещё в протоколе RTSP принято указывать лишь одно направление для канала передачи медиа: только на приём — recvonly, только на передачу — sendonly. А вот в WebRTC довольно распространена передача в двух направлениях — sendrecv. Это довольно вредно для работы с камерами, потому что часто встречается, что камера передает звук в одном кодеке, а вот получает звук в другом кодеке.
В итоге при попытке отправить звук из браузера в пылесос на стыке двух подключений WebRTC внутри go2rtc я просто запутался в собственном коде. В результате чего взял паузу в пару недель и переписал ядро проекта.
Раньше за передачу медиаданных отвечал класс Track. Активный участник (producer) отдавал треки функцией GetTrack, а пассивный участник (consumer) подключал их к себе функцией AddTrack. При этом backchannel Track от активного участника не генерировал данные, а настраивался на их получение. Это очень путало и усложняло отладку в многофункциональных модулях вроде RTSP и WebRTC.
В новой версии класс Track был заменён классами Receiver (тот, кто получает данные от поставщика) и Sender (тот, кто отправляет данные потребителю). Функция GetTrack теперь стала создавать только Receiver-ов. Функция AddTrack стала принимать на вход этих Receiver-ов и подключать их к Sender-ам.
Интересный факт. Одного Receiver можно подключить к нескольким Sender. Эта функция активно используется, если у вас бюджетная камера. Они обычно не выдерживают одновременного подключения нескольких потребителей. И в этом случае go2rtc может выступать в роли прокси-балансировщика.
В случае backchannel к одному Sender можно подключить несколько Receiver. Например, если у вас есть активное подключение к камере с включенной двухсторонней связью, камера не даст выполнить второе подключение с включенным backchannel. А вот новая архитектура go2rtc позволяет обойти это ограничение.
Итоги
WebRTC можно использовать для стриминга видео с камер с задержкой менее 0.5 секунды.
WebRTC можно использовать не только в браузере, но и на серверах и на IoT-устройствах (например, пылесосах).
Можно как получать видео по протоколу WHEP, так и отправлять видео по протоколу WHIP.
Можно использовать протокол WebTorrent, чтоб расшарить WebRTC-поток, не открывая при этом публичный доступ к своему компьютеру или серверу.
Можно использовать протокол WebTorrent, чтоб забрать поток, находящийся в закрытой удалённой сети.
Можно превратить любой браузер PC или смартфона в IP-камеру (видеоняня в пару кликов).
WebRTC удобно использовать для two way audio связи.
В браузере Safari в WebRTC можно использовать кодек H265.
IoT-устройства не обязательно как-то взламывать, паять и прошивать, чтоб использовать их в своих домашних проектах. Все эти интеграции работают с оригинальной заводской прошивкой устройств: Sonoff, Xiaomi, Яндекс, TP-Link, Roborock.