Шумоподавление, которое не стирает голос
Лекции, записанные десятилетия назад, приходят с шипением плёнки, гулом сети и треском. Трудно не убрать шум, а убрать его так, чтобы не съесть голос, не превратить паузы в мёртвую тишину и не выдумать звук, которого никогда не было.
Лекции, которые мы публикуем, записали десятилетия назад на кассеты и катушки — в храмовых комнатах и в дороге. Возраст слышен: ровное шипение плёнки, гул сети, треск дешёвого микрофона. Кажется, задача давно решена — запусти шумоподавитель. Но шумоподавитель, который стирает шипение, обычно стирает и часть голоса. Он превращает паузы в неестественный вакуум. А новейшие обучаемые модели ещё и выдумывают звук, которого никто не произносил.
Поэтому правило очистки у нас то же, что и везде: сначала верность источнику. Понижаем шум, не калечим голос, не выбрасываем оригинал.
Как очищается запись
На входе — зашумлённая запись, на выходе — чистая. Между ними очиститель подбирает стратегию под материал, прогоняет её, выравнивает громкость и перекодирует в стандартный моно-MP3 128 кбит/с.
flowchart TD
IN["Зашумлённая запись"] --> STR{"Выбор стратегии"}
STR -->|afftdn| A["Шумоподавление через FFT"]
STR -->|"rnnoise-mix"| R["RNNoise плюс подмешивание по вероятности голоса"]
STR -->|deepfilternet| D["DeepFilterNet3, обучаемая модель"]
A --> NORM["Нормализация громкости"]
R --> NORM
D --> NORM
NORM --> OUT["Чистая запись"]
Пять стратегий, один принцип
Единого шумоподавителя нет. Есть пять стратегий на выбор. Каждая — своя точка компромисса между тем, сколько шума уходит, и тем, сколько записи уцелеет.
| Стратегия | Что это | Что даёт |
|---|---|---|
afftdn | адаптивный FFT-шумоподавитель из ffmpeg | мягко понижает фон, без ML, самый быстрый |
rnnoise | речевой шумоподавитель RNNoise | агрессивный — может загнать паузы в тишину |
rnnoise-mix | RNNoise, подмешанный обратно по вероятности голоса | в паузах остаётся естественный фон |
afftdn-rnnoise-mix | afftdn → RNNoise → подмешивание истинного оригинала | ровный шум усмирён, фон сохранён |
deepfilternet (по умолчанию) | обучаемый шумоподавитель DeepFilterNet3 | убирает шипение без лишнего гейтинга |
afftdn — классический ход. Это потомок спектрального
вычитания: он оценивает спектр
шума и вычитает его в частотной области. Фон он понижает лишь на заданную
величину и не делает жёсткого гейтинга — безопасно, но мягко:
# afftdn: reduce the noise floor by `nr` dB, then a safety limiter
af = f"afftdn=nr={nr}:nf={nf},alimiter=limit=0.95" # nr=12 dB, nf=-25 dB
rnnoise сильнее, но на шумном материале он передавливает: паузу для вдоха
загоняет в мёртвую, безвоздушную тишину, которая звучит ещё хуже шипения.
Лечится это не отказом от него, а тем, что его подмешивают обратно к
оригиналу.
По умолчанию стоит deepfilternet — обучаемый полнополосный усилитель
речи, который работает в реальном
времени на обычном CPU. Мы выбрали его вот почему. Ровное шипение плёнки он
убирает гораздо лучше afftdn и без артефактов чрезмерного гейтинга, как у
RNNoise. И, что важно, это дискриминативная модель, а не генеративная: она
подавляет всё, что не голос, но не станет
галлюцинировать звук, которого
в записи не было. Тот же ужас, от которого мы защищаемся в чате, — модель
придумывает то, чего не было, — есть и в звуке. Избегаем его так же: берём
инструменты, которые вычитают, а не воображают.
Смесь, которая сохраняет фон комнаты
Самая интересная строка кода борется с передавливанием. Для каждого кадра в 10 мс RNNoise выдаёт и очищенный звук, и вероятность голоса — сигнал детектора речевой активности: около 1 на речи и около 0 в паузе. По нему мы решаем кадр за кадром, сколько оригинала оставить.
Пусть — вероятность голоса в кадре . Долю оригинала, которую мы подмешиваем обратно, задаёт огибающая между двумя границами: в паузах оригинала больше, на голосе меньше:
а на выходе — выпуклая смесь оригинального и обработанного сигнала:
В паузе () оставляем 10% оригинала — и тишина сохраняет слабый, правдоподобный фон комнаты вместо вакуума. На голосе () оставляем 25% — и тембр остаётся. Потом огибающую сглаживаем гауссианом, чтобы соотношение смеси не прыгало от кадра к кадру и не рождало музыкальный шум — переливчатый артефакт, который выдаёт небрежный шумоподавитель:
mix_envelope[start:end] = min_mix + (max_mix - min_mix) * voice_prob
mix_envelope = gaussian_filter1d(mix_envelope, sigma=transition_samples / 3)
mixed = mix_envelope * original_float + (1.0 - mix_envelope) * processed_float
Цепочка afftdn-rnnoise-mix идёт ещё дальше: сначала afftdn усмиряет ровный
шум, потом RNNoise очищает результат. Но обратно подмешивается истинный
оригинал, а не промежуточный afftdn — так что фон в паузах берётся из
настоящей записи, а не из её обработанной копии.
До и после
Вот тридцать секунд из лекции по «Шримад-Бхагаватам» 1973 года, а под ней — наш прогон по умолчанию через DeepFilterNet3. На «до» постоянное шипение плёнки заполняет каждый промежуток: фиолетовая дымка размазана по всему верхнему спектру и видна даже в паузах между словами. На «после» этой дымки нет: паузы падают в чистый чёрный, голос звучит чётко и с выровненной громкостью, и ни слова речи не потеряно. Вот вся цель на одной картинке — треск исчезает, голос — нет.

Сверху: сырая запись 1973 года — сплошная дымка и есть шипение плёнки. Снизу: после DeepFilterNet3, нашего варианта по умолчанию. Шипение срезано почти до тишины в паузах, а голос сохранён и выровнен по громкости.
Защита пения
Речевой шумоподавитель считает, что на входе речь. Наведи DeepFilterNet на киртану или на санскрит, который читают нараспев, — и он их изуродует: примет мелодию за шум и вычистит начисто. Ранний вариант корпуса так и сделал, и результат вышел непригодным.
Поэтому очистку применяют не вслепую. План склейки размечает участки с пением и
распевом и направляет их иначе: пение идёт через мягкий afftdn (или copy —
совсем без обработки), а DeepFilterNet проходит только по разговорным фрагментам.
Поверх стыков накладывают единый проход громкости, чтобы соединения были не
слышны. Шумоподавителю показывают, где музыка, и велят её не трогать.
Честная громкость
Старые записи не просто шумные — они ещё и неровные: где-то шёпот, где-то крик.
После шумоподавления громкость выравнивают в два этапа, без накачки:
speechnorm мягко приподнимает тихую речь, а loudnorm приводит весь корпус к
единому вещательному уровню — в духе EBU R128:
интегрированная громкость −16 LUFS и потолок истинного пика −1.5 дБ:
NORMALIZE_FILTER = "speechnorm=e=12.5:r=0.0001:l=1,loudnorm=I=-16:TP=-1.5:LRA=11"
Так что слушатель, переходя от лос-анджелесской лекции 1972 года к вриндаванской 1976-го, не хватается между ними за ручку громкости.
Всегда сохраняй оригинал
Конвейер никогда не перезаписывает источник. Он пишет чистую дорожку рядом с оригиналом, а ползунок «оригинал ↔ чистая» в приложении даёт слушателю в любой момент сдвинуться обратно к сырой записи — тот же порыв, что держать источник в одном касании под каждой переведённой цитатой. В духовных записях артефакт — это ещё и реликвия. Чистая версия — это удобство. Оригинал — сама вещь, и стирать его за слушателя мы не вправе.
Вот что значит «очистка» здесь. Не убрать как можно больше шума — а сохранить как можно больше голоса.
Часть проекта
Слушай Садху