Введение в Pipewire

Pipewire – это мультимедийный Сервер и фреймворк, доступный в качестве звукового сервера по умолчанию в последних версиях Fedora. По своей сути он предназначен для маршрутизации и обработки аудио- и видеопотоков с низкой задержкой. Он способен мультиплексировать мультимедийные потоки для нескольких клиентов – важная особенность современных операционных систем.

Общие соображения

Эволюция звуковой подсистемы Linux происходила послойно. Самый нижний уровень – это аппаратный уровень с различными аудиоустройствами. Для взаимодействия с драйверами оборудования в Linux существует стандартизированный API под названием Advanced Linux Sound Architecture(ALSA). Другой слой над ALSA, звуковой сервер, должен обрабатывать взаимодействие с приложениями пользовательского пространства. Изначально этим уровнем были PulseAudio и Jack, но недавно он был заменен на PipeWire. Это первые шаги стороннего наблюдателя в работе с Pipewire. Текущая версия – 1.2.7 на Fedora 41 Workstation.

Pipewire – это активируемая сокетами пользовательская служба systemd. Обратите внимание, что менеджер сессий является псевдонимом для wireplumber.service.

rg@f41:~$ systemctl --user list-unit-files "pipewire*"
UNIT FILE                        STATE    PRESET      
pipewire.service                 disabled disabled
pipewire.socket                  enabled  enabled 
pipewire-session-manager.service alias    -

Разрешения и владение

Текущий пользователь является владельцем процесса. Однако каждый вошедший в систему пользователь имеет свой экземпляр pipewire.

rg@f41:~$ ps -eo pid,uid,gid,user,comm,label | grep pipewire
2216  1000  1000 rg pipewire unconfined_t

Большинство файлов мультимедийных устройств, к которым обращается этот процесс, расположены на /dev/snd и /dev/video .

rg@f41:~$ ls -laZ /dev/video* /dev/media* /dev/snd/*
crw-rw----+ root video system_u:object_r:v4l_device_t:s0   /dev/video0
crw-rw----+ root video system_u:object_r:v4l_device_t:s0   /dev/media0
crw-rw----+ root audio system_u:object_r:sound_device_t:s0 hwC0D0
crw-rw----+ root audio system_u:object_r:sound_device_t:s0 pcmC0D0c
...

Разрешения равны 660 и ограничены владельцами root:video и root:audio. Обратите внимание на дополнительный знак, указывающий на ACL-разрешения. Systemd-logind – это менеджер входа в систему Fedora. Эта служба, как часть процесса входа в систему, выполнит двоичный файл по адресу /usr/lib/systemd/systemd-logind. Он создаст сессию и назначит место. При входе в систему он изменит список контроля доступа к файлам мультимедийных устройств для текущего пользователя. Так Pipewire получает доступ к файлам устройств, которые в противном случае принадлежат root:audio или root:video. Чтобы проверить текущий ACL файлов:

rg@f41:~$ getfacl /dev/video0
file: dev/video0
owner: root
group: video
user::rw-
user:rg:rw-  # current user
group::rw-
mask::rw-
other::---

Команда lsof может показать все процессы, обращающиеся к нашим звуковым и видеоустройствам:

rg@f41:~$ lsof | grep -E '/dev/snd|/dev/video*|/dev/media*'
COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
pipewire  53799 rg     44u   CHR  116,9      0t0  913 /dev/snd/controlC0
pipewire  53799 rg     83u   CHR   81,0      0t0  884 /dev/video0
wireplumb 53801 rg     24u   CHR  116,9      0t0  913 /dev/snd/controlC0
...

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

Настройка

Конфигурационный файл /usr/share/pipewire/pipewire.conf принадлежит root:root и содержит системные настройки по умолчанию. Обязательно проверьте комментарии; среди прочего вы найдете список загруженных модулей. Сервис настраивается с помощью конфигурационных файлов с выпадающим адресом homedir. Процесс является легковесным и состоит всего из 3 потоков. Ниже в качестве небольшого теста демонстрируется увеличение количества циклов данных.

rg@f41:~$ cat <<EOF > ~/.config/pipewire/pipewire.conf.d/loops.conf
context.properties = {
	context.num-data-loops = -1
}
EOF
rg@f41:~$ systemctl --user daemon-reload
rg@f41:~$ systemctl --user restart pipewire.service 
rg@f41:~$ ps -eLo pid,tid,command,comm,pmem,pcpu | grep pipewire
PID     TID   COMMAND                   COMMAND        PMEM PCPU
40187   40187 /usr/bin/pipewire         pipewire       0.3  0.0
40187   40193 /usr/bin/pipewire         module-rt      0.3  0.0
40187   40194 /usr/bin/pipewire         data-loop.0    0.3  0.0
40187   40195 /usr/bin/pipewire         data-loop.1    0.3  0.0
40187   40196 /usr/bin/pipewire         data-loop.2    0.3  0.0
...

С помощью pw-config вы можете проверить состояние нашей обновленной конфигурации:

rg@f41:~$ pw-config
{
  "config.path": "/usr/share/pipewire/pipewire.conf",
  "override.config.path": "~/.config/pipewire/pipewire.conf.d/loops.conf"
}

Некоторые архитектурные соображения

Pipewire состоит примерно из 30 тысяч строк кода на языке программирования C. Архитектура напоминает событийно-управляемый шаблон производитель-потребитель. Управляющие структуры представляют собой графоподобные структуры с узлами-производителями и узлами-потребителями. Одной из причин достижения низкой задержки и низкого уровня загрузки процессора является использование концепции, называемой нулевым копированием через memfd_create. Сигнализация между слабосвязанными компонентами осуществляется с помощью eventfd. Код является расширяемым и модульным.

Все состояние Pipewire представляет собой граф со следующими основными элементами:

  • Узлы (с портами ввода/вывода)
  • Связи (ребра)
  • Клиенты (пользовательские процессы)
  • Модули (разделяемые объекты)
  • Фабрики (библиотеки модулей)
  • Метаданные (настройки)

Граф состоит из узлов и ребер/связей. Узлы, имеющие только входные порты, называются стоками, а узлы, имеющие только выходные порты, называются источниками. Узлы с обоими портами обычно называются фильтрами.

Модули

Каждый модуль обеспечивает определенную функциональность сервера. Потратьте немного времени на просмотр названий модулей. Есть модуль, управляющий доступом между компонентами графа, модуль, управляющий приоритетами потоков, модуль, эмулирующий бывшую Pulseaudio, модуль для XDG Portal, модуль для профилирования и даже модуль, реализующий потоковую передачу по сети через RTP.

rg@f41:~$ ls -la /usr/lib64/pipewire-0.3/
-rwxr-xr-x. 1 root root  28296 Nov 26 01:00 libpipewire-module-access.so
-rwxr-xr-x. 1 root root  98808 Nov 26 01:00 libpipewire-module-avb.so
...

Плагины

Плагины управляют взаимодействием с устройствами, включая создание кольцевых буферов, которые активно используются в мультимедийных процессах. Есть плагин, который управляет взаимодействием с ALSA, плагин, который управляет взаимодействием с Bluetooth, libcamera и т.д.

rg@f41:~$ ls -la /usr/lib64/spa-0.2/
drwxr-xr-x. 1 root root     28 Dec 11 12:13 alsa
drwxr-xr-x. 1 root root    420 Dec 11 12:13 bluez5
...

Плагины расширяют общий API, называемый SPA, или Simple Plugin API. Эти самодостаточные разделяемые библиотеки предоставляют фабрики, содержащие интерфейсы. Поскольку они являются автономными, некоторые из этих плагинов используются менеджером сессий. Утилита _spa-inspect _ позволяет запрашивать общие объекты на предмет фабрик и интерфейсов.

Полезные команды

Мы можем увидеть ®unning узлы с помощью pw-top. В следующем примере в настоящее время работают 3 узла. Любая потоковая передача, осуществляемая через Pipewire, будет отображаться в этой таблице вывода.

rg@f41:~$ pw-top
S   ID  QUANT   RATE    WAIT    BUSY  FORMAT         NAME                                                                                                                                
R   90    512  48000 139.8us  23.0us  S16LE 2 48000  bluez_output..
R   99    900  48000  66.7us  37.7us  F32LE 2 48000  + Firefox
R  112   4320  48000 105.5us  11.4us  S16LE 2 48000  + Videos

Квантование (QUANT) можно рассматривать как количество аудиосэмплов (размер буфера), которые будут обрабатываться в каждом цикле графа. Это зависит от типа устройства и может быть настроено или согласовано с помощью конфигурационных файлов. Например, в приложении Videos размер буфера составляет 4320 сэмплов, а в узле вывода Bluetooth – только 512.

Rate (RATE) – это частота обработки графика. В приведенном выше выводе график работает на частоте 48 кГц, то есть каждую секунду график может обрабатывать 48 000 образцов.

Соотношение между Quantum и Rate – это задержка в секундах. В случае Bluetooth-гарнитур задержка составляет 11 мс, а для приложения «Видео» – 90 мс. На man-странице pw-top есть ссылки на более подробные объяснения. Понимание этих показателей может помочь повысить производительность.

Еще одна мощная утилита – pw-cli. Она открывает оболочку и позволяет интерактивно работать с графом Pipewire во время выполнения программы. Для постоянных изменений необходимо внести поправки в конфигурационные файлы.

rg@f41:~$ pw-cli h
Available commands:
	help | h            	Show this help
	load-module | lm    	Load a module. 
	unload-module | um  	Unload a module. 
	connect | con       	Connect to a remote. 
	disconnect | dis    	Disconnect from a remote. 
	list-remotes | lr   	List connected remotes.
	switch-remote | sr  	Switch between current remotes.
	list-objects | ls   	List objects or current remote. 
	info | i            	Get info about an object. 
	create-device | cd  	Create a device from a factory.
	create-node | cn    	Create a node from a factory. 
	destroy | d         	Destroy a global object. 
	create-link | cl    	Create a link between nodes. 
	export-node | en    	Export a local node
	enum-params | e     	Enumerate params of an object 
	set-param | s       	Set param of an object 
	permissions | sp    	Set permissions for a client 
	get-permissions | gp	Get permissions of a client 
	send-command | c    	Send a command <object-id>
	quit | q            	Quit

Например, мы можем распечатать приведенные выше потоковые узлы с помощью pw-cli info и просмотреть все их свойства.

Визуализация графиков

Все производные Утилиты обрабатывают один и тот же большой JSON-граф, который представляет собой состояние Pipewire. С помощью pw-dump мы получим это состояние. Но вывод трудно читать. Другая утилита pw-dot даст нам удобный для восприятия граф. Запомните следующие 3 шага, так как вам придется повторять их в течение этого урока. Утилита dot, используемая в следующем примере, доступна при установке Graphviz.

rg@f41:~$ pw-dot --detail --all
rg@f41:~$ dot -Tpng pw.dot -o pw.png
rg@f41:~$ loupe pw.png

Это большой график. Потратьте минуту, чтобы рассмотреть различные узлы, типы узлов, выделенные разными цветами, и некоторые значимые свойства, такие как порты, класс носителя и ссылки. Описание этих свойств можно найти в man’е. Обратите внимание на свойство media.name, которое указывает на текущую песню, проигрываемую в Firefox.

Большинство потоков, которые вы увидите здесь, будут потоками Source/Audio.

Новое приложение камеры по умолчанию в Fedora 41 Workstation – Snapshot. Запустите это приложение, и вы заметите, что оно видно в Pipewire. Давайте оставим Snapshot запущенным и откроем другое приложение камеры. Не забудьте установить ffmpeg-free для следующих шагов.

rg@f41:~$ ffplay -f v4l2 -i /dev/video0
/dev/video0: Device or resource busy

ffplay пытается получить доступ к /dev/video0 напрямую. Согласно документации ядра, общие потоки данных должны быть реализованы прокси в пользовательском пространстве, а не драйверами v4l2. Pipewire уже использует libcamera api, как вы видели в списке плагинов, и предлагает pw-v4l2 в качестве совместимой обертки. Давайте снова воспользуемся той же командой.

rg@f41:~$ pw-v4l2 ffplay -f v4l2 -i /dev/video0

На этот раз все получилось, и у нас есть два приложения, которые используют один и тот же поток камеры. Это и есть дополнительная польза от высокопроизводительного мультимедийного сервера. На графике мы также видим оба потока Source/Video. То же самое можно сказать и о браузере. В настройках браузера вы должны иметь возможность выбрать, какие устройства использовать – Pipewire или v4l2.

Вы также можете использовать Helvum или Qpwgraph. Протестируйте любое из этих приложений. Вы обнаружите упрощенный граф с определенными типами узлов и функциями перетаскивания. Вы оцените простоту использования, но иногда вам понадобится полный граф и полный набор свойств узлов. В таких случаях вы вернетесь к pw-dump, pw-cli и pw-dot.

Управление узлами

Одним из способов управления временными узлами графа является использование pw-cli. Например, здесь мы создаем временный тестовый узел, действующий до тех пор, пока открыта сессия. Имена фабрик можно посмотреть в исходном коде, а некоторые полезные примеры – в комментариях к странице conf.

rg@f41:~$ PIPEWIRE_DEBUG=2 pw-cli 
pipewire-0>> create-node spa-node-factory factory.name=support.node.driver node.name=mynewnode

Другой способ создания узлов, но на этот раз в нашей постоянной конфигурации, описан в комментариях к конфигурации:

rg@f41:~$ cat <<EOF > ~/.config/pipewire/pipewire.conf.d/mynewnode.conf
context.objects = [{ 
factory = adapter
args = {
    factory.name           = api.alsa.pcm.source
    node.name              = "alsa-testnode"
    node.description       = "PCM TEST"
    media.class            = "Audio/Source"
    api.alsa.path          = "hw:0"
    api.alsa.period-size   = 1024
    audio.format           = "S16LE"
    audio.channels         = 2
    audio.position         = "FL,FR" }
}]
EOF
rg@f41:~$ systemctl --user daemon-reload
rg@f41:~$ systemctl --user restart pipewire.service

Более быстрый и простой способ создания узлов – использовать Вспомогательные утилиты pw-record и pw-play для создания стоков и источников. В последнем параграфе мы приведем небольшой пример.

Соображения безопасности

Двоичный файл принадлежит root и может быть выполнен другими пользователями.

rg@f41:~$ ls -la /usr/bin/pipewire
-rwxr-xr-x. 1 root root 20104 Nov 26 01:00 /usr/bin/pipewire

Протокол, используемый для взаимодействия с сокетами, называется native protocol. Сокеты имеют большие права доступа, как и другие временные файлы systemd. Полезно помнить, что /run – это файловая система tmpfs в памяти.

rg@f41:~$ ls -laZ /run/user/1000/pipewire*
srw-rw-rw-. rg rg object_r:user_tmp_t:s0 pipewire-0
-rw-r-----. rg rg object_r:user_tmp_t:s0 pipewire-0.lock
srw-rw-rw-. rg rg object_r:user_tmp_t:s0 pipewire-0-manager
-rw-r-----. rg rg object_r:user_tmp_t:s0 pipewire-0-manager.lock

Сокеты systemd создают структуры потоков; после этого аудиоузлы будут взаимодействовать напрямую через memfd. Согласно man-странице, memfd_create создает анонимный файл in-memory, что очень важно для производительности. Исходный код Pipewire использует эти вызовы с определенными флагами, такими как MFD_HUGETLB, а также с флагами MFD_ALLOW_SEALING, MFD_CLOEXEC и т. д., чтобы запечатать и предотвратить утечку памяти в другие процессы.

Pipewire поставляется с модулем под названием protocol-pulse. Модуль имеет отдельную пользовательскую службу systemd, активируемую через сокет. Он эмулирует сервер PulseAudio для совместимости с большим количеством клиентов, все еще использующих библиотеки PulseAudio.

rg@f41:~$ ls -laZ /run/user/1000/pulse/native
srw-rw-rw-. rg rg user_tmp_t:s0 /run/user/1000/pulse/native

Systemd предлагает способы анализа и «песочницы» сервисов. Некоторые рекомендации приведены ниже, но они не гарантируют защиту на уровне системы.

rg@f41:~$ systemd-analyze --user --no-pager security pipewire.service

Любое приложение пользовательского пространства может просматривать граф, обращаться к сокету и создавать узлы. Свойства узлов в некоторых случаях содержат информацию, которая может быть приватной. Примером может служить свойство media.name. Этого не происходит для приватных вкладок Firefox. Это свойство может быть полезно для некоторых функций графического интерфейса, например для уведомлений в трее Gnome.

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

Пример

В качестве небольшого примера попробуем создать аудиокнигу – поток распознавания голоса с узлами-источниками и узлами-стоками Pipewire. Создадим узел-источник с помощью pw-play. В других сценариях _pw-play _может читать из stdin (-).

rg@f41:~$ curl https://ia600707.us.archive.org/8/items/alice_in_wonderland_librivox/wonderland_ch_10_64kb.mp3 --output test.mp3
rg@f41:~$ ffmpeg -i test.mp3 -ar 48000 -ac 1 -sample_fmt s16 test.wav
rg@f41:~$ pw-play --target=0 --format=s16 \
--quality=14 --media-type=Audio --channels=1 \
--properties='{node.name=mytestsource}' test.wav

Автоподключение процесса к колонкам не произошло из-за аргумента target. Давайте создадим узел поглотителя. Для простоты мы будем использовать пакет pocketsphinx для тестирования распознавания голоса.

rg@f41:~$ dnf install pocketsphinx
rg@f41:~$ pw-record --media-type=Audio --target=0 --rate=16000 \
 --channels=1 --quality=14 --properties='{node.name=mytestsink}' - \
 | pocketsphinx live -

Как вы можете видеть, мы создали узел поглотителя. Автоподключение не произошло ни к одному узлу-источнику, о чем свидетельствует аргумент target. Остальные аргументы, например количество каналов, являются рекомендациями с man-страниц Pocketsphinx. Мы будем создавать связи вручную. Используйте pw-dot, чтобы найти идентификаторы узлов и портов источника и поглотителя. Также вы можете использовать любое из двух приложений Flatpak для создания связей путем перетаскивания.

rg@f41:~$ PIPEWIRE_DEBUG=2 pw-cli 
pipewire-0>> create-link <nodeid> <portid> <nodeid> <portid>

Мы создали поток между источником и поглотителем и уже можем видеть некоторые выходные данные в терминале Pocketsphinx. Задействованные процессы видны Pipewire и могут использовать все его возможности. Аналогичным образом вы можете взаимодействовать с вашим любимым Flatpak. Многие из этих приложений существуют в «песочнице» и взаимодействуют с базовой системой с помощью API XDG Portal.

Еще один пример

Давайте попробуем привести еще один пример, связанный с фильтрами. Ранее мы уже видели, что в Pipewire есть модуль под названием filter-chain. Прежде всего, нам нужно будет установить нужные плагины фильтров. В репозиториях DNF есть большое количество плагинов фильтров LADSPA и LV2. В Pipewire также есть несколько встроенных фильтров, доступных благодаря плагину audiomixer.

rg@f41:~$ dnf install ladspa ladspa-rev-plugins
rg@f41:~$ rpm -ql ladspa-rev-plugins
/usr/lib64/ladspa/g2reverb.so

Как мы видим, пакет устанавливает общий объект. Для плагинов LADSPA мы можем извлечь метаданные с помощью утилиты analyseplugin. Важную информацию можно увидеть в следующем выводе:

rg@f41:~$ analyseplugin /usr/lib64/ladspa/g2reverb.so
Plugin Name: "Stereo reverb"  
Plugin Label: "G2reverb"       
Ports:	...
	"Room size" input, control, 10 to 150
	"Reverb time" input, control, 1 to 20
	"Input BW" input, control, 0 to 1
	"Damping" input, control, 0 to 1
	"Dry sound" input, control, -80 to 0
	"Reflections" input, control, -80 to 0
	"Reverb tail" input, control, -80 to 0

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

rg@f41:~$ cat <<EOF > ~/.config/pipewire/pipewire.conf.d/myfilter.conf
context.modules = [
{   
     name = libpipewire-module-filter-chain
     args = {
         node.description =  "My filter"
         media.name =  "My filter"
         filter.graph = {
             nodes = [
                 {
                     type = ladspa
                     name = "Stereo reverb"
                     plugin = "/usr/lib64/ladspa/g2reverb.so"
                     label = "G2reverb"
                     control = {
                        "Reverb time" = 2      
                     }
                 }
                ]
          }
          audio.channels = 2
          audio.position = [ FL FR ]
          capture.props = {
              node.name =  "myfilter"
              media.class = Audio/Sink
              node.target = 0
          }
          playback.props = {
              node.name =  "myfilter"
              media.class = Audio/Source
              node.target = 0
          }
     }
}
]
EOF
rg@f41:~$ systemctl --user restart pipewire.service

Проверьте граф, например с помощью Qpwgraph, чтобы показать пару поглотитель/источник фильтра. Обратите внимание, что узел поглотителя также имеет порты монитора. Эти порты позволяют просматривать поток перед обработкой. Теперь создайте узел источника, как и раньше:

rg@f41:~$ pw-play --target=0 --format=s16 --media-type=Audio \
--properties='{node.name=mytestsource}' test.mp3

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

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

Заключение

Pipewire – это интересная часть низкоуровневого высокопроизводительного программного обеспечения. Мы обсудили основные команды, параметры конфигурации и взаимодействие с файловой системой. Настоятельно рекомендуем прочитать документацию по Pipewire. Книга «Звуковое Программирование в Linux» Яна Ньюмарча также даст представление об исторических аспектах. Надеюсь, у вас будет достаточно контекста, чтобы лучше понимать встречающиеся термины. Спасибо людям, поддерживающим упомянутые пакеты.

Зарубин Иван Эксперт по Linux и Windows

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

Похожие статьи

Комментарии (0)