В первой части мы создали конфиг-файл, который добавляет 15-полосный эквалайзер в граф PipeWire. Теперь разберем вопрос, а как же изменять его параметры на лету, без перезапуска PipeWire. Вопрос очень животрепещущий, редакция завалена письмами с ним. ))
В этой части мы разберем задачу на «низком» уровне CLI-утилит. На этой базе несложно сделать любой GUI-фронтенд, который позволит управлять параметрами уже из GUI.
Сначала получим текущие настройки эквалайзера из PipeWire. Эти настройки хранятся в ноде Equalizer Sink, надо просто знать как их извлечь оттуда. Для этого есть два способа и две CLI утилиты – pw-cli
и pw-dump
. Начнем с первого варианта, пригодного для ручной работы (но он неудобен для скрипта).
Сначала надо получить номер ноды Equalizer Sink. Для этого выполним команду pw-cli ls Node
. Получаем список всех нод, и в нем видим ноду с именем node.name = "effect_input.eq6"
, это как раз то имя, которое мы задали в конфиг-файле.
id 39, type PipeWire:Interface:Node/3
object.serial = "39"
factory.id = "19"
client.id = "38"
node.description = "Equalizer Sink"
node.name = "effect_input.eq6"
media.class = "Audio/Sink"
Следовательно, наш номер 39. запомним его. Теперь получим список параметров filter-chain, с их значениями, командой pw-cli enum-params 39 Props
, где 39 – номер ноды, найденный ранее. В выводе этой команды, посмотрев чуть дальше начала, видим такое:
String "eq_preamp:Freq"
Float 0,000000
String "eq_preamp:Q"
Float 1,000000
String "eq_preamp:Gain"
Float 5,000000
String "eq_preamp:b0"
Float 1,778279
String "eq_preamp:b1"
Float 0,000000
...
И так далее. Это и есть параметры, заданные нами в конфиг-файле, с их текущими значениями. Тут есть еще параметры вида a0, b0 которые мы не задавали, это особенность эффекта bq_peak, он их сам добавляет, нам это не сильно интересно. Нам нужны параметры, заканчивающиеся на «:Gain».
Мы получили то что хотели, но в такой форме оно не удобно для человека и еще менее удобно для обработки в скрипте. Поэтому есть второй способ — выполним команду pw-dump
. Эта команда выводит все параметры всех объектов pipewire в JSON формате. В этой огромной простыне, видим вот что:
{
"params": [
"eq_preamp:Freq",
0.000000,
"eq_preamp:Q",
1.000000,
"eq_preamp:Gain",
5.000000,
"eq_preamp:b0",
1.778279,
"eq_preamp:b1",
0.000000,
"eq_preamp:b2",
0.000000,
"eq_preamp:a0",
1.000000,
"eq_preamp:a1",
0.000000,
"eq_preamp:a2",
и так далее, все это внутри ноды 39 c node.name=«effect_input.eq6», то есть все то же самое, только в JSON формате, который уже удобно парсить в скрипте либо в приложении. Как извлечь оттуда нужную информацию в python скрипте, показано в конце статьи.
Теперь разберем главный вопрос – как эти параметры изменять. Это тоже позволяет делать утилита pw-cli
, команда имеет следующую форму:
pw-cli s 39 Props '{params = [ "eq_band_6:Gain" 5.0 ]}'
s – значит set, нода 39, массив Props, имя параметра eq_band_6:Gain
, установить значение 5.0. Таким простым образом можно изменить любой параметр, если мы знаем его имя и номер ноды!
Теперь, реализуем простейший Python скрипт, который будет работать как CLI утилита управления эквалайзером. Пользоваться им так:
eqcli.py get
– получить текущие значения параметров, выводит вот что (номер полосы эквалайзера, ее частота, ее значение):
0. Freq: 0.0 Gain: 5.0
1. Freq: 25.0 Gain: -10.0
2. Freq: 40.0 Gain: -10.0
3. Freq: 63.0 Gain: -9.0
4. Freq: 100.0 Gain: 3.0
5. Freq: 160.0 Gain: 3.0
6. Freq: 250.0 Gain: 3.0
7. Freq: 400.0 Gain: -5.0
8. Freq: 630.0 Gain: -5.0
9. Freq: 1000.0 Gain: -3.0
10. Freq: 1600.0 Gain: -3.0
11. Freq: 2500.0 Gain: -3.0
12. Freq: 4000.0 Gain: -5.0
13. Freq: 6300.0 Gain: 10.0
14. Freq: 10000.0 Gain: 0.0
15. Freq: 16000.0 Gain: 0.0
eqcli.py set 6 -3.5
– установить полосу номер 6 на значение -3.5 децибел. Эта команда тоже выведет значения параметров, как и команда get
, но уже измененных.
Вот листинг скрипта, надо сохранить в файл eqcli.py
#!/usr/bin/python
import sys
import subprocess
import json
def parse_pw_data():
result = subprocess.run(["pw-dump"], capture_output=True)
parsed_data = json.loads(result.stdout.decode())
eq_item_id = 0
eq_item = 0
for item in parsed_data:
try:
if item['info']['props']['node.name'] == 'effect_input.eq6':
eq_item_id = int(item['id'])
eq_item = item
except:
pass
return eq_item_id, eq_item
def get_func(eq_item):
eq_params = eq_item['info']['params']['Props'][1]['params']
i = 0
while i < len(eq_params):
print(str(int(i / 18)) + ". Freq: " + str(eq_params[i + 1]) + " Gain: " + str(eq_params[i + 5]))
i = i + 18
def set_func(eq_item_id, eq_item):
try:
band_num = int(sys.argv[2])
band_gain = float(sys.argv[3])
set_command_line = ""
if band_num == 0:
set_command_line = "{params = [ \"eq_preamp:Gain\" " + str(band_gain) + " ]}"
else:
set_command_line = "{params = [ \"eq_band_" + str(band_num) + ":Gain\" " + str(band_gain) + " ]}"
result = subprocess.run(["pw-cli", "s", str(eq_item_id), "Props", set_command_line], capture_output=True)
print(result.stdout.decode())
eq_item_id, eq_item = parse_pw_data()
get_func(eq_item)
except:
print("Wrong parameters")
eq_item_id, eq_item = parse_pw_data()
if eq_item_id == 0:
print("No equalizer found, exiting...")
exit()
try:
if (sys.argv[1] == 'get'):
get_func(eq_item)
elif (sys.argv[1] == 'set'):
set_func(eq_item_id, eq_item)
else:
print("Unknown command")
except:
print("No command")
Как это работает? Сначала скрипт получает JSON-описание всех объектов командой pw-dump
. Находит нужную ноду. Извлекает из нее значения параметров и печатает их, если дана команда get
. Формирует и вызывает pw-cli s ...
если дана команда set
.
Я думаю, очевидно, что этот пример не сложно расширить до GUI-варианта на PyQt, к примеру, и получить графическую «морду» для настройки нашего эквалайзера. К этому мы вернемся позднее.