Мониторинг IP-камер по RTSP: 60 Мбит/с, которых не было
Мы делали в списке мониторов колонку «Битрейт»: щёлкаешь по заголовку — и наверху самые тяжёлые видеопотоки. Перед выкаткой посмотрели, как такая сортировка выглядит на живых данных. У одного из клиентов — 458 камер в одном пространстве и около 21 тысячи замеров видео. Первой строкой встал бы замер на 60 Мбит/с. За этот замер от камеры пришло 300 КБ за две с половиной секунды — около 1 Мбит/с.
Ни камера, ни замер не сломались. Замер честно поделил байты на время — просто время оказалось не тем. Ниже — как устроен наш мониторинг видеокамер: как мы проверяем камеры по RTSP, как меряем поток, не декодируя ни одного кадра, где ошиблись в первой гипотезе и как одна проверка на полсекунды убрала все фальшивые значения, задев 18 честных из 20 991.
Коротко
- «Пингуется» и «есть видео» — разные вещи. Нужны минимум два уровня: ответ RTSP на
DESCRIBEраз в 1–5 минут и сам поток раз в 15 минут.- Частоту кадров и битрейт можно мерить по меткам времени RTP и размерам пакетов, не декодируя видео. Время прихода для этого не годится: по TCP кадры приходят пачками.
- Короткий замер врёт по-крупному: два ключевых кадра на 40 мс медиа дают 60 Мбит/с. Битрейт, измеренный меньше чем по 0,5 с медиа, мы теперь не храним.
- Средний битрейт «по проводу» тоже врёт — вниз, когда поток подвисает. Мы сначала поверили ему, а не RTP, и ошиблись.
Почему «пингуется» — ещё не «есть видео»
На ping отвечает сетевой стек камеры, порт 554 держит её RTSP-сервер, а видео выдаёт кодер — и ломаются они по отдельности. Камера может сутками пинговаться и отвечать на RTSP-запросы, а в потоке при этом ноль кадров.
Типичные поломки — из практики и с форумов производителей:
- Служба RTSP зависла, а сеть жива. Ping есть, порт открыт, но ни регистратор, ни плеер поток не получают. Помогает только перезагрузка.
- Камера сбросилась к заводским настройкам после пропажи питания. Другой пароль, другой поток, иногда другой адрес. Ping отвечает, а старая ссылка на поток — уже нет.
- Все подключения заняты. У многих моделей предел — несколько одновременных потоков. Регистратор, пара телефонов, мониторинг — и следующий клиент получает отказ.
- Поток есть, но плохой. Падает частота кадров, ключевые кадры приходят раз в десятки секунд, теряются пакеты, доставка подвисает на секунды.
- Ночь. Включается ИК-подсветка, камера потребляет больше, и при бюджете PoE впритык уходит в перезагрузки.
Поэтому проверки камер выстраивают по уровням — от дешёвых к глубоким:
| Уровень | Что видно | Что пропустит |
|---|---|---|
| Ping (ICMP) | камера в сети | всё остальное |
| TCP-порт 554 | RTSP-сервер принимает соединения | зависшую службу, неверный путь и пароль |
RTSP OPTIONS и DESCRIBE | камера отвечает, пароль подходит, поток по этому пути есть | замершее видео, низкую частоту кадров, потери |
| Замер потока | кадры идут: частота, битрейт, ключевые кадры, потери | чёрную или застывшую картинку |
| Анализ картинки | кадр не чёрный, не застыл, камеру не закрыли | идёт ли запись |
| Регистратор | диски, режим записи, ошибки | — |
Анализ картинки требует декодировать видео, а состояние регистратора — ходить в его API. Мы остановились на четвёртом уровне: он ловит почти всё из списка выше, а стоит несколько секунд потока раз в 15 минут.
Как мы проверяем камеру
RTSP сам видео не передаёт. Он договаривается с камерой, что и как слать, а картинка идёт отдельно, по RTP (RFC 7826, RFC 3550). Это удобно: камеру можно расспросить, не открывая видео, а можно открыть поток на несколько секунд и посмотреть, что в нём.
| Проверка | Как часто | Что узнаём |
|---|---|---|
Доступность: OPTIONS и DESCRIBE, видео не передаётся | раз в 1, 5 или 15 минут | отвечает ли камера, подходит ли пароль, есть ли поток; кодек и разрешение из SDP |
Проверка видео: SETUP и PLAY, 3–10 секунд потока, затем TEARDOWN | раз в 5, 15 или 60 минут | кадры в секунду, битрейт, интервал ключевых кадров, потери, задержки доставки |
| Кадр с камеры: один ключевой кадр | только когда вы открыли страницу камеры | что камера видит |
Несколько решений, которые важны, когда камер сотни:
- Проверка видео занимает место обычной. Когда подходит её время, чекер вместо
OPTIONSиDESCRIBEоткрывает поток. Поэтому при интервале 15 минут замер приходит примерно раз в 16: он ждёт ближайшую обычную проверку. - Первый замер — в случайный момент интервала. Иначе перезапуск чекера открыл бы 458 потоков разом.
- Замер короткий. Он заканчивается, как только прошло 3 секунды и пришли два ключевых кадра, а поток смотрит не дольше 10 секунд. Обычно это около мегабайта трафика. Запомните это правило — оно ещё сыграет.
- Один вход на камеру за проверку. Камеры блокируют вход после нескольких неверных паролей, поэтому отклонённый пароль мы не повторяем. Подробнее — в разделе про регистраторы.
Как измерить поток, не декодируя видео
Чтобы узнать частоту кадров и битрейт, картинку декодировать не нужно: всё есть в заголовках RTP и в размерах пакетов.
- У каждого пакета есть порядковый номер. По пропускам в нумерации считаем потери — по алгоритму из приложения A к RFC 3550.
- У каждого пакета есть метка времени медиа. Часы видео в RTP идут с частотой 90 кГц, и все пакеты одного кадра несут одну метку: новая метка — новый кадр.
- Ключевой кадр видно по типу NAL-блока в начале полезной нагрузки — у H.264 это IDR, тип 5. Разрешение, а если камера его указывает, то и частоту кадров, на которую она настроена, сообщает SPS. Камера присылает его в SDP или прямо в потоке.
Упрощённо расчёт выглядит так:
CLOCK = 90_000 # часы RTP для видео, тиков в секунду
def rates(frames):
"""frames: [(rtp_timestamp, payload_bytes), ...], по одному на кадр."""
stamps = sorted(ts for ts, _ in frames)
media_s = (stamps[-1] - stamps[0]) / CLOCK # сколько медиа в замере
fps = (len(stamps) - 1) / media_s
kbps = sum(size for _, size in frames) * 8 / media_s / 1000
return fps, kbpsПочему по меткам RTP, а не по часам чекера? По TCP кадры приходят пачками, и время прихода несёт в себе всю неровность сети. Одна реальная камера по времени прихода показывала 15,8 кадра в секунду, по меткам RTP — свои честные 25,0.
Время прихода мы тоже записываем, но для другого. Если между двумя кадрами по часам прошло 2,5 секунды, а по меткам — 40 мс, значит, доставка вставала. Это отдельная метрика — задержка доставки, и она пригодится нам ниже.
Без настройки мы считаем поток плохим, если после PLAY не пришло ни одного кадра, если доставка вставала больше чем на 2 секунды, если потерь больше 1 % или если кадров меньше половины от того, на что настроена камера. Можно задать свои пороги — частоту кадров, интервал ключевых кадров, битрейт — и попросить предупредить о смене формата: так ловится сброс камеры к заводским настройкам. О проблеме с видео мы сообщаем, только когда её подтвердил второй замер, — его чекер делает сразу, на следующей проверке.
Запомните функцию rates: 60 Мбит/с спрятались в ней.
Колонка «Битрейт»
Пока камер десяток, хватает страницы каждой камеры. Когда их 458, хочется одной сортировкой увидеть самые тяжёлые и самые лёгкие потоки. Так появилась колонка «Битрейт» в списке мониторов:
- она видна только там, где в пространстве есть камеры;
- первый щелчок по заголовку сортирует от самых тяжёлых;
- в ячейке — битрейт последнего замера видео, а в подсказке — когда сделан замер;
- у камер на паузе и недоступных — «—», и в сортировке они уходят в конец.
Новой колонки в базе для этого не понадобилось. Метрики замера уже лежат в результате проверки, в JSONB, поэтому список берёт последний замер видео каждой камеры на странице одним запросом:
SELECT cr.*
FROM unnest(
CAST(:monitor_ids AS int[]),
CAST(:sinces AS timestamptz[])
) AS m(id, since)
CROSS JOIN LATERAL (
SELECT *
FROM check_results cr
WHERE cr.monitor_id = m.id
AND cr.created_at >= m.since -- не дальше двух интервалов проверки видео
AND cr.rtsp_metrics ? 'fps' -- только замеры видео
ORDER BY cr.created_at DESC
LIMIT 1
) cr;На 458 камерах он выполняется за 15 мс: для каждой камеры это короткий проход по индексу с LIMIT 1. Осталось убедиться, что сортировке можно верить.
Расследование: сначала мы обвинили не тот битрейт
Данные такие. 458 камер, у всех проверка видео раз в 15 минут. Битрейт — от 0,64 до 5,6 Мбит/с, медиана 2,3. И 22 замера из примерно 21 тысячи — 0,1 % — от 3 до 60 Мбит/с.
Битрейт можно посчитать двумя способами:
- по RTP — байты полезной нагрузки делим на время медиа, как в
rates; - по проводу — все пришедшие байты делим на длительность замера по часам чекера.
Мы сравнили их по всем замерам. У 19 камер битрейт по RTP оказался в 2–3 раза выше, чем по проводу. Первая гипотеза напрашивалась сама: RTP завышает — где-то путаем часы, неправильно склеиваем кадры или считаем лишнее.
Оказалось наоборот. У этих камер поток подвисал на 1,5–4 секунды. Замер, который обычно укладывается в 3 секунды, растягивался до 5–7: байтов приходило столько же, а делили их на большее время, и средний битрейт по проводу падал. Битрейт по RTP пауз не видит — он считает по времени медиа. Проверили на одной и той же камере: на чистых замерах 2120 кбит/с по RTP, на «завышенных» — 2188.
Вывод, который стоило сделать сразу: средний битрейт по проводу отвечает на вопрос «сколько трафика пришло, пока мы ждали», а не «какой поток отдаёт камера». Для сортировки нужен второй вопрос, так что RTP остался. Но 22 замера на десятки мегабит это не объясняло.
Откуда взялись 60 Мбит/с
Вот самый яркий из 22 — поля замера, которые нам понадобятся:
{"fps": 25.0, "bitrate_kbps": 60041, "wire_bytes": 303693,
"sample_s": 2.511, "keyframe_interval_s": 0.04, "max_stall_ms": 2471}Посчитаем, сколько медиа стоит за этим битрейтом:
- за замер пришло 303 693 байта — это 2,43 Мбит;
- 2,43 Мбит ÷ 60 041 кбит/с = 0,04 с — один интервал между кадрами при 25 кадрах в секунду;
- по часам чекера от первого кадра до последнего прошло 2,51 с, из них 2,47 с поток стоял (
max_stall_ms); keyframe_interval_s= 0,04: между двумя ключевыми кадрами 40 мс по меткам RTP.
Судя по полям, в замер попали ровно два кадра, и оба ключевые. Пришёл первый, поток встал на две с половиной секунды, пришёл второй — и правило раннего завершения выполнилось: 3 секунды прошли, два ключевых кадра есть. Правило, которое экономит трафик, оборвало замер на двух кадрах. По меткам RTP между ними 40 мс, поэтому функция rates честно поделила 300 КБ на 40 мс медиа и получила 60 Мбит/с. Те же байты, поделённые на время по часам, дают около 1 Мбит/с.
Есть и вторая тонкость. Байты в rates — за все кадры замера, а время — за промежутки между ними, которых на один меньше. На замере из сотни кадров эта разница незаметна. На замере из двух кадров на один 40-миллисекундный промежуток приходятся байты двух кадров, и оба ключевые — а ключевой кадр весит в разы больше обычного.
Таких замеров 0,1 %. Мало, но сортировка «сначала тяжёлые» вытаскивает наверх именно их: без защиты фальшивое значение стояло бы первой строкой примерно раз в 40 минут.
Фикс: битрейт — только по полсекунды медиа
Битрейт, измеренный меньше чем по 0,5 с медиа, мы не храним. Отдельного поля с длиной медиа в результате нет, но она выводится из двух сохранённых чисел:
MIN_BITRATE_MEDIA_S = 0.5
def bitrate_covers_too_little_media(m: dict) -> bool:
media_s = m["wire_bytes"] * 8 / 1000 / m["bitrate_kbps"]
return media_s < MIN_BITRATE_MEDIA_SПорог взяли из данных:
| Замеров | Медиа в замере | |
|---|---|---|
| Фальшивые, от 3 до 60 Мбит/с | 22 | от 0,040 до 0,405 с |
| Честные | 20 991 | меньше 0,5 с — только у 18 |
Остальные метрики замера — кадры в секунду, ключевые кадры, потери — остаются: врёт только битрейт. Проверка стоит на сервере и срабатывает каждый раз, когда результат проверки собирается — и когда он приходит от чекера, и когда читается из базы. Поэтому она сразу работает для всех чекеров, в том числе для приватных, которые клиенты обновляют сами, а 22 старые фальшивки пропали из колонки и графиков без миграции. Почему битрейт отброшен, сервер пишет в журнал.
Цена — 18 честных значений из 20 991. До следующего замера в их ячейке будет «—» с подсказкой «Последняя проверка видео не смогла измерить битрейт».
Что ещё можно было сделать и почему мы не стали:
- Отсекать выбросы относительно обычного битрейта камеры. Спрятали бы и настоящие всплески — о них в следующем разделе.
- Показывать медиану нескольких замеров. Колонка отставала бы от камеры на полчаса, а запрос на каждую камеру стал бы тяжелее.
- Удлинить замер. Больше трафика и нагрузки на камеры ради 0,1 % замеров, а поток может встать и на длинном замере.
Настоящие всплески: от 0,8 до 30 Мбит/с по вечерам
Не всё, что выглядит выбросом, — ошибка замера. Четыре камеры, которые обычно отдают около 0,8 Мбит/с, по вечерам часа на два поднимались до 7–30 Мбит/с. Разрешение не менялось, а байты по проводу подтверждали битрейт по RTP — это настоящий поток.
Наша гипотеза — ночной режим и шум матрицы при слабом свете. Кодер с переменным битрейтом (VBR) честно кодирует шум как детали, и поток толстеет. На кадрах мы это не проверяли, так что пока это только гипотеза.
Интересно, что по всему парку медиана битрейта по часам ночью не падает, хотя обычно советуют ждать обратного: в неподвижной ночной сцене VBR-кодеру почти нечего кодировать. Вывод практический: порог «битрейт не ниже» ставьте с большим запасом, а резкий рост вечером — не обязательно авария.
Сортировке по последнему замеру можно верить
Оставался вопрос, не шумит ли сама сортировка: замер один раз в 16 минут, а битрейт у VBR-камер гуляет. Мы сравнили два среза с разницей в 12 часов: из 20 самых тяжёлых камер 16 остались среди 40 самых тяжёлых. Середина списка перемешивается, края — нет. Колонка показывает свойство камеры, а не случайность одного замера.
Кадр с камеры — единственное место, где мы декодируем
Битрейт и частоту кадров мы меряем без декодирования. Но иногда нужно просто посмотреть, что видит камера: не закрыли ли её, не развернули ли, не залепили ли объектив. Для этого на странице камеры есть «Кадр с камеры».
Как это устроено:
- кадр делается, только когда вы открыли страницу камеры или нажали «Обновить кадр»;
- чекер открывает поток, ждёт до 10 секунд первый целый ключевой кадр и отправляет его вместе с параметрами кодека (SPS и PPS);
- декодирует кадр ваш браузер через WebCodecs, а мы его нигде не храним;
- видят кадр участники пространства с доступом к этому монитору — на статус-страницах и в оповещениях его нет;
- пока это работает для H.264. У большинства камер в этом формате дополнительный поток.
Почему декодирует браузер, а не наш чекер: видеопоток — недоверенные данные. Камерой может назваться что угодно, и разбирать присланный ею H.264 безопаснее в песочнице браузера, чем держать видеодекодер на чекере, через который идут проверки всех клиентов.
Регистраторы и блокировки по паролю
У регистраторов (NVR) две особенности, о которые спотыкается почти каждый.
Лавина оповещений. Регистратор на 16 каналов перезагрузился, и мониторинг, который следит за каждым каналом отдельно, присылает 16 сообщений «камера недоступна». К восьмому их никто не читает. Если не отвечает весь регистратор, нужно одно сообщение о регистраторе. Если сломался один канал — отдельное, о нём.
Блокировка по паролю. Камеры защищаются от подбора пароля. У Hikvision по умолчанию после 7 неверных паролей администратора (5 — для обычного пользователя) IP-адрес блокируется на 30 минут. У Dahua после 5 неверных попыток блокируется учётная запись — по умолчанию на 5 минут. Отсюда два неприятных сценария:
- пароль на камере сменили, а мониторинг продолжает входить со старым — и через несколько проверок заблокирован адрес, с которого он ходит, а за NAT это может быть адрес всей сети;
- мониторинг ходит под той же учётной записью, что и регистратор, — и блокировка учётки останавливает запись.
Поэтому за одну проверку мы входим на камеру не больше одного раза. Если камера пароль отклонила, мы час не входим на это устройство с этим логином — на всех его каналах. Проверки одного устройства с паролем идут по очереди, а сеанс мы закрываем запросом TEARDOWN, чтобы не держать слот камеры до таймаута. Мониторингу лучше завести отдельную учётную запись с правами только на просмотр.
Где ставить чекер
Камеры проверяют только чекеры, центральный сервер к камерам не подключается. Вариантов два, и оба рабочие:
- Публичный чекер (Москва, Новосибирск, Минск) — для камер с белым IP: порт 554 проброшен на роутере, и проверка идёт из интернета. Ставить у себя ничего не нужно: добавили адрес потока — проверки пошли.
- Приватный чекер в сети камер — для камер за NAT и без белого IP. Он сам подключается к PingZen по 443/TCP, на роутере ничего не пробрасывается.
Камера с белым IP — обычная рабочая схема, хватает базовой гигиены: свой логин вместо admin, длинный пароль и отдельная учётная запись для мониторинга с правами только на просмотр. Длинный случайный пароль перебором не подобрать, камеры к тому же сами блокируют подбор (об этом выше), а мы входим на камеру не больше раза за проверку и после отказа час не повторяем пароль — так что блокировку мониторинг не вызовет.
Как поставить приватный чекер — в инструкции по чекерам и на странице о мониторинге локальной сети.
Как проверить RTSP-поток руками
Быстрее всего — ffprobe из FFmpeg. Он выполнит DESCRIBE, откроет поток и покажет, что в нём:
ffprobe -rtsp_transport tcp \
"rtsp://логин:пароль@АДРЕС_КАМЕРЫ:554/Streaming/Channels/101"Строка Stream #0:0: Video: h264 ... значит, что поток живой. 401 Unauthorized — неверный логин или пароль, 404 Not Found — неверный путь. Путь к потоку зависит от производителя и указан в инструкции к камере и в её веб-интерфейсе, в настройках RTSP.
Чек-лист
- Проверяется не только ping, но и ответ RTSP на
DESCRIBE— раз в 1–5 минут. - Раз в 15–60 минут проверяется сам поток: кадры, битрейт, ключевые кадры, потери.
- Частота кадров и битрейт считаются по меткам времени RTP, а не по времени прихода.
- Битрейт, измеренный по доле секунды медиа, не попадает в отчёты и сортировки.
- Для мониторинга заведена отдельная учётная запись только на просмотр, а отклонённый пароль не повторяется по кругу.
- Порог «битрейт не ниже» стоит с большим запасом.
- Сбой регистратора приходит одним сообщением.
- У камеры с белым IP свой логин вместо
adminи длинный пароль; камеры за NAT проверяет приватный чекер.
Что в итоге есть в PingZen
- Монитор типа «Камера»: одна камера, ссылка на поток или регистратор целиком — от 2 до 64 каналов Hikvision, Dahua или Reolink, пути к потокам подставляются сами.
- Доступность раз в 1, 5 или 15 минут, проверка видео раз в 5, 15 или 60 минут, свои пороги и предупреждение о смене формата. О настоящем сбое вы узнаете за несколько минут.
- Колонка «Битрейт» в списке и график «Качество видео во времени» на странице камеры.
- Кадр с камеры по запросу.
- Публичные чекеры и приватные — на бесплатном тарифе до 10 на аккаунт.
Чего мы не делаем: не записываем и не храним видео, не анализируем картинку и не распознаём движение, не работаем с событиями ONVIF. Запись и просмотр остаются за вашим регистратором.
Если камер у вас сотни, их удобнее завести CSV-импортом, чем по одной. Коротко обо всём этом — на странице мониторинга IP-камер и видеорегистраторов, остальные возможности — на странице возможностей PingZen, а попробовать можно, зарегистрировавшись и добавив монитор типа «Камера».
А как вы следите за своими камерами — ping, скрипт на ffprobe, система видеонаблюдения? И если видели вечерние всплески битрейта, чем вы их объяснили? Напишите нам в Telegram — интересно сравнить с нашими данными.