.. _video_cameras: Подключение видеокамер ====================== Камера в платформе — это коннектор с адресом 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.``. * Отдача обслуживает теги. Пустой ``nodes`` значит все теги видеопотока. На ``prsTag.model`` она не подписывается: голос в создании и удалении тега ей не принадлежит. Адрес она сообщает RPC ``video.delivery.locate.``. Браузер ходит только в ``/v1/data/`` и ``/v1/ptz/``. Платформа проверяет право, по RPC узнаёт отдачу и проксирует запрос по HTTP. Отдача так же узнаёт подхват и забирает поток, звук, снимок или фрагмент по HTTP внутри сети compose. В теле сообщения брокера только идентификатор и базовый URL. Один id камеры обслуживает один подхват. Либо один процесс с пустым ``nodes``, либо несколько с непересекающимися списками. Два процесса на одну камеру откроют два сеанса RTSP. Два процесса с одним и тем же первым id в ``nodes`` делят одну очередь брокера. При непустом ``nodes`` базовый ``AppSvc`` не подписывает подхват на создание коннектора. Подхват подписывается на ``prsConnector.model.created`` сам и оставляет только камеры из списка. Правка хранилища видео приходит сообщением ``prsDataStorage.model.created``, ``updated.`` или ``deleted.`` и применяется сразу. При старте подхват один раз читает уже существующие камеры: брокер не повторяет старые события. Контракт ``GET /v1/data/`` и ``POST /v1/ptz/`` разобран в :ref:`video_tag`. Здесь — порядок действий в :ref:`конфигураторе `. Что нужно от камеры ------------------- * Поток ``rtsp://`` или ``rtsps://``. Адрес пути смотрите в паспорте камеры. Логин и пароль можно вписать в сам адрес: ``rtsp://user:password@192.168.1.72:554/live``. * Для поворота, зума и параметров изображения — OnVif. Хост берётся из адреса RTSP. Порт по умолчанию ``8899``, его меняют ключом ``onvifPort``. * Платформа уже запущена, конфигуратор открыт. Коннектор --------- #. В папке «Коннекторы» создайте коннектор. #. Дайте ему имя, например ``rear-cam``. #. В поле **Конфигурация** вставьте JSON и нажмите **Записать**. .. code-block:: 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 } } .. figure:: pics/video/01.connector.png :alt: Конфигурация коннектора-камеры Как у остальных коннекторов, параметры источника лежат в ``source``, а журнал — в ``log``. Ключ ``source.rtspUrl`` обязателен. По нему платформа и считает коннектор камерой. Плоский JSON, где ``rtspUrl`` стоит в корне, тоже принимается. Дополнительно можно выставить код типа узла ``10`` — этого достаточно, чтобы узел считался камерой, но адрес потока код не заменяет. Ключ ``log`` тот же, что у коннектора в ``prs-connector-core``. .. list-table:: :header-rows: 1 :widths: 28 72 * - Ключ - Смысл * - ``level`` - Минимальный уровень строк файла: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``. По умолчанию ``INFO``. * - ``fileName`` - Путь к файлу журнала на машине подхвата. ``{id}`` заменяется на id коннектора. Пустое значение — ``logs/prs_connector_.log``. * - ``maxBytes`` - Максимальный размер одного файла в байтах. По умолчанию 10485760. * - ``backupCount`` - Сколько предыдущих файлов хранить. По умолчанию 10. * - ``sendToPlatform`` - Флажок **Лог в платформу**. Пока выключен, строки остаются в файле. Когда включён, те же строки уходят сообщением ``prsConnector.log_line`` и видны на вкладке **Лог коннектора**. Строки декодера ffmpeg в этот журнал не пишутся: их слишком много, они остаются в журнале контейнера. .. list-table:: :header-rows: 1 :widths: 28 72 * - Ключ - Смысл * - ``rtspUrl`` - Адрес потока ``rtsp://`` или ``rtsps://``. * - ``rtspTransport`` - ``tcp`` или ``udp``. Если ключ опущен — TCP. * - ``onvifPort`` - Порт OnVif, от 1 до 65535. По умолчанию 8899. * - ``onvifUser`` - Логин OnVif. Пустая строка — камера логин не требует. * - ``onvifPassword`` - Пароль OnVif. Сохранение коннектора публикует в брокер сообщение ``prsConnector.model.updated.``. Видеосервер заново читает конфигурацию. Новый адрес или транспорт открывают сеанс заново. Хранилище видео --------------- Каталог архива и срок хранения задаются у хранилища данных с кодом типа ``3``, а не у коннектора. Коннектор только поставляет поток. .. code-block:: json { "archivePath": "/var/lib/peresvet/video", "retentionHours": 24, "archiveWidth": null, "archiveFps": null } ``archivePath`` — абсолютный каталог. Каждая камера пишется в подкаталог ``/``. Каталог вне тома ``/var/lib/peresvet/video`` пропадёт вместе с контейнером. ``retentionHours`` — срок хранения в часах. Если ключ опущен — 24. ``archiveWidth`` — ширина картинки архива в пикселях, чётное число. Высота подстраивается под кадр. ``null`` — ширина камеры, поток копируется. ``archiveFps`` — частота кадров архива. ``null`` — частота камеры. Число больше нуля задаёт частоту явно и включает кодирование архива. Сохранение хранилища публикует ``prsDataStorage.model.updated.``. Подхват по этому сообщению заново читает путь, срок, ширину и частоту. Новые ширина, частота или путь открывают сеанс заново. Новый срок удаляет лишние фрагменты и не переоткрывает поток. Тег видеопотока привязывают и к коннектору-камере, и к этому хранилищу. Пока такой пары нет, сеанс RTSP не открывается и клиент не получает кадр. Подхват забирает поток у коннектора и пишет архив. Отдача отвечает на чтение тега. Пока сеанс RTSP жив, кружок у коннектора зелёный. Коды качества 100 и 101 в теги камеры не пишутся. Тег «Видеопоток» ---------------- #. Под объектом или в папке «Теги» создайте тег. #. В поле **Тип значений** выберите «Видеопоток». #. На вкладке **Коннектор** выберите коннектор камеры. #. Нажмите **Записать**. .. figure:: pics/video/02.tag.png :alt: Тег видеопотока, привязанный к камере История такого тега в хранилище данных не ведётся. Если при создании тег успел привязаться к хранилищу истории, запись свойств снимает эту связь. Хранилище видео (тип ``3``) и коннектор-камера остаются. Просмотр открывается на вкладке **Хранилище**, даже если хранилище не выбрано. Просмотр -------- На вкладке **Хранилище** блок **Данные тега** у видеопотока открыт без привязки к истории. Кнопка с треугольником читает тег по тем же правилам, что ``GET /v1/data/``. .. figure:: pics/video/03.preview.png :alt: Живой поток и кнопки управления * Пустые ``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.`` и применяется сразу. Список фрагментов: .. code-block:: bash 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. .. code-block:: html Камера При включённой авторизации браузер отправит только куку этого хоста, поэтому страница должна открываться с него же. Чтобы остановить просмотр, уберите адрес из ``src``. Архив продолжит писаться. Снимок и фрагмент, коды ошибок и тело ``POST /v1/ptz/`` — в :ref:`video_tag`. Если картинки нет ----------------- * В конфигурации коннектора нет ``rtspUrl`` вида ``rtsp://хост/путь``, или коннектор выключен. Кружок связи при этом красный или серый. * Тег не типа «Видеопоток» или не привязан к этому коннектору. Платформа отвечает 424: «Тег видеопотока не привязан к активному коннектору-камере.» * Тег не привязан к хранилищу видео (тип ``3``). Платформа отвечает 424: «Тег видеопотока не привязан к хранилищу видео.» * Тег выключен — ответ 409. * Каталог ``archivePath`` хранилища не абсолютный или лежит вне тома подхвата: сеанс может подняться, а архив не переживёт перезапуск контейнера. * Надпись «Не удалось открыть поток» или ответ 502 — камера или ffmpeg не отдали кадр. Проверьте адрес, транспорт и доступ контейнера ``prs-video-capture`` до камеры в сети. * Ответ 503 «Сервис отдачи видео не запущен.» — нет процесса, который ответил на ``video.delivery.locate``. 503 «Сервис подхвата видео не запущен.» значит, что отдача не нашла владельца камеры. * «Нет записи камеры на этот момент» — для снимка или фрагмента на диске нет файла, покрывающего метку.