Началась эта история чуть менее 10 лет назад, и началась она с того, что однажды мне пришла в голову идея, что было бы неплохо иметь возможность наблюдать за квартирой, пока находишься в длительных отъездах, например в отпуске. Тогда задача виделась достаточно тривиальной, но в итоге растянулась на долгие годы. Ниже я постараюсь рассказать о всех этапах что я прошел на пути к текущему (и не факт, что финальному) решению.

Итерация первая, IP камеры будет достаточно

Самое первое и самое очевидное что нужно было сделать - приобрести IP камеру. Уже тогда было понимание что использование аналоговой камеры будет неправильным решением, поскольку требует дополнительного, и достаточно громоздкого, оборудования. В случае же IP камеры, было предположение что ее одной будет достаточно. Ну что ж, иду в ближайший магазин и приобретаю практически первое что попадается под руку и стоит вменяемых денег. Приношу домой, подключаю, настраиваю - и приходит озарение: в той же самой локальной сети получить доступ к видео потоку не проблема, а как же получить к нему доступ извне? Открывать порты и открывать потенциально дырявое устройство всему миру? - бред, ибо как раз в то время шумел по всему миру ботнет Mirai. Да и заставлять конечного пользователя лезть в настройки роутера - очень неправильный (и потенциально опасный) подход. А значит нужен некий дополнительный элемент для обеспечения доступа извне к камере внутри закрытой сети 😒

Итерация вторая, патчу GStreamer RTSP server

Ну что ж, надо, значит надо. Приобретенная камера поддерживает доступ по протоколу RTSP, а это весьма популярный протокол, и как следствие, имеет смысл строить решение вокруг него. И первое что приходит в голову, после ознакомления с RTSP протоколом - мне нужен некий сервер для размещения где-нибудь в облаке, и умеющий с одной стороны получать видео поток с камеры, а с другой выдавать этот же поток пользователю. Но здесь снова возникает вопрос, открывать порты очень плохая идея, а значит в локальной сети нужен некий активный агент, который, с одной стороны, будет подключаться к камере и получать видео поток с камеры, а с другой, посылать этот же поток на сервер 😒 Изобретать свой протокол для общения между сервером и агентом сильно не хочется, а значит смотрим внимательно на описание RTSP протокола и пытаемся найти решение. После внимательного изучения я наткнулся на упоминание RTSP команды RECORD. Что же она дает? - а как раз то что мне нужно, возможность с клиентского устройства посылать видео поток на сервер! А RTSP оказался гораздо мощнее чем я привык о нем думать!

Ну допустим, а что с сервером то? Неужели самому писать? - похоже на то 😒 Никаких готовых решений на тот момент я не нашел, может плохо искал, а может и не особо хотел найти (был азарт "запилить велосипед", я-ж программист!). Хорошо, значит нужно искать хоть что-то, что позволило бы не реализовывать свой RTSP сервер с нуля (что с большой долей вероятности отодвинуло бы сроки завершения проекта в бесконечность). Поскольку к тому моменту у меня уже был некоторый опыт работы с GStreamer, первое что я сделал - проверил, нет ли чего готового к использованию там? Оказалось есть! Не в самом GStreamer, но в библиотеке поддерживаемой той же командой - GStreamer RTSP Server. Удача! Второй удачей оказалась наличие поддержки RECORD в этой библиотеке. Но обнаружилась и ложка дегтя - текущая реализация не позволяет перенаправлять видео полученное по RECORD другим клиентам 😒 Значит придется делать то чего сильно не хотелось - патчить GStreamer RTSP Server для добавления нужного мне функционала. Долго ли коротко ли, но мне удалось это сделать! И оно даже работало 🥳 Но беда пришла откуда не ждали - пока я ковырялся с реализацией новой логики, вышла новая версия библиотеки 😒 Попытка сделать git rebase на master бранч (а это обязательное условия для возможной последующей подготовки патча для отправки в основной репозиторий) показала что в основном проекте произошли существенные изменения, и обойтись механической подгонкой моих изменений не получается. Пришло понимание, что если я буду двигаться этим же путем, то ситуация с большой долей вероятности повторится, а значит нужно искать другое решение 😒

Итерация третья, не патчу GStreamer RTSP server, но использую gst-interpipe

Эх, ну что ж... Пытаюсь найти решение позволяющее передавать видео потоки между GStreamer пайплайнами (pipeline - это собранная и настроеная цепочка из нескольких GStreamer элементов для обработки видео или аудио потоков). Такой подход позволил бы мне совсем не менять GStreamer RTSP Server и выполнить реализацию полностью на уровне моего приложения. Некоторое количество поисков в интернете привело меня к библиотеке под названием gst-interpipe от RidgeRun - она предназначена как раз для решения упомянутой выше проблемы. Отлично, пишу соответствующий код и в конечном итоге получаю нечто что даже работает 🥳

Так, часть вопросов решена. Но есть нюанс, в текущей реализации возможен доступ к видео потоку извне в произвольный момент времени только если сервер получает видео поток со стороны агента 24*7, т.е. даже если нет ни одного клиента. Жуткий расход сетевого трафика в пустую. Плохо, очень плохо! 😒 А значит нужен некий канал для общения между сервером и агентом, для извещения последнего о подключении клиента.

Эх... Беру asio, Google protobuf и добавляю недостающий функционал. Долго ли, коротко ли, но в итоге получается очередное нечто что даже работает🥳

Отлично, остался последний вопрос - а смотреть то как? В текущей реализации везде RTSP, соответственно браузер не может быть использован в качестве клиента (NPAPI интерфейс уже благополучно похоронен к этому времени). Можно конечно использовать любой медиа проигрыватель типа VLC, но UX (user experience) такого подхода просто ужасен. И даже это не все проблемы. В конечном итоге мне гарантировано понадобится механизм аутентификации для предотвращения доступа к личным видео потокам со стороны посторонних, а RTSP протокол не поддерживает шифрования. Существует конечно модификация протокола с поддержкой шифрования - RTSPS, но он очень мало распространен, да и вопрос с поддержкой браузеров как основного инструмента потребления контента остается нерешенным 😒

Итерация четвертая, мне нужен WebRTC!

Так как же мне получить возможность использовать в качестве клиента браузер? Смотрю на список поддерживаемых браузерами протоколов и понимаю что самым адекватным вариантом является WebRTC:
1. Поддерживается всеми современными браузерами
2. Изначально решен вопрос обхода NAT
3. Шифрует видео потоки по умолчанию
4. Имеет минимальную задержку (latency)

Ну допустим, а как же преобразовать RTSP в WebRTC? Долгие поиски решения привели меня к Open Source проекту под названием Janus Gateway (ныне Janus WebRTC Server). Почему именно он? - во-первых, он написан на языке C (не возникнет лишних сложностей при необходимости его модификации), во-вторых, стандартная поставка содержит streaming плагин помимо прочего умеющий транслировать видео поток IP камеры в браузер. Отлично, провожу некоторое количество экспериментов с ним и выясняю дополнительный положительный момент - streaming плагин не делает абсолютно никакого перекодирования видео потока при его отправке в браузер (что сильно снижает требования к оборудованию), все что необходимо - это поддержка камерой h264 кодека для кодирования видео (на самом деле h264 baseline profile, но плагин делает кой-какой трюк, благодаря которому браузер начинает понимать не только baseline profile).

Ну что ж, отлично, лезу в исходники streaming plugin и понимаю что все не так радужно. Во-первых, streaming плагин требует прямого доступа к RTSP источнику (здравствуй port forwarding на роутере). Во-вторых, плагин использует бесконечное воспроизведение даже если нет ни одного просматривающего клиента (здравствуй расход трафика в пустую). Значит придется опять писать код. Поскольку GStreamer показал себя как весьма гибкий инструмент, принимаю решение интегрировать его с Janus. Долго ли, коротко ли, но в итоге получается очередное нечто что даже работает🥳

И тут приходит очередная новость - в GStreamer добавляют поддержку протокола WebRTC... Да сколько можно то! Снова все переделывать!

Итерация пятая, мне нужен свой протокол!

Так чем-же меня не устраивал Janus? Причин несколько:
1. Использовать полноценный сервер рассчитанный на сотни (а то и тысячи) одновременных подключений там где будет от силы пара - это явное "из пушки по воробьям"
2. Janus - это отдельный процесс, управление которым добавит дополнительных сложностей в конечное приложение, а в данном конкретном случае смысла в этом нет абсолютно никакого, т.к. в рамках монолита реализовать логику однозначно проще, а с учетом предполагаемого количества клиентов (даже если это будут десятки) задумываться о горизонтальном масштабировании смысла нет никакого, достаточно вертикального.

Итак, принимаю решение о реализации очередного велосипеда использующего WebRTC в качестве протокола передачи видео и webrtcbin из GStreamer в качестве конкретной его реализации. Но есть нюанс, если сравнивать WebRTC с RTSP можно увидеть явное концептуальное отличие: в описании WebRTC нет никакой конкретики касающейся способа передачи информационных сообщений между участниками. В RTSP для этого есть явно определенный стандартом набор команд, а WebRTC описывает исключительно вопросы касающиеся передачи собственно видео/аудио/данных. Реализация механизма обмена сообщений для всех вопросов согласования отдается на откуп конкретному приложению. А значит мне нужен свой протокол для обмена сообщениями. Но зачем изобретать очередной велосипед? Почему бы не взять за основу то что себя уже давно зарекомендовало? А именно набор команд из RTSP и создать на их основе реализацию адаптированную для использования с WebRTC? То что в итоге получилось я недолго думая назвал WebRTSP (как объединение RTSP и WebRTC) и как оно конкретно выглядит я расскажу в следующей части.

Продолжение следует...