Распределение битов внутри GOP

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

TL;DR

Группа кадров (GOP) – это единица, которую современный энкодер бюджетирует целиком: один I-кадр и связанная с ним цепочка P- и B-кадров. Распределение битов – это цикл, который решает, как разделить общий битовый бюджет GOP между этими кадрами, чтобы картинка получилась максимально стабильной при минимальной цене. Тип кадра, уровень в иерархической B-пирамиде и частота повторного использования кадра в качестве референса – каждый из этих факторов оттягивает биты к кадрам, которые важнее: I-кадры получают скидку, опорные B-кадры – скидку поменьше, а «одноразовые» листовые B-кадры платят полную цену. Если распределение настроено верно, на типичном VOD-каталоге вы экономите 20–40% битрейта при той же визуальной чёткости; если нет – картинка пульсирует каждые две секунды, ровно там, где появляется новый I-кадр.

Почему это важно

Распределение битов внутри GOP – это слой, который превращает целевое значение rate-control «5 Мбит/с в среднем» в точный квантователь для каждого кадра. Продакт-менеджер, который понимает этот слой, перестанет просить инженеров «просто дать I-кадрам больше битов» – современный распределитель уже делает это, и перетюнинг только ухудшает результат. Стриминг-инженер, умеющий читать лог x264 и видеть, отработал ли macroblock-tree корректно, отлавливает регрессии качества до того, как они попадают в продакшн. Основатель, выбирающий сервис транскодирования, будет знать, какие вопросы задавать: глубину иерархической B-пирамиды, размер lookahead, включены ли MB-tree или CU-tree, соблюдает ли энкодер границы VBV/HRD по строкам или только по кадрам. Эта статья проходит по реальному механизму распределения – соотношения типов кадров, QP-офсеты по временным уровням, масштабирование по сложности и алгоритмы, учитывающие распространение информации (MB-tree, CU-tree, TPL), – и показывает настройки, о которых вы реально будете спорить в продакшне.

Что такое распределение битов и где оно находится

Внутри энкодера друг над другом располагаются три цикла. Внешний цикл – rate control: он выбирает цель (битрейт, среднее, фактор качества) и решает, сколько битов получает весь файл или следующая секунда. Внутренний цикл – принятие решений о режиме (mode decision): для одного блока он выбирает самое дешёвое кодирование при фиксированном балансе «качество против битов» (этот баланс называется лямбда, λ). Между ними сидит распределение битов. Распределитель берёт бюджет внешнего цикла, разносит его по кадрам внутри ближайшей группы кадров, затем превращает долю каждого кадра в параметр квантования (QP), который уже использует mode decision.

Распределение – отдельная задача, потому что кадры внутри GOP не равны. I-кадр кодируется с нуля и служит якорем, на котором висит всё остальное. P-кадр для большей части данных смотрит на предыдущий якорь. B-кадр смотрит и назад, и вперёд, а в современных кодеках сами B-кадры образуют пирамиду, где одни B-кадры являются опорой для других. Если дать всем кадрам один и тот же QP, якоря недополучают битов, а каждый последующий кадр, копирующий с них, наследует их артефакты сжатия. Распределитель – это цикл, который исправляет проблему.

Полезная аналогия. Бюджет автопутешествия не делится поровну между днями. На ночь в отеле в точке назначения вы тратите больше, чем на чашку кофе на заправке, потому что точка назначения – то, ради чего вся поездка. Распределение битов работает так же: больше тратится на кадры, на которые опирается остальная часть GOP.

Рис. 1. Три вложенных цикла внутри современного энкодера. Распределение битов сидит между rate control и mode decision и переводит цель по битрейту в QP для каждого кадра.

Первое разделение: по типу кадра

Самый старый компонент распределения битов, присутствующий в каждом видеокодеке со времён MPEG-1, – это соотношение по типам кадров. Каждый тип получает множитель квантователя относительно P-кадра, рабочей лошадки кодека. В x264 две ручки называются ipratio (шаг квантователя I → P) и pbratio (шаг P → B). Значения по умолчанию – 1.40 и 1.30. Это значит: если энкодер выбрал QP 22 для P-кадра, соответствующий I-кадр кодируется при QP 22 − 6 × log₂(1.40) ≈ 22 − 2.9 ≈ 19, а соответствующий B-кадр – при QP 22 + 6 × log₂(1.30) ≈ 22 + 2.3 ≈ 24. Множитель 6 в формуле берётся из правила семейства H.26x: «+6 QP» уменьшает битрейт вдвое.

Физическая причина таких значений по умолчанию. Биты I-кадра используются повторно всеми кадрами GOP в течение следующих ~2 секунд, поэтому 1 бит, потраченный на I-кадр, покупает примерно такое же качество, как 4 бита на P-кадре, который с него копирует. B-кадр «одноразовый» – никто больше с него не копирует, – поэтому переплата за B-кадр не покупает остальной части GOP ничего. Понизить QP I-кадра примерно на 3 и поднять QP B-кадра примерно на 2 – это попадает в оптимум на естественном видео с точностью до нескольких процентов.

Численный пример, чтобы закрепить. Допустим, средний P-кадр в потоке 4 Мбит/с занимает 133 000 бит. При ipratio 1.40 целевой размер I-кадра – около 1.40 × 133 000 ≈ 186 000 бит, а целевой размер B-кадра – около 133 000 / 1.30 ≈ 102 000 бит. На 30-кадровой GOP с 1 I, 9 P и 20 B-кадрами вы тратите 186 + 9 × 133 + 20 × 102 ≈ 3 423 кбит в секунду – близко к цели 4 Мбит/с ещё до того, как покадровый распределитель доводит результат.

Соотношения чувствительны к контенту. Зернистое шумное кино выигрывает от более низких значений (ближе к 1.15 / 1.15), потому что в I-кадре больше высокоэнтропийного шума, который стоит одинаково независимо от QP; плоская анимация терпит более высокие значения (ближе к 1.6 / 1.4), потому что копирование и деформация в P-кадре покрывают почти всю картинку. Современные энкодеры адаптируют это автоматически – конкретно для x264 алгоритм macroblock-tree замещает константные соотношения, когда включён (а он включён по умолчанию для любого пресета медленнее ultrafast).

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

Современные кодеки не выстраивают B-кадры плоской линейкой между P-якорями. Они выстраивают их пирамидой. Типичная 8-кадровая mini-GOP в порядке отображения выглядит так:

порядок отображения:  I   B   B   B   B   B   B   B   P
позиция:              0   1   2   3   4   5   6   7   8
временной уровень:    0   3   2   3   1   3   2   3   0

Кадры на уровне 0 (I и P) являются якорями для всего. Кадр 4 на уровне 1 кодируется следующим и служит якорем для уровней 2 и 3. Кадры 2 и 6 на уровне 2 – якоря для уровня 3. Кадры 1, 3, 5, 7 на уровне 3 не используются никем в качестве референса, и их можно сбросить, не задев никакой другой кадр. Декодер, которому нужна половинная частота кадров, просто пропускает уровень 3.

Каждый временной уровень получает собственный QP-офсет, и офсеты складываются в каскад. Уровень 0 получает самый низкий QP (больше битов). Каждый следующий уровень добавляет положительный офсет. В референсной реализации HEVC стандартный каскад для 8-кадровой mini-GOP – QP+1, QP+2, QP+3, QP+4 для уровней 0–3 соответственно, с более тонкими вариантами в зависимости от типа слайса. В SVT-AV1 каскад управляется данными из модели временных зависимостей (TPL) и зависит от пресета энкодера. Форма всегда одна: глубокие уровни платят больше за каждый бит, потому что их искажение никуда не распространяется.

Это и есть самая большая одиночная победа по качеству за последние 15 лет в инженерии кодеков. Иерархические B-кадры с QP-каскадом дали примерно 0.5 дБ Y-PSNR над плоским B-кодированием на тестовых последовательностях MPEG времён стандартизации HEVC. На естественном контенте при фиксированном битрейте это эквивалент бесплатного полушага по качеству – без изменения кодека, просто за счёт перераспределения битов к тем кадрам, от которых зависят остальные.

Рис. 2. 8-кадровая иерархическая B-пирамида. Кадры уровня 0 (I, P) – якоря для всего; каждый следующий уровень добавляет +1 QP, потому что кадры в нём используются реже и их искажение не идёт дальше.

Третье разделение: сложность каждого кадра

Соотношения по типам кадров и офсеты по временным уровням – статичны. Они не смотрят на то, что реально содержится в следующих 8 кадрах. Распределитель, управляемый сложностью, смотрит – он оценивает, насколько тяжело закодировать каждый предстоящий кадр, а затем перераспределяет бюджет GOP так, чтобы более сложные кадры получили больше битов.

Каноническая формула масштабирования в x264, прямая цитата из ratecontrol.txt Лорена Мерритта:

target_bits[i] = complexity[i] ** qcomp × scale

где complexity[i] – стоимость кадра i в битах при референсном QP (берётся из первого прохода в режиме два прохода или из лукахеда на половинном разрешении в одном проходе), qcomp – ручка между 0 и 1, а scale – то, что делает сумму равной бюджету rate-control. При qcomp = 0 все кадры получают одинаковое число битов (constant bitrate, большие колебания качества). При qcomp = 1 все кадры получают биты строго пропорционально сложности (constant quantizer, большие колебания битрейта). Значение по умолчанию 0.60 – эмпирически найденный оптимум для человеческого восприятия на естественном видео: он тратит чуть меньше, чем пропорционально, на сложных сценах (глаз маскирует артефакты в движении) и чуть больше, чем поровну, на простых сценах (так статичные фоны не показывают бэндинг).

Численный пример, чтобы сделать распределение по сложности конкретным. Пусть в GOP три кадра со стоимостями первого прохода при QP 22 равными 80, 200 и 50 кбит, а целевой бюджет GOP – 270 кбит. Равное распределение даст по 90 кбит каждому – катастрофически недокормит средний кадр на 200 кбит. Строго пропорциональное даёт 80/(80+200+50) × 270 = 65, 162 и 41 кбит – близко к оригиналам, но равномерно сжато. Дефолт x264 при qcomp 0.60 даёт 80^0.6, 200^0.6, 50^0.6 (= 14.6, 24.6, 10.5 в условных единицах), нормированных к сумме 270: 81, 137 и 58 кбит. Сложный кадр получает меньше своей строгой доли (137 < 162), потому что перцептивная маскировка прощает потери, а простые кадры получают небольшую надбавку, чтобы их статичный контент остался чистым.

В однопроходном режиме оценка сложности приходит из lookahead x264: он гоняет быструю оценку движения на половинном разрешении в скользящем окне (по умолчанию 40 кадров, до 250 с --rc-lookahead), фиксирует стоимость residual (SATD – сумма абсолютных значений преобразованных разностей) и использует её как прокси сложности. В двухпроходном режиме оценка lookahead заменяется реальной стоимостью первого прохода – точнее, но удваивает время кодирования.

Четвёртое разделение: распределение, учитывающее распространение (MB-tree, CU-tree, TPL)

Соотношения по типам кадров и сложность по кадру останавливаются на уровне кадра. Они не знают, что блок (10, 5) в кадре 4 станет референсом для блока (10, 5) в кадрах 5, 6, 7 и 8 – а значит, дополнительные биты на нём купят качество сразу для пяти кадров. Распределение, учитывающее распространение, закрывает этот пробел: оно распределяет биты на уровне блока, взвешивая по тому, сколько будущих блоков с этого блока скопируют.

Три реализации, с которыми вы столкнётесь в продакшне:

Macroblock-tree (MB-tree) – алгоритм x264, написанный Джейсоном Гарретт-Глейзером и Лореном Мерриттом в 2009. Энкодер запускает lookahead, чтобы оценить для каждого макроблока 16×16, как часто этот блок используется в качестве референса блоками более поздних кадров в окне lookahead. Часто используемые блоки получают поблочную скидку QP; быстро затираемые – небольшой штраф. Скидка может достигать 6–10 QP для неподвижного фона, который копируется на протяжении двух секунд. На естественном контенте MB-tree экономит около 5–10% битрейта при том же SSIM по сравнению с плоским покадровым распределением, и стоит почти ничего – lookahead, который ему нужен, уже работает для расстановки B-кадров и детекции смены сцен.

CU-tree – HEVC-эквивалент x265. Принцип идентичен, масштабируется с макроблоков 16×16 на единицы кодирования (CU) переменного размера HEVC (до 64×64 в ранних версиях и 128×128 с расширением screen-content). Статья Пуррезы и соавторов 2018 года («Optimize x265 Rate Control: An Exploration of Lookahead in Frame Bit Allocation and Slice Type Decision») оценила выигрыш в 2–8% BD-rate (Bjøntegaard-delta bitrate) на общих тестовых условиях MPEG, в зависимости от пресета.

Temporal dependency model (TPL) – трекер распространения эры AV1, встроенный в libaom и принятый SVT-AV1 в версии 0.9 (2021). TPL формализует то, что MB-tree делал эмпирически: решает линейную программу над всей GOP, минимизирующую сумму искажений, взвешенных по значению «r0» каждого блока (доля его rate-distortion-стоимости, которая распространяется в будущие блоки). Выигрыш в продакшен-потоках AV1 – 5–12% BD-rate относительно baseline без TPL, верхний край – на контенте с высоко-структурированным движением (спорт, анимация).

Общий лейтмотив: распределение, учитывающее распространение, – самая ценная техника на основе lookahead в современных энкодерах. Отключите её – и вы отдадите 5–15% битрейта при том же качестве на всём, кроме малоподвижных «говорящих голов» (где распространять нечего).

Рис. 3. Распределение битов с учётом распространения. Энкодер считает для каждого блока текущего кадра, сколько будущих блоков в окне lookahead копируют с него. Часто используемые блоки получают скидку QP; быстро затираемые – небольшой штраф.

Как современные энкодеры распределяют работу – сравнение

Стратегия похожа во всех энкодерах, но имена ручек и значения по умолчанию различаются. Таблица ниже покрывает то, что вы увидите в продакшне сегодня.

ЭнкодерРучки типов кадровРучки временного уровняРаспределитель по сложностиТрекер распространенияLookahead по умолчанию
x264 (H.264)--ipratio 1.40, --pbratio 1.30неявно через B-пирамиду + MB-tree--qcomp 0.60MB-tree (включён по умолчанию)40 (--rc-lookahead)
x265 (HEVC)--ipratio 1.40, --pbratio 1.30--bframe-bias, таблица офсетов--qcomp 0.60CU-tree (включён по умолчанию)20 (--rc-lookahead)
libaom-AV1настраивается по проходу, скрытоиерархическая, 5 уровней по умолчаниюстатистика первого прохода + сегментный qTPL (включён для --good)--lag-in-frames 19
SVT-AV1настраивается по пресету, скрыто5 или 6 уровней, управляется пресетомзначения TPL r0TPL (включён по умолчанию)управляется пресетом
VVenC (H.266/VVC)управляется профилемтаблица QP-офсетов по уровнямкарта стоимостей rate-distortionлагранжиан по уровням16 по умолчанию
libvpx-VP9похоже на libaomдо 3 уровней иерархиистатистика первого проходабазовое распространение (без TPL)--lag-in-frames 25

Два паттерна, которые стоит заметить. Во-первых, семейство H.26x (x264, x265) выводит ручки наружу, чтобы оператор мог тюнить вручную. Семейство AOMedia (libaom, SVT-AV1) прячет их за пресетами и TPL, потому что модель управляется данными. Во-вторых, каждый современный энкодер включает трекинг распространения по умолчанию – MB-tree, CU-tree и TPL все «из коробки» включены. Если кодировать без них, вы отдаёте биты ни за что.

Два прохода, один проход, и что меняется внутри распределителя

В режиме два прохода у распределителя – полная информация. Первый проход записывает stats-файл с точной стоимостью каждого кадра при placeholder-QP, а второй проход использует эти стоимости как вход «сложности» для формулы выше. Распределитель может спланировать распределение по GOP ещё до того, как закодирует хотя бы один кадр второго прохода, и результат укладывается в 0.5% от целевого битрейта на 90-минутном файле.

В однопроходном режиме у распределителя есть только то, что видит lookahead. Чем дальше тянется lookahead, тем ближе однопроходное качество к двухпроходному; компромисс – задержка кодирования, потому что энкодер не может выпустить кадр N, пока не проанализировал кадры N+1 … N+lookahead. Для VOD-задач lookahead 60 кадров – sweet spot, дальше кривая качества выходит на плато. Для live lookahead ограничен бюджетом сквозной задержки: WebRTC-конвейеры с задержкой меньше секунды используют lookahead 0–8; HLS-конвейеры с задержкой до 3 секунд – 16–40.

Распределитель битов делает ещё одну компенсацию в обоих режимах: после кодирования каждого кадра, когда реальная битовая стоимость известна, QP будущих кадров подстраиваются, чтобы скомпенсировать ошибку предсказания. Если распределитель предсказал 100 кбит для следующего P-кадра, а получил 130, QP последующих кадров масштабируются на 100/130, чтобы вернуть дефицит. Сила этой подстройки – разница между VBV-строгим CBR (вернуть дефицит жёстко, принять колебание качества) и более мягким ABR (вернуть мягко, принять разброс битрейта ±10%).

Числа, которые реально показывает лог энкодера

Когда вы запускаете x264 с --verbose или x265 с --csv-log-level 2, строка по каждому кадру показывает работу распределителя. Типичная строка x264:

[debug] frame=  243 QP=22.0 NAL=2 Slice:P Poc:486 I:174  P:1542 SKIP:684 size=10623 bytes

QP равен 22.0, но это среднее по кадру. Реальные QP по макроблокам варьируются из-за поблочной скидки MB-tree. Энкодер также печатает однострочную гистограмму в конце, показывающую распределение QP:

[info] x264 [info]: frame I:23   Avg QP:17.85  size:113042
[info] x264 [info]: frame P:1085 Avg QP:21.21  size: 13420
[info] x264 [info]: frame B:2367 Avg QP:23.42  size:  4811

Три вещи, которые читаются из этого. Во-первых, средний QP I-кадра (17.85) сидит примерно на 3.4 QP ниже QP P-кадра (21.21) – это --ipratio 1.40 в действии (6 × log₂(1.40) ≈ 2.9 плюс небольшая скидка MB-tree за распространение, которое эти I-кадры создают). Во-вторых, средний QP B-кадра (23.42) сидит на 2.2 QP выше P-кадра – это --pbratio 1.30 в действии (6 × log₂(1.30) ≈ 2.3). В-третьих, средний размер кадра в соотношении (113 : 13 : 4.8 кбит) – примерно 23 : 3 : 1, что близко к правилу: I-кадр ~5× P-кадра, P-кадр ~2.5× B-кадра при том же воспринимаемом качестве. Если в вашем логе соотношения сильно отклоняются от этих чисел – например, I-кадр относится к P как 50:1, – энкодер моряит P/B-кадры, и картинка будет пульсировать на каждом ключевом кадре.

Распространённая ошибка: ручная подкрутка ipratio при включённом MB-tree

Самая частая продакшн-ошибка – прочитать старый гайд, решить «у меня зернистый источник, надо понизить ipratio до 1.15» и так и отгрузить. Проблема: x264 с MB-tree (а это умолчание) игнорирует ipratio для решения о распределении и использует веса распространения. Ручной ipratio применяется только в fallback-пути, который используется на первых нескольких кадрах, пока lookahead не наполнился. Чистый эффект: оператор думает, что тюнит энкодер, энкодер использует свою модель, и вывод идентичен дефолтному.

Правильное действие – одно из двух. Либо принять дефолты MB-tree – они тюнились командой x264 и Лореном Мерриттом на тестовом наборе MPEG с 2009 года и выигрывают почти для любого входа. Либо, если есть весомая причина переопределить (например, low-latency live-ингест с --rc-lookahead 0), передать --no-mbtree явно, чтобы распределитель упал в путь по соотношениям, и ваш тюнинг реально вступил в силу. Та же оговорка для --no-cutree в x265 и для отключения TPL в libaom (--enable-tpl-model=0). Если вы выключили трекер распространения, сами того не зная, – теряете 5–15% битрейта; если выключили осознанно, потому что бюджет задержки запрещает lookahead, – нужно перетюнить ручки соотношений вручную.

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

Распределение битов невидимо, пока всё хорошо, и становится первым, что замечает зритель, когда что-то пошло не так. В live-конвейерах, которые мы строили для клиентов в видеоконференциях и видеостриминге, вопрос тюнинга почти всегда сводится к задержке против качества: сколько lookahead может позволить себе use case, что прямо переводится в то, насколько агрессивным может быть распределитель внутри каждой GOP. Для OTT- и VOD-каталогов – телемедицинские архивы, e-learning-библиотеки, записи видеонаблюдения – мы опираемся на двухпроходное распределение с полным трекингом распространения, потому что цена задержки здесь не применяется, а экономия битрейта прямо снижает счёт за CDN. Конфигурация, которую мы отгружаем, зависит от поколения кодека и от того, что умеет декодировать клиентская цепочка, но базовое решение всегда одно: выбрать циклы, которые можем себе позволить, остальное отдать дефолтам энкодера и читать покадровый лог, чтобы убедиться, что распределитель делает именно то, что вы думаете.

Главное

  • Битовый бюджет GOP делится четыре раза: по типу кадра, по временному уровню, по сложности кадра, по весу распространения блока.
  • Дефолтные x264 ipratio 1.40 и pbratio 1.30 дают I-кадру ~3 QP скидки и B-кадру ~2 QP штрафа относительно P-кадра.
  • Иерархические B с QP-каскадом дали ~0.5 дБ Y-PSNR бесплатно на тестовом наборе HEVC.
  • MB-tree, CU-tree и TPL отвечают на один вопрос – как часто используется этот блок – и все экономят 5–15% битрейта.
  • Двухпроходное распределение укладывается в 0.5% от цели на полнометражном файле; одному проходу нужен lookahead 40–60.
  • Ручной тюнинг ipratio при включённом MB-tree не делает ничего; либо принимайте дефолты, либо сначала передайте --no-mbtree.

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

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

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