Forward Error Correction (FEC), In-Band FEC и RED-избыточность

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

Кратко

Forward error correction, или FEC, – это приём, при котором немного дополнительного звука отправляется заранее, так что при потере пакета приёмник восстанавливает его, не запрашивая повторно: без ожидания и без обратного хода по сети. В реальном времени вам встретятся два механизма FEC: встроенный in-band FEC кодека Opus, который вкладывает копию предыдущего кадра низкого качества в следующий пакет, и более старый формат RED из 1997 года (RFC 2198), который оборачивает целые предыдущие пакеты в текущий ради более сильной защиты от потери нескольких кадров. Компромисс всегда один: FEC тратит полосу на каждом пакете независимо от того, случилась потеря или нет, – поэтому он помогает на «плохих» каналах и тихо расходует битрейт на чистых, а если потеря вызвана перегрузкой сети, добавление FEC только ухудшит ситуацию. Эта статья объясняет простым языком, почему повторная передача не годится для живого звука, как именно работают Opus in-band FEC и RED на проводе, сколько стоит каждый из них и по какому правилу решать, включать ли их.

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

Если вы строите видеозвонки, телемедицину, контакт-центры или онлайн-классы, именно потеря пакетов решает, прозвучит ли звук чисто или превратится в роботизированное заикание, – а FEC и есть главный рычаг, чтобы исправить это до того, как слушатель услышит пропуск. Выбор между Opus in-band FEC и RED, а также битрейт, который вы готовы на любой из них потратить, – это продуктовое решение с реальной ценой и реальными последствиями для качества, а не деталь, которую стоит оставить на значениях по умолчанию. Статья написана для продукт-менеджера, основателя или операционного руководителя, которому нужно понять, что покупает FEC, сколько он стоит и когда вредит, – чтобы верно прочитать жалобу на качество и поставить инженерам задачу нужными словами. Старший инженер тоже найдёт здесь каждый факт со ссылкой на управляющие RFC, спецификацию Opus и инженерную историю WebRTC.

Проблема: пакет нельзя запросить заново

Начнём с ограничения, которое отметает очевидное решение. Когда пакет теряется в сети, проще всего попросить отправителя прислать его снова. Этот приём называется повторной передачей, сокращённо RTX, и для RTP-медиа он определён в IETF RFC 4588 (июль 2006). Для скачивания файла или загрузки веб-страницы повторная передача – именно то, что нужно: вы немного ждёте, недостающий кусок приходит, и ничего не потеряно.

Для живого разговора повторная передача обычно приходит слишком поздно. Вот почему – с числами вслух. Запрос пакета заново стоит одного сетевого кругового обхода (round trip) – это время, пока ваш запрос дойдёт до отправителя, плюс время, пока пересланный пакет вернётся:

время кругового обхода (RTT)   = до отправителя + обратно
пример трансконтинентального RTT = 150 ms
время удержания jitter buffer    = 60 ms
вердикт: 150 ms > 60 ms  →  пересланный пакет приходит после своего слота

Приёмник не может поставить разговор на паузу и ждать. В WebRTC аудиоустройство требует свежий кусочек звука – ровно 10 миллисекунд – точно вовремя, 100 раз в секунду, бесконечно (этот «такт» мы разбираем в статье про аудиоконвейер WebRTC). Если пересланный пакет не вернётся до момента своего воспроизведения, он бесполезен. Повторная передача помогает только тогда, когда круговой обход короток относительно того, как долго буфер дрожания приёмника готов удерживать звук, – об этом подробно в статье про буфер дрожания NetEQ. На канале с большой задержкой – мобильном или трансконтинентальном – это условие часто не выполняется.

Значит, нужен способ восстановить потерянный пакет без кругового обхода. Это и есть forward error correction.

Что значит «forward»: платить вперёд

Forward error correction – это семейство приёмов, которые отправляют избыточную информацию заранее, подмешивая её в обычный поток пакетов, чтобы потерянный пакет можно было восстановить на приёмнике в момент обнаружения потери – без запроса к отправителю и без дополнительной задержки. Слово «forward» (вперёд) здесь ключевое: вы платите вперёд, на каждом пакете, ещё до того, как узнаете, будет ли потерян именно этот пакет. RFC 8854, спецификация FEC для WebRTC (январь 2021), формулирует цель прямо: FEC – это «отправка избыточной информации в исходящем потоке пакетов, чтобы информацию можно было восстановить даже при потере пакета».

Представьте, что отправляете кому-то важный документ и кладёте его копию в тот же конверт, а ещё одну копию – в завтрашний конверт. Если один конверт потеряется на почте, у получателя останется копия из другого. Вы тратите лишнюю бумагу на каждый конверт, каждый день, хотя большинство конвертов доходят нормально. Эта трата – в случае сети это полоса – и есть цена за то, чтобы не писать в ответ и не просить переслать.

Эта природа «плати вперёд» – самое важное, что нужно понять про FEC, потому что это же и его главный минус. RFC 8854 называет цену прямо: «использование FEC всегда приводит к передаче избыточных данных, и общий объём данных должен оставаться в пределах ограничений полосы… это приведёт к тому, что для основного кодирования останется меньше полосы, даже когда избыточные данные не используются». Иначе говоря, на чистой сети FEC – это чистые накладные расходы, которые могут даже снизить качество звука, потому что битрейт, который он потребляет, отнимается у основного звука.

Рисунок 1. Повторная передача запрашивает пакет заново и ждёт круговой обход; forward error correction отправляет избыточность заранее, поэтому восстановление мгновенно.

Три формы, которые может принимать FEC

RFC 8854 разбивает способы делать FEC для RTP-медиа на три механизма, и различия важны, потому что у них совершенно разные накладные расходы. Понимания этих трёх достаточно, чтобы участвовать в любом разговоре про устойчивость звука с инженером.

Первый – отдельный поток FEC (RFC 5956). Здесь избыточность идёт как самостоятельный RTP-поток со своими заголовками. Один ремонтный пакет может защитить сразу несколько медиапакетов, что эффективно для видео, – но для звука, где обычно на кадр приходится один маленький пакет, каждый FEC-пакет несёт собственный полный набор заголовков IP, UDP и RTP, и эти накладные расходы велики относительно крошечной аудионагрузки. RFC 8854 высказывается прямо: отдельный поток FEC для звука «несёт более высокие накладные расходы, чем другие механизмы, и поэтому НЕ РЕКОМЕНДУЕТСЯ».

Второй – избыточное кодирование (RFC 2198, формат RED), при котором избыточные данные подкладываются в тот же пакет, что и основной звук, разделяя один набор заголовков. Это эффективно именно потому, что заголовок пакета один, и это правильная форма для звука. RED мы разберём подробно ниже.

Третий – codec-specific in-band FEC, при котором сам кодек встраивает избыточность в собственный битстрим. Так делает Opus, а ещё речевой кодек AMR из мобильной телефонии (про него – в статье про речевые кодеки). Поскольку избыточность живёт внутри нагрузки кодека и «не добавляет дополнительного обрамления», RFC 8854 отмечает, что это «может быть немного эффективнее», чем RED. Именно этот механизм по умолчанию используется в большинстве звонков WebRTC.

Механизм FECГде живёт избыточностьНакладные расходы заголовковХорош дляRFC
Отдельный поток FECСвой RTP-потокВысокие (свои заголовки)Видео, многопакетные кадры5956 / 8627
Избыточное кодирование (RED)Внутри того же пакетаНизкие (общий заголовок)Звук, защита нескольких кадров2198
In-band FEC кодекаВнутри нагрузки кодекаМинимальные (без обрамления)Звук, защита одного кадра6716 (Opus)

Opus in-band FEC: дешёвая копия последнего кадра

Opus – это кодек, который WebRTC использует почти для всей речи (мы разбираем его целиком в статье про кодек Opus). Его встроенный FEC работает через механизм, который спецификация Opus, RFC 6716 (сентябрь 2012), называет LBRR – Low Bit-Rate Redundancy (RFC 6716, §4.2.5). Идея проста: когда FEC включён, кодер берёт только что отправленный кадр, перекодирует его в гораздо более низком качестве и вкладывает эту маленькую копию внутрь следующего пакета. RFC 8854 описывает это чисто: для Opus «кадры, признанные важными, перекодируются с меньшим битрейтом и добавляются к следующей нагрузке, позволяя частично восстановить потерянный пакет».

Так что пакет N несёт две вещи: полнокачественную копию кадра N и низкокачественную копию кадра N−1. Если пакет N−1 потерян, но пакет N пришёл, декодер вынимает резервную копию кадра N−1 из пакета N и проигрывает её – ухудшенную, но гораздо лучше, чем угаданное продолжение. В этом и разница между FEC и сокрытием потери пакетов: сокрытие придумывает правдоподобную замену потерянному звуку, а FEC восстанавливает то, что было сказано на самом деле, потому что была отправлена реальная копия. Эти инструменты мы сравниваем бок о бок в статье про сокрытие потери пакетов (PLC).

У Opus in-band FEC есть одно жёсткое ограничение, которое определяет всё в том, как его использовать. RFC 8854 говорит прямо: «этот механизм может нести информацию избыточности только для непосредственно предшествующего аудиокадра; таким образом, декодер не может полностью восстановить несколько подряд потерянных пакетов, что может быть проблемой на беспроводных сетях». Проще говоря: Opus FEC защищает от потери одного пакета за раз. Потеряйте два подряд – пачку (burst), а это ровно то, что выдают плохой Wi-Fi и сотовые каналы, – и резервная копия первого потерянного кадра ехала внутри второго потерянного пакета, так что и она пропала.

Как включить и сколько битрейта он съедает

Чтобы принимать Opus FEC, приёмник сигнализирует о готовности параметром useinbandfec=1, определённым в формате RTP-нагрузки Opus RFC 7587 (июнь 2015). На стороне отправителя кодеру нужно сообщить, что FEC нужен, и сказать, какую потерю ожидать, чтобы он знал, насколько защищаться. Два практических факта из инженерной истории WebRTC определяют, делает ли он что-то вообще.

Во-первых, Opus начинает производить LBRR только когда считает, что потеря происходит. Команда WebRTC в Mozilla, включая аудио-FEC в Firefox, обнаружила, что «порог включения LBRR внутри Opus – около 1%» потери пакетов (Mozilla, «Audio FEC Experimentation», ноябрь 2016). Ниже этого FEC ничего не отправляет – а значит, он не может защитить вас от первой пачки внезапной потери, только от продолжающейся.

Во-вторых, избыточность – это не битрейт, добавленный сверху; она вырезается из вашего существующего бюджета битрейта. Реализация WebRTC вычитает битрейт FEC из целевого, и защита ограничена – инженерный анализ стека WebRTC показал, что коррекция Opus FEC «ограничена 25%» и что «битрейт для FEC вычитается из целевого максимального битрейта» (webrtcHacks, «RED: Improving Audio Quality with Redundancy», август 2020). И Opus нужен достаточный общий битрейт, чтобы вообще нашлось место для копии. Mozilla обнаружила, что при дефолтных 40 kbps Firefox для двух каналов FEC не включится – битрейт «пришлось бы поднять до 58000» бит в секунду (58 kbps), прежде чем стерео-Opus начнёт производить пакеты LBRR (Mozilla, ноябрь 2016). Урок: если хотите, чтобы Opus FEC работал, поднимите аудиобитрейт, чтобы было место для избыточности, иначе он тихо ничего не делает.

Рисунок 2. Opus in-band FEC упаковывает низкобитрейтную копию кадра N−1 в пакет N; одна изолированная потеря восстановима, пачка из двух – нет.

RED: оборачиваем целые пакеты ради сильной защиты

Когда одного кадра защиты мало, более старый и гибкий ответ – RED – RTP Payload for Redundant Audio Data, определённый в RFC 2198 ещё в сентябре 1997 года. RED создавался для аудиоконференц-инструментов раннего интернета, и его суть хорошо состарилась: взять одну или несколько целых предыдущих аудионагрузок и упаковать их в текущий RTP-пакет за маленьким заголовком, всё под одним RTP-заголовком.

RFC 2198 излагает формат точно. RED-пакет – это контейнер: обычный RTP-заголовок (поля которого описывают основной, самый свежий звук), затем короткий блок заголовка для каждой избыточной нагрузки, затем данные нагрузок. Заголовок каждого избыточного блока – четыре байта и несёт флаг (F), говорящий, следует ли ещё один блок, 7-битный тип нагрузки, определяющий кодек этого блока, 14-битное смещение метки времени (timestamp offset), говорящее, насколько в прошлом звук этого блока, и 10-битную длину блока. Заголовок основного блока – всего один байт, потому что его метка времени и длина уже известны из RTP-заголовка и общего размера пакета. Спецификация строга: «избыточное кодирование НЕ ДОЛЖНО иметь больший битрейт, чем основное» – резервные копии должны быть маленькими.

В WebRTC RED согласуется в описании сессии как кодек с именем red, и чтобы его использовать, его ставят перед Opus в списке кодеков. Согласованная строка выглядит как a=rtpmap:111 red/48000/2 – RED на 48 kHz, 2 канала – и приложение переупорядочивает кодеки так, чтобы RED оборачивал нагрузку Opus (webrtcHacks, август 2020). Число избыточных копий, которые несёт RED, называется дистанцией (distance): дистанция 1 – каждый пакет несёт ещё и предыдущий, дистанция 2 – несёт два предыдущих.

Дистанция 2 – золотая середина, и она примерно удваивает битрейт

Самый полезный производственный результат по RED дала инженерная работа, вернувшая его в WebRTC. При жёстокой 60% случайной потере пакетов команда измеряла качество звука по доле сэмплов, которые приёмнику пришлось скрывать – отношению concealedSamples к totalSamplesReceived из статистического API WebRTC. Числа говорят сами за себя:

потеря в сети = 60%
без RED:              скрыто 60% сэмплов   → слова трудно разобрать
RED, дистанция 1:     скрыто 32%           → слышны артефакты
RED, дистанция 2:     скрыто 18%           → почти идеальный звук

Дистанция 2 снизила сокрытие с 60% до 18% при 60% потери, и «дистанция 2… – это объём избыточности, который теперь используется» в WebRTC (webrtcHacks, август 2020). Цена – полоса: RED с одной избыточной копией примерно удваивает аудиобитрейт – с около 30 kbps до около 60 kbps в опубликованном тесте, «плюс дополнительные 10 kbps на заголовок», – а дистанция 2 толкает его ещё выше (webrtcHacks, август 2020). Два приёма держат эту цену в узде. Voice activity detection (детектор речевой активности) означает, что избыточные копии отправляются только когда кто-то действительно говорит, а discontinuous transmission (DTX) останавливает отправку во время тишины – оба разобраны в статье про VAD и DTX – так что удвоенный битрейт применяется только к тем частям звонка, где есть речь.

Причина, почему дистанция 2 настолько важнее дистанции 1, – снова проблема пачки: если потери идут две подряд, единственный резерв дистанции 1 может сам оказаться в потерянном пакете, тогда как дистанция 2 распределяет два резерва по двум последующим пакетам, поэтому переживает пачку из двух пакетов. Как сказал инженер WebRTC, проделавший эту работу, «человеческий мозг может терпеть определённый уровень прерывистой тишины» – дистанция 2 держит пропуски достаточно короткими, чтобы оставаться ниже этого порога.

Фронтир: Deep REDundancy (DRED)

И Opus in-band FEC, и RED упираются в одну стену на длинных пачках. Opus FEC защищает один кадр; RED на практической дистанции защищает два-три. На сети, которая роняет десяток пакетов подряд – четверть секунды речи пропала, – ни один не поможет, потому что они просто не несут столько истории без неприемлемой цены по битрейту.

Намечающийся ответ – DRED – Deep Audio Redundancy – нейросетевое расширение Opus, стандартизуемое через рабочую группу IETF mlcodec (draft-ietf-mlcodec-opus-dred-05, истекает в июле 2026; авторы из Google и Meta). DRED заменяет идею «перекодировать последний кадр в более низком качестве» нейросетевым компрессором – вариационным автоэнкодером, оптимизированным по «скорость–искажение», – который сжимает гораздо более длинный кусок недавнего звука в крошечный объём данных. Черновик говорит прямо: там, где существующий LBRR Opus «ограничен одним кадром избыточности и обычно использует около 2/3 битрейта обычного пакета Opus», DRED «позволяет включить до одной секунды или более избыточности в каждый пакет, используя битрейт около 1/50 от обычного битрейта Opus» (draft-ietf-mlcodec-opus-dred-05, §2). Избыточность перевозится внутри заполнения (padding) пакета Opus, так что старые декодеры, не понимающие DRED, просто её игнорируют.

Практический эффект в том, что каждый кадр фактически передаётся много раз по многим последующим пакетам, так что даже длинную пачку можно заполнить звуком, который был сказан на самом деле, а не угадан. DRED появился в экспериментальной форме с Opus 1.5 (март 2024) – в том же релизе, что добавил нейросетевое сокрытие потери пакетов. Поскольку это всё ещё черновик, точные числа считайте подвижной целью – но направление ясно: граница между «избыточностью» и «сокрытием» размывается, и следующее поколение устойчивого звука живёт на ней.

Разобранный пример: FEC помогает или просто стоит?

Решение включать FEC сводится к тому, покупает ли потраченная полоса больше качества, чем стоит. Разберём конкретный случай. Пусть звонок один на один использует Opus на 40 kbps, и вы решаете, добавлять ли RED на дистанции 1, что примерно удваивает аудиобитрейт.

основной аудиобитрейт        = 40 kbps
RED дистанция 1 добавляет    ≈ 40 kbps + ~10 kbps заголовок
итого аудиобитрейт с RED     ≈ 90 kbps

Теперь взвесим эти 50 kbps лишней траты против ситуации:

На чистом офисном соединении с почти нулевой потерей эти 50 kbps не покупают ничего – нет потерянных пакетов, которые надо восстанавливать, – и конкурируют со всем остальным на канале. Рекомендация RFC 8854 – предпочесть повторную передачу, когда круговой обход укладывается в бюджет задержки, и «в противном случае ДОЛЖЕН передавать только тот объём FEC, который нужен для защиты от наблюдаемой потери пакетов». Так что на чистом канале правильный ответ – мало FEC или вовсе без него.

На «плохом» мобильном соединении с 8% пачечной потери те же 50 kbps – это разница между чистым звонком и роботизированным, а на фоне видеопотока, который может отправлять 1,5 Mbps, лишние 50 kbps защиты звука – погрешность округления. Как отметил анализ webrtcHacks, стоимость полосы RED «по сравнению с более чем мегабитом в секунду видеоданных… не кажется такой уж большой».

И есть случай, когда FEC активно вредит: когда потеря вызвана перегрузкой – канал и так заполнен, – добавление избыточных данных отправляет больше данных в насыщенную трубу, увеличивая потерю. RFC 8854 предупреждает, что «если потеря вызвана перегрузкой сети, дополнительная полоса, используемая избыточными данными, может фактически ухудшить ситуацию». RFC 2198 делает то же замечание про RED: слишком много избыточности «увеличит перегрузку сети и, следовательно, потерю пакетов, ухудшая ту самую проблему, для решения которой избыточность и применялась». Вот почему производственные стеки прибавляют и убавляют FEC по измеренной потере, а не оставляют его на полной мощности.

Частая ошибка: оставить FEC на полной мощности на чистой сети

Самая частая ошибка – относиться к FEC как к бесплатному апгрейду качества и выкручивать его везде. Он не бесплатен: каждая избыточная копия – это битрейт, отнятый у основного звука или добавленный к каналу, и на чистом соединении это чистая трата, которая может качество снизить, а не поднять. Хуже того, если потеря, которую вы видите, вызвана перегрузкой, FEC на полной мощности подливает масла в огонь. Исправление – адаптивный FEC: следить за фактической долей потери по отчётам приёмника RTCP и отправлять ровно столько избыточности, сколько оправдывает наблюдаемая потеря, как рекомендует RFC 8854. «Поставил и забыл» – здесь неверный инстинкт.

Вторая ловушка – спутать FEC с повторной передачей и взять не тот инструмент. Они не взаимозаменяемы. RTX восстанавливает точный пакет, но стоит кругового обхода, поэтому он правилен при низкой задержке; FEC стоит ровной полосы, но не добавляет задержки, поэтому он правилен при высокой задержке. Правило RFC 8854 явное: предпочесть RTX или восстановление в стиле повторной передачи, «когда RTT соединения укладывается в бюджет задержки приложения», и использовать FEC в остальных случаях. Выбор FEC на низколатентной локальной сети тратит полосу впустую; выбор RTX на высоколатентном мобильном канале восстанавливает пакеты слишком поздно для воспроизведения.

Третья ловушка – ждать, что Opus in-band FEC переживёт пачки. Поскольку он защищает только один предыдущий кадр, он блистает на изолированной случайной потере и рушится на потерях подряд, которые выдают реальные беспроводные сети. Если ваш трафик пачечный, RED на дистанции 2 – или со временем DRED – это инструмент под ваш паттерн потери; выкручивание ручки Opus FEC выше не закроет многопакетный пропуск.

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

Фора Софт встраивает звук реального времени в продукты для видеоконференций, телемедицины, онлайн-обучения и live-шопинга с 2005 года, и выбор правильного набора средств устойчивости к потере – рутинная часть того, как мы настраиваем эти системы. Когда клиент сообщает о роботизированном или пропадающем звуке, мы читаем долю и паттерн пачек потери из статистики WebRTC, а затем решаем осознанно: оставить Opus in-band FEC при обычной случайной потере, переключиться на RED дистанции 2, когда канал пачечный, поднять аудиобитрейт, чтобы у избыточности было место работать, и держать всё это адаптивным, чтобы чистое соединение не облагалось налогом. Телемедицинский звонок на больничном Wi-Fi и большой вебинар на разнородных потребительских каналах теряют пакеты по-разному, и правильный ответ читается из измеренного профиля потери, а не задаётся по умолчанию. Мы инструментируем эту статистику с самого начала, чтобы «звук разваливается» становилось числом, по которому можно действовать.

Главное

  • FEC отправляет избыточность заранее, восстанавливая потерю без кругового обхода, в отличие от RTX.
  • FEC стоит полосы на каждом пакете: помогает на «плохих» каналах и тратит битрейт на чистых.
  • Opus in-band FEC (LBRR) дёшево копирует предыдущий кадр, но защищает лишь одну изолированную потерю.
  • RED (RFC 2198) оборачивает целые пакеты; дистанция 2 снизила сокрытие с 60% до 18% при 60% потери.
  • При потере от перегрузки FEC может ухудшить ситуацию – держите его адаптивным к измеренной потере.
  • DRED упаковывает ~1 секунду избыточности при ~1/50 битрейта – намечающееся решение для длинных пачек.

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

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

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