Il video si blocca, l'audio continua: rilevare uno stallo che per libvlc è ancora Playing
Un'immagine bloccata non è una condizione di errore. Il motore continua a riportare Playing, e l'unica cosa che cambia è un delta di contatore che va a zero.
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
Due campioni adiacenti da un secondo. È cambiato un numero solo. I byte arrivano ancora alla stessa velocità, il demuxer li consuma ancora, i buffer audio arrivano ancora alla scheda audio, e il motore riporta Playing su entrambe le righe e continuerà a riportarlo per tutto il tempo in cui lasci il canale aperto. Quello che sta sullo schermo è l'ultimo fotogramma arrivato all'uscita video. Quelle costanti sono quelle che il nostro banco di prova dà in pasto al rilevatore, non una cattura da un canale vero: uno stream da 4 Mbit/s legge circa 500.000 byte al secondo, un ordine di grandezza sopra quelle costanti, e i suoi numeri di lettura e di demux si seguono molto più da vicino di uno scarto costante del 4 per cento. Quello che si trasferisce è la forma. Niente in questa coppia è un errore, non la segue nessun evento di errore, e quei quattro delta sono tutto il corpo di prove che un rilevatore riceve.
Tutto quello che segue è misurato contro libvlc 3.0.21 attraverso LibVLCSharp 3.9.7 sulla nostra app desktop per Windows. L'API delle statistiche si è spostata fra una major di libvlc e l'altra, quindi leggi le affermazioni sui contatori come affermazioni sulla 3.x.
libvlc riporta Playing anche con l'immagine bloccata
Lo stato del motore risponde a una domanda più stretta di quella che la gente ci legge dentro. Playing vuol dire che il thread di riproduzione non è stato fermato, messo in pausa o terminato. Non afferma che un'immagine sia arrivata sullo schermo negli ultimi quattro secondi, e non afferma che l'immagine che ci è arrivata avesse qualcosa dentro.
La specifica del nostro watchdog enuncia la premessa su cui poggia tutto il sottosistema: libvlc resta in Playing a tempo indeterminato sia durante un blocco sia davanti a un feed nero, e EncounteredError è riservato ai fallimenti duri di apertura e di protocollo, quindi non scatta quasi mai per le due modalità di guasto che gli spettatori segnalano davvero. Quella frase è la nostra lettura del motore, non una riga presa dalla sua documentazione, ed è il motivo per cui nessuna delle regole qui sotto chiede al motore come sta.
La conseguenza osservata è la dimensione della tassonomia che abbiamo dovuto costruire. Le classi definite sono nove: nessun byte allo strato di accesso, una fine dello stream silenziosa su un canale in diretta, l'errore del motore stesso, byte che arrivano mentre il demuxer non produce niente, video in stallo con l'audio vivo, tutti e due in stallo mentre i dati scorrono, fotogrammi neri su una pipeline sana, un orologio di ingresso fermo, e buffering ripetuto dentro una finestra scorrevole.
Tre di quelle nove viaggiano su eventi del motore. Una è l'errore del motore. Le altre due sono eventi ordinari che non sono affatto errori: una fine dello stream o un arresto silenzioso su un canale che nessuno ha fermato, e una segnalazione a livello di cache che contiamo dentro un ciclo di buffering. Sei si deducono dai contatori, anche se l'orologio fermo tra queste si appoggia pure all'evento di orologio del motore. E nove sopravvaluta quello che il sistema sa dire, perché la classe dell'orologio fermo non viene dichiarata da nessuna parte. Esiste per confermare e non ha un codice di tassonomia suo.
La scheda di errore buttava via tutto questo e mostrava una sola frase per ogni caso, quindi uno spettatore la cui sorgente aveva rifiutato la connessione e uno spettatore il cui dispositivo non riusciva a decodificare il video leggevano parole identiche. Adesso ogni classe corrisponde a un codice, e la stessa classe vuol dire cose diverse da una parte e dall'altra del primo fotogramma, quindi la corrispondenza porta tutte e due. Nessun byte dentro il periodo di grazia della connessione è uno stream che non è mai partito. Lo stallo identico dieci minuti dopo è uno stream che si è fermato.
Rilevare un blocco del video è questione di quattro delta di contatore in un ordine fisso
Un campione è fatto di undici campi: un timestamp monotono e dieci contatori cumulativi, letti dal motore in un solo passaggio. Di otto dei dieci si calcola la differenza rispetto al campione precedente. Gli altri due, le immagini in ritardo scartate e i buffer audio scartati, non li differenzia niente; sono stati raccolti nel caso una regola li volesse e nessuna regola li ha mai voluti. A portare i verdetti sono quattro delta:
- Byte letti, allo strato di accesso. Zero vuol dire che dalla rete non arriva niente.
- Byte consumati dal demuxer. Lettura diversa da zero con demux a zero vuol dire che arrivano byte che non contengono nessun programma.
- Immagini visualizzate, all'uscita video. È il segnale primario di vitalità del video, e la sezione dopo parla del perché non gli si può credere a prima vista.
- Buffer audio riprodotti, all'uscita audio. È quello che separa un blocco del video da uno totale.
Il video decodificato è l'unico contatore di supporto che abbia autorità: subentra quando il contatore delle immagini visualizzate si rivela inutilizzabile. L'audio decodificato non ha un ruolo corrispondente. Viene letto una volta, per dimostrare che esiste un flusso audio. Le unità corrotte e le discontinuità sono la coppia che resta, e un picco su una qualunque delle due arma per un po' finestre di blocco accorciate.
L'ordine conta più di qualunque singola soglia, e il codice dice perché sul posto:
// 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.
L'accesso si prova per primo, il demuxer per secondo, la presentazione per ultima. Uno strato di accesso morto lascia a secco tutto quello che ha a valle, quindi un'immagine che si ferma quattro secondi dopo che si sono fermati i byte è un sintomo e non una diagnosi, e riportarla come blocco fa entrare la scala di ripristino al gradino sbagliato. Le nostre finestre attuali sono quattro secondi di zero byte, sei di demuxer in carenza, quattro di presentazione in stallo. Sono valori di taratura che si sposteranno la prossima volta che qualcuno li misura; a portare il significato è l'ordine.
Il sospetto è memorizzato come un istante e non come un flag. Quattro marcatori tengono ciascuno il timestamp in cui la loro condizione è iniziata, e un verdetto è un marcatore la cui età ha superato la sua finestra. Un solo tick buono rimette il marcatore a null e l'età sparisce, quindi la finestra è una sottrazione e non una contabilità tick per tick. Il picco di corruzione è l'eccezione che conferma la forma: memorizza una scadenza invece di un inizio, niente lo azzera in anticipo, e nessun verdetto ne legge l'età.
Una nota di ambito prima che le soglie comincino a sembrare universali. Tutto questo è calibrato su MPEG-TS grezzo. Il tipo di sorgente lo portiamo come ts oppure hls, ma solo per la telemetria e per soglie per tipo che non ha ancora scritto nessuno. Su HLS il demuxer adattivo esegue per conto suo i propri prelievi a valle, quindi la divisione fra accesso e demux al livello più alto non vuol dire quello che questa sezione dice che vuol dire, e nessuna soglia che spediamo è specifica per HLS.
Perché il contatore delle immagini dice che un canale sano continua a bloccarsi?
Alcuni percorsi di uscita video e di decodifica hardware sottostimano le immagini visualizzate, e non c'è modo di chiedere su quale ti trovi. Il commento nel nostro codice ne dà la colpa al modulo di accesso, e non può essere così: lo strato di accesso sta a monte del demux e della decodifica e non tocca mai il contatore che incrementa il nucleo dell'uscita video. Il commento è sbagliato, e il comportamento che descrive è reale.
Quindi per i primi trenta secondi dopo che uno stream comincia a presentare, la macchina guarda invece di giudicare. Alla fine della finestra si impegna su una di tre posizioni attraverso cinque rami: il contatore delle immagini visualizzate è avanzato e ci si fida; il contatore delle immagini visualizzate è morto ma il video decodificato è avanzato e diventa il segnale di ricaduta; oppure ogni contatore video è morto mentre l'orologio di ingresso avanza ed esiste un'uscita video, quindi quel percorso sottostima e per quella sessione le regole di blocco si spengono. Un quarto ramo prende il canale etichettato male, dove dal lato video non si è mosso niente e non è comparsa nessuna uscita video ma l'audio suona, e subentra la vitalità dell'audio. Un quinto, per impostazione predefinita, si fida del contatore.
Sopra tutto questo c'è una guardia. Nessun verdetto di blocco è possibile finché un contatore video non è avanzato almeno una volta in questa sessione, perché un contatore che non si è mai mosso è indistinguibile da un contatore che sottostima, e il caso davvero morto fin dall'inizio appartiene al classificatore di avvio, che ha prove diverse e un ingresso diverso nella scala. Quarantacinque tick da un secondo con l'audio che suona, l'orologio che avanza e un'uscita video attiva, tutti i contatori video piatti, non dichiarano niente.
Il caso limite che ha dato forma alla calibrazione è il cartello a bassa frequenza di fotogrammi, un cartello di identificazione dell'emittente o il visual di una radio che si aggiorna a circa mezza immagine al secondo. Su un campionatore da un secondo quel canale passa un tick sì e uno no a zero immagini visualizzate, esattamente la firma di un blocco. La calibrazione rileva la frequenza e allarga la finestra di blocco da quattro secondi a quindici, e l'allargamento è la metà controintuitiva: l'istinto dice di giudicare prima su un canale che ha meno da mostrare, e così ti ritrovi un verdetto falso su ogni cartello che hai. Un arresto completo del cartello viene comunque preso, sulla finestra più larga.
Armare un sospetto che nessuna regola può consumare ha un costo, e noi l'abbiamo pagato. Uno stallo dell'audio accanto a un video sano sollevava un sospetto su un canale video, dove nessun verdetto può mai leggerlo, quindi la sessione se ne restava in quello stato per tutta la vita: campionava il doppio più in fretta e non emetteva mai il segnale di stabilità che azzera il registro dei ripristini. Ogni guasto successivo in quella sessione ereditava una scala con i tentativi già consumati e andava dritto alla scheda della resa. Un sospetto che non può diventare un verdetto non è prudenza. È una falla.
Una pausa è identica a un blocco
Tutti i contatori del campione si fermano su una pausa dell'utente, esattamente nello schema che produce un blocco totale.
Dai numeri non c'è modo di distinguere i due casi, quindi la pausa non è affatto a tempo: maschera i verdetti a tempo indeterminato, e dieci minuti di contatori fermi dietro una pausa non dichiarano niente. Un seek riceve invece una maschera limitata di cinque secondi, perché un seek o si risolve o non si risolve. Il buffering sospende i verdetti di presentazione ma lascia armata la regola dell'accesso, sul ragionamento che zero byte durante il buffering è una sorgente morta e non un collegamento lento, dato che un collegamento lento fa comunque vedere byte che arrivano.
Le riaperture sono l'altro posto in cui l'aritmetica morde. I contatori di byte sono a 32 bit e hanno per ambito un solo media, quindi ripartono da zero quando si applica un media nuovo e vanno in overflow su una sessione abbastanza lunga. In entrambi i casi il delta torna negativo, e un delta negativo su uno qualunque dei sei contatori di vitalità reimposta la base di riferimento e salta la valutazione per quel tick, invece di riportare uno stallo di tutta la pipeline.
Rilevare un fotogramma nero costringe a guardare i pixel
Un feed nero decodifica, visualizza, riproduce l'audio e tira byte alla velocità di sempre. Tutte le regole viste finora vedono uno stream sano. È l'unico percorso del rilevatore che deve guardare i pixel, e gli serve un fotogramma vero catturato dall'uscita video a 96 per 54.
Una sola soglia di luma non basta, e il motivo è il contenuto. Il verdetto prende due assi e la ripetizione:
- Frazione di pixel neri pari o superiore a 0.98, dove un pixel conta come nero a luma 24 o meno.
- Deviazione standard della luma pari o inferiore a 4.0, che è quello che risparmia una scena scura ma strutturata.
- Quattro campioni neri consecutivi, che è quello che una dissolvenza non riesce a soddisfare.
Tutti e due i fallimenti dovuti al contenuto sono fissati dai test invece che discussi. Una scena notturna, quasi tutta vicina al nero con qualche struttura illuminata dalla luna, fallisce su entrambi gli assi in una volta sola. Un contenuto con le bande nere esce sotto la metà di nero, perché le bande sono una minoranza del fotogramma anche su un formato largo.
L'argomento della dissolvenza ha un ramo che non dovrei saltare. Per dieci secondi dopo che un ripristino si conferma, la sequenza richiesta scende da quattro campioni a due, circa tre secondi al pavimento configurato e di più nella pratica, che una dissolvenza lenta riesce a tenere. È un compromesso voluto: le prove che hanno condannato la sorgente un attimo fa valgono ancora, quindi una sorgente che torna rotta viene ricondannata più in fretta, e la guardia sulla dissolvenza è quello che spendiamo per comprarlo. Anche l'intervallo di 1500 ms è un pavimento e non una spaziatura. La sonda si riarma su quello e scatta al primo messaggio che arriva, quindi la spaziatura reale è più lunga: sette secondi e poi nove, nell'esecuzione qui sotto.
Quando la sonda gira è una decisione con un costo visibile, e la descrizione onesta è più stretta di quello che suggerisce il nome. Gira una volta come passata subito dopo che uno stream presenta per la prima volta, e di nuovo al passaggio nel sospetto quando a sollevarlo è stato uno stallo video. Una volta chiusa la passata iniziale non gira più finché la sessione resta sana. Un nero a metà riproduzione su uno stream per il resto sano non produce quindi nessun verdetto, in modo riproducibile, e abbiamo scelto di non risolverlo: prenderlo costa un'istantanea su ogni sessione di ogni stream, per rilevare quello che chi sta guardando vede all'istante, e comunque cambiare canale e tornare indietro fa rigirare la passata.
Un secondo limite che non riesco a chiudere con delle prove. La cattura passa dalla chiamata di istantanea del motore stesso, e la rilettura da una superficie decodificata in hardware è un posto notissimo in cui un lettore restituisce un fotogramma vuoto per motivi che non hanno niente a che fare con quello che sta sullo schermo. L'istantanea che non arriva mai la gestiamo: è la prova di un'uscita video incastrata, conferma un verdetto dei contatori ma non dichiara mai da sola. Quella che arriva ed è nera per sbaglio no, e non ho nessuna misura di quanto spesso succeda sul nostro percorso.
La parte con cui sono meno a mio agio è quello che la sonda misura e poi butta via. Ogni campione calcola un hash medio a 64 bit del piano della luma e una luma media, tutti e due viaggiano dentro il messaggio, e il motore delle regole non ne legge nessuno dei due. Uno stream bloccato su un fotogramma fisso e luminoso, su un canale il cui contatore delle immagini si è calibrato come non affidabile, è proprio quello che prenderebbero due hash uguali su campioni distanziati. Noi non li confrontiamo, e se il caso sia abbastanza frequente da giustificare la regola è una domanda a cui non sappiamo rispondere, per un motivo a cui arrivano le ultime due sezioni. Un dettaglio dell'hash mi ha sorpreso abbastanza da tenermelo: un fotogramma piatto produce un hash di tutti uno, tutti e 64 i bit accesi, perché la media di ogni cella sta esattamente sulla media globale e il confronto è maggiore o uguale. Il bianco pieno e il nero pieno sono lo stesso hash.
Il peggior bug qui è stato un blocco che non stava succedendo
Leggere le statistiche del lettore non è una chiamata sicura. Ogni sua riga dereferenzia handle nativi che il motore libera quando l'utente cambia canale, lascia la pagina, o quando la ricostruzione di una swapchain manda in pensione il lettore, e il campionatore gira su un task suo. Perdere quella corsa non solleva qualcosa che un catch possa assorbire: libvlc va in errore con una violazione di accesso e il processo sparisce senza niente nel log. Quindi il campionatore gira dietro lo stesso cancello che usa il pool dei lettori.
Dentro quel cancello, una decisione di caching che presa da sola sembra corretta ha prodotto il peggior bug che questo sottosistema abbia mai spedito. Il campionatore si teneva stretto il wrapper del media invece di prenderlo a ogni tick, perché prenderlo una volta al secondo sembrava uno spreco. Un wrapper di media di LibVLCSharp prende un proprio riferimento nativo, quindi il wrapper in cache restava valido dopo che il motore gli aveva scambiato sotto il media su un thread del pool. Continuava a rispondere. Non ha mai sollevato niente. Restituiva i contatori del media morto, che non si muovono, che per ogni regola della macchina è uno stallo totale dei byte.
Il sintomo era un canale sano segnalato come rete morta. Il costo è stato un cambio di sorgente via da uno stream che funzionava, osservato dal vivo durante una prova di accettazione sul campo il 2026-07-24 alle 23:24. Il commento che adesso sta sopra la correzione lo dice chiaro:
// 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.
Un rilevatore che legge un ingresso stantio non fallisce in silenzio. Produce lo stesso verdetto, con la stessa sicurezza, su un canale che sta andando perfettamente, e poi ci agisce sopra. Ogni altro guasto in un watchdog ti costa uno stallo che non hai preso. Questo ti costa uno stallo che ti sei inventato, e chi guarda vede il ripristino.
Ecco che cosa emette davvero un blocco di dodici secondi
Qui sotto c'è una sessione a copione fatta girare attraverso il motore delle regole che spediamo, sul banco di prova già esistente, dodici tick da un secondo. È stata eseguita, non tracciata a mano, e il driver è stato cancellato dopo. Leggi i numeri come valori di prova. Ogni richiesta di istantanea è stata soddisfatta 300 ms dopo, come farebbe un host vero, dato che il budget dell'istantanea è di un secondo.
La sessione si apre, riporta Playing e riporta un'uscita video. Poi:
| t | dRead | dDemux | dDisplayed | dAudio | stato dopo | effetti emessi |
|---|---|---|---|---|---|---|
| 1 s | +50.000 | +48.000 | +25 | +40 | Starting | nessuno |
| 2 s | +50.000 | +48.000 | +25 | +40 | Healthy | StateChanged, SetSampleRate 1 Hz, RequestSnapshot |
| 3 s | +50.000 | +48.000 | +25 | +40 | Healthy | nessuno |
| 4 s | +50.000 | +48.000 | +25 | +40 | Healthy | nessuno |
| 5 s | +50.000 | +48.000 | +25 | +40 | Healthy | nessuno |
| 6 s | +50.000 | +48.000 | +25 | +40 | Healthy | nessuno |
| 7 s | +50.000 | +48.000 | +0 | +40 | Suspect | StateChanged, SetSampleRate 2 Hz, RequestSnapshot |
| 8 s | +50.000 | +48.000 | +0 | +40 | Suspect | nessuno |
| 9 s | +50.000 | +48.000 | +0 | +40 | Suspect | RequestSnapshot |
| 10 s | +50.000 | +48.000 | +0 | +40 | Suspect | nessuno |
| 11 s | +50.000 | +48.000 | +0 | +40 | Recovering | StateChanged, FaultDeclared VideoFreeze, età 4000 ms |
| 12 s | +50.000 | +48.000 | +0 | +40 | Recovering | nessuno |
Il tick 1 non produce niente, perché un solo campione non è un delta; diventa la base di riferimento. Il tick 2 è la prima coppia valutata, il progresso della presentazione è dimostrato, e la macchina lascia Starting. Ridichiara anche una frequenza di campionamento che stava già usando, cosa ridondante e innocua. La richiesta di istantanea su quella riga è la passata iniziale, e l'host le ha risposto con contenuto vero, il 6 per cento di pixel neri a una deviazione standard di 41, quindi la passata si è chiusa.
I tick da 3 a 6 non emettono proprio niente. È il regime stazionario voluto: uno stream sano non produce effetti, quindi il caso di successo è un log vuoto.
Il tick 7 è dove l'immagine si ferma. Il marcatore dello stallo video viene timbrato con 7 s, la macchina passa a Suspect, il campionatore raddoppia a 2 Hz, e la sonda viene armata e chiede un fotogramma. Non viene dichiarato niente, perché un marcatore di età zero non è un verdetto. I tick 8 e 10 sono la finestra che scorre, e il tick 9 è la sonda che ricampiona alla sua cadenza, due secondi dopo l'ultima richiesta invece dei 1500 ms configurati, perché scatta al primo messaggio dopo l'ora prevista. I byte e l'audio non vacillano mai in tutto questo: per ogni regola tranne una questa sessione sembra perfetta.
Il tick 11 è il verdetto. Il marcatore dello stallo video raggiunge i quattro secondi, il demuxer non è sotto sospetto, l'audio è avanzato durante lo stallo e sta ancora avanzando, quindi è un blocco del video e non uno totale, e l'età dell'anomalia riportata all'esecutore è 4000 ms. Fai girare lo stesso identico copione con anche l'audio che si ferma al tick 7 e la stessa riga dichiara un blocco totale. Fallo girare di nuovo con l'istantanea che va in timeout invece di restituire contenuto e il verdetto non cambia, ma la sua stringa del motivo diventa video stalled while audio keeps playing (snapshot timeout observed): l'uscita incastrata ha confermato, non ha deciso.
Il tick 12 non emette niente perché la macchina ha passato la mano. La suite Playback attorno a questo codice è di 108 test, tutti verdi mentre scrivo.
Un verdetto compra cinque gradini, e uno non è costruito
Il verdetto è un ingresso per una scala, non una risposta. I gradini che esistono sono cinque: una scossa di pausa e riproduzione, che tocca solo ai blocchi totali; una riapplicazione completa dello stesso URL; un passaggio al candidato successivo; una ricostruzione del lettore stesso; e la resa. Un guasto entro quindici secondi dalla sintonizzazione entra al passaggio invece che alla riapplicazione, perché una sorgente che non si è mai dimostrata non merita un secondo tentativo sull'URL che ha appena fallito. Le sessioni mature tengono la riapplicazione per prima, così un singhiozzo dieci minuti dopo non costa a chi guarda un cambio di sorgente. I tentativi di riavvio prima di salire di livello sono esattamente uno, perché i dati dal campo hanno mostrato che il secondo non serve mai.
Al momento il gradino della ricostruzione è una finzione dichiarata. Non ha una primitiva nell'host, è disattivato nelle impostazioni che spediamo, e l'unica condizione che ci porta, un arresto andato in timeout che ha lasciato la sessione nativa incastrata, oggi chiude la scala in primo piano e finisce invece sulla resa. È prudente ed è corretto, è anche una lacuna, e il codice lo dice dove succede.
Fra un tentativo e l'altro il backoff è esponenziale con il primo tentativo gratis: base per due elevato all'indice del tentativo, meno uno, con un tetto, e poi con un jitter fino al venti per cento in un verso o nell'altro. In pratica, si parte subito, poi un secondo, tre, sette, e così via fino al tetto. Il jitter moltiplica dopo il tetto e non prima, quindi il soffitto vero è trentasei secondi, non i trenta che il tetto lascia credere. Roba da poco, ma è il genere di roba da poco che fa sembrare sbagliato un grafico alle 3 di notte.
I pixel restano sulla macchina che li ha catturati
La sonda scrive due file PNG a rotazione dentro la cartella dati locale dell'app e li sovrascrive sul posto. Nessuno li carica da nessuna parte e nessuno li manda da nessuna parte; il fotogramma esiste il tempo necessario a ridursi a due numeri e a un hash, e poi viene sovrascritto dal successivo. I verdetti di guasto vanno nel log locale sulla stessa macchina, e lì un canale è identificato dai primi sedici caratteri esadecimali di uno SHA-256, mai da un URL.
È quest'ultima parte il motivo per cui non posso dirti quanto sia frequente il caso del fotogramma luminoso bloccato. Le forme degli eventi di telemetria sono scritte e tipizzate, nel loro commento di documentazione nominano un caricatore a lotti, e quel caricatore non esiste. Tre tipi record sono dichiarati e non vengono mai costruiti da nessuna parte nel codice. Ogni giudizio di questo articolo su quali modalità di guasto contino viene da una sola prova di accettazione sul campo, da un banco di iniezione dei guasti e da una suite di test, non da una flotta. È un limite vero sulla confidenza di tutto quello che sta sopra.
Se preferisci usare il rilevatore invece di leggerne, nell'app My TV Player per Windows è attivo per impostazione predefinita, e puoi creare un account gratuito per provarlo. Quando fa il suo lavoro non lo vedrai: l'immagine si impunterà per quattro secondi e tornerà, e sullo schermo non sarà comparso niente a dirti che è mai stato raggiunto un verdetto.
Cosa ha misurato questo articolo37 affermazioni, ciascuna con le prove a sostegno
| Richiedi | Prove | Contato |
|---|---|---|
| Tutto quello che c'è in questo articolo è misurato contro libvlc 3.0.21 attraverso LibVLCSharp 3.9.7 sulla testa Windows. L'API delle statistiche in libvlc 4.x è diversa, quindi le affermazioni sui contatori sono limitate alla 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. | Specifica | Non applicabile |
| libvlc resta in Playing a tempo indeterminato sia durante un blocco sia davanti a un feed nero, e EncounteredError è riservato ai fallimenti duri di apertura e di protocollo, quindi non scatta quasi mai per le due modalità di guasto dominanti in diretta.docs/research/MTP-PlaybackWatchdog-Spec-v1.0.md:18 (within the cited 15-19 block), header checked, no confidentiality marking. | Specifica | Non applicabile |
| Sono definite nove classi di guasto, da F1 a F9, e solo otto di esse possono essere dichiarate. F8 ClockStall non viene dichiarata da nessuna parte e non ha un codice di tassonomia suo. | n = 9 | 22 ago 2026 |
| Tre delle nove classi viaggiano su eventi del motore. F3 è l'errore del motore stesso, F2 viene dichiarata a partire da EndReached o da uno Stopped silenzioso, e F9 viene dichiarata a partire da Buffering. Le altre si deducono dai contatori. | n = 3 | 22 ago 2026 |
| La scheda di errore mostrava una sola frase per ogni classe, e adesso ogni classe corrisponde a un codice di tassonomia che dipende anche dal fatto che il guasto sia arrivato prima o dopo il primo fotogramma. | n = 1 | 22 ago 2026 |
| Un campione di statistiche è fatto di undici campi: un timestamp monotono e dieci contatori cumulativi. Di otto si calcola la differenza; di LostPictures e LostAudioBuffers non la calcola mai nessuna regola. | n = 11 | 22 ago 2026 |
| Solo il video ha un contatore di ricaduta. DecodedVideo subentra quando DisplayedPictures è inutilizzabile; DecodedAudio serve soltanto a dimostrare che esiste un flusso audio e nessuna regola lo legge mai come segnale di vitalità. | n = 1 | 22 ago 2026 |
| I verdetti si controllano prima sull'accesso, poi sul demuxer, per ultima sulla presentazione, e le regole di blocco stanno ferme finché si sospetta una carenza del demuxer. | n = 1 | 22 ago 2026 |
| Taratura attuale: 4 s di zero byte per F1, 6 s per F4, 4 s per F5 e F6, 15 s per un cartello a bassa frequenza di fotogrammi. | n = 1 | 22 ago 2026 |
| Quattro marcatori annullabili tengono l'istante in cui ogni condizione è iniziata, e un solo tick buono li azzera. Un quinto segnale, il picco di corruzione e discontinuità, funziona al contrario: memorizza un istante di scadenza e accorcia le finestre di blocco finché resta armato. | n = 4 | 22 ago 2026 |
| Il modello dei contatori è calibrato su MPEG-TS grezzo. Il tipo di sorgente viene portato come "ts" oppure "hls" per la telemetria e per future soglie per tipo, e oggi nelle opzioni non c'è nessuna soglia specifica per HLS. | n = 1 | 22 ago 2026 |
| I contatori di byte di libvlc 3.x sono a 32 bit e per singolo media, quindi ripartono da zero a ogni riapertura e vanno in overflow sulle sessioni lunghe. In entrambi i casi il delta diventa 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. | Specifica | Non applicabile |
| Un delta negativo su uno qualunque dei sei contatori di vitalità reimposta la base di riferimento e salta la valutazione per quel tick. I delta delle unità corrotte e delle discontinuità non fanno parte di quella guardia. | n = 6 | 22 ago 2026 |
| Il contatore delle immagini viene calibrato su una finestra di 30 s verso Trusted, FallbackDecodedVideo o Untrusted attraverso cinque rami, e i verdetti di blocco pretendono anche che un contatore video sia avanzato almeno una volta in questa sessione. | n = 5 | 22 ago 2026 |
| I percorsi che sottostimano le immagini visualizzate sono quelli di vout e di decodifica hardware, non i moduli di accesso.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. | Specifica | Non applicabile |
| 45 tick da un secondo con l'audio che suona, l'orologio che avanza e un'uscita video attiva, ma tutti i contatori video morti, non dichiarano nessun guasto e finiscono su Untrusted. | n = 45 | 22 ago 2026 |
| Un cartello misurato a circa mezza immagine al secondo allarga la finestra di blocco a 15 s invece di accorciarla, e un arresto completo del cartello viene comunque rilevato sulla finestra allargata. | n = 1 | 22 ago 2026 |
| Uno stallo dell'audio accanto a un video sano poteva sollevare un sospetto ma non poteva mai arrivare a un verdetto, quindi una sessione se ne restava in SUSPECT per tutta la vita alla frequenza di campionamento più alta, non emetteva mai il segnale di stabilità e lasciava il registro dei ripristini non azzerato, così i guasti successivi ereditavano una scala già consumata e andavano dritti alla scheda della resa. | n = 1 | 22 ago 2026 |
| Una pausa dell'utente maschera i contatori fermi a tempo indeterminato; un seek li maschera solo per 5 s; il buffering sospende i verdetti di presentazione mentre la regola dello zero byte continua a girare. | n = 3 | 22 ago 2026 |
| Taratura attuale del nero: un pixel conta come nero a luma 24 o meno, un campione è nero al 98 per cento di pixel neri con una deviazione standard della luma di 4.0 o meno, e il verdetto pretende 4 campioni consecutivi, catturati a 96 per 54. | n = 1 | 22 ago 2026 |
| Per 10 s dopo che un tentativo di ripristino si conferma, i campioni neri consecutivi richiesti scendono a 2. | n = 1 | 22 ago 2026 |
| 1500 ms è un pavimento per il riarmo, non una spaziatura. La sonda scatta al primo messaggio che arriva all'ora prevista o dopo, quindi la spaziatura reale è più lunga: 2 s dove i messaggi arrivano a 1 Hz, come nell'esecuzione registrata qui. | n = 1 | 22 ago 2026 |
| Una scena notturna fallisce entrambi gli assi del nero e un contenuto con le bande nere sta sotto la metà di nero; tutti e due sono fissati dai test come non nero. | n = 2 | 22 ago 2026 |
| La sonda gira come una passata una tantum subito dopo che lo stream presenta per la prima volta, e di nuovo al passaggio in SUSPECT quando è stato uno stallo video a sollevare il sospetto. Una volta chiusa la passata iniziale non gira mai più durante la salute a regime, quindi un nero a metà riproduzione su uno stream per il resto sano non produce nessun verdetto. | n = 2 | 22 ago 2026 |
| La sonda cattura attraverso la chiamata di istantanea del motore stesso, e la rilettura di un'istantanea da una superficie decodificata in hardware è un posto noto in cui un lettore restituisce un fotogramma vuoto per motivi che non hanno niente a che fare con il contenuto. L'istantanea fallita la trattiamo come prova a conferma; non abbiamo nessuna misura di quanto spesso una rilettura riuscita torni indietro nera per sbaglio.PlaybackEngine.Watchdog.cs:253 calls MediaPlayer.TakeSnapshot (libvlc_video_take_snapshot); the timeout path is handled at WatchdogStateMachine.cs:867-873. | Specifica | Non applicabile |
| Su ogni campione della sonda si calcolano un hash medio a 64 bit e una luma media, tutti e due viaggiano dentro il messaggio, e il motore delle regole non ne legge nessuno dei due. | n = 1 | 22 ago 2026 |
| Un fotogramma piatto produce un hash medio con tutti i bit accesi, perché ogni cella sta esattamente sulla media globale e il confronto è maggiore o uguale. Il bianco pieno e il nero pieno sono quindi indistinguibili per questo hash. | n = 1 | 22 ago 2026 |
| Un use-after-free dentro libvlc emerge come una violazione di accesso nativa, non come un'eccezione CLR, quindi un catch gestito non gira mai.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. | Specifica | Non applicabile |
| Un wrapper Media di LibVLCSharp tenuto in cache mantiene un proprio retain nativo, quindi dopo uno scambio di media su un thread del pool continua a restituire i contatori fermi del media morto senza mai sollevare niente.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. | Specifica | Non applicabile |
| Un wrapper di media tenuto in cache ha fatto riportare al campionatore i contatori fermi di un media morto su uno stream sano, letti come uno stallo di rete. Osservato dal vivo durante una prova di accettazione sul campo il 2026-07-24 alle 23:24. Risolto acquisendo e rilasciando il wrapper a ogni tick. | n = 1 | 24 lug 2026 |
| Dodici tick da un secondo scritti a copione raggiungono HEALTHY a 2 s, SUSPECT a 7 s e dichiarano F5 VideoFreeze a 11 s con un'età dell'anomalia di 4000 ms. Lo stesso identico copione con anche il contatore audio che si ferma dichiara F6 TotalFreeze allo stesso tick, e una terza esecuzione in cui le istantanee di SUSPECT vanno in timeout dichiara F5 con la stringa del motivo estesa a "video stalled while audio keeps playing (snapshot timeout observed)". | n = 12 | 22 ago 2026 |
| La suite di test Playback esegue 108 test con 0 fallimenti. | n = 108 | 22 ago 2026 |
| Cinque gradini: una scossa di pausa e riproduzione solo per i blocchi totali, una riapplicazione completa, un passaggio al candidato successivo, una ricostruzione del lettore, poi la resa. Un solo tentativo di riavvio prima di salire di livello, e un guasto entro 15 s dalla sintonizzazione entra al passaggio invece che alla riapplicazione. | n = 5 | 22 ago 2026 |
| Il gradino della ricostruzione del lettore non ha ancora una primitiva nell'host ed è disattivato nelle impostazioni che spediamo, quindi oggi un episodio di timeout in arresto chiude la scala in primo piano e finisce sulla resa. | n = 1 | 22 ago 2026 |
| Il tentativo k aspetta min(base per (2^k - 1), tetto) con jitter simmetrico, quindi il primo tentativo scatta subito e dal secondo la crescita è 1 s, 3 s, 7 s fino al tetto. Il jitter si applica dopo il tetto, quindi le attese al tetto si distribuiscono simmetricamente attorno a 30 s e il soffitto vero è 36 s. | n = 1 | 22 ago 2026 |
| La sonda scrive due file PNG a rotazione dentro la cartella dati locale dell'app e li sovrascrive. Gli eventi di guasto vanno nel log locale, dove un canale è identificato dai primi 16 caratteri esadecimali di uno SHA-256 e mai da un URL. | n = 1 | 22 ago 2026 |
| I tipi record della telemetria sono dichiarati e non vengono mai costruiti, e il caricatore a lotti nominato nel loro commento di documentazione non esiste. | n = 3 | 22 ago 2026 |