Удаленный доступ к IP камерам. Часть 3. HEVC и web

Экран «группы». Воспроизводит несколько камер одновременноЭкран «группы». Воспроизводит несколько камер одновременно

HEVC (High Efficiency Video Coding — высокоэффективное кодирование видеоизображений), также известный как H.265, это видеокодек, широко используемый, в том числе, в системах видеонаблюдения. До недавнего времени веб браузеры практически не поддерживали этот формат. Но ситуация изменилась с выходом браузеров Chrome/Chromium версии 106. Это событие показалось мне достойным упоминания на Хабре, и в этой части статьи я расскажу, почему поддержка HEVC важна, о своих попытках подружить IP камеры с браузером и что из этого получилось.

В первой части я рассказывал о проксировании видеопотоков, во второй — о мобильном приложении. В этой статье будут использованы данные из обеих частей.

Итак, по данным caniuse HEVC работает в браузерах на основе Chrome/Chromium не ниже 106 версии (для Linux — не ниже 108 версии) при наличии аппаратного ускорения видео.

87b40cd8de0251ab30a8d4d6356e3758.jpg

Меня интересует, в первую очередь, Linux. Но тут придется немного повозиться. Мне удалось запустить аппаратное декодирование для интегрированных видеокарт Intel HD Graphics 530 и Iris Plus Graphics 655, только указав переменные окружения LIBVA_DRIVER_NAME и LIBVA_DRIVERS_PATH (пример для Дебиан):

export LIBVA_DRIVER_NAME=iHD
export LIBVA_DRIVERS_PATH=/usr/lib/x86_64-linux-gnu/dri

В Федоре путь другой: /usr/lib64/dri, в Арче — /usr/lib/dri.

Кроме того, мне пришлось включить API vulkan в настройках chrome://flags/#enable-vulkan (или можно запускать браузер с флагами --enable-features=Vulkan, VaapiVideoDecoder).

Конечно, в системе должен быть установлен нужный драйвер видеокарты. Вывод команды vainfo у меня выглядит примерно так:

$ vainfo 
Trying display: wayland 
vainfo: VA-API version: 1.17 (libva 2.17.1) 
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 23.1.0 () 
vainfo: Supported profile and entrypoints
    ...
    VAProfileHEVCMain: VAEntrypointVLD 
    VAProfileHEVCMain: VAEntrypointEncSlice 
    VAProfileHEVCMain: VAEntrypointFEI 
    VAProfileHEVCMain10: VAEntrypointVLD 
    VAProfileHEVCMain10: VAEntrypointEncSlice 
    ...

После этого на странице chrome://gpu/ браузера в разделе Video Acceleration Information должно появиться упоминание HEVC:

9534cb126962055ec547c12f04f1b964.jpg

К сожалению, это работало только в браузерах на основе Chromium 108. В 109-й версии поддержку libva в «никсах» сломали, и мне пока приходится пользоваться старой версией Brave (я его просто не обновляю). Следить за состоянием поддержки аппаратного декодирования можно, например, тут. В Windows и на Андроиде таких проблем я не наблюдал.

Тестирование MP4

Теперь, наконец, можно проверить, сможет ли браузер воспроизводить запись с IP камеры в формате H.265. Записываю тестовый фрагмент

ffmpeg -i rtsp://:554 -c copy -t 10 265.mp4

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

HLS, MPEG-DASH и MSE

Браузер может «нативно» воспроизводить HLS (HTTP Live Streaming) — протокол потоковой передачи от Apple, это не опечатка. Дело в том, что HLS поддерживает совместимый с MPEG-DASH формат m4s. Записать такой поток можно примерно так:

ffmpeg -i rtsp://:554 -c:v copy -an -strftime 1 -strftime_mkdir 1 \
-f hls -hls_segment_type fmp4 -hls_segment_filename %Y%m%d/%H%M%S.m4s live.m3u8

Полученные сегменты можно воспроизвести в браузере, используя MSE (Media Source Extensions).

Пример

const baseUrl = 'http://0.0.0.0:8000';
const initUrl = baseUrl + '/init.mp4';
const templateUrl = baseUrl + '/20230201/$dt$.m4s';
const sourceType = 'video/mp4; codecs="hev1.1.6.L120.0"';
let datetime = '095227';

const mediaSource = new MediaSource();

if ('MediaSource' in window && MediaSource.isTypeSupported(sourceType)) {
    mediaSource.addEventListener('sourceopen', onSourceOpen, { once: true });
    window.video.src = URL.createObjectURL(mediaSource);
} else {
    console.error('Unsupported MIME type or codec: ', sourceType);
}

function onSourceOpen() {
    const sourceBuffer = mediaSource.addSourceBuffer(sourceType);

    fetch(initUrl)
        .then(response => response.arrayBuffer())
        .then(data => {
            sourceBuffer.appendBuffer(data);
            sourceBuffer.addEventListener('updateend', fetchNextSegment, { once: true });
        });

}

function fetchNextSegment() {
    fetch(templateUrl.replace('$dt$', datetime))
        .then(response => response.arrayBuffer())
        .then(data => {
            const sourceBuffer = mediaSource.sourceBuffers[0];
            sourceBuffer.appendBuffer(data);
            // TODO: Fetch further segment and append it.
        });
}

API mse требует явного указания кодека, в этом примере — hev1.1.6.L120.0. Получить его можно так:

MP4Box -info init.mp4  2>&1 | grep RFC6381 | awk '{print $4}' | paste -sd , -

Впрочем, в моем случае браузер не декодирует поток, поэтому сработает любой совместимый кодек.

Аналогичным образом можно использовать и технологию MPEG-DASH (Dynamic Adaptive Streaming over HTTP). Команда

ffmpeg -i rtsp://:554 -c:v copy -an -f dash -t 10 playlist.mpd

создаст похожую структуру (только strftime тут не поддерживается), сегменты так же будут записаны в файлы .m4s, для воспроизведения можно так же использовать MSE.

Но для воспроизведения сегментов m4s необходим файл инициализации и плейлист, поэтому воспроизвести их напрямую не получится. Мне же хотелось иметь архив, который можно воспроизводить любыми подручными средствами, поэтому я стал поглядывать в сторону «чистого» mp4.

MP4

Сегменты mp4 можно записывать, например, так:

ffmpeg -i rtsp://:554 -c:v copy -an -f segment -reset_timestamps 1 \
-strftime 1 %Y%m%d/%H/%M/%S.mp4

Их можно воспроизводить чем угодно, а в браузере не нужен даже MSE. Это меня устроило, и я написал небольшое веб приложение для записи и воспроизведения видеопотоков с камер в режиме почти реального времени.

При создании приложения я использовал наработки проектов python-rtsp-server и Камеры. Не уверен, есть ли смысл дублировать листинги файлов здесь. Пожалуй, детали реализации не так важны (в проекте около двадцати небольших файлов). Если возникнут вопросы, прошу задавать их в комментариях.

Итак, поскольку я не буду использовать MSE, сегменты придется «склеивать» в непрерывный видеопоток. Просто менять атрибут src тега

Вот что получилось в итоге:

Экран камеры. Изображение заменено на тестовое видео «Big Buck Bunny» с официального канала Blender FoundationЭкран камеры. Изображение заменено на тестовое видео «Big Buck Bunny» с официального канала Blender Foundation

Линия с ползунком внизу экрана — шкала времени. По умолчанию ползунок находится в крайнем правом положении, что соответствует прямой трансляции. Доступ ко всему видеоархиву предоставляется с этого же экрана, точки на шкале времени помогают ориентироваться по дням. Здесь, пожалуй, нужно подробнее остановиться на упомянутой выше команде ffmpeg и том, что и как она делает.

Файлы

Ffmpeg просто записывает файлы сегментов, последовательно и непрерывно. Длительность каждого сегмента равна длине GOP (Group of Pictures), а точнее, интервалу между ключевыми I-кадрами (intra-coded frame). Это минимально возможная длительность сегмента, задается она в настройках камеры и для моего парка камер обычно равна 4 секундам, что при частоте кадров 25 fps соответствует 100 кадрам. Таким образом, при заданной вложенности папок ГГГГММДД/ЧЧ/MM/CC.mp4 каждая конечная папка должна содержать 15 файлов CC.mp4.

Эти файлы не удаляются в течение заданного в файле server/_config.py периода времени (по умолчанию 14 дней) и просто загружаются в элементы

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

Размер файла сегмента составляет в среднем 100…200 кБ в зависимости о освещенности сцены и активности движения. Это связано с особенностями алгоритма сжатия HEVC. Впрочем, к сжатию я вернусь позже, а сейчас важно то, что сегменты имеют небольшой размер, а это, в свою очередь, обеспечивает быструю загрузку видео в браузер. Очень быструю загрузку. В среднем в локальной сети (а это наиболее типичный случай применения) со скоростью соединения 1 Гбит и при использовании SSD на сервере сегмент загружается около 50 мс. Это дает возможность менять кадры с частотой до 20 fps при движении ползунка времени, то есть фактически без видимых задержек вручную двигать время в нужном направлении и с нужной скоростью. Такая отзывчивость действительно впечатляет!

Правда, при проксировании трафика через интернет скорость может значительно падать (у меня частота кадров опускалась до 2 fps). Это же относится и к мобильным устройствам, у которых не хватает ни вычислительных ресурсов, ни ширины канала. Впрочем, загружается сегмент все равно значительно быстрее, чем при подключении по rtsp.

Нагрузка

Важным достоинством такой схемы является, помимо скорости, практически нулевая нагрузка и на сервер, и на клиента. Дело в том, что видеопоток передается от камеры до клиента без транскодирования. То есть, грубо говоря, видеопроцессор клиентского устройства воспроизводит видео в том виде, в котором его передает камера. Средняя нагрузка на процессор сервера, по моим наблюдениям, составляет около 0.05% в зависимости от производительности оборудования. Это дает возможность свободно подключить к одному серверу более сотни камер.

Объяснять важность снижения нагрузки на клиентское устройство, особенно на смартфон, я думаю, не нужно.

Место на диске

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

И тут на помощь снова приходит HEVC, а точнее, его модификация H.265+ — cовременный метод компрессии в видеонаблюдении, впервые предложенный компанией Hikvision. Эта технология, в числе прочего, позволяет существенно (по данным компании на 30–50%) сэкономить место на жестком диске. Но и это еще не всё, позже я рассмотрю эту тему под другим углом, а сейчас вернусь к интерфейсу приложения.

Интерфейс

При ширине экрана 1920 пикселей и продолжительности видеоархива 14 дней точность позиционирования шкалы времени (под капотом там обычный ) составляет около 10 минут. Это позволяет очень быстро найти нужное событие, если известно его время или событие оставило визуальный след. Быстро, но не очень точно. Чтобы добраться до нужного сегмента, можно воспользоваться дополнительными стрелками над шкалой времени.

Шкала перемотки почти логарифмическая: 1 сегмент (~4 секунды), 1 минута, 10 минут, 1час. Это позволяет попасть в нужное время за ограниченное число последовательных итераций.

Молния в центре — переключатель ускоренного воспроизведения, а значок, похожий на Wi-Fi — детектор движения. Тут, пожалуй, стоит сделать еще одно отступление.

Детектор движения

Эксплуатирует ещё одну особенность алгоритма сжатия Н.265+. Дело в том, что упомянутый выше ключевой кадр, упрощенно говоря, содержит полное изображение сцены, а дальнейшие элементы GOP — лишь её изменение. Это означает, что при появлении в кадре движущегося объекта размер файла сегмента растет, и в случае сжатия Н.265+ растет настолько значительно, что это увеличение размера достаточно достоверно можно считать признаком движения. Поэтому в режиме «детектора движения» я просто сравниваю размер текущего файла с предыдущим и, в случае превышения порогового значения, такой сегмент попадает в на экран. Удивительно, но несмотря на примитивнейший алгоритм, это сработало. Безусловно, этот алгоритм можно и нужно улучшить, но в рамках моего эксперимента по велосипедостроению достаточно просто знать, что это возможно.

Кстати, в режиме H.265+ камеры Hikvision не поддерживают доступ к API встроенной системы оповещения (по крайней мере, мои экземпляры), поэтому такой способ обнаружения изменения вполне имеет право на жизнь.

PWA и SSL

Получившееся приложение я оформил в виде PWA (progressive web app), в основном, ради мобильных устройств. Одним из обязательных требований для работы PWA является наличие валидного SSL сертификата.Но если для обычного интернет ресурса можно использовать, например, сертификат letsencrypt, то в локальной сети это проблематично. Ну и ладно, создам самозаверенный сертификат, примерно так:

sudo openssl genrsa -out rootCA.key 4096
sudo openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 \
    -subj "/C=ХХ/L=Nsk/O=R&K/OU=R&K/CN=R&K" -out rootCA.crt
sudo chown $(whoami):$(whoami) rootCA.key
openssl genrsa -out localhost.key 2048
openssl req -new -sha256 -key localhost.key \
    -subj "/C=ХХ/L=Nsk/O=localhost/OU=localhost/CN=localhost" -out localhost.csr
openssl x509 -req -sha256 -in localhost.csr -out localhost.crt -days 3650 \
    -CAkey rootCA.key -CA rootCA.crt -CAcreateserial -extensions SAN \
    -extfile <(printf "[SAN]\nsubjectAltName=DNS:localhost,DNS:ваш-домен,IP:127.0.0.1,IP:ваш-ip")
sudo chown root:root rootCA.key

В этом примере известная корпорация «Рога и копыта» из страны ХХ выдает сертификат не менее известной организации Локалхост. После этого файлы localhost.crt и localhost.key нужно поместить в папку server приложения, а корневой сертификат rootCA.crt импортировать в браузер в разделе chrome://settings/security — Настроить сертификаты — Центры сертификации — Импорт. Конечно, ключ rootCA.key нужно хранить в максимально защищенном месте.

Теперь в меню браузера должен появиться пункт «Установить приложение». Полную информацию о соответствии приложения требованиям PWA можно посмотреть, выполнив анализ страницы на вкладке Lighthouse в инструментах разработчика.

Организация приложения

Настройки приложения находятся в файле server/_config.py и, в целом, аналогичны настройкам python-rtsp-server.

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

Приложение целиком закрыто авторизацией. В конфигурации задается мастер-пароль для доступа ко всем камерам и группам и пароль для доступа к отдельным камерам. Для тестирования мне этого достаточно.

Итоги

Прошу не считать сказанное выше непреложной истиной — это всего лишь моё личное мнение, основанное на личном опыте. Приложение находится в стадии раннего альфа-тестирования и ни в коем случае не готово к промышленной эксплуатации. Но у него уже есть очевидные преимущества и недостатки. Чтобы было понятней, сравню cams-pwa с предыдущим проектом Камеры.

Преимущества

1. Несмотря на статус эксперимента, в этом приложении уже сейчас больше функционала. По сути, это «киллер-фича» проекта. Похоже, получив свободный доступ к видеоархиву, моя мини-фокус-группа не собирается возвращаться к предыдущей версии:)

2. Работает на любых устройствах, где можно установить свежий Хром.

3. Скорость. Подключение по протоколу rtsp может «зависнуть», пока не будет получен ключевой кадр. В зависимости от длины GOP, подключение может занимать более 10 секунд. Это много. Проксирование через сегменты гарантирует почти мгновенное получение картинки.

4. Простые настройки на стороне клиента. Пользователю не нужно вводить страшные rtsp адреса, айпи, каналы и прочие непонятные штуки.

5. «Нативное» масштабирование. Из-за особенностей реализации плагина libvlc, используемого в Камерах, поверхность изображения увеличивается «как есть». Это приводит к тому, что при просмотре камер высокого разрешения на устройствах с более низкой плотностью пикселей увеличенное изображение смазывается. Масштабирование средствами браузера, в числе прочего, решает эту проблему.

Недостатки

1. Требуется серверная часть и специалист для её настройки.

2. Более высокая задержка воспроизведения в «прямом эфире». Поскольку в этом проекте воспроизводится последний законченный файл, задержка может составлять от одного до двух GOP. Пока я с этим ничего не делал. Не очень критично из-за того, что отчасти компенсируется временем на открытие приложения, но всё же требует внимания.

3. Нет звука. Для записи аудиодорожки нужен отдельный канал, этот функционал я (пока) не реализовал.

4. Сохранение сегментов средствами ffmpeg чувствительно к качеству оборудования, надёжности питания, сетевому соединению и даже производительности файловой системы сервера (но это не точно), особенно при адаптивных алгоритмах сжатия. Ffmpeg имеет тенденцию портить ключевой кадр сегмента. Визуально это проявляется в виде цветных пятен на нижней части изображения или его пикселизации. В равной степени относится к MP4, HLS и DASH.

***

Ну вот, теперь, кажется, предупредил обо всём. Если что-то забыл или в чём-то ошибся, прошу высказываться в комментариях. Конструктивная критика, замечания, пожелания и предложения, как всегда, приветствуются!

© Habrahabr.ru