Аудиоконвейер WebRTC целиком: от микрофона до динамика

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

Опубликовано: 2026-06-06 · Время чтения: 24 мин · Автор: Nikolay Sapunov, CEO в Фора Софт

Кратко

Аудиоконвейер WebRTC – это цепочка шагов, которая в реальном времени несёт голос от микрофона одного человека к уху другого: захват, очистка, кодирование, упаковка в пакеты, отправка по сети, буферизация, декодирование и воспроизведение. Большинство шагов существует, чтобы бороться с тремя врагами – эхом, шумом и ненадёжной сетью, – и каждый шаг стоит несколько миллисекунд, поэтому у всей цепочки есть бюджет задержки, который хорошие продукты держат примерно ниже 200 мс «ото рта до уха». Две части, определяющие, звучит ли звонок профессионально или любительски, – это очистка на стороне захвата (эхоподавление, шумоподавление, контроль усиления) и буфер джиттера на стороне приёма (NetEQ); обе невидимы, пока не дадут сбой. Эта статья проходит весь конвейер шаг за шагом простым языком, называет реальный компонент в браузерах и нативных стеках на каждом шаге и даёт бюджет задержки, который можно повесить на стену.

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

Если вы строите приложение для телемедицины, инструмент видеоконференций, контакт-центр, стрим для лайв-шопинга или онлайн-класс, аудиоконвейер – это та часть, по которой пользователи судят вас в первую очередь. Замёрзший кадр видео людям простят; эхо, роботизированные провалы или голос, который то появляется, то пропадает, – повесят трубку. Эта статья для менеджера продукта, основателя или операционного руководителя, которому нужно понять, что происходит с голосом между двумя телефонами, достаточно хорошо, чтобы принимать решения, читать баг-репорт инженера и знать, про какую ручку спросить, когда клиент говорит «звук сломан». Опытный инженер тоже найдёт каждое утверждение со ссылкой на документацию WebRTC или соответствующий RFC. К концу вы сможете нарисовать конвейер сами и объяснить, зачем нужен каждый блок.

Что на самом деле значит «конвейер»

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

Технология, благодаря которой это работает в браузере – без плагина и установки, – называется WebRTC (Web Real-Time Communication), набор стандартов и общий код, который поставляет каждый крупный браузер. Когда вы заходите в видеозвонок в Chrome, Safari или Firefox, аудиочасть звонка идёт по конвейеру, описанному ниже. Нативные мобильные приложения обычно используют тот же открытый код (libwebrtc) под капотом, поэтому конвейер почти одинаков – что во вкладке браузера, что в приложении на телефоне.

Вот вся эстафета на одной строке, которую остальная статья разбирает по одному бегуну за раз:

Микрофон → захват → high-pass filter → эхоподавление → шумоподавление → контроль усиления → кодирование (Opus) → упаковка (RTP) → сеть → буфер джиттера (NetEQ) → декодирование → воспроизведение → динамик.

Рис. 1. Аудиоконвейер WebRTC целиком. Звук входит у микрофона слева и выходит у динамика справа. Пунктирная петля – эталон эха: копия того, что играет динамик, подаётся обратно, чтобы эхоподавитель знал, что убрать.

Конвейер чисто делится на три области, и держать их раздельно в голове помогает. Сторона отправки (от захвата до упаковки) живёт на устройстве говорящего и в основном занята очисткой звука и его сжатием. Сеть – это часть, которую никто не контролирует, где пакеты задерживаются, переставляются и теряются. Сторона приёма (от буфера джиттера до воспроизведения) живёт на устройстве слушателя и в основном занята тем, чтобы скрыть, что сеть сделала со звуком.

Сторона отправки: захват и очистка

Захват – превращаем воздух в числа

Микрофон – это датчик, который превращает давление воздуха в колеблющееся напряжение. Аналого-цифровой преобразователь звуковой карты затем измеряет это напряжение многие тысячи раз в секунду и записывает каждое измерение как число. WebRTC работает на 48 000 измерений в секунду – частота дискретизации 48 кГц, – потому что именно на этой частоте нативно работает его кодек Opus. Частота дискретизации – как тиканье часов: 48 000 раз в секунду система спрашивает микрофон «какой у тебя сейчас уровень?» и записывает ответ. (Если частота дискретизации для вас в новинку, об этом есть базовая статья: Частота дискретизации: 44.1, 48, 96, 192 кГц и почему 48 кГц победила в видео.)

В браузере захват начинается, когда ваш код вызывает getUserMedia – функцию из стандарта W3C Media Capture and Streams, которая запрашивает у пользователя доступ к микрофону и возвращает живую аудиодорожку. За этим одним вызовом прячется многое: выбор устройства, путь звука через операционную систему и ограничения, которые можно запросить (частота дискретизации, число каналов и включена ли встроенная очистка браузера).

// Запрашиваем микрофон со встроенной очисткой WebRTC.
// echoCancellation, noiseSuppression, autoGainControl — три
// процессора стороны захвата, описанные ниже; в браузерах по умолчанию true.
const stream = await navigator.mediaDevices.getUserMedia({
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true
  }
});

Эти три флага – echoCancellation, noiseSuppression, autoGainControl – включают три процессора, которые делают тяжёлую работу дальше. Они включены по умолчанию в каждом браузере, поэтому обычный видеозвонок звучит приемлемо даже на ноутбуке с включёнными на полную динамиками.

Audio Processing Module: один блок, несколько задач

В libwebrtc очистка стороны захвата живёт в компоненте Audio Processing Module (APM). Его задача, словами самого проекта, – применять эффекты улучшения речи к сигналу микрофона, и канонические примеры, которые он перечисляет, – эхоподавление, шумоподавление и автоматический контроль усиления. APM обрабатывает захваченный звук в фиксированном порядке, и порядок важен: high-pass filter → акустическое эхоподавление (AEC3) → шумоподавление (NS) → автоматический контроль усиления (AGC2). Каждая стадия предполагает, что предыдущая уже сделала свою работу, поэтому перестановка сломала бы допущения, на которые каждая опирается.

High-pass filter – выметаем низкочастотный гул

Первая стадия – high-pass filter (фильтр верхних частот), который убирает энергию самых низких частот: стук по столу, гул кондиционера, рокот от тряски ниже диапазона речи. Человеческая речь живёт примерно между 80 Гц и 8 кГц; энергия ниже этой полосы не несёт полезного голоса и только заставляет последующие стадии работать тяжелее. High-pass filter – как сито, которое пропускает высокие частоты и держит низкий рокот. Убрав его рано, вы даёте эхоподавителю и шумоподавителю более чистый сигнал для анализа.

Акустическое эхоподавление – самая трудная работа конвейера

Эхо – самый разрушительный аудиодефект на звонке, и причина в геометрии. Когда вы на громкой связи, звук из динамика – голос дальнего конца – снова попадает в ваш микрофон и уходит обратно. Говорящий на дальнем конце слышит сам себя с задержкой кругового пути, что мучительно и почти делает разговор невозможным.

Акустическое эхоподавление (AEC, acoustic echo cancellation) решает это хитрым приёмом. Устройство уже точно знает, что собирается проиграть через динамик, потому что само сгенерировало этот сигнал. Этот известный сигнал называется эталоном (reference). Эхоподавитель берёт эталон, моделирует, как комната и путь «динамик→микрофон» искажают и задерживают его, и вычитает предсказанное эхо из сигнала микрофона. То, что остаётся, в идеале – только ваш голос. Современный эхоподавитель WebRTC называется AEC3 и используется в конвейере по умолчанию. Пунктирная петля на Рис. 1 – это как раз эталонный сигнал, идущий из тракта воспроизведения обратно к подавителю.

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

«Частая ловушка: проблема double-talk. Эхоподавление легко, когда говорит только один человек. Оно становится трудным, когда оба говорят одновременно – это «double-talk», – потому что микрофон теперь несёт ваш голос и эхо их голоса, и подавитель должен убрать одно, не повредив другое. Слабый подавитель будет рубить ваши слова, когда дальний конец тоже говорит. Это симптом номер один, за которым гоняются инженеры, и Bluetooth-громкая связь делает его хуже, добавляя непредсказуемую задержку в петлю. Полностью разбираем это в статье Акустическое эхоподавление (AEC): как это на самом деле работает.»

Шумоподавление – вытащить голос из шума комнаты

Шумоподавление (NS, noise suppression) идёт после эхоподавления. Его задача – ослабить устойчивый фоновый звук (вентиляторы, трафик, шипение клавиатуры, гул кафе), оставив речь нетронутой. Классическое шумоподавление оценивает шум в паузах между словами, затем вычитает эту оценку из всего сигнала. Современные подавители на глубоком обучении (RNNoise и коммерческие системы вроде Krisp и NVIDIA RTX Voice) вместо этого учатся тому, как выглядит речь, и оставляют только её, что позволяет им убирать неустойчивый шум вроде лая собаки или хлопка двери. Компромисс всегда один: переусердствуйте с подавлением – и сам голос начнёт звучать «под водой». Полная лестница техник – в статье Шумоподавление: классический NS, RNNoise, Krisp, NVIDIA RTX Voice.

Автоматический контроль усиления – держим уровень стабильным

Последняя стадия APM – автоматический контроль усиления (AGC, automatic gain control), и текущая версия WebRTC – AGC2. Люди сидят на разном расстоянии от микрофона и говорят с разной громкостью; задача AGC – привести каждый голос к стабильному целевому уровню, чтобы слушателю не приходилось крутить громкость. AGC2 сочетает несколько контроллеров: контроллер входного уровня, который подталкивает усиление микрофона в ОС, адаптивное цифровое усиление, следящее за уровнем речи, и фиксированный лимитер, который ловит резкие пики. Режим отказа здесь – «погоня AGC»: если петля управления слишком агрессивна, она слышимо качает уровень вверх-вниз, и фоновый шум тихой комнаты вздымается каждый раз, когда вы делаете паузу. О настройке – в статье Автоматический контроль усиления (AGC): держим уровень голоса стабильным.

Сторона отправки: кодирование и упаковка

Кодирование – сжимаем фрейм кодеком Opus

После очистки звук всё ещё сырые числа – около 768 килобит в секунду на один 48-кГц 16-битный канал. Слать это по мобильной сети было бы расточительно и хрупко, поэтому кодек сжимает каждый фрейм. Кодек WebRTC по умолчанию – Opus, определённый в IETF RFC 6716, и именно благодаря ему звук WebRTC хорошо звучит на низких битрейтах. Opus работает с фреймами от 2.5 мс до 60 мс, поддерживает битрейты от 6 кбит/с до 510 кбит/с и работает на 48 кГц внутри. На практике WebRTC упаковывает Opus во фреймы по 20 мс – это стандартный компромисс между задержкой (короче лучше) и эффективностью (длиннее сжимает лучше).

Opus необычен тем, что содержит два движка: речевой (SILK) и музыкальный (CELT) с переключателем, который выбирает нужный или смешивает их. Поэтому один кодек тянет и шёпот телемед-консультации, и фоновую музыку на стриме лайв-шопинга. Две особенности Opus важны для конвейера и снова всплывут на стороне приёма:

  • In-band forward error correction (FEC). Opus может встроить низкобитрейтную копию предыдущего фрейма внутрь текущего пакета (это так называемые LBRR-фреймы). Если один пакет потерян, следующий несёт грубую копию пропущенного, так что декодер может закрыть пробел. FEC работает только в речевом режиме Opus.
  • Discontinuous transmission (DTX). Когда вы перестаёте говорить, Opus может перестать слать полные пакеты и вместо этого выдавать крошечный «дескриптор тишины» примерно каждые 400 мс. Это срезает трафик молчащего говорящего примерно на 85–90 %, ведь в звонке двоих каждый молчит около половины времени.

Глубокий разбор самого кодека – в статье Opus: открытый кодек, который съел WebRTC.

Упаковка – оборачиваем фрейм для сети протоколом RTP

Сжатый фрейм ещё не готов к путешествию. Ему нужна адресная этикетка, чтобы приёмник мог собрать поток по порядку и в нужное время. Эта этикетка – заголовок Real-time Transport Protocol, определённый в IETF RFC 3550, а сам шаг обёртывания – упаковка (packetization). Специфичные для Opus правила, как фрейм ложится в RTP-пакет, – в IETF RFC 7587. Три поля в каждом RTP-заголовке делают настоящую работу:

  • Sequence number (номер последовательности), растущий на единицу с каждым пакетом, чтобы приёмник мог обнаружить потерю и перестановку.
  • Timestamp (метка времени), отмечающая, где фрейм сидит на медиачасах, чтобы приёмник знал, как разложить звук для воспроизведения. (Важно: RTP timestamp – это не настенное время; согласование звука и видео использует отдельный отчёт, разбираемый в статье RTP timestamps, RTCP sender reports и синхронизация по NTP.)
  • SSRC (synchronization source identifier), называющий, какому потоку принадлежит пакет, чтобы клиент, принимающий звук нескольких людей, мог их различать.

Затем пакет шифруется – WebRTC обязывает использовать SRTP, защищённый рукопожатием DTLS (IETF RFC 5764), – и выталкивается в сеть. Отсюда сторона отправки закончена; пакет на воле.

Сеть: часть, которую вы не контролируете

Как только пакет покидает устройство, с ним могут случиться три вещи, и все три плохи. Он может быть задержан сильнее предыдущего, так что пакеты, ушедшие равномерно, придут сгустками – это называется джиттер. Он может быть переставлен, придя после пакета, отправленного позже. И он может быть полностью потерян, отброшен перегруженным маршрутизатором или слабым Wi-Fi. Типичное мобильное соединение теряет небольшой процент пакетов и колеблет задержку на десятки миллисекунд; плохое – куда хуже.

Сторона отправки это починить не может, ведь к моменту потери пакета его уже нет. Всё, что делает сторона приёма, – это устранение последствий: сглаживание джиттера, перестановка пришедшего не по порядку и выдумывание правдоподобного звука вместо того, что не пришло вовсе. Поэтому именно на стороне приёма, а не отправки, живёт большая часть аудиоумений WebRTC.

Сторона приёма: NetEQ, мозг аудио WebRTC

Почему буфер неизбежен

Пакеты приходят по нерегулярному расписанию, но воспроизведение должно быть идеально регулярным – динамику нужны свежие 10 мс звука каждые 10 мс, вечно, без пробелов. Буфер джиттера – это устройство, которое соединяет эти два мира. Он как зона ожидания у выхода на посадку: пассажиры (пакеты) прибывают в непредсказуемое время, но рейсы (воспроизведение) улетают по фиксированному расписанию, поэтому зона ожидания держит людей, пока не настанет время посадки. Буфер держит приходящие пакеты ровно столько, чтобы выпускать их по порядку и вовремя.

Трудная часть – выбрать, как долго ждать. Ждать слишком мало – слегка опоздавший пакет промахнётся мимо слота и засчитается как потерянный. Ждать слишком много – добавите слышимую задержку в разговор, и люди начнут говорить друг через друга. Фиксированный буфер не может выиграть этот компромисс, потому что сети меняются минута за минутой. Ответ – адаптивный буфер джиттера, который непрерывно измеряет сеть и подстраивает свою целевую задержку.

Что делает NetEQ

Адаптивный буфер джиттера WebRTC называется NetEQ, и в описании самого проекта это «буфер джиттера аудио и средство сокрытия потерь пакетов», непрерывно оптимизирующее задержку буферизации под условия сети, чтобы держать воспроизведение гладким с как можно меньшей задержкой. У него две двери. Пакеты входят через InsertPacket, который отбрасывает всё слишком позднее, чтобы быть полезным, а иначе сохраняет пакет, обновляя свою статистику о том, как приходят пакеты. Звук выходит через GetAudio, который устройство воспроизведения вызывает, чтобы вытянуть ровно 10 мс звука, когда оно их требует.

Приём, позволяющий NetEQ держать задержку низкой и избегать пробелов, в том, что он может тихо растягивать или сжимать звук во времени. Когда буфер наполняется (сеть ускорилась), NetEQ играет декодированный фрейм чуть быстрее, чтобы слить избыток. Когда буфер пустеет (сеть замедлилась), он играет чуть медленнее, чтобы выиграть время. Эти правки достаточно малы, чтобы быть неслышимыми. Каждый раз, когда устройство воспроизведения просит звук, NetEQ возвращает результат одной из шести операций:

Операция NetEQКогда происходитЧто слышит слушатель
NormalПакет доступен, уровень буфера в нормеЧистый декодированный звук
AccelerationБуфер слишком полон (сеть ускорилась)Звук играется чуть быстрее, чтобы слить буфер
Preemptive expandБуфер слишком пуст (сеть замедлилась)Звук играется чуть медленнее, чтобы добавить задержку
ExpandПакета нет – он опоздал или потерянСокрытие потери: выдуманный звук
MergeРеальный пакет приходит сразу после выдуманногоВыдуманный звук гладко сшивается с реальным фреймом
Comfort noise (CNG)Говорящий молчит и использует DTXМягкий синтетический фоновый шум вместо мёртвой тишины

Таблица 1. Шесть операций воспроизведения NetEQ. Первые три управляют задержкой; последние три прячут потерю и тишину. Источник: документация WebRTC NetEQ.

Рис. 2. Как NetEQ решает, что играть. Проблемы задержки решаются растяжением во времени (верхняя полоса); пропавший звук решается сокрытием (нижняя полоса). Полный разбор – в статье про NetEQ.

Прячем потерянные пакеты: PLC, FEC и RED

Когда пакет так и не приходит, операция Expand NetEQ генерирует сокрытие потери пакета (packet loss concealment) – она экстраполирует из уже имеющегося звука, повторяя и затухая недавнюю форму волны, чтобы пробел звучал как короткое продолжение, а не как щелчок или тишина. Это хорошо работает для одного потерянного пакета и ухудшается по мере накопления потерь.

NetEQ также сотрудничает с двумя схемами избыточности со стороны отправки. Если поток Opus несёт in-band FEC, NetEQ может восстановить потерянный фрейм из низкобитрейтной копии в следующем пакете. Документация WebRTC NetEQ перечисляет «forward error correction (RED или codec inband FEC)» среди его обязанностей, наряду с отслеживанием потерь по negative acknowledgement (NACK) и даже обязанностью синхронизации аудио и видео – NetEQ можно велеть намеренно добавить задержку, чтобы держать звук в линию с видео. RED, определённый в IETF RFC 2198, – это общая схема, которая упаковывает копии старых фреймов в новые пакеты. Компромиссы между PLC, FEC и RED – когда каждый спасает звонок, а когда лишь тратит трафик – разбираем в статьях Сокрытие потери пакетов (PLC): прячем пропавшие фреймы и Forward error correction (FEC), in-band FEC и избыточность RED.

Декодирование и воспроизведение

Как только NetEQ решил, какие 10 мс звука играть, декодер Opus превращает сжатый фрейм обратно в сырые отсчёты, а стадия воспроизведения отдаёт эти отсчёты аудиовыходу операционной системы, который приводит в движение динамик или наушники. Эстафета завершена: волна, начавшаяся в одной комнате, теперь волна в другой.

Бюджет задержки, который можно повесить на стену

Каждый прыжок стоит времени, и сумма – это то, что определяет, ощущается ли разговор естественным. Отраслевое правило большого пальца, выведенное из старого руководства телефонных сетей (рекомендация ITU-T G.114), таково: односторонняя задержка «ото рта до уха» ниже примерно 150 мс незаметна, до примерно 200 мс комфортна для большинства разговоров, а за примерно 400 мс люди начинают говорить друг через друга. Вот представительный бюджет для хорошего звонка WebRTC; реальные числа меняются с сетью и устройством.

СтадияТипичный односторонний вкладПочему
Захват + очистка APM~10–20 мсБуферизация плюс обработка эха/шума/усиления
Кодирование Opus~20 мсОдин фрейм 20 мс нужно собрать до кодирования
Упаковка + отправка~1 мсRTP-заголовок, шифрование, передача в ОС
Сеть (в одну сторону)~10–150 мсДоминирующий и наименее контролируемый член
Буфер джиттера (NetEQ)~20–100 мсАдаптивная задержка, которую он выбирает для поглощения джиттера
Декодирование + воспроизведение~10–30 мсДекодирование плюс собственный буфер выходного устройства
Итого (хорошая сеть)~70–120 мсУверенно ниже цели 200 мс

Таблица 2. Представительный односторонний бюджет задержки для аудиозвонка WebRTC. Два члена, которые вы контролируете меньше всего, – сеть и буфер джиттера – также два самых больших, поэтому ум на стороне приёма важнее оптимизации на стороне отправки. Сложим контролируемые члены: 15 + 20 + 1 + 10 + 10 = 56 мс задержки на стороне устройства при хорошей сети, остальное остаётся на NetEQ и провод.

Рис. 3. Тот же бюджет в виде составной полосы. Сеть и буфер джиттера – длинные переменные сегменты; стадии на стороне устройства короткие и фиксированные. Здоровый звонок заканчивается заметно левее потолка комфорта в 200 мс.

Арифметику стоит проговорить вслух один раз. На хорошей локальной сети контролируемые прыжки на стороне устройства складываются примерно в 15 мс (захват) + 20 мс (кодирование) + 1 мс (упаковка) + 10 мс (воспроизведение) = 46 мс, плюс небольшая стоимость декодирования. Сеть добавляет, может, 10–30 мс в каждую сторону, а NetEQ добавляет 20–60 мс намеренной буферизации. Это сажает здоровый звонок около 90–120 мс в одну сторону – уверенно внутри комфортной полосы. В момент, когда сеть деградирует, NetEQ растит свой буфер, чтобы защитить гладкость, и это та задержка, которую вы ощущаете на плохом соединении.

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

Мы строим продукты, которые живут или умирают по этому конвейеру: платформы телемедицины, где врач должен чётко слышать пациента; инструменты видеоконференций и e-learning, где десятки людей делят комнату; приложения контакт-центров и лайв-шопинга, где каждая секунда звука – это и есть продукт. В этих проектах мы настраиваем те же стадии, что описывает статья: решаем, опираться ли на Opus FEC или RED для аудитории с потерями на мобильной сети, подбираем размер NetEQ под задержку, которую терпит конкретный сценарий, и решаем, когда микшировать звук на сервере, а когда пересылать его нетронутым. Конвейер стандартен; сделать так, чтобы он звучал правильно для конкретного продукта и конкретной сети, – вот в чём инженерия.

Главное

  • Конвейер – это эстафета: захват, очистка, кодирование, упаковка, отправка, буфер, декодирование, воспроизведение.
  • Очистка стороны отправки идёт в фиксированном порядке: high-pass filter, AEC3, шумоподавление, AGC2.
  • Opus на фреймах 20 мс – кодек по умолчанию; FEC и DTX помогают пережить потери и сэкономить трафик.
  • Сеть задерживает, переставляет и теряет пакеты – всё устранение последствий делает сторона приёма.
  • NetEQ адаптирует буфер и использует шесть операций, балансируя низкую задержку и гладкое воспроизведение.
  • Держите одностороннюю задержку «ото рта до уха» ниже ~200 мс; доминируют в ней сеть и буфер джиттера.

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

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

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