Il numero che ha reso concreta questa ottimizzazione è semplice: un video reale di produzione è passato da circa 280 MB a 50 MB dopo l’introduzione della nuova configurazione H.264. Significa circa 5,6× più piccolo, con un risparmio di circa 230 MB, cioè dell’82% della dimensione originale.
Non ho ottenuto il risultato spostando la distribuzione su AV1, HEVC o VP9. Il risultato è rimasto H.264 in MP4. È cambiata la configurazione attorno al codec: meno pixel inutili, meno campioni temporali superflui, un obiettivo di qualità molto meno conservativo, più lavoro del codificatore una volta sola e un contratto di decodifica volutamente limitato.
Il carico era molto specifico: brevi filmati illustrati e animati, circa l’80% del traffico da dispositivi mobili, banda come costo ricorrente e codifica preventiva, dove spendere CPU una volta è economico rispetto a distribuire continuamente file troppo grandi.
In forma compatta, le configurazioni vecchia e nuova erano:
configurazione precedente
H.264 Main @ Level 4.0
CRF 19
preimpostazione slow
fino a 1920x1080 / 1080x1920
30 fps
refs = 3
fotogrammi B = 3
GOP ≈ 2 secondi
VBV ≈ 10M / 20M
configurazione nuova
H.264 Main @ Level 3.1
CRF 28
preimpostazione veryslow
limite di classe 720p, senza ingrandimento
CFR utile, normalmente <= 30 fps
refs = 4
fotogrammi B = 5
GOP ≈ 5 secondi
VBV ≈ 4M / 8M
yuv420p / avc1 / faststart
Il risultato misurato: da ~280 MB a ~50 MB
Ho diverse misurazioni reali di produzione, ma non appartengono tutte allo stesso esperimento. Tenerle separate è più importante che scegliere la percentuale più appariscente.
| Misurazione | Prima | Dopo | Riduzione |
|---|---|---|---|
| Stesso video concreto | ~280 MB | ~50 MB | ~5,6× più piccolo / ~82% in meno |
| Fase precedente dello stesso file | ~350 MB | ~238 MB | ~1,47× più piccolo / 32% in meno |
La prima riga è la prova più pulita del titolo: lo stesso video concreto prima e dopo la configurazione più recente, circa 280 MB contro circa 50 MB. Nei vecchi registri recuperati non è rimasta una riga con durata/flusso di bit per quella coppia esatta, quindi non la invento. Il cambio di dimensione da solo corrisponde a circa 5,6×.
Il caso ~350→238 MB apparteneva a una fase precedente di ottimizzazione dello stesso esempio in quel momento. Il risultato era circa 1264×720, 30 fps, ~500 secondi, senza suono e attorno a 3,8 Mbps. I conti tornano: 3,8 Mbps per ~500 secondi sono circa 238 MB. Era già un risparmio del 32% rispetto a ~350 MB, ma ancora troppo per il mio obiettivo di banda.
Anche la vecchia libreria mostrava che H.264 molto grandi non erano un’anomalia isolata. Una verifica conteneva 238 video di produzione per 6,37 GB complessivi: 117 H.264 e 121 AV1. 101 file erano almeno 20 MB e 34 almeno 50 MB. Alcuni H.264 grandi erano:
| Dimensione | Durata | flusso di bit medio |
|---|---|---|
| 121,0 MB | 4:25 | 3,83 Mbps |
| 101,2 MB | 5:19 | 2,659 Mbps |
| 92,78 MB | 4:44 | 2,735 Mbps |
| 90,10 MB | 3:55 | 3,206 Mbps |
| 89,74 MB | 4:41 | 2,676 Mbps |
Sono contenuti diversi, quindi la tabella fornisce contesto e non un test A/B. Mostra però che il vecchio esempio da 3,8 Mbps non era un caso isolato: diversi file H.264 grandi della raccolta precedente si trovavano davvero intorno a 2,6–3,8 Mbps.
L’ottimizzazione più importante non era un’opzione di FFmpeg
Il cambiamento decisivo è stato il modo in cui pensavo al costo.
La codifica avviene una volta. Il trasferimento del file avviene ogni volta che qualcuno lo richiede.
Nel video in tempo reale, spendere molta più CPU per risparmiare un po’ di flusso di bit può essere un pessimo compromesso. I miei file vengono invece codificati in anticipo e poi serviti ripetutamente. In questo modello, risparmiare dieci minuti durante la codifica può avere pochissimo valore economico se una codifica più veloce rende più pesante ogni richiesta futura.
Per questo -preset veryslow ha senso per me. Sono disposto a spendere CPU una volta se x264 può usarla per trovare una rappresentazione più efficiente. Il browser non ripete la ricerca del codificatore; decodifica soltanto il flusso di bit finale.
La regola è diventata: spendere calcolo nel passaggio che avviene una volta ed essere avari di byte nel passaggio che si ripete.
Perché sono rimasto su H.264 invece di inseguire il codec più nuovo
Non sto dicendo che H.264 sia il codec con la migliore efficienza di compressione disponibile. Non lo è. I codec più recenti possono essere molto interessanti quando il sistema di distribuzione può mantenere più versioni e scegliere quella migliore per ogni dispositivo.
Il mio vincolo era diverso: un URL, un file, un codec e il minor numero possibile di sorprese in riproduzione per un pubblico che usa prevalentemente dispositivi mobili.
Per questo lavoro, H.264 in MP4 rimane una base molto sicura. Oggi Apple indica agli sviluppatori di siti di usare file MP4 codificati in H.264 per i video statici in Safari. La documentazione Android attuale include H.264 in MP4 e richiede un decodificatore profilo Main da Android 6.0 in poi; le raccomandazioni di riproduzione H.264 includono anche 1280×720 a 30 fps per l’HD, precisando che l’HD non è disponibile su tutti i dispositivi. Vedi i formati multimediali supportati da Android.
Questo non significa che i dispositivi moderni siano limitati a profilo Main o livello 3.1. Per HLS, ad esempio, Apple tende a preferire profilo High rispetto a Main o profilo Baseline. Ho scelto Main@3.1 perché volevo requisiti di decodifica volutamente modesti per un singolo MP4 statico, non perché Apple lo imponga.
Ho smesso di codificare pixel che non servivano
La risoluzione è stata una delle leve più forti. Il mio tetto è diventato circa 1280×720 in orizzontale, 720×1280 in verticale e circa 960×960 per materiale quadrato o con orientamento misto.
La regola più importante è: non fare ingrandimento solo per raggiungere quel tetto.
Se una sorgente è 900×600, portarla a 1280×720 non recupera dettaglio. Crea soltanto più campioni che il codificatore deve descrivere. Una sorgente 1920×1080 può essere ridotta alla classe 720p, mentre una 900×600 può rimanere intorno a 900×600. Il tetto è un massimo, non un obiettivo.
Sembra banale, ma eliminare pixel inutili può contare più di molte regolazioni poco comuni del codificatore.
Quel limite non è un numero tondo scelto a caso. Un fotogramma 1920×1080 contiene 2.073.600 pixel, mentre 1280×720 ne contiene 921.600. Passare da 1080p a 720p elimina quindi circa il 55,6% dei campioni spaziali prima ancora che il codificatore inizi a prendere decisioni di compressione.
Ho valutato anche 540p come impostazione generale. Tuttavia 960×540 contiene solo 518.400 pixel: il 43,75% in meno di 1280×720, quindi resta il 56,25% dei campioni di 720p. Nel materiale illustrato quei campioni descrivono linee sottili, occhi, capelli, dita, volti e bordi netti. Se devo ridurre ancora i byte, preferisco provare prima un CRF leggermente più alto invece di eliminare alla cieca un altro 43,75% dell’informazione spaziale. La quantizzazione si può cambiare in una nuova codifica; il dettaglio cancellato dal ridimensionamento è già perso.
Per questo la classe 720p è il mio limite generale prudente, non un’affermazione che 540p sia sbagliato. Per un singolo file una versione 540p misurata può tranquillamente vincere. Semplicemente non trasformo quel taglio spaziale irreversibile in una regola globale senza dati.
Ho smesso di pagare per fotogrammi che la sorgente non aveva davvero
La frequenza dei fotogrammi è un altro moltiplicatore. Se un’animazione contiene circa 16 stati visivi realmente utili al secondo, memorizzarla a 30 o 60 fps non crea automaticamente un movimento migliore. Può soprattutto aggiungere campioni temporali ripetuti o sintetizzati che devono comunque essere codificati.
Il mio criterio è preservare la cadenza utile della sorgente e normalmente restare a 30 fps o meno. Per questo tipo di materiale, 12, 15, 16, 18, 20, 24, 25 o 30 fps possono essere tutti valori sensati quando descrivono davvero la sorgente.
Preferisco anche un CFR pulito nell’uscita generata. Il VFR non è intrinsecamente sbagliato; il CFR rende semplicemente più facili marcatori temporali, conteggio dei fotogrammi, controlli di durata, ricerca della posizione e validazione successiva nella mia catena di elaborazione.
Il principio generale è più utile di un singolo valore: non pagare banda per informazione temporale che la sorgente non contiene.
CRF 28 è una scelta per il mio caso d’uso, non un numero magico
Non volevo forzare ogni filmato verso lo stesso obiettivo di flusso di bit. Un’illustrazione quasi statica e una scena con movimento complesso non richiedono lo stesso numero di bit per apparire accettabili.
Uso quindi la modalità CRF di x264 e, per questo caso d’uso illustrato in cui la banda è la priorità, mi sono fermato intorno a -crf 28. FFmpeg documenta CRF in libx264 come controllo del flusso di bit a qualità costante; vedi la documentazione dei codec di FFmpeg.
CRF 28 è volutamente aggressivo. Non lo copierei alla cieca su grana cinematografica, video rumoroso da camera, testo minuscolo in una registrazione dello schermo o un caso d’uso in cui la fedeltà conta più della banda.
Non ho nemmeno un punteggio percettivo universale che dimostri che CRF 28 sia trasparente. Posso affermare qualcosa di più ristretto: sul mio materiale i file sono diventati molto più piccoli e continuavano a sembrare normali durante la riproduzione ordinaria. È un’osservazione pratica, non un’affermazione di assenza di perdita visiva.
Veryslow è costoso per il codificatore, non automaticamente per il decodificatore
La mia preimpostazione è -preset veryslow. Una preimpostazione più lenta offre a x264 più opportunità per cercare decisioni di previsione e codifica efficienti. Il prezzo è CPU e tempo di codifica.
La distinzione fondamentale è che lo sforzo del codificatore e la complessità del decodificatore non sono la stessa cosa.
Posso lasciare x264 lavorare molto e, allo stesso tempo, limitare separatamente il flusso finale. Il mio contratto di uscita conservativo è:
H.264, profilo Main
Level 3.1
yuv420p a 8 bit
avc1
refs = 4
fotogrammi B = 5
B-pyramid = normal
GOP aperto = disattivato
FFmpeg espone separatamente CRF, preimpostazioni, regolazione, restrizioni di profilo, fotogrammi di riferimento e B-fotogrammi. È esattamente così che li considero: il codificatore può cercare a fondo, mentre il lato della riproduzione rimane convenzionale.
GOP e VBV sono limiti di sicurezza, non il controllo principale della qualità
Per questi filmato progressivi brevi uso un GOP massimo di circa cinque secondi: circa -g 150 a 30 fps, -g 120 a 24 fps o -g 80 a 16 fps.
È una scelta per il mio caso d’uso, non una regola universale. La trasmissione adattiva ha vincoli diversi; Apple, per esempio, raccomanda IDR ogni due secondi per HLS. Non trasferisco automaticamente quella regola HLS ai miei brevi MP4 statici progressivi.
Uso inoltre circa:
-maxrate:v 4M
-bufsize:v 8M
Questi valori sono tetti contro picchi di flusso di bit insoliti. Non significano “codificare tutto a 4 Mbps”. CRF continua a controllare l’allocazione normale dei bit, quindi i filmato facili restano liberi di diventare molto piccoli.
Ho mantenuto convenzionale anche il contenitore MP4
Uso esplicitamente avc1. La documentazione HLS attuale di Apple raccomanda formati di campionamento come avc1 invece di avc3. Non è questo a rendere più piccoli i file, ma è coerente con l’obiettivo di produrre un normale H.264 in MP4.
Uso anche -movflags +faststart. La documentazione dei formati FFmpeg spiega che faststart sposta l’indice MP4 moov all’inizio del file. I requisiti Android per trasmissione HTTP indicano inoltre che, per MPEG-4, moov deve precedere mdat dopo ftyp.
ftyp
moov
mdat
Faststart non migliora la compressione. Rende meno problematica la riproduzione progressiva via HTTP.
Per SDR normale uso yuv420p 8 bit e segnalo BT.709 con intervallo video limitato. Se un filmato non richiede suono, non creo una traccia audio. Per i contenuti illustrati uso anche -tune animation; lo considero una scelta specifica del contenuto, non parte del contratto universale di compatibilità.
Il profilo FFmpeg centrale
Per una sorgente illustrata a 30 fps, il nucleo del comando è circa questo:
ffmpeg -i input \
-c:v libx264 \
-preset veryslow \
-tune animation \
-crf 28 \
-profile:v main \
-level:v 3.1 \
-pix_fmt yuv420p \
-tag:v avc1 \
-refs 4 \
-bf 5 \
-g 150 \
-maxrate:v 4M \
-bufsize:v 8M \
-x264-params "open-gop=0:b-pyramid=normal:nal-hrd=none" \
-color_range tv \
-color_primaries bt709 \
-color_trc bt709 \
-colorspace bt709 \
-an \
-movflags +faststart \
output.mp4
Le fasi di ridimensionamento e di gestione della frequenza dei fotogrammi non sono volutamente fissate qui. Una sorgente 900×600 non deve essere ingrandita solo perché il tetto è 1280×720, e un’animazione naturalmente a bassa frequenza non deve essere forzata a 30 fps solo perché l’esempio usa -g 150.
Il comando implementa il criterio; non è il criterio stessa.
Perché i file sono diventati diverse volte più piccoli
Non c’è stata un’opzione miracolosa.
La riduzione è arrivata combinando decisioni che eliminavano sprechi diversi: pixel inutili, fotogrammi inutili, un obiettivo di qualità troppo conservativo, impostazioni del codificatore orientate alla velocità anziché all’efficienza, fotogrammi chiave troppo frequenti e flussi di cui non avevo bisogno.
Per questo dire “questo file è H.264” spiega sorprendentemente poco della sua dimensione. Due codifiche H.264 della stessa sorgente possono essere molto diverse perché il nome del codec non descrive risoluzione, frequenza dei fotogrammi, controllo del flusso di bit, preimpostazione, struttura GOP, profilo o preparazione della sorgente.
Nel mio caso, cambiare queste decisioni intorno al codec ha contato più del cambiare codec.
Cosa non dimostra questo risultato
Non ho isolato ogni impostazione in un esperimento controllato, quindi non posso attribuire onestamente una percentuale esatta del risparmio a veryslow, CRF 28, riduzione della risoluzione o riduzione della frequenza dei fotogrammi singolarmente.
Non posso nemmeno sostenere che ogni uscita CRF 28 sia percettivamente trasparente. “Nessuna perdita di qualità evidente” è la mia osservazione per questo caso d’uso illustrato a normali dimensioni di visualizzazione, non una garanzia scientifica per qualsiasi video.
E non sto dicendo che un unico file H.264 sia l’architettura giusta per ogni sito. Più versioni, trasmissione adattiva, HDR, 4K e negoziazione del codec cambiano i compromessi.
Quello che posso davvero affermare è più ristretto: per una libreria di brevi filmati illustrati e animati in cui la banda è la priorità, il pubblico usa prevalentemente dispositivi mobili, il tempo di codifica costa poco e la riproduzione prevedibile è importante, questo profilo ha reso i miei file diverse volte più piccoli mantenendo un aspetto normale nella riproduzione ordinaria.
Come questo risultato ha cambiato il mio modo di ottimizzare
Prima pensavo all’ottimizzazione video soprattutto come a un problema di impostazioni del codificatore. Ora la considero un problema di costo lungo tutta la vita del file.
Il codificatore può lavorare una sola volta. I byte possono attraversare la rete migliaia o milioni di volte.
Questo cambia il significato di “costoso”.
Sono felice di spendere CPU una volta. Sono molto meno disposto a inviare a ogni futura richiesta pixel creati dall’ingrandimento, fotogrammi che non aggiungono movimento utile o flusso di bit di cui il contenuto non ha bisogno.
Il codec è rimasto noioso: H.264 in MP4. L’ottimizzazione è avvenuta intorno.
Per il mio caso d’uso, la lezione è più utile di qualsiasi singola opzione FFmpeg: ottimizzare il costo che paghi ripetutamente, non quello che paghi una sola volta.