Зачем ещё один просмотрщик камер
У заказчика склад и цех с пятнадцатью IP-камерами и один старый офисный компьютер, чтобы за ними смотреть. Каждый NVR-пакет, который он пробовал, либо съедал машину целиком, либо останавливался на нескольких потоках. Запрос был простой: показать все камеры сразу, вести архив, дать отмотать назад, не падать и не требовать нового компьютера.
Ответ — написать на C с двумя зависимостями, которые я контролирую: FFmpeg для демуксинга и декодирования, SDL2 для рендеринга, звука и потоков. Всё остальное — вывод текста, ONVIF-клиент, интерфейс — написано руками. В итоге около 17 000 строк на C и портативная поставка: exe плюс две DLL.
Весь фокус — в дисциплине потоков
У каждой камеры свой рабочий поток: открывает RTSP-сессию, декодирует и отдаёт кадры рендереру. Блокировки только на уровне камеры, между камерами ничего общего. Потоки декодирования никогда не трогают диск; регистратор каждой камеры полностью асинхронный и не блокирует декодирование. Поток интерфейса только заглядывает в последний кадр. Семафор, ограничивающий одновременные RTSP-подключения, убирает всплески рукопожатий, из-за которых регистраторы отказывают в соединении при одновременном старте всех камер.
Сетка переключает превью и основной поток на каждой плитке, прогревает буферы при тёплом старте и сразу показывает закэшированные миниатюры — приложением можно пользоваться до прихода первого ключевого кадра.
Запись и «машина времени»
Запись идёт почасовыми сегментами MPEG-TS в recordings/<камера>/<дд.ММ.гггг>/<начало__конец>.ts; при закрытии файл переименовывается по точному интервалу. Обрезчик архива держит лимит MaxArchiveGB.
Режим DVR (F9) сканирует записи в индекс покрытия и рисует внизу полосу таймлайна, по которой можно двигаться, масштабировать и перематывать; воспроизведение идёт в темпе 1× поверх живой плитки, чтобы не терять контекст, на какую камеру смотришь.
ONVIF без gSOAP
Поворотные камеры управляются мышью. ONVIF-клиент — обнаружение устройства, медиапрофилей, ContinuousMove/Stop — это самописный SOAP 1.2 с аутентификацией WS-Security UsernameToken digest. Он живёт в отдельном потоке с почтовым ящиком на одну команду (побеждает самая свежая) и сторожевым таймером, который останавливает голову, если команды перестали приходить.
Плагинный ABI и счётчик лиц без зависимостей
Аналитика не вкомпилирована. Плагины — библиотеки на C с версионированным ABI (магическое число, версия ABI, структуры с проверкой размера); они получают кадры YUV420P и звук S16 и возвращают прямоугольники наложения. Поставляемый плагин «Счётчик лиц» находит лица каскадом pico, повторно идентифицирует их по LBP-гистограммам и считает уникальных посетителей за день — без OpenCV и внешних библиотек, ставится своим MSI.
Надёжность и упаковка
Журнал падений через SEH, поток-сторож зависаний интерфейса, трассировочный лог старта и автоматическое переподключение закрывают все режимы отказа, которые встретились на объекте. Настройки в settings.ini пишет фоновый писатель; менеджер камер, настройки и менеджер плагинов — оверлеи внутри приложения (F1/F2/F10) на русском и английском. Сборка CMake + Ninja под MSYS2 UCRT64 со скриптом минимальной статической сборки FFmpeg; установщики — WiX MSI и MSIX.
Что сделал бы иначе
Заложил бы плагинный ABI раньше. Он появился после запроса на счётчик лиц, и встраивание задним числом означало правку пути кадра в каждом рабочем потоке. Всё остальное — правило «один поток на камеру», никакого диска в потоках декодирования — оставил бы ровно как есть: именно поэтому старый офисный ПК справляется.