Пайплайн транскодинга вглубь: decode → filter → encode → mux

Автор: Николай СапуновОбновлено: август 202622 мин чтения
Содержание статьи +

TL;DR

Пайплайн транскодинга – это конвейер из четырёх стадий, который берёт видеофайл в одном формате и выдаёт другой: demux, decode, filter, encode и mux. Каждая стадия решает одну задачу, передаёт результат следующей и в современных инструментах вроде FFmpeg 7 работает в своём потоке. Большинство продакшен-багов – рассинхронизация звука и картинки, замёрзшие превью, взрывной рост счёта за CPU – это путаница в том, какая стадия отвечает за какую задачу. Эта статья проходит пайплайн от начала до конца: математика, типичные сценарии отказа и плейбук оператора в 2026 году.

Зачем это нужно

За каждым видеосервисом, которым вы когда-либо пользовались – Netflix, YouTube, Zoom, дашборд камер видеонаблюдения, портал e-learning – стоит пайплайн транскодинга. Когда вы заливаете 4K-файл с телефона и он гладко проигрывается у коллеги на ноутбуке с медленным интернетом – четыре стадии отработали по порядку. Когда звук плывёт на секунду от картинки или счёт от облака утраивается после обычного деплоя – одна из стадий ошиблась. Эта статья – для продакт-менеджера, основателя или операционного руководителя, которому нужно понимать пайплайн достаточно, чтобы говорить с инженерами, оценивать проект и читать отчёт об инциденте, не становясь при этом видеоинженером.

Пайплайн на одном дыхании

Каждый транскодер – от Raspberry Pi с FFmpeg до AWS Elemental MediaConvert на сотнях ядер – следует одному и тому же рецепту из пяти шагов.

Пять шагов по порядку: demux, decode, filter, encode и mux. Слово «транскодинг» – это краткое обозначение всех пяти, выполняемых подряд, но большинство команд говорят о пайплайне как о четырёх стадиях, потому что demux на практике почти неотделим от decode. Мы используем обе формулировки: пайплайн = decode → filter → encode → mux в заголовке, при этом demux разбираем отдельно как «парадную дверь».

Полезная аналогия – кухня ресторана. Файл-контейнер (MP4, MKV) – это запечатанная коробка с продуктами; шаг demux её распаковывает. Decoder – повар-приготовитель, который превращает упакованные ингредиенты обратно в сырьё. Filters – это собственно готовка: нарезка, обжарка, сервировка. Encoder – повар на линии, упаковывающий каждое блюдо в контейнер для доставки. Muxer – курьер, складывающий всё в один пакет для клиента. Если любой из них ошибётся по времени – приготовитель медлит, линейный повар забывает блюдо, курьер разливает суп в сумке – заказ придёт неправильным.

Рисунок 1. Пайплайн транскодинга от начала до конца. Сжатые packets идут в декодер; raw frames проходят через фильтры в энкодер; сжатые packets идут в muxer. Тип данных меняется ровно два раза.

Стадия 0 – Demux: открыть коробку

Видеофайл, который вы скачиваете, – это не один поток пикселей. Это контейнер – MP4, MKV, MOV, WebM, MPEG-TS – в котором лежат один или несколько сжатых потоков (видео, аудио, субтитры), переслоённых с тайм-кодами и метаданными. Demuxer читает этот контейнер и достаёт каждый поток отдельно, packet за packet.

Packet – фундаментальная единица сжатого медиа. В модели данных FFmpeg это AVPacket. Один packet обычно содержит один сжатый видеокадр или короткий кусок сжатого аудио плюс две метки времени: decode timestamp (DTS), которая сообщает декодеру, когда обработать packet, и presentation timestamp (PTS), которая сообщает плееру, когда показать или проиграть результат. Для простого аудио эти метки выглядят одинаково, но для видео они различаются всегда, когда кодек использует B-кадры – кадры, которым нужны будущие кадры для декодирования, то есть порядок воспроизведения и порядок декодирования различны.

Если вы когда-нибудь задумывались, почему плеер заикается в начале воспроизведения, – это часть причины. Декодеру нужно несколько packets в буфере в порядке DTS, прежде чем он сможет выдать первый кадр в порядке PTS. Стадия demux – это первое место, где тайм-коды должны быть абсолютно правильными. Потеряете миллисекунду здесь – позже звук уплывёт от картинки.

Demuxing – самый дешёвый шаг в пайплайне. По сути это задача парсинга: никакой математики над пикселями, никакой кодек-работы, просто чтение раскладки контейнера. На современном CPU demuxer тянет тысячи packets в секунду на поток.

Стадия 1 – Decode: packets становятся пикселями

Декодер берёт сжатые packets от демуксера и превращает их обратно в несжатые кадры. Тип данных здесь меняется в первый раз: на входе AVPacket (сжатые байты), на выходе AVFrame (raw-пиксели для видео, raw-сэмплы для аудио).

На декодинг уходит большая часть CPU, когда фильтрация не происходит. Поток H.264 1080p, декодируемый программно на 60 fps на современном ноутбучном CPU, стоит ~10–15% одного ядра. Та же работа на аппаратном декодере GPU (NVIDIA NVDEC, Intel Quick Sync, Apple VideoToolbox, AMD VCN) для CPU практически бесплатна – fixed-function блок GPU делает её в выделенном кремнии, потребляя ~5–15 ватт.

Здесь возникает одно критическое архитектурное решение: где живут декодированные кадры? Программный декодинг выдаёт кадры в системной памяти (RAM). Аппаратный – в памяти GPU (VRAM). Если следующая стадия – filter или encode – тоже работает на GPU, кадр стоит держать в VRAM. Копирование 4K-кадра из VRAM в RAM и обратно стоит дороже, чем сам декодинг. FFmpeg выставляет это через -hwaccel cuda -hwaccel_output_format cuda, который держит кадры в CUDA-памяти на всём пайплайне. Это одна из самых распространённых десятистрочных оптимизаций в hardware-accelerated транскодере – многие продакшен-команды теряют 3–5× ускорения, оставляя output format по умолчанию в nv12 вместо cuda.

Третье архитектурное решение на стадии декодинга – stream copy. Если задача только сменить контейнер – например, переупаковать MP4 в MPEG-TS для аппаратного декодера, понимающего только TS, – декодировать вообще не нужно. Можно сделать demux, затем mux в новый контейнер, пропустив decode/filter/encode целиком. Это называется remuxing или stream copy, в FFmpeg вызывается через -c copy. Стоит почти ничего, потому что пикселей никто не трогает. Remuxing – правильный ответ примерно в 30% реальных пайплайнов и почти всегда упускается на первой итерации.

Стадия 2 – Filter: стадия, которая реально меняет картинку

Filter – это место, где пайплайн делает то, что виден пользователю. Уменьшить 4K-мастер до 720p-рендиции, сделать deinterlace для бродкаст-захвата, вшить субтитры, наложить лого, убрать шум на тёмном клипе, сконвертировать pixel format из yuv422p10le в yuv420p, чтобы потребительский энкодер его принял, – всё это фильтры.

FFmpeg моделирует это как filtergraph: направленный ацикличный граф из узлов-фильтров, соединённых рёбрами. Каждый узел принимает один или несколько кадров, делает одно преобразование и передаёт результат дальше. Простая цепочка – yadif,scale=1280:720,format=yuv420p – означает «deinterlace, потом scale в 720p, потом convert в 4:2:0 chroma». Сложный filtergraph (-filter_complex) может разделить один входной кадр на три ветки, отскалировать каждую в свою рендицию и подать сразу в три разных энкодера. Этот паттерн – вся техническая база ABR (adaptive bitrate) ladders.

Классическая цепочка фильтров для VOD ABR ladder идёт сверху вниз: deinterlace, если источник чересстрочный, нормализация color primaries и transfer function для HDR-to-SDR или наоборот, scale до целевого разрешения, установка chroma subsampling в формат, который ожидает энкодер, и добавление брендового overlay. Аудиофильтры идут параллельной цепочкой: resample до целевой sample rate, сведение в целевой channel layout, нормализация громкости по бродкаст-стандарту вроде ITU-R BS.1770 / EBU R128.

Filters – второй по затратам CPU потребитель в пайплайне. Чистый scale на CPU стоит примерно столько же, сколько decode для того же разрешения; конверсия chroma subsampling – бесплатно; серьёзный денойзер (nlmeans, bm3d) может стоить 10× от decode. Hardware-accelerated фильтры есть у каждого GPU-вендора – scale_npp и scale_cuda у NVIDIA, scale_qsv у Intel, scale_vt у Apple – и их стоит использовать, когда соседние стадии тоже идут на том же GPU.

Рисунок 2. Один 4K-вход разворачивается в трёхступенчатый ABR ladder через один комплексный filtergraph и три параллельных энкодера. Сплит происходит после decode и идёт один раз на входной кадр, затем каждая рендиция кодируется независимо.

Стадия 3 – Encode: пиксели снова становятся packets

Encoder – это инверсия декодера. На входе raw AVFrame, на выходе сжатые AVPacket, каждый с PTS и DTS, готовые к мультиплексированию в контейнер. Encoder – самая дорогая стадия любого пайплайна. Правило большого пальца: кодирование того же контента стоит в 5–50× больше CPU, чем декодирование, в зависимости от кодека и пресета. 60-секундный клип 1080p декодируется за 2–3 секунды, а кодируется за 30 секунд через x264 на slow или за 90–180 секунд через SVT-AV1 на preset 5 (имплементации энкодеров подробно сравнивают семь основных энкодеров).

Два решения на стадии encode двигают большую часть денег. Первое – выбор кодека и энкодера. H.264 с x264 для совместимости, H.265 с x265 для HDR-каталогов, AV1 с SVT-AV1 для новых пайплайнов, где decoder-база поддерживает AV1. Второе – rate control mode – CRF для постоянного качества, CBR для фиксированного битрейта, VBR для capped variable и capped-CRF для современного ABR sweet spot. Мы подробно разбираем это в rate control: CBR, VBR, CRF; для взгляда «через пайплайн» важно то, что encoder – единственная стадия, которая вообще разговаривает с бюджетом битрейта. Demux, decode и filter к бюджету слепы.

Hardware-энкодеры заслуживают отдельного упоминания. NVIDIA NVENC, Intel Quick Sync, Apple VideoToolbox, AMD AMF и ASIC-ускорители вроде NETINT кодируют в 10–50× быстрее софта ценой некоторого падения качества при равном битрейте. Обновление NVIDIA в январе 2026 расширило ultra-high-quality (UHQ) режим до AV1 на Blackwell-кремнии, сократив разрыв в качестве к software AV1 до нескольких процентов VMAF при 3× throughput. Для сервисов, отгружающих миллионы стримов, hardware-энкодеры больше не компромисс – они стандарт. Матрица вендоров – в Hardware-ускорение: NVENC, VPU, ASIC.

Стадия 4 – Mux: собрать посылку

Muxer – финальная стадия. Он берёт сжатые packets от одного или нескольких энкодеров, перемежает их в порядке доставки, пишет заголовки контейнера (atom moov в MP4, segment index в HLS) и выдаёт выходной файл или поток. Как и demuxer, muxer дешёв – почти не ест CPU – но у него две задачи, которые легко завалить.

Первая – выравнивание тайм-кодов. Каждый выходной packet от каждого энкодера должен лежать на согласованной шкале времени. MP4 требует, чтобы PTS монотонно росли, без прыжков назад. Некоторые downstream-плееры отвергают любой поток с отрицательным DTS. Muxer применяет глобальный offset, чтобы загнать тайм-коды каждого потока в положительный диапазон, потом перемежает packets в порядке DTS, чтобы стриминговый плеер мог декодировать их в порядке прибытия. Ошибётесь на 100 миллисекунд – получите слышимую рассинхронизацию. Ошибётесь на 5 секунд – плеер откажется играть.

Вторая задача – packaging, фрагментация потока на сегменты для HTTP-доставки. VOD MP4 содержит один atom moov и один непрерывный блок mdat. Streaming-ready fragmented MP4 (fMP4 или CMAF) содержит один moov и много пар moof + mdat, каждая по 2–6 секунд. Тот же выход энкодера, тот же энкодер; разница только в том, как muxer фрагментирует. CMAF – дефолт 2026 года, потому что одни и те же fMP4-сегменты работают и для HLS, и для DASH из тех же файлов (см. Контейнеры: MP4, fMP4, MKV, WebM, MOV, MPEG-TS).

Типы данных, которые ходят между стадиями

Весь пайплайн становится проще, как только вы запомните, что между стадиями ходит ровно два типа данных. Packets (AVPacket в FFmpeg) – это сжатые байты с тайм-кодами. Frames (AVFrame) – это raw, несжатые сетки пикселей (для видео) или аудиосэмплы (для аудио).

Каждая стадия преобразует один тип в другой:

  • Demux: bytes на входе (файл-контейнер) → packets на выходе.
  • Decode: packets на входе → frames на выходе.
  • Filter: frames на входе → frames на выходе (тот же тип, другое содержимое).
  • Encode: frames на входе → packets на выходе.
  • Mux: packets на входе → bytes на выходе (файл-контейнер).

Когда вы описываете баг транскодинга инженеру, называние типа данных на стадии, где баг проявился, режет время диагностики пополам. «PTS портится на packets, выходящих из энкодера» – это точный bug report. «Видео сломано» – нет.

Threading и шедулер FFmpeg 7

Большую часть двадцатилетней истории FFmpeg пайплайн транскодинга работал как один поток, протягивающий по одному packet через все стадии по порядку. У декодеров и энкодеров был свой внутренний threading, но внешний цикл был последовательным, и медленный энкодер блокировал всё за собой.

FFmpeg 7.0, выпущенный в апреле 2024, ввёл полноценный многопоточный шедулер – лид-мейнтейнер Антон Хирнов описал это как «один из самых сложных рефакторингов FFmpeg CLI за десятилетия». Каждый demuxer, decoder, filtergraph, encoder и muxer теперь работает в своём потоке, а центральный Scheduler гоняет packets и frames между ними через ограниченные очереди. Изменение разблокировало параллелизм, который раньше был невозможен: одна команда может декодировать вход, фильтровать его в три ветки, кодировать каждую рендицию на отдельном наборе ядер и мультиплексировать всё, причём ни одна стадия не блокирует другую.

Практический эффект – более линейный скейлинг на многоядерных машинах. На 32-ядерном Threadripper трёхступенчатый ABR-транскод, занимавший 90 секунд на FFmpeg 6, заканчивается за 35–40 секунд на FFmpeg 7 без изменения команды. CPU тот же; пайплайн просто больше не одно­поточный между стадиями. Если у вас self-hosted транскодер-ферма и вы ещё не обновились с FFmpeg 6, апгрейд – самое высокорентабельное изменение, которое можно сделать в 2026 году.

Рисунок 3. Шедулер FFmpeg 7 держит каждую стадию пайплайна на своём потоке, с ограниченными очередями между ними. Экономия по wall-clock на многоядерной машине обычно 2–3× для ABR ladders.

Разобранный пример: математика 3-ступенчатого ABR-транскода

Возьмём конкретную нагрузку: один 60-секундный 4K HDR mezzanine, транскодируемый в трёхступенчатый ABR ladder 1080p / 720p / 480p H.264 SDR для HLS-доставки. На одной 16-ядерной машине с FFmpeg 7 и софтверными x264-энкодерами CPU-бюджет по стадиям раскладывается так.

СтадияCPU за секундуВсего на 60-секундный клип
Demux 4K HDR HEVC~0.5% одного ядра0.3 core-секунды
Decode 4K HDR HEVC (софт)~25% одного ядра15 core-секунд
Filter: tonemap + scale ×3~30% одного ядра (общий)18 core-секунд
Encode 1080p H.264 (x264 slow)~150% одного ядра90 core-секунд
Encode 720p H.264 (x264 slow)~70% одного ядра42 core-секунды
Encode 480p H.264 (x264 slow)~30% одного ядра18 core-секунд
Mux 3 выходов в fMP4 / HLS~0.5% одного ядра0.3 core-секунды
Итого~184 core-секунды

Читаем таблицу: кодирование доминирует, забирая ~82% CPU. Decoding + filtering – ~18%. Demux и mux – погрешность округления. Любой счёт за транскодинг следует этой пропорции. Срежете стоимость энкодера вдвое – переключившись на hardware-энкодер, понизив пресет, сократив ladder – срежете весь счёт вдвое. Оптимизировать demux – потерянное время.

Теперь добавим железо. Та же нагрузка на той же 16-ядерной машине с NVDEC для decode, scale_npp для scale и NVENC для всех трёх энкодеров:

СтадияCPU за секундуЗаметки
Demux~0.5% одного ядрабез изменений
Decode (NVDEC)~0% CPU, GPU ~10WGPU fixed-function
Filter (scale_npp ×3)~0% CPU, GPU ~5Wтот же GPU
Encode (NVENC ×3)~0% CPU, GPU ~30Wfixed-function
Mux~0.5% одного ядрабез изменений
Итого~1 core-сек + 45W GPUwall clock: 10–15 сек

Wall clock падает с ~12 секунд (16 ядер забиты) до ~10–15 секунд, но 16 ядер CPU теперь свободны транскодировать следующий клип. Одна GPU-машина может перегонять ~20–40 таких клипов в минуту, тогда как чисто CPU-вариант тянет 5–8. В этом весь экономический смысл hardware-транскодинг-пайплайнов в 2026 году.

Типичные ошибки в пайплайне

«Ошибка: перекодируете там, где нужен был remux. Частая просьба – «сконвертируй этот MKV в MP4» – это remux, не транскод. Запуск ffmpeg -i in.mkv out.mp4 без -c copy декодирует и перекодирует всё, занимая в 10× больше времени и теряя качество. Правильный ответ – ffmpeg -i in.mkv -c copy out.mp4. Всегда спрашивайте, что именно нужно: «сменить контейнер» (remux) или «сменить кодек / разрешение / битрейт» (transcode).»
«Ошибка: гоняете frames между RAM и VRAM без нужды. Пайплайн, который декодирует на GPU, фильтрует на CPU и кодирует на GPU, тратит большую часть времени на копирование кадров через PCIe. Либо держите всё в VRAM через -hwaccel cuda -hwaccel_output_format cuda и используйте GPU-фильтры, либо держите всё в RAM. Смешивать два пути почти всегда медленнее, чем любой чистый.»
«Ошибка: забываете про аудио. Типичный пайплайн имеет 1 видеопоток и 1–6 аудиопотоков плюс опциональные субтитры. Забыть указать -map 0:a – значит молча уронить звук, и выход проиграется в тишине. Всегда проверяйте, что все ожидаемые потоки доходят до muxer.»
«Ошибка: доверяете дефолтным тайм-кодам live ingest. RTMP и SRT live ingests иногда выдают отрицательный DTS или wrapped timestamps после долгого аптайма. Если muxer их отвергает, выход останавливается. Безопасный дефолт – -fflags +genpts -avoid_negative_ts make_zero, чтобы muxer пересоздал чистую монотонную шкалу.»

Где здесь Фора Софт

Фора Софт строит и эксплуатирует транскодинг-пайплайны с 2005 года в области конференций, видеостриминга, OTT и IPTV, видеонаблюдения, e-learning, телемедицины и AR/VR. Повторяющийся урок по 239+ проектам: пайплайн – не место, откуда у большинства команд возникают баги, а место, где баги становятся видны. Камера с неверными color primaries заставляет энкодер выдавать неправильный цвет; энкодер с неверным rate-control mode заставляет muxer выдавать поток, который CDN отказывается кэшировать; CDN с неверным cache key показывает зрителям чужие DRM-токены. Хорошо эксплуатировать пайплайн – значит знать каждую стадию настолько хорошо, чтобы читать flame graph и понимать, какая из них лжёт.

Ключевые выводы

  • Пайплайн – это пять стадий: demux, decode, filter, encode, mux – каждая делает ровно одну работу.
  • Между стадиями ходят ровно два типа данных: сжатые packets и несжатые frames.
  • На encoding уходит 80%+ CPU; demux и mux – погрешность округления.
  • Шедулер FFmpeg 7 – бесплатные 2–3× ускорения на многоядерных машинах.
  • Remuxing – правильный ответ ~30% случаев и почти всегда упускается первой итерацией.
  • Держите frames на одном устройстве (RAM или VRAM) на весь пайплайн; копии через PCIe убивают производительность.

Что читать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.