Usare FFMPEG da linea di comando

by Andrea Paz on

 

Questo sarà un lungo articolo su come usare ffmpeg da linea di comando. Ci sono molte possibili interfacce grafiche, come per esempio Handbrake, ma saper usare ffmpeg da terminale significa capire le basi del suo funzionamento, cosa importante per un software così complesso e dalle infinite possibilità. Avverto però che mi sono basato su i miei appunti da utilizzatore di ffmpeg per cui sarà un articolo limitato e non esaustivo e che prende in considerazione funzionalità un po' borderline. Darò per scontato il funzionamento base, di cui farò solo semplici accenni. Anche i casi più complicati non saranno descritti, limitandomi a fornire link per approfondimenti; l'articolo diventerebbe davvero troppo lungo. Per un'introduzione abbastanza completa e approfondita su ffmpeg, vedere:

https://slhck.info/ffmpeg-encoding-course/#/

 In realtà vi basta leggere quel sito per poter fare a meno del mio articolo...

Comunque, cominciamo ad approcciare il funzionamento di ffmpeg.

Schema di funzionamento:

 


 L'immagine è stata presa da: https://slhck.info/ffmpeg-encoding-course/#/9

 libavformat è una libreria che si occupa di leggere e scrivere i formati (chiamati anche container); libavcodec si occupa dei codec; libavfilter si occupa degli effetti e libavdevice comprende i possibili device hardware (GPU). Ci sono molte altre librerie per altre funzionalità, che non vedremo.

 

 Comando generico per ffmpeg:

     ffmpeg <global-options> <input-options> -i <input> <output-options> <output>

 Le global options riguardano l'intero programma e non sono specifiche per singoli media file; servono per il log dell'output, file in sovrascrittura, metadati, ecc. Le input options servono per i files in lettura e le output options servono per le conversioni (codec, quality, ecc), per i filtri, per lo stream mapping; ecc

Una lista completa di tutte le opzioni si trovano qui:

https://gist.github.com/tayvano/6e2d456a9897f55025e25035478a3a50

https://www.bogotobogo.com/VideoStreaming/ffmpeg_options_list.php

 Sono un numero impressionante e ci fa capire quanto grande e complesso sia ffmpeg. Impossibile impararle tutte, si deve ricorrere per forza alla consultazione dell'help via via che abbiamo particolari esigenze.

Vediamo alcuni esempi di utilizzo di ffmpeg.

 

Transcodifica da un formato/codec ad un altro formato/codec:

     ffmpeg -i input.mp4 -c:v av1 -c:a flac output.mkv

 Abbiamo preso il file input.mp4, caratterizzato da un suo codec e da un suo container o formato (mp4) e lo abbiamo trasformato in output.mkv, caratterizzato da un altri codec (av1 e flac) e un altro formato (mkv).

 

Merge Video e Audio:

 Se vogliamo aggiungere ad un file video un dato file audio (merge), si presentano diversi casi. Se si parte da mp4 e si vuole ottenere mp4:

    ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac output.mp4

 Questo comando unisce uno stream video con uno stream audio in "output.mp4". La parte video non è transcodificata e subisce solo un demuxing/muxing. Lo stream audio deve invece subire una transcodifica poiché MP4 non supporta audio di tipo PCM (non compresso).

Stesso caso ma si parte da mp4 e si vuole ottenere mkv o altro:

    ffmpeg -i video.mp4 -i audio.wav -c:v copy output.mkv

Notare che non abbiamo cambiato il codec video (-c:v copy) ma solo il suo container. Non c'è bisogno di codificare l'audio perché è ben supportato dal formato mkv. In questi 2 casi la traccia video non ha audio.

Bisogna chiarire il concetto di stream audio/video. Un file multimediale può essere costituito solo dalla parte video, solo dalla parte audio o da entrambe. Nella parte audio ci possono anche essere più canali: un audio stereo ha due canali, il dolby 5.1 ha sei canali, ecc. La parte video ha sempre e solo un canale.  FFmpeg chiama ogni parte come stream e quindi abbiamo uno stream video (per esempio stream 0) e/o più stream audio (stream 1, stream 2, ecc). 

 

Settaggi di qualità per l'output (codec compressi):

Se non si usano opzioni di qualità ffmpeg userà quelle di default. Le opzioni di qualità si dividono in quelle che agiscono sul "bitrate" (-b:v ... oppure -b:a ...) e quelle che agiscono sul "livello di qualità" (-q:v ... oppure -q:a ...). In  più ogni codec può avere le proprie opzioni di qualità. Per es. libx264/265 hanno "-cfr" (constant rate factor); il codec audio AAC ha "-vbr" (variable bitrate); ecc. Se si imposta un tipo di qualità non si possono usare gli altri tipi.

Il rate control può essere fatto in vari modi: CBR (Constant BitRate), VBR (Variable BitRate), ABR (Average BitRate), CQP (Constant Quantization Parameter), CQ (Constant Quality), VBV (Constrained Bitrate). Per un approfondimento di questi metodi vedere:

https://slhck.info/video/2017/03/01/rate-control.html


Triangolo "Velocità-Qualità-Size"

 


 L'immagine è stata presa da: https://slhck.info/ffmpeg-encoding-course/#/32



Questo triangolo ci fornisce una visualizzazione intuitiva per decidere i valori che ci servono per ottenere il risultato cercato: ci serve maggiormente la qualità? Oppure la taglia del file? In che rapporto con le altre caratteristiche?

Sempre riguardo la qualità ci sono dei preset che, intuitivamente, riguardano la velocità con cui viene effettuato l'encoding:

     -preset ultrafast/superfast/veryfast/faster/fast/medium/slow/slower/veryslow

Per avere maggiori informazioni sui metodi di qualità in ffmpeg, vedere:

https://slhck.info/video/2017/02/24/vbr-settings.html

https://slhck.info/video/2017/02/24/crf-guide.html

 

Script e funzionalità batch:

Se si vuole prendere tutti i video (in una cartella) di un dato formato e convertirli in batch in un altro formato:

    for f in $(find . -name '*.avi'); do ffmpeg -i "$f" -c:v copy -c:a copy "remux/{f%.*}.mkv "; done

oppure:

    for i in $(find . -name '*.avi'); do ffmpeg -fflags +genpts -i "$i" -c:v copy -c:a copy "remux/${i%.*}.mkv"; done

La comunità che ruota attorno a ffmpeg ha creato molti script per le funzionalità e i bisogni più vari. Alcuni repository git con script per ffmpeg sono i seguenti:

https://github.com/NapoleonWils0n/ffmpeg-scripts

https://github.com/leandromoreira/ffmpeg-libav-tutorial


Funzionalità di mapping

Nel video editing può capitare di voler sostituire la traccia audio embedded, in genere di poca qualità, con una traccia audio esterna (di alta qualità). Si usa la funzionalità di mappatura di ffmpeg:

    ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 output.mp4

Con video.mp4 ==> stream 0 e audio.wav ==> stream 1

In questo modo ffmpeg usa la prima traccia video (v:0) del primo "stream 0" (escludendo il resto) e il primo canale audio (a:0) del secondo "stream 1".



Approfondiamo l'uso dell'opzione "-map" perché è un concetto importante in ffmpeg:



    ffmpeg -i stream0.mp4 -i stream1.mp4 -map 0:0 -map 1:3 outoput.mp4

 

    ffmpeg -i stream0.mp4 -i stream1.mp4 -map 0:v:0 -map 1:a:2 output.mp4

Questi due comandi danno esattamente lo stesso risultato, cioè accoppiare il canale video dello stream0 con il terzo canale audio dello stream1

I 2 file di partenza sono così composti:



stream0.mp4 ==>    0:0 --> video; 0:1 --> audio



stream1.mp4 ==>    1:0 --> video; 1:1 --> audio 1; 1:2 --> audio 2;        1:3 --> audio 3

 

La differenza dei 2 comandi è data, nel primo caso, dal metodo "assoluto" di specificare streams e canali. Quindi il canale audio da accoppiare è 1:3
Nel secondo caso si usano gli "streams specifiers" cioè "v" per il video e "a" per l'audio. Quindi -map 0:v:0 significa il primo canale (v:0) del primo stream (0); mentre -map 1:a:2 significa il terzo canale audio (a:2), contato a partire a 0 ma escludendo il canale video, del secondo stream (1).

Insomma, la differenza è che il terzo canale audio dello stream 1 è 3 se contato in maniera assoluta (che assegna 0 al canale video e 1, 2, 3 ai seguenti canali audio); mentre è 2 se contato escludendo il canale video e quindi nominando i tre canali audio 0, 1, 2.



Per approfondire la mappatura, vedere:



https://trac.ffmpeg.org/wiki/Map


Cenni sull'uso dei filtri

 

Il generico comando per usare filtri in ffmpeg è:



    ffmpeg -i <input> -filter:v "<filter1>,<filter2>,<filter3>, ..." <output>

 

Ogni filtro può avere varie opzioni:



    -filter:v <name option>=<option1>=<value1>:<option2>=<value2>: ...

 

è facile ottenere stringhe molto lunghe se si usano tanti filtri e tante opzioni dei filtri, ricordiamoci che possiamo andare a capo per ottenere una scrittura più ordinata e facile da leggere (usando "\" come indicazione di fine riga). Alcuni esempi:

Applicare il filtro audio "loudnorm" prima senza opzioni e poi con due opzioni:



    ffmpeg -i input -filter:a loudnorm  output

 

    ffmpeg -i input -filter:a loudnorm=print_format=summary:linear=true  output

 

È possibile saltare il "<name option>=" indicando solo il valore dell'opzione. Per esempio se applichiamo il filtro "scale" è implicito che le due opzioni rigurdano la larghezza (w) e l'altezza (h) del frame perché questo filtro non ha altre opzioni:



    ffmpeg -i input -vf scale=iw/2:-1 output

 

Notare che si è anche accorciato il comando "-filter:v" in "-vf" (o "-af" per i filtri audio). Si possono avere anche filtraggi complessi, cioè con più input e più filtri che interagiscono fra di loro. Metto solo la pagina del mauale di ffmpeg dove approfondirli:



https://ffmpeg.org/ffmpeg-all.html#Filtergraph-syntax-1


Concatenare spezzoni video con il comando "concat"

    ffmpeg -i "concat:$(echo *.MTS | tr ' ' '|')" out.mp4

 

In questo caso ho concatenato i file .mts (AVCHD) prodotti da un camcorder in un unico video mp4. Ci sono due modi per concatenare i file:



1- Concatenare usando un FileList esterno:

 

creare una image list (mylist.txt, per esempio) dal contenuto:



    file 'path/to/file001.mp4'


    file 'path/to/file002.mp4'


    file 'path/to/file003.mp4'


    ...

 

E poi dare il comando:



    ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.mkv

 

I files saranno uniti senza re-encoding e possiamo usare anche un formato diverso (mkv, per esempio) da quello di partenza (demuxer).



Possiamo automatizzare la creazione di mylist.txt con uno script:



    for f in *.mp4; do echo "file '$f'" >> mylist.txt; done


2- Concatenare nella linea di comando:

    ffmpeg -i "concat:input1.ts|input2.ts|input3.ts" -c copy output.ts

 

è obbligatorio usare lo stesso codec per tutti gli inputNN.xxx; in questo modo non abbiamo re-encoding. Se vogliamo transcodificare oltre che concatenare, dobbiamo passare per un intermedio (quindi con re-encoding) prima di concatenarli fra loro:



    ffmpeg -i input1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts

 
    ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts

 
    ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4

 

questi comandi vanno bene per formati di tipo MPEG. Se abbiamo differenti codec dobbiamo usare "-map" per concatenare i vari media:



    ffmpeg -i input1.mp4 -i input2.webm -i input3.mov -filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0][2:v:0][2:a:0]concat=n=3:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" output.mkv

 


trim

Se vogliamo esguire un trim sul nostro media file, cioè tagliare via i primi secondi e gli ultimi da una clip senza fare re-encoding (ma possiamo cambiare il container, se si vuole):



    ffmpeg -i input.mp4 -ss 00:00:00:02 -to 00:00:01:31 -c:v copy -c:a copy output.mov

 

"-ss" indica il tempo da cui partire (tempo di avvio). Prima di quel tempo tutto viene tagliato via (nel nostro esempio i primi 2 secondi). "-to" indica il tempo in cui si ferma il trim (tempo di fine); i tempi successivi sono tagliati via (nel nostro esempio al minuto e 31 secondi). Quindi avremo un nuovo file (output.mov) che parte dal secondo 2 del file di partenza (input.mp4) e finisce al minuto e trentuno. Questo implica di conoscere la durata esatta del file input.mp4, in modo da poter scegliere i bordi esatti del taglio.


Usando "-t" al posto di "-to" si indca il tempo che deve durare il file, non c'è bisogno di sapere la durata del file di partenza, indichiamo solo la durata che noi vogliamo imporre al file in output, tutto l'eccesso, prima e dopo, viene eliminato. Questo può essere utile in uno script senza sapere la lunghezza precisa del file.
Se il parametro -ss ... è messo prima di "-i input.mp4" allora riguarderà l'input. Se messo dopo allora riguarderà l'output. In questo caso l'operazione è molto più lenta perché sarà effettuata una decodifica.
Notare che il taglio viene bene (senza ricodificare...) solo in corrispondenza dei keyframes; altrimenti effettua un taglio nel fotogramma chiave più vicino ma è costretto a fare un encoding (oltre a perdere di precisione). Per approfondire i problemi di tagli, keyframes e di seeking vedere:

https://trac.ffmpeg.org/wiki/Seeking


Cenni sull'accelerazione hardware (GPU)

1- Decodifica

ffmpeg ha i decodificatori interni per l'accelerazione hardware che si chiamano "hwaccel". Questi sono abilitati tramite l'opzione "-hwaccel". Se il decodificatore rileva uno stream con codec o profilo supportato allora userà l'accelerazione hardware automaticamente. Se lo stream non è adatto allora avvierà la normale decodificazione software. Se occorre specificare il dispositivo hardware (per es. quando abbiamo più di una GPU) lo possiamo fare tramite l'opzione "-hwaccel-device". Per es. io ho un unica scheda video AMD RX 5700XT che viene riconosciuta da Linux come "pci-0000:0c:00.0" oppure, come si vede nella cartella "/dev/dri" come "card1" o "renderD128". Quindi posso usare l'opzione:



    -hwaccel-device renderD128

 

Come detto il tutto avviene automaticamente, se è possibile. Possiamo anche usare un decoder esterno tramite wrapper; in questo caso dobbiamo far sapere il tipo di codec che stiamo usando tramite l'opzione "-c:v av1_cuvid" (per Nvidia), "h264_qsv" (per Intel) o altro. Ricordiamoci che stiamo parlando di decoder, non di encoder. Possiamo conoscere tutti i decoders supportati (sia hardware che software) tramite il comando:



    ffmpeg -decoders

 

Si ottengono centinaia di decoder perché ffmpeg riesce a leggere quasi tutti i codec esistenti. Notare però che ci sono codec apposta per Nvidia (cuvid) e Intel (qsv), ma non per AMD. Quindi con una scheda AMD non potremo avere decodifica hardware se non con specifiche API che vedremo fra poco. Altro aspetto importante nell'usare un wrapper esterno è che dobbiamo conoscere in anticipo il codec da decodificare e impostarlo manualmente. Non può succedere che il codec venga riconosciuto e accelerato da un wrapper automaticamente.

Infine possiamo usare i filtri. Tenere conto però che la maggior parte dei filtri dovranno essere racchiusi tra le opzioni "-hwupload" e "-hwdownload". hwupload serve a spostare i dati dei frame dalla normale memoria (non accessibile dai filtri) a quella del device connesso (si parla di "superfici hardware"). Poi, dopo l'azione del filtro, con hwdownload i dati sono nuovamente portati dalla superfice hardware alla normale memoria.

Al di là del funzionamento automatico dell'accelerazione hardware in ffmpeg, possiamo usare delle specifiche API che sono entrate nell'uso comune: Vdpau; Vaapi; Vulkan; Cuda (nvenc/nvdec); ecc.

VDPAU: si installa la libreria "libvdpau" che è proprietaria Nvidia e poi si può usare l'accelerazione hardware in ffmpeg tramite la sua libreria interna "libavcodec". È possibile decodificare in hardware h264/5; mpeg 1/2/4; VC-1; VP9 e AV1.



VAAPI: essendo una libreria Open Source è integrata in ffmpeg nella libreria "libva". Questa libreria userà i profili (se specificati) QSV per schede Intel e UVD/VCE per schede AMD. Non è compatibile con Nvidia. I codec supportati sono gli stessi di vdpau.



VULKAN: è in pieno sviluppo e supporta i codec h264; Hevc e AV1 con tutte le marche di schede video. Per usarlo bisogna specificarlo (non è automatico) inizializzando il device hardware tramite l'opzione "-init_hw_device". Si può fare un test per verificarne il funzionamento:



    ffmpeg -init_hw_device "vulkan=vk:0" -hwaccel vulkan -hwaccel_output_format vulkan -i input.mp4 -f null - -benchmark

 

Questo test non produce un risultato, ma verifica solo il funzionamento. Notare che in questo caso dobbiamo specificare "-hwaccel vulkan" invece del solo "-hwaccel". Per approfondimenti (sempre in divenire) vedere:



https://ffmpeg.org/ffmpeg-filters.html#Vulkan-Video-Filters

 

OPENCL: può essere supportato direttamente dai driver delle schede video. Per AMD si può usare ROCm; i driver specifici AMD (open o proprietari) o usare il framework AMF. Il tutto è in fase di rivoluzione e senza molta stabilità.



CUDA (nvenc/nvdec): è una soluzione proprietaria e richiede l'installazione dell'SDK di Cuda. Un esempio di uso (in cui è presente anche un codifica software):



    ffmpeg -hwaccel nvdec -c:v av1 -i input_av1.mp4 output.ts

 

AMF/UVD/VCE: La decodifica in hardware di AMD è problematica e in divenire. UVD (per schede fino a Polaris/Vega) funziona con vdpau e vaapi. VCE (per schede Navi e successive) funziona con vaapi. AMF non funziona (in Linux) per la decodifica ma solo per la codifica. Un esempio di decodifica software e codifica hardware potrebbe essere:



    ffmpeg -i input.mp4 -c:v av1_amf output.mkv


2- Codifica

L'uso di encoder hardware è molto più semplice e immediato, perché funziona senza opzioni particolari: basta usare i relativi profili. L'unico passo da fare è installare le relative API o frameworks. Alcuni esempi:



    ffmpeg -i input -c:v h264_nvenc -profile high444p -pixel_format yuv444p -preset default output.mp4

 

    ffmpeg -s 1920x1080 -pix_fmt yuv420p -i input.yuv -c:v av1_amf output.mp4

 

    ffmpeg -i input.mkv -vf scale=1280x720 -c:v h264_amf output.mp4

 

Gli encoders (hardware e software) utilizzabili da ffmpeg si trovano con il comando:



    ffmpeg -encoders

 

Si vede che sono molti, ma non quanto i decoders.


Siamo arrivati alla conclusione. Ecco un link che riassume alcuni dei comandi più utili di ffmpeg:



https://gist.github.com/steven2358/ba153c642fe2bb1e47485962df07c730


Leggi il contenuto originale su Marco's Box

Written by: Andrea Paz