Введение
В статье про RTSP я описал протокол, который доставляет видео от камеры к клиенту. У RTSP есть ясная задача и ясные границы: у вас уже есть адрес вида rtsp://…, а дальше — DESCRIBE, SETUP, PLAY и поток RTP.
Но этот адрес откуда-то нужно взять. Как найти камеру в сети? Какие у неё есть потоки и чем главный отличается от дополнительного? Как узнать, какой путь и порт у нужного потока — у каждого производителя он свой? Как пройти авторизацию, не угадывая схему? RTSP на эти вопросы не отвечает: он начинается там, где они уже закрыты.
Закрывает их ONVIF. В RTSP-статье я назвал его надстройкой, которая берёт на себя поиск камер и авторизацию. Это верно, но неполно: ONVIF — это целый набор веб-сервисов. Через него можно искать устройства, читать и менять настройки, управлять поворотной оптикой (PTZ), получать события (движение, саботаж, срабатывание тревожных входов), работать с записью на самой камере. Видео он при этом не передаёт — для этого ONVIF отдаёт ссылку на тот самый RTSP.
Аналогия. Если RTSP — это телефонная линия, по которой можно говорить, то ONVIF — телефонный справочник, регистратура и правила вежливого обращения: он подскажет, кого набрать, как представиться и какой внутренний номер соответствует нужному отделу. Сам разговор — уже не его дело.
По данным ONVIF на начало 2026 года, в мире насчитывается более 35 000 изделий, прошедших подтверждение соответствия, и около 500 компаний-участников. Это не гарантия того, что любая камера заработает с любым клиентом с первой попытки — ниже будет много о том, где «стандарт» расходится с реальностью, — но это объясняет, почему без ONVIF сегодня почти невозможно построить систему, которая работает не с одной маркой камер.
Как читать примеры. Все запросы и ответы в статье составлены по официальным схемам ONVIF (WSDL/XSD, версия 26.06) и текстам спецификаций. Запросы и ответы Media и Media2 дополнительно проверены на соответствие этим схемам автоматически (ответы Device составлены по описанию типов, но отдельно не валидировались). Но это не трассы с конкретной камеры: реальные устройства отвечают с вариациями — другие префиксы пространств имён, другой порядок вспомогательных полей, лишние расширения. Места, которые я не проверял на живом железе, помечены словом «проверить». Полный список — в конце.
История: зачем понадобился общий язык
Мир до ONVIF
Это общеизвестная картина отрасли, а не цитата из одного источника, поэтому сформулирую осторожно. До появления общего стандарта почти у каждого производителя IP-камер были собственный HTTP API и собственный SDK. Система видеонаблюдения, которая хотела работать с камерами нескольких марок, писала отдельный драйвер под каждую. Стоимость интеграции росла вместе с числом производителей, а у производителя камер не было способа попасть в чужую систему, кроме как договариваться об интеграции вручную.
Чем больше становилось камер и производителей, тем менее устойчивой выглядела такая схема.
2008 год: три компании
ONVIF основали в 2008 году Axis Communications, Bosch Security Systems и Sony. Организация зарегистрирована 25 ноября 2008 года как некоммерческая корпорация в штате Делавэр (501(c)(6)). Первоначально название расшифровывалось как Open Network Video Interface Forum; когда область интересов вышла за пределы видео (например, в контроль доступа), длинное название отбросили, и осталась одна аббревиатура.
Важно, что стандарт с самого начала задумывался как интерфейс, а не как формат видео: договориться о том, как клиент находит устройство, спрашивает его возможности и просит поток. Кодеки, форматы и протоколы передачи при этом использовались уже существующие — RTP/RTSP, H.264 и так далее.
Был ли конкурент
Примерно в то же время — по данным открытых источников, в августе 2008 года — появилась ещё одна инициатива, PSIA (Physical Security Interoperability Alliance), с похожей целью и другим техническим подходом. Источники здесь в основном форумные и отраслевые блоги, поэтому ограничусь осторожным выводом: поддержку PSIA оказали отдельные производители, но массового распространения она, судя по всему, не получила, и сегодня именно ONVIF стал общепринятым языком.
От бумаги к работающему стандарту
Спецификация без проверки остаётся бумагой. Поэтому важнее самой первой версии оказалась последовательность шагов, которая сделала стандарт проверяемым:
| Дата | Событие |
|---|---|
| Ноябрь 2008 | Core Specification 1.0 |
| Декабрь 2008 | Test Specification 1.0 |
| Май 2009 | Тестовый инструмент и процесс подтверждения соответствия |
| Июль 2009 | Первые устройства, подтвердившие соответствие |
| Октябрь 2009 | 100 участников |
| Январь 2011 | около 600 подтверждённых изделий |
| 2018 / 2021 | 10 000 / 20 000 изделий |
| Начало 2026 | более 35 000 изделий |
Ключевое слово здесь — conformance: устройство не просто заявляет «поддерживаю ONVIF», а проходит проверку тестовым инструментом, и результат публикуется. Актуальный и самый надёжный источник для проверки конкретной модели — официальный список conformant products. Подтверждение соответствия, впрочем, не отменяет проверки на собственном стенде: тест проверяет заявленные функции, а не все сочетания настроек реальной прошивки.
Профили: как стандарт научился говорить «обещаю»
Core Specification описывает очень много возможностей, и ни одна камера не реализует их все. Если бы стандарт требовал «поддержи всё», его не поддерживал бы никто. Если бы он требовал «поддержи что хочешь», клиент не мог бы ничего предполагать. Решением стали профили (Profiles): именованные наборы обязательных функций. Профиль — это обещание: «устройство с этим профилем умеет вот это».
| Профиль | Появился | О чём обещание |
|---|---|---|
| Profile S | январь 2012 | Потоковое видео: профили медиа, получение RTSP-ссылки, базовое управление |
| Profile C | март 2014 | Контроль доступа (двери, замки) |
| Profile G | июнь 2014 | Запись и воспроизведение на самом устройстве |
| Profile Q | декабрь 2014 | Быстрая установка: обнаружение и первичная настройка |
| Profile A | июль 2017 | Конфигурация контроля доступа |
| Profile T | октябрь 2018 | Современное потоковое видео: Media2, H.265, Digest, события, метаданные |
| Profile M | июнь 2021 | Метаданные и события аналитики |
| Profile D | июль 2021 | Периферия контроля доступа |
| Profile V | июль 2026 (Release Candidate) | Облачное видео — см. последний раздел |
Для видеонаблюдения на практике важны три: S (многолетний фундамент), T (его преемник) и G (запись на карту памяти камеры). Остальные профили касаются другой части мира: дверей, замков, считывателей.
Отдельно стоит запомнить: у профиля бывают две стороны — устройство и клиент. «Камера соответствует Profile T» и «VMS соответствует Profile T» — два разных обещания, которые проверяются отдельно. Первое не означает второго.
Почему именно SOAP
ONVIF построен на SOAP — XML поверх HTTP, с описанием интерфейса в WSDL. Сегодня это выглядит архаично: многословные конверты, пространства имён, клиенты, которые приходится писать на коленке. Однако для 2008 года выбор выглядит не случайным. Мотивы авторов стандарта нигде не сформулированы одной фразой, поэтому то, что ниже, — моя реконструкция, а не цитата.
Тогда в корпоративном мире сложился целый набор спецификаций WS-* (Web Services), и ONVIF почти ничего не придумывал заново — он собрал готовые блоки:
| Задача | Что взято | Где проявляется |
|---|---|---|
| Поиск устройств в сети | WS-Discovery (UDP multicast) | Probe / ProbeMatch / Hello / Bye |
| Описание интерфейса | WSDL + XSD | Машинно-читаемый контракт каждого сервиса |
| Авторизация | WS-Security, UsernameToken | Заголовок wsse:Security в запросе |
| События | WS-BaseNotification + WS-Topics | Подписки, PullPoint, темы вида tns1:… |
Главное достоинство такого выбора — типизированный контракт. WSDL и XSD описывают каждую операцию, каждое поле, допустимые значения. По ним можно сгенерировать клиентский код (так делают некоторые библиотеки) и автоматически проверить запрос — именно так проверены примеры в этой статье.
Платить за это приходится объёмом, сложностью и тем, что реализации «слегка расходятся» в деталях XML. Кроме того, спецификация живёт десять лет и остаётся обратно совместимой — иногда в ущерб аккуратности. Показательный пример: в схеме до сих пор есть значение MPV4-ES, а в комментарии прямо сказано, что на самом деле имелось в виду MP4V-ES, но ошибку в имени оставили ради обратной совместимости.
Устройство ONVIF
Сервисы
Камера с ONVIF — это не один «API», а набор сервисов, у каждого из которых свой адрес (XAddr) и своё пространство имён:
| Сервис | Что даёт | Зачем нашему узлу |
|---|---|---|
| Device | Идентификация, время, пользователи, сеть, список остальных сервисов | Точка входа и проверка часов |
| Media (ver10) | Профили, кодеки, GetStreamUri в «старом» стиле | Основной путь для камер Profile S |
| Media2 (ver20) | То же в новой редакции, с H.265 | Основной путь для камер Profile T |
| Events | Подписки и события: движение, саботаж, входы | Дешёвый триггер вместо анализа картинки |
| PTZ | Поворот, наклон, масштаб, пресеты | Только если нужна поворотная камера |
| Imaging | Экспозиция, фокус, ИК-подсветка | Обычно не нужен |
| Analytics | Правила и детекторы на стороне камеры | Источник метаданных (Profile M) |
| Recording / Search / Replay | Запись на самой камере | Резервный источник при потере связи (Profile G) |
Пространства имён главных сервисов (по схемам ONVIF):
| Префикс | Пространство имён |
|---|---|
tds | http://www.onvif.org/ver10/device/wsdl |
trt | http://www.onvif.org/ver10/media/wsdl |
tr2 | http://www.onvif.org/ver20/media/wsdl |
tt | http://www.onvif.org/ver10/schema |
Адреса сервисов не фиксированы. Руководство для разработчиков (ONVIF Application Programmer’s Guide) прямо предупреждает: они зависят от производителя и устройства, поэтому их нужно получать динамически. Популярный путь /onvif/device_service встречается в ProbeMatch у многих камер, но это соглашение, а не требование.
Media и Media2: два поколения одного сервиса
У нас получилось два сервиса медиа не потому, что кому-то не хватило фантазии. Первый, Media (ver10), появился вместе с Profile S. Второй, Media2 (ver20), пришёл вместе с Profile T. Различия, которые видны в схемах:
- H.265. В перечислении
VideoEncoding, на которое опирается Media, — толькоJPEG,MPEG4иH264. H.265 появляется лишь в Media2 (там кодек передаётся как медиа-подтип IANA:H264,H265). - Запрос ссылки. В Media клиент описывает поток структурой
StreamSetup(тип потока и транспорт). В Media2 достаточно строкиProtocol. - Безопасный транспорт. Среди возможностей Media2 — RTSPS/SRTP и RTSP поверх WebSocket (
RTSPWebSocketUri). В текущей схеме Media (ver10) таких возможностей нет.
Для клиента вывод простой: сначала проверяем, есть ли у камеры Media2, и если есть — работаем через него; иначе откатываемся на Media.
Что обещает Profile T
Здесь приведено только то, что найдено в тексте Profile T v1.0, без дополнений «по памяти»:
- Устройство обязано аутентифицировать HTTP- и RTSP-запросы по Digest.
- Устройство обязано поддерживать
GetServices,GetSystemDateAndTime,SetSystemDateAndTime,GetProfiles,GetStreamUri(через Media2) иSetSynchronizationPoint(запрос внеочередного ключевого кадра). - Хотя бы один из кодеков H.264 или H.265 — обязателен; какой именно — на усмотрение производителя.
- Обязательны потоки по RTP/UDP, RTP/RTSP/HTTP/TCP и RTP/UDP multicast. HTTPS и WebSocket — условные, то есть не для всех.
- Обязательны потоковая передача метаданных, OSD, события движения (Motion Alarm) и хотя бы один тип событий саботажа (Tampering).
- Устройство должно допускать не менее двух подписок типа PullPoint (
MaxPullPoints). - Клиент Profile T обязан определять сервисы через
GetServices, а не через устаревшийGetCapabilities.
Здесь есть неожиданный нюанс: HTTPS в Profile T необязателен. Если вы рассчитываете на шифрование канала, придётся проверять это отдельно (в 2024 году ONVIF выпустил TLS Configuration Add-on, но это отдельный документ, а не часть Profile T).
Как клиент находит камеру и просит поток
Самая полезная часть для практики — последовательность. Она почти не меняется от камеры к камере, и если реализовать её аккуратно, остальное — вопрос деталей.
Шаг 1. Поиск: WS-Discovery
Клиент отправляет Probe — SOAP-сообщение по UDP на multicast-адрес 239.255.255.250, порт 3702 (для IPv6 — ff02::c). Любое устройство, которое слушает и подходит под запрос, отвечает ProbeMatch прямо клиенту.
<e:Envelope xmlns:e="http://www.w3.org/2003/05/soap-envelope"
xmlns:w="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:dn="http://www.onvif.org/ver10/network/wsdl">
<e:Header>
<w:MessageID>uuid:84ede3de-7dec-11d0-c360-f01234567890</w:MessageID>
<w:To e:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>
<w:Action e:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>
</e:Header>
<e:Body>
<d:Probe>
<d:Types>dn:NetworkVideoTransmitter</d:Types>
</d:Probe>
</e:Body>
</e:Envelope>
Устройство отвечает так:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsadis="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:dn="http://www.onvif.org/ver10/network/wsdl"
xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
<SOAP-ENV:Header>
<wsadis:MessageID>uuid:1b2c3d4e-0000-4000-8000-0123456789ab</wsadis:MessageID>
<wsadis:RelatesTo>uuid:84ede3de-7dec-11d0-c360-f01234567890</wsadis:RelatesTo>
<wsadis:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsadis:To>
<wsadis:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsadis:Action>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<d:ProbeMatches>
<d:ProbeMatch>
<wsadis:EndpointReference>
<wsadis:Address>urn:uuid:0f1e2d3c-0000-4000-8000-ba9876543210</wsadis:Address>
</wsadis:EndpointReference>
<d:Types>dn:NetworkVideoTransmitter tds:Device</d:Types>
<d:Scopes>onvif://www.onvif.org/Profile/Streaming
onvif://www.onvif.org/Profile/T
onvif://www.onvif.org/name/Demo-Camera
onvif://www.onvif.org/hardware/DC-100</d:Scopes>
<d:XAddrs>http://192.168.1.64/onvif/device_service</d:XAddrs>
<d:MetadataVersion>1</d:MetadataVersion>
</d:ProbeMatch>
</d:ProbeMatches>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Что здесь важно:
XAddrs— адрес Device-сервиса. Элемент может содержать несколько адресов через пробел; клиент должен перебрать их и сохранить тот, который реально отвечает.Scopes— метки видаonvif://www.onvif.org/…: имя, аппаратная модель, расположение и, что нам нужно, поддерживаемые профили. Устройство, соответствующее Profile T, обязано включить в список scope соответствующую запись. В примере выше —Profile/TиProfile/Streaming(эта запись встречается в примере из Core Specification; сопоставлять её именно с Profile S стоит только после проверки по тексту профиля).Types— по Core Specification устройство обязано включить вTypesтипtds:Device. Типdn:NetworkVideoTransmitterчасто используют клиенты; в Core Specification 19.06 он не упоминается. По логике WS-Discovery Probe безTypesподходит всем устройствам, поэтому для широкого поиска можно отправить именно такой — или указать оба типа.
По умолчанию устройство находится в режиме discoverable, но Core Specification допускает и режим non-discoverable, в котором камера не слушает WS-Discovery вообще. Поэтому «камера не нашлась» не означает «в камере нет ONVIF».
Ещё одна деталь, объясняющая многие загадочные «молчания»: по Core Specification при ошибке в multicast-пакете устройство должно молча отбросить его, а не отвечать ошибкой — чтобы много камер не завалили сеть ответами. Поэтому нестандартно оформленный Probe не получает ничего: ни отказа, ни подсказки. В сообществе встречаются жалобы, что часть камер не терпит нестандартных префиксов пространств имён; проверить это стоит на своих моделях.
Шаг 2. Часы: GetSystemDateAndTime
Первый вызов после обнаружения — не GetProfiles, а GetSystemDateAndTime. У него две роли: он проверяет, что адрес из XAddrs вообще достижим, и сообщает время камеры. Согласно Core Specification, это вызов класса PRE_AUTH: пароль не нужен.
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
<s:Body>
<tds:GetSystemDateAndTime/>
</s:Body>
</s:Envelope>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tds="http://www.onvif.org/ver10/device/wsdl"
xmlns:tt="http://www.onvif.org/ver10/schema">
<s:Body>
<tds:GetSystemDateAndTimeResponse>
<tds:SystemDateAndTime>
<tt:DateTimeType>NTP</tt:DateTimeType>
<tt:DaylightSavings>false</tt:DaylightSavings>
<tt:TimeZone>
<tt:TZ>CST-8</tt:TZ>
</tt:TimeZone>
<tt:UTCDateTime>
<tt:Time><tt:Hour>8</tt:Hour><tt:Minute>15</tt:Minute><tt:Second>27</tt:Second></tt:Time>
<tt:Date><tt:Year>2026</tt:Year><tt:Month>10</tt:Month><tt:Day>4</tt:Day></tt:Date>
</tt:UTCDateTime>
<tt:LocalDateTime>
<tt:Time><tt:Hour>16</tt:Hour><tt:Minute>15</tt:Minute><tt:Second>27</tt:Second></tt:Time>
<tt:Date><tt:Year>2026</tt:Year><tt:Month>10</tt:Month><tt:Day>4</tt:Day></tt:Date>
</tt:LocalDateTime>
</tds:SystemDateAndTime>
</tds:GetSystemDateAndTimeResponse>
</s:Body>
</s:Envelope>
Зачем это нужно, объясняется ниже, в разделе про авторизацию. Пока — главное: это тот самый вызов, которым стоит начинать общение с любой камерой, особенно с незнакомой.
Шаг 3. Сервисы: GetServices
Теперь можно спросить, что камера умеет и по каким адресам. GetServices тоже относится к PRE_AUTH. Есть и устаревший GetCapabilities с той же функцией; в WSDL он помечен как заменённый на GetServices, а Profile T требует от клиента именно GetServices.
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
<s:Body>
<tds:GetServices>
<tds:IncludeCapability>false</tds:IncludeCapability>
</tds:GetServices>
</s:Body>
</s:Envelope>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tds="http://www.onvif.org/ver10/device/wsdl"
xmlns:tt="http://www.onvif.org/ver10/schema">
<s:Body>
<tds:GetServicesResponse>
<tds:Service>
<tds:Namespace>http://www.onvif.org/ver10/device/wsdl</tds:Namespace>
<tds:XAddr>http://192.168.1.64/onvif/device_service</tds:XAddr>
<tds:Version><tt:Major>2</tt:Major><tt:Minor>60</tt:Minor></tds:Version>
</tds:Service>
<tds:Service>
<tds:Namespace>http://www.onvif.org/ver10/media/wsdl</tds:Namespace>
<tds:XAddr>http://192.168.1.64/onvif/media_service</tds:XAddr>
<tds:Version><tt:Major>2</tt:Major><tt:Minor>60</tt:Minor></tds:Version>
</tds:Service>
<tds:Service>
<tds:Namespace>http://www.onvif.org/ver20/media/wsdl</tds:Namespace>
<tds:XAddr>http://192.168.1.64/onvif/media2_service</tds:XAddr>
<tds:Version><tt:Major>2</tt:Major><tt:Minor>60</tt:Minor></tds:Version>
</tds:Service>
</tds:GetServicesResponse>
</s:Body>
</s:Envelope>
По Namespace можно понять, какие сервисы есть. Наличие http://www.onvif.org/ver20/media/wsdl означает Media2.
Шаг 4. Профили медиа: GetProfiles
Media Profile — это набор конфигураций, привязанных друг к другу: источник видео, видеокодер, при необходимости аудио, PTZ, метаданные. Камера обычно содержит несколько готовых профилей: для главного потока, для дополнительного.
Начиная с этого шага нужна авторизация (подробнее — ниже). Заголовок с подписью (формат подробнее разобран в разделе про авторизацию) добавляется в s:Header:
<s:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>admin</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">O7QloJhZpZwZB0wE/cDlHXB6L3g=</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">LKqI6G/AikKCQrN0zqZFlg==</wsse:Nonce>
<wsu:Created>2026-10-04T08:15:30Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</s:Header>
А сам запрос выглядит так:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tr2="http://www.onvif.org/ver20/media/wsdl">
<s:Body>
<tr2:GetProfiles>
<tr2:Type>All</tr2:Type>
</tr2:GetProfiles>
</s:Body>
</s:Envelope>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tr2="http://www.onvif.org/ver20/media/wsdl"
xmlns:tt="http://www.onvif.org/ver10/schema">
<s:Body>
<tr2:GetProfilesResponse>
<tr2:Profiles token="profile_main" fixed="true">
<tr2:Name>mainStream</tr2:Name>
<tr2:Configurations>
<tr2:VideoSource token="vsc_1">
<tt:Name>VideoSourceConfig</tt:Name>
<tt:UseCount>2</tt:UseCount>
<tt:SourceToken>vs_1</tt:SourceToken>
<tt:Bounds x="0" y="0" width="2560" height="1440"/>
</tr2:VideoSource>
<tr2:VideoEncoder token="venc_main" GovLength="50" Profile="Main">
<tt:Name>VideoEncoder_Main</tt:Name>
<tt:UseCount>1</tt:UseCount>
<tt:Encoding>H265</tt:Encoding>
<tt:Resolution><tt:Width>2560</tt:Width><tt:Height>1440</tt:Height></tt:Resolution>
<tt:RateControl ConstantBitRate="false">
<tt:FrameRateLimit>25</tt:FrameRateLimit>
<tt:BitrateLimit>4096</tt:BitrateLimit>
</tt:RateControl>
<tt:Quality>5</tt:Quality>
</tr2:VideoEncoder>
</tr2:Configurations>
</tr2:Profiles>
<tr2:Profiles token="profile_sub" fixed="true">
<tr2:Name>subStream</tr2:Name>
<tr2:Configurations>
<tr2:VideoSource token="vsc_1">
<tt:Name>VideoSourceConfig</tt:Name>
<tt:UseCount>2</tt:UseCount>
<tt:SourceToken>vs_1</tt:SourceToken>
<tt:Bounds x="0" y="0" width="2560" height="1440"/>
</tr2:VideoSource>
<tr2:VideoEncoder token="venc_sub" GovLength="25" Profile="Main">
<tt:Name>VideoEncoder_Sub</tt:Name>
<tt:UseCount>1</tt:UseCount>
<tt:Encoding>H264</tt:Encoding>
<tt:Resolution><tt:Width>640</tt:Width><tt:Height>360</tt:Height></tt:Resolution>
<tt:RateControl ConstantBitRate="false">
<tt:FrameRateLimit>10</tt:FrameRateLimit>
<tt:BitrateLimit>512</tt:BitrateLimit>
</tt:RateControl>
<tt:Quality>3</tt:Quality>
</tr2:VideoEncoder>
</tr2:Configurations>
</tr2:Profiles>
</tr2:GetProfilesResponse>
</s:Body>
</s:Envelope>
Что стоит заметить:
- Название профиля (
mainStream,subStream) — произвольная метка производителя. Выбирать поток нужно не по имени, а по параметрам кодера:Encoding,Resolution,FrameRateLimit,BitrateLimit. Токен (token) — это идентификатор, который передаётся дальше. - Атрибут
GovLength— длина группы кадров. По описанию в схеме он определяет, как часто в потоке встречаются опорные (I) кадры. Для нас это важно практически: именно от него зависит, сколько ждать первой картинки после подключения. В Profile T для этого естьSetSynchronizationPoint, но проверять реальную задержку нужно на конкретных камерах — проверить. fixed="true"значит, что профиль нельзя удалить.
Шаг 5. Адрес потока: GetStreamUri
Наконец, ради чего всё затевалось. Запрос в Media2:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tr2="http://www.onvif.org/ver20/media/wsdl">
<s:Body>
<tr2:GetStreamUri>
<tr2:Protocol>RTSP</tr2:Protocol>
<tr2:ProfileToken>profile_main</tr2:ProfileToken>
</tr2:GetStreamUri>
</s:Body>
</s:Envelope>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:tr2="http://www.onvif.org/ver20/media/wsdl">
<s:Body>
<tr2:GetStreamUriResponse>
<tr2:Uri>rtsp://192.168.1.64:554/stream1</tr2:Uri>
</tr2:GetStreamUriResponse>
</s:Body>
</s:Envelope>
Значение RTSP в Protocol означает RTP поверх TCP (то есть чередующиеся interleaved-кадры внутри RTSP-соединения), а не «просто RTSP». Остальные значения Media2: RtspUnicast (RTP/UDP), RtspMulticast, RtspsUnicast, RtspsMulticast (SRTP) и RtspOverHttp (туннелирование).
В Media (ver10) тот же запрос выглядит иначе:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
xmlns:tt="http://www.onvif.org/ver10/schema">
<s:Body>
<trt:GetStreamUri>
<trt:StreamSetup>
<tt:Stream>RTP-Unicast</tt:Stream>
<tt:Transport>
<tt:Protocol>RTSP</tt:Protocol>
</tt:Transport>
</trt:StreamSetup>
<trt:ProfileToken>profile_main</trt:ProfileToken>
</trt:GetStreamUri>
</s:Body>
</s:Envelope>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
xmlns:tt="http://www.onvif.org/ver10/schema">
<s:Body>
<trt:GetStreamUriResponse>
<trt:MediaUri>
<tt:Uri>rtsp://192.168.1.64:554/stream1</tt:Uri>
<tt:InvalidAfterConnect>false</tt:InvalidAfterConnect>
<tt:InvalidAfterReboot>false</tt:InvalidAfterReboot>
<tt:Timeout>PT0S</tt:Timeout>
</trt:MediaUri>
</trt:GetStreamUriResponse>
</s:Body>
</s:Envelope>
Две подробности, которые легко пропустить:
- В ответе Media стоят поля
InvalidAfterConnect=false,InvalidAfterReboot=falseиTimeout=PT0S: по спецификации ссылка действует бессрочно, даже если профиль изменился. Её можно сохранить и не запрашивать при каждом старте. - В описании операции в WSDL Media значение типа потока написано как
RTP_unicast, тогда как перечисление в XSD допускаетRTP-Unicast. Это расхождение внутри самих документов; в запросе стоит использовать значение из XSD — именно по нему и проверяются схемы.
Дальше начинается территория статьи про RTSP: DESCRIBE, SETUP, PLAY, Digest уже на уровне RTSP. ONVIF свою часть сделал — вернул адрес.
Авторизация: самая болезненная часть
Два способа, один пользователь
По Core Specification все сервисы ONVIF должны быть защищены HTTP Digest (RFC 2617), с двумя исключениями: «унаследованные» устройства, которые поддерживают WS-UsernameToken, и TLS-авторизация клиента. Если устройство поддерживает оба способа, клиент может использовать любой: на уровне HTTP (Digest) или на уровне SOAP (заголовок wsse:Security). Пользователи общие.
| Способ | Где живёт | Что нужно знать |
|---|---|---|
| HTTP Digest | Заголовки HTTP | Обязателен для Profile T; Core Specification ссылается на RFC 2617, где базовый алгоритм — MD5 |
| WS-UsernameToken | SOAP-заголовок | Обязателен для устройств Profile S; требует точного времени и nonce |
| TLS | Транспорт | Рекомендуется Core Specification; в Profile T условный |
Профиль S по его тексту (v1.1.1) требует от устройства WS-UsernameToken, а HTTP Digest для устройства — необязательная функция, при этом клиент обязан уметь и то, и другое. Сведения о тексте Profile S взяты из вторичной копии документа, поэтому формулировку стоит перепроверить на onvif.org. Практический вывод для клиента: поддерживать оба способа, иначе часть старых камер окажется недоступной.
Сама спецификация достаточно прямолинейно оценивает защиту: и Digest, и UsernameToken дают лишь «rudimentary level of security» (элементарный уровень), а там, где безопасность важна, рекомендуется настраивать доступ по TLS. Мой вывод для проектирования: аутентификация камеры защищает от ошибок и случайных подключений, но не от злоумышленника, уже находящегося в одной сети с камерой. Поэтому камеры лучше держать в отдельном VLAN — это отдельный разговор, и он связан с архитектурой Edge-узла.
Как отличить один способ от другого по ответу
Core Specification описывает поведение, по которому можно определить, какую авторизацию ждёт устройство:
- Если устройство поддерживает Digest и клиент не прислал учётных данных, оно вернёт HTTP 401 — клиент понимает, что нужен Digest.
- Если устройство поддерживает только UsernameToken, ответ другой: HTTP 400 и SOAP-ошибка
ter:NotAuthorizedс кодомs:Sender.
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:ter="http://www.onvif.org/ver10/error">
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value>ter:NotAuthorized</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en">Sender not authorized</s:Text>
</s:Reason>
</s:Fault>
</s:Body>
</s:Envelope>
Клиент не должен отправлять одновременно HTTP-Digest и WS-UsernameToken: устройство сначала проверит HTTP-уровень, а затем — SOAP-уровень. Если вы смешаете оба, ошибка в любом из них приведёт к отказу.
Подпись UsernameToken: формула и время
Пароль в SOAP не передаётся: вместо него идёт дайджест:
PasswordDigest = Base64( SHA1( Nonce + Created + Password ) )
Здесь Nonce — случайные байты, которые клиент генерирует заново для каждого запроса (в XML они кодируются Base64, а в хеш входят сырые байты), Created — время запроса в UTC, Password — пароль. Core Specification требует, чтобы клиент использовал и nonce, и метку времени; сервер обязан отклонить токен, где нет любого из них.
Из-за Created возникает главная практическая проблема. Камера сравнивает время в запросе со своим, и если расхождение больше допуска, запрос отклоняется. Допуск стандартом не задан; в ответах разработчиков встречается значение около 5 секунд у Axis, а руководство для разработчиков ONVIF в примерах использует ту же «пятисекундную» оговорку. Часы камеры, которая не ходила к NTP-серверу, быстро уезжают.
Руководство предлагает аккуратное решение, которое меняет клиент, а не камеру:
- Вызвать
GetSystemDateAndTime(без авторизации). - Вычислить разницу между временем камеры и своим.
- Хранить эту разницу как поправку для этой камеры и подставлять её в
Created.
Не нужно менять часы камеры только для того, чтобы с ней можно было общаться. Минимальный пример на Python (стандартная библиотека, без сторонних пакетов):
import base64, hashlib, os, re
from datetime import datetime, timedelta, timezone
from urllib.request import Request, urlopen
SOAP = "http://www.w3.org/2003/05/soap-envelope"
def envelope(body, header=""):
return (f'<s:Envelope xmlns:s="{SOAP}"><s:Header>{header}</s:Header>'
f'<s:Body>{body}</s:Body></s:Envelope>')
def device_time_offset(xaddr):
"""GetSystemDateAndTime needs no authentication: ask the camera what time it is."""
body = '<tds:GetSystemDateAndTime xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>'
req = Request(xaddr, envelope(body).encode(),
{"Content-Type": "application/soap+xml; charset=utf-8"})
xml = urlopen(req, timeout=5).read().decode()
utc = re.search(r"<(?:\w+:)?UTCDateTime>(.*?)</(?:\w+:)?UTCDateTime>", xml, re.S).group(1)
g = lambda tag: int(re.search(rf"<(?:\w+:)?{tag}>(\d+)<", utc).group(1))
cam = datetime(g("Year"), g("Month"), g("Day"), g("Hour"), g("Minute"), g("Second"),
tzinfo=timezone.utc)
return cam - datetime.now(timezone.utc) # remember it; do NOT touch the camera clock
def username_token(user, password, offset=timedelta(0)):
nonce = os.urandom(16)
created = (datetime.now(timezone.utc) + offset).strftime("%Y-%m-%dT%H:%M:%SZ")
digest = base64.b64encode(
hashlib.sha1(nonce + created.encode() + password.encode()).digest()).decode()
return (
'<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" '
'xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'
f"<wsse:UsernameToken><wsse:Username>{user}</wsse:Username>"
'<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'
f"{digest}</wsse:Password>"
'<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'
f"{base64.b64encode(nonce).decode()}</wsse:Nonce>"
f"<wsu:Created>{created}</wsu:Created></wsse:UsernameToken></wsse:Security>"
)
Код проверен на локальном «макете камеры», который сверяет дайджест и отклоняет запрос при расхождении часов более 5 секунд: без поправки при разнице в 17 минут приходит HTTP 400, с поправкой запрос проходит. Формула дайджеста дополнительно сверена с независимой реализацией (библиотека zeep). Это проверка логики, а не гарантия работы с любой реальной камерой.
Камеры на практике: где стандарт заканчивается
Ниже — типичные проблемы. Здесь оставлены только те проблемы, для которых есть источник; остальные честно помечены как требующие проверки.
Проблема 1: часы камеры
Камера без NTP и с разъехавшимися часами не принимает UsernameToken, при этом отвечая невнятной ошибкой авторизации. В инструкции Digital Watchdog для камер Hikvision сказано, что расхождение более 10 минут может нарушить авторизацию.
Рекомендация. Всегда начинайте с GetSystemDateAndTime, храните поправку и логируйте её. Если поправка велика — это сигнал оператору «у камеры нет NTP», а не повод молча переписывать её часы.
Проблема 2: ONVIF по умолчанию выключен
По той же инструкции, в прошивках Hikvision начиная с версии 5.5.0 ONVIF по умолчанию отключён, а режим аутентификации называется «Digest & ws-username token». Включать его приходится вручную, и это нужно сделать до того, как вы начнёте считать камеру «неподдерживаемой».
Та же инструкция предупреждает о блокировке: многократные неверные попытки входа могут заблокировать камеру на 30 минут («Illegal Login Lock»).
Рекомендация. Различайте «не нашлась» (нет ответа на Probe), «ONVIF выключен» (порт не отвечает или нет сервиса) и «не авторизовалась» (401/400). После двух-трёх неудач — пауза и сообщение оператору, а не бесконечный цикл повторов: он может заблокировать камеру на полчаса.
Проблема 3: две разные ошибки авторизации
Как показано выше, 401 означает «нужен Digest», а 400 с NotAuthorized — «ждали UsernameToken». Не все клиенты это различают, а в логах это выглядит как просто «ошибка».
Рекомендация. Разбирайте ответ, а не только код: определяйте способ авторизации по первому ответу и запоминайте его для этой камеры. Используйте один способ за раз.
Проблема 4: адрес в ответе не тот
Согласно описанию GetServices в WSDL, схема и IP в XAddr должны совпадать с теми, по которым устройству отправили запрос. Это означает, что камера, которая возвращает внутренний адрес вместо того, по которому к ней обратились (например, из-за NAT или проброса портов), нарушает спецификацию. Для GetStreamUri такого прямого требования в изученных документах нет: там сказано лишь, что адрес бессрочный и не должен быть длиннее 128 октетов. Насколько часто встречается «неправильный IP» в ссылке на поток — проверить.
Рекомендация. Сравнивайте хост из ответа с тем, к которому вы подключались. Если они различаются — запишите это в лог. Подменять хост (оставляя путь и порт) стоит только как отдельное, явное, проверенное на конкретной камере решение.
Проблема 5: «главный» и «дополнительный» поток
Имена профилей произвольны, а настройки (кодек, разрешение, частота, GovLength) у главного и дополнительного потоков различаются. Какие именно различия бывают у реальных камер и как они влияют на задержку первого кадра, не измерялось — проверить.
Рекомендация. Выбирайте профиль по параметрам кодера. Для обнаружения событий часто достаточно дополнительного потока, а главный нужен для записи. Но решение принимайте по измерениям на своих камерах.
Проблема 6: камера молчит на Probe
Причины, подтверждённые документами: устройство может быть в режиме non-discoverable; нестандартный Probe молча отбрасывается. Ещё одна, о которой пишут практики, но которая здесь не проверялась: multicast-обнаружение не проходит между подсетями и VLAN без специальной настройки — проверить на своей сети.
Рекомендация. Всегда оставляйте ручной режим: если оператор указал IP камеры, пробуйте GetSystemDateAndTime напрямую по http://<ip>/onvif/device_service, а при неудаче — по адресам из документации производителя. Это соглашение, а не гарантия.
Проблема 7: лимиты
Один подтверждённый лимит есть в Profile T: устройство обязано допускать минимум две подписки PullPoint. Больше — на усмотрение производителя, поэтому рассчитывать на много одновременных читателей событий нельзя. Для Media2 устройство сообщает MaximumNumberOfProfiles. Сколько одновременных RTSP-подключений выдержит конкретная камера, стандарт не определяет — проверить на своих моделях.
Рекомендация. Одно подключение к каждому потоку, а дальше — ретрансляция внутри узла. Так камера не зависит от числа потребителей. Это согласуется с архитектурой Edge-узла, где камеру читает один компонент (CameraManager).
Куда движется ONVIF
Конец Profile S
В октябре 2025 года ONVIF объявил, что завершает поддержку Profile S и рекомендует Profile T. Причина — устаревшая модель авторизации UsernameToken. По данным отраслевых блогов, новые подтверждения соответствия по Profile S принимаются до 31 марта 2027 года; точные сроки лучше проверить на onvif.org. Установленные камеры продолжат работать как работали, а значит, Profile S останется в парке на годы.
Для клиента это решает вопрос приоритета: Profile T — основа для новых устройств, Profile S — запасной вариант для старых.
Profile V: камера звонит домой
В июле 2026 года ONVIF объявил Release Candidate Profile V — профиля для облачного видео, который должен быть окончательно утверждён к концу 2026 года. Основная идея: устройство само устанавливает защищённое исходящее соединение (Uplink) с облачной системой, и ничего пробрасывать через NAT или поднимать VPN обычно не нужно. По данным анонса, живое видео и аудио идут по WebRTC, события отправляются в облако, а зашифрованные записи — в облачное хранилище; для авторизации требуется OAuth 2.0.
Подробности (версия спецификации Uplink, WebSocket Secure, mutual TLS) найдены только в блоге вендора и считаются вторичным источником. Сама спецификация сейчас доступна только участникам ONVIF для рецензии, поэтому всё, что сказано выше, — направление, а не факт. Но направление знакомое: архитектура Edge-узла тоже строится на исходящем соединении без входящих портов, и если Profile V утвердят в рассматриваемом виде, они окажутся близки по идее.
Что это значит для Edge-узла
Если свести всё сказанное к решениям для проекта:
- Profile T первым, Profile S запасным. Сначала
GetServices; есть Media2 — работаем через него; нет — через Media. - Начинать с
GetSystemDateAndTime. Поправка времени хранится вместе с остальными данными камеры. - Поддерживать оба способа авторизации и запоминать, какой нужен камере.
- Не хардкодить адреса сервисов. Сохранять
XAddrизGetServices, аGetStreamUriвызывать один раз и сохранять ссылку. - События движения и саботажа (в Profile T они обязательны) — дешёвый источник сигналов без анализа видео.
- Профиль V — не опора, а направление; слой, работающий с камерой, стоит отделить от остального так, чтобы замена не требовала переписывания.
Что я пока не проверял на живых камерах
Эта статья опирается на спецификации, схемы и документацию производителей, но не на измерения. Вот что остаётся открытым:
- Как часто камеры возвращают неверный IP в
GetStreamUriиGetServices. - Различия главного и дополнительного потоков и реальная задержка первого кадра при разных
GovLength. - Работает ли WS-Discovery между VLAN и что именно для этого нужно настроить.
- Допуск по времени у конкретных моделей: 5 секунд — лишь одно из встречающихся значений.
- Сколько одновременных RTSP-соединений выдерживают конкретные камеры.
- Какие именно префиксы и форматы XML принимают капризные реализации.
- Актуальная формулировка Profile S (изучена вторичная копия) и точные сроки его вывода.
- Всё, что касается Profile V: профиль ещё не утверждён.
Когда появятся результаты по реальным камерам, я дополню статью таблицей совместимости.
Ключевые выводы
- ONVIF отвечает на вопросы, с которых начинается RTSP: где камера, что она умеет, какой адрес у потока и как авторизоваться. Видео он не передаёт.
- Стандарт возник из договорённости трёх компаний в 2008 году и стал рабочим, потому что рядом сразу появились тесты и процесс подтверждения соответствия.
- Профили — это обещания. S — прошлое, T — настоящее, V — возможное будущее. Обещание камеры и обещание клиента проверяются раздельно.
- Последовательность неизменна: Probe →
GetSystemDateAndTime→GetServices→GetProfiles→GetStreamUri→ RTSP. - Время решает всё. Для UsernameToken нужны синхронные часы; самое аккуратное решение — не менять камеру, а хранить поправку на клиенте.
- Авторизация — самая хрупкая часть: два способа, два вида ошибок, блокировки после неудачных попыток. Различайте их и не повторяйте вслепую.
- Не доверяйте адресам из ответов и именам профилей — проверяйте и сравнивайте с тем, что вы знаете о камере.
- Всё, что не проверено на железе, должно быть помечено. Стандарт описывает идеальную камеру, и реальные устройства расходятся с ним в деталях.
Источники
- ONVIF — Wikipedia: основание, даты версий и профилей.
- ONVIF: Building your security future (2026) и Mission: число изделий и участников; список conformant products.
- ONVIF Core Specification v19.06: discovery, авторизация, классы доступа; схемы WSDL/XSD: Device, Media, Media2, onvif.xsd (версия 26.06).
- ONVIF Profile T Specification v1.0.
- ONVIF Application Programmer’s Guide v1.0: первые действия после обнаружения, поправка времени.
- OASIS WS-Security UsernameToken Profile 1.0.
- Security Systems News: ONVIF ending support for Profile S (2025-10-09) и о Profile V (2026-07-10).
- Digital Watchdog: Enabling ONVIF on Hikvision cameras.
- Вторичные источники (блоги и форумы; использованы осторожно): сведения о PSIA, допуск по времени у Axis (обсуждения на Stack Overflow), детали Uplink в Profile V (блог Fora Soft).