O VMS não fala Profile M
Não há trilha de metadados para consumir. Não há como o VMS desenhar um box que ele não sabe receber.
Caso de sucesso — Caso 01 · Video Gateway
Como levamos analítica de vídeo por IA para VMS que nunca foram feitos para recebê-la. Três fases, um gateway: overlay em pixel para sistemas legados, atuação física via relé e ONVIF Profile M com 20× mais densidade por servidor — sem tocar em uma linha do VMS do cliente.
Resumo executivo
A tecnologia de IA do cliente era excelente — mas boa parte da base instalada não conseguia enxergá-la. Esses clientes finais operavam VMS (Video Management Systems) antigos: plataformas que gravam e exibem vídeo, projetadas antes de a indústria padronizar o transporte de metadados de analítica. Não falavam ONVIF Profile M.
E, por política de segurança e de suporte, não permitiam nenhuma integração externa no VMS: nada de plugins, nada de SDK, nada de agente instalado. O pior cenário para um fornecedor de IA — o alerta existia, era preciso, era acionável, e morria numa tela paralela que o operador não olhava.
Construímos o Sentry Vision Gateway: um gateway de vídeo que resolve o impasse em três fases, sem tocar em uma linha sequer do VMS do cliente final. Um único produto passou a atender toda a base instalada — do VMS de quinze anos atrás ao lançado no ano passado — com um salto de densidade que derrubou o custo de infraestrutura por canal em mais de 90% no modo moderno.
| Fase | O que entrega | Para quem |
|---|---|---|
| 1 · Overlay Engine | Redesenha os bounding boxes dentro do próprio pixel da imagem e republica a stream. O VMS antigo enxerga “mais uma câmera”. | VMS legado, sem integração possível |
| 2 · Decision Engine | Motor de regras + driver completo de relé de rede. O alerta vira ação física: cancela, sirene, strobe, iluminação. | Qualquer instalação que precise atuar no mundo real |
| 3 · Stack ONVIF Profile M | Vídeo em passthrough e metadados em trilha dedicada, sincronizados no mesmo timestamp RTP. | VMS moderno, com ganho de ~20× em densidade |
O problema
Analítica de vídeo só gera valor quando chega ao operador dentro do fluxo de trabalho que ele já usa — o VMS. A indústria resolveu isso com o ONVIF Profile M, mas o perfil é recente diante da vida útil de um VMS: trocar o sistema não é decisão de TI, é decisão de CapEx, de projeto e de risco operacional. Bases instaladas de dez, quinze anos são a norma.
Não há trilha de metadados para consumir. Não há como o VMS desenhar um box que ele não sabe receber.
Sem plugin, sem SDK, sem agente no ambiente do VMS. Motivos legítimos: homologação, contrato de suporte, política de segurança, superfície de ataque.
Ver o alerta é metade do valor. A outra metade é o portão que fecha, a sirene que dispara, a cancela que trava — atuação em relés de rede com garantias de confiabilidade e rastreabilidade.
Restava um único canal que todo VMS entende, do mais antigo ao mais novo, sem exceção e sem integração: a própria stream RTSP de vídeo. Se o único canal disponível é o vídeo em si, é possível entregar analítica através dele — de forma sincronizada, confiável, auditável e viável em escala?
O Sentry Vision Gateway é a resposta em três atos.
Visão geral da solução
O gateway se posiciona entre as câmeras, o sistema de analítica e os consumidores finais:
-c copy, sem recodificar) num servidor de mídia interno, de onde a analítica consome. A IA sempre vê a imagem original, sem overlay — essencial para não contaminar a inferência.Fase 1
O objetivo: entregar o bounding box para um VMS que não tem nenhuma forma de recebê-lo, escrevendo-o no único lugar que esse VMS sabe ler — o pixel.
Para cada câmera, dois processos FFmpeg e um estágio .NET no meio. Ingestão: FFmpeg conecta via RTSP/TCP com -fflags nobuffer -flags low_delay e emite vídeo cru yuv420p no stdout. Producer thread: uma thread dedicada (LongRunning, fora do thread pool) lê os quadros com leitura zero-copy — os bytes vão direto para a memória não gerenciada de um Mat do OpenCV alugado de um pool. Desenho: os boxes são pintados diretamente nos planos YUV. Saída: um segundo FFmpeg recodifica com libx264 -preset ultrafast -tune zerolatency e publica a nova stream RTSP — o VMS aponta para essa URL como se fosse a câmera.
A analítica leva um tempo Δ para processar um quadro e emitir o alerta — de centenas de milissegundos a alguns segundos, variando com modelo, carga e rede. Pintar o box assim que ele chega é pintá-lo sobre um quadro do futuro: o operador vê um retângulo vermelho pairando sobre asfalto vazio. Pior que não ter overlay — porque destrói a confiança no sistema.
A solução: dois buffers e uma linha do tempo negociável. Uma fila de vídeo e uma fila de metadados, cada entrada carimbada com seu instante de chegada, e três parâmetros ajustáveis. Um quadro só é liberado quando envelheceu o suficiente; se o TTL expira, ele sai limpo — é preferível vídeo sem box do que box errado. Se o buffer se aproxima da capacidade, o atraso é abandonado e os quadros são forçados para fora.
| Parâmetro | Função |
|---|---|
| VideoDelayMs | Quanto tempo o vídeo é segurado antes de sair, para o metadado alcançá-lo |
| MetadataDelayMs | Ajuste fino de deslocamento, para analíticas que adiantam ou atrasam sistematicamente |
| MetadataTtlMs | Por quanto tempo um box permanece válido antes de expirar e o vídeo voltar limpo |
A abordagem ingênua converte YUV → BGR, desenha, e converte de volta: em quinze câmeras a 12 fps, são 180 pares de conversão por segundo sobre quadros de quase um megapixel. Optamos por desenhar direto no formato nativo: o plano Y é extraído com RowRange (view sem cópia), os planos U e V são reinterpretados com Reshape, as coordenadas são forçadas a fronteiras pares (exigência da subamostragem 4:2:0) e a cor hexadecimal é convertida para Y/U/V uma única vez, na ingestão do alerta. No caminho quente não existe aritmética de cor — apenas três Rectangle com escalares pré-calculados. Resultado: zero conversões de espaço de cor, e o overlay deixa de ser o gargalo.
MatPool. Pool thread-safe de Mat com dimensões fixas. Em regime permanente, nenhum quadro é alocado — os objetos circulam entre producer e consumer, com fallback seguro se o pool estourar.ReadOnlySpan<byte> direto da memória do OpenCV para o pipe, sem cópia intermediária.O endpoint REST recebe alertas polimórficos — onze tipos, cada um com seu esquema (TripWire, Entering, Exiting, LeftObject, Congestion, Speed, DeFence, Directional, UnusualBehaviour, Trex, Generic) — resolvidos por um conversor JSON customizado. Coordenadas chegam em percentual e são convertidas com a resolução real detectada, o que torna o sistema imune a divergências de resolução. E o endpoint responde imediatamente: a decisão de relé é enfileirada fora do ciclo de requisição — a IA nunca espera o gateway.
Fase 2
O objetivo: transformar alerta em ação no mundo real, com regras configuráveis, tolerância a falha de hardware e rastreabilidade completa.
Driver do módulo de relé de rede sobre a API do fabricante — não só o comando de acionar, mas o ciclo completo: consulta de capabilities, descoberta e enumeração de portas, configuração como saída (OpenCollectorOutput, sem a qual o acionamento não tem efeito), nice names gravados no próprio hardware, leitura de estado, acionamento individual e em lote, autenticação Digest.
Entre “chegou um alerta” e “aciona a porta 3” existe uma esteira determinística, projetada para falhar em segurança: validação da câmera (UUID desconhecido é descartado e auditado), normalização do nome de regra (trim, underscores, maiúsculas, remoção do prefixo AskAurora_ — absorvendo divergências de digitação entre sistemas) e arbitragem explícita de colisão:
| Regras que casaram | Comportamento |
|---|---|
| 0 | UNMAPPED_NAME — bloqueia, audita, segue. Nunca aciona por engano. |
| 1 | Caminho feliz: busca o mapeamento câmera + regra → porta de relé. |
| 2 | Regra de IA condicional + regra padrão: prioriza a de IA. Duas do mesmo tipo (DOUBLE_AURORA / DOUBLE_STANDARD): bloqueia — ambiguidade não vira acionamento. |
| >2 | COLLISION — bloqueia e reporta os IDs conflitantes para diagnóstico. |
Um mapeamento curinga por câmera (uuid_*) cobre regras não catalogadas. E as regras condicionais de IA avaliam respostas em linguagem natural com operadores configuráveis (Contains, Exactly matches, Starts with…) em AND estrito: uma resposta fora do esperado derruba a regra inteira. É onde o cliente calibra a taxa de falso acionamento.
TriggerOnDismiss — aciona justamente quando o alerta é rejeitado, para sinalizar “verificado e liberado” a sistemas externos.Não é log de aplicação — é um registro tipado e polimórfico, com esquema próprio por categoria: PROVISIONING, CAMERA_LIFECYCLE, CAMERA_RUNTIME, ALERT_DECISION e RELAY_ACTION. A pergunta que responde é “o que aconteceu com aquele alerta das 03h14?” — e a resposta é uma cadeia completa: chegou, casou com esta regra, decisão de acionar, porta 3 fechada por 5.000 ms, hardware respondeu 200, reabriu às 03h14:05. Em segurança eletrônica, isso é prova pericial, defesa de SLA, sintonia de falso positivo e conformidade.
Fase 3
O objetivo: para os VMS que já falam o padrão moderno, parar de queimar pixel — e ganhar uma ordem de grandeza em densidade.
Na fase 1 o gateway reescreve o vídeo; na fase 3 ele não toca no vídeo. O H.264 que sai é bit a bit idêntico ao da câmera; os metadados viajam numa trilha RTSP dedicada em tt:MetadataStream. Isso muda três coisas: qualidade (some a perda de geração), custo (somem decode e encode, ~95% do trabalho) e utilidade — o box deixa de ser pixel e vira informação: o VMS liga e desliga a exibição, busca por classe, indexa, aplica as próprias regras.
ONVIF define o espaço de bounding box em [-1, +1], origem no centro e eixo Y invertido. O percentual da analítica é convertido:
left = (x₁ × 2) − 1 right = (x₂ × 2) − 1 top = 1 − (y₁ × 2) bottom = 1 − (y₂ × 2)
O XML tt:MetadataStream é montado por substituição em template externo, não por serializador — deliberadamente: o template fica editável em campo, acomodando excentricidades de VMS específicos sem recompilar, e a substituição de texto é ordens de magnitude mais barata no caminho quente.
Cada câmera tem sua linha do tempo com âncora dupla: no primeiro quadro registram-se o relógio local e o relógio da câmera. O momento de emissão de cada quadro projeta o ritmo da câmera sobre o relógio local — em vez de assumir que os dois batem, o que nunca acontece:
alvo = base_local
+ (tempo_da_câmera − base_da_câmera)
+ VideoDelayMsRtpTimestamp do quadro que descreve — não com o timestamp do “agora”. O VMS desenha a caixa sobre o quadro certo, sempre, independente de jitter, reordenação ou latência da analítica. O sincronismo deixa de ser uma esperança e vira propriedade estrutural do protocolo.Um VMS moderno não recebe uma stream — ele descobre um dispositivo, autentica, consulta capacidades, escolhe um perfil e assina eventos. O stack de controle inteiro roda sobre CoreWCF:
| Serviço | Função |
|---|---|
| WS-Discovery | Multicast UDP :3702 em todas as interfaces aptas, IPv4 e IPv6. O VMS acha o gateway sozinho. |
| device_service | Identidade, capabilities, rede, usuários, data e hora |
| media / media2 | Perfis de mídia, configuração de metadados, GetStreamUri |
| analytics_service | Módulos e regras de analítica expostos ao VMS |
| events_service | Assinaturas base e PullPoint, com roteamento por ID |
| Autenticação | Digest e WS-UsernameToken — exigência de VMS corporativos |
Do ponto de vista do VMS, o gateway é indistinguível de uma câmera inteligente de primeira linha. Não há passo de integração — há um passo de descoberta.
A conta da densidade
Âncora medida: 15 câmeras 720p a 12 fps saturam o servidor de referência.
1280 × 720 px × 1,5 B (YUV420p) = 1,38 MB/quadro 1,38 MB × 12 fps = 16,6 MB/s por câmera Agregado · 15 câmeras: vídeo cru manipulado ~249 MB/s tráfego de pipe (×2) ~498 MB/s trabalho de codec ~332 Mpx/s processos FFmpeg 45
332 Mpx/s equivale a decodificar e recodificar ~5,3 streams 1080p30 simultaneamente. O gargalo é aritmética de pixel — a CPU satura muito antes de a rede sentir qualquer coisa.
Sem decode e sem encode, o trabalho por quadro vira: varrer bytes já comprimidos por start codes e copiá-los para pacotes RTP. Uma câmera 720p a ~2 Mbps são ~250 KB/s. Redução no volume de bytes tocados por câmera: ~66× a 12 fps, ~138× a 25 fps. Aplicando margem deliberadamente conservadora de 20× (há custos fixos que não escalam com bytes: syscalls, RTSP interleaved, threads), a projeção é ~300 câmeras por servidor — e o gargalo migra de CPU para rede (1 GbE ≈ 200 canais; 10 GbE ≈ 300–500).
| Fase 1 · Overlay | Fase 3 · Profile M | |
|---|---|---|
| Câmeras por servidor | 15 | ~300 |
| Dados manipulados | ~498 MB/s | ~75 MB/s |
| Trabalho de codec | 332 Mpx/s | zero |
| Processos FFmpeg | 45 | 1 remux por câmera |
| Qualidade do vídeo | recodificado | bit a bit idêntico |
| Latência adicionada | decode + buffer + encode | apenas o alinhamento configurado |
| Gargalo | CPU | rede |
Engenharia
| # | Desafio | Solução |
|---|---|---|
| 01 | Metadado chega depois do quadro (latência variável da IA) | Buffers duplos com atraso, TTL e deslocamento ajustáveis em runtime, por câmera |
| 02 | Custo de conversão de espaço de cor no overlay | Desenho direto nos planos Y/U/V com views sem cópia; cor convertida uma vez na ingestão |
| 03 | Pressão de GC e fragmentação de memória não gerenciada | MatPool com reuso em regime permanente e fallback seguro |
| 04 | Travamento em cascata por pipe cheio do FFmpeg | Ler-e-descartar sob contrapressão, mantendo o produtor sempre drenando o stdout |
| 05 | Artefato verde / tela cinza ao conectar cliente RTSP | Espera obrigatória pelo primeiro IDR e extração de SPS/PPS antes de emitir |
| 06 | Dessincronia vídeo × metadado em Profile M | XML amarrado ao mesmo RtpTimestamp do quadro descrito |
| 07 | Deriva entre relógio da câmera e local | Âncora dupla e projeção de timeline, com descarte além de 1 s de atraso |
| 08 | Divergência de nomes de regra entre sistemas | Normalização (trim, underscore, maiúsculas) e remoção de prefixo |
| 09 | Ambiguidade de configuração de regras | Arbitragem explícita de colisão, com prioridade definida e bloqueio em empate |
| 10 | Falha de relé derrubando o serviço | Retry com backoff + circuit breaker, em domínio de falha isolado do vídeo |
| 11 | Chaveamento errático de sirenes e cancelas | Leitura de estado real antes de acionar; desligamento temporizado em background |
| 12 | Validar mapeamentos em produção sem atuar | Dry-run com auditoria completa e zero comandos ao hardware |
| 13 | Convenção de coordenadas ONVIF ([-1,+1], Y invertido) | Camada de conversão explícita a partir do percentual da analítica |
| 14 | Interpretações divergentes de XML entre VMS | Template de metadados externo, editável em campo sem recompilação |
| 15 | Descoberta pelo VMS sem passo de integração | WS-Discovery multicast em todas as interfaces aptas, IPv4 e IPv6 |
| 16 | Pipeline travado sem operador presente | Watchdog com reinício automático e backoff exponencial |
| 17 | Configuração manual de resolução propensa a erro | Detecção automática de resolução e FPS reais, persistidos no banco |
| 18 | Provisionamento de dezenas de câmeras | Sincronização automática com a plataforma de analítica, com proteção contra reentrância |
Buffer ciente de GOP. Câmeras com GOP longo (5–10 s entre quadros-chave) expõem uma premissa sutil: um buffer dimensionado só pela taxa de quadros pode ser menor que um GOP — e, ao aparar o excesso, descartar o IDR. O cliente conecta, não recebe referência, e o timeout parece problema de rede. Três frentes: encurtar o GOP na origem, tornar o buffer ciente de fronteira de GOP, e cache de IDR por câmera para entrega imediata a cada novo cliente. A lição: buffer de vídeo se dimensiona pelo intervalo de GOP, não só pela taxa de quadros.
Empacotamento Windows. Distribuído como serviço Windows via WiX, com MajorUpgrade, registro idempotente, rollback e verificação estrita de retorno. A lição é de arquitetura: portas e caminhos precisam de uma fonte única de verdade — centralizar configuração é barato no dia um e caro no dia mil.
Teste de carga embarcado. O produto traz o próprio arnês: sobe N câmeras com rampa configurável, zera contadores após estabilizar, amostra CPU, memória, threads, handles e processos FFmpeg — e, por câmera, FPS de entrada, saída e descartes — persistindo a série temporal para comparação entre execuções. É o que permite responder “cabe quantas câmeras nesse servidor?” com um dado, não uma impressão.
Console de operação em React: dashboard em tempo real, cadastro e diagnóstico de câmeras, sliders de sincronismo com efeito imediato, grade ao vivo, mapeamento de relés com teste individual, usuários, streams e testes de carga. Provisionamento automático: câmeras e regras sincronizam sozinhas com a plataforma de analítica, cada mudança auditada — instalações com centenas de câmeras não dependem de cadastro manual nem divergem da fonte de verdade.
Stack tecnológico
Benefícios de negócio
Resultados
| Indicador | Resultado |
|---|---|
| Base instalada atendível | De apenas VMS com Profile M para qualquer VMS com suporte a RTSP |
| Integrações necessárias no VMS do cliente | Zero |
| Densidade por servidor (Profile M) | ~300 câmeras vs. 15 no modo overlay — 20× |
| Dados manipulados no servidor | 6,6× menos, com 20× mais câmeras |
| Infraestrutura para 1.000 câmeras | De ~67 para ~4 servidores — redução de ~94% |
| Qualidade de vídeo (Profile M) | Sem perda de geração — bit a bit idêntico à fonte |
| Tipos de alerta suportados | 11, com tratamento polimórfico |
| Cobertura de auditoria | 5 categorias, do provisionamento à porta de relé |
| Ajuste de sincronismo | Em runtime, por câmera, sem reinício |
O problema parecia ser de compatibilidade de protocolo. Não era — era um problema de canal: como entregar informação nova através do único caminho que o sistema antigo aceita. A fase 1 usou o próprio pixel como canal, o que só funciona com sincronismo temporal resolvido de verdade. A fase 2 estendeu esse canal até o mundo físico, com as garantias que atuação em hardware exige. E a fase 3 reconheceu que, quando o canal correto existe, usá-lo não é apenas mais elegante — é 20× mais barato.
O que fica é um produto que não obriga ninguém a escolher entre o sistema que já tem e a tecnologia que quer usar.
Próximo projeto