El vídeo se congela y el audio sigue: detectar un parón que libvlc todavía llama Playing
Una imagen congelada no es una condición de error. El motor sigue informando de Playing, y lo único que cambia es un delta de contador que se va a cero.
displayed +25 read +50,000 demux +48,000 audio +40 engine state: Playing
displayed +0 read +50,000 demux +48,000 audio +40 engine state: Playing
Dos muestras contiguas de un segundo. Cambió un número. Los bytes siguen llegando al mismo ritmo, el demuxer los sigue consumiendo, los búferes de audio siguen llegando a la tarjeta de sonido, y el motor informa de Playing en las dos líneas y lo va a seguir informando todo el tiempo que dejes el canal abierto. Lo que hay en pantalla es el último fotograma que llegó a la salida de vídeo. Esas son las constantes que nuestro banco de pruebas le da de comer al detector, no una captura de un canal real: un flujo de 4 Mbit/s lee unos 500000 bytes por segundo, un orden de magnitud por encima de esas constantes, y sus cifras de lectura y de demuxado se siguen mucho más de cerca que un hueco sostenido del 4 por ciento. Lo que se traslada es la forma. Nada de ese par es un error, no le sigue ningún evento de error, y esos cuatro deltas son todo el cuerpo de evidencia que recibe un detector.
Todo lo de abajo está medido contra libvlc 3.0.21 a través de LibVLCSharp 3.9.7 en nuestra app de escritorio de Windows. La API de estadísticas se movió entre versiones mayores de libvlc, así que lee las afirmaciones sobre contadores como afirmaciones sobre 3.x.
libvlc informa de Playing con la imagen congelada
El estado del motor responde a una pregunta más estrecha de lo que la gente le lee dentro. Playing significa que el hilo de reproducción no se ha detenido, ni pausado, ni terminado. No afirma nada sobre que haya llegado una imagen a la pantalla en los últimos cuatro segundos, ni sobre que la imagen que sí llegó tuviera algo dentro.
Nuestra especificación del watchdog enuncia la premisa sobre la que se apoya todo el subsistema: libvlc se queda en Playing indefinidamente tanto durante una congelación como con una señal negra, y EncounteredError está reservado a los fallos duros de apertura y de protocolo, así que casi nunca salta en los dos modos de fallo que los espectadores reportan de verdad. Esa frase es nuestra lectura del motor y no una línea sacada de su documentación, y es la razón por la que ninguna de las reglas de abajo le pregunta al motor cómo se encuentra.
La consecuencia observada es el tamaño de la taxonomía que tuvimos que construir. Hay nueve clases definidas: cero bytes en la capa de acceso, un fin de flujo silencioso en un canal en directo, el error propio del motor, bytes que llegan mientras el demuxer no produce nada, vídeo parado con el audio vivo, los dos parados mientras fluyen los datos, fotogramas negros sobre una tubería sana, un reloj de entrada detenido y rebuffering repetido dentro de una ventana móvil.
Tres de esas nueve van montadas sobre eventos del motor. Una es el error del motor. Las otras dos son eventos corrientes que no son errores en absoluto: un fin de flujo o una parada silenciosa en un canal que nadie paró, y un aviso a nivel de caché que contamos dentro de un bucle de rebuffering. Seis se infieren de los contadores, aunque el reloj detenido entre ellas se apoya además en el evento de reloj del motor. Y nueve exagera lo que el sistema puede llegar a decir, porque la clase del reloj detenido no se declara en ninguna parte. Existe para corroborar y no tiene código de taxonomía propio.
La tarjeta de fallo tiraba todo eso a la basura y mostraba una sola frase para cada caso, así que un espectador cuya fuente rechazaba la conexión y un espectador cuyo dispositivo no podía decodificar el vídeo leían exactamente las mismas palabras. Ahora cada clase se corresponde con un código, y una misma clase significa cosas distintas a cada lado del primer fotograma, así que la correspondencia lleva las dos cosas. Cero bytes dentro del margen de conexión es un flujo que nunca empezó. El mismo parón diez minutos después es uno que se paró.
Detectar una congelación de vídeo son cuatro deltas de contador en un orden fijo
Una muestra son once campos: una marca de tiempo monótona y diez contadores acumulativos, leídos del motor en una sola pasada. De ocho de los diez se calcula la diferencia contra la muestra anterior. Los otros dos, las imágenes tardías descartadas y los búferes de audio descartados, no los diferencia nada; se recogieron por si alguna regla los quería y nunca los ha querido ninguna. Cuatro deltas cargan con los veredictos:
- Bytes leídos, en la capa de acceso. Cero significa que no está llegando nada de la red.
- Bytes consumidos por el demuxer. Lectura distinta de cero con demuxado a cero significa que llegan bytes que no contienen ningún programa.
- Imágenes mostradas, en la salida de vídeo. Es la señal principal de actividad del vídeo, y la siguiente sección va de por qué no se la puede creer a primera vista.
- Búferes de audio reproducidos, en la salida de audio. Es lo que separa una congelación de vídeo de una total.
El vídeo decodificado es el único contador de apoyo con autoridad: hace de sustituto cuando el contador de imágenes mostradas resulta no servir. El audio decodificado no tiene un papel equivalente. Se lee una vez, para demostrar que existe un flujo de audio. Las unidades corruptas y las discontinuidades son el par que queda, y un pico en cualquiera de los dos arma ventanas de congelación más cortas durante un rato.
El orden importa más que cualquier umbral concreto, y el código dice por qué allí mismo:
// F5 / F6 presentation freezes. Suppressed while demux starvation is suspected: starvation
// stalls presentation too, and F4's longer window owns the classification (its ladder entry
// differs). F1 needs no such guard; it shares the shorter window and is checked first.
(En español: las congelaciones de presentación se suprimen mientras se sospecha inanición del demuxer, porque la inanición también para la presentación y la ventana más larga de esa clase es la que se queda con la clasificación.)
El acceso se prueba primero, el demuxer segundo, la presentación la última. Una capa de acceso muerta deja sin comer a todo lo que tiene aguas abajo, así que una imagen que se para cuatro segundos después de que se paren los bytes es un síntoma y no un diagnóstico, y reportarla como congelación mete la escalera de recuperación por el peldaño equivocado. Nuestras ventanas actuales son cuatro segundos de cero bytes, seis de demuxer sin comer, cuatro de presentación parada. Son valores de ajuste que se van a mover la próxima vez que alguien los mida; lo que carga con el significado es el orden.
La sospecha se guarda como un instante y no como una bandera. Cuatro marcas guardan cada una el instante en que empezó su condición, y un veredicto es una marca cuya edad ha superado su ventana. Un solo tick bueno devuelve la marca a nulo y la edad desaparece, así que la ventana es una resta y no una contabilidad tick a tick. El pico de corrupción es la excepción que confirma la forma: guarda una caducidad en vez de un comienzo, no lo limpia nada antes de tiempo y ningún veredicto lee su edad.
Una nota de alcance antes de que los umbrales empiecen a parecer universales. Todo esto está calibrado sobre MPEG-TS en bruto. El tipo de fuente lo llevamos como ts o hls, pero solo para telemetría y para unos umbrales por tipo que nadie ha escrito todavía. En HLS el demuxer adaptativo hace sus propias descargas aguas abajo, así que la separación de alto nivel entre acceso y demuxado no significa lo que esta sección dice que significa, y ningún umbral que enviemos es específico de HLS.
¿Por qué el contador de imágenes dice que un canal sano se congela una y otra vez?
Algunos caminos de salida de vídeo y de decodificación por hardware contabilizan de menos las imágenes mostradas, y no hay forma de preguntar en cuál estás. Nuestro propio comentario de código le echa la culpa de esto al módulo de acceso, y eso no puede ser: la capa de acceso está aguas arriba del demuxado y de la decodificación y no toca nunca el contador que incrementa el núcleo de la salida de vídeo. El comentario está mal y el comportamiento que describe es real.
Así que durante los primeros treinta segundos desde que un flujo empieza a presentar, la máquina mira en vez de juzgar. Al final de la ventana se decide por una de tres posturas repartidas en cinco ramas: el contador de imágenes mostradas avanzó y se le cree; el contador de imágenes mostradas está muerto pero el vídeo decodificado avanzó y pasa a ser la señal de respaldo; o todos los contadores de vídeo están muertos mientras el reloj de entrada avanza y existe una salida de vídeo, con lo que el camino contabiliza de menos y las reglas de congelación se apagan para esa sesión. Una cuarta rama pilla un canal mal etiquetado, donde no se movió nada del lado del vídeo y no apareció ninguna salida de vídeo pero el audio suena, y toma el relevo la actividad del audio. Una quinta cae por defecto en creerle al contador.
Por encima de todo eso hay una guarda. No cabe ningún veredicto de congelación hasta que algún contador de vídeo haya avanzado al menos una vez en esta sesión, porque un contador que no se ha movido nunca es indistinguible de un contador que contabiliza de menos, y el caso de estar genuinamente muerto desde el principio le pertenece al clasificador de arranque, que tiene otra evidencia y otra entrada a la escalera. Cuarenta y cinco ticks de un segundo con el audio sonando, el reloj avanzando y una salida de vídeo levantada, con todos los contadores de vídeo planos, no declaran nada.
El caso límite que dio forma a la calibración es la cartela de baja tasa de imágenes, la tarjeta de identidad de una emisora o el visual de una radio que se actualiza a alrededor de media imagen por segundo. Con un muestreador de un segundo, ese canal pasa un tick sí y otro no con cero imágenes mostradas, exactamente la firma de una congelación. La calibración detecta la tasa y ensancha la ventana de congelación de cuatro segundos a quince, y el ensanchamiento es la mitad contraintuitiva: el instinto pide juzgar antes en un canal que tiene menos que enseñar, y eso produce un veredicto falso en todas las cartelas que tengas. Una parada completa de la cartela se sigue pillando, con la ventana más ancha.
Armar una sospecha que ninguna regla puede consumir tiene un precio, y lo pagamos. Un parón de audio junto a un vídeo sano levantaba sospecha en un canal de vídeo, donde ningún veredicto puede llegar a leerla, así que la sesión se quedaba en ese estado toda su vida: muestreando el doble de rápido y sin emitir nunca la señal de estabilidad que limpia el registro de recuperación. Todos los fallos posteriores de esa sesión heredaban una escalera cuyos intentos ya estaban gastados e iban directos a la tarjeta de abandono. Una sospecha que no puede convertirse en veredicto no es prudencia. Es una fuga.
Una pausa se parece exactamente a una congelación
Con una pausa del usuario se paran todos los contadores de la muestra, exactamente con el patrón que produce una congelación total.
No hay forma de distinguir las dos cosas por los números, así que la pausa no se cronometra en absoluto: enmascara veredictos indefinidamente, y diez minutos de contadores congelados detrás de una pausa no declaran nada. Un salto se lleva en cambio una máscara acotada de cinco segundos, porque un salto se resuelve o no se resuelve. El buffering suspende los veredictos de presentación pero deja armada la regla de acceso, con el razonamiento de que cero bytes durante el buffering es una fuente muerta y no un enlace lento, porque un enlace lento sigue enseñando bytes que llegan.
Las reaperturas son el otro sitio donde muerde la aritmética. Los contadores de bytes son de 32 bits y están acotados a un solo medio, así que se reinician a cero cuando se aplica un medio nuevo y desbordan en una sesión lo bastante larga. En los dos casos el delta vuelve negativo, y un delta negativo en cualquiera de los seis contadores de actividad reinicia la línea base y se salta la evaluación de ese tick en vez de reportar un parón de toda la tubería.
Detectar un fotograma negro obliga a mirar los píxeles
Una señal negra decodifica, muestra, reproduce audio y tira de bytes al ritmo normal. Todas las reglas de hasta aquí ven un flujo sano. Este es el único camino del detector que tiene que mirar los píxeles, y necesita un fotograma de verdad capturado de la salida de vídeo a 96 por 54.
Un solo umbral de luma no basta, y el motivo es el contenido. El veredicto se apoya en dos ejes y en la repetición:
- Fracción de píxeles negros igual o superior a 0.98, donde un píxel cuenta como negro con una luma de 24 o menos.
- Desviación estándar de la luma igual o inferior a 4.0, que es lo que salva a una escena oscura pero con estructura.
- Cuatro muestras negras consecutivas, que es lo que un fundido no puede satisfacer.
Los dos fallos por contenido están fijados por test en vez de argumentados. Una escena nocturna, casi toda cercana al negro con estructura dispersa iluminada por la luna, falla los dos ejes a la vez. El contenido con bandas negras sale por debajo de la mitad de negro, porque las bandas son una minoría del fotograma incluso en una relación de aspecto ancha.
El argumento del fundido tiene una rama que no debería saltarme. Durante diez segundos después de que se confirme una recuperación, la racha exigida baja de cuatro muestras a dos, unos tres segundos en el suelo configurado y más en la práctica, que un fundido lento sí puede sostener. Es un compromiso deliberado: la evidencia que condenó a la fuente hace un momento sigue en pie, así que una fuente que vuelve rota se vuelve a condenar más rápido, y la guarda contra fundidos es lo que gastamos para comprarlo. El intervalo de 1500 ms tampoco es un espaciado, es un suelo. La sonda se rearma con él y dispara con el siguiente mensaje que llegue, así que el espaciado real es mayor: siete segundos y luego nueve, en la ejecución de abajo.
Cuándo corre la sonda es una decisión con un precio visible, y la descripción honesta es más estrecha de lo que sugiere el nombre. Corre una vez, como barrido, justo después de que un flujo presente por primera vez, y otra vez en la transición a la sospecha cuando la levantó un parón de vídeo. Una vez cerrado el barrido inicial no vuelve a correr mientras la sesión siga sana. Un negro a mitad de reproducción sobre un flujo por lo demás sano no produce por tanto ningún veredicto, de forma reproducible, y decidimos no arreglarlo: pillarlo cuesta una captura en cada sesión de cada flujo, para detectar lo que la persona que está mirando ve al instante, y zapear a otro canal y volver ya relanza el barrido de todos modos.
Una segunda limitación que no puedo cerrar con evidencia. La captura pasa por la propia llamada de captura del motor, y releer desde una superficie decodificada por hardware es un sitio de sobra conocido para que un reproductor te devuelva un fotograma en blanco por motivos que no tienen nada que ver con lo que hay en pantalla. La captura que no llega nunca sí la tratamos: es evidencia de una salida de vídeo atascada, que corrobora un veredicto de contadores pero nunca declara sola. La que llega y es negra sin serlo no la tratamos, y no tengo ninguna medición de con qué frecuencia pasa eso en nuestro camino.
La parte con la que menos cómodo me siento es lo que la sonda mide y después tira. En cada muestra se calcula un hash promedio de 64 bits del plano de luma y una luma media, los dos viajan en el mensaje, y el motor de reglas no lee ninguno de los dos. Un flujo congelado en un fotograma fijo y brillante, en un canal cuyo contador de imágenes se calibró como no fiable, es justo lo que pillarían dos hashes iguales en muestras separadas. No los comparamos, y si el caso es lo bastante común como para merecer la regla es una pregunta que no podemos responder, por un motivo al que llegan las dos últimas secciones. Un detalle del hash me sorprendió lo suficiente como para dejarlo aquí: un fotograma plano da un hash de todo unos, los 64 bits puestos, porque la media de cada celda cae exactamente en la media global y la comparación es mayor o igual. El blanco liso y el negro liso son el mismo hash.
El peor fallo de todo esto fue una congelación que no estaba ocurriendo
Leer las estadísticas del reproductor no es una llamada segura. Cada línea de esa lectura desreferencia manejadores nativos que el motor libera cuando el usuario zapea, se va de la página o una reconstrucción de la swapchain retira el reproductor, y el muestreador corre en su propia tarea. Perder esa carrera no lanza nada que un catch pueda absorber: libvlc revienta con una violación de acceso y el proceso desaparece sin dejar nada en el log. Así que el muestreador corre detrás de la misma puerta que usa el pool de reproductores.
Dentro de esa puerta, una decisión de caché que aislada parece correcta produjo el peor fallo que este subsistema ha llegado a enviar. El muestreador se quedaba con el envoltorio del medio en vez de pedirlo en cada tick, porque pedirlo una vez por segundo parecía un derroche. Un envoltorio de medio de LibVLCSharp toma su propia referencia nativa, así que el envoltorio en caché seguía siendo válido después de que el motor le cambiara el medio por debajo en un hilo del pool. Seguía respondiendo. No lanzaba nunca nada. Devolvía los contadores del medio muerto, que no se mueven, que es un parón total de bytes para todas las reglas de la máquina.
El síntoma era un canal sano reportado como una red muerta. El precio fue un cambio de fuente que abandonó un flujo que funcionaba, observado en directo durante una prueba de aceptación en campo el 2026-07-24 a las 23:24. El comentario que ahora está encima del arreglo lo dice sin rodeos:
// Acquire and dispose the wrapper on every tick. A cached wrapper holds its own native
// retain, so after a pool-thread media swap it would keep returning the DEAD media's
// frozen counters without ever throwing, which reads as a net stall on a healthy stream
// (observed live: [...] 2026-07-24 23:24). One retain/release per second is negligible.
(En español: hay que adquirir y liberar el envoltorio en cada tick, porque uno en caché mantiene su propia retención nativa y, tras un cambio de medio en un hilo del pool, seguiría devolviendo los contadores congelados del medio MUERTO sin lanzar nunca nada, cosa que se lee como un parón de red sobre un flujo sano.)
Un detector que lee entrada rancia no falla en silencio. Produce el mismo veredicto, con la misma confianza, sobre un canal que se está reproduciendo perfectamente, y después actúa en consecuencia. Cualquier otro fallo de un watchdog te cuesta un parón que no pillaste. Este te cuesta un parón que te inventaste, y el espectador ve la recuperación.
Esto es lo que emiten de verdad doce segundos de congelación
Abajo hay una sesión guionizada pasada por el motor de reglas que enviamos sobre el banco de pruebas que ya existía, doce ticks de un segundo. Se ejecutó en vez de seguirse a mano, y el controlador se borró después. Lee las cifras como valores de banco de pruebas. Cada petición de captura se respondió 300 ms más tarde, como haría un anfitrión real, porque el presupuesto de una captura es de un segundo.
La sesión abre, informa de Playing e informa de una salida de vídeo. Después:
| t | dRead | dDemux | dDisplayed | dAudio | estado después | efectos emitidos |
|---|---|---|---|---|---|---|
| 1 s | +50000 | +48000 | +25 | +40 | Starting | ninguno |
| 2 s | +50000 | +48000 | +25 | +40 | Healthy | StateChanged, SetSampleRate 1 Hz, RequestSnapshot |
| 3 s | +50000 | +48000 | +25 | +40 | Healthy | ninguno |
| 4 s | +50000 | +48000 | +25 | +40 | Healthy | ninguno |
| 5 s | +50000 | +48000 | +25 | +40 | Healthy | ninguno |
| 6 s | +50000 | +48000 | +25 | +40 | Healthy | ninguno |
| 7 s | +50000 | +48000 | +0 | +40 | Suspect | StateChanged, SetSampleRate 2 Hz, RequestSnapshot |
| 8 s | +50000 | +48000 | +0 | +40 | Suspect | ninguno |
| 9 s | +50000 | +48000 | +0 | +40 | Suspect | RequestSnapshot |
| 10 s | +50000 | +48000 | +0 | +40 | Suspect | ninguno |
| 11 s | +50000 | +48000 | +0 | +40 | Recovering | StateChanged, FaultDeclared VideoFreeze, edad 4000 ms |
| 12 s | +50000 | +48000 | +0 | +40 | Recovering | ninguno |
El tick 1 no produce nada, porque una muestra no es un delta; se convierte en la línea base. El tick 2 es el primer par evaluado, queda demostrado que la presentación avanza, y la máquina sale de Starting. También vuelve a declarar una tasa de muestreo que ya estaba usando, cosa redundante e inofensiva. La petición de captura de esa línea es el barrido inicial, y el anfitrión la respondió con contenido real, un 6 por ciento de píxeles negros con una desviación estándar de 41, así que el barrido se cerró.
Los ticks 3 a 6 no emiten absolutamente nada. Ese es el estado estable que se busca: un flujo sano no produce efectos, así que un log vacío es el caso de éxito.
El tick 7 es donde se para la imagen. La marca de parón de vídeo se sella con 7 s, la máquina pasa a Suspect, el muestreador dobla a 2 Hz, y la sonda se arma y pide un fotograma. No se declara nada, porque una marca con edad cero no es un veredicto. Los ticks 8 y 10 son la ventana corriendo, y el tick 9 es la sonda volviendo a muestrear a su propia cadencia, dos segundos después de la última petición y no a los 1500 ms configurados, porque dispara con el siguiente mensaje pasado el instante de vencimiento. Los bytes y el audio no flaquean en ningún momento de todo esto: esta sesión le parece perfecta a todas las reglas menos a una.
El tick 11 es el veredicto. La marca de parón de vídeo llega a cuatro segundos, el demuxer no está bajo sospecha, el audio avanzó durante el parón y sigue avanzando, así que es una congelación de vídeo y no una total, y la edad de anomalía que se le reporta al ejecutor es de 4000 ms. Ejecuta el mismo guion con el audio parándose también en el tick 7 y esa misma línea declara una congelación total. Ejecútalo otra vez con la captura agotando su tiempo en vez de devolver contenido y el veredicto no cambia, pero su cadena de motivo pasa a ser video stalled while audio keeps playing (snapshot timeout observed) (el vídeo se paró mientras el audio sigue sonando, con el tiempo de la captura agotado): la salida atascada corroboró, no decidió.
El tick 12 no emite nada porque la máquina ya ha pasado el testigo. La batería de Playback que rodea a este código son 108 tests, todos pasando en el momento de escribir esto.
Un veredicto compra cinco peldaños, y uno de ellos no está construido
El veredicto es una entrada a una escalera, no una respuesta. Existen cinco peldaños: una sacudida de pausa y reproducción, que solo se llevan las congelaciones totales; una reaplicación completa de la misma URL; un cambio al siguiente candidato; una reconstrucción del reproductor en sí; y abandonar. Un fallo dentro de los quince segundos siguientes a sintonizar entra por el cambio y no por la reaplicación, porque una fuente que nunca llegó a demostrar nada no merece que se reintente la URL que acaba de fallar. Las sesiones maduras conservan la reaplicación en primer lugar, así que un tropiezo diez minutos después no le cuesta al espectador un cambio de fuente. Hay exactamente un intento de reinicio antes de escalar, porque los datos de campo enseñaron que el segundo no ayuda nunca.
El peldaño de reconstrucción es, hoy por hoy, ficción honesta. No tiene ninguna primitiva en el anfitrión, está desactivado en los ajustes que enviamos, y la única condición que lleva hasta él, una parada a la que se le agotó el tiempo y dejó la sesión nativa atascada, hoy termina la escalera en primer plano y aterriza en abandonar. Eso es prudente y correcto, es también un hueco, y el código lo dice allí donde pasa.
Entre intentos la espera es exponencial con el primer intento gratis: la base por dos elevado al índice del intento menos uno, con tope, y después con un jitter de hasta el veinte por ciento en cualquier dirección. En la práctica: dispara de inmediato, luego un segundo, tres, siete, y así hasta el tope. El jitter multiplica después del tope y no antes, así que el techo de verdad son treinta y seis segundos y no los treinta que sugiere el tope. Es una nimiedad, pero es el tipo de nimiedad que hace que una gráfica parezca mal a las 3 de la madrugada.
Los píxeles se quedan en la máquina que los capturó
La sonda escribe dos archivos PNG rotatorios en la carpeta de datos local de la propia app y los sobrescribe en el sitio. No los sube nada y no los manda nada a ninguna parte; el fotograma existe el tiempo justo para quedar reducido a dos números y un hash, y después lo sobrescribe el siguiente. Los veredictos de fallo van al log local de esa misma máquina, y allí un canal se identifica por los primeros dieciséis caracteres hexadecimales de un SHA-256, nunca por una URL.
Esa última parte es la razón por la que no te puedo decir cómo de común es el caso del fotograma brillante congelado. Las formas de los eventos de telemetría están escritas y tipadas, su comentario de documentación nombra una subida por lotes, y esa subida no existe. Hay tres tipos de registro declarados y nunca construidos en ninguna parte del código. Todos los juicios de este artículo sobre qué modos de fallo importan salen de una prueba de aceptación en campo, un banco de inyección de fallos y una batería de tests, y no de una flota. Eso es un límite real a la confianza de todo lo de arriba.
Si prefieres usar el detector antes que leer sobre él, viene activado por defecto en la app de My TV Player para Windows, y puedes crear una cuenta gratuita para probarlo. Cuando hace su trabajo no lo vas a ver: la imagen se entrecortará durante cuatro segundos y volverá, y no habrá aparecido nada en pantalla que te diga que se llegó a ningún veredicto.
Lo que midió este artículo37 afirmaciones, cada una con la evidencia detrás de ellas
| Reclamar | evidencia | contado |
|---|---|---|
| Todo lo de este artículo está medido contra libvlc 3.0.21 a través de LibVLCSharp 3.9.7 en la app de Windows. La API de estadísticas es distinta en libvlc 4.x, así que las afirmaciones sobre contadores se limitan a 3.x.clients/windows/docs/PLAYBACK-WATCHDOG-PLAN.md:7 pins the head to LibVLCSharp 3.9.7 / libvlc 3.0.21; docs/research/MTP-PlaybackWatchdog-Spec-v1.0.md:5 scopes the spec to libvlc 3.x with forward-compatible notes for 4.x. | Especificación | No aplicable |
| libvlc se queda en Playing indefinidamente tanto durante una congelación como con una señal negra, y EncounteredError está reservado a los fallos duros de apertura y de protocolo, así que casi nunca salta en los dos modos de fallo dominantes en directo.docs/research/MTP-PlaybackWatchdog-Spec-v1.0.md:18 (within the cited 15-19 block), header checked, no confidentiality marking. | Especificación | No aplicable |
| Hay nueve clases de fallo definidas, de F1 a F9, y ocho de ellas pueden llegar a declararse. F8 ClockStall no se declara en ninguna parte y no tiene código de taxonomía propio. | n = 9 | 22 ago 2026 |
| Tres de las nueve clases van montadas sobre eventos del motor. F3 es el error propio del motor, F2 se declara a partir de EndReached o de un Stopped silencioso, y F9 se declara a partir de Buffering. El resto se infieren de los contadores. | n = 3 | 22 ago 2026 |
| La tarjeta de fallo mostraba una sola frase para todas las clases, y ahora cada clase se corresponde con un código de taxonomía que además depende de si el fallo cayó antes o después del primer fotograma. | n = 1 | 22 ago 2026 |
| Una muestra de estadísticas son once campos: una marca de tiempo monótona y diez contadores acumulativos. De ocho se calcula el delta; LostPictures y LostAudioBuffers no los diferencia ninguna regla. | n = 11 | 22 ago 2026 |
| Solo el vídeo tiene un contador de respaldo. DecodedVideo hace de sustituto cuando DisplayedPictures no sirve; DecodedAudio solo se usa para demostrar que existe un flujo de audio y ninguna regla lo lee nunca como señal de actividad. | n = 1 | 22 ago 2026 |
| Los veredictos se comprueban primero el de acceso, segundo el del demuxer y el de presentación el último, y las reglas de congelación se retiran mientras se sospecha inanición del demuxer. | n = 1 | 22 ago 2026 |
| Ajuste actual: 4 s de cero bytes para F1, 6 s para F4, 4 s para F5 y F6, 15 s para una cartela de baja tasa de imágenes. | n = 1 | 22 ago 2026 |
| Cuatro marcas de inicio anulables guardan el instante en que empezó cada condición, y un solo tick bueno las borra. Una quinta señal, el pico de corrupción y discontinuidades, funciona al revés: guarda un instante de caducidad y acorta las ventanas de congelación mientras está armada. | n = 4 | 22 ago 2026 |
| El modelo de contadores está calibrado sobre MPEG-TS en bruto. El tipo de fuente se lleva como «ts» o «hls» para telemetría y para futuros umbrales por tipo, y hoy ningún umbral de las opciones es específico de HLS. | n = 1 | 22 ago 2026 |
| Los contadores de bytes de libvlc 3.x son de 32 bits y pertenecen a un solo medio, así que se reinician a cero al reabrir y desbordan en sesiones largas. En los dos casos el delta se vuelve negativo.WatchdogStateMachine.cs:274-275 states both causes in place ("libvlc zeroes stats on reopen and int counters can wrap"). libvlc 3.x declares i_read_bytes and i_demux_read_bytes as int in libvlc_media_stats_t. | Especificación | No aplicable |
| Un delta negativo en cualquiera de los seis contadores de actividad reinicia la línea base y se salta la evaluación de ese tick. Los deltas de unidades corruptas y de discontinuidades no entran en esa guarda. | n = 6 | 22 ago 2026 |
| El contador de imágenes se calibra durante una ventana de 30 s hacia Trusted, FallbackDecodedVideo o Untrusted a lo largo de cinco ramas, y los veredictos de congelación exigen además que algún contador de vídeo haya avanzado al menos una vez en esta sesión. | n = 5 | 22 ago 2026 |
| Los caminos que contabilizan de menos las imágenes mostradas son los de vout y los de decodificación por hardware, no los módulos de acceso.WatchdogStateMachine.cs:441 says "this access/vout combo underreports". The access module sits upstream of demux and decode and cannot influence i_displayed_pictures, which the vout core increments. | Especificación | No aplicable |
| 45 ticks de un segundo con el audio sonando, el reloj avanzando y una salida de vídeo levantada, pero con todos los contadores de vídeo muertos, no declaran ningún fallo y acaban en Untrusted. | n = 45 | 22 ago 2026 |
| Una cartela medida a alrededor de media imagen por segundo ensancha la ventana de congelación hasta 15 s en vez de acortarla, y una parada completa de la cartela se sigue detectando con la ventana ensanchada. | n = 1 | 22 ago 2026 |
| Un parón de audio junto a un vídeo sano podía levantar sospecha pero nunca podía llegar a un veredicto, así que una sesión se quedaba en SUSPECT toda su vida a la tasa de muestreo rápida, no emitía nunca la señal de estabilidad y dejaba sin limpiar el registro de recuperación, con lo que los fallos posteriores heredaban una escalera ya gastada e iban directos a la tarjeta de abandono. | n = 1 | 22 ago 2026 |
| Una pausa del usuario enmascara indefinidamente unos contadores congelados; un salto los enmascara solo 5 s; el buffering suspende los veredictos de presentación mientras la regla de cero bytes sigue corriendo. | n = 3 | 22 ago 2026 |
| Ajuste actual del negro: un píxel cuenta como negro con una luma de 24 o menos, una muestra es negra con un 98 por ciento de píxeles negros y una desviación estándar de luma de 4.0 o menos, y el veredicto necesita 4 muestras consecutivas, capturadas a 96 por 54. | n = 1 | 22 ago 2026 |
| Durante 10 s después de que se confirme un intento de recuperación, las muestras negras consecutivas exigidas bajan a 2. | n = 1 | 22 ago 2026 |
| 1500 ms es un suelo de rearme, no un espaciado. La sonda dispara con el siguiente mensaje que llegue en el instante de vencimiento o después, así que el espaciado real es mayor: 2 s allí donde los mensajes llegan a 1 Hz, como en la ejecución que aquí se recoge. | n = 1 | 22 ago 2026 |
| Una escena nocturna falla los dos ejes del negro y el contenido con bandas negras se queda por debajo de la mitad de negro; los dos están fijados por test como no negros. | n = 2 | 22 ago 2026 |
| La sonda corre como un barrido de una sola vez justo después de que el flujo presente por primera vez, y otra vez en la transición a SUSPECT cuando la sospecha la levantó un parón de vídeo. Una vez cerrado el barrido inicial no vuelve a correr durante la salud en régimen estable, así que un negro a mitad de reproducción sobre un flujo por lo demás sano no produce ningún veredicto. | n = 2 | 22 ago 2026 |
| La sonda captura a través de la propia llamada de captura del motor, y releer una captura desde una superficie decodificada por hardware es un sitio conocido en el que un reproductor devuelve un fotograma en blanco por motivos que no tienen nada que ver con el contenido. La captura fallida la tratamos como prueba corroborante; no tenemos ninguna medición de con qué frecuencia una relectura correcta vuelve negra sin serlo.PlaybackEngine.Watchdog.cs:253 calls MediaPlayer.TakeSnapshot (libvlc_video_take_snapshot); the timeout path is handled at WatchdogStateMachine.cs:867-873. | Especificación | No aplicable |
| En cada muestra de la sonda se calculan un hash promedio de 64 bits y una luma media, los dos viajan en el mensaje, y el motor de reglas no lee ninguno de los dos. | n = 1 | 22 ago 2026 |
| Un fotograma plano produce un hash promedio con todos los bits puestos, porque la media de cada celda cae en la media global y la comparación es mayor o igual. El blanco liso y el negro liso son por tanto indistinguibles para este hash. | n = 1 | 22 ago 2026 |
| Un use-after-free dentro de libvlc aflora como una violación de acceso nativa y no como una excepción del CLR, así que un catch gestionado no llega a ejecutarse nunca.clients/windows/src/MyTvPlayer.Shared/Media/LiveTvPreviewMediaHelper.LibVlc.cs:165-180, reproduced 2026-08-19, 0xC0000005 in libvlc_media_player_release with a concurrent stats read. | Especificación | No aplicable |
| Un envoltorio Media de LibVLCSharp en caché mantiene su propia retención nativa, así que después de un cambio de medio en un hilo del pool sigue devolviendo los contadores congelados del medio muerto sin lanzar nunca nada.libvlc 3.x doxygen for libvlc_media_player_get_media: the returned media is reference counted and the caller must release it, which is why a cached LibVLCSharp Media keeps the old media alive and keeps answering. Recorded verbatim at clients/windows/src/MyTvPlayer.Shared/Media/Watchdog/PlaybackWatchdog.cs:322-325. | Especificación | No aplicable |
| Un envoltorio de medio en caché hizo que el muestreador informara de los contadores congelados de un medio muerto sobre un flujo sano, y eso se leyó como un parón de red. Observado en directo durante una prueba de aceptación en campo el 2026-07-24 a las 23:24. Corregido adquiriendo y liberando el envoltorio en cada tick. | n = 1 | 24 jul 2026 |
| Doce ticks guionizados de un segundo llegan a HEALTHY a los 2 s, a SUSPECT a los 7 s y declaran F5 VideoFreeze a los 11 s con una edad de anomalía de 4000 ms. El mismo guion con el contador de audio parándose también declara F6 TotalFreeze en el mismo tick, y una tercera ejecución en la que se agota el tiempo de las capturas de SUSPECT declara F5 con la cadena de motivo ampliada a «video stalled while audio keeps playing (snapshot timeout observed)». | n = 12 | 22 ago 2026 |
| La batería de tests de Playback ejecuta 108 tests con 0 fallos. | n = 108 | 22 ago 2026 |
| Cinco peldaños: una sacudida de pausa y reproducción solo para las congelaciones totales, una reaplicación completa, un cambio al siguiente candidato, una reconstrucción del reproductor y, por último, abandonar. Un solo intento de reinicio antes de escalar, y un fallo dentro de los 15 s siguientes a sintonizar entra por el cambio en vez de por la reaplicación. | n = 5 | 22 ago 2026 |
| El peldaño de reconstrucción del reproductor todavía no tiene ninguna primitiva en el anfitrión y está desactivado en los ajustes que enviamos, así que hoy un episodio de parada con el tiempo agotado termina la escalera en primer plano y va a abandonar. | n = 1 | 22 ago 2026 |
| El intento k espera min(base por (2^k - 1), tope) con jitter simétrico, así que el primer intento dispara de inmediato y el crecimiento a partir del segundo es 1 s, 3 s, 7 s hasta el tope. El jitter se aplica después del tope, así que las esperas en el tope se dispersan simétricamente en torno a 30 s y el techo real es de 36 s. | n = 1 | 22 ago 2026 |
| La sonda escribe dos archivos PNG rotatorios en la carpeta de datos local de la propia app y los sobrescribe. Los eventos de fallo van al log local, donde un canal se identifica por los primeros 16 caracteres hexadecimales de un SHA-256 y nunca por una URL. | n = 1 | 22 ago 2026 |
| Los tipos de registro de telemetría están declarados y nunca se construyen, y la subida por lotes que nombra su comentario de documentación no existe. | n = 3 | 22 ago 2026 |