Войти
ПрограммированиеФорумОбщее

Нужен алгоритм компрессии звука

Страницы: 1 2 Следующая »
#0
(Правка: 13:03) 13:00, 14 окт. 2021

Разрабатываю приложение, в котором пользователь записывает свою речь на телефон и на неё накладывается музыка. Проблема в том, что иногда музыка заглушает речь. Одни люди говорят тихо, другие громко, а ещё у всех разные микрофоны. Ещё может так получиться, что в середине речи человек перекрыл микрофон и звук стал в 10 раз тише.
Поэтому я хочу привести громкость голоса к фиксированному значению, чтобы скомпенсировать вышеперечисленные эффекты и получить примерно один уровень громкости, под который можно будет подобрать подходящую фиксированную громкость музыки.

Вспомнил про компрессию звука, нашёл алгоритм https://github.com/velipso/sndfilter/blob/master/src/compressor.c и встроил его в приложение. Запускаю с дефолтными параметрами (sf_defaultcomp) и получаю какие-то странные периодические щелчки, при этом громкость не выровнялась - скачки в 10 раз как были, так и остались. Пробовал методом тыка менять параметры, но эффекта особо не заметил.
Ещё пробовал уменьшать samplesperchunk с дефолтных 32. Только при значении 1 щелчки пропали.
Это вообще рабочий код? Пробовал немного подебажить, ничего подозрительного не нашёл. А в целом, этот алгоритм для меня делает какую-то магию, поэтому сложно понять, в чём проблема.

Может у кого-нибудь есть на примете другой рабочий код, подходящий для моей задачи?
Мне нужен код на Си, и он должен уметь работать в потоковом режиме. Желательно, чтобы не надо было тянуть целые библиотеки, а просто скопировать алгоритм себе в проект из 1-2 исходных файлов.


#1
15:08, 14 окт. 2021

https://www.musicdsp.org/en/latest/index.html смотрел?
Например https://www.musicdsp.org/en/latest/Effects/204-simple-compressor-class-c.html .
Я, впрочем, не присматривался. И вообще в dynamic range compression не вникал.

#2
(Правка: 15:34) 15:23, 14 окт. 2021

А самому написать слабо? Самый примитивный алгоритм - берешь текущую амплитуду звука (максимальное значение огибющей), если это значение выше некой переменной - добавляешь в эту переменную величину разницы умноженную на коэфициент к1 , если ниже - уменьшаешь ее значение на величину разницы с коэфициентом к2. Потом делишь входной сигнал на значение этой переменной - выходной сигнал получается скомпрессированным.
Собственно это алгоритм работы примитивного аналогового компрессора где величина усиления/ослабления берется от конденсатора стоящего после выпрямителя. Скорость заряда конденсатора (к1) это скорость реакции на резкое усиление входного сигнала а скорость его разряда (к2) - вытаскивание звука при его уменьшении.

#3
(Правка: 15:53) 15:49, 14 окт. 2021

FordPerfect
> Например
> https://www.musicdsp.org/en/latest/Effects/204-simple-compressor-class-c.html .
Как раз после того, как задал вопрос, нашёл этот код в репозитории другого человека, который утащил его себе. Недостаток в том, что это C++, а приложение на другом языке. Сходу не прикрутишь как тот пример, надо в Си оборачивать. И там у классов зависимости друг от друга, так что видимо всё содержимое архива тащить придётся или руками править.

san
> А самому написать слабо?
Была идея, но хотел посмотреть, что есть из готового. Для задачи из #0 можно было бы и свой велосипед написать, но не исключено, что потом полноценная компрессия понадобится. В будущем приложение будет решать множество задач и шаблоны их решения будут делать дизайнеры и другие люди, не являющиеся программистами. Лучше дать им универсальный инструмент, чем велосипед для одного кейса.

#4
(Правка: 18:08) 18:04, 14 окт. 2021

san
> берешь текущую амплитуду звука (максимальное значение огибющей)

Лучше сумму квадратов за некоторый промежуток времени. Это правильнее. Потому что если в сигнале две синусоиды близких частот и одинаковой амплитуды, биения будут иметь удвоенную амплитуду, тогда как для получения такой же громкости из одной синусоиды ее надо увеличивать не в два раза, а лишь в корень квадратный из двух.

#5
18:28, 14 окт. 2021

gammaker
> Ещё пробовал уменьшать samplesperchunk с дефолтных 32. Только при значении 1
> щелчки пропали.
> Это вообще рабочий код? Пробовал немного подебажить, ничего подозрительного не
> нашёл. А в целом, этот алгоритм для меня делает какую-то магию, поэтому сложно
> понять, в чём проблема.

В общем случае у тебя будут как минимум три параметра, которые надо будет подбирать вручную (поэтому эти параметры обычно выводят на ручки-крутилки во всяких VST-плагинах) для каждой ситуации.

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

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

В-третьих - время затухания огибающей

Ну и еще в особо продвинутых случаях - входной фильтр перед анализом RMS, скажем, чтоб реагировал только на частотный диапазон речи, но не на посторонние звуки. В еще более продвинутых - вообще эквалайзер.

#6
18:32, 14 окт. 2021

Кстати, если у тебя не совсем реалтайм, а обработка уже записанного сигнала, или хотябы реалтайм с упреждением - имеет смысл окно опроса сдвинуть "вперед" по времени относительно применения огибающей, и тогда можно не делать атаку слишком короткой, а сделать ее в соответствии с длиной упреждающего окна.

#7
19:38, 14 окт. 2021

Dmitry_Milk
> Во-первых - порог срабатывания огибающей, чтоб компрессор не пытался
> "выровнять" шум тишины, а выравнивал только уровень полезного сигнала.
>
> Во-вторых - время атаки огибающей. Слишком долгая атака - будет медленно
> реагировать после долгих пауз. Слишком быстрая - будет восприниматься как
> щелчок.
>
> В-третьих - время затухания огибающей
Всё же я предполагаю, что дефолтные значения в sf_defaultcomp по ссылке должны были быть разумными и эффект компрессии должен был быть адекватным. Я взял запись звука, искусственно понизил громкость в 10 раз фрагмента посередине длиной 5 секунд. Я ожидал, что в момент скачка громкости переход можно будет заметить, но потом громкость выровняется. Но она не выровнялась. Похоже, что тот алгоритм не работает вообще.

Dmitry_Milk
> Кстати, если у тебя не совсем реалтайм, а обработка уже записанного сигнала,
> или хотябы реалтайм с упреждением - имеет смысл окно опроса сдвинуть "вперед"
> по времени относительно применения огибающей, и тогда можно не делать атаку
> слишком короткой, а сделать ее в соответствии с длиной упреждающего окна.
У меня обработка записанного звука в потоковом режиме с размером буфера примерно 80 мс. Оно синхронизируется с видео. Я так понимаю, твой совет позволит избежать лага при длинной атаке, но в любом случае не думаю, что задержка в несколько десятков миллисекунд будет заметна.

#8
20:57, 14 окт. 2021

gammaker
> Я взял запись звука, искусственно понизил громкость в 10 раз фрагмента
> посередине длиной 5 секунд.

Может быть твоя оригинальная запись сама по себе уже тихая, и поэтому ослабленный участок оказался ниже порога срабатывания?

#9
21:01, 14 окт. 2021

Dmitry_Milk
> Может быть твоя оригинальная запись сама по себе уже тихая
Нет, она громкая, максимальное значение семплов почти единица.

#10
0:39, 15 окт. 2021

Dmitry_Milk
> Лучше сумму квадратов за некоторый промежуток времени. Это правильнее. Потому что если в сигнале две синусоиды близких частот и одинаковой амплитуды, биения будут иметь удвоенную амплитуду
Я же написал, что к значению добавляется разница умноженная на некий коэффициент. Другими словами там не мгновенное абсолютное значение а накопление отклонения. Скажем если коэффициент 0.1 то реакция на биение будет в 10 раз сглажена. Ну и входной сигнал компрессора обычно фильтрован (высокие частоты срезаны). Как следствие при резком увеличении амплитуды наблюдается "выброс" в первый момент, но на практике это незаметно. Собственно так и работают аналоговые компрессоры испокон веков.

gammaker
> не исключено, что потом полноценная компрессия понадобится.
Я тебе и описал полноценную компрессию в реальном времени. Используя постобработку можно достичь лучшего результата но это явно не твой случай. А в реалтайм все равно все сведется к приведенному алгоритму. В любом случае полезно понимать КАК работает программа а не бездумно копипастить чужой код.

#11
(Правка: 7:00) 6:59, 15 окт. 2021

тренируй ИИ - проще всего.

> Мне нужен код на Си

ааа вы из этих, как там в 1969?

#12
(Правка: 11:09) 11:07, 15 окт. 2021

san
> ругими словами там не мгновенное абсолютное значение а накопление отклонения.

Да и я имел в виду не мгновенное, а накопление/усреднение. Но я уточняю, что накапливать надо не абсолютное значение сигнала (т.к. это будет просто 0 при отсутствии постоянной составляющей), и даже не максимумы усреднять, как предлагаешь ты.

Для нормализации громкости накапливать и усреднять надо именно квадратичные значения выборок сигнала, беря от среднего корень (часто это обозначают англоязычным термином RMS, RootMeanSquare), потому что громкость сложного сигнала сопоставляется именно с RMS, а не с амплитудой.

Оценку амплитуд тоже используют, но для других целей - для того, чтоб не было клиппирования, чтоб амплитуда сигнала не выскакивала за пределы представления. А амплитуда сложного сигнала может быть совершенно разной при одном и том же RMS, т.к. она зависит от моментов совпадения фаз различных составляющих синусоид, и в принципе стопроцентно точно она не предсказывается.

#13
11:31, 15 окт. 2021

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

И если это накладывается на "бубнящую" музыку, то скорее всего речь будет хорошо различима даже если ее громкость заметно меньше громкости музыки. И в то же время наоборот, даже достаточно громкую речь, если она записана с близкого расстояния динамическим микрофоном, будет заглушать не очень громкая музыка с преобладанием частот в районе от килогерца до четырех-пяти.

Так что для общего случая надо оценку соотношения громкостей производить по разным частотным полосам отдельно.

#14
13:17, 15 окт. 2021

gammaker
Вот мой компрессор из этой демки:

Public Sub MainCompressor()
  Dim i As Long
  Dim s1 As Single, s2 As Single
  Dim k As Single
  Const Att As Single = 1 / (44100 * 0.005)
  Const Rel As Single = 1 / (44100 * 0.2)
  Const Pre As Single = 200000
  Dim Lev As Single

  Lev = 22000
  k = 1
  For i = 0 To SizeSong - 1
    s1 = Abs(ArSong(i).L) * Pre
    s2 = Abs(ArSong(i).R) * Pre
    If s1 < s2 Then s1 = s2

    If s1 > Lev Then
      Lev = Lev * (1 - Att) + s1 * Att
    Else
      Lev = Lev * (1 - Rel) + s1 * Rel
    End If
    If Lev > 22000 Then
      k = 22000 / Lev
    Else
      k = 33000 / (Lev + 11000)
    End If
    DSBAr(0, i) = Sgm(ArSong(i).L * Pre * k)
    DSBAr(1, i) = Sgm(ArSong(i).R * Pre * k)
  Next i
End Sub

Private Function Sgm(ByVal d As Single) As Integer
  Select Case d
    Case Is > 32000
      d = d - 32000
      Sgm = 32000 + 767 * d / (767 + d)
    Case Is < -32000
      d = 32000 - d
      Sgm = -32000 - 767 * d / (767 + d)
    Case Else
      Sgm = d
  End Select
End Function
ArSong() - float стерео входной массив, "нормальный" уровень около 1.
DSBAr() - int16 стерео выходной массив (от -32767 до 32768).
Константы Att, Rel, Pre - атака, затухание и входная чувствительность.
Страницы: 1 2 Следующая »
ПрограммированиеФорумОбщее