Подключение видеокамер

Камера в платформе — это коннектор с адресом RTSP и тег типа «Видеопоток». Кадры не проходят через RabbitMQ и не пишутся в PostgreSQL или VictoriaMetrics. Их принимает сервис подхвата: один сеанс RTSP на камеру и архив на диске. Сервис отдачи забирает у него байты по HTTP и отдаёт их платформе.

Оба сервиса входят в обычный запуск ./run_one_app.sh. Подхват — контейнер prs-video-capture, архив лежит в томе /var/lib/peresvet/video. Отдача — prs-video-delivery, тома архива у неё нет. Порты обоих контейнеров наружу не публикуются.

Два сервиса

Подхват и отдача наследуют AppSvc. Список nodes — поле конфигурации процесса (AppSvcSettings). Это не узел каталога и не атрибут LDAP.

  • Подхват обслуживает коннекторы-камеры. Пустой nodes значит все камеры. Процесс держит RTSP, пишет архив, говорит с OnVif и открывает MQTT-сессию индикатора связи. Свой адрес для соседей он берёт из http_base_url и сообщает его коротким RPC video.capture.lookup.<id коннектора>.

  • Отдача обслуживает теги. Пустой nodes значит все теги видеопотока. На prsTag.model она не подписывается: голос в создании и удалении тега ей не принадлежит. Адрес она сообщает RPC video.delivery.locate.<id тега>.

Браузер ходит только в /v1/data/ и /v1/ptz/. Платформа проверяет право, по RPC узнаёт отдачу и проксирует запрос по HTTP. Отдача так же узнаёт подхват и забирает поток, звук, снимок или фрагмент по HTTP внутри сети compose. В теле сообщения брокера только идентификатор и базовый URL.

Один id камеры обслуживает один подхват. Либо один процесс с пустым nodes, либо несколько с непересекающимися списками. Два процесса на одну камеру откроют два сеанса RTSP. Два процесса с одним и тем же первым id в nodes делят одну очередь брокера.

При непустом nodes базовый AppSvc не подписывает подхват на создание коннектора. Подхват подписывается на prsConnector.model.created сам и оставляет только камеры из списка. Правка хранилища видео приходит сообщением prsDataStorage.model.created, updated.<id> или deleted.<id> и применяется сразу. При старте подхват один раз читает уже существующие камеры: брокер не повторяет старые события.

Контракт GET /v1/data/ и POST /v1/ptz/ разобран в Видеопоток (тег типа 6). Здесь — порядок действий в конфигураторе.

Что нужно от камеры

  • Поток rtsp:// или rtsps://. Адрес пути смотрите в паспорте камеры. Логин и пароль можно вписать в сам адрес: rtsp://user:password@192.168.1.72:554/live.

  • Для поворота, зума и параметров изображения — OnVif. Хост берётся из адреса RTSP. Порт по умолчанию 8899, его меняют ключом onvifPort.

  • Платформа уже запущена, конфигуратор открыт.

Коннектор

  1. В папке «Коннекторы» создайте коннектор.

  2. Дайте ему имя, например rear-cam.

  3. В поле Конфигурация вставьте JSON и нажмите Записать.

{
  "source": {
    "rtspUrl": "rtsp://192.168.1.72:554/live/ch00_0",
    "rtspTransport": "tcp",
    "onvifPort": 8899,
    "onvifUser": "",
    "onvifPassword": ""
  },
  "log": {
    "level": "INFO",
    "fileName": "logs/prs_connector_{id}.log",
    "maxBytes": 10485760,
    "backupCount": 10,
    "sendToPlatform": false
  }
}
Конфигурация коннектора-камеры

Как у остальных коннекторов, параметры источника лежат в source, а журнал — в log. Ключ source.rtspUrl обязателен. По нему платформа и считает коннектор камерой. Плоский JSON, где rtspUrl стоит в корне, тоже принимается. Дополнительно можно выставить код типа узла 10 — этого достаточно, чтобы узел считался камерой, но адрес потока код не заменяет.

Ключ log тот же, что у коннектора в prs-connector-core.

Ключ

Смысл

level

Минимальный уровень строк файла: DEBUG, INFO, WARNING, ERROR. По умолчанию INFO.

fileName

Путь к файлу журнала на машине подхвата. {id} заменяется на id коннектора. Пустое значение — logs/prs_connector_<id>.log.

maxBytes

Максимальный размер одного файла в байтах. По умолчанию 10485760.

backupCount

Сколько предыдущих файлов хранить. По умолчанию 10.

sendToPlatform

Флажок Лог в платформу. Пока выключен, строки остаются в файле. Когда включён, те же строки уходят сообщением prsConnector.log_line и видны на вкладке Лог коннектора.

Строки декодера ffmpeg в этот журнал не пишутся: их слишком много, они остаются в журнале контейнера.

Ключ

Смысл

rtspUrl

Адрес потока rtsp:// или rtsps://.

rtspTransport

tcp или udp. Если ключ опущен — TCP.

onvifPort

Порт OnVif, от 1 до 65535. По умолчанию 8899.

onvifUser

Логин OnVif. Пустая строка — камера логин не требует.

onvifPassword

Пароль OnVif.

Сохранение коннектора публикует в брокер сообщение prsConnector.model.updated.<id>. Видеосервер заново читает конфигурацию. Новый адрес или транспорт открывают сеанс заново.

Хранилище видео

Каталог архива и срок хранения задаются у хранилища данных с кодом типа 3, а не у коннектора. Коннектор только поставляет поток.

{
  "archivePath": "/var/lib/peresvet/video",
  "retentionHours": 24,
  "archiveWidth": null,
  "archiveFps": null
}

archivePath — абсолютный каталог. Каждая камера пишется в подкаталог <archivePath>/<id коннектора>. Каталог вне тома /var/lib/peresvet/video пропадёт вместе с контейнером. retentionHours — срок хранения в часах. Если ключ опущен — 24. archiveWidth — ширина картинки архива в пикселях, чётное число. Высота подстраивается под кадр. null — ширина камеры, поток копируется. archiveFps — частота кадров архива. null — частота камеры. Число больше нуля задаёт частоту явно и включает кодирование архива. Сохранение хранилища публикует prsDataStorage.model.updated.<id>. Подхват по этому сообщению заново читает путь, срок, ширину и частоту. Новые ширина, частота или путь открывают сеанс заново. Новый срок удаляет лишние фрагменты и не переоткрывает поток.

Тег видеопотока привязывают и к коннектору-камере, и к этому хранилищу. Пока такой пары нет, сеанс RTSP не открывается и клиент не получает кадр. Подхват забирает поток у коннектора и пишет архив. Отдача отвечает на чтение тега.

Пока сеанс RTSP жив, кружок у коннектора зелёный. Коды качества 100 и 101 в теги камеры не пишутся.

Тег «Видеопоток»

  1. Под объектом или в папке «Теги» создайте тег.

  2. В поле Тип значений выберите «Видеопоток».

  3. На вкладке Коннектор выберите коннектор камеры.

  4. Нажмите Записать.

Тег видеопотока, привязанный к камере

История такого тега в хранилище данных не ведётся. Если при создании тег успел привязаться к хранилищу истории, запись свойств снимает эту связь. Хранилище видео (тип 3) и коннектор-камера остаются. Просмотр открывается на вкладке Хранилище, даже если хранилище не выбрано.

Просмотр

На вкладке Хранилище блок Данные тега у видеопотока открыт без привязки к истории. Кнопка с треугольником читает тег по тем же правилам, что GET /v1/data/.

Живой поток и кнопки управления
  • Пустые start и finish — живой поток.

  • Только finish — снимок на этот момент. Если фрагмента нет, картинки не будет.

  • start и finish — фрагмент за период. Пока подхват склеивает mp4, на месте кадра написано «Загрузка…».

Пока картинка открыта, та же кнопка показывает паузу и закрывает просмотр в браузере. Запись архива при этом не останавливается: сеанс держит сервис подхвата, а не открытая страница. Кнопка в углу кадра распахивает картинку на весь экран; повторное нажатие или Escape возвращает её в панель.

Кнопка Микрофон говорит в динамик камеры, пока её держат. Браузер отдаёт микрофон только со страницы по HTTPS и только если для этого сайта микрофон разрешён (значок замка слева от адреса). Звук идёт на подхват по HTTP и оттуда в камеру отдельным каналом, не вторым RTSP и не через RabbitMQ. Если камера не принимает разговор, над кадром появляется текст ошибки.

Выключенный тег видеопотока останавливает сеанс этой камеры: приём RTSP, запись архива и индикатор связи. Включение тега поднимает сеанс снова. Если к одной камере привязано несколько таких тегов, сеанс идёт, пока включён хотя бы один.

Подсказка под кадром повторяет эти три режима.

Метку времени кадра ставит подхват: это момент, когда кадр пришёл в сервис, а не часы камеры. У H.264 в RTSP шкала всегда 90 кГц. Камера, которая сдвигает её на один тик за пакет, для ffmpeg выглядит как 90 000 кадров в секунду: архив раздувается, живой просмотр кадров почти не получает. Кадры ближе 20 мс считаются одним кадром и в запись не попадают.

Для картинки в браузере подхват один раз кодирует H.264: 15 кадров в секунду и ширина 960 пикселей, профиль Baseline. Этот поток копируется всем зрителям по WebRTC, отдельного кодирования на каждого нет. Если сеанс уже идёт, новый зритель к нему подключается и камеру заново не открывает. У камеры без звуковой дорожки следующий сеанс звук не пробует. Сигналинг — POST /v1/video/webrtc через nginx. Кадры тоже идут в nginx, на порт 8189 (UDP и TCP), и он передаёт их подхвату. Картинка звук не несёт. Если в том же RTSP есть звук, конфигуратор включает его отдельным проигрывателем: к адресу чтения добавляется audio=1, ответ — audio/mpeg. Второй сеанс к камере не открывается. В архив звук не пишется: по умолчанию туда копируется поток камеры, без второго кодирования.

Поворот и параметры изображения

Кнопки стоят слева от кадра и ходят по OnVif, второе RTSP-подключение при этом не открывается.

  • Стрелки двигают камеру, пока их держат. Если камера объявляет шкалу скорости больше 1, платформа один раз поднимает скорость до максимума: иначе команда принимается сразу, а объектив ползёт.

  • Ближе и Дальше делают один шаг зума.

  • Стоп останавливает поворот и наклон сразу.

  • Домой возвращает камеру по движениям, сделанным с этой страницы с момента старта сервиса подхвата, и снимает накопленный зум. Сама камера положение не запоминает.

Под стрелками — параметры, которые камера отдала по OnVif: яркость, контраст, насыщенность, резкость, засветка, динамический диапазон, ИК-фильтр (день, ночь, авто) и баланс белого. Ползунок или список отправляет только изменённый параметр.

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

Архив

Архив — фрагменты MPEG-TS. Имя файла — время начала в секундах Unix, например 1791117621.ts. Номинальная длина фрагмента — VIDEO_SEGMENT_SECONDS, по умолчанию 10 секунд. Если сеанс перезапускался, промежуток между файлами бывает длиннее.

Файлы старше retentionHours удаляет сам открытый сеанс. Новый срок приходит сообщением prsDataStorage.model.updated.<id> и применяется сразу.

Список фрагментов:

docker exec prs-video-capture ls -lh /var/lib/peresvet/video/rear-cam

Запись за период смотрят в конфигураторе: у тега задают start и finish и нажимают кнопку воспроизведения. Плеер получает один mp4, склеенный из покрывающих период фрагментов. Период длиннее VIDEO_MAX_FRAGMENT_SECONDS (по умолчанию 7200 секунд) платформа отклоняет.

Объём архива одной камеры примерно часы хранения × битрейт, Мбит/с × 0,44 гигабайт. Битрейт зависит от сюжета и от archiveWidth / archiveFps, его меряют по уже записанным файлам. Нагрузка считается по числу камер: на каждую один процесс приёма RTSP, записи и подготовки просмотра в контейнере подхвата. Число открытых страниц на это кодирование не влияет. Перед следующей камерой смотрите память контейнера prs-video-capture.

Своя страница

Живой поток можно поставить на страницу того же хоста, что и платформа. Адрес /v1/data/ уже проксируется nginx.

<img id="cam" alt="Камера"
     src="/v1/data/?tagId=67bde18c-5431-1041-925c-470278307e33">

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

Снимок и фрагмент, коды ошибок и тело POST /v1/ptz/ — в Видеопоток (тег типа 6).

Если картинки нет

  • В конфигурации коннектора нет rtspUrl вида rtsp://хост/путь, или коннектор выключен. Кружок связи при этом красный или серый.

  • Тег не типа «Видеопоток» или не привязан к этому коннектору. Платформа отвечает 424: «Тег видеопотока не привязан к активному коннектору-камере.»

  • Тег не привязан к хранилищу видео (тип 3). Платформа отвечает 424: «Тег видеопотока не привязан к хранилищу видео.»

  • Тег выключен — ответ 409.

  • Каталог archivePath хранилища не абсолютный или лежит вне тома подхвата: сеанс может подняться, а архив не переживёт перезапуск контейнера.

  • Надпись «Не удалось открыть поток» или ответ 502 — камера или ffmpeg не отдали кадр. Проверьте адрес, транспорт и доступ контейнера prs-video-capture до камеры в сети.

  • Ответ 503 «Сервис отдачи видео не запущен.» — нет процесса, который ответил на video.delivery.locate. 503 «Сервис подхвата видео не запущен.» значит, что отдача не нашла владельца камеры.

  • «Нет записи камеры на этот момент» — для снимка или фрагмента на диске нет файла, покрывающего метку.