Содержание статьи +
- Кратко
- Почему это важно
- Проблема: представление должно продолжаться
- Самый простой ответ и почему от него отказались
- Замещение формой волны: повторить недавнее прошлое
- Модельная маскировка: продлевать речь, а не форму волны
- Нейросетевая маскировка: пусть сеть вообразит пропавший звук
- Что на самом деле делает WebRTC: Expand и Merge в NetEQ
- Разбор на числах: как быстро падает качество в зависимости от паттерна
- PLC – один из трёх инструментов; знайте, какую работу он делает
- Частая ошибка: считать высокий уровень маскировки багом PLC
- Где здесь Фора Софт
- Главное
- Что почитать дальше
Кратко
Когда звуковой пакет теряется в сети, получатель не может его дождаться – динамику нужен свежий кусочек звука прямо сейчас, – поэтому вместо «дыры» он синтезирует правдоподобное продолжение звука; это и называется маскировкой потерь пакетов, или PLC. Классический PLC повторяет и затухает последний хороший фрагмент звука на его естественной высоте: это хорошо скрывает один пропавший пакет, но при серии потерь превращается в роботизированный, «булькающий» звук. С 2020 года появилось новое поколение PLC на нейросетях – Google WaveNetEQ, deep PLC и Deep REDundancy (DRED) в Opus 1.5, – которые генерируют пропавший звук из выученной модели речи и остаются разборчивыми даже при очень высоких потерях. В статье простым языком разбираются четыре семейства PLC, что именно делает NetEQ в WebRTC при потере пакета, чем PLC отличается от упреждающей коррекции ошибок и ретрансляции, и какие метрики в продакшене покажут, сколько вашего звука «придумывается».
Почему это важно
Если вы делаете видеозвонки, телемедицину, контакт-центр или онлайн-классы, потеря пакетов – это не редкий случай, а обычная погода мобильных и домашних сетей, и первое, что замечают пользователи. Звук либо деградирует плавно, либо превращается в глючную роботизированную кашу, и разница между этими двумя исходами почти полностью определяется качеством вашего PLC. Статья написана для продакт-менеджера, основателя или операционного руководителя, которому нужно понять, что PLC может и чего не может, – чтобы прочитать диагностическую метрику, понять, относится ли жалоба «звук плохой» к маскировке или к проблеме выше по тракту, и решить, стоит ли затрат внедрение более нового нейросетевого PLC. Senior-инженер тоже найдёт здесь каждый факт, прослеженный до документации исходников WebRTC, профильных RFC и рекомендаций ITU, либо до оригинальных научных работ.
Проблема: представление должно продолжаться
Начнём с ограничения, которое и делает PLC необходимым. В реальном звонке динамик беспощаден: ему нужен свежий, непрерывный кусочек звука – в WebRTC ровно 10 миллисекунд – для воспроизведения вовремя, 100 раз в секунду, бесконечно. Аудиоустройство не делает вежливую паузу, пока опоздавший пакет завершает путь по сети. Если следующий кусочек звука не готов к моменту запроса, из динамика всё равно должно что-то прозвучать.
В реальной сети пакеты теряются постоянно. Их отбрасывают перегруженные маршрутизаторы, они теряются на слабом Wi-Fi, повреждаются на сотовом участке или просто приходят слишком поздно, чтобы быть полезными. Для одностороннего стриминга это досадно, но поправимо, потому что плеер может буферизовать несколько секунд и подождать. Для живого разговора так не выйдет: ждать – значит добавлять задержку, а задержка свыше примерно 200 миллисекунд в одну сторону разрушает естественный диалог (этот «потолок» задержки мы разбираем в статье про WebRTC-конвейер звука). Поэтому при потере пакета у получателя остаются только плохие варианты, и PLC – наименее плохой из них.
Маскировка потерь пакетов – это семейство методов, которые заполняют пробел от пропавшего пакета синтезированным звуком, чтобы слушатель услышал что-то правдоподобное вместо дыры. Название точное: PLC не восстанавливает потерянный звук – оригинал утрачен – он маскирует потерю, генерируя замену. Всё искусство в том, чтобы эта замена была достаточно близка к тому, что было сказано на самом деле, и слушатель ничего не заметил – или хотя бы не вздрогнул.
Самый простой ответ и почему от него отказались
Грубейший PLC – это вставка нулей (zero insertion), она же подстановка тишины: при потере пакета на эти миллисекунды воспроизводится тишина. Это тривиально реализуется и ничего не стоит.
И звучит ужасно. Внезапная тишина посреди гласного воспринимается не как тихий момент, а как резкий щелчок на каждом краю пробела, потому что форма волны скачком уходит в ноль и обратно. Человеческое ухо крайне чувствительно к таким разрывам. Череда пробелов, заполненных тишиной, превращает непрерывную речь в заикающуюся, щёлкающую кашу, следить за которой труднее, чем подсказывает сам процент потерь. Вставка нулей – это базовый уровень, относительно которого измеряют все остальные методы, и любая серьёзная система от неё отказалась.
Замещение формой волны: повторить недавнее прошлое
Следующая идея, на которой VoIP держался два десятилетия, – это замещение формой волны (waveform substitution): вместо тишины пробел заполняется куском звука, который вы только что получили. Речь локально повторяема – тянущийся гласный – это, по сути, одна и та же короткая форма волны, скопированная много раз, – поэтому недавний фрагмент обычно неплохо угадывает то, что идёт дальше.
Простейший вариант просто повторяет последний полученный кадр. Версии получше, например стандартизированный в рекомендации ITU G.711 Appendix I, работают на уровне периода основного тона (pitch period) – длины одного цикла голоса, того, что делает голос высоким или низким. Вместо повтора произвольного куска алгоритм находит самый недавний период основного тона и зацикливает его, чтобы синтезированный звук сохранял естественную высоту голоса, а не гудел на частоте кадров (ITU-T G.711 Appendix I, 1999). G.711 Appendix I намеренно дешёв – его опубликованная сложность около 0,5 MIPS на канал, что под силу даже простейшему телефонному железу (ITU-T G.711 Appendix I, 1999).
Чтобы найти период основного тона, алгоритм сдвигает недавнюю форму волны относительно самой себя и ищет сдвиг, при котором она лучше всего совпадает, – этот расчёт называется нормированной взаимной корреляцией. Сдвиг с наибольшей корреляцией и есть период основного тона, и именно этот фрагмент зацикливается (патентная литература по PLC с выравниванием формы волны, используется в Expand WebRTC). Две доработки делают звук менее механическим. Во-первых, повторяемый фрагмент затухает по громкости тем сильнее, чем дольше длится пробел, потому что слишком долгий цикл превращается в очевидный гул; затухая к тишине, длинная потеря деградирует плавно, а не гудит. Во-вторых, когда реальный звук наконец возвращается, синтезированный «хвост» и свежий реальный звук сшиваются плавным кроссфейдом, чтобы стык не щёлкнул.
Замещение формой волны действительно хорошо для коротких изолированных потерь – один пропавший пакет в 20 миллисекунд обычно неслышен. Его пределы проявляются в двух случаях. Оно не умеет придумывать новые звуки: если потерян пакет с началом нового слова, зацикливание предыдущего гласного даёт неверный звук, а не пропавшее слово. И оно быстро деградирует при серии (burst) подряд идущих потерь, потому что каждый повторённый-и-затухший фрагмент всё сильнее уходит от реальности. Растяните один период основного тона на 200 миллисекунд потери – и получите тот самый «булькающий», роботизированный звук, знакомый по плохому звонку.
Модельная маскировка: продлевать речь, а не форму волны
Третье семейство поднимается на уровень абстракции выше. Вместо копирования сырой формы волны модельная (параметрическая) маскировка использует то, что кодек уже описывает речь набором небольших параметров – грубо говоря, модель речевого тракта (которая формирует гласные и согласные) плюс сигнал возбуждения (гул голосовых связок или шипение дыхания). При потере пакета декодер продлевает эти параметры вперёд – удерживая форму речевого тракта, продолжая высоту тона, мягко уменьшая энергию – и синтезирует новый звук из продлённой модели, а не из зацикленной формы волны.
Именно так работает маскировка, встроенная в современные речевые кодеки. Слой SILK в кодеке Opus, который WebRTC использует для большей части голоса, имеет путь модельной маскировки, который декодер запускает автоматически, когда ему сообщают, что кадр пропал (IETF RFC 6716, §4.4, сентябрь 2012). Поскольку он работает с речевой моделью, он способен пережить чуть более длинную потерю, чем сырое замещение формой волны, прежде чем начнёт звучать неправильно, и переходит между звуками естественнее. Слова он по-прежнему придумать не может – продлённая в пробел модель даёт правдоподобное продолжение текущего звука, а не реальный следующий слог, – но для того же разговора это заметный шаг вверх по качеству.
Нейросетевая маскировка: пусть сеть вообразит пропавший звук
Четвёртое семейство изменило само представление о возможном, и в продакшене оно появилось около 2020 года. Нейросетевой PLC заменяет вручную настроенные правила продления глубокой нейросетью, обученной на огромных объёмах реальной речи. Сеть, по сути, выучила, как звучит человеческая речь, поэтому, получив звук перед пробелом, она генерирует продолжение, следующее естественной статистике речи – верному контуру высоты тона, верному способу перехода гласного в согласный – намного убедительнее фиксированного правила.
Первым широко развёрнутым примером был WaveNetEQ от Google, появившийся в Google Duo в 2020 году. Это генеративная модель на основе WaveRNN от DeepMind, создающая кусок звука для заполнения каждого пробела. Поскольку Duo использует сквозное шифрование, модель работает целиком на телефоне получателя, и Google спроектировал её достаточно быстрой для этого при сохранении высокого качества звука. Её обучали на речи более 100 дикторов на 48 языках, смешанной с разнообразным фоновым шумом, чтобы она держалась в шумных помещениях (Google Research, «Improving Audio Quality in Duo with WaveNetEQ», апрель 2020). Её честное заявленное ограничение показательно: WaveNetEQ «учится правдоподобно продолжать речь на короткой дистанции» – он может закончить слог, но не предсказывает слова (Google Research, апрель 2020). Нейросетевой PLC, как и любой PLC до него, маскирует; мысли он не читает.
Самое важное недавнее событие – в Opus 1.5, выпущенном в марте 2024 года. Opus добавил PLC на глубоком обучении, который вместо вручную настроенных эвристик позволяет DNN генерировать пропавший звук; его авторы заняли второе место в Microsoft Audio Deep Packet Loss Concealment Challenge 2022 года с лежащей в основе техникой (Xiph.Org, «Opus 1.5 Released», март 2024). Важно, что deep PLC – это чисто декодерное обновление: оно ничего не меняет «на проводе», поэтому полностью совместимо с существующим стандартом Opus (RFC 6716), и любой кодер Opus может говорить с декодером, у которого включён deep PLC. Включается он на этапе сборки флагом --enable-deep-plc (около 1 МБ бинарника) и в рантайме установкой сложности декодера в 5 или выше; дополнительная стоимость при высоком уровне потерь – около 1 процента одного ядра CPU ноутбука (Xiph.Org, март 2024).
Нейросетевой вокодер, который делает это достаточно дешёвым для продакшена, сам по себе заслуживает упоминания: Opus 1.5 представил FARGAN (framewise autoregressive generative adversarial network) со сложностью около 600 MFLOPS – одной пятой вокодера LPCNet, который он заменил, – и это позволяет ему работать менее чем на 1 проценте ядра CPU на современном телефоне (Xiph.Org, март 2024).
Когда потеря слишком длинная, чтобы её вообразить: DRED
У чистого PLC, даже нейросетевого, есть жёсткий потолок: он способен лишь продолжить то, что уже услышал. Когда пакеты пропадают серией (burst) – а в реальных сетях обычно так и бывает, несколько подряд – исчезают целые фонемы или слова, и никакое умное продолжение не вернёт слова, которые так и не были получены. Честное решение – избыточность: передать звук более одного раза, чтобы серию можно было заполнить тем, что было сказано на самом деле, а не догадкой.
В Opus 1.5 есть яркий ответ – Deep REDundancy (DRED). Обычный кодек держит пакеты короткими – как правило, 20 миллисекунд – ради низкой задержки, что ограничивает объём истории внутри. DRED снимает это ограничение для избыточной копии: он использует нейросетевой компрессор (вариационный автоэнкодер с оптимизацией скорость-искажения), чтобы упаковать до одной полной секунды недавнего звука примерно в 12–32 килобита в секунду накладных расходов, переносимых внутри padding обычного пакета Opus, так что старые декодеры просто его игнорируют. В итоге каждый 20-миллисекундный кадр фактически передаётся около 50 раз, распределённый по многим последующим пакетам, при стоимости, близкой к старому механизму избыточности (Xiph.Org, март 2024). В тестах проекта на полном стеке WebRTC DRED сохранял разборчивость речи даже при 90 процентах потерь пакетов (Xiph.Org, март 2024). DRED пока не финализированный стандарт – он прорабатывается в рабочей группе IETF mlcodec, – так что версию из 1.5 стоит считать экспериментальной, но направление ясно: именно на границе между «маскировкой» и «избыточностью» решается будущее устойчивого звука.
Что на самом деле делает WebRTC: Expand и Merge в NetEQ
В WebRTC маскировка потерь пакетов – это не отдельный модуль, который вы подключаете: она живёт внутри NetEQ, того же адаптивного буфера джиттера, что сглаживает неровный приход пакетов и который мы подробно разбираем в статье про буфер джиттера NetEQ. Каждые 10 миллисекунд аудиоустройство вызывает функцию NetEQ GetAudio, и NetEQ обязан вернуть кусочек звука. Когда нужного пакета нет – потерян или просто опоздал – декодировать нечего, и вместо тишины NetEQ выполняет операцию, которую документация WebRTC называет Expand: он генерирует маскировку потерь пакетов, «экстраполируя оставшийся звук в sync buffer или прося декодер его произвести» (документация WebRTC NetEq, chromium.googlesource.com).
Эта одна фраза схватывает двухуровневую конструкцию. У NetEQ есть собственная встроенная маскировка замещением формы волны – описанная выше экстраполяция по периоду основного тона, – работающая для любого кодека. Но для кодеков со своей, лучшей маскировкой NetEQ вместо этого просит декодер кодека произвести пропавший звук. Для Opus это значит, что NetEQ может вызвать собственную модельную (а в Opus 1.5 – нейросетевую) маскировку Opus, поэтому стек WebRTC на свежем Opus при потерях звучит заметно лучше, чем один лишь общий путь.
Когда реальный пакет наконец приходит после того, как Expand синтезировал звук, их нельзя просто состыковать – на стыке будет щелчок, ровно как на краях пробела при вставке нулей. Поэтому NetEQ выполняет вторую операцию, Merge, которая плавно сшивает выход маскировки со свежедекодированным реальным звуком, чтобы стык был неслышен (документация WebRTC NetEq). Expand заполняет дыру; Merge скрывает края заплатки. Вместе они и есть полная пост-фактум обработка потерь в WebRTC.
Разбор на числах: как быстро падает качество в зависимости от паттерна
Числа делают проблему серий наглядной. Пусть звук использует кадры по 20 миллисекунд, так что каждый потерянный пакет – это дыра в 20 миллисекунд. Рассмотрим две сети, которые обе теряют по 5 процентов пакетов, но в разном паттерне.
В первой сети потери случайные и изолированные – один пакет тут, один там, никогда два подряд. При кадрах в 20 миллисекунд это значит, что типичный пробел – одна дыра в 20 миллисекунд, окружённая хорошим звуком с обеих сторон. Замещение формой волны зацикливает один недавний период основного тона на 20 миллисекунд, и результат обычно неслышен. Посчитаем худший одиночный пробел:
размер кадра = 20 мс
изолированная потеря = 1 кадр
маскируемый интервал = 1 × 20 мс = 20 мс ← в пределах «неслышно»Во второй сети те же 5 процентов потерь приходят сериями – когда падает один пакет, обычно с ним падают и следующие несколько. Серия, скажем, из шести подряд потерянных – это один непрерывный пробел:
размер кадра = 20 мс
серийная потеря = 6 кадров
маскируемый интервал = 6 × 20 мс = 120 мс ← далеко за пределами естественного зацикливанияТот же процент потерь даёт едва заметный артефакт в одном случае и явный роботизированный «булёж» в другом – исключительно из-за паттерна. Поэтому измерять только процент потерь обманчиво, и поэтому индустрия перешла к реалистичным серийным моделям потерь для тестирования PLC; это же и тот режим, где нейросетевая избыточность вроде DRED оправдывает себя, ведь 120 миллисекунд пропавшей речи вообразить нельзя, но можно восстановить, если она была отправлена с избыточностью. Практический вывод: заголовок «5 процентов потерь» почти ничего не говорит о том, как реально будет звучать ваш звук, – решает серийность.
PLC – один из трёх инструментов; знайте, какую работу он делает
Маскировка потерь пакетов – это последняя линия обороны, и намеренно та, что связана с догадкой. Стоит поставить её рядом с двумя другими инструментами устойчивости, потому что команды регулярно их путают и хватаются не за тот.
Ретрансляция (RTX) просит отправителя прислать потерянный пакет заново. Она восстанавливает точный звук – без догадок – но стоит как минимум одного сетевого round trip, поэтому помогает лишь когда round trip короток относительно времени удержания буфера джиттера. На канале с высокой задержкой ретранслированный пакет приходит слишком поздно для воспроизведения (определена в IETF RFC 4588).
Упреждающая коррекция ошибок (FEC), включая встроенную низкоскоростную избыточность Opus и формат RED, заранее отправляет избыточные данные, чтобы потерянный пакет можно было восстановить, не запрашивая его повторно. Она стоит полосы на каждом пакете – есть потери или нет – но не добавляет задержки round trip. Её компромиссы мы разбираем в статье про упреждающую коррекцию ошибок, in-band FEC и избыточность RED.
Маскировка потерь пакетов (PLC) не делает ни того, ни другого – она синтезирует замену на стороне получателя с нулевой добавленной задержкой и нулевой лишней полосой, но это догадка, и она может лишь продолжить то, что уже было услышано. Эти три инструмента дополняют друг друга, а не заменяют: хорошо построенный стек WebRTC использует FEC, чтобы избежать большинства потерь, RTX, чтобы вернуть часть остальных, когда позволяет задержка, и PLC, чтобы замаскировать то, что всё же прошло. DRED интересен именно тем, что стирает границу – это избыточность, доставленная в путь маскировки.
| Инструмент | Что делает | Цена в задержке | Цена в полосе | Возвращает точный звук? |
|---|---|---|---|---|
| Ретрансляция (RTX) | Пересылает потерянный пакет по запросу | Один round trip | Только при потере | Да |
| Коррекция ошибок (FEC/RED) | Заранее шлёт избыточные данные | Нет | На каждом пакете | Да (в пределах избыточности) |
| Маскировка (PLC) | Синтезирует замену у получателя | Нет | Нет | Нет – это догадка |
Частая ошибка: считать высокий уровень маскировки багом PLC
Самая дорогая ошибка команд – увидеть высокий уровень маскировки и заключить «наш PLC сломан». PLC сидит в самом конце конвейера; шквал маскировки почти всегда означает, что пакеты реально не приходят – настоящие сетевые потери, обвал управления перегрузкой или отправитель, срезавший битрейт, – а не что алгоритм маскировки выбрал плохо. Подмена на более навороченный PLC, когда реальная проблема – 8 процентов серийных потерь, делает «булёж» чуть приятнее, но дыры не убирает. Сначала измерьте потери; почините сеть или добавьте избыточность; маскировку настраивайте в последнюю очередь.
Вторая ловушка – судить о PLC по неверной метрике. Старая привычка – отчитываться процентом потерь и общей оценкой звука, но ни то ни другое не отражает, как реально работает маскировка, потому что один и тот же процент потерь звучит совершенно по-разному в зависимости от серийности (как показал разбор на числах). Сейчас в поле используют специальные меры – PLCMOS от Microsoft, метрика на данных, спроектированная именно для оценки качества маскировки, была представлена вместе с Deep PLC Challenge 2022 года ровно потому, что общие метрики звука пропускали артефакты маскировки (Xiph.Org, март 2024). Если вы сравниваете две реализации PLC, сравнивайте их на реалистичных серийных трассах потерь с метрикой, учитывающей маскировку, а не на равномерных случайных потерях с PESQ.
Третья ловушка – считать, что нейросетевой PLC бесплатен. Он дёшев – около 1 процента ядра CPU для deep PLC в Opus, – но «дёшево» это не «ноль», и он требует сборки, в которую он скомпилирован, и сложности декодера, выставленной достаточно высоко, чтобы его включить (5 или выше для deep PLC) (Xiph.Org, март 2024). Команда, которая ждёт нейросетевой маскировки, но поставляет стоковую сборку декодера, получает общий путь и недоумевает, почему звук всё ещё «булькает».
Где здесь Фора Софт
Фора Софт встраивает звук реального времени в видеоконференции, телемедицину, онлайн-обучение и лайв-шопинг с 2005 года, и поведение маскировки – рутинная часть того, как мы диагностируем и настраиваем эти системы. Когда клиент жалуется на роботизированный или «булькающий» звук, первый шаг – прочитать счётчики маскировки в getStats, посмотреть, изолированные потери или серийные, и решить, где чинить: выше по тракту (сеть, управление перегрузкой, добавление FEC) или в сборке декодера (включение лучшего пути маскировки Opus). Телемедицинский звонок по больничному Wi-Fi и вебинар на 500 человек на разнородных пользовательских каналах теряют пакеты по-разному, и верный набор средств устойчивости – FEC, ретрансляция, маскировка, а когда это важно, нейросетевая избыточность – берётся из измеренного профиля потерь, а не из догадки. Мы инструментируем эти метрики с самого начала, чтобы «звук звучит сломанным» становилось числом, которое можно прочитать.
Главное
- PLC заполняет потерянный пакет синтезированным звуком, чтобы слушатель услышал звук, а не дыру.
- Четыре семейства: вставка нулей (щелчки), замещение формой волны, модельное, нейросетевое.
- WebRTC маскирует операцией Expand в NetEQ; Merge кроссфейдит реальный пакет обратно.
- Нейросетевой PLC (WaveNetEQ, deep PLC в Opus 1.5) звучит куда лучше и стоит около 1% ядра CPU.
- PLC продолжает прошлое; вернуть потерянные слова может только избыточность вроде DRED.
- Процент потерь скрывает серийность; именно серийность делает звук роботизированным.