HEVC (High Efficiency Video Coding — высокоэффективное кодирование видеоизображений), также известный как H.265, это видеокодек, широко используемый, в том числе, в системах видеонаблюдения. До недавнего времени веб браузеры практически не поддерживали этот формат. Но ситуация изменилась с выходом браузеров Chrome/Chromium версии 106. Это событие показалось мне достойным упоминания на Хабре, и в этой части статьи я расскажу, почему поддержка HEVC важна, о своих попытках подружить IP камеры с браузером и что из этого получилось. В первой части я рассказывал о проксировании видеопотоков, во второй — о мобильном приложении. В этой статье будут использованы данные из обеих частей.  Итак, по данным caniuse HEVC работает в браузерах на основе Chrome/Chromium не ниже 106 версии (для Linux — не ниже 108 версии) при наличии аппаратного ускорения видео.  Меня интересует, в первую очередь, Linux. Но тут придется немного повозиться. Мне удалось запустить аппаратное декодирование для интегрированных видеокарт Intel HD Graphics 530 и Iris Plus Graphics 655, только указав переменные окружения LIBVA_DRIVER_NAME и LIBVA_DRIVERS_PATH (пример для Дебиан): В Федоре путь другой: /usr/lib64/dri, в Арче — /usr/lib/dri. Кроме того, мне пришлось включить API vulkan в настройках chrome://flags/#enable‑vulkan (или можно запускать браузер с флагами ‑enable‑features=Vulkan,VaapiVideoDecoder). Конечно, в системе должен быть установлен нужный драйвер видеокарты. Вывод команды vainfo у меня выглядит примерно так: После этого на странице chrome://gpu/ браузера в разделе Video Acceleration Information должно появиться упоминание HEVC: К сожалению, это работало только в браузерах на основе Chromium 108. В 109-й версии поддержку libva в «никсах» сломали, и мне пока приходится пользоваться старой версией Brave (я его просто не обновляю). Следить за состоянием поддержки аппаратного декодирования можно, например, тут. В Windows и на Андроиде таких проблем я не наблюдал. Теперь, наконец, можно проверить, сможет ли браузер воспроизводить запись с IP камеры в формате H.265. Записываю тестовый фрагмент и открываю его в браузере — работает. Теперь можно рассмотреть варианты доставки видеопотока пользователю, используя только элемент <video>, без сторонних библиотек и зависимостей и не устанавливая на клиентское устройство никаких дополнительных программ и плагинов. Браузер может «нативно» воспроизводить HLS (HTTP Live Streaming) — протокол потоковой передачи от Apple, это не опечатка. Дело в том, что HLS поддерживает совместимый с MPEG‑DASH формат m4s. Записать такой поток можно примерно так: Полученные сегменты можно воспроизвести в браузере, используя MSE (Media Source Extensions).  API mse требует явного указания кодека, в этом примере — hev1.1.6.L120.0. Получить его можно так: Впрочем, в моем случае браузер не декодирует поток, поэтому сработает любой совместимый кодек. Аналогичным образом можно использовать и технологию MPEG‑DASH (Dynamic Adaptive Streaming over HTTP). Команда создаст похожую структуру (только strftime тут не поддерживается), сегменты так же будут записаны в файлы .m4s, для воспроизведения можно так же использовать MSE. Но для воспроизведения сегментов m4s необходим файл инициализации и плейлист, поэтому воспроизвести их напрямую не получится. Мне же хотелось иметь архив, который можно воспроизводить любыми подручными средствами, поэтому я стал поглядывать в сторону «чистого» mp4. Сегменты mp4 можно записывать, например, так: Их можно воспроизводить чем угодно, а в браузере не нужен даже MSE. Это меня устроило, и я написал небольшое веб приложение для записи и воспроизведения видеопотоков с камер в режиме почти реального времени. При создании приложения я использовал наработки проектов python-rtsp-server и Камеры. Не уверен, есть ли смысл дублировать листинги файлов здесь, исходники доступны на гитхабе. Пожалуй, детали реализации не так важны (в проекте около двадцати небольших файлов). Если возникнут вопросы, прошу задавать их в комментариях. Итак, поскольку я не буду использовать MSE, сегменты придется «склеивать» в непрерывный видеопоток. Просто менять атрибут src тега <video> не получится, даже если предварительно загружать сегменты в blob  — изображение при этом мигает. Поэтому для «бесшовного» воспроизведения я использовал два элемента <video>, расположенные друг над другом (с разными значениями z-index). По окончании воспроизведения сегмента элементы просто меняются местами. Атрибут src нижнего (невидимого) элемента меняется заранее, и браузер асинхронно выполняет предварительную загрузку изображения (preload). Это, пожалуй, единственная тонкость в клиентской части приложения. UPD. В реальных проектах использование MSE всё же предпочтительней из-за меньшей нагрузки на браузер при манипуляциях с DOM. В этом случае нужно дополнительно фрагментировать каждый сегмент (добавить к ffmpeg опцию "-segment_format_options movflags=faststart+frag_keyframe+empty_moov+default_base_moof"), а буфер MediaSource переключить в режим "sequence" для воспроизведения в порядке добавления сегментов. Вот что получилось в итоге: Линия с ползунком внизу экрана — шкала времени. По умолчанию ползунок находится в крайнем правом положении, что соответствует прямой трансляции. Доступ ко всему видеоархиву предоставляется с этого же экрана, точки на шкале времени помогают ориентироваться по дням. Здесь, пожалуй, нужно подробнее остановиться на упомянутой выше команде ffmpeg и том, что и как она делает. Ffmpeg просто записывает файлы сегментов, последовательно и непрерывно. Длительность каждого сегмента равна длине GOP (Group of Pictures ), а точнее, интервалу между ключевыми I-кадрами (intra-coded frame). Это минимально возможная длительность сегмента, задается она в настройках камеры и для моего парка камер обычно равна 4 секундам, что при частоте кадров 25 fps соответствует 100 кадрам. Таким образом, при заданной вложенности папок ГГГГММДД/ЧЧ/MM/CC.mp4 каждая конечная папка должна содержать 15 файлов CC.mp4. Эти файлы не удаляются в течение заданного в файле server/_config.py периода времени (по умолчанию 14 дней) и просто загружаются в элементы <video> клиентской стороны в нужном порядке. Клиенты не обращаются к камерам напрямую, а читают уже записанные статические сегменты. Таким образом выполняется одно из главных условий — к камере должно быть лишь одно подключение (в первой части статьи я упоминал, что камеры плохо поддерживают множественные подключения). Размер файла сегмента составляет в среднем 100...200 кБ в зависимости о освещенности сцены и активности движения. Это связано с особенностями алгоритма сжатия HEVC. Впрочем, к сжатию я вернусь позже, а сейчас важно то, что сегменты имеют небольшой размер, а это, в свою очередь, обеспечивает быструю загрузку видео в браузер. Очень быструю загрузку. В среднем в  локальной сети (а это наиболее типичный случай применения) со скоростью соединения 1 Гбит и при использовании SSD на сервере сегмент загружается около 50 мс. Это дает возможность менять кадры с частотой до 20 fps при движении ползунка времени, то есть фактически без видимых задержек вручную двигать время в нужном направлении и с нужной скоростью. Такая отзывчивость действительно впечатляет! Правда, при проксировании трафика через интернет скорость может значительно падать (у меня частота кадров опускалась до 2 fps). Это  же относится и к мобильным устройствам, у которых не хватает ни вычислительных ресурсов, ни ширины канала. Впрочем, загружается сегмент все равно значительно быстрее, чем при подключении по rtsp. Важным достоинством такой схемы является, помимо скорости, практически нулевая нагрузка и на сервер, и на клиента. Дело в том, что видеопоток передается от камеры до клиента без транскодирования. То есть, грубо говоря, видеопроцессор клиентского устройства воспроизводит видео в том виде, в котором его передает камера. Средняя нагрузка на процессор сервера, по моим наблюдениям, составляет около 0.05 % в зависимости от производительности оборудования. Это дает возможность свободно подключить к одному серверу более сотни камер. Объяснять важность снижения нагрузки на клиентское устройство, особенно на смартфон, я думаю, не нужно. При почти нулевой нагрузке и высокой скорости узким местом серверной стороны приложения становится место на диске, дефицит которого заставляет ограничивать либо количество камер, либо длительность видеоархива. И тут на помощь снова приходит HEVC, а точнее, его модификация H.265+ — cовременный метод компрессии в видеонаблюдении, впервые предложенный компанией Hikvision. Эта технология, в числе прочего, позволяет существенно (по данным компании на 30-50%) сэкономить место на жестком диске. Но и это еще не всё, позже я рассмотрю эту тему под другим углом, а сейчас вернусь к интерфейсу приложения. При ширине экрана 1920 пикселей и продолжительности видеоархива 14 дней точность позиционирования шкалы времени (под капотом там обычный <input type="range">) составляет около 10 минут. Это позволяет очень быстро найти нужное событие, если известно его время или событие оставило визуальный след. Быстро, но не очень точно. Чтобы добраться до нужного сегмента, можно воспользоваться дополнительными стрелками над шкалой времени.    Шкала перемотки почти логарифмическая: 1 сегмент (~4 секунды), 1 минута, 10 минут, 1час. Это позволяет попасть в нужное время за ограниченное число последовательных итераций. Молния в центре — переключатель ускоренного воспроизведения, а значок, похожий на Wi-Fi — детектор движения. Тут, пожалуй, стоит сделать еще одно отступление. Эксплуатирует ещё одну особенность алгоритма сжатия Н.265+. Дело в том, что упомянутый выше ключевой кадр, упрощенно говоря, содержит полное изображение сцены, а дальнейшие элементы GOP — лишь её изменение. Это означает, что при появлении в кадре движущегося объекта размер файла сегмента растет, и в случае сжатия Н.265+ растет настолько значительно, что это увеличение размера достаточно достоверно можно считать признаком движения. Поэтому в режиме «детектора движения» я просто сравниваю размер текущего файла с предыдущим и, в случае превышения порогового значения, такой сегмент попадает в на экран. Удивительно, но несмотря на примитивнейший алгоритм, это сработало. Безусловно, этот алгоритм можно и нужно улучшить, но в рамках моего эксперимента по велосипедостроению достаточно просто знать, что это возможно. Кстати, в режиме H.265+ камеры Hikvision не поддерживают (UPD: на самом деле, поддерживают, но с некоторыми ограничениями) доступ к API встроенной системы оповещения (по крайней мере, мои экземпляры), поэтому такой способ обнаружения вполне имеет право на жизнь. Получившееся приложение я оформил в виде PWA (progressive web app), в основном, ради мобильных устройств. Одним из обязательных требований для работы PWA является наличие валидного SSL сертификата. Но если для обычного интернет ресурса можно использовать, например, сертификат letsencrypt, то в локальной сети это проблематично. Ну и ладно, создам самозаверенный сертификат, примерно так: В этом примере известная корпорация «Рога и копыта» из страны ХХ выдает сертификат не менее известной организации Локалхост. После этого файлы  localhost.crt и  localhost.key нужно поместить в папку server приложения, а корневой сертификат rootCA.crt импортировать в браузер в разделе chrome://settings/security — Настроить сертификаты — Центры сертификации — Импорт. Конечно, ключ rootCA.key нужно хранить в максимально защищенном месте. Теперь в меню браузера должен появиться пункт «Установить приложение». Полную информацию о соответствии приложения требованиям PWA можно посмотреть, выполнив анализ страницы на вкладке Lighthouse в инструментах разработчика. Настройки приложения находятся в файле server/_config.py и, в целом, аналогичны настройкам python-rtsp-server. Настройки групп временно находятся в этом же файле. Поясню: группа объединяет несколько камер на одном экране, это важнейшая часть приложения. Позволяет одновременно показывать объект с нескольких ракурсов или несколько объектов. Это самый востребованный экран для оперативного выяснения обстановки. Приложение целиком закрыто авторизацией. В конфигурации задается мастер-пароль для доступа ко всем камерам и группам и пароль для доступа к отдельным камерам. Для тестирования мне этого достаточно. Прошу не считать сказанное выше непреложной истиной — это всего лишь моё личное мнение, основанное на личном опыте. Приложение находится в стадии раннего альфа-тестирования и ни в коем случае не готово к промышленной эксплуатации. Но у него уже есть очевидные преимущества и недостатки. Чтобы было понятней, сравню cams-pwa с предыдущим проектом Камеры. Преимущества: Несмотря на статус эксперимента, в этом приложении уже сейчас больше функционала. По сути, это «киллер-фича» проекта. Похоже, получив свободный доступ к видеоархиву, моя мини-фокус-группа не собирается возвращаться к предыдущей версии:) Работает на любых устройствах, где можно установить свежий Хром. Скорость. Подключение по протоколу rtsp может «зависнуть», пока не будет получен ключевой кадр. В зависимости от длины GOP, подключение может занимать более 10 секунд. Это много. Проксирование через сегменты гарантирует почти мгновенное получение картинки. Простые настройки на стороне клиента. Пользователю не нужно вводить страшные rtsp адреса, айпи, каналы и прочие непонятные штуки. «Нативное» масштабирование. Из‑за особенностей реализации плагина libvlc, используемого в Камерах, поверхность изображения увеличивается «как есть». Это приводит к тому, что при просмотре камер высокого разрешения на устройствах с более низкой плотностью пикселей увеличенное изображение смазывается. Масштабирование средствами браузера, в числе прочего, решает эту проблему. Недостатки: Требуется серверная часть и специалист для её настройки. Более высокая задержка (отставание) воспроизведения в «прямом эфире». Поскольку в этом проекте воспроизводится последний законченный файл, задержка составляет 1 GOP относительно реального времени. Пока я с этим ничего не делал. Не очень критично из-за того, что отчасти компенсируется временем на открытие приложения, но всё же требует внимания. P.S. Сохранение сегментов средствами ffmpeg чувствительно к качеству оборудования, надёжности питания, сетевому соединению и даже производительности файловой системы сервера (но это не точно), особенно при адаптивных алгоритмах сжатия. Ffmpeg имеет тенденцию портить ключевой кадр сегмента. Визуально это проявляется в виде цветных пятен на нижней части изображения или его пикселизации. В равной степени относится к MP4, HLS и DASH. Исправляется принудительным переключением на TCP командой ffmpeg -rtsp_transport tcp -i rtsp://... для камер, поддерживающих этот режим. *** Ну вот, теперь, кажется, предупредил обо всём. Если что-то забыл или в чём-то ошибся, прошу высказываться в комментариях. Конструктивная критика, замечания, пожелания и предложения, как всегда, приветствуются! UPD: дублирую ссылку на github здесь, чтобы не искать по тексту.  Разработчик