LINUX.ORG.RU

EasyEffects не нужно, или PipeWire для продвинутых: часть 5

 , ,


0

1

Расширение стереоэффекта для динамиков ноутбука

Предыдущие части:

Часть 1

Часть 2

Часть 3

Часть 4

ВАЖНО! Если вы не читали Часть 4, то настоятельно рекомендуется прочитать сначала ее, а то из этой ничего не будет понятно!

В предыдущих четырех статьях цикла мы рассмотрели возможности модуля Pipewire под названием filter-chain. Этот модуль позволяет добавлять цепочки фильтров, реализующих общесистемный эквалайзер, компрессор, лимитер и прочее. В общем, все то что обычно делают при помощи EasyEffects, но без дополнительных сервисов и фоновых приложений, силами самого процесса Pipewire, с минимальным потреблением ресурсов.

Помимо этих, привычных эффектов, сейчас во всех основных коммерческих ОС набирает популярность еще один – спатиалайзер. Этот эффект делает 3D для устройства стереовоспроизведения, создает иллюзию расположения источника звука в пространстве, спереди, сбоку, сзади, притом, что звук воспроизводится через обычные наушники или стереодинамики.

В Части 4 мы рассмотрели, как реализовать спатиалайзер для наушников, там же изложена теория и принципы работы этого эффекта. А сейчас мы реализуем вторую часть этой задачи – расширение стерео эффекта для динамиков ноутбука!

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

Обратимся к тому, что мы делали в Части 4. Там мы имитировали два источника звука в пространстве, расположенные под углами 30 градусов к направлению «прямо». Такое расположение дает оптимальный стереоэффект. А если мы сделаем то же самое с динамиками ноутбука? Заставим звук восприниматься не из самих динамиков, а из пространства впереди с более широкой и правильной стереобазой?

Тут есть одна серьезная проблема – динамики это не наушники. А функции HRTF (смотри часть 4), которые мы использовали, подготовлены для наушников! В чем же разница? Звук из наушников поступает в левое и правое ухо независимо, два стерео канала не смешиваются. Наши HRTF-функции рассчитаны именно на это. Если теперь мы тот же обработанный сигнал пропускаем через динамики и слушаем, то левый и правый каналы СИЛЬНО смешиваются в воздухе перед нами, и мы слышим не то, что нужно! По-английски это явление называется crosstalk. И у нас возникает дополнительная задача – подавить crosstalk, заставить левое ухо слышать только левый канал, а правое ухо – только правый канал, при том что звук идет из близко расположенных динамиков ноутбука!

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

Стерео сигнал можно представить в другой форме – не левый/правый канал, а средний/боковой канал. Если сложить левый и правый, то получится средний. Если из левого вычесть правый – получится боковой. То есть боковой канал содержит тот звук, который должен идти с боков, а средний – то что мы ощущаем посередине (вокал, ударные). Это называется Mid-Side техника, хорошо известная в звукорежиссуре.

Так вот, к чему приводит crosstalk, то есть смешение левого и правого канала звука из колонок, в представлении Mid-Side? Очевидно что, в целом, этот эффект смешивания усиливает средний канал, и ослабляет боковой! Потому что crosstalk добавляет в звук сумму левого и правого канала. А мы сделаем наоборот – ослабим средний канал, не тронув боковой! Это даст обратный эффект, в какой-то степени подавит crosstalk, компенсирует его действие! Не идеально, но вполне достаточно.

После этого, надо звук из Mid-Side формата преобразовать обратно в левый/правый. Сделать это не составит труда, средний плюс боковой дает левый, средний минус боковой дает правый.

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

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

{
  type = builtin
  label = mixer
  name = enhMixL
  control = {
    "Gain 1" = 0.5
  }
}
{
  type = builtin
  label = mixer
  name = enhMixR
  control = {
    "Gain 1" = 0.5
  }
}

Как всегда, привожу полный конфиг-файл, содержимое этого листинга надо поместить в файл по адресу ~/.config/pipewire/pipewire.conf.d/stereoenhancer.conf и перезапустить pipewire. Также обращаю внимание, что этот конфиг, как и приведенный в Части 4, использует сторонний sofa-файл! Его надо скачать, например отсюда https://sofacoustics.org/data/database/ari/dtf%20b_nh2.sofa, а в конфиге указать правильный путь к нему в вашей системе (в моем конфиге, приведенном ниже, прописан путь где он лежит у меня).

context.modules = [
    { name = libpipewire-module-filter-chain
        args = {
            node.description = "StereoEnhancer"
            media.name       = "StereoEnhancer"
            filter.graph = {
                nodes = [
                    {
                        type = builtin
                        label = copy
                        name = copyL
                    }
                    {
                        type = builtin
                        label = copy
                        name = copyR
                    }
                    {
                        type = sofa
                        label = spatializer
                        name = spFL
                        config = {
                            filename = "/home/curufinwe/dtf_b_nh2.sofa"
                        }
                        control = {
                            "Azimuth"    = 30.0
                            "Elevation"  = 0.0
                            "Radius"     = 1.0
                        }
                    }
                    {
                        type = sofa
                        label = spatializer
                        name = spFR
                        config = {
                            filename = "/home/curufinwe/dtf_b_nh2.sofa"
                        }
                        control = {
                            "Azimuth"    = 330.0
                            "Elevation"  = 0.0
                            "Radius"     = 1.0
                        }
                    }
                    {
                        type = sofa
                        label = spatializer
                        name = spRL
                        config = {
                            filename = "/home/curufinwe/dtf_b_nh2.sofa"
                        }
                        control = {
                            "Azimuth"    = 150.0
                            "Elevation"  = 0.0
                            "Radius"     = 1.0
                        }
                    }
                    {
                        type = sofa
                        label = spatializer
                        name = spRR
                        config = {
                            filename = "/home/curufinwe/dtf_b_nh2.sofa"
                        }
                        control = {
                            "Azimuth"    = 210.0
                            "Elevation"  = 0.0
                            "Radius"     = 1.0
                        }
                    }
                    {
                      type = builtin
                      label = mixer
                      name = mixL
                      control = {
                        "Gain 1" = 0.5
                        "Gain 2" = 0.5
                        "Gain 3" = 0.03
                        "Gain 4" = 0.03
                      }
                    }
                    {
                      type = builtin
                      label = mixer
                      name = mixR
                      control = {
                        "Gain 1" = 0.5
                        "Gain 2" = 0.5
                        "Gain 3" = 0.03
                        "Gain 4" = 0.03
                      }
                    }
                    {
                        type = builtin
                        label = copy
                        name = enhcopyL
                    }
                    {
                        type = builtin
                        label = copy
                        name = enhcopyR
                    }
                    {
                      type = builtin
                      label = mixer
                      name = enhAdd
                    }
                    {
                      type = builtin
                      label = mixer
                      name = enhSub
                    }
                    {
                        type = builtin
                        label = copy
                        name = enhcopyA
                    }
                    {
                        type = builtin
                        label = copy
                        name = enhcopyS
                    }
                    {
                        type = builtin
                        label = invert
                        name = enhinv1
                    }
                    {
                        type = builtin
                        label = invert
                        name = enhinv2
                    }
                    {
                      type = builtin
                      label = mixer
                      name = enhMixL
                      control = {
                        "Gain 1" = 0.5
                      }
                    }
                    {
                      type = builtin
                      label = mixer
                      name = enhMixR
                      control = {
                        "Gain 1" = 0.5
                      }
                    }
                ]
                links = [
                    { output = "convL:Out"  input="copyL:In" }
                    { output = "convR:Out"  input="copyR:In" }
                    { output = "copyL:Out"  input="spFL:In" }
                    { output = "copyR:Out"  input="spFR:In" }
                    { output = "copyL:Out"  input="spRL:In" }
                    { output = "copyR:Out"  input="spRR:In" }
                    { output = "spFL:Out L"  input="mixL:In 1" }
                    { output = "spFL:Out R"  input="mixR:In 1" }
                    { output = "spFR:Out L"  input="mixL:In 2" }
                    { output = "spFR:Out R"  input="mixR:In 2" }
                    { output = "spRL:Out L"  input="mixL:In 3" }
                    { output = "spRL:Out R"  input="mixR:In 3" }
                    { output = "spRR:Out L"  input="mixL:In 4" }
                    { output = "spRR:Out R"  input="mixR:In 4" }
                    { output = "mixL:Out"  input="enhcopyL:In" }
                    { output = "mixR:Out"  input="enhcopyR:In" }
                    { output = "enhcopyL:Out"  input="enhAdd:In 1" }
                    { output = "enhcopyR:Out"  input="enhAdd:In 2" }
                    { output = "enhcopyL:Out"  input="enhSub:In 1" }
                    { output = "enhcopyR:Out"  input="enhinv1:In" }
                    { output = "enhinv1:Out"  input="enhSub:In 2" }
                    { output = "enhAdd:Out"  input="enhcopyA:In" }
                    { output = "enhSub:Out"  input="enhcopyS:In" }
                    { output = "enhcopyA:Out"  input="enhMixL:In 1" }
                    { output = "enhcopyS:Out"  input="enhMixL:In 2" }
                    { output = "enhcopyA:Out"  input="enhMixR:In 1" }
                    { output = "enhcopyS:Out"  input="enhinv2:In" }
                    { output = "enhinv2:Out"  input="enhMixR:In 2" }

                ]
                inputs = [ "copyL:In" "copyR:In" ]
                outputs = [ "enhMixL:Out" "enhMixR:Out" ]
            }
            capture.props = {
                node.name   = "effect_input.stereoenhancer"
                media.class = Audio/Sink
                audio.channels = 2
                audio.position=[FL FR]
            }
            playback.props = {
                node.name   = "effect_output.stereoenhancer"
                node.passive = true
                audio.channels = 2
                audio.position=[FL FR]
            }
        }
    }
]
★★★★

Проверено: hobbit ()
Последнее исправление: hobbit (всего исправлений: 3)

Хм. А сделать так чтобы эффект включался толко при подключении наушников всё ещё нельзя, да?

И я не понимаю всё же зачем нужен этот эффект для динамиков.

R_He_Po6oT ★★★★★
()
Ответ на: комментарий от James_Holden

Нет, я про автоматическое пайпваревское поведение, встроенными средствами. Я всё никак не соберусь написать автору пайпвари, поговорить за жысь.

R_He_Po6oT ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.