Retour au blog
13 août 2026Sergei Solod14 min de lecture

Comment j’ai réduit une vidéo de production d’environ 280 Mo à environ 50 Mo en H.264

Une vidéo réelle du système en production est passée d’environ 280 Mo à environ 50 Mo après la refonte de la configuration H.264 autour de CRF 28, x264 veryslow, d’un plafond de résolution de classe 720p, de fréquences d’images utiles et d’exigences modérées pour le décodeur. Une étape antérieure avait déjà réduit le même exemple d’environ 350 à 238 Mo, et un audit de l’ancienne bibliothèque a montré que des H.264 à plusieurs mégabits par seconde étaient courants.

H.264FFmpegx264Compression vidéoPerformances des sitesOptimisation multimédia

Le nombre qui a rendu cette optimisation vraiment concrète est simple : une vidéo réelle de production est passée d’environ 280 Mo à 50 Mo après l’adoption de la nouvelle politique H.264. Cela représente environ 5,6× moins, soit près de 230 Mo ou 82% de taille économisée.

Je n’ai pas obtenu ce résultat en passant la diffusion à AV1, HEVC ou VP9. La sortie est restée du H.264 dans un MP4. Ce qui a changé, c’est la politique autour du codec : moins de pixels inutiles, moins d’échantillons temporels superflus, un objectif de qualité beaucoup moins conservateur, davantage de calcul au moment de l’encodage unique et un contrat de décodage volontairement borné.

La charge était très spécifique : de courts séquences illustrés et animés, environ 80% du trafic sur appareils mobiles, la bande passante comme coût récurrent et un encodage hors ligne où le temps CPU coûte peu par rapport au fait de servir des fichiers trop gros encore et encore.

En résumé, l’ancienne et la nouvelle politique ressemblaient à ceci :

ancienne configuration
H.264 Main @ Level 4.0
CRF 19
préréglage slow
jusqu’à 1920x1080 / 1080x1920
30 fps
refs = 3
images B = 3
GOP ≈ 2 secondes
VBV ≈ 10M / 20M

nouvelle configuration
H.264 Main @ Level 3.1
CRF 28
préréglage veryslow
plafond de classe 720p, sans agrandissement
CFR utile, normalement <= 30 fps
refs = 4
images B = 5
GOP ≈ 5 secondes
VBV ≈ 4M / 8M
yuv420p / avc1 / faststart

Le résultat mesuré : environ 280 Mo sont devenus environ 50 Mo

J’ai plusieurs mesures réelles de production, mais elles ne correspondent pas toutes à la même expérience. Les séparer correctement est plus important que de choisir le pourcentage le plus spectaculaire.

MesureAvantAprèsRéduction
Même vidéo concrète~280 Mo~50 Mo~5,6× plus petit / ~82% de moins
Étape antérieure du même fichier~350 Mo~238 Mo~1,47× plus petit / 32% de moins

La première ligne est la preuve la plus propre du titre : la même vidéo concrète avant et après la nouvelle politique, environ 280 Mo contre 50 Mo. Pour cette paire exacte, les anciens journaux récupérés ne contiennent plus de ligne conservée indiquant la durée et le débit binaire ; je n’en invente donc pas. Le changement de taille suffit à établir un facteur d’environ 5,6.

Le cas ~350→238 Mo correspond à une étape d’optimisation plus ancienne du même exemple de cette période. La sortie était d’environ 1264×720, 30 fps, ~500 secondes, sans son et proche de 3,8 Mbit/s. Le calcul correspond : 3,8 Mbit/s pendant ~500 secondes donne environ 238 Mo. C’était déjà 32% de moins que ~350 Mo, mais toujours bien trop lourd pour mon objectif de bande passante.

L’ancien corpus montrait aussi que les gros H.264 n’étaient pas un cas isolé. Un audit contenait 238 vidéos de production totalisant 6,37 Go : 117 H.264 et 121 AV1. 101 fichiers faisaient au moins 20 Mo et 34 au moins 50 Mo. Quelques gros H.264 ressemblaient à ceci :

TailleDuréeDébit moyen
121,0 Mo4:253,83 Mbit/s
101,2 Mo5:192,659 Mbit/s
92,78 Mo4:442,735 Mbit/s
90,10 Mo3:553,206 Mbit/s
89,74 Mo4:412,676 Mbit/s

Il s’agit de contenus différents : ce tableau donne donc du contexte, pas un test A/B. Il montre néanmoins que l’ancien exemple à 3,8 Mbit/s n’était pas un cas isolé : plusieurs gros fichiers H.264 de l’ancienne bibliothèque se situaient réellement autour de 2,6 à 3,8 Mbit/s.

L’optimisation la plus importante n’était pas une option FFmpeg

Le changement décisif concernait ma manière de penser le coût.

L’encodage n’a lieu qu’une fois. Le transfert du fichier, lui, se répète à chaque lecture.

Pour de la vidéo temps réel, dépenser beaucoup plus de CPU afin d’économiser un peu de débit binaire peut être un mauvais compromis. Mes fichiers sont encodés à l’avance puis servis encore et encore. Dans ce modèle, gagner dix minutes à l’encodage peut ne presque rien valoir si l’encodage plus rapide grossit chaque requête future.

C’est pour cela que -preset veryslow est rationnel dans mon cas. Je suis prêt à dépenser du CPU une fois si x264 peut s’en servir pour trouver une représentation plus efficace. Le navigateur ne répète jamais la recherche coûteuse de l’encodeur ; il ne fait que décoder le flux binaire final.

La règle est devenue simple : dépenser du calcul à l’étape unique et économiser agressivement les octets à l’étape répétée.

Pourquoi je suis resté sur H.264 au lieu de courir après le codec le plus récent

Je ne prétends pas que H.264 offre la meilleure compression disponible. Ce n’est pas le cas. Les codecs plus récents sont très intéressants quand l’architecture de distribution peut conserver plusieurs versions et choisir la meilleure pour chaque appareil.

Ma contrainte était différente : une URL, un fichier, un codec et le moins de surprises de lecture possible pour une audience très mobile.

Pour cet usage, H.264 dans MP4 reste une base très sûre. Apple recommande actuellement aux développeurs de sites d’utiliser des fichiers MP4 encodés en H.264 pour la vidéo statique dans Safari. La documentation Android actuelle répertorie H.264 dans MP4 et impose un décodeur profil Main à partir d’Android 6.0 ; ses recommandations de lecture H.264 mentionnent aussi 1280×720 à 30 fps pour la HD, tout en précisant que la HD n’est pas disponible sur tous les appareils. Voir les formats multimédias pris en charge par Android.

Cela ne signifie pas que les appareils modernes sont limités à profil Main ou niveau 3.1. Apple, par exemple, préfère généralement le profil High au Main ou au Baseline pour HLS. J’ai choisi Main@3.1 parce que je voulais une enveloppe de décodage volontairement modeste pour un seul MP4 statique, pas parce qu’Apple l’impose.

J’ai arrêté d’encoder des pixels qui n’avaient pas besoin d’exister

La résolution était l’un des leviers les plus importants. Mon plafond est devenu environ 1280×720 en paysage, 720×1280 en portrait et près de 960×960 pour du contenu carré ou d’orientation mixte.

La règle la plus importante est : ne jamais agrandir une source uniquement pour atteindre ce plafond.

Si une source fait 900×600, la passer à 1280×720 ne récupère aucun détail. Elle crée seulement davantage d’échantillons à décrire par l’encodeur. Une source 1920×1080 peut être réduite vers la classe 720p ; une source 900×600 peut rester proche de 900×600. Le plafond est un maximum, pas une cible.

Cela paraît simple, mais supprimer des pixels inutiles peut économiser davantage que beaucoup de réglages obscurs de l’encodeur.

Ce plafond n’est pas un chiffre rond choisi au hasard. Une image 1920×1080 contient 2 073 600 pixels, contre 921 600 pour 1280×720. Passer de 1080p à 720p supprime donc environ 55,6% des échantillons spatiaux avant même que l’encodeur ne prenne ses décisions de compression.

J’ai aussi envisagé 540p comme valeur générale. Mais 960×540 ne contient que 518 400 pixels, soit 43,75% de moins que 1280×720 et seulement 56,25% des échantillons de 720p. Sur du contenu illustré, ces échantillons décrivent les traits fins, les yeux, les cheveux, les doigts, les visages et les contours nets. Si je dois encore réduire la taille, je préfère tester un CRF légèrement plus élevé avant de supprimer aveuglément 43,75% d’information spatiale supplémentaire. La quantification peut être modifiée lors d’un nouvel encodage; le détail supprimé par la réduction de résolution est déjà perdu.

La classe 720p est donc mon plafond général prudent, pas une affirmation selon laquelle 540p serait mauvais. Une version 540p mesurée peut être meilleure pour un fichier précis. Je refuse simplement de rendre cette perte spatiale irréversible universelle sans données.

J’ai arrêté de payer pour des images que la source n’avait pas réellement

La fréquence d’images est un autre multiplicateur. Si une animation contient environ 16 états visuels réellement utiles par seconde, la stocker à 30 ou 60 fps n’améliore pas automatiquement le mouvement. On peut surtout ajouter des échantillons temporels répétés ou synthétisés qui doivent malgré tout être codés.

Ma politique consiste à conserver la cadence utile de la source et à rester normalement à 30 fps ou moins. Pour ce type de contenu, 12, 15, 16, 18, 20, 24, 25 ou 30 fps peuvent tous être raisonnables lorsqu’ils décrivent réellement la source.

Je préfère également un CFR propre dans la sortie générée. Le VFR n’est pas intrinsèquement cassé ; le CFR simplifie simplement les horodatages, le nombre d’images, les contrôles de durée, la recherche de position et la validation ultérieure dans ma chaîne de traitement.

Le principe général compte plus qu’un chiffre précis : ne pas payer de bande passante pour une information temporelle absente de la source.

CRF 28 est un choix de cas d’usage, pas un nombre magique

Je ne voulais pas forcer tous les séquences vers le même débit binaire cible. Une illustration presque statique et une scène avec un mouvement complexe n’ont pas besoin du même nombre de bits pour rester acceptables.

J’utilise donc le mode CRF de x264 et, pour ce cas d’usage illustré orienté bande passante, je me suis arrêté autour de -crf 28. FFmpeg documente CRF dans libx264 comme un contrôle de débit à qualité constante ; voir la documentation des codecs FFmpeg.

CRF 28 est volontairement agressif. Je ne copierais pas cette valeur aveuglément sur du grain cinéma, une vidéo caméra bruitée, du texte minuscule dans une capture d’écran ou un cas d’usage où la fidélité compte davantage que la bande passante.

Je n’ai pas non plus de mesure perceptuelle universelle prouvant que CRF 28 est transparent. Ce que je peux dire de mon contenu est plus limité : les fichiers sont devenus nettement plus petits tout en continuant à paraître normaux en lecture courante. C’est une observation pratique, pas une affirmation de transparence visuelle.

Veryslow coûte cher à l’encodeur, pas automatiquement au décodeur

Mon préréglage est -preset veryslow. Un préréglage plus lent donne à x264 davantage de possibilités pour chercher des décisions de prédiction et de codage efficaces. Le prix est le temps et le CPU d’encodage.

La distinction essentielle est que l’effort d’encodage et la complexité de décodage ne sont pas la même chose.

Je peux laisser x264 travailler très longtemps tout en limitant séparément le flux final. Mon contrat de sortie conservateur est :

H.264, profil Main
Level 3.1
yuv420p 8 bits
avc1
refs = 4
images B = 5
B-pyramid = normal
GOP ouvert = désactivé

FFmpeg expose séparément CRF, préréglages, réglage adapté, restrictions de profil, images de référence et images B. C’est exactement ainsi que je les traite : laisser l’encodeur chercher de manière coûteuse, tout en gardant le côté lecture banal.

GOP et VBV sont des garde-fous, pas le contrôle principal de qualité

Pour ces courts séquences progressifs, j’utilise un GOP maximal d’environ cinq secondes : près de -g 150 à 30 fps, -g 120 à 24 fps ou -g 80 à 16 fps.

C’est un choix de cas d’usage, pas une règle universelle. Le diffusion adaptative a d’autres contraintes ; Apple recommande par exemple des IDR toutes les deux secondes pour HLS. Je ne transpose pas automatiquement cette règle HLS à de courts fichiers MP4 statiques et progressifs.

J’utilise aussi environ :

-maxrate:v 4M
-bufsize:v 8M

Ces valeurs sont des plafonds contre les pics de débit binaire inhabituels. Elles ne signifient pas « tout encoder à 4 Mbit/s ». CRF continue de gérer l’allocation normale des bits, ce qui permet aux séquences faciles de devenir très petits.

J’ai aussi gardé le conteneur MP4 volontairement conventionnel

J’utilise explicitement avc1. La documentation HLS actuelle d’Apple recommande des formats d’échantillons comme avc1 plutôt que avc3. Ce n’est pas ce qui a réduit mes fichiers, mais cela correspond à l’objectif de produire un H.264 dans MP4 de façon conventionnelle.

J’utilise également -movflags +faststart. La documentation des formats FFmpeg indique que faststart déplace l’index MP4 moov au début du fichier. Les exigences Android pour le diffusion par HTTP indiquent également que, dans MPEG-4, moov doit précéder mdat après ftyp.

ftyp
moov
mdat

Faststart n’améliore pas la compression. Il rend la lecture HTTP progressive moins problématique.

Pour du SDR classique, j’utilise yuv420p 8 bits et je signale BT.709 en plage vidéo limitée. Si un séquence n’a pas besoin de son, je ne fabrique pas de piste son. Pour le contenu illustré, j’utilise aussi -tune animation ; je considère ce choix comme spécifique au type de contenu, pas comme une partie du contrat universel de compatibilité.

Le profil FFmpeg central

Pour une source illustrée à 30 fps, le cœur de la commande ressemble approximativement à ceci :

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

La mise à l’échelle et la fréquence d’images ne sont volontairement pas codées en dur ici. Une source 900×600 ne doit pas être agrandie simplement parce que le plafond est 1280×720, et une animation naturellement lente ne doit pas être forcée à 30 fps uniquement parce que l’exemple utilise -g 150.

La commande implémente la politique ; elle n’est pas la politique elle-même.

Pourquoi les fichiers sont devenus plusieurs fois plus petits

Il n’y avait pas d’option miracle.

La réduction est venue de plusieurs décisions qui supprimaient chacune une forme de gaspillage : pixels inutiles, images inutiles, objectif de qualité trop conservateur, réglages d’encodeur privilégiant la vitesse plutôt que l’efficacité, images clés trop fréquentes et flux dont je n’avais pas besoin.

C’est pourquoi « ce fichier est en H.264 » dit étonnamment peu sur sa taille. Deux encodages H.264 de la même source peuvent être très différents parce que le nom du codec ne décrit ni la résolution, ni le FPS, ni le contrôle de débit, ni le préréglage, ni le GOP, ni le profil, ni la préparation de la source.

Dans mon cas, ces décisions autour du codec ont compté davantage que le remplacement du codec lui-même.

Ce que ce résultat ne prouve pas

Je n’ai pas isolé chaque réglage dans une expérience contrôlée, donc je ne peux pas attribuer honnêtement un pourcentage exact de l’économie à veryslow, CRF 28, la réduction de résolution ou la réduction du FPS séparément.

Je ne peux pas non plus affirmer que toute sortie CRF 28 est perceptuellement transparente. « Pas de perte de qualité évidente » est mon observation pour ce cas d’usage illustré aux tailles d’affichage normales, pas une garantie scientifique pour n’importe quelle vidéo.

Enfin, je ne soutiens pas qu’un unique fichier H.264 soit la bonne architecture pour tous les sites. Plusieurs versions, le diffusion adaptative, le HDR, la 4K et la négociation de codec changent les compromis.

Ce que je peux réellement affirmer est plus limité : pour une bibliothèque de courts séquences illustrés et animés où la bande passante est prioritaire, le public est majoritairement mobile, le temps d’encodage coûte peu et une lecture prévisible compte, ce profil a réduit mes fichiers de plusieurs fois tout en conservant un rendu normal en lecture courante.

Comment ce résultat a changé ma façon d’optimiser

Avant, je voyais surtout l’optimisation vidéo comme un problème de réglages d’encodeur. Maintenant, je la vois comme un problème de coût sur toute la durée de vie du fichier.

L’encodeur peut s’exécuter une seule fois. Les octets peuvent traverser le réseau des milliers ou des millions de fois.

Cela change ce que signifie « cher ».

Je suis prêt à dépenser du CPU une fois. Je le suis beaucoup moins à envoyer à chaque future requête des pixels créés par agrandissement, des images qui n’ajoutent aucun mouvement utile ou un débit binaire dont le contenu n’a pas besoin.

Le codec est resté banal : H.264 dans MP4. L’optimisation s’est faite autour.

Pour mon cas d’usage, la leçon est plus utile que n’importe quelle option FFmpeg : optimiser le coût que l’on paie à répétition, pas celui que l’on paie une seule fois.