
Ao condicionar um fluxo para reprodução DASH, os pontos de acesso aleatórios devem estar exatamente no mesmo horário do fluxo de origem em todos os fluxos. A maneira usual de fazer isso é forçar uma taxa de quadros fixa e um comprimento de GOP fixo (ou seja, um quadro-chave a cada N quadros).
No FFmpeg, a taxa de quadros fixa é fácil (-r NUMBER).
Mas para locais fixos de quadros-chave (comprimento GOP), existem três métodos... qual deles é o "correto"? A documentação do FFmpeg é frustrantemente vaga sobre isso.
Método 1: mexendo com os argumentos da libx264
-c:v libx264 -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE:scenecut=-1
Parece haver algum debate se o corte de cena deve ser desativado ou não, pois 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 só está documentado de passagem na documentação do FFMPEG e, portanto, o efeito desse argumento não é muito claro.
Método 3: insira um quadro-chave a cada N segundos (Talvez?):
-force_key_frames expr:gte(t,n_forced*GOP_LEN_IN_SECONDS)
Esseé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 scenecut
quadro-chave injetado 3 segundos pela libx264, o próximo quadro-chave ocorreria 5 segundos depois ou 2 segundos depois?
Na verdade, a documentação do FFmpeg diferencia entre esta e a -g
opção, mas não diz realmente como essas duas opções acima são minimamente diferentes (obviamente, -g
exigirá uma taxa de quadros fixa).
Qual é certo?
Parece que -force_key_frames
seria superior, pois não exigiria uma taxa de quadros fixa.No entanto, isso exige que
- está em conformidade com as especificações GOP em H.264 (caso existam)
- GARANTE que haveria um quadro-chave em cadência fixa, independentemente dos
scenecut
quadros-chave libx264.
Parece também que -g
não poderia funcionar sem forçar uma taxa de quadros fixa ( -r
), pois não há garantia de que múltiplas execuções ffmpeg
com diferentes argumentos de codec forneceriam a mesma taxa de quadros instantânea em cada resolução. Taxas de quadros fixas podem reduzir o desempenho da compactação (IMPORTANTE em um cenário DASH!).
Finalmente,o keyint
método parece um hack. Espero contra a esperança que esta não seja a resposta correta.
Referências:
Um exemplo usando o -force_key_frames
método
Responder1
DR
Eu recomendaria o seguinte:
libx264
: (e opcionalmente adicione )-g X -keyint_min X
-force_key_frames "expr:gte(t,n_forced*N)"
libx265
:-x265-params "keyint=X:min-keyint=X"
libvpx-vp9
:-g X
onde X
é o intervalo em quadros e N
é o intervalo em segundos. Por exemplo, para um intervalo de 2 segundos com um vídeo de 30fps, X
= 60 e N
= 2.
Uma observação sobre os diferentes tipos de quadros
Para explicar adequadamente este tópico, primeiro temos que definir os dois tipos de I-frames/keyframes:
- Quadros Instantaneous Decoder Refresh (IDR): Permitem a decodificação independente dos quadros seguintes, sem acesso aos quadros anteriores ao quadro IDR.
- Quadros não IDR: requerem um quadro IDR anterior para que a decodificação funcione. 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:
- Certifique-se de que todos os quadros IDR estejam em posições regulares (por exemplo, em 2, 4, 6,… segundos) para que o vídeo possa ser dividido em segmentos de igual duração.
- Habilite a detecção de corte de cena, para melhorar a eficiência/qualidade da 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 desabilitada (e isso ainda faz parte de muitos guias), mas não é necessário.
O que os parâmetros fazem?
Para configurar o codificador, temos que entender o que os parâmetros do quadro-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 de quadro-chave.-x264-params "keyint=x:min-keyint=y"
é o mesmo que-g x -keyint_min y
.Observação:Ao definir ambos para o mesmo valor, o mínimo é definido internamente parametadeo intervalo máximo mais um, conforme visto no
x264
código: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 de quadro-chaveObservação:Devido ao funcionamento do FFmpeg,
-keyint_min
ele só é encaminhado ao codificador quando é igual ao-g
. No códigolibvpxenc.c
do 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 recurso?), pois
libvpx
definitivamente suporta a definição de um valor diferente parakf_min_dist
.
Você deve usar -force_key_frames
?
A -force_key_frames
opção insere quadros-chave à força em um determinado intervalo (expressão). Isso funciona para todos os codificadores, mas pode atrapalhar o mecanismo de controle de taxa. Especialmente para o VP9, notei graves flutuações de qualidade, por isso não posso recomendar seu uso neste caso.
Responder2
Aqui estão meus cinquenta centavos pelo caso.
Método 1:
mexendo com os argumentos da libx264
-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 conforme 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.
Método 3:
insira um quadro-chave a cada N segundos (TALVEZ):
-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, ele coloca iframes a cada 2 segundos E no corte de cena (segundos com parte flutuante), o que é importante para a complexidade do stream de vídeo, na minha opinião.
Os tamanhos dos arquivos gerados são praticamente os mesmos. Muito estranho que mesmo com mais quadros-chave noMétodo 3às vezes ele gera menos arquivos do que o algoritmo de biblioteca x264 padrão.
Para gerar vários arquivos de taxa de bits para fluxo HLS, escolhemos o método três. Está perfeitamente alinhado com 2 segundos entre os pedaços, eles têm iframe no início de cada pedaço e possuem iframes adicionais em cenas complexas, o que proporciona uma melhor experiência para usuários que possuem dispositivos desatualizados e não podem reproduzir perfis altos x264.
Espero que ajude alguém.
Responder3
Eu queria adicionar algumas informações aqui, já que minha pesquisa no Google aumentou bastante essa discussão em minha busca para encontrar informações sobre como tentar encontrar uma maneira de segmentar minha codificação DASH da maneira que eu queria, e nenhuma das informações que encontrei estava totalmente correta.
Primeiros vários equívocos dos quais se livrar:
Nem todos os quadros I são iguais. Existem grandes quadros “I” e pequenos quadros “i”. Ou, para usar a terminologia correta, I-Frames IDR e I-Frames não IDR. Os I-frames IDR (às vezes chamados de "quadros-chave") 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.
-x264opts keyint=GOPSIZE:min-keyint=GOPSIZE
← Isso não faz o que você pensa que faz. Isso me levou um pouco para descobrir. Acontece quemin-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 amin-keyint
ser reduzido pela metade durante a codificação.
O problema é o seguinte: o corte de cena é realmente ótimo, especialmente em vídeos com cortes rápidos e rígidos. Ele o mantém bonito e nítido, então não quero desativá-lo, mas ao mesmo tempo não consegui obter um tamanho fixo de GOP enquanto ele estivesse ativado. Eu queria ativar o corte de cena, mas apenas usar I-frames não IDR. Mas não estava funcionando. Até que descobri (depois de muitas leituras) sobre o equívoco nº 2.
Acontece que eu precisava keyint
dobrar o tamanho do GOP desejado. Isso significa que min-keyint
pode ser definido para o tamanho de GOP desejado (sem que o código interno o corte pela metade), o que evita que a detecção de corte de cena use quadros I de IDR dentro do tamanho de GOP porque a contagem de quadros desde o último quadro I de IDR é sempre menor que min-keyinit
.
E, finalmente, definir a force_key_frame
opção substitui o double size keyint
. Então aqui está o que funciona:
Eu prefiro segmentos em pedaços 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 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 informará 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ê só deve ver I-Frames IDR em GOPSIZE
intervalos fixos, enquanto todos os outros quadros I são I-frames não IDR inseridos conforme necessário pela detecção de corte de cena.
Responder4
Twitch tem um post sobre isso. Eles explicam que decidiram usar seu próprio programa por diversos motivos; um deles era que o ffmpeg não permite executar diferentes instâncias x264 em threads diferentes, mas dedica todos os threads especificados a um quadro em uma saída antes de passar para a próxima saída.
Se você não estiver fazendo streaming em tempo real, terá mais luxo. A maneira 'correta' é provavelmente codificar em uma resolução com apenas o tamanho do GOP especificado com -g e, em seguida, codificar as outras resoluções forçando os quadros-chave nos mesmos locais.
Se você quiser fazer isso, poderá usar ffprobe para obter os tempos dos quadros-chave e, em seguida, usar um script de shell ou uma linguagem de programação real para convertê-lo em um comando ffmpeg.
Mas para a maior parte do conteúdo, há muito pouca diferença entre ter um quadro-chave a cada 5 segundos e dois quadros-chave a cada 5 segundos (um forçado e outro de corte de cena). Trata-se aproximadamente do tamanho médio do quadro I versus o tamanho dos quadros P e dos quadros B. Se você usar x264 com configurações típicas (a única razão pela qual acho que você deve fazer algo para afetá-las é definir -qmin, como uma maneira inadequada de impedir que x264 use taxa de bits em conteúdo fácil; isso limita todos os tipos de quadros ao mesmo valor , eu acho) e obter um resultado como tamanho médio de quadro I de 46 kB, quadro P de 24 kB, quadro B de 17 kB (metade da frequência dos quadros P) e, em seguida, um quadro I extra a cada segundo a 30 fps é um aumento de apenas 3% no tamanho do arquivo. A diferença entre h264 e h263 pode ser composta por várias reduções de 3%, mas uma única não é muito importante.
Em outros tipos de conteúdo, os tamanhos dos quadros serão diferentes. Para ser justo, trata-se de complexidade temporal e não de complexidade espacial, portanto não se trata apenas de conteúdo fácil versus conteúdo difícil. Mas geralmente, os sites de streaming de vídeo têm um limite de taxa de bits, e o conteúdo com quadros I relativamente grandes é um conteúdo fácil que será codificado em alta qualidade, não importa quantos quadros-chave extras sejam adicionados. É um desperdício, mas esse desperdício geralmente não será notado. O caso de maior desperdício é provavelmente um vídeo que é apenas uma imagem estática acompanhando uma música, onde cada quadro-chave é exatamente o mesmo.
Uma coisa que não tenho certeza é como os quadros-chave forçados interagem com o limitador de taxa definido com -maxrate e -bufsize. Acho que até o YouTube teve problemas recentes ao definir corretamente as configurações de buffer para fornecer qualidade consistente. Se você estiver usando apenas configurações de taxa de bits média, como pode ser visto em alguns sites (já que você pode inspecionar as opções do x264 no cabeçalho/mov atom? com um editor hexadecimal), então o modelo de buffer não é um problema, mas se você estiver veiculando conteúdo gerado pelo usuário, a taxa de bits média incentiva os usuários a adicionar uma tela preta no final do vídeo.
A opção -g do Ffmpeg, ou qualquer outra opção de codificador que você usa, é mapeada para a opção específica do codificador. Portanto, '-x264-params keyint=GOPSIZE' é equivalente a '-g GOPSIZE'.
Um problema ao usar a detecção de cena é se você preferir quadros-chave próximos a números específicos por qualquer motivo. Se você especificar quadros-chave a cada 5 segundos e usar a detecção de cena, e houver uma mudança de cena em 4,5, ela deverá ser detectada, mas o próximo quadro-chave será em 9,5. Se o tempo continuar aumentando assim, você poderá acabar com quadros-chave em 42,5, 47,5, 52,5, etc., em vez de 40, 45, 50, 55. Por outro lado, se houver uma mudança de cena em 5,5, então haverá um quadro-chave em 5 e 5,5 será muito cedo para outro. O Ffmpeg não permite especificar "criar um quadro-chave aqui se não houver mudança de cena nos próximos 30 quadros". Alguém que entende C poderia adicionar essa opção.
Para vídeos com taxa de quadros variável, quando você não estiver transmitindo ao vivo como o Twitch, você poderá usar alterações de cena sem converter permanentemente para taxa de quadros constante. Se você usar o filtro 'select' no ffmpeg e usar a constante 'scene' na expressão, a saída de depuração (-v debug ou pressione '+' várias vezes durante a codificação) mostrará o número de mudança de cena. Provavelmente é diferente e não tão útil quanto o número usado por x264, mas ainda pode ser útil.
O procedimento, então, provavelmente seria fazer um vídeo de teste apenas para alterações de quadros-chave, mas talvez pudesse ser usado para dados de controle de taxa se estiver usando 2 passagens. (Não tenho certeza se os dados gerados são úteis para diferentes resoluções e configurações; os dados da árvore de macroblocos não serão.) Converta-os em vídeo com taxa de quadros constante, mas consulteesse bugsobre a falha na saída ao reduzir pela metade a taxa de quadros, se você decidir usar o filtro fps para outros fins. Execute-o em x264 com o quadro-chave desejado e as configurações de GOP.
Em seguida, basta usar esses tempos de quadro-chave com o vídeo original com taxa de quadros variável.
Se você permitir conteúdo completamente maluco gerado pelo usuário com um intervalo de 20 segundos entre os quadros, então, para a codificação de taxa de quadros variável, você pode dividir a saída, usar o filtro fps, de alguma forma usar o filtro de seleção (talvez construir uma expressão realmente longa que tenha a cada tempo de quadro-chave)... ou talvez você possa usar o vídeo de teste como entrada e decodificar apenas quadros-chave, se a opção ffmpeg funcionar, ou usar o filtro de seleção para selecionar quadros-chave. Em seguida, dimensione-o para o tamanho correto (há até um filtro scale2ref para isso) e sobreponha o vídeo original nele. Em seguida, use o filtro de intercalação para combinar esses quadros-chave destinados a serem forçados com o vídeo original. Se isso resultar em dois quadros separados por 0,001 segundos, o que o filtro de intercalação não impede, resolva você mesmo esse problema com outro filtro de seleção. Lidar com os limites do buffer de quadros para o filtro intercalado pode ser o principal problema aqui. Tudo isso poderia funcionar: usar algum tipo de filtro para armazenar em buffer o fluxo mais denso (filtro fifo?); consulte o arquivo de entrada várias vezes para que ele seja decodificado mais de uma vez e os quadros não precisem ser armazenados; usar o filtro 'streamselect', que nunca fiz, exatamente nos horários dos keyframes; melhore o filtro de intercalação alterando seu comportamento padrão ou adicionando uma opção para gerar o quadro mais antigo em um buffer em vez de descartar um quadro.