Содержание статьи +
- Коротко
- Почему это важно
- Как пользоваться этим runbook
- Браузер уже всё знает: минутный ввод в getStats
- Проверка 1 – устройство, права и маршрутизация (самая частая причина)
- Проверка 2 – сетевой путь: потери, джиттер и буфер
- Проверка 3 – сходимость эхоподавления
- Проверка 4 – рассинхрон частоты дискретизации
- Проверка 5 – захват пакетов: последнее средство
- Пять проверок рядом
- Разбор инцидента: «пациент не слышит врача»
- Где здесь Фора Софт
- Главное
- Что почитать дальше
Коротко
Когда пользователь сообщает, что на живом звонке «не работает звук», причина почти всегда одна из пяти, и найти её можно за минуты, если проверять в правильном порядке: права и маршрутизация устройства, сетевой путь, сходимость эхоподавления, рассинхрон частоты дискретизации или молчащий источник захвата. Самый полезный инструмент уже встроен в браузер – getStats() возвращает числа, по которым видно, уходит ли звук от отправителя, приходит ли к получателю и переживает ли он буфер джиттера. Этот runbook задаёт точный порядок проверок, конкретное число для чтения на каждом шаге и фикс для каждого отказа. Идите сверху вниз и останавливайтесь на первой проверке, которая сработала, – шаги выстроены так, что самые частые и дешёвые причины идут первыми.
Почему это важно
Сбои звука – это те тикеты в поддержку, из-за которых пользователи бросают продукт: глитч в видео раздражает, но звонок, на котором вас не слышно, бесполезен. Если вы строите или эксплуатируете видеоконференцию, телемедицинскую платформу, онлайн-класс или live-shopping, вашему дежурному инженеру нужна спокойная упорядоченная процедура на случай, когда звук ломается в два часа ночи, а не догадки. Этот runbook написан для инженера с пейджером и для продакт-менеджера, которому надо понимать, что этот инженер делает. Он превращает расплывчатое «нет звука» в диагностику из пяти шагов с названной причиной и известным фиксом, так что инцидент заканчивается за минуты, а не за день перебора вариантов.
Как пользоваться этим runbook
Сбой звука в продакшене кажется хаосом, потому что симптом – «я их не слышу» – одинаков для десятка разных корневых причин. Лекарство от хаоса – фиксированный порядок. В этом 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. Высокий джиттер – нестабильный канал: буфер вырастет, чтобы компенсировать, ценой задержки, и приложение мало что может сделать, кроме как позволить буферу адаптироваться. Оба высоки сразу – насыщенный или перегруженный путь, и это отсылает к истории управления битрейтом в статье Битрейт и полоса пропускания в реальном времени.
Проверка 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, потому что путаница с устройством и плохие сети – попросту самое частое, что ломается. Каждый шаг вниз по списку реже и дороже в расследовании. Соблюдение порядка – вот что превращает двухчасовую отладку в двухминутную диагностику.
Разбор инцидента: «пациент не слышит врача»
Конкретное бьёт абстрактное, поэтому вот 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 последним, а не первым – это самая дорогая проверка.