Es un caso real del trabajo que ocurrio hace bastante tiempo, anonimizado donde corresponde — las IPs, usuarios y contraseñas son ficticios, pero el incidente y los pasos son tal cual los viví.
Hay un tipo de problema que me resulta especialmente frustrante: el que se ve simple por fuera pero esconde algo completamente distinto adentro.
Este es uno de esos.
Estaba revisando el sistema de videovigilancia de un sitio industrial — un servidor Ubuntu corriendo ZoneMinder v1.36.12 sobre una VM VMware, con once cámaras IP distribuidas en la red LAN. Todo parecía estar funcionando, hasta que alguien notó que una de las cámaras, una AXIS Q1775, aparecía en la vista Montage con un número que no cuadraba: State: Idle - 3 fps.
La cámara estaba configurada a 15 fps en ZoneMinder. Algunos incluso habían intentado subirla a 30 en la URL del stream. No cambiaba nada.
Lo que hacía aún más confuso el asunto era que otro sistema de visualización — completamente independiente — mostraba la misma cámara, con el mismo stream RTSP, a 30 fps perfectamente fluidos. Sin problema alguno.
Entonces el problema no era la cámara. No era la red. Era algo en el medio.
En resumen
| Sistema | ZoneMinder v1.36.12 en Ubuntu (VM VMware) |
| Cámara | AXIS Q1775, stream RTSP sobre LAN industrial |
| Síntoma | 3 fps efectivos en ZoneMinder, configurada a 15 fps |
| Causa raíz | GOP=62 en la cámara → I-frame cada ~2 seg → frames corruptos descartados |
| Causa secundaria | Resolución desalineada entre cámara (800x450) y ZoneMinder (1280x720) |
| Solución | Bajar GOP a 30, bitrate fijo 4000 kbit/s, igualar resolución en ZM |
El primer paso: desconfiar de ZoneMinder
Cuando un stream se ve bien en un visualizador pero mal en otro, lo primero que hay que descartar es que el problema esté en cómo ZoneMinder está recibiendo e interpretando el video — no en lo que la cámara está enviando. Para eso, necesitaba hablarle directamente al stream, sin intermediarios.
ffprobe es perfecto para esto. Le pasé la URL RTSP de la cámara y le pedí que me dijera exactamente qué estaba llegando:
ffprobe -v error -select_streams v:0 \
-show_entries stream=r_frame_rate,avg_frame_rate \
-of default=noprint_wrappers=1 \
"rtsp://<usuario>:<contraseña>@<ip-camara>/axis-media/media.amp?videocodec=h264&resolution=1280x720&fps=30"
El output que obtuve fue este:
[h264] left block unavailable for requested intra4x4 mode -1
[h264] error while decoding MB 0 11, bytestream 109565
[h264] left block unavailable for requested intra4x4 mode -1
[h264] error while decoding MB 0 39, bytestream 32447
r_frame_rate=30/1
avg_frame_rate=0/0
A primera vista puede parecer contradictorio. r_frame_rate=30/1 dice que la cámara sí está enviando 30 fps. Pero avg_frame_rate=0/0 significa que ffprobe no pudo calcular un promedio real — señal de que algo estaba llegando corrupto o incompleto. Y los errores de intra4x4 mode -1 lo confirmaban: había frames H.264 corruptos llegando al servidor.
Lo que estaba pasando era esto: ZoneMinder recibía el stream, encontraba frames que no podía decodificar correctamente, y los descartaba. Solo guardaba los que llegaban íntegros. El resultado visible era 3 fps efectivos, aunque la cámara mandara 30.
Revisar la configuración en ZoneMinder
Con eso claro, fui a revisar cómo estaba configurado el monitor en ZoneMinder. En Edit → Source encontré esto:
- Source Path:
rtsp://<usuario>:<contraseña>@<ip-camara>/axis-media/media.amp?videocodec=h264&resolution=1280x720&fps=15 - Method: TCP
- Options:
rtsp_transport=tcp, framerate=15 - Capture Resolution:
1280x720
Ahí apareció el primer dato relevante: ZoneMinder estaba configurado para capturar a 1280x720. Pero esa no era la resolución que la cámara estaba enviando realmente. Eso lo confirmé en el siguiente paso.
La configuración interna de la cámara: donde estaba el problema real
Accedí a la interfaz web de la cámara y fui directo a Setup → Video & Audio → Video Stream. En la pestaña Image encontré lo primero:
- Resolution:
800x450
Ahí estaba la desalineación que había detectado antes. ZoneMinder esperaba un stream de 1280x720, pero la cámara enviaba 800x450. Esa diferencia sola ya es suficiente para generar frames mal interpretados.
Pero el problema principal estaba en la pestaña H.264:
- GOP length:
62 - H.264 profile: Main
- Bitrate Control: Variable bitrate
- Target bitrate: vacío
Ese número — 62 — era la causa raíz de todo.
Qué es el GOP y por qué importa
Para entender por qué GOP=62 causaba el problema, primero hay que entender cómo funciona el video H.264 por dentro — y no es tan complicado como suena.
Cuando una cámara graba y transmite video, enviar cada fotograma como una imagen completa e independiente consumiría un ancho de banda enorme. En lugar de eso, H.264 usa un sistema más inteligente: clasifica los frames en tres tipos según cuánta información contienen.
I-frame (Intra-frame): Es el fotograma completo. Contiene toda la información visual de ese instante, sin depender de nada anterior. Es el punto de partida, la referencia. También se le llama keyframe. Es el más pesado en términos de datos.
P-frame (Predicted frame): Solo contiene lo que cambió respecto al frame anterior. Si una persona camina por una habitación, el P-frame no guarda toda la habitación de nuevo — solo guarda la diferencia: dónde estaba la persona antes y dónde está ahora. Pesa mucho menos que un I-frame, pero depende de él para tener sentido.
B-frame (Bidirectional frame): Similar al P-frame, pero más eficiente aún: calcula diferencias mirando tanto el frame anterior como el siguiente. Es el más liviano de los tres, pero también el más dependiente.
Una buena analogía: imagina que estás describiendo una película por escrito. Un I-frame sería describir la escena completa desde cero — cada personaje, cada objeto, cada color. Un P-frame sería decir "igual que antes, pero ahora el protagonista está dos metros a la derecha". Un B-frame sería "el punto intermedio entre el frame anterior y el siguiente". Mucho más eficiente, pero si pierdes la descripción base, las demás no tienen sentido.
El GOP (Group of Pictures) es justamente el intervalo entre un I-frame y el siguiente. Define cada cuántos frames se "resetea" la referencia con una imagen completa.
Con GOP=62 y la cámara transmitiendo a 30 fps, la matemática es directa:
62 frames ÷ 30 fps = un I-frame completo cada ~2 segundos
En esos dos segundos, la cámara envía un I-frame seguido de 61 frames que dependen de él para decodificarse. Si en algún punto de esos dos segundos se pierde un paquete en la red — algo perfectamente posible en una red industrial con tráfico variado — todos los P-frames y B-frames que siguen se vuelven indecodificables. No hay forma de reconstruirlos sin la referencia completa.
ZoneMinder no adivina ni interpola: simplemente descarta los frames corruptos. Y lo que queda son los pocos frames que llegaron íntegros — en este caso, suficiente para sumar unos 3 fps efectivos.
El otro visualizador lo manejaba mejor probablemente porque implementaba algún mecanismo de corrección de errores o tenía un buffer más generoso. Pero eso era un parche encima del problema real: el stream estaba mal configurado desde la cámara.
La solución era bajar el GOP a 30 — un I-frame por segundo a 30 fps. Así, si se pierde un paquete, la ventana de afectación se reduce a un segundo en lugar de dos, y la probabilidad de que ZoneMinder descarte frames masivamente baja drásticamente.
La solución
Con la causa raíz clara, los cambios a aplicar eran concretos y en dos frentes: la cámara y ZoneMinder.
En la cámara Axis, accediendo a la interfaz web en Setup → Video & Audio → Video Stream → H.264:
- GOP length: de
62a30— un I-frame por segundo a 30 fps, ventana de afectación mínima ante pérdida de paquetes - Bitrate Control: de Variable a
Maximum bitrate - Target bitrate:
4000 kbit/s
El cambio a bitrate máximo fijo también ayuda: con bitrate variable, la cámara decide cuántos datos enviar en cada momento, y en escenas con poco movimiento puede reducirlo tanto que la calidad del stream se degrada. Con un valor fijo, el stream es más predecible y estable.
En ZoneMinder, en Edit → Source del monitor:
- Capture Resolution: de
1280x720a800x450— alineada con lo que la cámara realmente envía - Options: limpiar el campo, dejar solo
rtsp_transport=tcp
Después de aplicar ambos cambios, la cámara pasó a capturar 30 fps efectivos en ZoneMinder. Sin frames corruptos, sin descarte silencioso, sin más State: Idle - 3 fps.
Comandos útiles si te encontrás con algo similar
Si en algún momento tenés una cámara que no se comporta como esperás en ZoneMinder, estos tres comandos son un buen punto de partida antes de tocar cualquier configuración:
# Verificar fps y estado real del stream RTSP, directo desde la cámara
ffprobe -v error -select_streams v:0 \
-show_entries stream=r_frame_rate,avg_frame_rate \
-of default=noprint_wrappers=1 \
"rtsp://<usuario>:<contraseña>@<ip-camara>/ruta-del-stream"
# Ver los fps que está capturando ZoneMinder por monitor
grep "Capturing at" /var/log/zm/zmdc.log | tail -20
# Ver errores de decodificación en tiempo real
tail -f /var/log/zm/zmdc.log | grep -i "error\|fps\|frame"
El primero es el más valioso: te dice si el problema está en lo que la cámara envía o en cómo ZoneMinder lo procesa. Si r_frame_rate reporta los fps correctos pero avg_frame_rate devuelve 0/0, ya sabés que los frames llegan corruptos — y el GOP es el primer sospechoso.
Resumen
| Problema | Causa | Solución |
|---|---|---|
| 3 fps efectivos en ZoneMinder | GOP=62 generaba I-frames cada ~2 segundos; pérdida de paquetes descartaba todos los frames dependientes | Bajar GOP a 30 |
| Frames corruptos en el stream | GOP alto combinado con red imperfecta | GOP=30 + bitrate fijo a 4000 kbit/s |
| Resolución desalineada | ZoneMinder configurado en 1280x720, cámara enviando 800x450 | Igualar resolución en ZoneMinder |
Si llegaste hasta acá probablemente fue porque tu cámara también estaba mostrando un número que no tenía sentido. Espero que esto te haya ahorrado las horas que me tomó a mí llegar al GOP.
Si encontraste una causa diferente, si tu entorno es distinto, o si simplemente tenés una duda sobre algún paso — escribime. Este tipo de problemas rara vez se presentan exactamente igual dos veces, y siempre vale la pena comparar notas.
Lección de cierre
En sistemas industriales, una señal congelada, una cámara lenta o una interfaz caída rara vez se explican con una sola causa. El valor está en ordenar síntomas, evidencias, cambios recientes y dependencias técnicas antes de intervenir.
Este artículo tiene fines educativos. Los casos pueden estar simplificados o anonimizados y no representan a ninguna empresa, cliente o proveedor.
Member discussion