web-dev-qa-db-fra.com

Quelle est la bonne façon de réparer les images clés dans FFmpeg pour DASH?

Lors du conditionnement d'un flux pour la lecture DASH, les points d'accès aléatoires doivent être exactement au même temps de flux source dans tous les flux. La manière habituelle de faire cela est de forcer une cadence d'images fixe et une longueur de GOP fixe (c'est-à-dire une image clé toutes les N images).

Dans FFmpeg, la cadence fixe est facile (-r NUMBER).

Mais pour les emplacements d’images clés fixes (longueur du groupe d’opérations), il existe trois méthodes ... laquelle est "correcte"? La documentation de FFmpeg est frustrante et vague à ce sujet.

Méthode 1: jouer avec les arguments de libx264

-c:v libx264 -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE:scenecut=-1

Il semble y avoir un débat quant à l'activation ou la désactivation du scénario, car il est difficile de savoir si le "compteur" des images clés est redémarré lorsqu'une scène est coupée.

Méthode 2: définition d'une taille de groupe d'images fixe:

-g GOP_LEN_IN_FRAMES

Ceci n'est malheureusement documenté que de manière passagère dans la documentation FFMPEG, et l'effet de cet argument est donc très flou.

Méthode 3: insérez une image clé toutes les N secondes (Peut-être?):

-force_key_frames expr:gte(t,n_forced*GOP_LEN_IN_SECONDS)

Cette est explicitement documentée. Mais il n'est toujours pas clair si le "compteur de temps" redémarre après chaque image clé. Par exemple, dans un GOP attendu de 5 secondes, s'il existe une image clé scenecut injectée 3 secondes dans libx264, l'image clé suivante sera-t-elle 5 secondes plus tard ou 2 secondes plus tard?

En fait, la documentation de FFmpeg fait la distinction entre cette option et l'option -g, mais elle ne dit pas vraiment en quoi ces deux options ci-dessus diffèrent le moins (évidemment, -g nécessitera une fréquence d'images fixe).

Qui a raison?

Il semblerait que le -force_key_frames serait supérieur , car il ne nécessiterait pas de fréquence d'images fixe. Cependant, cela nécessite que

  • il est conforme aux spécifications GOP en H.264 (le cas échéant)
  • il GARANTIT qu'il y aurait une image clé à cadence fixe, indépendamment des images clés libx264 scenecut.

Il semblerait également que -g ne puisse fonctionner sans imposer une cadence fixe (-r) , car rien ne garantit que plusieurs exécutions de ffmpeg avec des arguments de codec différents fourniraient la même cadence instantanée dans chaque résolution. Des fréquences d'images fixes peuvent réduire les performances de compression (IMPORTANT dans un scénario DASH!).

Enfin, la méthode keyint apparaît simplement comme un hack . J'espère contre espoir que ce n'est pas la bonne réponse.

Références:

Un exemple utilisant la méthode -force_key_frames

Un exemple utilisant la méthode keyint

Section des options vidéo avancées de FFmpeg

32
Mark Gerolimatos

La réponse semble donc être:

  • La méthode 1 est vérifiée pour fonctionner, mais elle est spécifique à libx264 et entraîne le coût de l'élimination de l'option très utile scenecut dans libx264.
  • La méthode 3 fonctionne à partir de la version FFMPEG d'avril 2015, mais vous devez vérifier vos résultats avec le script inclus au bas de cet article, car la documentation de FFMPEG ne permet pas de déterminer l'effet de l'option. Si cela fonctionne, c'est le supérieur des deux options.
  • NE PAS UTILISER Méthode 2, -g semble être obsolète. Il ne semble pas fonctionner, ni défini explicitement dans la documentation, ni trouvé dans l'aide, ni utilisé dans le code. L'inspection du code montre que l'option -g est probablement destinée aux flux MPEG-2 (il existe même des strophes de code faisant référence à PAL et NTSC!).

Également:

  • Les fichiers générés avec la méthode 3 peuvent être légèrement plus grands que la méthode 1, car les images interstitielles I (images clés) sont autorisées.
  • Vous devez explicitement définir le drapeau "-r" dans les deux cas, même si la méthode 3 place une trame I au prochain emplacement de trame ou après l'heure spécifiée. Si vous ne définissez pas l'indicateur "-r", vous êtes à la merci du fichier source, éventuellement avec un taux de trame variable. Cela pourrait entraîner des transitions DASH incompatibles.
  • Malgré les avertissements de la documentation FFMPEG, la méthode 3 estPASmoins efficace que d’autres. En fait, les tests montrent qu’elle pourrait être légèrement PLUS efficace que la méthode 1.

Script pour l'option -force_key_frames

Voici un court programme Perl que j'ai utilisé pour vérifier la cadence de l'image I en fonction du résultat de la suggestion ffprobe de slhck. Il semble vérifier que la méthode -force_key_frames fonctionnera également, et présente l’avantage supplémentaire de permettre des images scenecut. Je n'ai absolument aucune idée de la façon dont FFMPEG rend ce travail efficace, ni de la chance que j'ai eu parce que mes flux sont bien conditionnés.

Dans mon cas, j'ai encodé à 30 images par seconde avec une taille de GOP attendue de 6 secondes, ou 180 images. J'ai utilisé 180 comme argument gopsize pour ce programme qui vérifiait une trame I à chaque multiple de 180, mais sa valeur à 181 (ou à tout autre nombre non multiple de 180) l'a fait se plaindre.

#!/usr/bin/Perl
use strict;
my $gopsize = shift(@ARGV);
my $file = shift(@ARGV);
print "GOPSIZE = $gopsize\n";
my $linenum = 0;
my $expected = 0;
open my $pipe, "ffprobe -i $file -select_streams v -show_frames -of csv -show_entries frame=pict_type |"
        or die "Blah";
while (<$pipe>) {
  if ($linenum > $expected) {
    # Won't catch all the misses. But even one is good enough to fail.
    print "Missed IFrame at $expected\n";
    $expected = (int($linenum/$gopsize) + 1)*$gopsize;
  }
  if (m/,I\s*$/) {
    if ($linenum < $expected) {
      # Don't care term, just an extra I frame. Snore.
      #print "Free IFrame at $linenum\n";
    } else {
      #print "IFrame HIT at $expected\n";
      $expected += $gopsize;
    }
  }
  $linenum += 1;
}
6

TL; DR

Je recommanderais ce qui suit:

  • libx264: -g X -keyint_min X (et éventuellement ajouter -force_key_frames "expr:gte(t,n_forced*N)")
  • libx265: -x265-params "keyint=X:min-keyint=X"
  • libvpx-vp9: -g X

X est l'intervalle en images et N est l'intervalle en secondes. Par exemple, pour un intervalle de 2 secondes avec une vidéo à 30 images par seconde, X = 60 et N = 2.

Une note sur les différents types de cadres

Pour bien expliquer ce sujet, nous devons d’abord définir les deux types d’images I/images clés:

  • Cadres de rafraîchissement instantané du décodeur (IDR): ils permettent le décodage indépendant des trames suivantes, sans accéder aux trames antérieures à la trame IDR.
  • Images non IDR: elles nécessitent une image IDR antérieure pour que le décodage fonctionne. Les cadres non-IDR peuvent être utilisés pour les découpes de scènes au milieu d'un groupe d'images.

Qu'est-ce qui est recommandé pour le streaming?

Pour le cas de streaming, vous voulez:

  • Assurez-vous que toutes les images IDR sont à des positions normales (par exemple, à 2, 4, 6,… secondes) de sorte que la vidéo puisse être divisée en segments de même longueur.
  • Activer la détection de coupure de scène, afin d'améliorer l'efficacité/la qualité du codage. Cela signifie qu'il est possible de placer les images I entre les images IDR. Vous pouvez toujours travailler avec la détection de coupure de scène désactivée (et cela fait toujours partie de nombreux guides), mais ce n'est pas nécessaire.

Que font les paramètres?

Afin de configurer l'encodeur, nous devons comprendre le rôle des paramètres d'image clé. J'ai fait quelques tests et découvert ce qui suit pour les trois encodeurs libx264, libx265 et libvpx-vp9 dans FFmpeg:

  • libx264:

    • -g définit l'intervalle d'images clés.
    • -keyint_min définit l'intervalle minimum des images clés.
    • -x264-params "keyint=x:min-keyint=y" est identique à -g x -keyint_min y.
    • Remarque: Lorsque vous définissez la même valeur, le minimum est défini en interne sur half l'intervalle maximum plus un, comme indiqué dans le code x264:

      h->param.i_keyint_min = x264_clip3( h->param.i_keyint_min, 1, h->param.i_keyint_max/2+1 );
      
  • libx265:

    • -g n'est pas implémenté.
    • -x265-params "keyint=x:min-keyint=y" fonctionne.
  • libvpx-vp9:

    • -g définit l'intervalle d'images clés.
    • -keyint_min définit l'intervalle minimum d'images clés
    • Remarque: En raison du fonctionnement de FFmpeg, -keyint_min n'est transmis à l'encodeur que s'il est identique à -g. Dans le code de libvpxenc.c dans FFmpeg, nous pouvons trouver:

      if (avctx->keyint_min >= 0 && avctx->keyint_min == avctx->gop_size)
          enccfg.kf_min_dist = avctx->keyint_min;
      if (avctx->gop_size >= 0)
          enccfg.kf_max_dist = avctx->gop_size;
      

      Cela pourrait être un bogue (ou un manque de fonctionnalité?), Puisque libvpx supporte définitivement la définition d'une valeur différente pour kf_min_dist.

Devez-vous utiliser -force_key_frames?

L'option -force_key_frames insère de force les images clés à l'intervalle donné (expression). Cela fonctionne pour tous les encodeurs, mais cela pourrait déranger le mécanisme de contrôle du débit. Surtout pour VP9, ​​j'ai remarqué de graves variations de qualité, je ne peux donc pas recommander son utilisation dans ce cas.

23
slhck

Voici mes cinquante cents pour le cas.

Méthode 1:

jouer avec les arguments de libx264

-c: v libx264 -x264opts keyint = GOPSIZE: min-keyint = GOPSIZE: scenecut = -1

Générez des iframes uniquement aux intervalles souhaités.

Exemple 1:

ffmpeg -i test.mp4 -codec:v libx264 \
-r 23.976 \
-x264opts "keyint=48:min-keyint=48:no-scenecut" \
-c:a copy \
-y test_keyint_48.mp4

Générez les iframes comme prévu de la manière suivante:

Iframes     Seconds
1           0
49          2
97          4
145         6
193         8
241         10
289         12
337         14
385         16
433         18
481         20
529         22
577         24
625         26
673         28
721         30
769         32
817         34
865         36
913         38
961         40
1009        42
1057        44
1105        46
1153        48
1201        50
1249        52
1297        54
1345        56
1393        58

La méthode 2 est amortie. Ommitted.

Méthode 3:

insérez une image clé toutes les N secondes (PEUT-ÊTRE):

-force_key_frames expr: gte (t, n_forced * GOP_LEN_IN_SECONDS)

Exemple 2

ffmpeg -i test.mp4 -codec:v libx264 \
-r 23.976 \
-force_key_frames "expr:gte(t,n_forced*2)"
-c:a copy \
-y test_fkf_2.mp4

Générez un iframes d'une manière légèrement différente:

Iframes     Seconds
1           0
49          2
97          4
145         6
193         8
241         10
289         12
337         14
385         16
433         18
481         20
519         21.58333333
529         22
577         24
625         26
673         28
721         30
769         32
817         34
865         36
913         38
931         38.75
941         39.16666667
961         40
1008        42
1056        44
1104        46
1152        48
1200        50
1248        52
1296        54
1305        54.375
1344        56
1367        56.95833333
1392        58
1430        59.58333333
1440        60
1475        61.45833333
1488        62
1536        64
1544        64.33333333
1584        66
1591        66.29166667
1632        68
1680        70
1728        72
1765        73.54166667
1776        74
1811        75.45833333
1824        75.95833333
1853        77.16666667
1872        77.95833333
1896        78.95833333
1920        79.95833333
1939        80.75
1968        81.95833333

Comme vous pouvez le voir, il place les iframes toutes les 2 secondes ET sur le scénario (secondes avec partie flottante), ce qui est important pour la complexité du flux vidéo à mon avis.

La taille des fichiers générés est à peu près la même. Très étrange que même avec plus d'images clés dans Méthode 3 , il génère parfois moins de fichiers que l'algorithme de bibliothèque x264 standard.

Pour générer plusieurs fichiers à débit binaire pour le flux HLS, nous avons choisi la troisième méthode. Il est parfaitement aligné avec 2 secondes entre les morceaux, ils ont iframe au début de chaque morceau et ils ont des iframes supplémentaires sur les scènes complexes qui offrent une meilleure expérience pour les utilisateurs qui ont des périphériques obsolètes et ne peuvent pas lire les profils élevés x264.

J'espère que ça aide quelqu'un.

11
Ara Saahov

Je voulais ajouter quelques informations ici, car mon travail sur Google a fait avancer la discussion dans ma quête pour trouver des informations sur la tentative de segmentation de mon codage DASH comme je le voulais, et aucune des informations que j'ai trouvées n'était totalement correcte.

D'abord, plusieurs idées fausses à éliminer:

  1. Toutes les images I ne sont pas identiques. Il y a de grands cadres en "I" et de petits cadres en "i". Ou pour utiliser la terminologie correcte, IDR I-Frames et non-IDR I-Frames. Les images I IDR (parfois appelées "images clés") créeront un nouveau groupe d'images. Les cadres non-IDR ne le seront pas. Ils sont pratiques pour avoir dans un GOP où il y a un changement de scène.

  2. -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE ← Cela ne fait pas ce que vous pensez. Cela m'a pris un peu de temps pour comprendre. Il s'avère que le min-keyint est limité dans le code. Il n'est pas autorisé à être supérieur à (keyint / 2) + 1. Donc, si vous affectez la même valeur à ces deux variables, la valeur de min-keyint sera réduite de moitié lors du codage.

Voici la chose: la scène est vraiment géniale, surtout dans les vidéos qui ont des coupes rapides et dures. Cela le maintient agréable et net, donc je ne veux pas le désactiver, mais en même temps, je ne pouvais pas obtenir une taille de GOP fixe tant qu'elle était activée. Je voulais activer la découpe de scènes, mais ne lui faire utiliser que des images I non IDR. Mais ça ne marchait pas. Jusqu’à ce que j’ai compris (à partir de beaucoup de lectures) au sujet de l’idée fausse n ° 2.

Il s'est avéré que je devais configurer keyint pour doubler la taille de GOP souhaitée. Cela signifie que min-keyint peut être réglé sur la taille de GOP souhaitée (sans que le code interne ne le coupe en deux), ce qui empêche la détection de découpes de scène d'utiliser des images IDR I dans la taille du GOP car le nombre d'images depuis le dernier ID I IDR est toujours inférieur à min-keyinit.

Enfin, la définition de l'option force_key_frame remplace la taille double keyint. Alors, voici ce qui fonctionne:

Je préfère les segments en tranches de 2 secondes, donc mon GOPSIZE = Framerate * 2

ffmpeg <other_options> -force_key_frames "expr:eq(mod(n,<GOPSIZE>),0)" -x264opts rc-lookahead=<GOPSIZE>:keyint=<GOPSIZE * 2>:min-keyint=<GOPSIZE> <other_options>

Vous pouvez vérifier en utilisant ffprobe:

ffprobe <SRC_FLE> -select_streams v -show_frames -of csv -show_entries frame=coded_picture_number,key_frame,pict_type > frames.csv

Dans le fichier CSV généré, chaque ligne vous indiquera: frame, [is_an_IDR_?], [frame_type], [frame_number]:

frame,1,I,60  <-- frame 60, is I frame, 1 means is an IDR I-frame (aka KeyFrame)
frame,0,I,71  <-- frame 71, is I frame, 0 means not an IDR I_frame

Le résultat est que vous ne devriez voir les images I IDR qu’à des intervalles GOPSIZE fixes, alors que toutes les autres images I sont des images I non IDR insérées au besoin par la détection de la scène.

4
Reuben

Il semble que cette syntaxe ne fonctionne pas toujours. J'ai testé pas mal de contenu VOD ainsi que de contenu en direct (vidages de fichiers) et parfois, scénecut ne fonctionne pas et déclenche un entre-deux iframe:

Syntaxe pour une conversion ascendante i50 -> p50, 2 secondes gop/segment, IDR au début, iframes entre deux si nécessaire

ffmpeg.exe -loglevel verbose -i avc_50i.ts -pix_fmt yuv420p -filter_complex yadif=1,scale=1920:1080 -vcodec libx264 -preset fast -x264-params "rc-lookahead=100:keyint=200:min-keyint=100:hrd=1:vbv_maxrate=12000:vbv_bufsize=12000:no-open-gop=1" -r 50 -crf 22 -force_key_frames "expr:eq(mod(n,100),0)" -codec:a aac -b:a 128k -y target.ts
0
TEB