Как правильно исправить ключевые кадры в FFmpeg для DASH?

Как правильно исправить ключевые кадры в FFmpeg для DASH?

При подготовке потока для воспроизведения DASH случайные точки доступа должны быть в одном и том же времени исходного потока во всех потоках. Обычный способ сделать это — принудительно установить фиксированную частоту кадров и фиксированную длину GOP (т. е. ключевой кадр каждые N кадров).

В FFmpeg фиксированная частота кадров устанавливается просто (-r NUMBER).

Но для фиксированных положений ключевых кадров (длины GOP) есть три метода... какой из них "правильный"? Документация FFmpeg по этому поводу удручающе расплывчата.

Метод 1: манипуляции с аргументами libx264

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

Похоже, ведутся споры о том, следует ли отключать функцию смены сцен или нет, поскольку неясно, перезапускается ли «счетчик» ключевых кадров при смене сцен.

Метод 2: установка фиксированного размера GOP:

-g GOP_LEN_IN_FRAMES

К сожалению, в документации FFMPEG это задокументировано лишь вскользь, и поэтому эффект этого аргумента весьма неясен.

Метод 3: вставлять ключевой кадр каждые N секунд (Может быть?):

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

Этотявляетсяявно задокументировано. Но все еще не ясно, перезапускается ли "счетчик времени" после каждого ключевого кадра. Например, в ожидаемом 5-секундном GOP, если есть ключевой scenecutкадр, введенный libx264 через 3 секунды, будет ли следующий ключевой кадр на 5 секунд позже или на 2 секунды позже?

На самом деле, документация FFmpeg различает эту опцию и -gопцию , но в ней не говорится, чем эти две опции, указанные выше, хоть немного отличаются (очевидно, -gчто для них потребуется фиксированная частота кадров).

Какой правильный?

Казалось бы, что -force_key_framesбыло бы лучше, так как для этого не требуется фиксированная частота кадров.Однако для этого необходимо, чтобы

  • он соответствует спецификациям GOP в H.264 (если есть)
  • он ГАРАНТИРУЕТ, что будет ключевой кадр в фиксированной каденции, независимо от scenecutключевых кадров libx264.

Также, похоже, что это -gне может работать без принудительной установки фиксированной частоты кадров ( -r), так как нет гарантии, что несколько запусков ffmpegс разными аргументами кодека обеспечат одинаковую мгновенную частоту кадров в каждом разрешении. Фиксированная частота кадров может снизить производительность сжатия (ВАЖНО в сценарии DASH!).

Окончательно,этот keyintметод просто кажется хаком. Я надеюсь, что это неправильный ответ.

Использованная литература:

Пример использования -force_key_framesметода

Пример использования keyintметода

Раздел расширенных параметров видео FFmpeg

решение1

TL;DR

Я бы порекомендовал следующее:

  • libx264: (и по желанию добавьте )-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

где X— интервал в кадрах, а N— интервал в секундах. Например, для 2-секундного интервала с видео 30 кадров в секунду X= 60 и N= 2.

Примечание о различных типах рам

Чтобы правильно объяснить эту тему, нам сначала нужно определить два типа I-кадров/ключевых кадров:

  • Кадры мгновенного обновления декодера (IDR): они позволяют независимо декодировать последующие кадры без доступа к кадрам, предшествующим кадру IDR.
  • Не-IDR-кадры: для работы декодирования требуется предыдущий IDR-кадр. Не-IDR-кадры можно использовать для резки сцен в середине GOP (группы изображений).

Что рекомендуется для потоковой передачи?

В случае потоковой передачи вам необходимо:

  • Убедитесь, что все кадры IDR находятся в правильных позициях (например, на 2, 4, 6, … секундах), чтобы видео можно было разделить на сегменты одинаковой длины.
  • Включите обнаружение среза сцены, чтобы улучшить эффективность кодирования/качество. Это означает, что можно разрешить размещение I-кадров между кадрами IDR. Вы по-прежнему можете работать с отключенным обнаружением среза сцены (и это все еще часть многих руководств), но это не обязательно.

Что делают параметры?

Чтобы настроить кодер, нам нужно понять, что делают параметры ключевого кадра. Я провел несколько тестов и обнаружил следующее для трех кодеров libx264и libx265в libvpx-vp9FFmpeg:

  • libx264:

    • -gустанавливает интервал между ключевыми кадрами.
    • -keyint_minустанавливает минимальный интервал между ключевыми кадрами.
    • -x264-params "keyint=x:min-keyint=y"такой же как -g x -keyint_min y.
    • Примечание:При установке обоих значений на одно и то же минимальное значение устанавливается внутриполовинамаксимальный интервал плюс один, как видно из x264кода:

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

    • -gне реализовано.
    • -x265-params "keyint=x:min-keyint=y"работает.
  • libvpx-vp9:

    • -gустанавливает интервал между ключевыми кадрами.
    • -keyint_minустанавливает минимальный интервал между ключевыми кадрами
    • Примечание:Из-за особенностей работы FFmpeg, -keyint_minпересылается кодировщику только тогда, когда он совпадает с -g. В коде из libvpxenc.cFFmpeg мы можем найти:

      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;
      

      Это может быть ошибкой (или отсутствием функции?), поскольку libvpxопределенно поддерживается установка другого значения для kf_min_dist.

Стоит ли использовать -force_key_frames?

Опция -force_key_framesпринудительно вставляет ключевые кадры с заданным интервалом (выражением). Это работает для всех кодировщиков, но может испортить механизм управления скоростью. Особенно для VP9 я заметил серьезные колебания качества, поэтому я не могу рекомендовать использовать его в этом случае.

решение2

Вот мои пятьдесят центов за это дело.

Метод 1:

возня с аргументами libx264

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

Генерируйте iframe только с желаемыми интервалами.

Пример 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

Сгенерируйте iframes, как и ожидалось, следующим образом:

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

Метод 2 амортизирован. Опущен.

Метод 3:

вставлять ключевой кадр каждые N секунд (МОЖЕТ БЫТЬ):

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

Пример 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

Сгенерируйте iframe немного другим способом:

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

Как вы можете видеть, он размещает iframes каждые 2 секунды И на сцене (секунды с плавающей частью), что, по моему мнению, важно для сложности видеопотока.

Размеры сгенерированных файлов примерно одинаковы. Очень странно, что даже с большим количеством ключевых кадров вМетод 3он иногда генерирует меньше файлов, чем стандартный алгоритм библиотеки x264.

Для генерации файлов с несколькими битрейтами для потока HLS мы выбираем метод 3. Он идеально выровнен с 2 секундами между фрагментами, у них есть iframe в начале каждого фрагмента и есть дополнительные iframe на сложных сценах, что обеспечивает лучший опыт для пользователей, которые имеют устаревшие устройства и не могут воспроизводить высокие профили x264.

Надеюсь, это кому-то поможет.

решение3

Я хотел бы добавить сюда немного информации, поскольку поиск в Google вывел меня на эту дискуссию, пока я пытался найти информацию о том, как сегментировать кодировку DASH так, как мне нужно, и ни одна из найденных мной данных не была полностью верной.

Сначала следует избавиться от нескольких заблуждений:

  1. Не все I-кадры одинаковы. Есть большие "I"-кадры и маленькие "i"-кадры. Или, если использовать правильную терминологию, IDR I-кадры и не-IDR I-кадры. IDR I-кадры (иногда называемые "ключевыми кадрами") создадут новую GOP. Не-IDR-кадры — нет. Их удобно иметь внутри GOP, где происходит смена сцены.

  2. -x264opts keyint=GOPSIZE:min-keyint=GOPSIZE← Это не делает то, что вы думаете. Мне потребовалось некоторое время, чтобы разобраться. Оказывается, min-keyintограничено в коде. Оно не может быть больше (keyint / 2) + 1. Поэтому присвоение одинакового значения этим двум переменным приводит к значению, которое min-keyintсбивается вдвое при кодировании.

Вот в чем дело: scene-cut действительно хорош, особенно в видео с быстрыми жесткими сменами. Он сохраняет его красивым и четким, поэтому я не хочу его отключать, но в то же время я не мог получить фиксированный размер GOP, пока он был включен. Я хотел включить scene-cut, но чтобы он использовал только не-IDR I-кадры. Но это не работало. Пока я не выяснил (из большого количества прочитанного) о заблуждении №2.

Оказывается, мне нужно было установить keyintудвоение желаемого размера GOP. Это означает, что его min-keyintможно установить на желаемый размер GOP (без того, чтобы внутренний код обрезал его вдвое), что не позволяет обнаружению смены сцен использовать I-кадры IDR внутри размера GOP, поскольку количество кадров с момента последнего I-кадра IDR всегда меньше min-keyinit.

И наконец, установка force_key_frameопции переопределяет двойной размер keyint. Итак, вот что работает:

Я предпочитаю сегменты по 2 секунды, поэтому мой GOPSIZE = Частота кадров * 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>

Проверить можно с помощью ffprobe:

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

В сгенерированном CSV-файле каждая строка будет содержать следующую информацию 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

В результате вы увидите только I-кадры IDR с фиксированными GOPSIZEинтервалами, в то время как все остальные I-кадры — это не-IDR I-кадры, вставляемые по мере необходимости при обнаружении смены сцен.

решение4

У Twitch есть пост об этом. Они объясняют, что решили использовать свою собственную программу по нескольким причинам; одна из них заключается в том, что ffmpeg не позволяет запускать разные экземпляры x264 в разных потоках, а вместо этого выделяет все указанные потоки одному кадру в одном выводе, прежде чем перейти к следующему выводу.

Если вы не занимаетесь потоковой передачей в реальном времени, у вас больше роскоши. «Правильный» способ, вероятно, заключается в кодировании в одном разрешении с указанием только размера GOP с помощью -g, а затем в кодировании других разрешений с принудительной установкой ключевых кадров в тех же местах.

Если вы хотите это сделать, вы можете использовать ffprobe для получения времени ключевых кадров, а затем использовать скрипт оболочки или реальный язык программирования, чтобы преобразовать это в команду ffmpeg.

Но для большей части контента разница между одним ключевым кадром каждые 5 секунд и двумя ключевыми кадрами каждые 5 секунд (один принудительно и один из scenecut) очень мала. Это примерно средний размер I-кадра по сравнению с размером P-кадров и B-кадров. Если вы используете x264 с типичными настройками (единственная причина, по которой, я думаю, вам следует что-то делать, чтобы повлиять на них, это если вы установите -qmin, как плохой способ запретить x264 использовать битрейт для легкого контента; это ограничивает все типы кадров одним и тем же значением, я думаю) и получите результат, такой как средний размер I-кадра 46 кБ, P-кадра 24 кБ, B-кадра 17 кБ (вдвое реже, чем P-кадры), то дополнительный I-кадр каждую секунду при 30 кадрах в секунду увеличит размер файла всего на 3%. Разница между h264 и h263 может состоять из кучи уменьшений на 3%, но одно из них не так уж важно.

Для других типов контента размеры кадров будут другими. Справедливости ради, это касается временной сложности, а не пространственной, так что это не просто сравнение простого контента и сложного контента. Но, как правило, сайты потокового видео имеют ограничение по битрейту, и контент с относительно большими I-кадрами — это простой контент, который будет кодироваться с высоким качеством независимо от того, сколько дополнительных ключевых кадров будет добавлено. Это расточительство, но эти растраты обычно не будут замечены. Самый расточительный случай, вероятно, это видео, которое представляет собой просто статичное изображение, сопровождающее песню, где каждый ключевой кадр абсолютно одинаков.

Единственное, в чем я не уверен, так это в том, как принудительные ключевые кадры взаимодействуют с ограничителем скорости, установленным с помощью -maxrate и -bufsize. Я думаю, что даже у YouTube недавно были проблемы с правильной настройкой параметров буфера для обеспечения постоянного качества. Если вы просто используете настройки среднего битрейта, как это можно увидеть на некоторых сайтах (поскольку вы можете проверить параметры x264 в заголовке/атоме mov? с помощью шестнадцатеричного редактора), то модель буфера не является проблемой, но если вы обслуживаете контент, созданный пользователями, средний битрейт побуждает пользователей добавлять черный экран в конце своего видео.

Параметр -g ffmpeg или любой другой используемый вами параметр кодировщика сопоставляется с параметром, специфичным для кодировщика. Таким образом, '-x264-params keyint=GOPSIZE' эквивалентен '-g GOPSIZE'.

Одна из проблем с использованием определения сцены заключается в том, что если по какой-либо причине вы предпочитаете ключевые кадры около определенных чисел. Если вы указываете ключевые кадры каждые 5 секунд и используете определение сцены, и есть смена сцены в 4,5, то она должна быть обнаружена, но затем следующий ключевой кадр будет в 9,5. Если время продолжает увеличиваться таким образом, вы можете получить ключевые кадры в 42,5, 47,5, 52,5 и т. д. вместо 40, 45, 50, 55. И наоборот, если есть смена сцены в 5,5, то будет ключевой кадр в 5, а 5,5 будет слишком рано для другого. Ffmpeg не позволяет вам указать «сделать ключевой кадр здесь, если нет смены сцены в течение следующих 30 кадров». Однако тот, кто понимает C, может добавить эту опцию.

Для видео с переменной частотой кадров, когда вы не ведете прямую трансляцию, как Twitch, вы должны иметь возможность использовать смену сцен без постоянного преобразования в постоянную частоту кадров. Если вы используете фильтр 'select' в ffmpeg и используете константу 'scene' в выражении, то вывод отладки (-v debug или нажмите '+' несколько раз во время кодирования) показывает номер смены сцен. Это, вероятно, отличается от номера, используемого x264, и не так полезно, как номер, используемый x264, но все равно может быть полезным.

Тогда процедура, вероятно, будет заключаться в создании тестового видео, которое будет использоваться только для изменения ключевых кадров, но, возможно, может быть использовано для данных управления скоростью при использовании 2-проходного режима. (Не уверен, что сгенерированные данные вообще полезны для разных разрешений и настроек; данные макроблок-дерева будут бесполезны.) Преобразуйте его в видео с постоянной частотой кадров, но см.этот баго заикании вывода при уменьшении частоты кадров вдвое, если вы когда-нибудь решите использовать фильтр fps для других целей. Пропустите его через x264 с нужными вам ключевыми кадрами и настройками GOP.

Затем просто используйте эти ключевые кадры с исходным видео с переменной частотой кадров.

Если вы допускаете совершенно безумный пользовательский контент с 20-секундным интервалом между кадрами, то для кодирования с переменной частотой кадров вы можете разделить вывод, использовать фильтр fps, как-то использовать фильтр select (возможно, создать очень длинное выражение, которое имеет время каждого ключевого кадра)... или, может быть, вы можете использовать тестовое видео в качестве входных данных и либо декодировать только ключевые кадры, если эта опция ffmpeg работает, либо использовать фильтр select для выбора ключевых кадров. Затем масштабируйте его до нужного размера (для этого даже есть фильтр scale2ref) и наложите на него исходное видео. Затем используйте фильтр interleave, чтобы объединить эти предназначенные для принудительного использования ключевые кадры с исходным видео. Если это приведет к двум кадрам, которые находятся на расстоянии 0,001 секунды друг от друга, которые фильтр interleave не предотвращает, то решите эту проблему самостоятельно с помощью другого фильтра select. Основной проблемой здесь может быть работа с ограничениями буфера кадров для фильтра interleave. Все это может сработать: используйте какой-нибудь фильтр для буферизации более плотного потока (фильтр fifo?); обращаться к входному файлу несколько раз, чтобы он декодировался более одного раза и кадры не приходилось сохранять; использовать фильтр «streamselect», чего я никогда не делал, точно в моменты ключевых кадров; улучшить фильтр чередования, изменив его поведение по умолчанию или добавив опцию для вывода самого старого кадра в буфер вместо его удаления.

Связанный контент