
При подготовке потока для воспроизведения 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
метода
решение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-vp9
FFmpeg:
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.c
FFmpeg мы можем найти: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 так, как мне нужно, и ни одна из найденных мной данных не была полностью верной.
Сначала следует избавиться от нескольких заблуждений:
Не все I-кадры одинаковы. Есть большие "I"-кадры и маленькие "i"-кадры. Или, если использовать правильную терминологию, IDR I-кадры и не-IDR I-кадры. IDR I-кадры (иногда называемые "ключевыми кадрами") создадут новую GOP. Не-IDR-кадры — нет. Их удобно иметь внутри GOP, где происходит смена сцены.
-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», чего я никогда не делал, точно в моменты ключевых кадров; улучшить фильтр чередования, изменив его поведение по умолчанию или добавив опцию для вывода самого старого кадра в буфер вместо его удаления.