Диагностика проблем со звуком в рабочей системе: runbook

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

Коротко

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

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

Сбои звука – это те тикеты в поддержку, из-за которых пользователи бросают продукт: глитч в видео раздражает, но звонок, на котором вас не слышно, бесполезен. Если вы строите или эксплуатируете видеоконференцию, телемедицинскую платформу, онлайн-класс или live-shopping, вашему дежурному инженеру нужна спокойная упорядоченная процедура на случай, когда звук ломается в два часа ночи, а не догадки. Этот runbook написан для инженера с пейджером и для продакт-менеджера, которому надо понимать, что этот инженер делает. Он превращает расплывчатое «нет звука» в диагностику из пяти шагов с названной причиной и известным фиксом, так что инцидент заканчивается за минуты, а не за день перебора вариантов.

Как пользоваться этим runbook

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

Прежде всего соберите у сообщившего пользователя два факта – они вдвое сужают пространство поиска.

Первое: отказ односторонний или двусторонний? «Я их слышу, а они меня нет» – совсем другая проблема, чем «никто никого не слышит». Односторонний отказ указывает на захват у одного конкретного участника или на путь в одну сторону; двусторонний – на общую причину вроде сервера или несовпадения кодеков.

Второе: это полная тишина, искажённый звук или эхо? Полная тишина значит, что сигнал не приходит или не воспроизводится. Искажение (роботизированный, рваный, «подводный» звук) значит, что сигнал приходит, но повреждается в пути – это сетевая проблема. Эхо значит, что сигнал приходит нормально, но отказала одна из стадий обработки. Эти три симптома ведут к разным шагам, как показывает дерево решений ниже.

Рисунок 1. Runbook как дерево решений. Сначала классифицируйте симптом, затем выполняйте пять проверок по порядку. Симптом подсказывает, какая проверка сработает вероятнее всего, но вы всё равно идёте по списку сверху.

Остальная часть статьи – это те самые пять проверок, у каждой свой симптом, число для чтения, инструмент и фикс.

Браузер уже всё знает: минутный ввод в getStats

Четыре из пяти проверок читают число из одного места, поэтому стоит понять это место до того, как им пользоваться. Каждое живое WebRTC-соединение – технология, которой браузеры переносят звук и видео в реальном времени, – ведёт о себе текущую сводку. Вы читаете её, вызывая на соединении функцию getStats(), определённую в спецификации W3C «Identifiers for WebRTC's Statistics API». Она возвращает отчёт с десятками именованных чисел, и горстка из них говорит всё о том, течёт ли звук.

Думайте о getStats() как о приборной панели автомобиля. Не нужно открывать двигатель; панель уже показывает уровень топлива, скорость и индикаторы. Сбой звука почти всегда виден на панели, если знать, какие три датчика читать.

Вот минимальный код, читающий панель для звука, который приходит к получателю. Он работает в консоли разработчика любого современного браузера при подключённом звонке.

// Читаем входящую статистику звука для живого RTCPeerConnection с именем `pc`.
const report = await pc.getStats();
report.forEach((stat) => {
  if (stat.type === "inbound-rtp" && stat.kind === "audio") {
    console.log("получено пакетов:", stat.packetsReceived);
    console.log("потеряно пакетов:", stat.packetsLost);
    console.log("jitter (с):", stat.jitter);
    console.log("задержка буфера джиттера (с):", stat.jitterBufferDelay);
    console.log("concealed samples:", stat.concealedSamples);
  }
});

Пять чисел, которые это печатает, почти один в один ложатся на классы отказов в этом runbook. Растущий packetsReceived значит, что звук вообще приходит; растущие packetsLost и jitter – сетевая проблема; быстро растущий concealedSamples – получатель выдумывает звук, чтобы закрыть пропуски, а это и есть то, как звучит искажение. Спецификация W3C точно определяет concealed sample: это «сэмпл, который был потерян или пришёл слишком поздно для воспроизведения и поэтому был заменён локально синтезированным сэмплом». Мы используем каждое из этих чисел в проверках ниже. Смежная статья WebRTC-конвейер звука целиком объясняет всю цепочку, которую описывают эти числа.

«О чтении накопителей. Большинство чисел getStats() – это итоги, накопленные с начала звонка: concealedSamples, packetsLost, totalAudioEnergy. Одно измерение почти ничего не говорит; всё говорит скорость изменения между двумя измерениями с разницей в секунду. Всегда снимайте дважды и вычитайте. packetsLost, равный 4000, звучит тревожно, пока не узнаешь, что он накопился за двухчасовой звонок без слышимого эффекта; те же 4000 за десять секунд – это мёртвый звонок.»

Проверка 1 – устройство, права и маршрутизация (самая частая причина)

Какой симптом объясняет: полная тишина, обычно односторонняя, часто «вчера работало». Какое число читать: audioLevel на источнике у отправителя и состояние разрешения на микрофон. Инструмент: navigator.permissions, ограничения getUserMedia, отчёт media-source в getStats().

Больше тикетов по звуку вызвано операционной системой, браузером и железом, чем вашим кодом. Микрофон выключен в ОС, разрешение в браузере отклонено, пользователь выбрал не то устройство ввода или звук уходит на Bluetooth-гарнитуру, которая подключена, но не выбрана. Ни одно из этого не баг вашего приложения, и всё маскируется под «у вас всё сломалось».

Начните с источника: производит ли микрофон сигнал вообще? На это отвечает собственная статистика отправителя. API статистики W3C выдаёт audioLevel на источнике звука – «число от 0 до 1 (линейно), где 1.0 это 0 dBov, 0 это тишина». Если пользователь говорит, а audioLevel держится на нуле или около, в конвейер ничего не входит, и ничто ниже по течению это не починит. Перед вами проблема выключенного или не того устройства, а не транспорта.

// Производит ли микрофон сигнал? Читаем источник на стороне отправителя.
const report = await pc.getStats();
report.forEach((stat) => {
  if (stat.type === "media-source" && stat.kind === "audio") {
    console.log("уровень звука (0-1):", stat.audioLevel);
    console.log("суммарная энергия звука:", stat.totalAudioEnergy);
  }
});

Если audioLevel ровно на нуле, идите по цепочке источника наружу в таком порядке – каждый пункт чаще следующего: микрофон выключен на уровне ОС (физический переключатель или настройки звука системы); вкладка браузера заглушена или разрешение на микрофон отозвано; выбрано не то устройство ввода (встроенный микрофон ноутбука вместо гарнитуры); или выбранное устройство выдернули посреди звонка, а браузер не переключился. Состояние разрешения читается одной строкой:

// Действительно ли странице разрешено пользоваться микрофоном?
const status = await navigator.permissions.query({ name: "microphone" });
console.log("разрешение на микрофон:", status.state); // "granted" | "denied" | "prompt"

Фиксы операционные, а не кодовые: попросите пользователя проверить mute в ОС, заново выдать разрешение или выбрать нужное устройство в селекторе приложения. Долгосрочный фикс – это дизайн продукта: каждое серьёзное приложение для звонков показывает живой индикатор уровня входа на экране перед звонком, питаемый ровно тем же audioLevel, чтобы пользователь видел мёртвый микрофон до звонка, а не во время. Ограничения, запрашивающие микрофон, и флаги echoCancellation, autoGainControl, noiseSuppression определены в спецификации W3C «Media Capture and Streams»; правильный выбор устройства и его ограничений на этапе getUserMedia() предотвращает большинство таких тикетов.

«Частая ошибка: винить сеть за выключенный микрофон. Инстинкт при тикете «нет звука» – проверять соединение. Но если audioLevel отправителя равен нулю, сеть не при делах – отправлять нечего. Чтение уровня источника первым спасает от бесплодного захвата пакетов. Поэтому устройство – это Проверка 1, а захват – Проверка 5.»

Проверка 2 – сетевой путь: потери, джиттер и буфер

Какой симптом объясняет: рваный, роботизированный, «подводный» или прерывистый звук – сигнал есть, но повреждён. Какое число читать: скорость packetsLost, jitter, jitterBufferDelay и скорость concealedSamples. Инструмент: отчёт inbound-rtp в getStats(), снятый дважды.

Если звук приходит, но звучит неправильно, главный подозреваемый – сеть, и входящая статистика получателя называет проблему точно. Три числа работают вместе, и читать их группой – и есть весь навык.

Потеря пакетов – доля аудиопакетов, которые не дошли. Прочитайте packetsLost и packetsReceived дважды с разницей в несколько секунд и посчитайте долю потерь за это окно:

доля потерь = (packetsLost_сейчас - packetsLost_до)
            / (packetsReceived_сейчас - packetsReceived_до
               + packetsLost_сейчас - packetsLost_до)

Подставим реальные числа. Допустим, между двумя измерениями с разницей в десять секунд вы видите 120 новых потерянных пакетов и 4880 новых полученных: доля потерь = 120 / (4880 + 120) = 120 / 5000 = 0,024, или 2,4%. Для голоса всё ниже примерно 1% неслышно благодаря маскировке потерь; 2–5% заметны как редкие щелчки; выше 5% звонок резко деградирует, если не работает избыточность. Инструменты починки потерь живут в двух смежных статьях: Packet loss concealment (PLC) скрывает пропавшие фреймы постфактум, а FEC и RED-избыточность предотвращает пропуск, отправляя звук дважды.

Джиттер – это разброс во времени прихода пакетов: пакеты, которые должны приходить ровно каждые 20 миллисекунд, вместо этого скучиваются и отстают. Поле jitter сообщает об этом, а его расчёт определён в IETF RFC 3550, Приложение A.8, как скользящее среднее разницы времени передачи между соседними пакетами. Одна тонкость подводит всех: джиттер сообщается в getStats() в секундах, но RTP внутри измеряет время в единицах частоты дискретизации, и для Opus – кодека почти каждого WebRTC-звонка – эта частота фиксирована на 48 000 тактов в секунду по IETF RFC 7587. Джиттер 0,03 в статистике означает 30 миллисекунд разброса, которые буфер джиттера обязан поглотить.

Буфер джиттера – это амортизатор получателя. Он коротко держит приходящие пакеты, чтобы выдавать их по ровному расписанию вопреки неравномерному приходу – как зал ожидания в аэропорту, который держит пассажиров с нерегулярных рейсов, чтобы автобус ушёл по фиксированному расписанию. Поле jitterBufferDelay, делённое на jitterBufferEmittedCount, даёт среднее время ожидания звука. Больший буфер скрывает больше джиттера, но добавляет задержку; буфер автоматически меняет задержку на плавность. Когда потери или джиттер его перегружают, буфер сдаётся, и получатель синтезирует замену, считая её в concealedSamples. Быстро растущий concealedSamples – более процента-двух от totalSamplesReceived за окно – это числовая подпись слышимого искажения. Вся механика – предмет статьи Буфер джиттера: NetEQ.

Фикс зависит от того, какое число высоко. Высокие потери при низком джиттере – это канал с потерями: включите FEC или RED. Высокий джиттер – нестабильный канал: буфер вырастет, чтобы компенсировать, ценой задержки, и приложение мало что может сделать, кроме как позволить буферу адаптироваться. Оба высоки сразу – насыщенный или перегруженный путь, и это отсылает к истории управления битрейтом в статье Битрейт и полоса пропускания в реальном времени.

Рисунок 2. Проверка сети в числах. Потери случаются в облаке; джиттер – неровный приход; буфер поглощает джиттер ценой задержки; concealed samples – получатель выдумывает звук, когда буфер пуст. Каждое – именованное поле getStats.

Проверка 3 – сходимость эхоподавления

Какой симптом объясняет: эхо – одна сторона слышит собственный голос с задержкой. Какое число читать: настройку echoCancellation на дорожке захвата; используются ли наушники. Инструмент: MediaStreamTrack.getSettings() плюс быстрый вопрос об окружении.

Эхо – не сетевая проблема и не проблема тишины устройства; это проблема обработки, и она в отдельной проверке, потому что её симптом ни с чем не спутаешь. Эхо возникает, когда динамик одного участника воспроизводит голос другого, этот звук возвращается в микрофон первого и отправляется обратно – так второй слышит себя с задержкой. Задача акустического эхоподавления, AEC, – распознать выход динамика внутри входа микрофона и вычесть его. Современная реализация WebRTC, AEC3, делает это хорошо, но у неё есть режим отказа, который стоит знать.

Сначала убедитесь, что AEC вообще включён. Дорожка захвата несёт свои текущие настройки, и спецификация W3C Media Capture and Streams определяет echoCancellation как булево значение, которое можно прочитать обратно:

// Убеждаемся, что эхоподавление действительно включено на живой дорожке захвата.
const [track] = localStream.getAudioTracks();
const settings = track.getSettings();
console.log("echoCancellation:", settings.echoCancellation); // ожидаем true

Если читается false, что-то в вашем коде захвата запросило сырой звук – частая ошибка, когда разработчик копирует сниппет getUserMedia, предназначенный для записи музыки, где эхоподавление намеренно выключено. Запросите дорожку заново с echoCancellation: true.

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

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

Проверка 4 – рассинхрон частоты дискретизации

Какой симптом объясняет: стойкое искажение или сдвиг тона, которое не объяснила проверка сети, – звук слегка быстрый, медленный или «бурундучий». Какое число читать: частоту дискретизации на захвате, на кодеке и на аудиоконтексте. Инструмент: AudioContext.sampleRate, настройки дорожки и конфигурация кодека на сервере.

Это тонкий случай, и он Проверка 4, потому что реже первых трёх, но для них невидим. Цифровой звук – это поток сэмплов, взятых с фиксированной частотой; для видео и WebRTC стандарт – 48 000 сэмплов в секунду, 48 кГц. Беда появляется, когда одна стадия конвейера предполагает другую частоту, чем другая. Если звук захвачен на 44,1 кГц, а стадия ниже трактует его как 48 кГц, каждая секунда звука проигрывается меньше чем за секунду, и тон растёт – эффект «бурундука». Обратное роняет тон. Более тонкая версия даёт периодические щелчки или медленный дрейф, пока две частоты расходятся.

WebRTC стандартизирует 48 кГц именно чтобы этого избежать – Opus работает внутри на 48 кГц, и тактовая частота его RTP-полезной нагрузки фиксирована на 48 000 Гц в RFC 7587, – так что в чистом WebRTC-звонке рассинхрон обычно вносится на краях: граф Web Audio на нативной частоте железа, ветка записи, написанная на неправильной частоте, или шлюз в сторону PSTN-плеча на 8 кГц. Проверка – прочитать частоту на каждой границе и убедиться, что они совпадают или между ними стоит явный ресэмплер:

// На какой частоте дискретизации реально работает граф Web Audio?
const ctx = new AudioContext();
console.log("частота AudioContext:", ctx.sampleRate); // обычно 48000, иногда 44100

Фикс – никогда не «форсировать» частоту в надежде на лучшее; это вставить правильный ресэмплер на границе, где частота законно меняется, чтобы захват 44,1 кГц был пересэмплирован в 48 кГц до кодека на 48 кГц. Фон о том, почему 48 кГц победили в видео и сколько стоит ресэмплинг, – в статье Частота дискретизации: 44,1, 48, 96, 192 кГц. Ветка записи или транскрипции – частый виновник, потому что прикручивается уже после того, как звонок заработал; см. Запись и транскрипция: ASR-конвейер.

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

Проверка 5 – захват пакетов: последнее средство

Какой симптом объясняет: всё, что не объяснили первые четыре проверки, особенно серверные и сигнальные проблемы. Инструмент: chrome://webrtc-internals (или эквивалент браузера) и Wireshark на PCAP, когда нужен сам провод.

Если проблема пережила первые четыре проверки, вы исчерпали то, что видит приложение, и пора смотреть на байты. Есть две глубины.

Первая, и та, что пробовать до тяжёлой артиллерии, – собственный дамп браузера. Chrome выдаёт chrome://webrtc-internals, живую страницу, которая пишет каждое значение getStats() как график для каждого активного соединения и позволяет выгрузить всю сессию в файл. Это те же данные, что читает код выше, но отрисованные во времени и собранные автоматически – бесценно, когда проблема прерывистая и не воспроизводится по требованию. Попросите пользователя открыть chrome://webrtc-internals во второй вкладке до звонка, воспроизвести сбой и прислать вам дамп. График concealedSamples или packetsLost за ту самую секунду, когда сломался звук, обычно называет причину без дальнейшей работы.

Вторая глубина – настоящий захват пакетов в Wireshark, стандартном анализаторе сетевых протоколов. К нему тянутся, когда вопрос ниже приложения – уходят ли RTP-пакеты с машины вообще, несут ли они ожидаемый кодек, договорилось ли согласование (SDP) о формате полезной нагрузки, который обе стороны предположили. Wireshark декодирует RTP и RTCP, может показать потери и джиттер по потоку, посчитанные независимо от браузера, а на захвате звука даже воспроизвести RTP-поток, чтобы вы услышали, что реально прошло по проводу. Так ловятся отказы, которые вообще не доходят до getStats(): файрвол, роняющий медиа, пока сигналинг проходит (звонок «соединяется», но молчит), несовпадение кодеков из-за сломанного согласования SDP или односторонняя медиа из-за асимметричного NAT. Архитектура, решающая, куда идут эти пакеты и где их захватывать, разобрана в статье Аудио в SFU, MCU и P2P.

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

Пять проверок рядом

Таблица – это runbook в одном виде. Распечатайте, прикрепите рядом с дежурной панелью и идите сверху вниз.

ПроверкаСимптомЧисло / сигнал для чтенияТипичный фикс
1Устройство и праваПолная тишина, односторонняяaudioLevel отправителя ≈ 0; разрешение deniedСнять mute в ОС, выдать заново, выбрать устройство
2Сетевой путьРваный, роботизированный, прерывистыйскорость packetsLost, jitter, скорость concealedSamplesВключить FEC/RED; дать буферу адаптироваться
3ЭхоподавлениеЗвонящий слышит себяechoCancellation = false; на громкой связи?Включить AEC; наушники
4Рассинхрон частотыНеправильный тон/скорость, чистая сетьчастоты на захвате vs кодеке vs контекстеВставить ресэмплер на границе
5Захват пакетовВсё необъяснённоедамп webrtc-internals; RTP в WiresharkНайти дропнутую/несовпадающую медиа на проводе

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

Рисунок 3. Почему важен порядок. Большинство инцидентов решаются на первых двух проверках; каждая следующая реже и дороже. Идти по порядку – вот разница между двухминутной и двухчасовой диагностикой.

Разбор инцидента: «пациент не слышит врача»

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

Проверка 1, на стороне врача: читаем audioLevel отправителя у врача, пока он говорит. Читается 0,34 – здоровый уровень. Микрофон производит сигнал, значит, это не выключенный микрофон на стороне врача. Дальше.

Проверка 2, на стороне пациента: читаем входящую статистику inbound-rtp пациента для звука врача. packetsReceived не растёт между двумя измерениями. Пакеты не приходят вообще. Это не потери и не джиттер – те показали бы приходящие и повреждаемые пакеты. Нулевой приход при соединённом звонке указывает мимо приложения, на сам путь.

Перепрыгиваем на Проверку 5, потому что нулевой приход пакетов при «соединённом» звонке – классическая подпись блокировки медиа при успешном сигналинге. Дамп webrtc-internals у пациента подтверждает: входящий поток звука существует, но показывает ноль полученных байт. Короткий захват Wireshark на сети пациента показывает, что RTP врача не приходит – корпоративный файрвол на больничной сети пациента пропускает сигнальный канал, но роняет UDP-медиа. Фикс операционный (TURN-релей через TCP/443 для обхода файрвола), а не изменение кода звука. Общее время до названной причины: меньше десяти минут, потому что односторонняя классификация и чтение «пакеты не растут» вывели нас прямо на нужную глубину.

Урок – весь тезис runbook: классификация симптома плюс одно-два чтения getStats() почти всегда указывают на нужную проверку, а нужная проверка называет причину.

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

Мы эксплуатируем звук в реальном времени в телемедицине, видеоконференциях, онлайн-обучении и live-событиях с 2005 года, а значит, прогоняли этот runbook в бою больше раз, чем можем сосчитать. Закономерность держится по всем вертикалям: подавляющее большинство обращений «сломался звук» – это проблемы устройства, прав или сети, созданные окружением пользователя, а не дефекты медиакода, – и буксуют те команды, у которых нет фиксированного порядка проверок. В продуктах, которые мы поставляем, экран перед звонком всегда показывает живой индикатор микрофона, чтобы мёртвое устройство ловилось до звонка, а клиент во время звонка непрерывно снимает getStats(), чтобы поддержка могла прочитать числа потерь и concealment из уже завершившейся сессии. Встроенная с самого начала наблюдаемость – вот разница между диагностикой по данным и догадками по скриншоту.

Главное

  • Сначала классифицируйте: односторонний или двусторонний и тишина, искажение или эхо.
  • Идите по пяти проверкам по порядку; большинство тикетов кончается на Проверке 1 или 2.
  • getStats() – это панель: читайте audioLevel источника, затем потери и concealment на входе.
  • Снимайте два измерения с разницей в секунду; важна скорость изменения, а не итог.
  • Эхо – наушники или AEC; неправильный тон при чистой сети – частота дискретизации.
  • Тянитесь к Wireshark последним, а не первым – это самая дорогая проверка.

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

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

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