Инструменты пользователя

Инструменты сайта



// FFmpeg loop input

Если нужно сделать повторный стриминг небольшого файла (или просто сделать большой файл из нескольких повторов маленького), то поможет такая команда:

ffmpeg -re -f lavfi -i "movie=filename=/some/path/input.mp4:loop=0, setpts=N/(FRAME_RATE*TB)" -ar 44100 -f flv rtmp://some.server/app/live

Чуть подробнее:

  1. -re процессит входные фреймы так, как если бы это был live stream
  2. формат входного файла lavfi говорит, что входной файл, это не регулярный файл, устройство или сетевой ресурс, а описание графа фильтра (комплексного)
  3. соответственно аргументом -i становится описание графа фильтра:
    1. movie - фильтр задаёт источник данных, а его параметр loop=N задаёт число повторов, 0 - бесконечное число раз, отличное от нуля - будет сделано именно такое число повторов.
    2. setpts - предыдущий фильтр действует принципу KISS: попросили повторять файл - буду, но PTS будет оставаться без модификаций, соответсвенно муксеру это может (и даже, скорее всего, обязательно) показаться неприятным. Соответственно нам нужно пересчитать PTS на основе прошедших (N) кадров, фреймрейта (FRAME_RATE) и time-base входного PTS (TB, про time-base я уже писал)

Дальнейшие опции роли не играют и могут сильно варьироваться. Выходным файлом может быть что угодно. Проблемы могут быть с входными файлами с переменным фреймрейтом, но я таких не видел.

Продублировал как ответы на вопросы на сайта Stack Exchange:

// FFmpeg и VP9 (libvpx)

Тут немец один в почту постучался. У него проблемы с кодированием кодеком VP9:

ffmpeg: /build/buildd/libvpx-1.3.0/vp9/encoder/vp9_encodeframe.c:1747: rd_pick_partition: Assertion `tp_orig < *tp' failed.

Если вы сидите на каком-нить Mint 17 или Ubuntu 14.04, то у вас в системе стоит libvpx версии 1.3.0 в котором есть баг: при параметре arnr-maxframes равным нулю он крешится вот таким вот образом. Причем 0 - это валидное значение согласно документации. Если эту опцию принудительно не указывать для vpxenc, то он нормально кодирует, т.е. использует какое-то значение отличное от нуля, если указать - такой же креш. А вот FFmpeg, там внутри тоже не без косяка: лапша в настройках, задаёт дефолтное значение в 0, хотя разработчики рассчитывали (судя по всему) как раз на -1. Потому такая петрушка.

Лечится указанием ffmpeg опции -arnr-maxframes со значением -1 или больше 0. -1 - включит это самое «дефолтное» значение внутри libvpx.

В новых версиях libvpx проблема исправлена. Репорты:

ЗЫ о целесообразности использования VP9 меня не спрашивайте. Меня просто спросили, а я разобрался :) ЗЗЫ FFmpeg версии 2.8.1 но роли это не играет.

// Ускорение позиционирования в удалённом flv файле при использовании http-транспорта

Пост основан на двух вопросах на Тостере и RU.SO. На RU.SO я же накатал ответ, на основе которого и буду строить свой псто. Кроме того, рассматриваю более подробно как работает перемотка со стороны FFmpeg.

В общем, жаждущие - подкат.

// AvCpp: API-2 rework, веха вторая

Продолжаем отслеживать судьбу изменений, описанных в AvCpp: API-2 rework, веха первая.

Как обычно, объём работ отличается от того, что было запланировано. Итак, что сделано:

  1. Работа со словарями и поддержка оных во всех местах, где только можно. Это вызвало необходимость разнести открытие входного потока и поиск информации о стриме (Снова FFmpeg и low-latency). Как следствие, добавился новый вызов: av::FormatContext::findStreamInfo(). Он может принимать коллекцию словарей для каждого стрима в потоке.
  2. Начата работа над фильтрами. Уже появилась базовая инфраструктура. Но пока нет понимания в каком виде оно должно получиться на выходе.
  3. В рамках чистки кода тотально переработана обработка ошибок. Теперь это делается через std::error_code, который передаётся опциональным аргументом в те функции фейл которых может приводить к неоднозначностям в будущем. Если переменная для кода ошибки не передана, будет выброшено исключение av::Exception которое будет содержать данный код. Планируется, что набор исключений расширится под каждую сущность. Данные изменений повлекли достаточно сильные изменений в API, требующие изменений в коде приложений, которые их используют. При обработке ошибок стоит учитывать категорию оных. На дынный момент используется, как минимум три:
    • avcpp_category() - для внутренних ошибок AvCpp
    • ffmpeg_category() - для ошибок, пришедших от FFmpeg
    • system_category() - для системных ошибок, в основном так рапортуются ошибки выделения памяти при помощи встроенных av_alloc() (когда владение принимается FFmpeg'ом и он сам освобождает, как следствие, использовать new/new[] нет возможности). Целесообразность использовать std::bad_alloc обдумывается.

Ну и главное, реорганизация бранчей:

  • все наработки первой вехи выделены в отдельный бранч api-2.0 (исправляются только ошибки)
  • старый мастер стал бранчем api-legacy (для совместимости, самостоятельно тут ничего не исправляется, только принимаются мерж-реквесты)
  • вся разработка перемещена на бранч master

Что осталось:

  1. Интерфейс опций
  2. Доделать фильтры
  3. Ревизия и чистка кода в т.ч. примеров.

Планируется, что это будет сделано в следующей, третье вехе.

Как обычно, за отзывы, багрепорты и пул-реквесты буду благодарен.

// Снова FFmpeg и low-latency

В дополнение к моей прошлой заметке: x264 low latency пару статей на эту же тему, но без привязки к x264:

Стоит отметить, что в первой статье наихудшие показатели показала avformat_find_stream_info(), эта же функция не понравилась коллеге на работе, но без неё могут быть такие артефакты:

В общем, согласно документации:

The above code attempts to allocate an AVFormatContext, open the specified file (autodetecting the format) and read the header, exporting the information stored there into s. Some formats do not have a header or do not store enough information there, so it is recommended that you call the avformat_find_stream_info() function which tries to read and decode a few frames to find missing information.

Суть: просто открыть файл не всегда достаточно, что бы делать предположения о некоторых параметрах файла. Походу, с этим нужно просто смириться или попытаться предпринять меры из ссылки 1, а именно зарезать параметры probesize и analyzeduration в 32 (минимально допустимое значение).

// AvCpp: API-2 rework, веха первая

Для начала, что такое AvCpp - это C++ враппер над FFmpeg, позволяющий несколько упростить работу непосредственно с функциями FFmpeg.

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

Изначально структура классов опиралась на Xuggle API, ведь когда начинал его делать, многое не понимал - почему оно так. Со временем такой подход перестал мне нравится, начали вылазить всякие неприятные оверхеды, баги. Так появился подпроект API-2, цели которого:

  1. Уйти где только можно от shared_ptr (это получилось почти везде).
  2. Использовать по максимуму возможности новых версий FFmpeg, где появился reference-counting для AVFrame/AVPacket, при этом предоставить возможности создавать «тяжёлые» копии, где нужно.
  3. Более логично организовать мапинг сущностей FFmpeg в AvCpp, к примеру, если раньше AVCodecContext мапился в av::StreamCoder, то теперь av::CodecContext, аналогично для AVFormatContext (av::Container vs av::FormatContext).
  4. Корректно реализовать аудио-ресемплер
  5. Добавить побольше примеров использования API2
  6. Работа с av_options для поддерживаемых сущностей
  7. Поддержка передачи параметров через словари
  8. Тотальная переработка фильтров
  9. Чистка кода, удаление ненужных сущностей

На текущий момент на бранче api-2 уже лежат почти все эти наработки. Нереализованными остались последние 4 пункта. Приэтому, попутно, устранено несколько багов, особенно связанных с последовательностью уничтожения разных объектов (например Stream2 и FormatContext: стримом владеет FormatContext, Stream2 только предоставляет доступ к нему, при этом, даже если уничтожить объект контекста, объект Stream2 может теперь корректно рапортовать, что он инвалидировался).

Вторая веха, судя по всему, будет реализация поддержки AVOptions/AVDict, третья веха - фильтры (к слову их сейчас нет вообще в рамках API-2). Последующие вехи пока не сформированы, скорее всего нужно будет подумать над возможностями использования аппаратного ускорения для декодирования (оно достаточно некрасиво и в самом FFmpeg реализовано - нужно делать много телодвижений).

В любом случае, я уже настоятельно рекомендую начинать использовать ветку api-2. В скором времени она заместит master-ветку, а текущий мастер переедет на ветку legacy.

За отзывы, багрепорты и пул-реквесты буду благодарен.

// x264 low latency

Очень хорошая статья на тему: http://x264dev.multimedia.cx/archives/249
UPD, ссылка недоступна, через веб-архив: http://web.archive.org/web/20150421033553/http://x264dev.multimedia.cx/archives/249

Цитата оттуда:

The total latency of x264, including encoder/decoder-side buffering, is:

B-frame latency (in frames) + Threading latency (in frames) + RC-lookahead (in frames) + Sync-lookahead (in frames) + VBV buffer size (in seconds) + Time to encode one frame (in milliseconds)

Собственно отсюда видно какие ручки крутить у того же FFmpeg что бы сделать задержку на стриминг как можно меньше:

-rc-lookahead #
-bf #
-threads #
-refs #
-x264-params sync-lookahead=#

и всякие буффера.

Эти же опции применительно к AVCodecContext:

AVCodecContext *ctx = ...;
...
av_opt_set(ctx, "rc-lookahead", "#", AV_OPT_SEARCH_CHILDREN);
av_opt_set(ctx, "threads",      "#", AV_OPT_SEARCH_CHILDREN);
av_opt_set(ctx, "bf",           "#", AV_OPT_SEARCH_CHILDREN);
av_opt_set(ctx, "refs",         "#", AV_OPT_SEARCH_CHILDREN);

GOP size (-g/«g») будет влиять на объём траффика и как быстро картинка сможет восстановиться, если ключевой кадр был потерян.

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

Есть ещё опция -tune («tune») со значением «zerolatency» - вундервафля, которая почти сгоняет задержку в ноль, но и качество картинки примерно туда же. Про то, что включает в себя различные опции тюнинга можно посмотреть в выводе:

x264 --fullhelp

Дополнительные материалы:

UPD: что-то странно, что все ссылки стали недоступны. Особенно рекомендации Ясона.

// FFMPEG: немного про time-base и PTS

В терминах FFMPEG time-base, это параметр, используя который можно перевести PTS (Presentation Timestamp) закодированного пакета (AVPacket) или раскодированного фрейма (AVFrame) в реальное время, выраженное в секундах (это же верно для DTS закодированного пакета). Представляет собой рациональное число типа AVRational.

А вот теперь интересности, связанные с ним.

Общие сведения

Внутри библиотек libavformat и libavcodec, входящих в состав FFMPEG time-base может храниться в двух контекстах:

  • контекст потока (AVStream)
  • и контекст кодека (AVCodecContext)

При этом временные метки (далее я буду говорить только про PTS, если потребуется сказать про DTS - отдельно обращу внимание) могут храниться у AVFrame (поле pts) и AVPacket (аналогично, поле pts) тип всех временных меток (включая поле dts у AVPacket) - int64_t.

Отсюда возникает резонный вопрос: как соотносить time-base и временные метки.

Короткий ответ:

  • time-base AVStream является основной для временных меток AVPacket
  • time-base AVCodecContext является основой для временных меток AVFrame

Далее рассмотрим, когда и как мы должны устанавливать руками, а что и когда установится само.

Чтение и раскодирования файла

После того как создан и открыт AVFormatContext, прочитана информация о потоках картина получается такой:

  • поле time_base каждого потока (AVStream) заполнено самой библиотекой, изменять вручную его крайне не желательно, даже не могу представить, какие последствия может принести данная операция.
  • поле time_base созданных декодеров для каждого типа потока (AVCodecContext) так же заполняется библиотекой и изменять его не желательно

Логично было бы предположить, что временные метки для потока и его декодировщика должны быть одинаковы, но это не так. Как следствие временные метки пакета и раскодированного фрейма нельзя сравнивать напрямую. И если вы хотите установить, после раскодировки, PTS фрейма, это нужно делать при помощи функции av_rescale_q(…), примерно так:

frame.pts = av_rescale_q(packet.pts, packetTimeBase, frameTimeBase);

Ясно, что frameTimeBase берётся из AVCodecContext, а packetTimeBase из AVStream.

Стоит отметить так же то, что у некоторых пакетов поле pts имеет значение AV_NOPTS_VALUE, тогда как поле dts имеет корректное значение, тогда его и стоит брать для присваивания значению PTS фрейма. Суть такого поведения для меня не ясна.

Так же стоит отметить для для звуковых семплов, после раскодировки значение PTS фрейма будет AV_NOPTS_VALUE и НИ В КОЕМ СЛУЧАЕ НЕ МЕНЯЙТЕ ЕГО НА ЧТО-ТО ЛИБО ДРУГОЕ, это приведёт в смятение микшер, как следствие, звука в результирующем файле вы не услышите. И снова, суть такого поведения для меня не ясна.

Кодировка и запись (микширование потоков)

При кодировании и микшировании мы сами добавляем потоки в выходной формат. При этом time-base мы должны задавать только для кодировщика (AVCodecContext) и делать это обязательно.

Поле time-base у потока (AVStream) можно устанавливать, а можно и нет, всё равно, после вызова avformat_write_header(…) оно будет сброшено и установлено в необходимое значение подходящее для данного типа контейнера. При этом, значение time-base из AVCodecContext будет использоваться как подсказка, если контейнер не определяет требований к значению time-base.

Соответственно, важно: даже при записи вы не можете гарантировать, что значения time-base в AVStream и в AVCodecContext будут эквивалентны. Кроме того, вы не можете опираться на значение time-base внутри AVStream пока не будет вызвана avformat__write_header(…). Как следствие, при копировании временной метки из фрейма в пакет нужно использовать av_rescale_q(…)

Особо хочу отметить, что если вы делаете масштабирование поля pts пакета, не забудьте сделать то же для поля dts, иначе получите интересные артефакты при воспроизведении.

Для аудио-фреймов и аудио пакетов поля pts и dts всегда будут AV_NOPTS_VALUE и никаких преобразований делать не нужно.

Случай транскодирования

Пусть мы имеем входной файл формата Format1 с одним видео потоком Stream1, закодированный кодеком Codec1, нам нужно его перекодировать в файл формата Format2, так же с одним видео потоком Stream2, закодированным кодеком Codec2.

Что получается:

  1. после открытия входного файла Stream1 имеет time-base InStreamTimeBase, а Coder1 имеет time-base InCoderTimeBase.
  2. после настройки выходного формата, и записи заголовка мы имеем: Stream2 с OutStreamTimeBase и Codec2 с OutCoderTimeBase.
  3. av_read_fream() возвращает пакеты с pts/dts в масштабе InStreamTimeBase.
  4. после декодирования видео-фрагментов, мы получаем фрейм с pts в масштабе InCoderTimeBase
  5. если возникает необходимость копировать pts/dts пакеты в pts фрейма необходимо сделать масштабирование: frame.pts = av_rescale_q(packet.pts/dts, InStreamTimeBase, InCoderTimeBase);
  6. перед кодировкой фрейма при помощи Coder1 нужно смаштабировать его pts: frame.pts = av_rescale_q(frame.pts, InCoderTimeBase, OutCoderTimeBase);
  7. перед записью получившегося пакета, нужно смаштабировать его pts/dts: packet.pts/dts = av_rescale_q(packet.pts/dts, OutCoderTimeBase, OutStreamTimeBase);
  8. записать пакет.

Масштабирования нужно делать только если значение временной метки не равно AV_NOPTS_VALUE.

За сим всё.