Тематика
Тематика
На два канала:
ffmpeg -f lavfi -i "sine=f=1000:sample_rate=48000" -filter_complex "[0][0]amerge" -c:a pcm_s24le -f alsa "hw:1,0"
для увеличения: добавляем [0]
перед amerge
Можно генерировать и разные синусы по кадому каналу, просто увеличиваем число lavfi
входов:
ffmpeg -f lavfi -i "sine=f=1000:sample_rate=48000" \ -f lavfi -i "sine=f=400:sample_rate=48000" --filter_complex "[0][1]amerge" \ --c:a pcm_s24le -f alsa "hw:1,0"
и не забываем в этом случае менять номера входов для amerge
Если нужно сделать повторный стриминг небольшого файла (или просто сделать большой файл из нескольких повторов маленького), то поможет такая команда:
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
Чуть подробнее:
-re
процессит входные фреймы так, как если бы это был live streamlavfi
говорит, что входной файл, это не регулярный файл, устройство или сетевой ресурс, а описание графа фильтра (комплексного)-i
становится описание графа фильтра:movie
- фильтр задаёт источник данных, а его параметр loop=N
задаёт число повторов, 0 - бесконечное число раз, отличное от нуля - будет сделано именно такое число повторов.setpts
- предыдущий фильтр действует принципу KISS: попросили повторять файл - буду, но PTS будет оставаться без модификаций, соответсвенно муксеру это может (и даже, скорее всего, обязательно) показаться неприятным. Соответственно нам нужно пересчитать PTS на основе прошедших (N
) кадров, фреймрейта (FRAME_RATE
) и time-base входного PTS (TB
, про time-base я уже писал)Дальнейшие опции роли не играют и могут сильно варьироваться. Выходным файлом может быть что угодно. Проблемы могут быть с входными файлами с переменным фреймрейтом, но я таких не видел.
Продублировал как ответы на вопросы на сайта Stack Exchange:
Тут немец один в почту постучался. У него проблемы с кодированием кодеком 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 но роли это не играет.
Продолжаем отслеживать судьбу изменений, описанных в AvCpp: API-2 rework, веха первая.
Как обычно, объём работ отличается от того, что было запланировано. Итак, что сделано:
av::FormatContext::findStreamInfo()
. Он может принимать коллекцию словарей для каждого стрима в потоке.std::error_code
, который передаётся опциональным аргументом в те функции фейл которых может приводить к неоднозначностям в будущем. Если переменная для кода ошибки не передана, будет выброшено исключение av::Exception
которое будет содержать данный код. Планируется, что набор исключений расширится под каждую сущность. Данные изменений повлекли достаточно сильные изменений в API, требующие изменений в коде приложений, которые их используют. При обработке ошибок стоит учитывать категорию оных. На дынный момент используется, как минимум три:avcpp_category()
- для внутренних ошибок AvCppffmpeg_category()
- для ошибок, пришедших от FFmpegsystem_category()
- для системных ошибок, в основном так рапортуются ошибки выделения памяти при помощи встроенных av_alloc()
(когда владение принимается FFmpeg'ом и он сам освобождает, как следствие, использовать new
/new[]
нет возможности). Целесообразность использовать std::bad_alloc
обдумывается.Ну и главное, реорганизация бранчей:
api-2.0
(исправляются только ошибки)api-legacy
(для совместимости, самостоятельно тут ничего не исправляется, только принимаются мерж-реквесты)master
Что осталось:
Планируется, что это будет сделано в следующей, третье вехе.
Как обычно, за отзывы, багрепорты и пул-реквесты буду благодарен.
В дополнение к моей прошлой заметке: x264 low latency пару статей на эту же тему, но без привязки к x264:
Стоит отметить, что в первой статье наихудшие показатели показала avformat_find_stream_info()
, эта же функция не понравилась коллеге на работе, но без неё могут быть такие артефакты:
av_read_frame()
)В общем, согласно документации:
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 - это C++ враппер над FFmpeg, позволяющий несколько упростить работу непосредственно с функциями FFmpeg.
Написание враппера помогло очень хорошо разобраться во внутренностях FFmpeg, а так же в большей части скрыть некоторые нюансы работы, которые неподготовленному человеку могут встать в долгие часы отладки и попытках понять, что он сделал не так.
Изначально структура классов опиралась на Xuggle API, ведь когда начинал его делать, многое не понимал - почему оно так. Со временем такой подход перестал мне нравится, начали вылазить всякие неприятные оверхеды, баги. Так появился подпроект API-2, цели которого:
На текущий момент на бранче api-2 уже лежат почти все эти наработки. Нереализованными остались последние 4 пункта. Приэтому, попутно, устранено несколько багов, особенно связанных с последовательностью уничтожения разных объектов (например Stream2 и FormatContext: стримом владеет FormatContext, Stream2 только предоставляет доступ к нему, при этом, даже если уничтожить объект контекста, объект Stream2 может теперь корректно рапортовать, что он инвалидировался).
Вторая веха, судя по всему, будет реализация поддержки AVOptions/AVDict, третья веха - фильтры (к слову их сейчас нет вообще в рамках API-2). Последующие вехи пока не сформированы, скорее всего нужно будет подумать над возможностями использования аппаратного ускорения для декодирования (оно достаточно некрасиво и в самом FFmpeg реализовано - нужно делать много телодвижений).
В любом случае, я уже настоятельно рекомендую начинать использовать ветку api-2. В скором времени она заместит master-ветку, а текущий мастер переедет на ветку legacy.
За отзывы, багрепорты и пул-реквесты буду благодарен.
Очень хорошая статья на тему: 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 (Presentation Timestamp) закодированного пакета (AVPacke
t) или раскодированного фрейма (AVFrame
) в реальное время, выраженное в секундах (это же верно для DTS закодированного пакета). Представляет собой рациональное число типа AVRational
.
А вот теперь интересности, связанные с ним.
Внутри библиотек libavformat и libavcodec, входящих в состав FFMPEG time-base может храниться в двух контекстах:
AVStream
)AVCodecContext
)
При этом временные метки (далее я буду говорить только про PTS, если потребуется сказать про DTS - отдельно обращу внимание) могут храниться у AVFrame
(поле pts
) и AVPacket
(аналогично, поле pts
) тип всех временных меток (включая поле dts
у AVPacket
) - int64_t
.
Отсюда возникает резонный вопрос: как соотносить time-base и временные метки.
Короткий ответ:
AVStream
является основной для временных меток AVPacket
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.
Что получается:
av_read_fream()
возвращает пакеты с pts/dts в масштабе InStreamTimeBase.Масштабирования нужно делать только если значение временной метки не равно AV_NOPTS_VALUE.
За сим всё.