Qual é a maneira CORRETA de corrigir quadros-chave no FFMPEG para DASH?

28

Ao condicionar um fluxo para a reprodução DASH, os pontos de acesso aleatório devem estar exatamente no mesmo tempo do fluxo de origem em todos os fluxos. A maneira usual de fazer isso é forçar uma taxa fixa de quadros e um comprimento GOP fixo (ou seja, um quadro-chave a cada N quadros).

No FFMPEG, a taxa fixa de quadros é fácil (-r NUMBER).

Mas para locais fixos de quadros-chave (comprimento GOP), existem três métodos ... qual deles é "correto"? A documentação do FFMPEG é frustrantemente vaga sobre isso.

Método 1: mexendo nos argumentos da libx264

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

Parece haver algum debate se o cenário deve ser desativado ou não, já que não está claro se o "contador" do quadro-chave é reiniciado quando ocorre um corte de cena.

Método 2: definir um tamanho fixo de GOP:

-g GOP_LEN_IN_FRAMES

Infelizmente, isso é documentado apenas na documentação do FFMPEG e, portanto, o efeito desse argumento é muito incerto.

Método 3: insira um quadro-chave a cada N segundos ( MAYBE ):

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

Este é explicitamente documentado. Mas ainda não está claro se o "contador de tempo" reinicia após cada quadro chave. Por exemplo, em um GOP esperado de 5 segundos, se houver um keyframe scenecut injetado por 3 segundos na libx264, o próximo keyframe será 5 segundos mais tarde ou 2 segundos depois?

Na verdade, a documentação do FFMPEG diferencia entre isso e a opção -g , mas não diz exatamente como essas duas opções acima são o mínimo (obviamente, -g exigirá uma taxa fixa de quadros ).

QUAL ESTÁ CERTO?

Parece que o -force_key_frames seria superior , já que não exigiria uma taxa de quadros fixa. No entanto, isso exige que

  • está em conformidade com as especificações do GOP no H.264 ( se houver )
  • GARANTE que haveria um quadro-chave em cadência fixa, independentemente de quadros-chave libx264 scenecut .

Também parece que -g não funcionou sem forçar uma taxa fixa de quadros ( -r ) , pois não há garantia de que várias execuções de ffmpeg com diferentes argumentos de codec fornecer a mesma taxa de quadros instantânea em cada resolução. Taxas de quadros fixas podem reduzir o desempenho de compactação (IMPORTANTE em um cenário de DASH!).

Por fim, o método keyint parece apenas um hack . Espero que não seja a resposta correta.

Referências:

Um exemplo usando o% método-force_key_frames

Um exemplo usando o método keyint

Seção de opções avançadas de vídeo do FFMPEG

    
por Mark Gerolimatos 30.04.2015 / 21:16

5 respostas

6

A resposta, portanto, parece ser:

  • O método 1 foi verificado para funcionar, mas é libx264 -específico e tem o custo de eliminar a opção scenecut muito útil em libx264 .
  • O método 3 funciona a partir da versão do FFMPEG de abril de 2015, mas você deve verificar seus resultados com o script incluído na parte inferior desta postagem, pois a documentação do FFMPEG não está clara quanto ao efeito da opção. Se funciona, é a superior das duas opções.
  • NÃO USE o Método 2, -g parece estar obsoleto. Ele não parece funcionar, nem está explicitamente definido na documentação, nem é encontrado na ajuda, nem parece ser usado no código. A inspeção de código mostra que a opção -g é provavelmente destinada a fluxos MPEG-2 (existem até mesmo estrofes de código referentes a PAL e NTSC!).

Além disso:

  • Arquivos gerados com o Método 3 podem ser um pouco maiores que o Método 1, como quadros I intersticiais (quadros-chave) são permitidos.
  • Você deve definir explicitamente o sinalizador "-r" em ambos os casos, mesmo que o Método 3 coloque um quadro I no próximo intervalo de quadros ou após o horário especificado. A falha em definir o sinalizador "-r" coloca você à mercê do arquivo de origem, possivelmente com uma taxa de quadros variável. Podem ocorrer transições DASH incompatíveis.
  • Apesar dos avisos na documentação do FFMPEG, o método 3 é NÃO menos eficiente que outros. De fato, os testes mostram que pode ser um pouco MAIS eficiente que o método 1.

Script para a opção -force_key_frames

Aqui está um pequeno programa PERL que eu usei para verificar a cadência do I-frame com base na saída da sugestão do ffprobe do slhck. Parece verificar que o método -force_key_frames também funcionará e tem o benefício adicional de permitir scenecut frames. Eu não tenho absolutamente nenhuma idéia de como o FFMPEG faz esse trabalho, ou se eu apenas tive sorte de alguma forma porque meus fluxos foram bem condicionados.

No meu caso, codifiquei a 30fps com um tamanho esperado de GOP de 6 segundos ou 180 quadros. Eu usei 180 como o argumento gopsize para este programa verificou um quadro I em cada múltiplo de 180, mas defini-lo para 181 (ou qualquer outro número não um múltiplo de 180) fez reclamar.

#!/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;
}
    
por 01.05.2015 / 21:51
18

TL; DR

Eu recomendaria o seguinte:

  • libx264 : -g X -keyint_min X (e, opcionalmente, adicione -force_key_frames "expr:gte(t,n_forced*N)" )
  • libx265 : -x265-params "keyint=X:min-keyint=X"
  • libvpx-vp9 : -g X

em que X é o intervalo em quadros e N é o intervalo em segundos. Por exemplo, para um intervalo de 2 segundos com um vídeo de 30 fps, X = 60 e N = 2.

Uma nota sobre diferentes tipos de quadros

Para explicar corretamente este tópico, primeiro precisamos definir os dois tipos de quadros-chave / frames-chave:

  • Quadros Instantâneos de Atualização do Decodificador (IDR): Permitem a decodificação independente dos seguintes quadros, sem acesso a quadros anteriores ao quadro IDR.
  • Quadros não-IDR: exigem um quadro IDR anterior para a decodificação funcionar. Quadros não-IDR podem ser usados para cortes de cena no meio de um GOP (grupo de imagens).

O que é recomendado para streaming?

Para o caso de streaming, você deseja:

  • Verifique se todos os quadros IDR estão em posições regulares (por exemplo, 2, 4, 6,… segundos) para que o vídeo possa ser dividido em segmentos de igual duração.
  • Ative a detecção de corte de cena, para melhorar a eficiência / qualidade de codificação. Isso significa permitir que quadros I sejam colocados entre quadros IDR. Você ainda pode trabalhar com a detecção de corte de cena desativada (e isso ainda faz parte de muitos guias), mas não é necessário.

O que os parâmetros fazem?

Para configurar o codificador, precisamos entender o que os parâmetros dos quadros-chave fazem. Fiz alguns testes e descobri o seguinte, para os três codificadores libx264 , libx265 e libvpx-vp9 no FFmpeg:

  • libx264 :

    • -g define o intervalo do quadro-chave.
    • -keyint_min define o intervalo mínimo do quadro-chave.
    • -x264-params "keyint=x:min-keyint=y" é o mesmo que -g x -keyint_min y .
    • Nota: Ao definir ambos para o mesmo valor, o mínimo é definido internamente para metade o intervalo máximo mais um, como visto no código x264 :

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

    • -g não está implementado.
    • -x265-params "keyint=x:min-keyint=y" funciona.
  • libvpx-vp9 :

    • -g define o intervalo do quadro-chave.
    • -keyint_min define o intervalo mínimo do quadro-chave
    • Observação: Devido a como o FFmpeg funciona, -keyint_min é encaminhado apenas para o codificador quando é igual a -g . No código de libvpxenc.c no FFmpeg podemos encontrar:

      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;
      

      Isso pode ser um bug (ou falta de recursos?), pois libvpx definitivamente suporta a configuração de um valor diferente para kf_min_dist .

Você deve usar -force_key_frames ?

A opção -force_key_frames forçosamente insere quadros-chave no intervalo dado (expressão). Isso funciona para todos os codificadores, mas pode atrapalhar o mecanismo de controle de taxa. Especialmente para o VP9, notei grandes flutuações de qualidade, por isso não posso recomendar usá-lo neste caso.

    
por 30.04.2015 / 23:31
10

Aqui estão meus cinquenta centavos para o caso.

Method 1:

messing with libx264's arguments

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

Gere iframes apenas nos intervalos desejados.

Exemplo 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

Gere iframes como esperado assim:

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

O método 2 é depreciado. Omitido.

Method 3:

insert a keyframe every N seconds (MAYBE):

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

Exemplo 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

Gere iframes de uma maneira um pouco diferente:

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

Como você pode ver, coloca iframes a cada 2 segundos E no scenecut (segundos com parte flutuante) o que é importante para a complexidade do fluxo de vídeo na minha opinião.

Os tamanhos de arquivo gerados são praticamente os mesmos. É muito estranho que, mesmo com mais quadros-chave no Método 3 , ele gere, às vezes, menos arquivos do que o algoritmo padrão da biblioteca x264.

Para gerar vários arquivos de taxa de bits para o fluxo HLS, escolhemos o método três. Ele está perfeitamente alinhado com 2 segundos entre trechos, eles têm iframe no início de cada trecho e eles possuem iframes adicionais em cenas complexas, o que proporciona uma melhor experiência para usuários que têm dispositivos desatualizados e não podem reproduzir perfis altos de x264.

Espero que ajude alguém.

    
por 08.07.2016 / 09:17
3

Eu queria adicionar algumas informações aqui desde que meu googling puxou essa discussão um pouco na minha busca para encontrar informações sobre como tentar segmentar minha codificação DASH da maneira que eu queria, e nenhuma das informações que encontrei foi totalmente correto.

Primeiros equívocos para se livrar de:

  1. Nem todos os quadros I são iguais. Há grandes quadros "I" e pequenos quadros "i". Ou para usar a terminologia correta, IDR I-Frames e não IDR I-Frames. IDR I-frames (às vezes chamados de "keyframes") criarão um novo GOP. Os quadros não-IDR não. Eles são úteis para ter dentro de um GOP, onde há uma mudança de cena.

  2. -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE ← Isso não faz o que você acha que faz. Isso me levou um tempo para descobrir. Acontece que o min-keyint é limitado no código. Não é permitido ser maior que (keyint / 2) + 1 . Portanto, atribuir o mesmo valor a essas duas variáveis resulta no valor de min-keyint sendo reduzido pela metade na codificação.

Aqui está a coisa: o corte de cena é realmente ótimo, especialmente em vídeos que têm cortes rápidos. Ele mantém tudo bem e nítido, então não quero desabilitá-lo, mas ao mesmo tempo não consegui obter um tamanho fixo de GOP, desde que ele estivesse habilitado. Eu queria ativar o corte de cena, mas usar apenas quadros I não IDR. Mas não estava funcionando. Até que eu descobri (de muitas leituras) sobre o conceito errado # 2.

Acontece que eu precisava definir keyint para duplicar o tamanho desejado do GOP. Isso significa que min-keyint pode ser configurado para o tamanho GOP desejado (sem o código interno cortando ao meio), o que evita que a detecção de corte de cena use IDR I-frames dentro do tamanho GOP porque a contagem de quadros desde o último IDR I -Frame é sempre menor que min-keyinit .

E, finalmente, definir a opção force_key_frame substitui o tamanho duplo keyint . Então, eis o que funciona:

Eu prefiro segmentos em blocos de 2 segundos, então meu 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>

Você pode verificar usando o ffprobe:

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

No arquivo CSV gerado, cada linha informa: 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

O resultado é que você deve ver apenas I-Frames IDR em GOPSIZE intervalos fixos, enquanto todos os outros I frames são I-frames IDR não inseridos conforme necessário pela detecção de corte de cena.

    
por 27.06.2017 / 20:39
0

Parece que essa sintaxe não funciona. Eu testei muito em nosso conteúdo de VOD, bem como conteúdo ao vivo (lixões de arquivos) e, às vezes, o cenário não funciona e aciona um iframe intermediário:

Sintaxe para uma upconvertion de i50- > p50, gop / segmento de 2 seg, IDR no início, iframes entre si, se necessário

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

    
por 31.10.2018 / 13:06