Prima parte:
Cosa? Un codec video è una parte del software/hardware che comprime e/o decomprime video digitale.
A cosa serve? Nonostante alcune limitazioni sia in termini di larghezza di banda che
di spazio di archiviazione, il mercato richiede video di qualità sempre superiore. Ricordate come nel post precedente abbiamo calcolato il minimo necessario per 30 fotogrammi al secondo, 24 bit per pixel, con una risoluzione di 480×240? Abbiamo ottenuto 82,944 Mbit/s senza compressione. La compressione è attualmente l'unico modo per trasmettere HD/FullHD/4K su schermi televisivi e su Internet. Come si ottiene? Vediamo ora brevemente i principali metodi.
La traduzione è stata realizzata con il supporto di EDISON Software.Ci occupiamo di , così come .
Codec vs Contenitore
Un errore comune tra i principianti è confondere il codec video digitale con il contenitore video digitale. Un contenitore è un formato, una sorta di involucro che contiene i metadati del video (e, possibilmente, dell'audio). Il video compresso può essere considerato come il payload del contenitore.
Di solito, l'estensione di un file video indica il tipo di contenitore. Ad esempio, un file video.mp4 è probabilmente un contenitore MPEG-4 Parte 14, mentre un file chiamato video.mkv è, molto probabilmente, . Per essere completamente sicuri del codec e del formato del contenitore, si può utilizzare o .
Un po' di storia
Prima di passare a Come?, immergiamoci un po' nella storia per comprendere meglio alcuni vecchi codec.
Il codec video H.261 è stato introdotto nel 1990 (tecnicamente nel 1988) e creato per funzionare a una velocità di trasmissione di 64 Kbit/s. Già in esso venivano utilizzate idee come la sottocampionatura del colore, macroblocchi, ecc. Nel 1995 è stato pubblicato lo standard del codec video H.263, che è stato sviluppato fino al 2001.
Nel 2003 è stata completata la prima versione H.264/AVC. Nello stesso anno, l'azienda «TrueMotion» ha rilasciato il suo codec video gratuito, che comprimeva video con perdita chiamato VP3. Nel 2008 Google ha acquistato questa azienda, rilasciando VP8 nello stesso anno. Nel dicembre 2012 Google ha rilasciato VP9, e supporta circa ¾ del mercato dei browser (inclusi i dispositivi mobili).
AV1 — è un nuovo codec video gratuito e open source, sviluppato da Alliance for Open Media (AOMedia), composto da aziende rinomate come: Google, Mozilla, Microsoft, Amazon, Netflix, AMD, ARM, Nvidia, Intel e Cisco. La prima versione del codec 0.1.0 è stata pubblicata il 7 aprile 2016.
Nascita di AV1
All'inizio del 2015, Google stava lavorando su VP10, Xiph (di proprietà di Mozilla) stava lavorando su Daala, e Cisco ha realizzato il suo codec video gratuito chiamato Thor.
Poi MPEG LA inizialmente ha annunciato limiti annuali per HEVC (H.265) e una tassa, 8 volte superiore rispetto a H.264, ma presto hanno cambiato nuovamente le regole:
senza limiti annuali,
una tassa per i contenuti (0,5% dei ricavi) e
una tassa per unità di prodotto circa 10 volte superiore rispetto a H.264.
Alliance for Open Media è stata creata da aziende di diversi settori: produttori hardware (Intel, AMD, ARM, Nvidia, Cisco), fornitori di contenuti (Google, Netflix, Amazon), creatori di browser (Google, Mozilla) e altri.
Le aziende avevano un obiettivo comune: un codec video senza royalty. Poi è arrivato AV1 con una licenza di brevetto molto più semplice. Timothy B. Terriberry ha fatto una presentazione straordinaria, che è diventata la fonte dell'attuale concetto di AV1 e del suo modello di licenza.
Rimarrete sorpresi di sapere che è possibile analizzare il codec AV1 direttamente dal browser (chi è interessato può visitare).

Codec universale
Esaminiamo i principali meccanismi alla base del codec video universale. Molte di queste concetti sono utili e vengono utilizzati nei codec moderni, come VP9, AV1 e HEVC. Avviso, molte delle cose spiegate saranno semplificate. A volte verranno utilizzati esempi reali (come nel caso di H.264) per dimostrare le tecnologie.
Passo 1 — suddivisione dell'immagine
Il primo passo consiste nel dividere il fotogramma in diverse sezioni, sottosezioni e così via.

Perché? Ci sono molte ragioni. Quando si suddivide l'immagine, è possibile prevedere più precisamente il vettore del movimento, utilizzando piccole sezioni per piccole parti in movimento. Mentre per uno sfondo statico si possono usare sezioni più grandi.
Di solito i codec organizzano queste sezioni in parti (o frammenti), macroblocchi (o blocchi dell'albero di codifica) e molte sottosezioni. La dimensione massima di queste sezioni varia: HEVC stabilisce 64×64, mentre AVC utilizza 16×16, e le sottosezioni possono essere suddivise fino a dimensioni di 4×4.
Ricordi le varie tipologie di fotogrammi dall'articolo precedente?! Questo si può applicare anche ai blocchi, quindi possiamo avere un frammento I, un blocco B, un macroblocco P, e così via.
Per chi desidera esercitarsi, date un'occhiata a come un'immagine sarà suddivisa in sezioni e sottosezioni. A tal fine, potete utilizzare il software già menzionato nell'articolo precedente. (quello a pagamento, ma con una versione di prova gratuita che ha un limite sui primi 10 fotogrammi). Qui sono analizzate le sezioni. VP9:

2° passo — previsione
Una volta che abbiamo le sezioni, possiamo creare previsioni astrologiche su di esse. Per la previsione INTER è necessario trasmettere i vettori di movimento e il residuo, mentre per la previsione INTRA si trasmette la direzione della previsione e il residuo.
3° passo — trasformazione
Dopo aver ottenuto il blocco residuo (sezione prevista → sezione reale), è possibile trasformarlo in modo da sapere quali pixel possono essere scartati, mantenendo comunque la qualità complessiva. Esistono alcune trasformazioni che garantiscono un comportamento preciso.
Sebbene ci siano altri metodi, esaminiamo più nel dettaglio trasformata discreta del coseno (DCT — da discrete cosine transform). Le principali funzioni della DCT:
- Trasforma blocchi di pixel in blocchi di coefficienti di frequenza di dimensioni equivalenti.
- Compatta l'energia, aiutando a rimuovere la ridondanza spaziale.
- Garantisce invertibilità.
Il 2 febbraio 2017, Cintra R.J. e Bayer F.M. hanno pubblicato un articolo sulla trasformata simile alla DCT per la compressione delle immagini, richiedente solo 14 somme.
Non preoccuparti se non hai compreso i vantaggi di ogni punto. Adesso vedremo esempi concreti per comprenderne il reale valore.
Prendiamo un blocco di pixel 8×8:

Questo blocco viene renderizzato nell'immagine 8 per 8 pixel seguente:

Applichiamo la DCT a questo blocco di pixel e otteniamo un blocco di coefficienti di dimensioni 8×8:

E se renderizziamo questo blocco di coefficienti, otteniamo un'immagine del genere:

Come possiamo vedere, questo non assomiglia all'immagine originale. Possiamo notare che il primo coefficiente differisce notevolmente dagli altri. Questo primo coefficiente è noto come coefficiente DC, che rappresenta tutte le campionature nell'array d'ingresso, simile a una media.
Questo blocco di coefficienti ha una proprietà interessante: separa le componenti ad alta frequenza da quelle a bassa frequenza.

Nell'immagine, gran parte della potenza è concentrata su frequenze più basse, pertanto, se convertiamo l'immagine nelle sue componenti di frequenza e scartiamo i coefficienti ad alta frequenza, possiamo ridurre la quantità di dati necessaria per descrivere l'immagine, senza compromettere troppo la qualità dell'immagine stessa.
La frequenza indica quanto velocemente varia il segnale.
Proviamo ad applicare le conoscenze acquisite nell'esempio di test, trasformando l'immagine originale nella sua frequenza (blocco di coefficienti) utilizzando la DCT e poi scartando parte dei coefficienti meno importanti.
Iniziamo a convertirlo nella zona di frequenza.

Successivamente scartiamo parte (67%) dei coefficienti, principalmente la parte inferiore destra.

Infine, ricostruiamo l'immagine da questo blocco di coefficienti scartato (ricorda, deve essere invertibile) e la confrontiamo con l'originale.

Possiamo vedere che assomiglia all'immagine originale, ma ci sono molte differenze rispetto all'originale. Abbiamo scartato il 67,1875% e abbiamo comunque ottenuto qualcosa che assomiglia all'originale. Si sarebbero potuti scartare i coefficienti in modo più ponderato per ottenere un'immagine di qualità ancora migliore, ma questa è un'altra questione.
Ogni coefficiente è formato utilizzando tutti i pixel
Importante: ogni coefficiente non viene direttamente visualizzato come un singolo pixel, ma rappresenta una somma pesata di tutti i pixel. Questo sorprendente grafico mostra come viene calcolato il primo e il secondo coefficiente utilizzando pesi unici per ogni indice.
Puoi anche provare a visualizzare la DCT, guardando la semplice formatura dell'immagine basata su di essa. Ad esempio, ecco il simbolo A, formato utilizzando ciascun peso del coefficiente:
4° passo – quantizzazione
Dopo aver scartato alcuni coefficienti nel passaggio precedente, nell'ultimo passaggio (trasformazione) eseguiamo una forma particolare di quantizzazione. In questa fase, è accettabile perdere informazioni. In altre parole, quantizzeremo i coefficienti per ottenere una compressione.
Come possiamo quantizzare un blocco di coefficienti? Uno dei metodi più semplici è la quantizzazione uniforme, in cui prendiamo il blocco, lo dividiamo per un valore (per 10) e arrotondiamo il risultato.

Possiamo invertire questo blocco di coefficienti? Sì, possiamo, moltiplicandolo per lo stesso valore con cui abbiamo diviso.

Questo approccio non è il migliore, poiché non considera l'importanza di ogni coefficiente. Si potrebbe usare una matrice di quantizzazione invece di un solo valore, e questa matrice può sfruttare le proprietà della DCT, quantizzando la maggior parte dei coefficienti nella parte bassa destra e pochi nella parte alta sinistra.
Passo 5 — codifica dell'entropia
Dopo aver quantificato i dati (blocchi di immagini, frammenti, fotogrammi), possiamo comunque comprimerli senza perdite. Esistono molti metodi algoritmici per la compressione dei dati. Ci proponiamo di presentare brevemente alcuni di essi; per una comprensione più approfondita, puoi leggere il libro «Understanding Compression: Data Compression for Modern Developers» («»).
Codifica video con VLC
Supponiamo di avere un flusso di simboli: a, e, r e t. La probabilità (nell'intervallo da 0 a 1) di quanto spesso si presenta ogni simbolo nel flusso è rappresentata in questa tabella.
| a | e | r | t | |
|---|---|---|---|---|
| Probabilità | 0,3 | 0,3 | 0,2 | 0,2 |
Possiamo assegnare codici binari unici (preferibilmente piccoli) ai simboli più probabili, e codici più lunghi ai simboli meno probabili.
| a | e | r | t | |
|---|---|---|---|---|
| Probabilità | 0,3 | 0,3 | 0,2 | 0,2 |
| Codice binario | 0 | 10 | 110 | 1110 |
Comprimiamo il flusso, supponendo che alla fine spenderemo 8 bit per ogni simbolo. Senza compressione, sarebbero necessari 24 bit per simbolo. Se sostituiamo ogni simbolo con il suo codice, si ottiene un risparmio!
Il primo passo consiste nella codifica del simbolo e, che è uguale a 10, e il secondo simbolo è a, che viene aggiunto (non in modo matematico): [10] [0], e infine, il terzo simbolo t, che rende il nostro flusso di bit compresso finale uguale a [10] [0] [1110] oppure 1001110, richiedendo solo 7 bit (3,4 volte meno spazio rispetto all'originale).
Si noti che ogni codice deve essere unico con un prefisso. aiuterà a trovare questi numeri. Sebbene questo metodo non sia privo di difetti, ci sono codec video che offrono ancora questo metodo algoritmico per la compressione.
Sia il codificatore che il decodificatore devono avere accesso alla tabella dei simboli con i loro codici binari. Pertanto, è necessario inviare anche la tabella nei dati di input.
La codifica aritmetica
Supponiamo di avere un flusso di simboli: a, e, r, s e t, e la loro probabilità è rappresentata da questa tabella.
| a | e | r | s | t | |
|---|---|---|---|---|---|
| Probabilità | 0,3 | 0,3 | 0,15 | 0,05 | 0,2 |
Con questa tabella costruiremo intervalli che contengono tutti i simboli possibili, ordinati per il numero maggiore.

Ora codifichiamo un flusso di tre simboli: eat.
Iniziamo scegliendo il primo simbolo e, che si trova nell'intervallo da 0,3 a 0,6 (escluso). Prendiamo questo sottointervallo e lo dividiamo di nuovo nelle stesse proporzioni di prima, ma per questo nuovo intervallo.

Continuiamo a codificare il nostro flusso eat. Ora prendiamo il secondo simbolo a, che si trova nel nuovo sottointervallo da 0,3 a 0,39, e poi prendiamo il nostro ultimo simbolo t e, ripetendo lo stesso processo di nuovo, otteniamo l'ultimo sottointervallo da 0,354 a 0,372.

Dobbiamo semplicemente scegliere un numero nell'ultimo sottointervallo da 0,354 a 0,372. Prendiamo 0,36 (ma possiamo scegliere anche un altro numero in questo intervallo). Solo con questo numero possiamo ripristinare il nostro flusso originale. È come se disegnassimo una linea all'interno degli intervalli per codificare il nostro flusso.

L'operazione inversa (cioè decodifica) è altrettanto semplice: con il nostro numero 0,36 e il nostro intervallo originale possiamo avviare lo stesso processo. Ma ora, utilizzando questo numero, identifichiamo il flusso codificato con questo numero.
Con il primo intervallo notiamo che il nostro numero corrisponde a una fetta, quindi questo è il nostro primo simbolo. Ora separiamo di nuovo questo sottointervallo, seguendo lo stesso processo di prima. Qui possiamo notare che 0,36 corrisponde al simbolo a, e dopo aver ripetuto il processo, siamo arrivati all'ultimo simbolo t (formando il nostro flusso codificato originale eat).
Sia il codificatore che il decodificatore devono disporre di una tabella delle probabilità dei simboli, quindi è necessario inviare anche essa nei dati di input.
Abbastanza elegante, vero? Chiunque abbia ideato questa soluzione era davvero molto intelligente. Alcuni codec video utilizzano questa tecnica (o, almeno, la propongono come opzione).
L'idea è quella di comprimere senza perdite un flusso di bit quantizzato. È probabile che in questo articolo manchino tonnellate di dettagli, motivazioni, compromessi, ecc. Ma se sei uno sviluppatore, dovresti saperne di più. Nuovi codec cercano di utilizzare diversi algoritmi di codifica di entropia, come ANS.
6 passo - formato del flusso di bit
Dopo aver fatto tutto ciò, rimane da decomprimere i frame compressi nel contesto dei passaggi eseguiti. È necessario informare esplicitamente il decodificatore delle decisioni prese dal codificatore. Al decodificatore deve essere fornita tutta l'informazione necessaria: profondità di bit, spazio colore, risoluzione, informazioni sulle previsioni (vettori di movimento, previsione INTER direzionale), profilo, livello, frequenza dei fotogrammi, tipo di fotogramma, numero di fotogramma e molto altro.
Diamo un'occhiata superficiale al bitstream H.264. Il nostro primo passo è creare un bitstream H.264 minimo (FFmpeg aggiunge automaticamente tutti i parametri di codifica, come SEI NAL — scopriremo più avanti di cosa si tratta). Possiamo farlo utilizzando il nostro repository e FFmpeg.
./s/ffmpeg -i /files/i/minimal.png -pix_fmt yuv420p /files/v/minimal_yuv420.h264
Questo comando genererà un bitstream non compresso H.264 con un fotogramma, risoluzione 64×64, con spazio colore YUV420. Viene utilizzata come fotogramma la seguente immagine.

Bitstream H.264
Standard AVC (H.264) definisce che le informazioni verranno inviate in macroblocchi (nel senso della rete), chiamati NAL (questo è un livello di astrazione della rete). L'obiettivo principale del NAL è fornire una rappresentazione video "friendly to the network". Questo standard deve funzionare su televisori (basato su flussi) e su Internet (basato su pacchetti).
![]()
Esiste un marcatore di sincronizzazione per determinare i confini degli elementi NAL. Ogni marcatore di sincronizzazione contiene il valore 0x00 0x00 0x01, tranne il primo, che è uguale a 0x00 0x00 0x00 0x01. Se eseguiamo hexdump per un flusso bitstream H.264 generato, identifichiamo almeno tre modelli NAL all'inizio del file.

Come detto, il decoder deve conoscere non solo i dati dell'immagine, ma anche i dettagli del video, del frame, dei colori, dei parametri utilizzati e molto altro. Il primo byte di ogni NAL determina la sua categoria e il suo tipo.
| Identificatore del tipo NAL | Descrizione |
|---|---|
| 0 | Tipo sconosciuto |
| 1 | Frammento codificato dell'immagine senza IDR |
| 2 | Sezione codificata dei dati del slice A |
| 3 | Sezione codificata dei dati del slice B |
| 4 | Sezione codificata dei dati del slice C |
| 5 | Frammento IDR codificato dell'immagine IDR |
| 6 | Informazioni aggiuntive sull'estensione SEI |
| 7 | Set di parametri della sequenza SPS |
| 8 | Set di parametri dell'immagine PPS |
| 9 | Separatore di accesso |
| 10 | Fine della sequenza |
| 11 | Fine del flusso |
| … | … |
Di solito, il primo NAL del flusso bitstream è SPS. Questo tipo di NAL è responsabile dell'informazione delle variabili generali di codifica, come profilo, livello, risoluzione e altro.
Se saltiamo il primo marker di sincronizzazione, possiamo decodificare il primo byte per scoprire quale tipo di NAL sia il primo.
Ad esempio, il primo byte dopo il marker di sincronizzazione è 01100111, dove il primo bit (0) si trova nel campo forbidden_zero_bit. I successivi 2 bit (11) ci indicano il campo nal_ref_idc, che indica se questo NAL è un campo di riferimento o meno. E i restanti 5 bit (00111) ci indicano il campo nal_unit_type, in questo caso è un blocco SPS (7) NAL.
Il secondo byte (binary=01100100, hex=0x64, dec=100) nello SPS NAL è un campo profile_idc, che mostra il profilo utilizzato dal coder. In questo caso è stato utilizzato il profilo alto limitato (cioè un profilo alto senza supporto per il segmento B bidirezionale).

Se consultiamo la specifica del flusso di bit H.264 per SPS NAL, troveremo molti valori per il nome del parametro, la categoria e la descrizione. Ad esempio, diamo un'occhiata ai campi pic_width_in_mbs_minus_1 e pic_height_in_map_units_minus_1.
| Nome del parametro | Categoria | Descrizione |
|---|---|---|
| pic_width_in_mbs_minus_1 | 0 | ue(v) |
| pic_height_in_map_units_minus_1 | 0 | ue(v) |
Se eseguiamo alcune operazioni matematiche con i valori di questi campi, otteniamo la risoluzione. Possiamo rappresentare 1920 x 1080 utilizzando pic_width_in_mbs_minus_1 con il valore 119 ((119 + 1) * macroblock_size = 120 * 16 = 1920). Ancora una volta, per risparmiare spazio, invece di codificare 1920, lo abbiamo fatto con 119.
Se continuiamo a controllare il nostro video creato in formato binario (ad esempio: xxd -b -c 11 v/minimal_yuv420.h264), possiamo passare all'ultimo NAL, che è il frame stesso.

Qui vediamo i suoi primi 6 valori byte: 01100101 10001000 10000100 00000000 00100001 11111111. Poiché si sa che il primo byte indica il tipo NAL, in questo caso (00101) si tratta di un frammento IDR (5), e possiamo ulteriormente indagarlo:

Utilizzando le informazioni specifiche, possiamo decodificare il tipo di frammento (slice_type) e il numero del frame (frame_num) tra gli altri campi importanti.
Per ottenere i valori di alcuni campi (ue(v), me(v), se(v) o ta(v), dobbiamo decodificare il frammento utilizzando un decodificatore speciale basato su . Questo metodo è molto efficace per codificare valori variabili, specialmente quando ci sono molti valori di default.
Valori slice_type e frame_num di questo video sono 7 (I-frame) e 0 (primo frame).
Il bitstream può essere considerato come un protocollo. Se desideri saperne di più sul bitstream, consulta la specifica ITU H.264. Ecco uno schema che mostra dove si trovano i dati dell'immagine (YUV in formato compresso).

È possibile esaminare anche altri bitstream, come VP9, H.265 (HEVC) o persino il nostro nuovo migliore bitstream AV1. Sono tutti simili? No, ma una volta compreso almeno uno — sarà molto più facile capire gli altri.
Vuoi esercitarti? Esplora il bitstream H.264
È possibile generare un video a fotogramma singolo e utilizzare MediaInfo per analizzare il flusso di bit. H.264. In effetti, nulla vieta di guardare anche il codice sorgente che analizza il flusso di bit. H.264 (AVC).
Per esercitarsi, si può usare Intel Video Pro Analyzer (ho già accennato che il programma è a pagamento, ma esiste una versione di prova gratuita, con un limite di 10 fotogrammi?).
Panoramica
Va notato che molti codec moderni utilizzano lo stesso modello che abbiamo appena studiato. Dunque, diamo un'occhiata allo schema del codec video. Thor. Esso contiene tutti i passaggi che abbiamo affrontato. L'intero scopo di questo documento è che, perlomeno, comprendiate meglio le innovazioni e la documentazione in questo campo.

In precedenza, abbiamo calcolato che sarebbero necessari 139 GB di spazio su disco per memorizzare un file video di un'ora a qualità 720p e 30 fps. Se utilizziamo i metodi trattati in questo articolo (previsioni inter-frame e intra-frame, trasformata, quantizzazione, codifica entropica, ecc.), si può ottenere (basandosi sul fatto che si spendono 0,031 bit per pixel), un video di qualità del tutto accettabile che occupa solo 367,82 MB, invece di 139 GB di memoria.
Come fa l'H.265 a raggiungere un tasso di compressione migliore rispetto all'H.264?
Ora che sappiamo di più su come funzionano i codec, è più facile comprendere come nuovi codec possano garantire risoluzioni più elevate con un minor numero di bit.
Se confrontiamo AVC e HEVC, è importante ricordare che quasi sempre si tratta di una scelta tra un maggiore carico sulla CPU e il grado di compressione.
HEVC ha più opzioni per le sezioni (e sottosezioni) rispetto a AVC, più direzioni di previsione interna, codifica entropica migliorata e molto altro. Tutti questi miglioramenti hanno reso H.265 capace di comprimere il 50% in più rispetto a H.264.

Prima parte:
Fonte: habr.com




