From f485c595d63467f0aa14e4b9612b95db8ec019aa Mon Sep 17 00:00:00 2001 From: Apertis CI <devel@lists.apertis.org> Date: Thu, 7 Dec 2023 20:36:18 +0000 Subject: [PATCH] Import Upstream version 1.0.0 --- .gitlab-ci.yml | 5 +- NEWS | 139 +++++- doc/Doxyfile.in | 8 +- doc/DoxygenLayout.xml | 238 +++++++++ doc/custom.css | 21 + doc/{api.dox => dox/api/index.dox} | 1 + doc/{ => dox/api}/spa-buffer.dox | 0 doc/{ => dox/api}/spa-design.dox | 0 doc/{ => dox/api}/spa-index.dox | 0 doc/{ => dox/api}/spa-plugins.dox | 0 doc/{ => dox/api}/spa-pod.dox | 0 doc/{ => dox}/index.dox | 2 +- .../internals/access.dox} | 0 .../internals/audio.dox} | 0 .../internals/daemon.dox} | 0 .../internals/design.dox} | 0 doc/{ => dox/internals}/dma-buf.dox | 0 doc/{pipewire.dox => dox/internals/index.dox} | 4 +- .../internals/library.dox} | 0 .../internals/midi.dox} | 0 .../internals/objects.dox} | 0 .../internals/portal.dox} | 4 +- .../internals/protocol.dox} | 146 +++++- doc/{ => dox/internals}/pulseaudio.dox | 0 .../internals/scheduling.dox} | 16 + .../internals/session-manager.dox} | 0 doc/{pipewire-modules.dox => dox/modules.dox} | 2 +- doc/{ => dox}/overview.dox | 0 doc/dox/programs/index.md | 23 + doc/dox/programs/libpipewire-modules.7.md | 44 ++ doc/dox/programs/pipewire-pulse-modules.7.md | 32 ++ doc/dox/programs/pipewire-pulse.1.md | 40 ++ doc/dox/programs/pipewire-pulse.conf.5.md | 56 +++ doc/dox/programs/pipewire.1.md | 44 ++ doc/dox/programs/pipewire.conf.5.md | 102 ++++ doc/dox/programs/pw-cat.1.md | 163 ++++++ doc/dox/programs/pw-cli.1.md | 189 +++++++ doc/dox/programs/pw-config.1.md | 97 ++++ doc/dox/programs/pw-dot.1.md | 56 +++ doc/dox/programs/pw-dump.1.md | 43 ++ doc/dox/programs/pw-jack.1.md | 45 ++ doc/dox/programs/pw-link.1.md | 135 +++++ doc/dox/programs/pw-loopback.1.md | 67 +++ doc/dox/programs/pw-metadata.1.md | 73 +++ doc/dox/programs/pw-mididump.1.md | 38 ++ doc/dox/programs/pw-mon.1.md | 36 ++ doc/dox/programs/pw-profiler.1.md | 46 ++ doc/dox/programs/pw-top.1.md | 207 ++++++++ doc/dox/pulse-modules.dox | 149 ++++++ doc/{tutorial.dox => dox/tutorial/index.dox} | 2 +- doc/{ => dox/tutorial}/tutorial1.dox | 0 doc/{ => dox/tutorial}/tutorial2.dox | 0 doc/{ => dox/tutorial}/tutorial3.dox | 0 doc/{ => dox/tutorial}/tutorial4.dox | 0 doc/{ => dox/tutorial}/tutorial5.dox | 0 doc/{ => dox/tutorial}/tutorial6.dox | 0 doc/{ => examples}/tutorial1.c | 0 doc/{ => examples}/tutorial2.c | 0 doc/{ => examples}/tutorial3.c | 0 doc/{ => examples}/tutorial4.c | 2 + doc/{ => examples}/tutorial5.c | 0 doc/{ => examples}/tutorial6.c | 0 doc/input-filter.py | 53 ++ doc/input-filter.sh | 10 - doc/man-fixup.py | 97 ++++ doc/manpage.dox.in | 5 - doc/meson.build | 189 ++++--- doc/pipewire-architecture.dox | 0 doc/pipewire-tools.dox.in | 7 - doc/{api-tree.dox => tree.dox} | 13 +- man/meson.build | 45 -- man/pipewire-pulse.1.rst.in | 48 -- man/pipewire.1.rst.in | 54 -- man/pipewire.conf.5.rst.in | 112 ----- man/pw-cat.1.rst.in | 176 ------- man/pw-cli.1.rst.in | 195 -------- man/pw-config.1.rst.in | 110 ----- man/pw-dot.1.rst.in | 65 --- man/pw-jack.1.rst.in | 65 --- man/pw-link.1.rst.in | 139 ------ man/pw-metadata.1.rst.in | 82 --- man/pw-mididump.1.rst.in | 49 -- man/pw-mon.1.rst.in | 46 -- man/pw-profiler.1.rst.in | 57 --- man/pw-top.1.rst.in | 184 ------- meson.build | 30 +- meson_options.txt | 10 +- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 49 +- pipewire-jack/src/pipewire-jack.c | 449 ++++++++++------- spa/include/spa/buffer/alloc.h | 3 +- spa/include/spa/debug/log.h | 2 +- spa/include/spa/graph/graph.h | 4 +- spa/include/spa/monitor/utils.h | 8 +- spa/include/spa/node/utils.h | 10 +- spa/include/spa/param/props.h | 18 +- spa/include/spa/pod/filter.h | 4 +- spa/include/spa/support/log-impl.h | 4 +- spa/include/spa/utils/json.h | 6 +- spa/include/spa/utils/keys.h | 1 + spa/plugins/alsa/acp/alsa-mixer.c | 42 +- spa/plugins/alsa/acp/compat.c | 77 +++ spa/plugins/alsa/acp/compat.h | 4 + .../alsa/alsa-compress-offload-device.c | 2 +- spa/plugins/alsa/alsa-pcm-sink.c | 96 +--- spa/plugins/alsa/alsa-pcm-source.c | 94 +--- spa/plugins/alsa/alsa-pcm.c | 465 ++++++++++++++++-- spa/plugins/alsa/alsa-pcm.h | 20 +- spa/plugins/bluez5/bap-codec-lc3.c | 130 +++-- spa/plugins/bluez5/bluez5-dbus.c | 19 +- spa/plugins/bluez5/codec-loader.c | 2 +- spa/plugins/bluez5/iso-io.c | 22 +- spa/plugins/bluez5/media-sink.c | 16 + spa/plugins/libcamera/libcamera-device.cpp | 9 +- spa/plugins/libcamera/libcamera-source.cpp | 19 +- spa/plugins/support/evl-system.c | 88 +++- src/daemon/meson.build | 5 - src/daemon/pipewire-pulse.conf.in | 2 + src/daemon/pipewire.conf.in | 5 +- src/daemon/systemd/system/meson.build | 2 +- .../systemd/system/pipewire-manager.socket | 13 + src/daemon/systemd/system/pipewire.service.in | 2 +- src/daemon/systemd/system/pipewire.socket | 3 +- src/examples/audio-capture.c | 1 + src/gst/gstpipewiresink.c | 9 +- src/gst/gstpipewiresrc.c | 10 +- src/modules/meson.build | 6 +- src/modules/module-access.c | 6 +- src/modules/module-adapter.c | 6 +- src/modules/module-avb.c | 6 +- src/modules/module-client-device.c | 6 +- src/modules/module-client-node.c | 6 +- src/modules/module-client-node/client-node.c | 153 +++--- src/modules/module-combine-stream.c | 6 +- src/modules/module-echo-cancel.c | 6 +- src/modules/module-example-filter.c | 6 +- src/modules/module-example-sink.c | 6 +- src/modules/module-example-source.c | 6 +- src/modules/module-fallback-sink.c | 11 +- src/modules/module-ffado-driver.c | 6 +- src/modules/module-filter-chain.c | 19 +- src/modules/module-jack-tunnel.c | 6 +- src/modules/module-jackdbus-detect.c | 6 +- src/modules/module-link-factory.c | 6 +- src/modules/module-loopback.c | 6 +- src/modules/module-metadata.c | 6 +- src/modules/module-netjack2-driver.c | 8 +- src/modules/module-netjack2-manager.c | 6 +- src/modules/module-pipe-tunnel.c | 59 ++- src/modules/module-portal.c | 6 +- src/modules/module-profiler.c | 6 +- src/modules/module-protocol-native.c | 8 +- src/modules/module-protocol-pulse.c | 8 +- src/modules/module-protocol-pulse/collect.c | 20 +- src/modules/module-protocol-pulse/collect.h | 3 +- .../modules/module-alsa-sink.c | 63 ++- .../modules/module-alsa-source.c | 63 ++- .../modules/module-always-sink.c | 15 +- .../modules/module-combine-sink.c | 39 +- .../modules/module-echo-cancel.c | 63 ++- .../modules/module-gsettings.c | 11 + .../modules/module-jackdbus-detect.c | 43 +- .../modules/module-ladspa-sink.c | 47 +- .../modules/module-ladspa-source.c | 47 +- .../modules/module-loopback.c | 40 +- .../modules/module-native-protocol-tcp.c | 20 +- .../modules/module-null-sink.c | 26 +- .../modules/module-pipe-sink.c | 34 +- .../modules/module-pipe-source.c | 32 +- .../modules/module-raop-discover.c | 15 + .../modules/module-remap-sink.c | 38 +- .../modules/module-remap-source.c | 38 +- .../modules/module-roc-sink-input.c | 43 +- .../modules/module-roc-sink.c | 39 +- .../modules/module-roc-source.c | 42 +- .../modules/module-rtp-recv.c | 24 +- .../modules/module-rtp-send.c | 44 +- .../modules/module-simple-protocol-tcp.c | 38 +- .../modules/module-switch-on-connect.c | 20 +- .../modules/module-tunnel-sink.c | 39 +- .../modules/module-tunnel-source.c | 39 +- .../modules/module-virtual-sink.c | 32 +- .../modules/module-virtual-source.c | 34 +- .../modules/module-x11-bell.c | 26 +- .../modules/module-zeroconf-discover.c | 21 +- .../modules/module-zeroconf-publish.c | 11 + .../module-protocol-pulse/pulse-server.c | 24 +- src/modules/module-protocol-simple.c | 6 +- src/modules/module-pulse-tunnel.c | 6 +- src/modules/module-raop-discover.c | 6 +- src/modules/module-raop-sink.c | 9 +- src/modules/module-roc-sink.c | 83 ++-- src/modules/module-roc-source.c | 85 ++-- src/modules/module-roc/common.h | 23 +- src/modules/module-rt.c | 128 ++++- src/modules/module-rtp-sap.c | 6 +- src/modules/module-rtp-session.c | 6 +- src/modules/module-rtp-sink.c | 6 +- src/modules/module-rtp-source.c | 6 +- src/modules/module-rtp/stream.c | 2 +- src/modules/module-session-manager.c | 6 +- src/modules/module-vban-recv.c | 6 +- src/modules/module-vban-send.c | 6 +- src/modules/module-x11-bell.c | 6 +- src/modules/module-zeroconf-discover.c | 6 +- src/pipewire/context.c | 23 +- src/pipewire/filter.c | 2 +- src/pipewire/impl-client.c | 3 +- src/pipewire/impl-client.h | 4 +- src/pipewire/impl-link.c | 8 +- src/pipewire/impl-module.c | 2 +- src/pipewire/pipewire.c | 8 +- src/pipewire/pipewire.h | 2 - src/pipewire/private.h | 2 + src/pipewire/proxy.h | 4 +- src/pipewire/stream.c | 2 +- src/pipewire/stream.h | 4 +- src/pipewire/thread-loop.h | 4 +- src/pipewire/utils.c | 2 +- src/pipewire/utils.h | 2 +- src/tools/pw-top.c | 2 +- 220 files changed, 5179 insertions(+), 2647 deletions(-) create mode 100644 doc/DoxygenLayout.xml rename doc/{api.dox => dox/api/index.dox} (99%) rename doc/{ => dox/api}/spa-buffer.dox (100%) rename doc/{ => dox/api}/spa-design.dox (100%) rename doc/{ => dox/api}/spa-index.dox (100%) rename doc/{ => dox/api}/spa-plugins.dox (100%) rename doc/{ => dox/api}/spa-pod.dox (100%) rename doc/{ => dox}/index.dox (95%) rename doc/{pipewire-access.dox => dox/internals/access.dox} (100%) rename doc/{pipewire-audio.dox => dox/internals/audio.dox} (100%) rename doc/{pipewire-daemon.dox => dox/internals/daemon.dox} (100%) rename doc/{pipewire-design.dox => dox/internals/design.dox} (100%) rename doc/{ => dox/internals}/dma-buf.dox (100%) rename doc/{pipewire.dox => dox/internals/index.dox} (85%) rename doc/{pipewire-library.dox => dox/internals/library.dox} (100%) rename doc/{pipewire-midi.dox => dox/internals/midi.dox} (100%) rename doc/{pipewire-objects-design.dox => dox/internals/objects.dox} (100%) rename doc/{pipewire-portal.dox => dox/internals/portal.dox} (96%) rename doc/{pipewire-protocol.dox => dox/internals/protocol.dox} (88%) rename doc/{ => dox/internals}/pulseaudio.dox (100%) rename doc/{pipewire-scheduling.dox => dox/internals/scheduling.dox} (92%) rename doc/{pipewire-session-manager.dox => dox/internals/session-manager.dox} (100%) rename doc/{pipewire-modules.dox => dox/modules.dox} (98%) rename doc/{ => dox}/overview.dox (100%) create mode 100644 doc/dox/programs/index.md create mode 100644 doc/dox/programs/libpipewire-modules.7.md create mode 100644 doc/dox/programs/pipewire-pulse-modules.7.md create mode 100644 doc/dox/programs/pipewire-pulse.1.md create mode 100644 doc/dox/programs/pipewire-pulse.conf.5.md create mode 100644 doc/dox/programs/pipewire.1.md create mode 100644 doc/dox/programs/pipewire.conf.5.md create mode 100644 doc/dox/programs/pw-cat.1.md create mode 100644 doc/dox/programs/pw-cli.1.md create mode 100644 doc/dox/programs/pw-config.1.md create mode 100644 doc/dox/programs/pw-dot.1.md create mode 100644 doc/dox/programs/pw-dump.1.md create mode 100644 doc/dox/programs/pw-jack.1.md create mode 100644 doc/dox/programs/pw-link.1.md create mode 100644 doc/dox/programs/pw-loopback.1.md create mode 100644 doc/dox/programs/pw-metadata.1.md create mode 100644 doc/dox/programs/pw-mididump.1.md create mode 100644 doc/dox/programs/pw-mon.1.md create mode 100644 doc/dox/programs/pw-profiler.1.md create mode 100644 doc/dox/programs/pw-top.1.md create mode 100644 doc/dox/pulse-modules.dox rename doc/{tutorial.dox => dox/tutorial/index.dox} (89%) rename doc/{ => dox/tutorial}/tutorial1.dox (100%) rename doc/{ => dox/tutorial}/tutorial2.dox (100%) rename doc/{ => dox/tutorial}/tutorial3.dox (100%) rename doc/{ => dox/tutorial}/tutorial4.dox (100%) rename doc/{ => dox/tutorial}/tutorial5.dox (100%) rename doc/{ => dox/tutorial}/tutorial6.dox (100%) rename doc/{ => examples}/tutorial1.c (100%) rename doc/{ => examples}/tutorial2.c (100%) rename doc/{ => examples}/tutorial3.c (100%) rename doc/{ => examples}/tutorial4.c (97%) rename doc/{ => examples}/tutorial5.c (100%) rename doc/{ => examples}/tutorial6.c (100%) create mode 100755 doc/input-filter.py delete mode 100755 doc/input-filter.sh create mode 100755 doc/man-fixup.py delete mode 100644 doc/manpage.dox.in delete mode 100644 doc/pipewire-architecture.dox delete mode 100644 doc/pipewire-tools.dox.in rename doc/{api-tree.dox => tree.dox} (91%) delete mode 100644 man/meson.build delete mode 100644 man/pipewire-pulse.1.rst.in delete mode 100644 man/pipewire.1.rst.in delete mode 100644 man/pipewire.conf.5.rst.in delete mode 100644 man/pw-cat.1.rst.in delete mode 100644 man/pw-cli.1.rst.in delete mode 100644 man/pw-config.1.rst.in delete mode 100644 man/pw-dot.1.rst.in delete mode 100644 man/pw-jack.1.rst.in delete mode 100644 man/pw-link.1.rst.in delete mode 100644 man/pw-metadata.1.rst.in delete mode 100644 man/pw-mididump.1.rst.in delete mode 100644 man/pw-mon.1.rst.in delete mode 100644 man/pw-profiler.1.rst.in delete mode 100644 man/pw-top.1.rst.in create mode 100644 src/daemon/systemd/system/pipewire-manager.socket diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 18eebef0..e0fce1cf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,8 +25,8 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2023-05-31.0' - FDO_DISTRIBUTION_VERSION: '37' + FDO_DISTRIBUTION_TAG: '2023-11-22.0' + FDO_DISTRIBUTION_VERSION: '39' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel avahi-devel @@ -75,6 +75,7 @@ include: pulseaudio-utils openal-soft readline-devel + pandoc # Uncommenting the following two lines and disabling the meson entry above # will re-enable use of Meson via pip but please consider using a newer distro # image first or making the build system compatible instead! This is because diff --git a/NEWS b/NEWS index 12ce28dc..76a75c23 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,139 @@ +# PipeWire 1.0.0 (2023-11-26) + +The PipeWire project is immensely proud to announce the 1.0 release +of PipeWire. + +It is API and ABI compatible with previous 0.3.x releases. + + "PipeWire represents the next evolution of audio handling for Linux, taking + the best of both pro-audio (JACK) and desktop audio servers (PulseAudio) and + linking them into a single, seamless, powerful new system." + - Paul Davis, JACK and Ardour author + + "What exciting times! PipeWire 1.0 is the culmination of 15 years of + Linux audio expertise, blending lessons from PulseAudio into a high-performance, + flexible, and user-friendly foundation for audio and multimedia on Linux. + I'm looking forward to the next decade of progress in the free software + consumer and professional audio space!." + - Arun Raghavan, PulseAudio developer/maintainer. + + "I'm thrilled to witness the first stable release of PipeWire after five years + of collaboration with its remarkable community, pushing the boundaries of + multimedia integration in the Linux ecosystem one step further.†+ - George Kiagiadakis, WirePlumber author + + "From the beginning of the libcamera project, we have always seen + PipeWire as the solution to handle desktop and mobile integration and + give a seemless multimedia integration to users while providing security + features and resource sharing between applications." + - Kieran Bingham, libcamera author + +Happy Holidays! + + +## Highlights + - Fix a memfd/dmabuf leak when uploading buffers while shutting down. + - Handle concurrent jack_port_get_buffer() calls because ardour seems to + be doing this. + - Improve time reporting (less jitter) in ALSA when using IRQ. + - Many doc improvements. + +## PipeWire + - Respect PIPEWIRE_DLCLOSE everywhere, remove pw_in_valgrind(). + - Remove a warning when a client tries to change ignored properties. + +## Modules + - Fix a memfd/dmabuf leak when uploading buffers while shutting down. + - Fix a potential segfault when copying mix structures. (#3658) + - Avoid races in setrlimit in module-rt. + - Fix a memory leak in filter-chain. + - Set rtp.ptime on senders, not receivers. + - The ROC modules were ported to ROC 0.3 + +## SPA + - Improve time reporting (less jitter) in ALSA when using IRQ. (#3657) + - Add latency param query in libcamera. + - Fix some compiler warnings. + - The EVL plugin was updated. + +## Bluetooth + - LC3 codec and compatibility improvements. + +## Pulse server + - Fix emission of events when a sink/source state changes. (#3660) + +## JACK + - Improve transport and time handling. Use unique ids to make consistent + snapshots of the current time and transport. + - Avoid enumerating port params that we are not going to use. + - Optimize buffer reuse. + - Handle concurrent jack_port_get_buffer() calls because ardour seems to + be doing this. (#3632) + +## Docs + - Many doc improvements. + - Add man pages for pw-dump, pw-loopback, modules, pipewire-pulse. + - Manpages are now made with Doxygen. + - Add docs for pulse-modules + +Older versions: + + +# PipeWire 0.3.85 (2023-11-16) + +This is the fifth (and last) 1.0 release candidate that is API and ABI +compatible with previous 0.3.x releases. + +## Highlights + - Fix an issue where a link could end up paused while not negotiated. + - Fix an infinite recursion issue when finding runnable nodes. + - Support XDG base directories when loading ACP config. + - Fix MIDI event recording preview in Ardour. + - Many more small fixes, cleanups and improvements. + + +## PipeWire + - Fix an issue where a link could end up paused while not negotiated. + (#3619) + - Fix an infinite recursion issue when finding runnable nodes by stopping + the scan on feedback links around the driver. (#3621) + - The system service now has better socket permissions. + +## Modules + - Add support for uclamp. This allows the scheduler to make better informed + decisions about where tasks should be placed, and what pstate to set + for the CPU it is running on. + - Emit warnings when applications are not doing the right locking instead + of crashing. + - Improve media.name for RAOP sinks. (#3801) + - Support pause/resume in pipe-tunnel. (#3197) + - Remove time rlimit when probing for realtime to avoid SIGXCPU. + +## SPA + - Fix a bug where the resampler would be activated even when there is an + ALSA pitch element. (#3628) + - Improve resume from suspend in ALSA. (#3646) + - Add option to expose ALSA controls as prop params. + - Support XDG base directories when loading ACP config. This makes it possible + to override the ACP config files. + +## Bluetooth + - Schedule nodes in the same ISO group together. + - More BAP fixes and cleanups. + +## JACK + - Fix MIDI events from peer ports. This makes the MIDI event recording preview + of Ardour work correctly. + +## GStreamer + - Fix some error handling in the source and sink. + +## ALSA plugin + - Improve poll descriptor handling. (#3648) + +## Docs + - Many improvements to the layout and organization. + # PipeWire 0.3.84 (2023-11-02) This is the fourth 1.0 release candidate that is API and ABI compatible @@ -56,9 +192,6 @@ with previous 0.3.x releases. ## ALSA - The ALSA plugin now handles NULL values from mmap_areas. (#3600) -Older versions: - - # PipeWire 0.3.83 (2023-10-19) This is the third 1.0 release candidate that is API and ABI compatible diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 15f9ae1b..fc3d0223 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -1,7 +1,7 @@ PROJECT_NAME = PipeWire PROJECT_NUMBER = @PACKAGE_VERSION@ OUTPUT_DIRECTORY = "@output_directory@" -FULL_PATH_NAMES = NO +FULL_PATH_NAMES = YES JAVADOC_AUTOBRIEF = YES TAB_SIZE = 8 OPTIMIZE_OUTPUT_FOR_C = YES @@ -26,6 +26,9 @@ EXAMPLE_PATH = "@top_srcdir@/src/examples" \ "@top_srcdir@/doc" EXAMPLE_PATTERNS = "*.c" +GENERATE_MAN = YES +MAN_EXTENSION = 3 + REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO IGNORE_PREFIX = pw_ \ @@ -36,6 +39,9 @@ GENERATE_TREEVIEW = YES SEARCHENGINE = YES GENERATE_LATEX = NO +TOC_INCLUDE_HEADINGS = 0 +LAYOUT_FILE = @layout@ + MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = PA_C_DECL_BEGIN= \ diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml new file mode 100644 index 00000000..29378f9d --- /dev/null +++ b/doc/DoxygenLayout.xml @@ -0,0 +1,238 @@ +<doxygenlayout version="1.0"> + <navindex> + <tab type="mainpage" visible="yes" title=""/> + <tab type="pages" visible="yes" title="Pages" intro=""/> + <tab type="modules" visible="yes" title="API Reference" intro="" /> + <tab type="namespaces" visible="no" title=""> + <tab type="namespacelist" visible="yes" title="" intro=""/> + <tab type="namespacemembers" visible="yes" title="" intro=""/> + </tab> + <tab type="concepts" visible="no" title=""> + </tab> + <tab type="interfaces" visible="no" title=""> + <tab type="interfacelist" visible="yes" title="" intro=""/> + <tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="interfacehierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="classes" visible="yes" title=""> + <tab type="classlist" visible="yes" title="" intro=""/> + <tab type="classindex" visible="yes" title=""/> + <tab type="hierarchy" visible="yes" title="" intro=""/> + <tab type="classmembers" visible="no" title="" intro=""/> + </tab> + <tab type="structs" visible="no" title=""> + <tab type="structlist" visible="yes" title="" intro=""/> + <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> + </tab> + <tab type="exceptions" visible="no" title=""> + <tab type="exceptionlist" visible="yes" title="" intro=""/> + <tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="exceptionhierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="files" visible="yes" title=""> + <tab type="filelist" visible="yes" title="" intro=""/> + <tab type="globals" visible="no" title="" intro=""/> + </tab> + <tab type="examples" visible="yes" title="" intro=""/> + </navindex> + + <!-- Layout definition for a class page --> + <class> + <briefdescription visible="yes"/> + <includes visible="$SHOW_HEADERFILE"/> + <inheritancegraph visible="$CLASS_GRAPH"/> + <collaborationgraph visible="$COLLABORATION_GRAPH"/> + <memberdecl> + <nestedclasses visible="yes" title=""/> + <publictypes title=""/> + <services title=""/> + <interfaces title=""/> + <publicslots title=""/> + <signals title=""/> + <publicmethods title=""/> + <publicstaticmethods title=""/> + <publicattributes title=""/> + <publicstaticattributes title=""/> + <protectedtypes title=""/> + <protectedslots title=""/> + <protectedmethods title=""/> + <protectedstaticmethods title=""/> + <protectedattributes title=""/> + <protectedstaticattributes title=""/> + <packagetypes title=""/> + <packagemethods title=""/> + <packagestaticmethods title=""/> + <packageattributes title=""/> + <packagestaticattributes title=""/> + <properties title=""/> + <events title=""/> + <privatetypes title=""/> + <privateslots title=""/> + <privatemethods title=""/> + <privatestaticmethods title=""/> + <privateattributes title=""/> + <privatestaticattributes title=""/> + <friends title=""/> + <related title="" subtitle=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <services title=""/> + <interfaces title=""/> + <constructors title=""/> + <functions title=""/> + <related title=""/> + <variables title=""/> + <properties title=""/> + <events title=""/> + </memberdef> + <allmemberslink visible="yes"/> + <usedfiles visible="$SHOW_USED_FILES"/> + <authorsection visible="yes"/> + </class> + + <!-- Layout definition for a namespace page --> + <namespace> + <briefdescription visible="yes"/> + <memberdecl> + <nestednamespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <concepts visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection visible="yes"/> + </namespace> + + <!-- Layout definition for a concept page --> + <concept> + <briefdescription visible="yes"/> + <includes visible="$SHOW_HEADERFILE"/> + <definition visible="yes" title=""/> + <detaileddescription title=""/> + <authorsection visible="yes"/> + </concept> + + <!-- Layout definition for a file page --> + <file> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <includegraph visible="$INCLUDE_GRAPH"/> + <includedbygraph visible="$INCLUDED_BY_GRAPH"/> + <sourcelink visible="yes"/> + <memberdecl> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <concepts visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection/> + </file> + + <!-- Layout definition for a group page --> + <group> + <briefdescription visible="yes"/> + <groupgraph visible="$GROUP_GRAPHS"/> + <memberdecl> + <nestedgroups visible="yes" title=""/> + <dirs visible="yes" title=""/> + <files visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <concepts visible="yes" title=""/> + <classes visible="yes" title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <defines title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <pagedocs/> + <inlineclasses title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <defines title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + </memberdef> + <authorsection visible="yes"/> + </group> + + <!-- Layout definition for a directory page --> + <directory> + <briefdescription visible="yes"/> + <directorygraph visible="yes"/> + <memberdecl> + <dirs visible="yes"/> + <files visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + </directory> +</doxygenlayout> diff --git a/doc/custom.css b/doc/custom.css index 43690cb8..97f033f7 100644 --- a/doc/custom.css +++ b/doc/custom.css @@ -17,3 +17,24 @@ --fragment-link: #729fcf; } } + +#nav-tree .arrow { + opacity: 1; + padding-right: 0.25em; +} + +.textblock h1 { + font-size: 150%; +} +.textblock h2 { + font-size: 100%; +} +.textblock h3, .textblock h4, .textblock h5, .textblock h6 { + font-size: 100%; + font-style: italic; + font-size: medium; +} + +.textblock dl.section dd { + margin-left: 2rem; +} diff --git a/doc/api.dox b/doc/dox/api/index.dox similarity index 99% rename from doc/api.dox rename to doc/dox/api/index.dox index 880127e9..96268669 100644 --- a/doc/api.dox +++ b/doc/dox/api/index.dox @@ -59,6 +59,7 @@ digraph API { It is common for clients to use both the \ref api_pw_core and the \ref api_pw_impl and both APIs are provided by the same library. +- \subpage page_spa - \subpage page_client_impl - \subpage page_proxy - \subpage page_streams diff --git a/doc/spa-buffer.dox b/doc/dox/api/spa-buffer.dox similarity index 100% rename from doc/spa-buffer.dox rename to doc/dox/api/spa-buffer.dox diff --git a/doc/spa-design.dox b/doc/dox/api/spa-design.dox similarity index 100% rename from doc/spa-design.dox rename to doc/dox/api/spa-design.dox diff --git a/doc/spa-index.dox b/doc/dox/api/spa-index.dox similarity index 100% rename from doc/spa-index.dox rename to doc/dox/api/spa-index.dox diff --git a/doc/spa-plugins.dox b/doc/dox/api/spa-plugins.dox similarity index 100% rename from doc/spa-plugins.dox rename to doc/dox/api/spa-plugins.dox diff --git a/doc/spa-pod.dox b/doc/dox/api/spa-pod.dox similarity index 100% rename from doc/spa-pod.dox rename to doc/dox/api/spa-pod.dox diff --git a/doc/index.dox b/doc/dox/index.dox similarity index 95% rename from doc/index.dox rename to doc/dox/index.dox index 36f3ebaf..93998e2e 100644 --- a/doc/index.dox +++ b/doc/dox/index.dox @@ -17,7 +17,7 @@ PipeWire ships with the following components: - A \ref page_daemon that implements the IPC and graph processing. - An example \ref page_session_manager that manages objects in the \ref page_daemon. -- A set of \ref page_tools to introspect and use the \ref page_daemon. +- A set of \ref page_programs to introspect and use the \ref page_daemon. - A \ref page_library to develop PipeWire applications and plugins (\ref page_tutorial "tutorial"). - The \ref page_spa used by both the \ref page_daemon and in the \ref diff --git a/doc/pipewire-access.dox b/doc/dox/internals/access.dox similarity index 100% rename from doc/pipewire-access.dox rename to doc/dox/internals/access.dox diff --git a/doc/pipewire-audio.dox b/doc/dox/internals/audio.dox similarity index 100% rename from doc/pipewire-audio.dox rename to doc/dox/internals/audio.dox diff --git a/doc/pipewire-daemon.dox b/doc/dox/internals/daemon.dox similarity index 100% rename from doc/pipewire-daemon.dox rename to doc/dox/internals/daemon.dox diff --git a/doc/pipewire-design.dox b/doc/dox/internals/design.dox similarity index 100% rename from doc/pipewire-design.dox rename to doc/dox/internals/design.dox diff --git a/doc/dma-buf.dox b/doc/dox/internals/dma-buf.dox similarity index 100% rename from doc/dma-buf.dox rename to doc/dox/internals/dma-buf.dox diff --git a/doc/pipewire.dox b/doc/dox/internals/index.dox similarity index 85% rename from doc/pipewire.dox rename to doc/dox/internals/index.dox index 551adc0f..f7152d61 100644 --- a/doc/pipewire.dox +++ b/doc/dox/internals/index.dox @@ -1,4 +1,4 @@ -/** \page page_pipewire PipeWire Design +/** \page page_internals Internals # Internals @@ -17,10 +17,8 @@ # Components - \subpage page_daemon -- \subpage page_tools - \subpage page_session_manager - # Backends - \subpage page_pulseaudio diff --git a/doc/pipewire-library.dox b/doc/dox/internals/library.dox similarity index 100% rename from doc/pipewire-library.dox rename to doc/dox/internals/library.dox diff --git a/doc/pipewire-midi.dox b/doc/dox/internals/midi.dox similarity index 100% rename from doc/pipewire-midi.dox rename to doc/dox/internals/midi.dox diff --git a/doc/pipewire-objects-design.dox b/doc/dox/internals/objects.dox similarity index 100% rename from doc/pipewire-objects-design.dox rename to doc/dox/internals/objects.dox diff --git a/doc/pipewire-portal.dox b/doc/dox/internals/portal.dox similarity index 96% rename from doc/pipewire-portal.dox rename to doc/dox/internals/portal.dox index 721d9817..935e2616 100644 --- a/doc/pipewire-portal.dox +++ b/doc/dox/internals/portal.dox @@ -13,7 +13,7 @@ client is a portal-managed client. PipeWire can detect and enforce extra permission checks on the portal managed clients. Once such portal is the [camera -portal](https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.Camera) +portal](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html) that provides a PipeWire session to stream video from a camera. @@ -176,7 +176,7 @@ The session manager listens for new clients to appear. It will use the from the portal the session manager checks the requested `media_roles` and enables or disables access to the respective PipeWire objects. It might have to consult a database to decide what is allowed, for example the -[org.freedesktop.impl.portal.PermissionStore](https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.impl.portal.PermissionStore). +[org.freedesktop.impl.portal.PermissionStore](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.PermissionStore.html). \dot strict digraph pw { diff --git a/doc/pipewire-protocol.dox b/doc/dox/internals/protocol.dox similarity index 88% rename from doc/pipewire-protocol.dox rename to doc/dox/internals/protocol.dox index 8e943b2a..9db6217c 100644 --- a/doc/pipewire-protocol.dox +++ b/doc/dox/internals/protocol.dox @@ -7,7 +7,9 @@ The reference implementation uses unix sockets and is implemented in We document the messages here. -# Message header +\tableofcontents + +# Message header {#native-protocol-message-header} Each message on the unix socket contains a 16 bytes header and a variable length payload size: @@ -54,7 +56,7 @@ The payload is a single POD see \ref page_spa_pod for details. After the payload, there is an optional footer POD object. -# Making a connection +# Making a connection {#native-protocol-making-connection} First a connection is made to a unix domain socket. By default, the socket is named as "pipewire-0" and searched in the following directories: @@ -106,7 +108,7 @@ The client then sends client properties to the server. This completes the setup of the client. The newly connected client will appear in the registry at this point. -# Core proxy/resource +# Core proxy/resource {#native-protocol-core} The core is always the object with Id 0. @@ -444,7 +446,7 @@ registry. - global_id: the global_id as it will appear in the registry. - props: the properties of the global -# Registry proxy/resource +# Registry proxy/resource {#native-protocol-registry} The registry is obtained with the GetRegistry method on the Core object. The Id depends on the new_id that was provided. @@ -522,7 +524,7 @@ A global with id was removed - id: the global id that was removed. -# PipeWire:Interface:Client +# PipeWire:Interface:Client {#native-protocol-client} The client object represents a client connect to the PipeWire server. Permissions of the client can be managed. @@ -638,7 +640,7 @@ Emitted as the reply of the GetPermissions method. - id: the global id of the object - permissions: the permission for the given id -# PipeWire:Interface:Device +# PipeWire:Interface:Device {#native-protocol-device} A device is an object that manages other devices or nodes. @@ -745,7 +747,7 @@ Emitted as a result of EnumParams or SubscribeParams. - param: the parameter. The object type depends on the id -# PipeWire:Interface:Factory +# PipeWire:Interface:Factory {#native-protocol-factory} A factory is an object that allows one to create new objects. @@ -781,7 +783,7 @@ Info is emitted when binding to the factory global or when the information chang - props: optional properties of the factory, valid when change_mask is (1<<0) -# PipeWire:Interface:Link +# PipeWire:Interface:Link {#native-protocol-link} A link is a connection between 2 ports. @@ -827,7 +829,7 @@ Info is emitted when binding to the link global or when the information changed. - props: optional properties of the link, valid when change_mask is (1<<2) -# PipeWire:Interface:Module +# PipeWire:Interface:Module {#native-protocol-module} A Module provides dynamically loaded functionality @@ -864,7 +866,7 @@ Info is emitted when binding to the module global or when the information change - props: optional properties of the module, valid when change_mask has (1<<0) -# PipeWire:Interface:Node +# PipeWire:Interface:Node {#native-protocol-node} A Node is a processing element in the graph @@ -991,7 +993,7 @@ Emitted as a result of EnumParams or SubscribeParams. - param: the parameter. The object type depends on the id -# PipeWire:Interface:Port +# PipeWire:Interface:Port {#native-protocol-port} A port is part of a node and allows links with other ports. @@ -1080,7 +1082,7 @@ Emitted as a result of EnumParams or SubscribeParams. - next: the index of the next parameter -# PipeWire:Interface:ClientNode +# PipeWire:Interface:ClientNode {#native-protocol-clientnode} The ClientNode object is created from the `client-node` factory that is provided by the `libpipewire-module-client-node` module. @@ -1497,7 +1499,7 @@ ports of a node. - peer_id: the id of the peer port - props: optional properties -# PipeWire:Interface:Metadata +# PipeWire:Interface:Metadata {#native-protocol-metadata} Metadata is a shared database of settings and properties. @@ -1550,7 +1552,7 @@ A metadata key changed. This is also emitted when binding to the metadata. - type: an optional type - value: a value -# PipeWire:Interface:Profiler +# PipeWire:Interface:Profiler {#native-protocol-profiler} The profiler object allows one to receive profiler information of the pipewire graph. @@ -1570,4 +1572,120 @@ The profiler has no methods ``` - object: a SPA_TYPE_OBJECT_Profiler object. See enum spa_profiler + +# Footer {#native-protocol-footer} + +The message footer contains additional messages, not directed to the +destination object defined by the `Id` field. + +The footer consists of a single POD, immediately following the payload +POD. The footer POD consists of a sequence of footer opcode Ids and +footer payload Structs containing their arguments: + +``` + Struct( + Id: opcode1, + Struct { ... }, + Id: opcode2, + Struct { ... }, + ... + ) +``` + +The footer opcodes are separate for server-to-client (`core`) and +client-to-server (`client`) directions. + +The message footer is processed before other parts of the message, +including the object Id lookup. + +## Core Generation (Footer Opcode 0) + +``` + Struct( + Long: registry_generation, + ) +``` + +Indicates to the client what is the current registry generation +number of the \ref pw_context on the server side. + +The server shall include this footer in the next message it sends that +follows the increment of the registry generation number. + +\see \ref native-protocol-registry-generation + +## Client Generation (Footer Opcode 0) + +``` + Struct( + Long: client_generation, + ) +``` + +Indicates to the server what is the last registry generation number +the client has processed. + +The client shall include this footer in the next message it sends, +after it has processed an incoming message whose footer includes a +registry generation update. + +\see \ref native-protocol-registry-generation + +# Registry generation {#native-protocol-registry-generation} + +The registry generation is a 64-bit integer in the PipeWire server +\ref pw_context that increments by one when the server allocates a new +global \ref PW_KEY_OBJECT_ID for an object. + +The server keeps track of each global *id* as a tuple ( *id*, *object +generation* ) where *object generation* is the registry generation +value when the *id* was allocated. + +When an object is destroyed, its *id* value may be reallocated to a +different object. Because the protocol is asynchronous, the +object *id* alone is not sufficient to uniquely identify objects. + +The server looks up objects based on a tuple ( *id*, *generation* ) as +follows: + +1. Look up the \ref pw_global based on the *id*. + +2. The lookup fails if there is no global for the *id*, or + if *generation* < *object generation*. + +The protocol message generally contains only the object *id*. The +registry generation part is passed around as follows: + +1. The server sends the current *registry generation* to clients in the + protocol footer, if it has changed. + +2. The clients keep track of the latest registry generation of the + messages they have processed. This is the *client generation*. + +3. Each client sends their *client generation* in the protocol footer + of the next message to the server, if its value has changed. + +4. The server keeps track for each client the *client generation* they + have sent back. + +5. In each protocol message received from client, the server considers + each object *id* as tuple ( *id*, *client generation* ). + +This allows the server to know if the object *id* the client refers to was +already destroyed, but the client has not yet processed the message +indicating that the *id* is gone. The server indicates failed lookups +of this type with error code ESTALE to the client. + +If a client has not sent any *client generation* updates to the +server, then the server will not do any registry generation checks in +object lookups. This is for backward compatibility only. + +The registry generation is an internal detail of the server +implementation and the native protocol, and is not visible to clients +in the PipeWire API. To identify objects uniquely, clients can use +\ref PW_KEY_OBJECT_SERIAL, which are unique for objects and not +reused, unlike \ref PW_KEY_OBJECT_ID + +\see \ref PW_KEY_OBJECT_SERIAL + */ diff --git a/doc/pulseaudio.dox b/doc/dox/internals/pulseaudio.dox similarity index 100% rename from doc/pulseaudio.dox rename to doc/dox/internals/pulseaudio.dox diff --git a/doc/pipewire-scheduling.dox b/doc/dox/internals/scheduling.dox similarity index 92% rename from doc/pipewire-scheduling.dox rename to doc/dox/internals/scheduling.dox index 6f6f061f..dffd2b3f 100644 --- a/doc/pipewire-scheduling.dox +++ b/doc/dox/internals/scheduling.dox @@ -6,6 +6,22 @@ Graph are constructed from linked nodes together with their ports. This results in a dependency graph between nodes. Special care is taken for loopback links so that the graph remains a directed graph. +# Processing threads + +The server (and clients) have two processing threads: + +- A main thread that will do all IPC with clients and server and configures the + nodes in the graph for processing. +- A (or more) data processing thread that only does the data processing. + + +The data processing threads are given realtime priority and are designed to +run with as little overhead as possible. All of the node resources such as +buffers, io areas and metadata will be set up in shared memory before the +node is scheduled to run. + +This document describes the processing that happens in the data processing +thread after the main-thread has configured it. # Nodes diff --git a/doc/pipewire-session-manager.dox b/doc/dox/internals/session-manager.dox similarity index 100% rename from doc/pipewire-session-manager.dox rename to doc/dox/internals/session-manager.dox diff --git a/doc/pipewire-modules.dox b/doc/dox/modules.dox similarity index 98% rename from doc/pipewire-modules.dox rename to doc/dox/modules.dox index 460b0934..1eaaf487 100644 --- a/doc/pipewire-modules.dox +++ b/doc/dox/modules.dox @@ -1,4 +1,4 @@ -/** \page page_pipewire_modules PipeWire Modules +/** \page page_modules Modules A PipeWire module is effectively a PipeWire client in an `.so` file that shares the \ref pw_context with the loading entity. Usually modules are diff --git a/doc/overview.dox b/doc/dox/overview.dox similarity index 100% rename from doc/overview.dox rename to doc/dox/overview.dox diff --git a/doc/dox/programs/index.md b/doc/dox/programs/index.md new file mode 100644 index 00000000..4ed988af --- /dev/null +++ b/doc/dox/programs/index.md @@ -0,0 +1,23 @@ +\page page_programs Programs + +Manual pages: + +- \subpage page_man_pipewire_1 +- \subpage page_man_pipewire_conf_5 +- \subpage page_man_pipewire-pulse_1 +- \subpage page_man_pipewire-pulse_conf_5 +- \subpage page_man_pipewire-pulse-modules_7 +- \subpage page_man_pw-cat_1 +- \subpage page_man_pw-cli_1 +- \subpage page_man_pw-config_1 +- \subpage page_man_pw-dot_1 +- \subpage page_man_pw-dump_1 +- \subpage page_man_pw-jack_1 +- \subpage page_man_pw-link_1 +- \subpage page_man_pw-loopback_1 +- \subpage page_man_pw-metadata_1 +- \subpage page_man_pw-mididump_1 +- \subpage page_man_pw-mon_1 +- \subpage page_man_pw-profiler_1 +- \subpage page_man_pw-top_1 +- \subpage page_man_libpipewire-modules_7 diff --git a/doc/dox/programs/libpipewire-modules.7.md b/doc/dox/programs/libpipewire-modules.7.md new file mode 100644 index 00000000..ae3f88be --- /dev/null +++ b/doc/dox/programs/libpipewire-modules.7.md @@ -0,0 +1,44 @@ +\page page_man_libpipewire-modules_7 libpipewire-modules + +PipeWire modules + +# DESCRIPTION + +A PipeWire module is effectively a PipeWire client running inside +`pipewire(1)` which can host multiple modules. Usually modules are +loaded when they are listed in the configuration files. For example the +default configuration file loads several modules: + + context.modules = [ + ... + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # The profile module. Allows application to access profiler + # and performance data. It provides an interface that is used + # by pw-top and pw-profiler. + { name = libpipewire-module-profiler } + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + { name = libpipewire-module-metadata } + + # Creates a factory for making devices that run in the + # context of the PipeWire server. + { name = libpipewire-module-spa-device-factory } + ... + ] + +# KNOWN MODULES + +$(LIBPIPEWIRE_MODULES) + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pipewire_conf_5 "pipewire.conf(5)" diff --git a/doc/dox/programs/pipewire-pulse-modules.7.md b/doc/dox/programs/pipewire-pulse-modules.7.md new file mode 100644 index 00000000..f31e3e07 --- /dev/null +++ b/doc/dox/programs/pipewire-pulse-modules.7.md @@ -0,0 +1,32 @@ +\page page_man_pipewire-pulse-modules_7 pipewire-pulse-modules + +PipeWire Pulseaudio modules + +# DESCRIPTION + +PipeWire's Pulseaudio emulation implements several Pulseaudio modules. +It only supports its own built-in modules, and cannot load external +modules written for Pulseaudio. + +The built-in modules can be loaded using Pulseaudio client programs, for +example `pactl load-module \<module-name\> \<module-options\>`. +They can also added to `pipewire-pulse.conf`, typically by a +drop-in file in `~/.config/pipewire/pipewire-pulse.conf.d/` +containing the module name and its arguments + + pulse.cmd = [ + { cmd = "load-module" args = "module-null-sink sink_name=foo" flags = [ ] } + ] + +# KNOWN MODULES + +$(PIPEWIRE_PULSE_MODULES) + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire-pulse_1 "pipewire-pulse(1)" diff --git a/doc/dox/programs/pipewire-pulse.1.md b/doc/dox/programs/pipewire-pulse.1.md new file mode 100644 index 00000000..90ed98f1 --- /dev/null +++ b/doc/dox/programs/pipewire-pulse.1.md @@ -0,0 +1,40 @@ +\page page_man_pipewire-pulse_1 pipewire-pulse + +The PipeWire PulseAudio replacement + +# SYNOPSIS + +**pipewire-pulse** \[*options*\] + +# DESCRIPTION + +**pipewire-pulse** starts a PulseAudio-compatible daemon that integrates +with the PipeWire media server, by running a pipewire process through a +systemd service. This daemon is a drop-in replacement for the PulseAudio +daemon. + +# OPTIONS + +\par -h | \--help +Show help. + +\par -v | \--verbose +Increase the verbosity by one level. This option may be specified +multiple times. + +\par \--version +Show version information. + +\par -c | \--config=FILE +Load the given config file (Default: pipewire-pulse.conf). + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire-pulse_conf_5 "pipewire-pulse.conf(5)", +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pipewire-pulse-modules_7 "pipewire-pulse-modules(7)" diff --git a/doc/dox/programs/pipewire-pulse.conf.5.md b/doc/dox/programs/pipewire-pulse.conf.5.md new file mode 100644 index 00000000..fe94352f --- /dev/null +++ b/doc/dox/programs/pipewire-pulse.conf.5.md @@ -0,0 +1,56 @@ +\page page_man_pipewire-pulse_conf_5 pipewire-pulse.conf + +The PipeWire Pulseaudio server configuration file + +# SYNOPSIS + +*$XDG_CONFIG_HOME/pipewire/pipewire-pulse.conf* + +*$(PIPEWIRE_CONFIG_DIR)/pipewire-pulse.conf* + +*$(PIPEWIRE_CONFDATADIR)/pipewire-pulse.conf* + +*$(PIPEWIRE_CONFDATADIR)/pipewire-pulse.conf.d/* + +*$(PIPEWIRE_CONFIG_DIR)/pipewire-pulse.conf.d/* + +*$XDG_CONFIG_HOME/pipewire/pipewire-pulse.conf.d/* + +# DESCRIPTION + +Configuration for PipeWire's PulseAudio-compatible daemon. + +The configuration file format is the same as for `pipewire.conf(5)`. +There are additional sections for configuring `pipewire-pulse(1)` +settings. + +# CONFIGURATION FILE SECTIONS + +\par pulse.properties +Dictionary. These properties configure the PipeWire Pulseaudio server +properties. + +\par pulse.cmd +Array of dictionaries. A set of commands to be executed on startup. + +\par pulse.rules +Array of dictionaries. A set of match rules and actions to apply to +clients. + +See \ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)" +for the detailed description. + +In addition, the general PipeWire daemon configuration sections apply, +see \ref page_man_pipewire_conf_5 "pipewire.conf(5)". + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)", +\ref page_man_pipewire_conf_5 "pipewire.conf(5)", +\ref page_man_pipewire-pulse_1 "pipewire-pulse(1)", +\ref page_man_pipewire-pulse-modules_7 "pipewire-pulse-modules(7)" diff --git a/doc/dox/programs/pipewire.1.md b/doc/dox/programs/pipewire.1.md new file mode 100644 index 00000000..9b6c389d --- /dev/null +++ b/doc/dox/programs/pipewire.1.md @@ -0,0 +1,44 @@ +\page page_man_pipewire_1 pipewire + +The PipeWire media server + +# SYNOPSIS + +**pipewire** \[*options*\] + +# DESCRIPTION + +PipeWire is a service that facilitates sharing of multimedia content +between devices and applications. + +The **pipewire** daemon reads a config file that is further documented +in \ref page_man_pipewire_conf_5 "pipewire.conf(5)" manual page. + +# OPTIONS + +\par -h | \--help +Show help. + +\par -v | \--verbose +Increase the verbosity by one level. This option may be specified +multiple times. + +\par \--version +Show version information. + +\par -c | \--config=FILE +Load the given config file (Default: pipewire.conf). + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pw-top_1 "pw-top(1)", +\ref page_man_pw-dump_1 "pw-dump(1)", +\ref page_man_pw-mon_1 "pw-mon(1)", +\ref page_man_pw-cat_1 "pw-cat(1)", +\ref page_man_pw-cli_1 "pw-cli(1)", +\ref page_man_libpipewire-modules_7 "libpipewire-modules(7)" diff --git a/doc/dox/programs/pipewire.conf.5.md b/doc/dox/programs/pipewire.conf.5.md new file mode 100644 index 00000000..4abe2160 --- /dev/null +++ b/doc/dox/programs/pipewire.conf.5.md @@ -0,0 +1,102 @@ +\page page_man_pipewire_conf_5 pipewire.conf + +The PipeWire server configuration file + +# SYNOPSIS + +*$XDG_CONFIG_HOME/pipewire/pipewire.conf* + +*$(PIPEWIRE_CONFIG_DIR)/pipewire.conf* + +*$(PIPEWIRE_CONFDATADIR)/pipewire.conf* + +*$(PIPEWIRE_CONFDATADIR)/pipewire.conf.d/* + +*$(PIPEWIRE_CONFIG_DIR)/pipewire.conf.d/* + +*$XDG_CONFIG_HOME/pipewire/pipewire.conf.d/* + +# DESCRIPTION + +PipeWire is a service that facilitates sharing of multimedia content +between devices and applications. + +On startup, the daemon reads a main configuration file to configure +itself. It executes a series of commands listed in the config file. + +The config files are loaded in the order listed in the +[SYNOPSIS](#synopsis). The environment variables `PIPEWIRE_CONFIG_DIR`, +`PIPEWIRE_CONFIG_PREFIX` and `PIPEWIRE_CONFIG_NAME` can be used to +specify an alternative config directory, subdirectory and file +respectively. + +Next to the configuration file can be a directory with the same name as +the file with a `.d/` suffix. All directories in the +[SYNOPSIS](#synopsis) directory search paths are traversed in the listed +order and the contents of the `*.conf` files inside them are appended to +the main configuration file as overrides. Object sections are merged and +array sections are appended. + +# CONFIGURATION FILE FORMAT + +The configuration file format is grouped into sections. A section is +either a dictionary, {}, or an array, \[\]. Dictionary and array entries +are separated by whitespace and may be simple value assignment, an array +or a dictionary. For example: +``` + name = value # simple assignment + + name = { key1 = value1 key2 = value2 } # a dictionary with two entries + + name = [ value1 value2 ] # an array with two entries + + name = [ { k = v1 } { k = v2 } ] # an array of dictionaries +``` + +The configuration files can be expressed in full JSON syntax but for +ease of use, a relaxed format may be used where: + +- `:` to delimit keys and values can be substuted by `=` or a space. +- <tt>\"</tt> around keys and string can be omitted as long as no special + characters are used in the strings. +- `,` to separate objects can be replaced with a whitespace character. +- `#` can be used to start a comment until the line end + +# CONFIGURATION FILE SECTIONS + +\par context.properties +Dictionary. These properties configure the PipeWire instance. + +\par context.spa-libs +Dictionary. Maps plugin features with globs to a spa library. + +\par context.modules +Array of dictionaries. Each entry in the array is a dictionary with the +*name* of the module to load, including optional *args* and *flags*. +Most modules support being loaded multiple times. + +\par context.objects +Array of dictionaries. Each entry in the array is a dictionary +containing the *factory* to create an object from and optional extra +arguments specific to that factory. + +\par context.exec +\parblock +Array of dictionaries. Each entry in the array is dictionary containing +the *path* of a program to execute on startup and optional *args*. + +This array used to contain an entry to start the session manager but +this mode of operation has since been demoted to development aid. Avoid +starting a session manager in this way in production environment. +\endparblock + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-mon_1 "pw-mon(1)", +\ref page_man_libpipewire-modules_7 "libpipewire-modules(7)" diff --git a/doc/dox/programs/pw-cat.1.md b/doc/dox/programs/pw-cat.1.md new file mode 100644 index 00000000..4860c427 --- /dev/null +++ b/doc/dox/programs/pw-cat.1.md @@ -0,0 +1,163 @@ +\page page_man_pw-cat_1 pw-cat + +Play and record media with PipeWire + +# SYNOPSIS + +**pw-cat** \[*options*\] \[*FILE* \| -\] + +**pw-play** \[*options*\] \[*FILE* \| -\] + +**pw-record** \[*options*\] \[*FILE* \| -\] + +**pw-midiplay** \[*options*\] \[*FILE* \| -\] + +**pw-midirecord** \[*options*\] \[*FILE* \| -\] + +**pw-dsdplay** \[*options*\] \[*FILE* \| -\] + +# DESCRIPTION + +**pw-cat** is a simple tool for playing back or capturing raw or encoded +media files on a PipeWire server. It understands all audio file formats +supported by `libsndfile` for PCM capture and playback. When capturing +PCM, the filename extension is used to guess the file format with the +WAV file format as the default. + +It understands standard MIDI files for playback and recording. This tool +will not render MIDI files, it will simply make the MIDI events +available to the graph. You need a MIDI renderer such as qsynth, +timidity or a hardware MIDI rendered to hear the MIDI. + +DSD playback is supported with the DSF file format. This tool will only +work with native DSD capable hardware and will produce an error when no +such hardware was found. + +When the *FILE* is - input and output will be raw data from STDIN and +STDOUT respectively. + +# OPTIONS + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +\par -v | \--verbose +Verbose operation. + +\par -R | \--remote=NAME +The name the *remote* instance to connect to. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -p | \--playback +Playback mode. Read data from the specified file, and play it back. If +the tool is called under the name **pw-play** or **pw-midiplay** this is +the default. + +\par -r | \--record +Recording mode. Capture data and write it to the specified file. If the +tool is called under the name **pw-record** or **pw-midirecord** this is +the default. + +\par -m | \--midi +MIDI mode. *FILE* is a MIDI file. If the tool is called under the name +**pw-midiplay** or **pw-midirecord** this is the default. Note that this +program will *not* render the MIDI events into audible samples, it will +simply provide the MIDI events in the graph. You need a separate MIDI +renderer such as qsynth, timidity or a hardware renderer to hear the +MIDI. + +\par -d | \--dsd +DSD mode. *FILE* is a DSF file. If the tool is called under the name +**pw-dsdplay** this is the default. Note that this program will *not* +render the DSD audio. You need a DSD capable device to play DSD content +or this program will exit with an error. + +\par \--media-type=VALUE +Set the media type property (default Audio/Midi depending on mode). The +media type is used by the session manager to select a suitable target to +link to. + +\par \--media-category=VALUE +Set the media category property (default Playback/Capture depending on +mode). The media type is used by the session manager to select a +suitable target to link to. + +\par \--media-role=VALUE +Set the media role property (default Music). The media type is used by +the session manager to select a suitable target to link to. + +\par \--target=VALUE +\parblock +Set a node target (default auto). The value can be: + +- **auto**: Automatically select (Default) + +- **0**: Don't try to link this node + +- <b>\<id\></b>: The object.serial or the node.name of a target node +\endparblock + +\par \--latency=VALUE\[*units*\] +\parblock +Set the node latency (default 100ms) + +The latency determines the minimum amount of time it takes for a sample +to travel from application to device (playback) and from device to +application (capture). + +The latency determines the size of the buffers that the application will +be able to fill. Lower latency means smaller buffers but higher +overhead. Higher latency means larger buffers and lower overhead. + +Units can be **s** for seconds, **ms** for milliseconds, **us** for +microseconds, **ns** for nanoseconds. If no units are given, the latency +value is samples with the samplerate of the file. +\endparblock + +\par -P | \--properties=VALUE +Set extra stream properties as a JSON object. + +\par -q | \--quality=VALUE +Resampler quality. When the samplerate of the source or destination file +does not match the samplerate of the server, the data will be resampled. +Higher quality uses more CPU. Values between 0 and 15 are allowed, the +default quality is 4. + +\par \--rate=VALUE +The sample rate, default 48000. + +\par \--channels=VALUE +The number of channels, default 2. + +\par \--channel-map=VALUE +The channelmap. Possible values include: **mono**, **stereo**, +**surround-21**, **quad**, **surround-22**, **surround-40**, +**surround-31**, **surround-41**, **surround-50**, **surround-51**, +**surround-51r**, **surround-70**, **surround-71** or a comma separated +list of channel names: **FL**, **FR**, **FC**, **LFE**, **SL**, **SR**, +**FLC**, **FRC**, **RC**, **RL**, **RR**, **TC**, **TFL**, **TFC**, +**TFR**, **TRL**, **TRC**, **TRR**, **RLC**, **RRC**, **FLW**, **FRW**, +**LFE2**, **FLH**, **FCH**, **FRH**, **TFLC**, **TFRC**, **TSL**, +**TSR**, **LLFR**, **RLFE**, **BC**, **BLC**, **BRC** + +\par \--format=VALUE +The sample format to use. One of: **u8**, **s8**, **s16** (default), +**s24**, **s32**, **f32**, **f64**. + +\par \--volume=VALUE +The stream volume, default 1.000. Depending on the locale you have +configured, "," or "." may be used as a decimal separator. Check with +**locale** command. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-mon_1 "pw-mon(1)", diff --git a/doc/dox/programs/pw-cli.1.md b/doc/dox/programs/pw-cli.1.md new file mode 100644 index 00000000..0108ef60 --- /dev/null +++ b/doc/dox/programs/pw-cli.1.md @@ -0,0 +1,189 @@ +\page page_man_pw-cli_1 pw-cli + +The PipeWire Command Line Interface + +# SYNOPSIS + +**pw-cli** \[*command*\] + +# DESCRIPTION + +Interact with a PipeWire instance. + +When a command is given, **pw-cli** will execute the command and exit + +When no command is given, **pw-cli** starts an interactive session with +the default PipeWire instance *pipewire-0*. + +Connections to other, remote instances can be made. The current instance +name is displayed at the prompt. + +Note that **pw-cli** also creates a local PipeWire instance. Some +commands operate on the current (remote) instance and some on the local +instance, such as module loading. + +Use the 'help' command to list the available commands. + +# GENERAL COMMANDS + +\par help | h +Show a quick help on the commands available. It also lists the aliases +for many commands. + +\par quit | q +Exit from **pw-cli** + +# MODULE MANAGEMENT + +Modules are loaded and unloaded in the local instance, thus the pw-cli +binary itself and can add functionality or objects to the local +instance. It is not possible in PipeWire to load modules in another +instance. + +\par load-module *name* \[*arguments...*\] +\parblock +Load a module specified by its name and arguments in the local instance. +For most modules it is OK to be loaded more than once. + +This command returns a module variable that can be used to unload the +module. + +The locally module is *not* visible in the remote instance. It is not +possible in PipeWire to load modules in a remote instance. +\endparblock + +\par unload-module *module-var* +Unload a module, specified either by its variable. + +# OBJECT INTROSPECTION + +\par list-objects +List the objects of the current instance. + +Objects are listed with their *id*, *type* and *version*. + +\par info *id* | *all* +Get information about a specific object or *all* objects. + +Requesting info about an object will also notify you of changes. + +# WORKING WITH REMOTES + +\par connect \[*remote-name*\] +\parblock +Connect to a remote instance and make this the new current instance. + +If no remote name is specified, a connection is made to the default +remote instance, usually *pipewire-0*. + +The special remote name called *internal* can be used to connect to the +local **pw-cli** PipeWire instance. + +This command returns a remote var that can be used to disconnect or +switch remotes. +\endparblock + +\par disconnect \[*remote-var*\] +\parblock +Disconnect from a *remote instance*. + +If no remote name is specified, the current instance is disconnected. +\endparblock + +\par list-remotes +List all *remote instances*. + +\par switch-remote \[*remote-var*\] +\parblock +Make the specified *remote* the current instance. + +If no remote name is specified, the first instance is made current. +\endparblock + +# NODE MANAGEMENT + +\par create-node *factory-name* \[*properties...*\] +\parblock +Create a node from a factory in the current instance. + +Properties are key=value pairs separated by whitespace. + +This command returns a *node variable*. +\endparblock + +\par export-node *node-id* \[*remote-var*\] +Export a node from the local instance to the specified instance. When no +instance is specified, the node will be exported to the current +instance. + +# DEVICE MANAGEMENT + +\par create-device *factory-name* \[*properties...*\] +\parblock +Create a device from a factory in the current instance. + +Properties are key=value pairs separated by whitespace. + +This command returns a *device variable*. +\endparblock + +# LINK MANAGEMENT + +\par create-link *node-id* *port-id* *node-id* *port-id* \[*properties...*\] +\parblock +Create a link between 2 nodes and ports. + +Port *ids* can be *-1* to automatically select an available port. + +Properties are key=value pairs separated by whitespace. + +This command returns a *link variable*. +\endparblock + +# GLOBALS MANAGEMENT + +\par destroy *object-id* +Destroy a global object. + +# PARAMETER MANAGEMENT + +\par enum-params *object-id* *param-id* +\parblock +Enumerate params of an object. + +*param-id* can also be given as the param short name. +\endparblock + +\par set-param *object-id* *param-id* *param-json* +\parblock +Set param of an object. + +*param-id* can also be given as the param short name. +\endparblock + +# PERMISSION MANAGEMENT + +\par permissions *client-id* *object-id* *permission* +\parblock +Set permissions for a client. + +*object-id* can be *-1* to set the default permissions. +\endparblock + +\par get-permissions *client-id* +Get permissions of a client. + +# COMMAND MANAGEMENT + +\par send-command *object-id* +Send a command to an object. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-mon_1 "pw-mon(1)", diff --git a/doc/dox/programs/pw-config.1.md b/doc/dox/programs/pw-config.1.md new file mode 100644 index 00000000..898f12eb --- /dev/null +++ b/doc/dox/programs/pw-config.1.md @@ -0,0 +1,97 @@ +\page page_man_pw-config_1 pw-config + +Debug PipeWire Config parsing + +# SYNOPSIS + +**pw-config** \[*options*\] paths + +**pw-config** \[*options*\] list \[*SECTION*\] + +**pw-config** \[*options*\] merge *SECTION* + +# DESCRIPTION + +List config paths and config sections and display the parsed output. + +This tool can be used to get an overview of the config file that will be +parsed by the PipeWire server and clients. + +# COMMON OPTIONS + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +\par -n | \--name=NAME +Config Name (default 'pipewire.conf') + +\par -p | \--prefix=PREFIX +Config Prefix (default '') + +\par -L | \--no-newline +Omit newlines after values + +\par -r | \--recurse +Reformat config sections recursively + +\par -N | \--no-colors +Disable color output + +\par -C | \-color\[=WHEN\] +whether to enable color support. WHEN is +*never*, *always*, or *auto* + +# LISTING PATHS + +Specify the paths command. It will display all the config files that +will be parsed and in what order. + +# LISTING CONFIG SECTIONS + +Specify the list command with an optional *SECTION* to list the +configuration fragments used for *SECTION*. Without a *SECTION*, all +sections will be listed. + +Use the -r options to reformat the sections. + +# MERGING A CONFIG SECTION + +With the merge option and a *SECTION*, pw-config will merge all config +files into a merged config section and dump the results. This will be +the section used by the client or server. + +Use the -r options to reformat the sections. + +# EXAMPLES + +\par pw-config +List all config files that will be used + +\par pw-config -n pipewire-pulse.conf +List all config files that will be used by the PipeWire pulseaudio +server. + +\par pw-config -n pipewire-pulse.conf list +List all config sections used by the PipeWire pulseaudio server + +\par pw-config -n jack.conf list context.properties +List the context.properties fragments used by the JACK clients + +\par pw-config -n jack.conf merge context.properties +List the merged context.properties used by the JACK clients + +\par pw-config -n pipewire.conf -r merge context.modules +List the merged context.modules used by the PipeWire server and reformat + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-dump_1 "pw-dump(1)", diff --git a/doc/dox/programs/pw-dot.1.md b/doc/dox/programs/pw-dot.1.md new file mode 100644 index 00000000..ab30c0d8 --- /dev/null +++ b/doc/dox/programs/pw-dot.1.md @@ -0,0 +1,56 @@ +\page page_man_pw-dot_1 pw-dot + +The PipeWire dot graph dump + +# SYNOPSIS + +**pw-dot** \[*options*\] + +# DESCRIPTION + +Create a .dot file of the PipeWire graph. + +The .dot file can then be visualized with a tool like **dotty** or +rendered to a PNG file with `dot -Tpng pw.dot -o pw.png`. + +# OPTIONS + +\par -r | \--remote=NAME +The name the remote instance to connect to. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +\par -a | \--all +Show all object types. + +\par -s | \--smart +Show linked objects only. + +\par -d | \--detail +Show all object properties. + +\par -o FILE | \--output=FILE +Output file name (Default pw.dot). Use - for stdout. + +\par -L | \--lr +Lay the graph from left to right, instead of dot's default top to +bottom. + +\par -9 | \--90 +Lay the graph using 90-degree angles in edges. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-cli_1 "pw-cli(1)", +\ref page_man_pw-mon_1 "pw-mon(1)", diff --git a/doc/dox/programs/pw-dump.1.md b/doc/dox/programs/pw-dump.1.md new file mode 100644 index 00000000..cf507e59 --- /dev/null +++ b/doc/dox/programs/pw-dump.1.md @@ -0,0 +1,43 @@ +\page page_man_pw-dump_1 pw-dump + +The PipeWire state dumper + +# SYNOPSIS + +**pw-dump** \[*options*\] + +# DESCRIPTION + +The *pw-dump* program produces a representation of the current PipeWire +state as JSON, including the information on nodes, devices, modules, +ports, and other objects. + +# OPTIONS + +\par -h | \--help +Show help. + +\par -r | \--remote=NAME +The name of the *remote* instance to dump. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -m | \--monitor +Monitor PipeWire state changes, and output JSON arrays describing +changes. + +\par -N | \--no-colors +Disable color output. + +\par -C | \--color=WHEN +Whether to enable color support. WHEN is `never`, `always`, or `auto`. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-cli_1 "pw-cli(1)", +\ref page_man_pw-top_1 "pw-top(1)", diff --git a/doc/dox/programs/pw-jack.1.md b/doc/dox/programs/pw-jack.1.md new file mode 100644 index 00000000..f043d213 --- /dev/null +++ b/doc/dox/programs/pw-jack.1.md @@ -0,0 +1,45 @@ +\page page_man_pw-jack_1 pw-jack + +Use PipeWire instead of JACK + +# SYNOPSIS + +**pw-jack** \[*options*\] *COMMAND* \[*FILE*\] + +# DESCRIPTION + +**pw-jack** modifies the `LD_LIBRARY_PATH` environment variable so that +applications will load PipeWire's reimplementation of the JACK client +libraries instead of JACK's own libraries. This results in JACK clients +being redirected to PipeWire. + +If PipeWire's reimplementation of the JACK client libraries has been +installed as a system-wide replacement for JACK's own libraries, then +the whole system already behaves in that way, in which case **pw-jack** +has no practical effect. + +# OPTIONS + +\par -h +Show help. + +\par -r NAME +The name of the remote instance to connect to. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -v +Verbose operation. + +# EXAMPLES + +\par pw-jack sndfile-jackplay /usr/share/sounds/freedesktop/stereo/bell.oga + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +**jackd(1)**, diff --git a/doc/dox/programs/pw-link.1.md b/doc/dox/programs/pw-link.1.md new file mode 100644 index 00000000..aff53d60 --- /dev/null +++ b/doc/dox/programs/pw-link.1.md @@ -0,0 +1,135 @@ +\page page_man_pw-link_1 pw-link + +The PipeWire Link Command + +# SYNOPSIS + +**pw-link** \[*options*\] -o-l \[*out-pattern*\] \[*in-pattern*\] + +**pw-link** \[*options*\] *output* *input* + +**pw-link** \[*options*\] -d *output* *input* + +**pw-link** \[*options*\] -d *link-id* + +# DESCRIPTION + +List, create and destroy links between PipeWire ports. + +# COMMON OPTIONS + +\par -r | \--remote=NAME +The name the *remote* instance to monitor. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +# LISTING PORTS AND LINKS + +Specify one of -o, -i or -l to list the matching optional input and +output ports and their links. + +\par -o | \--output +List output ports + +\par -i | \--input +List output ports + +\par -l | \--links +List links + +\par -m | \--monitor +Monitor links and ports. **pw-link** will not exit but monitor and print +new and destroyed ports or links. + +\par -I | \--id +List IDs. Also list the unique link and port ids. + +\par -v | \--verbose +Verbose port properties. Also list the port-object-path and the +port-alias. + +# CONNECTING PORTS + +Without any list option (-i, -o or -l), the given ports will be linked. +Valid port specifications are: + +*port-id* + +As obtained with the -I option when listing ports. + +*node-name:port-name* + +As obtained when listing ports. + +*port-object-path* + +As obtained from the first alternative name for the port when listing +them with the -v option. + +*port-alias* + +As obtained from the second alternative name for the ports when listing +them with the -v option. + +Extra options when linking can be given: + +\par -L | \--linger +Linger. Will create a link that exists after **pw-link** is destroyed. +This is the default behaviour, unless the -m option is given. + +\par -P | \--passive +Passive link. A passive link will keep both nodes it links inactive +unless another non-passive link is activating the nodes. You can use +this to link a sink to a filter and have them both suspended when +nothing else is linked to either of them. + +\par -p | \--props=PROPS +Properties as JSON object. Give extra properties when creaing the link. + +# DISCONNECTING PORTS + +When the -d option is given, an existing link between port is destroyed. + +To disconnect port, a single *link-id*, as obtained when listing links +with the -I option, or two port specifications can be given. See the +connecting ports section for valid port specifications. + +\par -d | \--disconnect +Disconnect ports + +# EXAMPLES + +**pw-link** -iol + +List all port and their links. + +**pw-link** -lm + +List all links and monitor changes until **pw-link** is stopped. + +**pw-link** paplay:output_FL alsa_output.pci-0000_00_1b.0.analog-stereo:playback_FL + +Link the given output port to the input port. + +**pw-link** -lI + +List links and their Id. + +**pw-link** -d 89 + +Destroy the link with id 89. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-cli_1 "pw-cli(1)" diff --git a/doc/dox/programs/pw-loopback.1.md b/doc/dox/programs/pw-loopback.1.md new file mode 100644 index 00000000..98293430 --- /dev/null +++ b/doc/dox/programs/pw-loopback.1.md @@ -0,0 +1,67 @@ +\page page_man_pw-loopback_1 pw-loopback + +PipeWire loopback client + +# SYNOPSIS + +**pw-loopback** \[*options*\] + +# DESCRIPTION + +The *pw-loopback* program is a PipeWire client that uses the PipeWire +loopback module to create loopback nodes, with configuration given via +the command-line options. + +# OPTIONS + +\par -h | \--help +Show help. + +\par -r | \--remote=NAME +The name of the *remote* instance to connect to. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -n | \--name=NAME +Name of the loopback node + +\par -g | \--group=NAME +Name of the loopback node group + +\par -c | \--channels=NUMBER +Number of channels to provide + +\par -m | \--channel-map=MAP +Channel map (default `[ FL, FR ]`) + +\par -l | \--latency=LATENCY +Desired latency in ms + +\par -d | \--delay=DELAY +Added delay in seconds (floating point allowed) + +\par -C | \--capture=TARGET +Target device to capture from + +\par -P | \--playback=TARGET +Target device to play to + +\par \--capture-props=PROPS +Wanted properties of capture node (in JSON) + +\par \--playback-props=PROPS +Wanted properties of capture node (in JSON) + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-cat_1 "pw-cat(1)", +**pactl(1)** + +Other ways to create loopback nodes are adding the loopback module in +the configuration of a PipeWire daemon, or loading the loopback module +using Pulseaudio commands (`pactl load-module module-loopback ...`). diff --git a/doc/dox/programs/pw-metadata.1.md b/doc/dox/programs/pw-metadata.1.md new file mode 100644 index 00000000..77078de1 --- /dev/null +++ b/doc/dox/programs/pw-metadata.1.md @@ -0,0 +1,73 @@ +\page page_man_pw-metadata_1 pw-metadata + +The PipeWire metadata + +# SYNOPSIS + +**pw-metadata** \[*options*\] \[*id* \[*key* \[*value* \[*type* \] \] \] \] + +# DESCRIPTION + +Monitor, set and delete metadata on PipeWire objects. + +Metadata are key/type/value triplets attached to objects identified by +*id*. The metadata is shared between all applications binding to the +same metadata object. When an object is destroyed, all its metadata is +automatically removed. + +When no *value* is given, **pw-metadata** will query and log the +metadata matching the optional arguments *id* and *key*. Without any +arguments, all metadata is displayed. + +When *value* is given, **pw-metadata** will set the metadata for *id* +and *key* to *value* and an optional *type*. + +# OPTIONS + +\par -r | \--remote=NAME +The name the remote instance to use. If left unspecified, a connection +is made to the default PipeWire instance. + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +\par -l | \--list +List available metadata objects + +\par -m | \--monitor +Keeps running and log the changes to the metadata. + +\par -d | \--delete +Delete all metadata for *id* or for the specified *key* of object *id*. +Without any option, all metadata is removed. + +\par -n | \--name +Metadata name (Default: "default"). + +# EXAMPLES + +**pw-metadata** + +Show metadata in default name. + +**pw-metadata** -n settings 0 + +Display settings. + +**pw-metadata** -n settings 0 clock.quantum 1024 + +Change clock.quantum to 1024. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-mon_1 "pw-mon(1)", +\ref page_man_pw-cli_1 "pw-cli(1)", diff --git a/doc/dox/programs/pw-mididump.1.md b/doc/dox/programs/pw-mididump.1.md new file mode 100644 index 00000000..83b229f2 --- /dev/null +++ b/doc/dox/programs/pw-mididump.1.md @@ -0,0 +1,38 @@ +\page page_man_pw-mididump_1 pw-mididump + +The PipeWire MIDI dump + +# SYNOPSIS + +**pw-mididump** \[*options*\] \[*FILE*\] + +# DESCRIPTION + +Dump MIDI messages to stdout. + +When a MIDI file is given, the events inside the file are printed. + +When no file is given, **pw-mididump** creates a PipeWire MIDI input +stream and will print all MIDI events received on the port to stdout. + +# OPTIONS + +\par -r | \--remote=NAME +The name the remote instance to monitor. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-cat_1 "pw-cat(1)" diff --git a/doc/dox/programs/pw-mon.1.md b/doc/dox/programs/pw-mon.1.md new file mode 100644 index 00000000..6d9b6392 --- /dev/null +++ b/doc/dox/programs/pw-mon.1.md @@ -0,0 +1,36 @@ +\page page_man_pw-mon_1 pw-mon + +The PipeWire monitor + +# SYNOPSIS + +**pw-mon** \[*options*\] + +# DESCRIPTION + +Monitor objects on the PipeWire instance. + +# OPTIONS + +\par -r | \--remote=NAME +The name the *remote* instance to monitor. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +\par -N | \--color=WHEN +Whether to use color, one of 'never', 'always', or 'auto'. The default +is 'auto'. **-N** is equivalent to **--color=never**. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)" diff --git a/doc/dox/programs/pw-profiler.1.md b/doc/dox/programs/pw-profiler.1.md new file mode 100644 index 00000000..a02e5a21 --- /dev/null +++ b/doc/dox/programs/pw-profiler.1.md @@ -0,0 +1,46 @@ +\page page_man_pw-profiler_1 pw-profiler + +The PipeWire profiler + +# SYNOPSIS + +**pw-profiler** \[*options*\] + +# DESCRIPTION + +Start profiling a PipeWire instance. + +If the server has the profiler module loaded, this program will connect +to it and log the profiler data. Profiler data contains times and +durations when processing nodes and devices started and completed. + +When this program is stopped, a set of **gnuplot** files and a script to +generate SVG files from the .plot files is generated, along with a .html +file to visualize the profiling results in a browser. + +This function uses the same data used by *pw-top*. + +# OPTIONS + +\par -r | \--remote=NAME +The name the remote instance to monitor. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -h | \--help +Show help. + +\par \--version +Show version information. + +\par -o | \--output=FILE +Profiler output name (default "profiler.log"). + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-top_1 "pw-top(1)" diff --git a/doc/dox/programs/pw-top.1.md b/doc/dox/programs/pw-top.1.md new file mode 100644 index 00000000..5460581b --- /dev/null +++ b/doc/dox/programs/pw-top.1.md @@ -0,0 +1,207 @@ +\page page_man_pw-top_1 pw-top + +The PipeWire process viewer + +# SYNOPSIS + +**pw-top** \[*options*\] + +# DESCRIPTION + +The *pw-top* program provides a dynamic real-time view of the pipewire +node and device statistics. + +A hierarchical view is shown of Driver nodes and follower nodes. The +Driver nodes are actively using a timer to schedule dataflow in the +followers. The followers of a driver node as shown below their driver +with a + sign in a tree-like representation. + +The columns presented are as follows: + +\par S +\parblock +Node status. + +- E = ERROR +- C = CREATING +- S = SUSPENDED +- I = IDLE +- R = RUNNING +\endparblock + +\par ID +The ID of the pipewire node/device, as found in *pw-dump* and +*pw-cli* + +\par QUANT +\parblock +The current quantum (for drivers) and the suggested quantum for +follower nodes. + +The quantum by itself needs to be divided by the RATE column to +calculate the duration of a scheduling period in fractions of a +second. + +For a QUANT of 1024 and a RATE of 48000, the duration of one period +in the graph is 1024/48000 or 21.3 milliseconds. + +Follower nodes can have a 0 QUANT field, which means that the node +does not have a suggestion for the quantum and thus uses what the +driver selected. + +The driver will use the lowest quantum of any of the followers. If +none of the followers select a quantum, the default quantum in the +pipewire configuration file will be used. + +The QUANT on the drivers usually translates directly into the number +of audio samples processed per processing cycle of the graph. + +See also +<https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained> +\endparblock + +\par RATE +\parblock +The current rate (for drivers) and the suggested rate for follower +nodes. + +This is the rate at which the *graph* processes data and needs to be +combined with the QUANT value to derive the duration of a processing +cycle in the graph. + +Some nodes can have a 0 RATE, which means that they don\'t have any +rate suggestion for the graph. Nodes that suggest a rate can make +the graph switch rates if the graph is otherwise idle and the new +rate is allowed as a possible graph rate (see the pipewire +configuration file). + +The RATE on (audio) driver nodes usually also translates directly to +the samplerate used by the device. Although some devices might not +be able to operate at the given samplerate, in which case resampling +will need to be done. The negotiated samplerate with the device and +stream can be found in the FORMAT column. + +\endparblock + +\par WAIT +\parblock +The waiting time of a node is the elapsed time between when the node +is ready to start processing and when it actually started +processing. + +For Driver nodes, this is the time between when the node wakes up to +start processing the graph and when the driver (and thus also the +graph) completes a cycle. The WAIT time for driver is thus the +elapsed time processing the graph. + +For follower nodes, it is the time spent between being woken up +(when all dependencies of the node are satisfied) and when +processing starts. The WAIT time for follower nodes is thus mostly +caused by context switching. + +A value of \-\-- means that the node was not signaled. A value of ++++ means that the node was signaled but not awake. +\endparblock + +\par BUSY +\parblock +The processing time is started when the node starts processing until +it completes and wakes up the next nodes in the graph. + +A value of \-\-- means that the node was not started. A value of +++ +means that the node was started but did not complete. +\endparblock + +\par W/Q +\parblock +Ratio of WAIT / QUANT. + +The W/Q time of the driver node is a good measure of the graph load. +The running averages of the driver W/Q ratios are used as the DSP +load in other (JACK) tools. + +Values of \-\-- and +++ are copied from the WAIT column. + +\endparblock + +\par B/Q +\parblock +Ratio of BUSY / QUANT + +This is a good measure of the load of a particular driver or +follower node. + +Values of \-\-- and +++ are copied from the BUSY column. +\endparblock + +\par ERR +\parblock +Total of Xruns and Errors + +Xruns for drivers are when the graph did not complete a cycle. This +can be because a node in the graph also has an Xrun. It can also be +caused when scheduling delays cause a deadline to be missed, causing +a hardware Xrun. + +Xruns for followers are incremented when the node started processing +but did not complete before the end of the graph cycle deadline. +\endparblock + +\par FORMAT +\parblock +The format used by the driver node or the stream. This is the +hardware format negotiated with the device or stream. + +If the stream of driver has a different rate than the graph, +resampling will be done. + +For raw audio formats, the layout is \<sampleformat\> \<channels\> +\<samplerate\>. + +For IEC958 passthrough audio formats, the layout is IEC958 \<codec\> +\<samplerate\>. + +For DSD formats, the layout is \<dsd-rate\> \<channels\>. + +For Video formats, the layout is \<pixelformat\> +\<width\>x\<height\>. +\endparblock + +\par NAME +\parblock +Name assigned to the device/node, as found in *pw-dump* node.name + +Names are prefixed by *+* when they are linked to a driver (entry +above with no +) +\endparblock + + +# OPTIONS + +\par -h | \--help +Show help. + +\par -b | \--batch-mode +Run in non-interactive batch mode, similar to top\'s batch mode. + +\par -n | \--iterations=NUMBER +Exit after NUMBER of batch iterations. Only used in batch mode. + +\par -r | \--remote=NAME +The name the *remote* instance to monitor. If left unspecified, a +connection is made to the default PipeWire instance. + +\par -V | \--version +Show version information. + +# AUTHORS + +The PipeWire Developers <$(PACKAGE_BUGREPORT)>; +PipeWire is available from <$(PACKAGE_URL)> + +# SEE ALSO + +\ref page_man_pipewire_1 "pipewire(1)", +\ref page_man_pw-dump_1 "pw-dump(1)", +\ref page_man_pw-cli_1 "pw-cli(1)", +\ref page_man_pw-profiler_1 "pw-profiler(1)" diff --git a/doc/dox/pulse-modules.dox b/doc/dox/pulse-modules.dox new file mode 100644 index 00000000..6ed4d343 --- /dev/null +++ b/doc/dox/pulse-modules.dox @@ -0,0 +1,149 @@ +/** \page page_pulse_modules Pulseaudio Modules + +PipeWire's Pulseaudio emulation implements several Pulseaudio modules. +It only supports its own built-in modules, and cannot load external +modules written for Pulseaudio. + +# Loading modules + +The built-in modules can be loaded using Pulseaudio client programs, +for example `pactl load-module <module-name> <module-options>`. They +can also added to `pipewire-pulse.conf`, typically by a drop-in file +in `~/.config/pipewire/pipewire-pulse.conf.d/` containing the module +name and its arguments +``` +pulse.cmd = [ + { cmd = "load-module" args = "module-null-sink sink_name=foo" flags = [ ] } +] +``` + +To list all modules currently loaded, with their arguments: +``` +pactl list modules +``` + +For a short list of loaded modules: +``` +pactl list modules short +``` + +Modules may be unloaded using either the module-name or index number: + +``` +pactl load-module <module-name> <parameters> +pactl unload-module <module-name|index#> +``` + +# Common module options + +Most modules that create streams/devices support the following properties: + +## sink_name, source_name + +Name for the sink (resp. source). Allowed characters in the name are a-z, A-Z, numbers, period (.) and underscore (_). The length must be 1-128 characters. + +## format + +The sample format. The supported audio formats are: + +### PCM + - u8: unsigned 8-bit integer + - aLaw: A-law encoded 8-bit integer + - uLaw: μ-law encoded 8-bit integer + - s16le: signed 16-bit little-endian integer + - s16be: signed 16-bit big-endian integer + - s16, s16ne: native-endian aliases for s16le or s16be + - s16re: reverse-endian alias for s16le or s16be + - float32le: 32-bit little-endian float + - float32be: 32-bit big-endian float + - float32, float32ne: native-endian aliases for float32le or float32be + - float32re: reverse-endian alias for float32le or float32be + - s32le: signed 32-bit little-endian integer + - s32be: signed 32-bit big-endian integer + - s32, s32ne: native-endian aliases for s32le or s32be + - s32re: reverse-endian alias for s32le or s32be + - s24le: signed 24-bit little-endian integer (note: ALSA calls this "S24_3LE") + - s24be: signed 24-bit big-endian integer (note: ALSA calls this "S24_3BE") + - s24, s24ne: native-endian aliases for s24le or s24be + - s24re: reverse-endian alias for s24le or s24be + - s24-32le: signed 24-bit little-endian integer, packed into a 32-bit integer so that the 8 most significant bits are ignored (note: ALSA calls this "S24_LE") + - s24-32be: signed 24-bit big-endian integer, packed into a 32-bit integer so that the 8 most significant bits are ignored (note: ALSA calls this "S24_BE") + - s24-32, s24-32ne: native-endian aliases for s24-32le or s24-32be + - s24-32re: reverse-endian alias for s24-32le or s24-32be + +### Compressed audio formats + +Below is a list of all supported compressed formats. The code at the beginning of each line is used whenever a textual identifier for a format is needed (for example in configuration files or on the command line). The formats whose identifier ends with -iec61937 have to be wrapped in IEC 61937 frames, which makes the compressed audio behave more like normal PCM audio. + + - ac3-iec61937: Dolby Digital (DD / AC-3 / A/52) + - eac3-iec61937: Dolby Digital Plus (DD+ / E-AC-3) + - mpeg-iec61937: MPEG-1 or MPEG-2 Part 3 (not MPEG-2 AAC) + - dts-iec61937: DTS + - mpeg2-aac-iec61937: MPEG-2 AAC (supported since PulseAudio 4.0) + - truehd-iec61937: Dolby TrueHD (added in PulseAudio 13.0, but doesn't work yet in practice) + - dtshd-iec61937: DTS-HD Master Audio (added in PulseAudio 13.0, but doesn't work yet in practice) + - pcm: PCM (not a compressed format, but listed here, because pcm is one of the recognized encoding identifiers) + - any: (special identifier for indicating that any encoding can be used) + + +## rate + +The sample rate. + +##channels + +Number of audio channels. + +## channel_map + +A channel map. A list of comma-separated channel names. The currently defined channel names are: +`left`, `right`, `mono`, `center`, `front-left`, `front-right`, `front-center`, +`rear-center`, `rear-left`, `rear-right`, `lfe`, `subwoofer`, `front-left-of-center`, +`front-right-of-center`, `side-left`, `side-right`, `aux0`, `aux1` to `aux15`, `top-center`, +`top-front-left`, `top-front-right`, `top-front-center`, `top-rear-left`, `top-rear-right`, +`top-rear-center` + +## sink_properties, source_properties + +Set additional properties of the sink/source. For example, you can set the description directly +when the module is loaded by setting this parameter. + +``` +load-module module-alsa-sink sink_name=headphones sink_properties=device.description=Headphones +``` + +# List of known built-in modules: + +- \subpage page_pulse_module_alsa_sink +- \subpage page_pulse_module_alsa_source +- \subpage page_pulse_module_always_sink +- \subpage page_pulse_module_combine_sink +- \subpage page_pulse_module_echo_cancel +- \subpage page_pulse_module_gsettings +- \subpage page_pulse_module_jackdbus_detect +- \subpage page_pulse_module_ladspa_sink +- \subpage page_pulse_module_ladspa_source +- \subpage page_pulse_module_loopback +- \subpage page_pulse_module_native_protocol_tcp +- \subpage page_pulse_module_null_sink +- \subpage page_pulse_module_pipe_sink +- \subpage page_pulse_module_pipe_source +- \subpage page_pulse_module_raop_discover +- \subpage page_pulse_module_remap_sink +- \subpage page_pulse_module_remap_source +- \subpage page_pulse_module_roc_sink +- \subpage page_pulse_module_roc_sink_input +- \subpage page_pulse_module_roc_source +- \subpage page_pulse_module_rtp_recv +- \subpage page_pulse_module_rtp_send +- \subpage page_pulse_module_simple_protocol_tcp +- \subpage page_pulse_module_switch_on_connect +- \subpage page_pulse_module_tunnel_sink +- \subpage page_pulse_module_tunnel_source +- \subpage page_pulse_module_virtual_sink +- \subpage page_pulse_module_virtual_source +- \subpage page_pulse_module_x11_bell +- \subpage page_pulse_module_zeroconf_discover +- \subpage page_pulse_module_zeroconf_publish + +*/ diff --git a/doc/tutorial.dox b/doc/dox/tutorial/index.dox similarity index 89% rename from doc/tutorial.dox rename to doc/dox/tutorial/index.dox index bc537256..a4fc4d57 100644 --- a/doc/tutorial.dox +++ b/doc/dox/tutorial/index.dox @@ -1,6 +1,6 @@ /** \page page_tutorial Tutorial -Welcome to the PipeWire tutorial. The goal is to learn +Welcome to the PipeWire API tutorial. The goal is to learn PipeWire API step-by-step with simple short examples. - \subpage page_tutorial1 diff --git a/doc/tutorial1.dox b/doc/dox/tutorial/tutorial1.dox similarity index 100% rename from doc/tutorial1.dox rename to doc/dox/tutorial/tutorial1.dox diff --git a/doc/tutorial2.dox b/doc/dox/tutorial/tutorial2.dox similarity index 100% rename from doc/tutorial2.dox rename to doc/dox/tutorial/tutorial2.dox diff --git a/doc/tutorial3.dox b/doc/dox/tutorial/tutorial3.dox similarity index 100% rename from doc/tutorial3.dox rename to doc/dox/tutorial/tutorial3.dox diff --git a/doc/tutorial4.dox b/doc/dox/tutorial/tutorial4.dox similarity index 100% rename from doc/tutorial4.dox rename to doc/dox/tutorial/tutorial4.dox diff --git a/doc/tutorial5.dox b/doc/dox/tutorial/tutorial5.dox similarity index 100% rename from doc/tutorial5.dox rename to doc/dox/tutorial/tutorial5.dox diff --git a/doc/tutorial6.dox b/doc/dox/tutorial/tutorial6.dox similarity index 100% rename from doc/tutorial6.dox rename to doc/dox/tutorial/tutorial6.dox diff --git a/doc/tutorial1.c b/doc/examples/tutorial1.c similarity index 100% rename from doc/tutorial1.c rename to doc/examples/tutorial1.c diff --git a/doc/tutorial2.c b/doc/examples/tutorial2.c similarity index 100% rename from doc/tutorial2.c rename to doc/examples/tutorial2.c diff --git a/doc/tutorial3.c b/doc/examples/tutorial3.c similarity index 100% rename from doc/tutorial3.c rename to doc/examples/tutorial3.c diff --git a/doc/tutorial4.c b/doc/examples/tutorial4.c similarity index 97% rename from doc/tutorial4.c rename to doc/examples/tutorial4.c index 67cb0c8d..bc445868 100644 --- a/doc/tutorial4.c +++ b/doc/examples/tutorial4.c @@ -42,6 +42,8 @@ static void on_process(void *userdata) stride = sizeof(int16_t) * DEFAULT_CHANNELS; n_frames = buf->datas[0].maxsize / stride; + if (b->requested) + n_frames = SPA_MIN(b->requested, n_frames); for (i = 0; i < n_frames; i++) { data->accumulator += M_PI_M2 * 440 / DEFAULT_RATE; diff --git a/doc/tutorial5.c b/doc/examples/tutorial5.c similarity index 100% rename from doc/tutorial5.c rename to doc/examples/tutorial5.c diff --git a/doc/tutorial6.c b/doc/examples/tutorial6.c similarity index 100% rename from doc/tutorial6.c rename to doc/examples/tutorial6.c diff --git a/doc/input-filter.py b/doc/input-filter.py new file mode 100755 index 00000000..2b64a2d6 --- /dev/null +++ b/doc/input-filter.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; eval: (blacken-mode); -*- +r""" +Doxygen input filter that: + +- adds \privatesection to all files +- removes macros +- parses pulse_module_options and substitutes it into @pulse_module_options@ + +This is used for .c files, and causes Doxygen to not include +any symbols from them, unless they also appeared in a header file. + +The Pulse module option parsing is used in documentation of Pulseaudio modules. +""" +import sys +import re +import os + + +def main(): + fn = sys.argv[1] + with open(fn, "r") as f: + text = f.read() + + text = re.sub("#define.*", "", text) + + if "@pulse_module_options@" in text: + m = re.search( + r"static const char[* ]*const pulse_module_options\s+=\s+(.*?\")\s*;\s*$", + text, + re.M | re.S, + ) + if m: + res = [] + for line in m.group(1).splitlines(): + m = re.match(r"\s*\"\s*([a-z0-9_]+)\s*=\s*(.*)\"\s*$", line) + if m: + name = m.group(1) + value = m.group(2).strip().strip("<>") + res.append(f"- `{name}`: {value}") + + res = "\n * ".join(res) + text = text.replace("@pulse_module_options@", res) + + if os.path.basename(fn).startswith("module-") and fn.endswith(".c"): + text = re.sub(r"^ \* ##", r" * #", text, flags=re.M) + + print("/** \\privatesection */") + print(text) + + +if __name__ == "__main__": + main() diff --git a/doc/input-filter.sh b/doc/input-filter.sh deleted file mode 100755 index 8c71befa..00000000 --- a/doc/input-filter.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# -# Doxygen input filter that adds \privatesection to all files, -# and removes macros. -# -# This is used for .c files, and causes Doxygen to not include -# any symbols from them, unless they also appeared in a header file. -# -echo -n "/** \privatesection */ " -sed -e 's/#define.*//' < "$1" diff --git a/doc/man-fixup.py b/doc/man-fixup.py new file mode 100755 index 00000000..758aa855 --- /dev/null +++ b/doc/man-fixup.py @@ -0,0 +1,97 @@ +#!/usr/bin/python3 +# -*- mode: python; coding: utf-8; eval: (blacken-mode); -*- +r""" +Fetch right Doxygen man file, replace dummy parts, and fixup nroff +""" +import argparse +import re +import sys +from subprocess import call +from pathlib import Path + + +def main(): + p = argparse.ArgumentParser(description=__doc__.strip()) + p.add_argument("htmldir", type=Path) + p.add_argument("page") + p.add_argument("name") + p.add_argument("section") + p.add_argument("version") + args = p.parse_args() + + page, name, section, version = args.page, args.name, args.section, args.version + + mandir = args.htmldir / ".." / "man" / "man3" + fn = mandir / f"{page}.3" + + # Doxygen < 1.9.7 names .md file output differently... + if not fn.exists(): + page2 = page.replace("page_man_", "md_doc_dox_programs_").replace("-", "_") + fn = mandir / f"{page2}.3" + else: + page2 = None + + try: + with open(fn, "r") as f: + text = f.read() + except: + print(f"ERROR: man file {fn} missing!", file=sys.stderr) + call(["ls", "-R", str(args.htmldir / ".." / "man")], stdout=sys.stderr) + raise + + text = text.replace(page, name) + if page2 is not None: + text = text.replace(page2, name) + + # Replace bad nroff header + text = re.sub( + r"^(\.TH[^\n]*)\n", + rf'.TH "{name}" {section} "{version}" "PipeWire" \\" -*- nroff -*-\n', + text, + ) + + # Fixup name field (can't be done in Doxygen, otherwise HTML looks bac) + text = re.sub( + rf"^\.SH NAME\s*\n{name} \\- {name}\s*\n\.PP\n *", + rf".SH NAME\n{name} \\- ", + text, + count=1, + flags=re.M, + ) + + # Add DESCRIPTION section if missing and NAME field has extra stuff + if not re.search(r"^\.SH DESCRIPTION\s*\n", text): + text = re.sub( + r"^(.SH NAME\s*\n[^\.].*\n)\.PP\s*\n([^\.\n ]+)", + r"\1.SH DESCRIPTION\n.PP\n\2", + text, + count=1, + flags=re.M, + ) + + # Upcase titles + def upcase(m): + return m.group(0).upper() + + text = re.sub(r"^\.SH .*?$", upcase, text, flags=re.M) + + # Replace PW_KEY_*, SPA_KEY_* by their values + def pw_key(m): + key = m.group(0) + key = key.replace("PW_KEY_", "").lower().replace("_", ".") + if key in ("protocol", "access", "client.access") or key.startswith("sec."): + return f"pipewire.{key}" + return key + + def spa_key(m): + key = m.group(0) + return key.replace("SPA_KEY_", "").lower().replace("_", ".") + + text = re.sub(r"PW_KEY_[A-Z_]+", pw_key, text, flags=re.S) + text = re.sub(r"SPA_KEY_[A-Z_]+", spa_key, text, flags=re.S) + + print(text) + + +if __name__ == "__main__": + main() diff --git a/doc/manpage.dox.in b/doc/manpage.dox.in deleted file mode 100644 index 9e6df789..00000000 --- a/doc/manpage.dox.in +++ /dev/null @@ -1,5 +0,0 @@ -/** \page @pagename@ @title@ - -\verbinclude @filename@ - -*/ diff --git a/doc/meson.build b/doc/meson.build index f23248fe..3edaeb9f 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -1,3 +1,5 @@ +fs = import('fs') + doxyfile_conf = configuration_data() doxyfile_conf.set('PACKAGE_NAME', meson.project_name()) doxyfile_conf.set('PACKAGE_VERSION', meson.project_version()) @@ -5,6 +7,14 @@ doxyfile_conf.set('top_srcdir', meson.project_source_root()) doxyfile_conf.set('top_builddir', meson.project_build_root()) doxyfile_conf.set('output_directory', meson.current_build_dir()) +doxygen_env = environment() +doxygen_env.set('PACKAGE_NAME', meson.project_name()) +doxygen_env.set('PACKAGE_VERSION', meson.project_version()) +doxygen_env.set('PACKAGE_URL', 'https://pipewire.org') +doxygen_env.set('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/pipewire/pipewire/issues') +doxygen_env.set('PIPEWIRE_CONFIG_DIR', pipewire_configdir) +doxygen_env.set('PIPEWIRE_CONFDATADIR', pipewire_confdatadir) + dot_found = find_program('dot', required: false).found() summary({'dot (used with doxygen)': dot_found}, bool_yn: true, section: 'Optional programs') if dot_found @@ -14,41 +24,74 @@ else endif # Note: order here is how doxygen will expose the pages in the sidebar -# api-tree.dox should be first to determine ordering of Modules. +# tree.dox should be first to determine the ordering. extra_docs = [ - 'api-tree.dox', - 'index.dox', - 'overview.dox', - 'pipewire.dox', - 'pipewire-design.dox', - 'pipewire-access.dox', - 'pipewire-midi.dox', - 'pipewire-portal.dox', - 'pipewire-daemon.dox', - 'pipewire-library.dox', - 'pipewire-modules.dox', - 'pipewire-session-manager.dox', - 'pipewire-objects-design.dox', - 'pipewire-audio.dox', - 'pipewire-scheduling.dox', - 'pipewire-protocol.dox', - 'tutorial.dox', - 'tutorial1.dox', - 'tutorial2.dox', - 'tutorial3.dox', - 'tutorial4.dox', - 'tutorial5.dox', - 'tutorial6.dox', - 'api.dox', - 'spa-index.dox', - 'spa-plugins.dox', - 'spa-design.dox', - 'spa-pod.dox', - 'spa-buffer.dox', - 'pulseaudio.dox', - 'dma-buf.dox', + 'tree.dox', + 'dox/index.dox', + 'dox/overview.dox', + 'dox/modules.dox', + 'dox/pulse-modules.dox', + 'dox/programs/index.md', + 'dox/internals/index.dox', + 'dox/internals/design.dox', + 'dox/internals/access.dox', + 'dox/internals/midi.dox', + 'dox/internals/portal.dox', + 'dox/internals/daemon.dox', + 'dox/internals/library.dox', + 'dox/internals/session-manager.dox', + 'dox/internals/objects.dox', + 'dox/internals/audio.dox', + 'dox/internals/scheduling.dox', + 'dox/internals/protocol.dox', + 'dox/internals/pulseaudio.dox', + 'dox/internals/dma-buf.dox', + 'dox/tutorial/index.dox', + 'dox/tutorial/tutorial1.dox', + 'dox/tutorial/tutorial2.dox', + 'dox/tutorial/tutorial3.dox', + 'dox/tutorial/tutorial4.dox', + 'dox/tutorial/tutorial5.dox', + 'dox/tutorial/tutorial6.dox', + 'dox/api/index.dox', + 'dox/api/spa-index.dox', + 'dox/api/spa-plugins.dox', + 'dox/api/spa-design.dox', + 'dox/api/spa-pod.dox', + 'dox/api/spa-buffer.dox', +] + +manpage_docs = [ + 'dox/programs/libpipewire-modules.7.md', + 'dox/programs/pipewire-pulse-modules.7.md', + 'dox/programs/pipewire-pulse.1.md', + 'dox/programs/pipewire-pulse.conf.5.md', + 'dox/programs/pipewire.1.md', + 'dox/programs/pipewire.conf.5.md', + 'dox/programs/pw-cat.1.md', + 'dox/programs/pw-cli.1.md', + 'dox/programs/pw-config.1.md', + 'dox/programs/pw-dot.1.md', + 'dox/programs/pw-dump.1.md', + 'dox/programs/pw-jack.1.md', + 'dox/programs/pw-link.1.md', + 'dox/programs/pw-loopback.1.md', + 'dox/programs/pw-metadata.1.md', + 'dox/programs/pw-mididump.1.md', + 'dox/programs/pw-mon.1.md', + 'dox/programs/pw-profiler.1.md', + 'dox/programs/pw-top.1.md', ] +manpages = [] + +foreach m : manpage_docs + name = fs.stem(fs.name(m)) + pagepart = name.replace('.', '_') + manpages += [[name, f'page_man_@pagepart@']] + extra_docs += m +endforeach + inputs = [] foreach extra : extra_docs inputs += meson.project_source_root() / 'doc' / extra @@ -68,7 +111,9 @@ endforeach foreach h : module_sources inputs += meson.project_source_root() / 'src' / 'modules' / h endforeach -inputs += meson.project_source_root() / 'test' / 'pwtest.h' +foreach h : pipewire_module_protocol_pulse_sources + inputs += meson.project_source_root() / 'src' / 'modules' / h +endforeach input_dirs = [ meson.project_source_root() / 'spa' / 'include' / 'spa' ] path_prefixes = [ @@ -117,35 +162,38 @@ examples_dox = configure_file(input: 'examples.dox.in', input_dirs += [ 'doc/examples.dox' ] -man_doxygen = [] -man_subpages = [] -foreach m : manpages - manconf = configuration_data() - pagename = 'page_man_' + m.split('.rst.in').get(0).replace('.', '_').replace('-', '_') - filename = m.split('.rst.in').get(0) + '.dox' - manconf.set('pagename', pagename) - manconf.set('title', m.split('.rst.in').get(0).replace('.1','').replace('.5','')) - manconf.set('filename', meson.project_source_root() / 'man' / m) - manfile = configure_file(input: 'manpage.dox.in', - output: filename, - configuration: manconf) - man_doxygen += [manfile] - man_subpages += ['- \subpage ' + pagename] - input_dirs += [ 'doc/' + filename ] +module_manpage_list = [] +foreach m : module_sources + name = fs.stem(m) + pagepart = name.replace('-', '_') + module_manpage_list += f'\\ref page_@pagepart@ "libpipewire-@name@(7)"' + manpages += [[f'libpipewire-@name@.7', f'page_@pagepart@']] endforeach -pw_tools_dox_conf = configuration_data() -pw_tools_dox_conf.set('man_subpages', '\n'.join(man_subpages)) -pw_tools_dox = configure_file(input: 'pipewire-tools.dox.in', - output: 'pipewire-tools.dox', - configuration: pw_tools_dox_conf) -input_dirs += [ 'doc/pipewire-tools.dox' ] +doxygen_env.set('LIBPIPEWIRE_MODULES', '<ul><li>' + '</li><li>'.join(module_manpage_list) + '</li></ul>') + +pulse_module_manpage_list = [] +foreach m : pipewire_module_protocol_pulse_sources + name = fs.stem(fs.name(m)) + if m.contains('/modules/') and name.startswith('module-') + pagepart = name.replace('-', '_') + pulse_module_manpage_list += f'\\ref page_pulse_@pagepart@ "pipewire-pulse-@name@(7)"' + manpages += [[f'pipewire-pulse-@name@.7', f'page_pulse_@pagepart@']] + endif +endforeach + +doxygen_env.set('PIPEWIRE_PULSE_MODULES', '<ul><li>' + '</li><li>'.join(pulse_module_manpage_list) + '</li></ul>') + +doxygen_layout = meson.project_source_root() / 'doc' / 'DoxygenLayout.xml' +doxygen_filter_c = meson.project_source_root() / 'doc' / 'input-filter.py' +doxygen_filter_h = meson.project_source_root() / 'doc' / 'input-filter-h.sh' doxyfile_conf.set('inputs', ' '.join(inputs + input_dirs)) doxyfile_conf.set('cssfiles', ' '.join(cssfiles)) +doxyfile_conf.set('layout', doxygen_layout) doxyfile_conf.set('path_prefixes', ' '.join(path_prefixes)) -doxyfile_conf.set('c_input_filter', meson.project_source_root() / 'doc' / 'input-filter.sh') -doxyfile_conf.set('h_input_filter', meson.project_source_root() / 'doc' / 'input-filter-h.sh') +doxyfile_conf.set('c_input_filter', doxygen_filter_c) +doxyfile_conf.set('h_input_filter', doxygen_filter_h) doxyfile = configure_file(input: 'Doxyfile.in', output: 'Doxyfile', @@ -157,8 +205,33 @@ if docdir == '' endif html_target = custom_target('pipewire-docs', - input: [ doxyfile, examples_dox, pw_tools_dox ] + inputs + cssfiles + man_doxygen, + input: [ doxyfile, doxygen_layout, examples_dox, doxygen_filter_c, doxygen_filter_h ] + inputs + cssfiles, output: [ 'html' ], command: [ doxygen, doxyfile ], - install: true, + env: doxygen_env, + install: install_docs, + install_tag: 'doc', install_dir: docdir) + + +man_fixup = files('man-fixup.py') + +manfiles = [] + +foreach m : manpages + file = m.get(0) + page = m.get(1) + name = fs.stem(file) + section = file.split('.').get(-1) + + manfiles += custom_target(file, + command : [ python, man_fixup, '@INPUT@', page, name, section, meson.project_version() ], + output : file, + input : html_target, + depend_files : [ man_fixup ], + capture : true, + install : install_man, + install_tag: 'man', + install_dir : get_option('mandir') / 'man' + section + ) +endforeach diff --git a/doc/pipewire-architecture.dox b/doc/pipewire-architecture.dox deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/pipewire-tools.dox.in b/doc/pipewire-tools.dox.in deleted file mode 100644 index e0bf1168..00000000 --- a/doc/pipewire-tools.dox.in +++ /dev/null @@ -1,7 +0,0 @@ -/** \page page_tools PipeWire Tools - -Manual pages: - -@man_subpages@ - -*/ diff --git a/doc/api-tree.dox b/doc/tree.dox similarity index 91% rename from doc/api-tree.dox rename to doc/tree.dox index 0c5c8fe4..3e358825 100644 --- a/doc/api-tree.dox +++ b/doc/tree.dox @@ -1,5 +1,7 @@ /** +This determines the ordering of items in Doxygen sidebar. + \defgroup api_pw_core Core API \brief PipeWire Core API \{ @@ -89,6 +91,7 @@ Utility data structures, macros, etc. \addtogroup spa_hooks \addtogroup spa_interfaces \addtogroup spa_json +\addtogroup spa_json_pod \addtogroup spa_keys \addtogroup spa_names \addtogroup spa_result @@ -119,8 +122,12 @@ Support interfaces provided by host \{ \} -\defgroup pwtest Test Suite -\{ -\} +\page page_overview +\page page_programs +\page page_modules +\page page_pulse_modules +\page page_internals +\page page_api +\page page_tutorial */ diff --git a/man/meson.build b/man/meson.build deleted file mode 100644 index e64b976b..00000000 --- a/man/meson.build +++ /dev/null @@ -1,45 +0,0 @@ -manpage_conf = configuration_data() -manpage_conf.set('PACKAGE_NAME', meson.project_name()) -manpage_conf.set('PACKAGE_VERSION', meson.project_version()) -manpage_conf.set('PACKAGE_URL', 'https://pipewire.org') -manpage_conf.set('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/pipewire/pipewire/issues') -manpage_conf.set('PIPEWIRE_CONFIG_DIR', pipewire_configdir) -manpage_conf.set('PIPEWIRE_CONFDATADIR', pipewire_confdatadir) - -manpages = [ - 'pipewire.1.rst.in', - 'pipewire-pulse.1.rst.in', - 'pipewire.conf.5.rst.in', - 'pw-cat.1.rst.in', - 'pw-cli.1.rst.in', - 'pw-config.1.rst.in', - 'pw-dot.1.rst.in', - 'pw-link.1.rst.in', - 'pw-metadata.1.rst.in', - 'pw-mididump.1.rst.in', - 'pw-mon.1.rst.in', - 'pw-profiler.1.rst.in', - 'pw-top.1.rst.in', -] - -if get_option('pipewire-jack').allowed() - manpages += 'pw-jack.1.rst.in' -endif - -if not generate_manpages - subdir_done() -endif - -foreach m : manpages - file = m.split('.rst.in').get(0) - rst = configure_file(input : m, - output : file + '.rst', - configuration : manpage_conf) - section = file.split('.').get(-1) - custom_target(file + '.target', - output : file, - input : rst, - command : [rst2man, '@INPUT@', '@OUTPUT@'], - install : true, - install_dir : get_option('mandir') / 'man' + section) -endforeach diff --git a/man/pipewire-pulse.1.rst.in b/man/pipewire-pulse.1.rst.in deleted file mode 100644 index 4695ec0b..00000000 --- a/man/pipewire-pulse.1.rst.in +++ /dev/null @@ -1,48 +0,0 @@ -pipewire-pulse -############## - ------------------------------------ -The PipeWire PulseAudio replacement ------------------------------------ - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pipewire-pulse** [*options*] - -DESCRIPTION -=========== - -**pipewire-pulse** starts a PulseAudio-compatible daemon that integrates with -the PipeWire media server, by running a pipewire process through a systemd -service. This daemon is a drop-in replacement for the PulseAudio daemon. - -OPTIONS -======= - --h | --help - Show help. - --v | --verbose - Increase the verbosity by one level. This option may be specified multiple - times. - ---version - Show version information. - --c | --config=FILE - Load the given config file (Default: pipewire-pulse.conf). - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; -PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)`` diff --git a/man/pipewire.1.rst.in b/man/pipewire.1.rst.in deleted file mode 100644 index 8b638396..00000000 --- a/man/pipewire.1.rst.in +++ /dev/null @@ -1,54 +0,0 @@ -pipewire -######## - -------------------------- -The PipeWire media server -------------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pipewire** [*options*] - -DESCRIPTION -=========== - -PipeWire is a service that facilitates sharing of multimedia content -between devices and applications. - -The **pipewire** daemon reads a config file that is further documented in -``pipewire.conf(5)`` manual page. - -OPTIONS -======= - --h | --help - Show help. - --v | --verbose - Increase the verbosity by one level. This option may be specified multiple - times. - ---version - Show version information. - --c | --config=FILE - Load the given config file (Default: pipewire.conf). - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; -PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pw-top(1)``, -``pw-dump(1)``, -``pw-mon(1)``, -``pw-cat(1)``, -``pw-cli(1)``, diff --git a/man/pipewire.conf.5.rst.in b/man/pipewire.conf.5.rst.in deleted file mode 100644 index fb8144bd..00000000 --- a/man/pipewire.conf.5.rst.in +++ /dev/null @@ -1,112 +0,0 @@ -pipewire.conf -############# - --------------------------------------- -The PipeWire server configuration file --------------------------------------- - -:Manual section: 5 -:Manual group: File Formats Manual - -.. _synopsis: - -SYNOPSIS -======== - -*$XDG_CONFIG_HOME/pipewire/pipewire.conf* - -*@PIPEWIRE_CONFIG_DIR@/pipewire.conf* - -*@PIPEWIRE_CONFDATADIR@/pipewire.conf* - -*@PIPEWIRE_CONFDATADIR@/pipewire.conf.d/* - -*@PIPEWIRE_CONFIG_DIR@/pipewire.conf.d/* - -*$XDG_CONFIG_HOME/pipewire/pipewire.conf.d/* - -DESCRIPTION -=========== - -PipeWire is a service that facilitates sharing of multimedia content -between devices and applications. - -On startup, the daemon reads a main configuration file to configure -itself. It executes a series of commands listed in the config -file. - -The config files are loaded in the order listed in the SYNOPSIS_. -The environment variables ``PIPEWIRE_CONFIG_DIR``, ``PIPEWIRE_CONFIG_PREFIX`` -and ``PIPEWIRE_CONFIG_NAME`` can be used to specify an alternative config -directory, subdirectory and file respectively. - -Next to the configuration file can be a directory with the same name as -the file with a ``.d/`` suffix. All directories in the SYNOPSIS_ directory -search paths are traversed in the listed order and the contents of the -``*.conf`` files inside them are appended to the main configuration file -as overrides. Object sections are merged and array sections are appended. - - -CONFIGURATION FILE FORMAT -========================= - -The configuration file format is grouped into sections. A section -is either a dictionary, {}, or an array, []. Dictionary and array -entries are separated by whitespace and may be simple value -assignment, an array or a dictionary. For example: - -name = value # simple assignment - -name = { key1 = value1 key2 = value2 } # a dictionary with two -entries - -name = [ value1 value2 ] # an array with two entries - -name = [ { k = v1 } { k = v2 } ] # an array of dictionaries - - -The configuration files can be expressed in full JSON syntax but for ease -of use, a relaxed format may be used where: - - * ``:`` to delimit keys and values can be substuted by ``=`` or a space. - * ``"`` around keys and string can be omitted as long as no special characters are used in the strings. - * ``,`` to separate objects can be replaced with a whitespace character. - * ``#`` can be used to start a comment until the line end - - -CONFIGURATION FILE SECTIONS -=========================== - -context.properties - Dictionary. These properties configure the PipeWire instance. - -context.spa-libs - Dictionary. Maps plugin features with globs to a spa library. - -context.modules - Array of dictionaries. Each entry in the array is a dictionary with the *name* of the module to load, - including optional *args* and *flags*. Most modules support being loaded - multiple times. - -context.objects - Array of dictionaries. Each entry in the array is a dictionary containing the *factory* to create an - object from and optional extra arguments specific to that factory. - -context.exec - Array of dictionaries. Each entry in the array is dictionary containing the *path* of a program to - execute on startup and optional *args*. - - This array used to contain an entry to start the session manager but this mode - of operation has since been demoted to development aid. Avoid starting a - session manager in this way in production environment. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-mon(1)``, diff --git a/man/pw-cat.1.rst.in b/man/pw-cat.1.rst.in deleted file mode 100644 index 87d4ab6c..00000000 --- a/man/pw-cat.1.rst.in +++ /dev/null @@ -1,176 +0,0 @@ -pw-cat -###### - ------------------------------------ -Play and record media with PipeWire ------------------------------------ - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-cat** [*options*] [*FILE* \| -] -| **pw-play** [*options*] [*FILE* \| -] -| **pw-record** [*options*] [*FILE* \| -] -| **pw-midiplay** [*options*] [*FILE* \| -] -| **pw-midirecord** [*options*] [*FILE* \| -] -| **pw-dsdplay** [*options*] [*FILE* \| -] - -DESCRIPTION -=========== - -**pw-cat** is a simple tool for playing back or -capturing raw or encoded media files on a PipeWire -server. It understands all audio file formats supported by -``libsndfile`` for PCM capture and playback. When capturing PCM, the filename -extension is used to guess the file format with the WAV file format as -the default. - -It understands standard MIDI files for playback and recording. This tool -will not render MIDI files, it will simply make the MIDI events available -to the graph. You need a MIDI renderer such as qsynth, timidity or a hardware -MIDI rendered to hear the MIDI. - -DSD playback is supported with the DSF file format. This tool will only work -with native DSD capable hardware and will produce an error when no such -hardware was found. - -When the *FILE* is - input and output will be raw data from STDIN and -STDOUT respectively. - -OPTIONS -======= - --h | --help - Show help. - ---version - Show version information. - --v | --verbose - Verbose operation. - --R | --remote=NAME - The name the *remote* instance to connect to. If left unspecified, - a connection is made to the default PipeWire instance. - --p | --playback - Playback mode. Read data from the specified file, and play it back. If the tool - is called under the name **pw-play** or **pw-midiplay** this is the default. - --r | --record - Recording mode. Capture data and write it to the specified file. If the tool is - called under the name **pw-record** or **pw-midirecord** this is the default. - --m | --midi - MIDI mode. *FILE* is a MIDI file. If the tool is called under the name - **pw-midiplay** or **pw-midirecord** this is the default. - Note that this program will *not* render the MIDI events into audible samples, - it will simply provide the MIDI events in the graph. You need a separate - MIDI renderer such as qsynth, timidity or a hardware renderer to hear the MIDI. - --d | --dsd - DSD mode. *FILE* is a DSF file. If the tool is called under the name - **pw-dsdplay** this is the default. - Note that this program will *not* render the DSD audio. You need a DSD capable - device to play DSD content or this program will exit with an error. - ---media-type=VALUE - Set the media type property (default Audio/Midi depending on mode). - The media type is used by the session manager to select a suitable target - to link to. - ---media-category=VALUE - Set the media category property (default Playback/Capture depending on mode). - The media type is used by the session manager to select a suitable target - to link to. - ---media-role=VALUE - Set the media role property (default Music). - The media type is used by the session manager to select a suitable target - to link to. - ---target=VALUE - Set a node target (default auto). The value can be: - - auto - Automatically select (Default) - - 0 - Don't try to link this node - - <id> - The object.serial or the node.name of a target node - ---latency=VALUE[*units*] - Set the node latency (default 100ms) - - The latency determines the minimum amount of time it takes - for a sample to travel from application to device (playback) and - from device to application (capture). - - The latency determines the size of the buffers that the - application will be able to fill. Lower latency means smaller - buffers but higher overhead. Higher latency means larger buffers - and lower overhead. - - Units can be **s** for seconds, **ms** for milliseconds, - **us** for microseconds, **ns** for nanoseconds. - If no units are given, the latency value is samples with the samplerate - of the file. - --P | --properties=VALUE - Set extra stream properties as a JSON object. - --q | --quality=VALUE - Resampler quality. When the samplerate of the source or - destination file does not match the samplerate of the server, the - data will be resampled. Higher quality uses more CPU. Values between 0 and 15 are - allowed, the default quality is 4. - ---rate=VALUE - The sample rate, default 48000. - ---channels=VALUE - The number of channels, default 2. - ---channel-map=VALUE - The channelmap. Possible values include: - **mono**, **stereo**, **surround-21**, - **quad**, **surround-22**, **surround-40**, - **surround-31**, **surround-41**, - **surround-50**, **surround-51**, - **surround-51r**, **surround-70**, - **surround-71** or a comma separated list of channel names: - **FL**, **FR**, **FC**, **LFE**, - **SL**, **SR**, **FLC**, **FRC**, - **RC**, **RL**, **RR**, **TC**, - **TFL**, **TFC**, **TFR**, **TRL**, - **TRC**, **TRR**, **RLC**, **RRC**, - **FLW**, **FRW**, **LFE2**, **FLH**, - **FCH**, **FRH**, **TFLC**, **TFRC**, - **TSL**, **TSR**, **LLFR**, **RLFE**, - **BC**, **BLC**, **BRC** - ---format=VALUE - The sample format to use. One of: - **u8**, **s8**, **s16** (default), **s24**, **s32**, - **f32**, **f64**. - ---volume=VALUE - The stream volume, default 1.000. - Depending on the locale you have configured, "," or "." may be - used as a decimal separator. Check with **locale** command. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``PipeWire(1)``, -``pw-mon(1)``, diff --git a/man/pw-cli.1.rst.in b/man/pw-cli.1.rst.in deleted file mode 100644 index 31a5acb9..00000000 --- a/man/pw-cli.1.rst.in +++ /dev/null @@ -1,195 +0,0 @@ -pw-cli -###### - ------------------------------------ -The PipeWire Command Line Interface ------------------------------------ - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-cli** [*command*] - -DESCRIPTION -=========== - -Interact with a PipeWire instance. - -When a command is given, **pw-cli** -will execute the command and exit - -When no command is given, **pw-cli** -starts an interactive session with the default PipeWire instance -*pipewire-0*. - -Connections to other, remote instances can be made. The current instance -name is displayed at the prompt. - -Note that **pw-cli** also creates a local PipeWire instance. Some commands -operate on the current (remote) instance and some on the local instance, such -as module loading. - -Use the 'help' command to list the available commands. - -GENERAL COMMANDS -================ - -help | h - Show a quick help on the commands available. It also lists the aliases - for many commands. - -quit | q - Exit from **pw-cli** - -MODULE MANAGEMENT -================= - -| Modules are loaded and unloaded in the local instance, thus the pw-cli -| binary itself and can add functionality or objects to the local -| instance. It is not possible in PipeWire to load modules in another -| instance. - -load-module *name* [*arguments...*] - Load a module specified by its name and arguments in the local instance. - For most modules it is OK to be loaded more than once. - - This command returns a module variable that can be used - to unload the module. - - The locally module is *not* visible in the remote instance. It is not - possible in PipeWire to load modules in a remote instance. - -unload-module *module-var* - Unload a module, specified either by its variable. - -OBJECT INTROSPECTION -==================== - -list-objects - List the objects of the current instance. - - Objects are listed with their *id*, *type* and *version*. - -info *id* | *all* - Get information about a specific object or *all* objects. - - Requesting info about an object will also notify you of changes. - -WORKING WITH REMOTES -==================== - -connect [*remote-name*] - Connect to a remote instance and make this the new current - instance. - - If no remote name is specified, a connection is made to - the default remote instance, usually *pipewire-0*. - - The special remote name called *internal* can be used to connect to - the local **pw-cli** PipeWire instance. - - This command returns a remote var that can be used to disconnect or - switch remotes. - -disconnect [*remote-var*] - Disconnect from a *remote instance*. - - If no remote name is specified, the current instance is disconnected. - -list-remotes - List all *remote instances*. - -switch-remote [*remote-var*] - Make the specified *remote* the current instance. - - If no remote name is specified, the first instance is made current. - -NODE MANAGEMENT -=============== - -create-node *factory-name* [*properties...*] - Create a node from a factory in the current instance. - - Properties are key=value pairs separated by whitespace. - - This command returns a *node variable*. - -export-node *node-id* [*remote-var*] - Export a node from the local instance to the specified instance. - When no instance is specified, the node will be exported to the current - instance. - -DEVICE MANAGEMENT -================= - -create-device *factory-name* [*properties...*] - Create a device from a factory in the current instance. - - Properties are key=value pairs separated by whitespace. - - This command returns a *device variable*. - - -LINK MANAGEMENT -=============== - -create-link *node-id* *port-id* *node-id* *port-id* [*properties...*] - Create a link between 2 nodes and ports. - - Port *ids* can be *-1* to automatically select an available port. - - Properties are key=value pairs separated by whitespace. - - This command returns a *link variable*. - -GLOBALS MANAGEMENT -================== - -destroy *object-id* - Destroy a global object. - - -PARAMETER MANAGEMENT -==================== - -enum-params *object-id* *param-id* - Enumerate params of an object. - - *param-id* can also be given as the param short name. - -set-param *object-id* *param-id* *param-json* - Set param of an object. - - *param-id* can also be given as the param short name. - -PERMISSION MANAGEMENT -===================== - -permissions *client-id* *object-id* *permission* - Set permissions for a client. - - *object-id* can be *-1* to set the default permissions. - -get-permissions *client-id* - Get permissions of a client. - - -COMMAND MANAGEMENT -================== - -send-command *object-id* - Send a command to an object. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-mon(1)``, diff --git a/man/pw-config.1.rst.in b/man/pw-config.1.rst.in deleted file mode 100644 index a3df2dbf..00000000 --- a/man/pw-config.1.rst.in +++ /dev/null @@ -1,110 +0,0 @@ -pw-config -######### - ------------------------------ -Debug PipeWire Config parsing ------------------------------ - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-config** [*options*] paths - -| **pw-config** [*options*] list [*SECTION*] - -| **pw-config** [*options*] merge *SECTION* - -DESCRIPTION -=========== - -List config paths and config sections and display the parsed -output. - -This tool can be used to get an overview of the config file that will be -parsed by the PipeWire server and clients. - -COMMON OPTIONS -============== - --h | --help - Show help. - ---version - Show version information. - --n | --name=NAME - Config Name (default 'pipewire.conf') - --p | --prefix=PREFIX - Config Prefix (default '') - --L | --no-newline - Omit newlines after values - --r | --recurse - Reformat config sections recursively - --N | --no-colors - Disable color output - --C | --color[=WHEN] - whether to enable color support. WHEN is `never`, `always`, or `auto` - -LISTING PATHS -============= - -Specify the paths command. It will display all the config files that will -be parsed and in what order. - -LISTING CONFIG SECTIONS -======================= - -Specify the list command with an optional *SECTION* to list the configuration -fragments used for *SECTION*. Without a *SECTION*, all sections will be -listed. - -Use the -r options to reformat the sections. - -MERGING A CONFIG SECTION -======================== - -With the merge option and a *SECTION*, pw-config will merge all config files into -a merged config section and dump the results. This will be the section used by -the client or server. - -Use the -r options to reformat the sections. - -EXAMPLES -======== - -**pw-config** - List all config files that will be used - -**pw-config** -n pipewire-pulse.conf - List all config files that will be used by the PipeWire pulseaudio server. - -**pw-config** -n pipewire-pulse.conf list - List all config sections used by the PipeWire pulseaudio server - -**pw-config** -n jack.conf list context.properties - List the context.properties fragments used by the JACK clients - -**pw-config** -n jack.conf merge context.properties - List the merged context.properties used by the JACK clients - -**pw-config** -n pipewire.conf -r merge context.modules - List the merged context.modules used by the PipeWire server and reformat - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-dump(1)``, diff --git a/man/pw-dot.1.rst.in b/man/pw-dot.1.rst.in deleted file mode 100644 index 45995398..00000000 --- a/man/pw-dot.1.rst.in +++ /dev/null @@ -1,65 +0,0 @@ -pw-dot -###### - ---------------------------- -The PipeWire dot graph dump ---------------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-dot** [*options*] - -DESCRIPTION -=========== - -Create a .dot file of the PipeWire graph. - -The .dot file can then be visualized with a tool like **dotty** -or rendered to a PNG file with ``dot -Tpng pw.dot -o pw.png``. - -OPTIONS -======= - --r | --remote=NAME - The name the remote instance to connect to. If left unspecified, - a connection is made to the default PipeWire instance. - --h | --help - Show help. - ---version - Show version information. - --a | --all - Show all object types. - --s | --smart - Show linked objects only. - --d | --detail - Show all object properties. - --o FILE | --output=FILE - Output file name (Default pw.dot). Use - for stdout. - --L | --lr - Lay the graph from left to right, instead of dot's default top to bottom. - --9 | --90 - Lay the graph using 90-degree angles in edges. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-cli(1)``, -``pw-mon(1)``, diff --git a/man/pw-jack.1.rst.in b/man/pw-jack.1.rst.in deleted file mode 100644 index 0781b3d7..00000000 --- a/man/pw-jack.1.rst.in +++ /dev/null @@ -1,65 +0,0 @@ -pw-jack -####### - ----------------------------- -Use PipeWire instead of JACK ----------------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-jack** [*options*] *COMMAND* [*FILE*] - -DESCRIPTION -=========== - -**pw-jack** modifies the ``LD_LIBRARY_PATH`` environment -variable so that applications will load PipeWire's reimplementation -of the JACK client libraries instead of JACK's own -libraries. This results in JACK clients being redirected to -PipeWire. - -If PipeWire's reimplementation of the JACK client libraries -has been installed as a system-wide replacement for JACK's -own libraries, then the whole system already behaves in that way, -in which case **pw-jack** has no practical effect. - -OPTIONS -======= - --h - Show help. - --r NAME - The name of the remote instance to connect to. If left - unspecified, a connection is made to the default PipeWire - instance. - --v - Verbose operation. - -EXAMPLES -======== - -| **pw-jack** sndfile-jackplay /usr/share/sounds/freedesktop/stereo/bell.oga - -NOTES -===== - -Using PipeWire for audio is currently considered to be -experimental. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; -PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``jackd(1)``, diff --git a/man/pw-link.1.rst.in b/man/pw-link.1.rst.in deleted file mode 100644 index 7d1001c5..00000000 --- a/man/pw-link.1.rst.in +++ /dev/null @@ -1,139 +0,0 @@ -pw-link -####### - -------------------------- -The PipeWire Link Command -------------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-link** [*options*] -o|-i|-l [*out-pattern*] [*in-pattern*] - -| **pw-link** [*options*] *output* *input* - -| **pw-link** [*options*] -d *output* *input* - -| **pw-link** [*options*] -d *link-id* - -DESCRIPTION -=========== - -List, create and destroy links between PipeWire ports. - -COMMON OPTIONS -============== - --r | --remote=NAME - The name the *remote* instance to monitor. If left unspecified, - a connection is made to the default PipeWire instance. - --h | --help - Show help. - ---version - Show version information. - -LISTING PORTS AND LINKS -======================= - -Specify one of -o, -i or -l to list the matching optional input and -output ports and their links. - --o | --output - List output ports - --i | --input - List output ports - --l | --links - List links - --m | --monitor - Monitor links and ports. **pw-link** will not exit but monitor and - print new and destroyed ports or links. - --I | --id - List IDs. Also list the unique link and port ids. - --v | --verbose - Verbose port properties. Also list the port-object-path and the port-alias. - -CONNECTING PORTS -================ - -Without any list option (-i, -o or -l), the given ports will be linked. -Valid port specifications are: - -*port-id* - As obtained with the -I option when listing ports. - -*node-name:port-name* - As obtained when listing ports. - -*port-object-path* - As obtained from the first alternative name for the port when listing - them with the -v option. - -*port-alias* - As obtained from the second alternative name for the ports when listing - them with the -v option. - -Extra options when linking can be given: - --L | --linger - Linger. Will create a link that exists after **pw-link** is destroyed. - This is the default behaviour, unless the -m option is given. - --P | --passive - Passive link. A passive link will keep both nodes it links inactive - unless another non-passive link is activating the nodes. You can use this - to link a sink to a filter and have them both suspended when nothing else - is linked to either of them. - --p | --props=PROPS - Properties as JSON object. Give extra properties when creaing the link. - -DISCONNECTING PORTS -=================== - -When the -d option is given, an existing link between port is destroyed. - -To disconnect port, a single *link-id*, as obtained when listing links with -the -I option, or two port specifications can be given. See the connecting -ports section for valid port specifications. - --d | --disconnect - Disconnect ports - -EXAMPLES -======== - -**pw-link** -iol - List all port and their links. - -**pw-link** -lm - List all links and monitor changes until **pw-link** is stopped. - -**pw-link** paplay:output_FL alsa_output.pci-0000_00_1b.0.analog-stereo:playback_FL - Link the given output port to the input port. - -**pw-link** -lI - List links and their Id. - -**pw-link** -d 89 - Destroy the link with id 89. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-cli(1)``, diff --git a/man/pw-metadata.1.rst.in b/man/pw-metadata.1.rst.in deleted file mode 100644 index 32f9c511..00000000 --- a/man/pw-metadata.1.rst.in +++ /dev/null @@ -1,82 +0,0 @@ -pw-metadata -########### - ---------------------- -The PipeWire metadata ---------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-metadata** [*options*] [*id* [*key* [*value* [*type* ] ] ] ] - -DESCRIPTION -=========== - -Monitor, set and delete metadata on PipeWire objects. - -Metadata are key/type/value triplets attached to objects identified -by *id*. The metadata is shared between all applications -binding to the same metadata object. When an object is destroyed, all its -metadata is automatically removed. - -When no *value* is given, **pw-metadata** will query and -log the metadata matching the optional arguments *id* -and *key*. Without any arguments, all metadata is displayed. - -When *value* is given, **pw-metadata** will set the -metadata for *id* and *key* to *value* and -an optional *type*. - -OPTIONS -======= - --r | --remote=NAME - The name the remote instance to use. If left unspecified, - a connection is made to the default PipeWire instance. - --h | --help - Show help. - ---version - Show version information. - --l | --list - List available metadata objects - --m | --monitor - Keeps running and log the changes to the metadata. - --d | --delete - Delete all metadata for *id* or for the specified *key* of object *id*. - Without any option, all metadata is removed. - --n | --name - Metadata name (Default: "default"). - -EXAMPLES -======== - -**pw-metadata** - Show metadata in default name. - -**pw-metadata** -n settings 0 - Display settings. - -**pw-metadata** -n settings 0 clock.quantum 1024 - Change clock.quantum to 1024. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-mon(1)``, -``pw-cli(1)``, diff --git a/man/pw-mididump.1.rst.in b/man/pw-mididump.1.rst.in deleted file mode 100644 index bb56ec67..00000000 --- a/man/pw-mididump.1.rst.in +++ /dev/null @@ -1,49 +0,0 @@ -pw-mididump -########### - ----------------------- -The PipeWire MIDI dump ----------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-mididump** [*options*] [*FILE*] - -DESCRIPTION -=========== - -Dump MIDI messages to stdout. - -When a MIDI file is given, the events inside the file are printed. - -When no file is given, **pw-mididump** creates a PipeWire -MIDI input stream and will print all MIDI events received on the port to -stdout. - -OPTIONS -======= - --r | --remote=NAME - The name the remote instance to monitor. If left unspecified, - a connection is made to the default PipeWire instance. - --h | --help - Show help. - ---version - Show version information. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-cat(1)``, diff --git a/man/pw-mon.1.rst.in b/man/pw-mon.1.rst.in deleted file mode 100644 index 775de0a4..00000000 --- a/man/pw-mon.1.rst.in +++ /dev/null @@ -1,46 +0,0 @@ -pw-mon -###### - --------------------- -The PipeWire monitor --------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-mon** [*options*] - -DESCRIPTION -=========== - -Monitor objects on the PipeWire instance. - -OPTIONS -======= - --r | --remote=NAME - The name the *remote* instance to monitor. If left unspecified, - a connection is made to the default PipeWire instance. - --h | --help - Show help. - ---version - Show version information. - --N | --color=WHEN - Whether to use color, one of 'never', 'always', or 'auto'. The - default is 'auto'. **-N** is equivalent to **--color=never**. - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, diff --git a/man/pw-profiler.1.rst.in b/man/pw-profiler.1.rst.in deleted file mode 100644 index 6fb57c8e..00000000 --- a/man/pw-profiler.1.rst.in +++ /dev/null @@ -1,57 +0,0 @@ -pw-profiler -########### - ---------------------- -The PipeWire profiler ---------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-profiler** [*options*] - -DESCRIPTION -=========== - -Start profiling a PipeWire instance. - -If the server has the profiler module loaded, this program will -connect to it and log the profiler data. Profiler data contains -times and durations when processing nodes and devices started and -completed. - -When this program is stopped, a set of **gnuplot** files and a script to generate -SVG files from the .plot files is generated, along with a .html file to -visualize the profiling results in a browser. - -This function uses the same data used by *pw-top*. - -OPTIONS -======= - --r | --remote=NAME - The name the remote instance to monitor. If left unspecified, - a connection is made to the default PipeWire instance. - --h | --help - Show help. - ---version - Show version information. - --o | --output=FILE - Profiler output name (default "profiler.log"). - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-top(1)``, diff --git a/man/pw-top.1.rst.in b/man/pw-top.1.rst.in deleted file mode 100644 index 0320a275..00000000 --- a/man/pw-top.1.rst.in +++ /dev/null @@ -1,184 +0,0 @@ -pw-top -###### - ---------------------------- -The PipeWire process viewer ---------------------------- - -:Manual section: 1 -:Manual group: General Commands Manual - -SYNOPSIS -======== - -| **pw-top** [*options*] - -DESCRIPTION -=========== - -The *pw-top* program provides a dynamic real-time view of the pipewire -node and device statistics. - -A hierarchical view is shown of Driver nodes and follower nodes. The Driver -nodes are actively using a timer to schedule dataflow in the followers. The -followers of a driver node as shown below their driver with a + sign in -a tree-like representation. - -The columns presented are as follows: - -S - Node status. - E = ERROR - C = CREATING - S = SUSPENDED - I = IDLE - R = RUNNING - -ID - The ID of the pipewire node/device, as found in *pw-dump* and *pw-cli* - -QUANT - The current quantum (for drivers) and the suggested quantum for follower - nodes. - - The quantum by itself needs to be divided by the RATE column to calculate - the duration of a scheduling period in fractions of a second. - - For a QUANT of 1024 and a RATE of 48000, the duration of one period in the - graph is 1024/48000 or 21.3 milliseconds. - - Follower nodes can have a 0 QUANT field, which means that the node does not - have a suggestion for the quantum and thus uses what the driver selected. - - The driver will use the lowest quantum of any of the followers. If none of - the followers select a quantum, the default quantum in the pipewire configuration - file will be used. - - The QUANT on the drivers usually translates directly into the number of audio - samples processed per processing cycle of the graph. - - See also https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained - -RATE - The current rate (for drivers) and the suggested rate for follower - nodes. - - This is the rate at which the *graph* processes data and needs to be combined with - the QUANT value to derive the duration of a processing cycle in the graph. - - Some nodes can have a 0 RATE, which means that they don't have any rate - suggestion for the graph. Nodes that suggest a rate can make the graph switch - rates if the graph is otherwise idle and the new rate is allowed as - a possible graph rate (see the pipewire configuration file). - - The RATE on (audio) driver nodes usually also translates directly to the - samplerate used by the device. Although some devices might not be able to - operate at the given samplerate, in which case resampling will need to be - done. The negotiated samplerate with the device and stream can be found in - the FORMAT column. - -WAIT - The waiting time of a node is the elapsed time between when the node - is ready to start processing and when it actually started processing. - - For Driver nodes, this is the time between when the node wakes up to - start processing the graph and when the driver (and thus also the graph) - completes a cycle. The WAIT time for driver is thus the elapsed time - processing the graph. - - For follower nodes, it is the time spent between being woken up (when all - dependencies of the node are satisfied) and when processing starts. The - WAIT time for follower nodes is thus mostly caused by context switching. - - A value of --- means that the node was not signaled. A value of +++ - means that the node was signaled but not awake. - -BUSY - The processing time is started when the node starts processing until it - completes and wakes up the next nodes in the graph. - - A value of --- means that the node was not started. A value of +++ - means that the node was started but did not complete. - -W/Q - Ratio of WAIT / QUANT. - - The W/Q time of the driver node is a good measure of the graph - load. The running averages of the driver W/Q ratios are used as the DSP - load in other (JACK) tools. - - Values of --- and +++ are copied from the WAIT column. - -B/Q - Ratio of BUSY / QUANT - - This is a good measure of the load of a particular driver or follower - node. - - Values of --- and +++ are copied from the BUSY column. - -ERR - Total of Xruns and Errors - - Xruns for drivers are when the graph did not complete a cycle. This can - be because a node in the graph also has an Xrun. It can also be caused when - scheduling delays cause a deadline to be missed, causing a hardware - Xrun. - - Xruns for followers are incremented when the node started processing but - did not complete before the end of the graph cycle deadline. - -FORMAT - The format used by the driver node or the stream. This is the hardware format - negotiated with the device or stream. - - If the stream of driver has a different rate than the graph, resampling will - be done. - - For raw audio formats, the layout is <sampleformat> <channels> <samplerate>. - - For IEC958 passthrough audio formats, the layout is IEC958 <codec> <samplerate>. - - For DSD formats, the layout is <dsd-rate> <channels>. - - For Video formats, the layout is <pixelformat> <width>x<height>. - -NAME - Name assigned to the device/node, as found in *pw-dump* node.name - - Names are prefixed by *+* when they are linked to a driver (entry above with no +) - - -OPTIONS -======= - --h | --help - Show help. - --b | --batch-mode - Run in non-interactive batch mode, similar to top's batch mode. - --n | --iterations=NUMBER - Exit after NUMBER of batch iterations. Only used in batch mode. - --r | --remote=NAME - The name the *remote* instance to monitor. If left unspecified, - a connection is made to the default PipeWire instance. - --V | --version - Show version information. - - -AUTHORS -======= - -The PipeWire Developers <@PACKAGE_BUGREPORT@>; PipeWire is available from @PACKAGE_URL@ - -SEE ALSO -======== - -``pipewire(1)``, -``pw-dump(1)``, -``pw-cli(1)``, -``pw-profiler(1)``, - diff --git a/meson.build b/meson.build index 535f9176..70e358f8 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '0.3.84', + version : '1.0.0', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', @@ -219,8 +219,7 @@ cdata.set_quoted('MODULEDIR', modules_install_dir) cdata.set_quoted('PIPEWIRE_CONFIG_DIR', pipewire_configdir) cdata.set_quoted('PLUGINDIR', spa_plugindir) cdata.set_quoted('SPADATADIR', spa_datadir) -cdata.set_quoted('PA_ALSA_PATHS_DIR', alsadatadir / 'paths') -cdata.set_quoted('PA_ALSA_PROFILE_SETS_DIR', alsadatadir / 'profile-sets') +cdata.set_quoted('PA_ALSA_DATA_DIR', alsadatadir) if host_machine.endian() == 'big' cdata.set('WORDS_BIGENDIAN', 1) @@ -486,22 +485,21 @@ if alsa_dep.found() subdir('pipewire-alsa/tests') endif -generate_manpages = false -if get_option('man').allowed() - rst2man = find_program('rst2man', required: false) - if not rst2man.found() - rst2man = find_program('rst2man.py', required: get_option('man')) - endif - if rst2man.found() - generate_manpages = true - endif +generate_docs = get_option('man').enabled() or get_option('docs').enabled() +if get_option('man').allowed() or get_option('docs').allowed() + doxygen = find_program('doxygen', required : generate_docs) + pymod = import('python') + python = pymod.find_installation('python3', required: generate_docs) + generate_docs = doxygen.found() and python.found() endif -summary({'Manpage generation': generate_manpages}, bool_yn: true) -subdir('man') +install_docs = get_option('docs').require(generate_docs).allowed() +install_man = get_option('man').require(generate_docs).allowed() + +summary({'Documentation ': install_docs}, bool_yn: true) +summary({'Man pages ': install_man}, bool_yn: true) -doxygen = find_program('doxygen', required : get_option('docs')) -if doxygen.found() +if generate_docs subdir('doc') endif diff --git a/meson_options.txt b/meson_options.txt index 3344b624..a09b6585 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,17 +2,17 @@ option('docdir', type : 'string', description : 'Directory for installing documentation to (defaults to pipewire_datadir/doc/meson.project_name() )') option('docs', - description: 'Build documentation', + description: 'Documentation', + type: 'feature', + value: 'disabled') +option('man', + description: 'Manual pages', type: 'feature', value: 'disabled') option('examples', description: 'Build examples', type: 'feature', value: 'enabled') -option('man', - description: 'Build manpages', - type: 'feature', - value: 'auto') option('tests', description: 'Build tests', type: 'feature', diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index dc4230e8..3464692d 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -89,7 +89,7 @@ typedef struct { static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io); -static int check_active(snd_pcm_ioplug_t *io) +static int update_active(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_sframes_t avail; @@ -97,7 +97,10 @@ static int check_active(snd_pcm_ioplug_t *io) avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); - if (io->state == SND_PCM_STATE_DRAINING) { + if (pw->error > 0) { + active = true; + } + else if (io->state == SND_PCM_STATE_DRAINING) { active = pw->drained; } else if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) { @@ -105,33 +108,27 @@ static int check_active(snd_pcm_ioplug_t *io) } else if (avail >= (snd_pcm_sframes_t)pw->min_avail) { active = true; - } else { + } + else { active = false; } if (pw->active != active) { + uint64_t val; + pw_log_trace("%p: avail:%lu min-avail:%lu state:%s hw:%lu appl:%lu active:%d->%d state:%s", pw, avail, pw->min_avail, snd_pcm_state_name(io->state), pw->hw_ptr, io->appl_ptr, pw->active, active, snd_pcm_state_name(io->state)); + + pw->active = active; + if (active) + spa_system_eventfd_write(pw->system, io->poll_fd, 1); + else + spa_system_eventfd_read(pw->system, io->poll_fd, &val); } return active; } - -static int update_active(snd_pcm_ioplug_t *io) -{ - snd_pcm_pipewire_t *pw = io->private_data; - pw->active = check_active(io); - uint64_t val; - - if (pw->active || pw->error < 0) - spa_system_eventfd_write(pw->system, io->poll_fd, 1); - else - spa_system_eventfd_read(pw->system, io->poll_fd, &val); - - return pw->active; -} - static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) { if (pw == NULL) @@ -162,15 +159,6 @@ static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io) return 0; } -static int snd_pcm_pipewire_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int space) -{ - snd_pcm_pipewire_t *pw = io->private_data; - update_active(io); - pfds->fd = pw->fd; - pfds->events = POLLIN | POLLERR | POLLNVAL; - return 1; -} - static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) @@ -183,10 +171,10 @@ static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io, return pw->error; *revents = pfds[0].revents & ~(POLLIN | POLLOUT); - if (pfds[0].revents & POLLIN && check_active(io)) { + if (pfds[0].revents & POLLIN && update_active(io)) *revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN; - update_active(io); - } + + pw_log_trace_fp("poll %d", *revents); return 0; } @@ -911,7 +899,6 @@ static snd_pcm_ioplug_callback_t pipewire_pcm_callback = { .delay = snd_pcm_pipewire_delay, .drain = snd_pcm_pipewire_drain, .prepare = snd_pcm_pipewire_prepare, - .poll_descriptors = snd_pcm_pipewire_poll_descriptors, .poll_revents = snd_pcm_pipewire_poll_revents, .hw_params = snd_pcm_pipewire_hw_params, .sw_params = snd_pcm_pipewire_sw_params, diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index b248ff46..34433fe1 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -9,6 +9,7 @@ #include <sys/mman.h> #include <regex.h> #include <math.h> +#include <threads.h> #include <jack/jack.h> #include <jack/intclient.h> @@ -110,6 +111,10 @@ struct globals { static struct globals globals; static bool mlock_warned = false; +#define MIDI_SCRATCH_FRAMES 8192 +static thread_local float midi_scratch[MIDI_SCRATCH_FRAMES]; + + #define OBJECT_CHUNK 8 #define RECYCLE_THRESHOLD 128 @@ -122,9 +127,10 @@ struct object { struct client *client; -#define INTERFACE_Port 0 -#define INTERFACE_Node 1 -#define INTERFACE_Link 2 +#define INTERFACE_Invalid 0 +#define INTERFACE_Port 1 +#define INTERFACE_Node 2 +#define INTERFACE_Link 3 uint32_t type; uint32_t id; uint32_t serial; @@ -296,6 +302,15 @@ struct metadata { char default_audio_source[1024]; }; +struct frame_times { + uint64_t frames; + uint64_t nsec; + uint64_t next_nsec; + uint32_t buffer_frames; + uint32_t sample_rate; + double rate_diff; +}; + struct client { char name[JACK_CLIENT_NAME_SIZE+1]; @@ -440,6 +455,7 @@ struct client { jack_position_t jack_position; jack_transport_state_t jack_state; + struct frame_times jack_times; }; #define return_val_if_fail(expr, val) \ @@ -502,7 +518,7 @@ static void recycle_objects(struct client *c, uint32_t remain) pthread_mutex_lock(&globals.lock); spa_list_for_each_safe(o, t, &c->context.objects, link) { if (o->removed) { - pw_log_info("%p: recycle object:%p type:%d id:%u/%u", + pw_log_debug("%p: recycle object:%p type:%d id:%u/%u", c, o, o->type, o->id, o->serial); spa_list_remove(&o->link); memset(o, 0, sizeof(struct object)); @@ -531,6 +547,15 @@ static void free_object(struct client *c, struct object *o) } +static inline struct object *port_to_object(const jack_port_t *port) +{ + return (struct object*)port; +} +static inline jack_port_t *object_to_port(struct object *o) +{ + return (jack_port_t*)o; +} + struct io_info { struct mix *mix; void *data; @@ -833,21 +858,6 @@ static struct object *find_link(struct client *c, uint32_t src, uint32_t dst) return NULL; } -static struct buffer *dequeue_buffer(struct client *c, struct mix *mix) -{ - struct buffer *b; - - if (SPA_UNLIKELY(spa_list_is_empty(&mix->queue))) - return NULL; - - b = spa_list_first(&mix->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); - pw_log_trace_fp("%p: port %p: dequeue buffer %d", c, mix->port, b->id); - - return b; -} - #if defined (__SSE__) #include <xmmintrin.h> static void mix_sse(float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples) @@ -1303,7 +1313,7 @@ static void client_remove_source(struct client *c) } } -static inline void reuse_buffer(struct client *c, struct mix *mix, uint32_t id) +static inline void queue_buffer(struct client *c, struct mix *mix, uint32_t id) { struct buffer *b; @@ -1316,6 +1326,21 @@ static inline void reuse_buffer(struct client *c, struct mix *mix, uint32_t id) } } +static inline struct buffer *dequeue_buffer(struct client *c, struct mix *mix) +{ + struct buffer *b; + + if (SPA_UNLIKELY(spa_list_is_empty(&mix->queue))) + return NULL; + + b = spa_list_first(&mix->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + pw_log_trace_fp("%p: port %p: dequeue buffer %d", c, mix->port, b->id); + + return b; +} + static size_t convert_from_midi(void *midi, void *buffer, size_t size) { @@ -1455,7 +1480,7 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t c, p->object->port.name, p->port_id, frames, mix->n_buffers, mix->io); - if (SPA_UNLIKELY((io = mix->io) == NULL)) + if (SPA_UNLIKELY((io = mix->io) == NULL || mix->n_buffers == 0)) return NULL; if (io->status == SPA_STATUS_HAVE_DATA && @@ -1463,21 +1488,26 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t b = &mix->buffers[io->buffer_id]; d = &b->datas[0]; } else { - if (io->buffer_id < mix->n_buffers) { - reuse_buffer(c, mix, io->buffer_id); - io->buffer_id = SPA_ID_INVALID; - } - if (SPA_UNLIKELY((b = dequeue_buffer(c, mix)) == NULL)) { - pw_log_warn("port %p: out of buffers", p); - return NULL; + if (mix->n_buffers == 1) { + b = &mix->buffers[0]; + } else { + if (io->buffer_id < mix->n_buffers) + queue_buffer(c, mix, io->buffer_id); + b = dequeue_buffer(c, mix); + + if (SPA_UNLIKELY(b == NULL)) { + pw_log_warn("port %p: out of buffers %d", p, mix->n_buffers); + io->buffer_id = SPA_ID_INVALID; + return NULL; + } } d = &b->datas[0]; d->chunk->offset = 0; d->chunk->size = frames * sizeof(float); d->chunk->stride = stride; - io->status = SPA_STATUS_HAVE_DATA; io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; } ptr = d->data; if (buf) @@ -1506,9 +1536,14 @@ static inline void process_empty(struct port *p, uint32_t frames) { struct buffer *b; ptr = get_buffer_output(p, c->max_frames, 1, &b); - if (SPA_LIKELY(ptr != NULL)) + if (SPA_LIKELY(ptr != NULL)) { + /* first build the complete pod in scratch memory, then copy it + * to the target buffer. This makes it possible for multiple threads + * to do this concurrently */ b->datas[0].chunk->size = convert_from_midi(src, - ptr, c->max_frames * sizeof(float)); + midi_scratch, MIDI_SCRATCH_FRAMES * sizeof(float)); + memcpy(ptr, midi_scratch, b->datas[0].chunk->size); + } break; } default: @@ -1564,38 +1599,38 @@ static void complete_process(struct client *c, uint32_t frames) static inline void debug_position(struct client *c, jack_position_t *p) { - pw_log_trace("usecs: %"PRIu64, p->usecs); - pw_log_trace("frame_rate: %u", p->frame_rate); - pw_log_trace("frame: %u", p->frame); - pw_log_trace("valid: %08x", p->valid); + pw_log_trace_fp("usecs: %"PRIu64, p->usecs); + pw_log_trace_fp("frame_rate: %u", p->frame_rate); + pw_log_trace_fp("frame: %u", p->frame); + pw_log_trace_fp("valid: %08x", p->valid); if (p->valid & JackPositionBBT) { - pw_log_trace("BBT"); - pw_log_trace(" bar: %u", p->bar); - pw_log_trace(" beat: %u", p->beat); - pw_log_trace(" tick: %u", p->tick); - pw_log_trace(" bar_start_tick: %f", p->bar_start_tick); - pw_log_trace(" beats_per_bar: %f", p->beats_per_bar); - pw_log_trace(" beat_type: %f", p->beat_type); - pw_log_trace(" ticks_per_beat: %f", p->ticks_per_beat); - pw_log_trace(" beats_per_minute: %f", p->beats_per_minute); + pw_log_trace_fp("BBT"); + pw_log_trace_fp(" bar: %u", p->bar); + pw_log_trace_fp(" beat: %u", p->beat); + pw_log_trace_fp(" tick: %u", p->tick); + pw_log_trace_fp(" bar_start_tick: %f", p->bar_start_tick); + pw_log_trace_fp(" beats_per_bar: %f", p->beats_per_bar); + pw_log_trace_fp(" beat_type: %f", p->beat_type); + pw_log_trace_fp(" ticks_per_beat: %f", p->ticks_per_beat); + pw_log_trace_fp(" beats_per_minute: %f", p->beats_per_minute); } if (p->valid & JackPositionTimecode) { - pw_log_trace("Timecode:"); - pw_log_trace(" frame_time: %f", p->frame_time); - pw_log_trace(" next_time: %f", p->next_time); + pw_log_trace_fp("Timecode:"); + pw_log_trace_fp(" frame_time: %f", p->frame_time); + pw_log_trace_fp(" next_time: %f", p->next_time); } if (p->valid & JackBBTFrameOffset) { - pw_log_trace("BBTFrameOffset:"); - pw_log_trace(" bbt_offset: %u", p->bbt_offset); + pw_log_trace_fp("BBTFrameOffset:"); + pw_log_trace_fp(" bbt_offset: %u", p->bbt_offset); } if (p->valid & JackAudioVideoRatio) { - pw_log_trace("AudioVideoRatio:"); - pw_log_trace(" audio_frames_per_video_frame: %f", p->audio_frames_per_video_frame); + pw_log_trace_fp("AudioVideoRatio:"); + pw_log_trace_fp(" audio_frames_per_video_frame: %f", p->audio_frames_per_video_frame); } if (p->valid & JackVideoFrameOffset) { - pw_log_trace("JackVideoFrameOffset:"); - pw_log_trace(" video_offset: %u", p->video_offset); + pw_log_trace_fp("JackVideoFrameOffset:"); + pw_log_trace_fp(" video_offset: %u", p->video_offset); } } @@ -1617,7 +1652,8 @@ static inline void jack_to_position(jack_position_t *s, struct pw_node_activatio } } -static inline jack_transport_state_t position_to_jack(struct pw_node_activation *a, jack_position_t *d) +static inline jack_transport_state_t position_to_jack(struct pw_node_activation *a, + jack_position_t *d, struct frame_times *t) { struct spa_io_position *s = &a->position; jack_transport_state_t state; @@ -1643,8 +1679,13 @@ static inline jack_transport_state_t position_to_jack(struct pw_node_activation return state; d->unique_1++; - d->usecs = s->clock.nsec / SPA_NSEC_PER_USEC; - d->frame_rate = s->clock.rate.denom; + t->frames = s->clock.position; + t->nsec = s->clock.nsec; + d->usecs = t->nsec / SPA_NSEC_PER_USEC; + t->next_nsec = s->clock.next_nsec; + t->rate_diff = s->clock.rate_diff; + t->buffer_frames = s->clock.duration; + d->frame_rate = t->sample_rate = s->clock.rate.denom; if ((int64_t)s->clock.position < s->offset) { d->frame = seg->position; @@ -1763,7 +1804,7 @@ static inline uint32_t cycle_run(struct client *c) return 0; if (SPA_LIKELY(driver)) { - c->jack_state = position_to_jack(driver, &c->jack_position); + c->jack_state = position_to_jack(driver, &c->jack_position, &c->jack_times); if (SPA_UNLIKELY(activation->pending_sync)) { if (c->sync_callback == NULL || @@ -2522,6 +2563,17 @@ static int client_node_port_set_param(void *data, return 0; } +static void midi_init_buffer(void *data, uint32_t max_frames) +{ + struct midi_buffer *mb = data; + mb->magic = MIDI_BUFFER_MAGIC; + mb->buffer_size = max_frames * sizeof(float); + mb->nframes = max_frames; + mb->write_pos = 0; + mb->event_count = 0; + mb->lost_events = 0; +} + static inline void *init_buffer(struct port *p) { struct client *c = p->client; @@ -2531,12 +2583,7 @@ static inline void *init_buffer(struct port *p) if (p->object->port.type_id == TYPE_ID_MIDI) { struct midi_buffer *mb = data; - mb->magic = MIDI_BUFFER_MAGIC; - mb->buffer_size = c->max_frames * sizeof(float); - mb->nframes = c->max_frames; - mb->write_pos = 0; - mb->event_count = 0; - mb->lost_events = 0; + midi_init_buffer(data, c->max_frames); pw_log_debug("port %p: init midi buffer size:%d", p, mb->buffer_size); } else memset(data, 0, c->max_frames * sizeof(float)); @@ -2681,7 +2728,7 @@ static int client_node_port_use_buffers(void *data, } SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); if (direction == SPA_DIRECTION_OUTPUT) - reuse_buffer(c, mix, b->id); + queue_buffer(c, mix, b->id); } pw_log_debug("%p: have %d buffers", c, n_buffers); @@ -3031,6 +3078,17 @@ static const char* type_to_string(jack_port_type_id_t type_id) return NULL; } } +static bool type_is_dsp(jack_port_type_id_t type_id) +{ + switch(type_id) { + case TYPE_ID_AUDIO: + case TYPE_ID_MIDI: + case TYPE_ID_VIDEO: + return true; + default: + return false; + } +} static jack_uuid_t client_make_uuid(uint32_t id, bool monitor) { @@ -3218,7 +3276,7 @@ static void node_info(void *data, const struct pw_node_info *info) } static const struct pw_node_events node_events = { - PW_VERSION_NODE, + PW_VERSION_NODE_EVENTS, .info = node_info, }; @@ -3243,7 +3301,7 @@ static void port_param(void *data, int seq, } static const struct pw_port_events port_events = { - PW_VERSION_PORT, + PW_VERSION_PORT_EVENTS, .param = port_param, }; @@ -3441,8 +3499,9 @@ static void registry_event_global(void *data, uint32_t id, pw_proxy_add_object_listener(o->proxy, &o->object_listener, &port_events, o); - pw_port_subscribe_params((struct pw_port*)o->proxy, - ids, 1); + if (type_is_dsp(type_id)) + pw_port_subscribe_params((struct pw_port*)o->proxy, + ids, 1); do_sync = true; } pthread_mutex_lock(&c->context.lock); @@ -5005,7 +5064,7 @@ jack_port_t * jack_port_register (jack_client_t *client, goto error_free; } - return (jack_port_t *) o; + return object_to_port(o); error_free: free_port(c, p, true); @@ -5037,7 +5096,7 @@ SPA_EXPORT int jack_port_unregister (jack_client_t *client, jack_port_t *port) { struct client *c = (struct client *) client; - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct port *p; int res; @@ -5148,12 +5207,10 @@ static void *get_buffer_input_float(struct port *p, jack_nframes_t frames) static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) { struct mix *mix; - void *ptr = p->emptyptr; + void *ptr = midi_scratch; struct spa_pod_sequence *seq[MAX_MIX]; uint32_t n_seq = 0; - jack_midi_clear_buffer(ptr); - spa_list_for_each(mix, &p->mix, port_link) { struct spa_data *d; struct buffer *b; @@ -5179,8 +5236,8 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) if (n_seq == MAX_MIX) break; } + midi_init_buffer(ptr, MIDI_SCRATCH_FRAMES); convert_to_midi(seq, n_seq, ptr, p->client->fix_midi_events); - return ptr; } @@ -5214,41 +5271,58 @@ static void *get_buffer_input_empty(struct port *p, jack_nframes_t frames) SPA_EXPORT void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) { - struct object *o = (struct object *) port; - struct port *p; - void *ptr; + struct object *o = port_to_object(port); + struct port *p = NULL; + void *ptr = NULL; return_val_if_fail(o != NULL, NULL); if (o->type != INTERFACE_Port || o->client == NULL) - return NULL; + goto done; if ((p = o->port.port) == NULL) { struct mix *mix; struct buffer *b; if ((mix = find_mix_peer(o->client, o->id)) == NULL) - return NULL; + goto done; pw_log_trace("peer mix: %p %d", mix, mix->peer_id); if ((b = get_mix_buffer(mix, frames)) == NULL) - return NULL; - - return get_buffer_data(b, frames); + goto done; + + if (o->port.type_id == TYPE_ID_MIDI) { + struct spa_pod_sequence *seq[1]; + struct spa_data *d; + void *pod; + + ptr = midi_scratch; + midi_init_buffer(ptr, MIDI_SCRATCH_FRAMES); + + d = &b->datas[0]; + if ((pod = spa_pod_from_data(d->data, d->maxsize, + d->chunk->offset, d->chunk->size)) == NULL) + goto done; + if (!spa_pod_is_sequence(pod)) + goto done; + seq[0] = pod; + convert_to_midi(seq, 1, ptr, o->client->fix_midi_events); + } else { + ptr = get_buffer_data(b, frames); + } + } else if (p->valid) { + ptr = p->get_buffer(p, frames); } - if (!p->valid) - return NULL; - - ptr = p->get_buffer(p, frames); - pw_log_trace_fp("%p: port %p buffer %p empty:%u", p->client, p, ptr, p->empty_out); +done: + pw_log_trace_fp("%p: port %p buffer %p", o->client, p, ptr); return ptr; } SPA_EXPORT jack_uuid_t jack_port_uuid (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); return jack_port_uuid_generate(o->serial); } @@ -5269,47 +5343,57 @@ static const char *port_name(struct object *o) SPA_EXPORT const char * jack_port_name (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); + if (o->type != INTERFACE_Port) + return NULL; return port_name(o); } SPA_EXPORT const char * jack_port_short_name (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); + if (o->type != INTERFACE_Port) + return NULL; return strchr(port_name(o), ':') + 1; } SPA_EXPORT int jack_port_flags (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); + if (o->type != INTERFACE_Port) + return 0; return o->port.flags; } SPA_EXPORT const char * jack_port_type (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); + if (o->type != INTERFACE_Port) + return NULL; return type_to_string(o->port.type_id); } SPA_EXPORT jack_port_type_id_t jack_port_type_id (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); + if (o->type != INTERFACE_Port) + return TYPE_ID_OTHER; return o->port.type_id; } SPA_EXPORT int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); return o->type == INTERFACE_Port && o->port.port != NULL && @@ -5319,7 +5403,7 @@ int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) SPA_EXPORT int jack_port_connected (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct client *c; struct object *l; int res = 0; @@ -5349,7 +5433,7 @@ SPA_EXPORT int jack_port_connected_to (const jack_port_t *port, const char *port_name) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct client *c; struct object *p, *l; int res = 0; @@ -5389,7 +5473,7 @@ int jack_port_connected_to (const jack_port_t *port, SPA_EXPORT const char ** jack_port_get_connections (const jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); if (o->type != INTERFACE_Port || o->client == NULL) @@ -5403,7 +5487,7 @@ const char ** jack_port_get_all_connections (const jack_client_t *client, const jack_port_t *port) { struct client *c = (struct client *) client; - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct object *p, *l; const char **res; int count = 0; @@ -5446,8 +5530,8 @@ const char ** jack_port_get_all_connections (const jack_client_t *client, SPA_EXPORT int jack_port_tie (jack_port_t *src, jack_port_t *dst) { - struct object *s = (struct object *) src; - struct object *d = (struct object *) dst; + struct object *s = port_to_object(src); + struct object *d = port_to_object(dst); struct port *sp, *dp; sp = s->port.port; @@ -5464,7 +5548,7 @@ int jack_port_tie (jack_port_t *src, jack_port_t *dst) SPA_EXPORT int jack_port_untie (jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct port *p; p = o->port.port; @@ -5485,7 +5569,7 @@ SPA_EXPORT int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) { struct client *c = (struct client *) client; - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct port *p; int res = 0; @@ -5527,7 +5611,7 @@ done: SPA_EXPORT int jack_port_set_alias (jack_port_t *port, const char *alias) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct client *c; struct port *p; const char *key; @@ -5583,7 +5667,7 @@ done: SPA_EXPORT int jack_port_unset_alias (jack_port_t *port, const char *alias) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct client *c; struct port *p; const char *key; @@ -5634,7 +5718,7 @@ done: SPA_EXPORT int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); int res = 0; return_val_if_fail(o != NULL, -EINVAL); @@ -5657,7 +5741,7 @@ int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) SPA_EXPORT int jack_port_request_monitor (jack_port_t *port, int onoff) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, -EINVAL); @@ -5688,13 +5772,13 @@ int jack_port_request_monitor_by_name (jack_client_t *client, return -1; } - return jack_port_request_monitor((jack_port_t*)p, onoff); + return jack_port_request_monitor(object_to_port(p), onoff); } SPA_EXPORT int jack_port_ensure_monitor (jack_port_t *port, int onoff) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, -EINVAL); @@ -5711,7 +5795,7 @@ int jack_port_ensure_monitor (jack_port_t *port, int onoff) SPA_EXPORT int jack_port_monitoring_input (jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); return_val_if_fail(o != NULL, -EINVAL); return o->port.monitor_requests > 0; } @@ -5889,7 +5973,7 @@ SPA_EXPORT int jack_port_disconnect (jack_client_t *client, jack_port_t *port) { struct client *c = (struct client *) client; - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct object *l; int res; @@ -5950,7 +6034,7 @@ size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_t SPA_EXPORT void jack_port_set_latency (jack_port_t *port, jack_nframes_t frames) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct client *c; jack_latency_range_t range = { frames, frames }; @@ -5970,17 +6054,20 @@ void jack_port_set_latency (jack_port_t *port, jack_nframes_t frames) SPA_EXPORT void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct client *c; jack_nframes_t nframes, rate; int direction; struct spa_latency_info *info; return_if_fail(o != NULL); - if (o->type != INTERFACE_Port || o->client == NULL) - return; c = o->client; + if (o->type != INTERFACE_Port || c == NULL) { + range->min = range->max = 0; + return; + } + if (mode == JackCaptureLatency) direction = SPA_DIRECTION_OUTPUT; else @@ -6012,7 +6099,7 @@ do_port_check_latency(struct spa_loop *loop, SPA_EXPORT void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); struct client *c; enum spa_direction direction; struct spa_latency_info latency; @@ -6065,7 +6152,7 @@ int jack_recompute_total_latencies (jack_client_t *client) static jack_nframes_t port_get_latency (jack_port_t *port) { - struct object *o = (struct object *) port; + struct object *o = port_to_object(port); jack_latency_range_t range = { 0, 0 }; return_val_if_fail(o != NULL, 0); @@ -6265,7 +6352,7 @@ jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) if (res == NULL) pw_log_info("%p: port \"%s\" not found", c, port_name); - return (jack_port_t *)res; + return object_to_port(res); } SPA_EXPORT @@ -6287,23 +6374,35 @@ jack_port_t * jack_port_by_id (jack_client_t *client, if (res == NULL) pw_log_info("%p: port %d not found", c, port_id); - return (jack_port_t *)res; + return object_to_port(res); +} + +static inline void get_frame_times(struct client *c, struct frame_times *times) +{ + jack_unique_t u1; + uint32_t count = 0; + do { + u1 = c->jack_position.unique_1; + *times = c->jack_times; + if (++count == 10) { + pw_log_warn("could not get snapshot %lu %lu", u1, c->jack_position.unique_2); + break; + } + } while (u1 != c->jack_position.unique_2); } SPA_EXPORT jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *client) { struct client *c = (struct client *) client; - struct spa_io_position *pos; + struct frame_times times; uint64_t diff; return_val_if_fail(c != NULL, 0); - if (SPA_UNLIKELY((pos = c->rt.position) == NULL)) - return 0; - - diff = get_time_ns() - pos->clock.nsec; - return (jack_nframes_t) floor(((double)c->sample_rate * diff) / SPA_NSEC_PER_SEC); + get_frame_times(c, ×); + diff = get_time_ns() - times.nsec; + return (jack_nframes_t) floor(((double)times.sample_rate * diff) / SPA_NSEC_PER_SEC); } SPA_EXPORT @@ -6316,14 +6415,13 @@ SPA_EXPORT jack_nframes_t jack_last_frame_time (const jack_client_t *client) { struct client *c = (struct client *) client; - struct spa_io_position *pos; + struct frame_times times; return_val_if_fail(c != NULL, 0); - if (SPA_UNLIKELY((pos = c->rt.position) == NULL)) - return 0; + get_frame_times(c, ×); - return pos->clock.position; + return times.frames; } SPA_EXPORT @@ -6334,17 +6432,20 @@ int jack_get_cycle_times(const jack_client_t *client, float *period_usecs) { struct client *c = (struct client *) client; - struct spa_io_position *pos; + struct frame_times times; return_val_if_fail(c != NULL, -EINVAL); - if (SPA_UNLIKELY((pos = c->rt.position) == NULL)) - return -EIO; + get_frame_times(c, ×); - *current_frames = pos->clock.position; - *current_usecs = pos->clock.nsec / SPA_NSEC_PER_USEC; - *period_usecs = pos->clock.duration * (float)SPA_USEC_PER_SEC / (c->sample_rate * pos->clock.rate_diff); - *next_usecs = pos->clock.next_nsec / SPA_NSEC_PER_USEC; + *current_frames = times.frames; + *current_usecs = times.nsec / SPA_NSEC_PER_USEC; + *next_usecs = times.next_nsec / SPA_NSEC_PER_USEC; + if (times.sample_rate == 0 || times.rate_diff == 0.0) + *period_usecs = (times.next_nsec - times.nsec) / SPA_NSEC_PER_USEC; + else + *period_usecs = times.buffer_frames * + (float)SPA_USEC_PER_SEC / (times.sample_rate * times.rate_diff); pw_log_trace("%p: %d %"PRIu64" %"PRIu64" %f", c, *current_frames, *current_usecs, *next_usecs, *period_usecs); @@ -6355,38 +6456,42 @@ SPA_EXPORT jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t frames) { struct client *c = (struct client *) client; - struct spa_io_position *pos; + struct frame_times times; return_val_if_fail(c != NULL, -EINVAL); - if (SPA_UNLIKELY((pos = c->rt.position) == NULL) || c->buffer_frames == 0) + get_frame_times(c, ×); + + if (times.buffer_frames == 0) return 0; - uint32_t nf = (uint32_t)pos->clock.position; - uint64_t w = pos->clock.nsec/SPA_NSEC_PER_USEC; - uint64_t nw = pos->clock.next_nsec/SPA_NSEC_PER_USEC; + uint32_t nf = (uint32_t)times.frames; + uint64_t w = times.nsec/SPA_NSEC_PER_USEC; + uint64_t nw = times.next_nsec/SPA_NSEC_PER_USEC; int32_t df = frames - nf; int64_t dp = nw - w; - return w + (int64_t)rint((double) df * (double) dp / c->buffer_frames); + return w + (int64_t)rint((double) df * (double) dp / times.buffer_frames); } SPA_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t usecs) { struct client *c = (struct client *) client; - struct spa_io_position *pos; + struct frame_times times; return_val_if_fail(c != NULL, -EINVAL); - if (SPA_UNLIKELY((pos = c->rt.position) == NULL)) + get_frame_times(c, ×); + + if (times.buffer_frames == 0) return 0; - uint32_t nf = (uint32_t)pos->clock.position; - uint64_t w = pos->clock.nsec/SPA_NSEC_PER_USEC; - uint64_t nw = pos->clock.next_nsec/SPA_NSEC_PER_USEC; + uint32_t nf = (uint32_t)times.frames; + uint64_t w = times.nsec/SPA_NSEC_PER_USEC; + uint64_t nw = times.next_nsec/SPA_NSEC_PER_USEC; int64_t du = usecs - w; int64_t dp = nw - w; - return nf + (int32_t)rint((double)du / (double)dp * c->buffer_frames); + return nf + (int32_t)rint((double)du / (double)dp * times.buffer_frames); } SPA_EXPORT @@ -6559,46 +6664,44 @@ jack_transport_state_t jack_transport_query (const jack_client_t *client, jack_position_t *pos) { struct client *c = (struct client *) client; - struct pw_node_activation *a; - jack_transport_state_t jack_state = JackTransportStopped; + jack_transport_state_t state; + jack_unique_t u1; + uint32_t count = 0; return_val_if_fail(c != NULL, JackTransportStopped); - if (SPA_LIKELY((a = c->rt.driver_activation) != NULL)) { - jack_state = position_to_jack(a, pos); - } else if ((a = c->driver_activation) != NULL) { - jack_state = position_to_jack(a, pos); - } else if (pos != NULL) { - memset(pos, 0, sizeof(jack_position_t)); - pos->frame_rate = jack_get_sample_rate((jack_client_t*)client); - } - return jack_state; + do { + u1 = c->jack_position.unique_1; + state = c->jack_state; + if (pos != NULL) + *pos = c->jack_position; + if (++count == 10) { + pw_log_warn("could not get snapshot %lu %lu", u1, c->jack_position.unique_2); + break; + } + } while (u1 != c->jack_position.unique_2); + + return state; } SPA_EXPORT jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) { struct client *c = (struct client *) client; - struct pw_node_activation *a; - struct spa_io_position *pos; - struct spa_io_segment *seg; - uint64_t running; + jack_transport_state_t state; + jack_nframes_t res; + jack_position_t pos; return_val_if_fail(c != NULL, -EINVAL); - if (SPA_UNLIKELY((a = c->rt.driver_activation) == NULL)) - return -EIO; + state = jack_transport_query(client, &pos); + res = pos.frame; - pos = &a->position; - running = pos->clock.position - pos->offset; - - if (pos->state == SPA_IO_POSITION_STATE_RUNNING) { - uint64_t nsecs = get_time_ns() - pos->clock.nsec; - running += (uint64_t)floor((((double) c->sample_rate) / SPA_NSEC_PER_SEC) * nsecs); + if (state == JackTransportRolling) { + float usecs = get_time_ns()/1000 - pos.usecs; + res += (jack_nframes_t)floor((((float) pos.frame_rate) / 1000000.0f) * usecs); } - seg = &pos->segments[0]; - - return (running - seg->start) * seg->rate + seg->position; + return res; } SPA_EXPORT diff --git a/spa/include/spa/buffer/alloc.h b/spa/include/spa/buffer/alloc.h index 7329c944..8b9e55e5 100644 --- a/spa/include/spa/buffer/alloc.h +++ b/spa/include/spa/buffer/alloc.h @@ -300,7 +300,8 @@ spa_buffer_alloc_array(uint32_t n_buffers, uint32_t flags, { struct spa_buffer **buffers; - struct spa_buffer_alloc_info info = { flags | SPA_BUFFER_ALLOC_FLAG_INLINE_ALL, }; + struct spa_buffer_alloc_info info = { flags | SPA_BUFFER_ALLOC_FLAG_INLINE_ALL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; void *skel; spa_buffer_alloc_fill_info(&info, n_metas, metas, n_datas, datas, data_aligns); diff --git a/spa/include/spa/debug/log.h b/spa/include/spa/debug/log.h index 43b3bd52..aa16c5e9 100644 --- a/spa/include/spa/debug/log.h +++ b/spa/include/spa/debug/log.h @@ -38,7 +38,7 @@ struct spa_debug_log_ctx { SPA_PRINTF_FUNC(2,3) static inline void spa_debug_log_log(struct spa_debug_context *ctx, const char *fmt, ...) { - struct spa_debug_log_ctx *c = (struct spa_debug_log_ctx*)ctx; + struct spa_debug_log_ctx *c = SPA_CONTAINER_OF(ctx, struct spa_debug_log_ctx, ctx); va_list args; va_start(args, fmt); spa_log_logtv(c->log, c->level, c->topic, c->file, c->line, c->func, fmt, args); diff --git a/spa/include/spa/graph/graph.h b/spa/include/spa/graph/graph.h index bfcb9b3d..39049183 100644 --- a/spa/include/spa/graph/graph.h +++ b/spa/include/spa/graph/graph.h @@ -215,7 +215,7 @@ spa_graph_node_init(struct spa_graph_node *node, struct spa_graph_state *state) } -static inline int spa_graph_node_impl_sub_process(void *data, struct spa_graph_node *node) +static inline int spa_graph_node_impl_sub_process(void *data SPA_UNUSED, struct spa_graph_node *node) { struct spa_graph *graph = node->subgraph; spa_debug("node %p: sub process %p", node, graph); @@ -322,7 +322,7 @@ static inline int spa_graph_node_impl_process(void *data, struct spa_graph_node return state->status; } -static inline int spa_graph_node_impl_reuse_buffer(void *data, struct spa_graph_node *node, +static inline int spa_graph_node_impl_reuse_buffer(void *data, struct spa_graph_node *node SPA_UNUSED, uint32_t port_id, uint32_t buffer_id) { struct spa_node *n = (struct spa_node *)data; diff --git a/spa/include/spa/monitor/utils.h b/spa/include/spa/monitor/utils.h index fcea4ac6..93f8f41b 100644 --- a/spa/include/spa/monitor/utils.h +++ b/spa/include/spa/monitor/utils.h @@ -22,8 +22,8 @@ struct spa_result_device_params_data { struct spa_result_device_params data; }; -static inline void spa_result_func_device_params(void *data, int seq, int res, - uint32_t type, const void *result) +static inline void spa_result_func_device_params(void *data, int seq SPA_UNUSED, int res SPA_UNUSED, + uint32_t type SPA_UNUSED, const void *result) { struct spa_result_device_params_data *d = (struct spa_result_device_params_data *)data; @@ -42,8 +42,8 @@ static inline int spa_device_enum_params_sync(struct spa_device *device, struct spa_pod **param, struct spa_pod_builder *builder) { - struct spa_result_device_params_data data = { builder, }; - struct spa_hook listener = {{0}}; + struct spa_result_device_params_data data = { builder, {0}}; + struct spa_hook listener = {{0}, {0}, 0, 0}; static const struct spa_device_events device_events = { .version = SPA_VERSION_DEVICE_EVENTS, .info = NULL, diff --git a/spa/include/spa/node/utils.h b/spa/include/spa/node/utils.h index a88ef895..01d249ab 100644 --- a/spa/include/spa/node/utils.h +++ b/spa/include/spa/node/utils.h @@ -24,7 +24,7 @@ struct spa_result_node_params_data { }; static inline void spa_result_func_node_params(void *data, - int seq, int res, uint32_t type, const void *result) + int seq SPA_UNUSED, int res SPA_UNUSED, uint32_t type SPA_UNUSED, const void *result) { struct spa_result_node_params_data *d = (struct spa_result_node_params_data *) data; @@ -43,8 +43,8 @@ static inline int spa_node_enum_params_sync(struct spa_node *node, struct spa_pod **param, struct spa_pod_builder *builder) { - struct spa_result_node_params_data data = { builder, }; - struct spa_hook listener = {{0}}; + struct spa_result_node_params_data data = { builder, {0}}; + struct spa_hook listener = {{0}, {0}, 0, 0}; static const struct spa_node_events node_events = { .version = SPA_VERSION_NODE_EVENTS, .info = NULL, @@ -77,8 +77,8 @@ static inline int spa_node_port_enum_params_sync(struct spa_node *node, struct spa_pod **param, struct spa_pod_builder *builder) { - struct spa_result_node_params_data data = { builder, }; - struct spa_hook listener = {{0}}; + struct spa_result_node_params_data data = { builder, {0}}; + struct spa_hook listener = {{0}, {0}, 0, 0}; static const struct spa_node_events node_events = { .version = SPA_VERSION_NODE_EVENTS, .info = NULL, diff --git a/spa/include/spa/param/props.h b/spa/include/spa/param/props.h index 8665013f..a7a2e4c2 100644 --- a/spa/include/spa/param/props.h +++ b/spa/include/spa/param/props.h @@ -64,24 +64,26 @@ enum spa_prop { SPA_PROP_patternType, SPA_PROP_ditherType, SPA_PROP_truncate, - SPA_PROP_channelVolumes, /**< a volume array, one volume per channel + SPA_PROP_channelVolumes, /**< a volume array, one (linear) volume per channel * (Array of Float). 0.0 is silence, 1.0 is - * without attenuation. This is the effective volume - * that is applied. It can result in a hardware volume - * and software volume (see softVolumes) */ + * without attenuation. This is the effective + * volume that is applied. It can result + * in a hardware volume and software volume + * (see softVolumes) */ SPA_PROP_volumeBase, /**< a volume base (Float) */ SPA_PROP_volumeStep, /**< a volume step (Float) */ SPA_PROP_channelMap, /**< a channelmap array * (Array (Id enum spa_audio_channel)) */ SPA_PROP_monitorMute, /**< mute (Bool) */ - SPA_PROP_monitorVolumes, /**< a volume array, one volume per + SPA_PROP_monitorVolumes, /**< a volume array, one (linear) volume per * channel (Array of Float) */ SPA_PROP_latencyOffsetNsec, /**< delay adjustment */ SPA_PROP_softMute, /**< mute (Bool) applied in software */ - SPA_PROP_softVolumes, /**< a volume array, one volume per channel + SPA_PROP_softVolumes, /**< a volume array, one (linear) volume per channel * (Array of Float). 0.0 is silence, 1.0 is without - * attenuation. This is the volume applied in software, - * there might be a part applied in hardware. */ + * attenuation. This is the volume applied in + * software, there might be a part applied in + * hardware. */ SPA_PROP_iec958Codecs, /**< enabled IEC958 (S/PDIF) codecs, * (Array (Id enum spa_audio_iec958_codec) */ diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 40e17109..3a682e1a 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -78,7 +78,7 @@ static inline int spa_pod_choice_fix_default(struct spa_pod_choice *choice) } static inline int spa_pod_filter_flags_value(struct spa_pod_builder *b, - uint32_t type, const void *r1, const void *r2, uint32_t size) + uint32_t type, const void *r1, const void *r2, uint32_t size SPA_UNUSED) { switch (type) { case SPA_TYPE_Int: @@ -104,7 +104,7 @@ static inline int spa_pod_filter_flags_value(struct spa_pod_builder *b, } static inline int spa_pod_filter_is_step_of(uint32_t type, const void *r1, - const void *r2, uint32_t size) + const void *r2, uint32_t size SPA_UNUSED) { switch (type) { case SPA_TYPE_Int: diff --git a/spa/include/spa/support/log-impl.h b/spa/include/spa/support/log-impl.h index 0af62f64..214417fa 100644 --- a/spa/include/spa/support/log-impl.h +++ b/spa/include/spa/support/log-impl.h @@ -19,7 +19,7 @@ extern "C" { * \{ */ -static inline SPA_PRINTF_FUNC(7, 0) void spa_log_impl_logtv(void *object, +static inline SPA_PRINTF_FUNC(7, 0) void spa_log_impl_logtv(void *object SPA_UNUSED, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, @@ -88,7 +88,7 @@ static inline SPA_PRINTF_FUNC(6,7) void spa_log_impl_log(void *object, va_end(args); } -static inline void spa_log_impl_topic_init(void *object, struct spa_log_topic *topic) +static inline void spa_log_impl_topic_init(void *object SPA_UNUSED, struct spa_log_topic *topic SPA_UNUSED) { /* noop */ } diff --git a/spa/include/spa/utils/json.h b/spa/include/spa/utils/json.h index 376d4c87..c97e2537 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -38,13 +38,13 @@ struct spa_json { uint32_t depth; }; -#define SPA_JSON_INIT(data,size) ((struct spa_json) { (data), (data)+(size), }) +#define SPA_JSON_INIT(data,size) ((struct spa_json) { (data), (data)+(size), 0, 0, 0 }) static inline void spa_json_init(struct spa_json * iter, const char *data, size_t size) { *iter = SPA_JSON_INIT(data, size); } -#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), }) +#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), 0, 0 }) static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub) { @@ -181,7 +181,7 @@ static inline int spa_json_is_container(const char *val, int len) return len > 0 && (*val == '{' || *val == '['); } -static inline int spa_json_container_len(struct spa_json *iter, const char *value, int len) +static inline int spa_json_container_len(struct spa_json *iter, const char *value, int len SPA_UNUSED) { const char *val; struct spa_json sub; diff --git a/spa/include/spa/utils/keys.h b/spa/include/spa/utils/keys.h index 5ef4e49d..2f007ade 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -44,6 +44,7 @@ extern "C" { #define SPA_KEY_API_ALSA_OPEN_UCM "api.alsa.open.ucm" /**< if UCM should be opened card */ #define SPA_KEY_API_ALSA_DISABLE_LONGNAME \ "api.alsa.disable-longname" /**< if card long name should not be passed to MIDI port */ +#define SPA_KEY_API_ALSA_BIND_CTLS "api.alsa.bind-ctls" /**< alsa controls to bind as params */ /** info from alsa card_info */ #define SPA_KEY_API_ALSA_CARD_ID "api.alsa.card.id" /**< id from card_info */ diff --git a/spa/plugins/alsa/acp/alsa-mixer.c b/spa/plugins/alsa/acp/alsa-mixer.c index 8138c6c6..34680b5c 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.c +++ b/spa/plugins/alsa/acp/alsa-mixer.c @@ -2795,20 +2795,6 @@ static int path_verify(pa_alsa_path *p) { return 0; } -static const char *get_default_paths_dir(void) { - const char *str; -#ifdef HAVE_RUNNING_FROM_BUILD_TREE - if (pa_run_from_build_tree()) - return PA_SRCDIR "mixer/paths"; - else -#endif - if (getenv("ACP_BUILDDIR") != NULL) - return "mixer/paths"; - if ((str = getenv("ACP_PATHS_DIR")) != NULL) - return str; - return PA_ALSA_PATHS_DIR; -} - pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction) { pa_alsa_path *p; char *fn; @@ -2873,10 +2859,9 @@ pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa items[2].data = &p->description; items[3].data = &mute_during_activation; - if (!paths_dir) - paths_dir = get_default_paths_dir(); + fn = get_data_path(paths_dir, "paths", fname); - fn = pa_maybe_prefix_path(fname, paths_dir); + pa_log_info("Loading path config: %s", fn); r = pa_config_parse(fn, NULL, items, p->proplist, false, p); pa_xfree(fn); @@ -4827,20 +4812,6 @@ void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) { pa_xfree(db_values); } -static const char *get_default_profile_dir(void) { - const char *str; -#ifdef HAVE_RUNNING_FROM_BUILD_TREE - if (pa_run_from_build_tree()) - return PA_SRCDIR "mixer/profile-sets"; - else -#endif - if (getenv("ACP_BUILDDIR") != NULL) - return "mixer/profile-sets"; - if ((str = getenv("ACP_PROFILES_DIR")) != NULL) - return str; - return PA_ALSA_PROFILE_SETS_DIR; -} - pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) { pa_alsa_profile_set *ps; pa_alsa_profile *p; @@ -4890,13 +4861,14 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel items[0].data = &ps->auto_profiles; - fn = pa_maybe_prefix_path(fname ? fname : "default.conf", - get_default_profile_dir()); + fn = get_data_path(NULL, "profile-sets", fname ? fname : "default.conf"); + + pa_log_info("Loading profile set: %s", fn); + if ((r = access(fn, R_OK)) != 0) { if (fname != NULL) { pa_log_warn("profile-set '%s' can't be accessed: %m", fn); - fn = pa_maybe_prefix_path("default.conf", - get_default_profile_dir()); + fn = get_data_path(NULL, "profile-sets", "default.conf"); r = access(fn, R_OK); } if (r != 0) { diff --git a/spa/plugins/alsa/acp/compat.c b/spa/plugins/alsa/acp/compat.c index 77034440..e2f317b0 100644 --- a/spa/plugins/alsa/acp/compat.c +++ b/spa/plugins/alsa/acp/compat.c @@ -18,9 +18,13 @@ along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. ***/ +#include <spa/utils/string.h> +#include <spa/utils/cleanup.h> + #include "compat.h" #include "device-port.h" #include "alsa-mixer.h" +#include "config.h" static const char *port_types[] = { [PA_DEVICE_PORT_TYPE_UNKNOWN] = "unknown", @@ -208,3 +212,76 @@ bool pa_alsa_device_init_description(pa_proplist *p, pa_card *card) { return true; } + +static char *try_path(const char *fname, const char *path) +{ + char *result = pa_maybe_prefix_path(fname, path); + + pa_log_trace("Check for file: %s", result); + + if (access(result, R_OK) == 0) + return result; + + pa_xfree(result); + return NULL; +} + +static char *get_xdg_home(const char *key, const char *fallback) +{ + const char *e; + + e = getenv(key); + if (e && *e) { + return strdup(e); + } else { + e = getenv("HOME"); + if (!(e && *e)) + e = getenv("USERPROFILE"); + if (e && *e) + return spa_aprintf("%s/%s", e, fallback); + } + return NULL; +} + +char *get_data_path(const char *data_dir, const char *data_type, const char *fname) +{ + static const char * const subpaths[] = { + "alsa-card-profile/mixer", + "alsa-card-profile", + }; + const char *e; + spa_autofree char *base = NULL; + char *result; + + if (data_dir) + if ((result = try_path(fname, data_dir)) != NULL) + return result; + + e = getenv("ACP_PATHS_DIR"); + if (e && *e && spa_streq(data_type, "paths")) + if ((result = try_path(fname, e)) != NULL) + return result; + + e = getenv("ACP_PROFILES_DIR"); + if (e && *e && spa_streq(data_type, "profile-sets")) + if ((result = try_path(fname, e)) != NULL) + return result; + + base = get_xdg_home("XDG_CONFIG_HOME", ".config"); + if (base) { + SPA_FOR_EACH_ELEMENT_VAR(subpaths, subpath) { + spa_autofree char *path = spa_aprintf("%s/%s/%s", base, *subpath, data_type); + if ((result = try_path(fname, path)) != NULL) + return result; + } + } + + SPA_FOR_EACH_ELEMENT_VAR(subpaths, subpath) { + spa_autofree char *path = spa_aprintf("/etc/%s/%s", *subpath, data_type); + if ((result = try_path(fname, path)) != NULL) + return result; + } + + spa_autofree char *path = spa_aprintf("%s/%s", PA_ALSA_DATA_DIR, data_type); + return pa_maybe_prefix_path(fname, path); +} diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h index d60f9ef9..3e704ddf 100644 --- a/spa/plugins/alsa/acp/compat.h +++ b/spa/plugins/alsa/acp/compat.h @@ -214,6 +214,7 @@ typedef enum pa_log_level { PA_LOG_NOTICE = 2, /* Notice messages */ PA_LOG_INFO = 3, /* Info messages */ PA_LOG_DEBUG = 4, /* Debug messages */ + PA_LOG_TRACE = 5, PA_LOG_LEVEL_MAX } pa_log_level_t; @@ -245,6 +246,7 @@ static inline PA_PRINTF_FUNC(5, 6) void pa_log_level_meta(enum pa_log_level leve #define pa_log_notice(fmt,...) pa_logl(PA_LOG_NOTICE, fmt, ##__VA_ARGS__) #define pa_log_info(fmt,...) pa_logl(PA_LOG_INFO, fmt, ##__VA_ARGS__) #define pa_log_debug(fmt,...) pa_logl(PA_LOG_DEBUG, fmt, ##__VA_ARGS__) +#define pa_log_trace(fmt,...) pa_logl(PA_LOG_TRACE, fmt, ##__VA_ARGS__) #define pa_log pa_log_error #define pa_assert_se(expr) \ @@ -677,6 +679,8 @@ static inline char *pa_readlink(const char *p) { #endif } +char *get_data_path(const char *data_dir, const char *data_type, const char *fname); + #include <spa/support/i18n.h> extern struct spa_i18n *acp_i18n; diff --git a/spa/plugins/alsa/alsa-compress-offload-device.c b/spa/plugins/alsa/alsa-compress-offload-device.c index 38695f60..31f95189 100644 --- a/spa/plugins/alsa/alsa-compress-offload-device.c +++ b/spa/plugins/alsa/alsa-compress-offload-device.c @@ -86,7 +86,7 @@ static void emit_node(struct impl *this, const char *device_node, unsigned int d * hardware that can capture audio is difficult to do. The only hardware * known is the Wolfson ADSP; the only driver in the kernel that exposes * Compress-Offload capture devices is the one for that hardware. */ - assert(false); + spa_assert_not_reached(); } info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index a0731f7c..aa8105ab 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -8,8 +8,6 @@ #include <spa/node/node.h> #include <spa/node/utils.h> -#include <spa/node/keys.h> -#include <spa/monitor/device.h> #include <spa/utils/keys.h> #include <spa/utils/names.h> #include <spa/utils/string.h> @@ -31,75 +29,6 @@ static void reset_props(struct props *props) props->use_chmap = DEFAULT_USE_CHMAP; } -static void emit_node_info(struct state *this, bool full) -{ - uint64_t old = full ? this->info.change_mask : 0; - - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - struct spa_dict_item items[7]; - uint32_t i, n_items = 0; - char latency[64], period[64], nperiods[64], headroom[64]; - - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); - if (this->have_format) { - snprintf(latency, sizeof(latency), "%lu/%d", this->buffer_frames / 2, this->rate); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency); - snprintf(period, sizeof(period), "%lu", this->period_frames); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period); - snprintf(nperiods, sizeof(nperiods), "%lu", - this->period_frames != 0 ? this->buffer_frames / this->period_frames : 0); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods); - snprintf(headroom, sizeof(headroom), "%u", this->headroom); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom); - } else { - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, NULL); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", NULL); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", NULL); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", NULL); - } - this->info.props = &SPA_DICT_INIT(items, n_items); - - if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { - for (i = 0; i < this->info.n_params; i++) { - if (this->params[i].user > 0) { - this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; - this->params[i].user = 0; - } - } - } - spa_node_emit_info(&this->hooks, &this->info); - - this->info.change_mask = old; - } -} - -static void emit_port_info(struct state *this, bool full) -{ - uint64_t old = full ? this->port_info.change_mask : 0; - - if (full) - this->port_info.change_mask = this->port_info_all; - if (this->port_info.change_mask) { - uint32_t i; - - if (this->port_info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { - for (i = 0; i < this->port_info.n_params; i++) { - if (this->port_params[i].user > 0) { - this->port_params[i].flags ^= SPA_PARAM_INFO_SERIAL; - this->port_params[i].user = 0; - } - } - } - spa_node_emit_port_info(&this->hooks, - SPA_DIRECTION_INPUT, 0, &this->port_info); - this->port_info.change_mask = old; - } -} - static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -348,8 +277,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, info.ns = lat_ns; handle_process_latency(this, &info); } - emit_node_info(this, false); - emit_port_info(this, false); + spa_alsa_emit_node_info(this, false); + spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_ProcessLatency: @@ -362,8 +291,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, handle_process_latency(this, &info); - emit_node_info(this, false); - emit_port_info(this, false); + spa_alsa_emit_node_info(this, false); + spa_alsa_emit_port_info(this, false); break; } default: @@ -425,8 +354,8 @@ impl_node_add_listener(void *object, spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - emit_node_info(this, true); - emit_port_info(this, true); + spa_alsa_emit_node_info(this, true); + spa_alsa_emit_port_info(this, true); spa_hook_list_join(&this->hooks, &save); @@ -673,7 +602,7 @@ static int port_set_format(void *object, } this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; - emit_node_info(this, false); + spa_alsa_emit_node_info(this, false); this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; this->port_info.rate = SPA_FRACTION(1, this->rate); @@ -686,7 +615,7 @@ static int port_set_format(void *object, this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } - emit_port_info(this, false); + spa_alsa_emit_port_info(this, false); return err; } @@ -722,7 +651,7 @@ impl_node_port_set_param(void *object, this->latency[info.direction] = info; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Latency].user++; - emit_port_info(this, false); + spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_Tag: @@ -741,7 +670,7 @@ impl_node_port_set_param(void *object, this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Tag].user++; - emit_port_info(this, false); + spa_alsa_emit_port_info(this, false); } break; } @@ -944,7 +873,12 @@ impl_init(const struct spa_handle_factory *factory, this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + if (this->main_loop == NULL) { + spa_log_error(this->log, "a main loop is needed"); + return -EINVAL; + } if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; diff --git a/spa/plugins/alsa/alsa-pcm-source.c b/spa/plugins/alsa/alsa-pcm-source.c index 4e783aa9..0a8ffec4 100644 --- a/spa/plugins/alsa/alsa-pcm-source.c +++ b/spa/plugins/alsa/alsa-pcm-source.c @@ -8,12 +8,10 @@ #include <spa/node/node.h> #include <spa/node/utils.h> -#include <spa/node/keys.h> #include <spa/utils/keys.h> #include <spa/utils/names.h> #include <spa/utils/list.h> #include <spa/utils/string.h> -#include <spa/monitor/device.h> #include <spa/param/audio/format.h> #include <spa/pod/filter.h> #include <spa/pod/dynamic.h> @@ -32,73 +30,6 @@ static void reset_props(struct props *props) props->use_chmap = DEFAULT_USE_CHMAP; } -static void emit_node_info(struct state *this, bool full) -{ - uint64_t old = full ? this->info.change_mask : 0; - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - struct spa_dict_item items[7]; - uint32_t i, n_items = 0; - char latency[64], period[64], nperiods[64], headroom[64]; - - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Source"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); - if (this->have_format) { - snprintf(latency, sizeof(latency), "%lu/%d", this->buffer_frames / 2, this->rate); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency); - snprintf(period, sizeof(period), "%lu", this->period_frames); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period); - snprintf(nperiods, sizeof(nperiods), "%lu", - this->period_frames != 0 ? this->buffer_frames / this->period_frames : 0); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods); - snprintf(headroom, sizeof(headroom), "%u", this->headroom); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom); - } else { - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, NULL); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", NULL); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", NULL); - items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", NULL); - } - this->info.props = &SPA_DICT_INIT(items, n_items); - - if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { - for (i = 0; i < this->info.n_params; i++) { - if (this->params[i].user > 0) { - this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; - this->params[i].user = 0; - } - } - } - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} - -static void emit_port_info(struct state *this, bool full) -{ - uint64_t old = full ? this->port_info.change_mask : 0; - if (full) - this->port_info.change_mask = this->port_info_all; - if (this->port_info.change_mask) { - uint32_t i; - - if (this->port_info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { - for (i = 0; i < this->port_info.n_params; i++) { - if (this->port_params[i].user > 0) { - this->port_params[i].flags ^= SPA_PARAM_INFO_SERIAL; - this->port_params[i].user = 0; - } - } - } - spa_node_emit_port_info(&this->hooks, - SPA_DIRECTION_OUTPUT, 0, &this->port_info); - this->port_info.change_mask = old; - } -} - - static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -311,8 +242,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, handle_process_latency(this, &info); } - emit_node_info(this, false); - emit_port_info(this, false); + spa_alsa_emit_node_info(this, false); + spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_ProcessLatency: @@ -325,8 +256,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, handle_process_latency(this, &info); - emit_node_info(this, false); - emit_port_info(this, false); + spa_alsa_emit_node_info(this, false); + spa_alsa_emit_port_info(this, false); break; } default: @@ -388,8 +319,8 @@ impl_node_add_listener(void *object, spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - emit_node_info(this, true); - emit_port_info(this, true); + spa_alsa_emit_node_info(this, true); + spa_alsa_emit_port_info(this, true); spa_hook_list_join(&this->hooks, &save); @@ -607,7 +538,7 @@ static int port_set_format(void *object, } this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; - emit_node_info(this, false); + spa_alsa_emit_node_info(this, false); this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; this->port_info.rate = SPA_FRACTION(1, this->rate); @@ -620,7 +551,7 @@ static int port_set_format(void *object, this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } - emit_port_info(this, false); + spa_alsa_emit_port_info(this, false); return err; } @@ -656,7 +587,7 @@ impl_node_port_set_param(void *object, this->latency[info.direction] = info; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Latency].user++; - emit_port_info(this, false); + spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_Tag: @@ -675,7 +606,7 @@ impl_node_port_set_param(void *object, this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Tag].user++; - emit_port_info(this, false); + spa_alsa_emit_port_info(this, false); } break; } @@ -898,7 +829,12 @@ impl_init(const struct spa_handle_factory *factory, this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + if (this->main_loop == NULL) { + spa_log_error(this->log, "a main loop is needed"); + return -EINVAL; + } if (this->data_loop == NULL) { spa_log_error(this->log, "%p: a data loop is needed", this); return -EINVAL; diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index f5e0edaf..e2d7835b 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -13,6 +13,8 @@ #include <spa/utils/result.h> #include <spa/support/system.h> #include <spa/utils/keys.h> +#include <spa/node/keys.h> +#include <spa/monitor/device.h> #include "alsa-pcm.h" @@ -184,6 +186,69 @@ static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, si return 0; } +static struct spa_pod *enum_bind_ctl_propinfo(struct state *state, uint32_t idx, struct spa_pod_builder *b) +{ + char param_name[1024]; + char param_desc[1024]; + snd_ctl_elem_info_t *info = state->bound_ctls[idx].info; + + if (!info) { + // This will end iteration early, so print a warning + spa_log_warn(state->log, "Don't have prop info for bind ctl, bailing"); + return NULL; + } + + snprintf(param_name, sizeof(param_name), "api.alsa.bind-ctl.%s", + snd_ctl_elem_info_get_name(info)); + snprintf(param_desc, sizeof(param_desc), "Value of ALSA control '%s'", + snd_ctl_elem_info_get_name(info)); + + // We don't have meaningful default values + switch (snd_ctl_elem_info_get_type(info)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + return spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(param_name), + SPA_PROP_INFO_description, SPA_POD_String(param_desc), + SPA_PROP_INFO_type, SPA_POD_Bool(false), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + + case SND_CTL_ELEM_TYPE_INTEGER: + return spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(param_name), + SPA_PROP_INFO_description, SPA_POD_String(param_desc), + SPA_PROP_INFO_type, SPA_POD_Int(0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + + case SND_CTL_ELEM_TYPE_INTEGER64: + return spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(param_name), + SPA_PROP_INFO_description, SPA_POD_String(param_desc), + SPA_PROP_INFO_type, SPA_POD_Long(0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + return spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(param_name), + SPA_PROP_INFO_description, SPA_POD_String(param_desc), + SPA_PROP_INFO_type, SPA_POD_Int(0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + + default: + // FIXME: we can probably support bytes but the length seems unknown in the API + spa_log_warn(state->log, "%s ctl '%s' not supported", + snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)), + snd_ctl_elem_info_get_name(info)); + return NULL; + } +} + struct spa_pod *spa_alsa_enum_propinfo(struct state *state, uint32_t idx, struct spa_pod_builder *b) { @@ -346,12 +411,67 @@ struct spa_pod *spa_alsa_enum_propinfo(struct state *state, SPA_PROP_INFO_type, SPA_POD_String(state->clock_name), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; + // While adding params here, update the math in default too default: - return NULL; + idx -= 17; + if (idx <= state->num_bind_ctls) + param = enum_bind_ctl_propinfo(state, idx - 1, b); + else + return NULL; } return param; } +static void add_bind_ctl_param(struct state *state, const snd_ctl_elem_value_t *elem, const snd_ctl_elem_info_t *info, + struct spa_pod_builder *b) +{ + char param_name[1024]; + + snprintf(param_name, sizeof(param_name), "api.alsa.bind-ctl.%s", + snd_ctl_elem_info_get_name(info)); + spa_pod_builder_string(b, param_name); + + switch (snd_ctl_elem_info_get_type(info)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + spa_pod_builder_bool(b, snd_ctl_elem_value_get_boolean(elem, 0)); + break; + + case SND_CTL_ELEM_TYPE_INTEGER: + spa_pod_builder_int(b, snd_ctl_elem_value_get_integer(elem, 0)); + break; + + case SND_CTL_ELEM_TYPE_INTEGER64: + spa_pod_builder_long(b, snd_ctl_elem_value_get_integer64(elem, 0)); + break; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + spa_pod_builder_int(b, snd_ctl_elem_value_get_enumerated(elem, 0)); + break; + + default: + // FIXME: we can probably support bytes but the length seems unknown in the API + spa_log_warn(state->log, "%s ctl '%s' not supported", + snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)), + snd_ctl_elem_info_get_name(info)); + break; + } +} + +static void add_bind_ctl_params(struct state *state, struct spa_pod_builder *b) +{ + int err; + + for (unsigned int i = 0; i < state->num_bind_ctls; i++) { + err = snd_ctl_elem_read(state->ctl, state->bound_ctls[i].value); + if (err < 0) { + spa_log_warn(state->log, "Could not read elem value for '%s': %s", + state->bound_ctls[i].name, snd_strerror(err)); + } + + add_bind_ctl_param(state, state->bound_ctls[i].value, state->bound_ctls[i].info, b); + } +} + int spa_alsa_add_prop_params(struct state *state, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; @@ -419,6 +539,8 @@ int spa_alsa_add_prop_params(struct state *state, struct spa_pod_builder *b) spa_pod_builder_string(b, "clock.name"); spa_pod_builder_string(b, state->clock_name); + add_bind_ctl_params(state, b); + spa_pod_builder_pop(b, &f[0]); return 0; } @@ -498,6 +620,123 @@ static void silence_error_handler(const char *file, int line, { } +static void fill_device_name(struct state *state, const char *params, char device_name[], size_t len) +{ + spa_scnprintf(device_name, len, "%s%s%s", + state->card->ucm_prefix ? state->card->ucm_prefix : "", + state->props.device, params ? params : ""); +} + +static void bind_ctl_event(struct spa_source *source) +{ + // We don't know if a bound element changed or not, so let's find out + struct state *state = source->data; + snd_ctl_elem_value_t *old_value; + bool changed = false; + + snd_ctl_elem_value_alloca(&old_value); + + for (unsigned int i = 0; i < state->num_bind_ctls; i++) { + int err; + + snd_ctl_elem_value_copy(old_value, state->bound_ctls[i].value); + + err = snd_ctl_elem_read(state->ctl, state->bound_ctls[i].value); + if (err < 0) { + spa_log_warn(state->log, "Could not read ctl '%s': %s", + state->bound_ctls[i].name, snd_strerror(err)); + continue; + } + + if (snd_ctl_elem_value_compare(old_value, state->bound_ctls[i].value) != 0) { + // We don't need to check all the ctls, if one changed, + // we'll emit a notification and they'll be read when + // the props are read + spa_log_debug(state->log, "bound ctl '%s' has changed", state->bound_ctls[i].name); + changed = true; + break; + } + } + + if (changed) { + state->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + state->params[NODE_Props].user++; + spa_alsa_emit_node_info(state, false); + } +} + +static void bind_ctls_for_params(struct state *state) +{ + struct pollfd pfds[16]; + int err; + + if (state->num_bind_ctls == 0) + return; + + if (!state->ctl) { + char device_name[256]; + + fill_device_name(state, NULL, device_name, sizeof(device_name)); + + err = snd_ctl_open(&state->ctl, device_name, SND_CTL_NONBLOCK); + if (err < 0) { + spa_log_info(state->log, "%s could not find ctl device: %s", + state->props.device, snd_strerror(err)); + state->ctl = NULL; + return; + } + } + + state->ctl_n_fds = snd_ctl_poll_descriptors_count(state->ctl); + if (state->ctl_n_fds > (int)SPA_N_ELEMENTS(state->ctl_sources)) { + spa_log_warn(state->log, "Too many poll descriptors (%d), listening to a subset", state->ctl_n_fds); + state->ctl_n_fds = SPA_N_ELEMENTS(state->ctl_sources); + } + + if ((err = snd_ctl_poll_descriptors(state->ctl, pfds, state->ctl_n_fds)) < 0) { + spa_log_warn(state->log, "Could not get poll descriptors: %s", snd_strerror(err)); + return; + } + + snd_ctl_subscribe_events(state->ctl, 1); + + for (int i = 0; i < state->ctl_n_fds; i++) { + state->ctl_sources[i].func = bind_ctl_event; + state->ctl_sources[i].data = state; + state->ctl_sources[i].fd = pfds[i].fd; + state->ctl_sources[i].mask = SPA_IO_IN; + state->ctl_sources[i].rmask = 0; + spa_loop_add_source(state->main_loop, &state->ctl_sources[i]); + } + + for (unsigned int i = 0; i < state->num_bind_ctls; i++) { + snd_ctl_elem_id_t *id; + + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_id_set_name(id, state->bound_ctls[i].name); + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM); + + snd_ctl_elem_info_malloc(&state->bound_ctls[i].info); + snd_ctl_elem_info_set_id(state->bound_ctls[i].info, id); + + err = snd_ctl_elem_info(state->ctl, state->bound_ctls[i].info); + if (err < 0) { + spa_log_warn(state->log, "Could not read elem info for '%s': %s", + state->bound_ctls[i].name, snd_strerror(err)); + + snd_ctl_elem_info_free(state->bound_ctls[i].info); + state->bound_ctls[i].info = NULL; + continue; + } + + snd_ctl_elem_value_malloc(&state->bound_ctls[i].value); + snd_ctl_elem_value_set_id(state->bound_ctls[i].value, id); + + spa_log_debug(state->log, "Binding ctl for '%s'", + snd_ctl_elem_info_get_name(state->bound_ctls[i].info)); + } +} + int spa_alsa_init(struct state *state, const struct spa_dict *info) { uint32_t i; @@ -525,6 +764,24 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) state->open_ucm = spa_atob(s); } else if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &state->quantum_limit, 0); + } else if (spa_streq(k, SPA_KEY_API_ALSA_BIND_CTLS)) { + struct spa_json it[2]; + char v[256]; + unsigned int i = 0; + + /* Read a list of ALSA control names to bind as params */ + spa_json_init(&it[0], s, strlen(s)); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], s, strlen(s)); + + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + i < SPA_N_ELEMENTS(state->bound_ctls)) { + strncpy(state->bound_ctls[i].name, v, sizeof(state->bound_ctls[i].name)); + i++; + } + state->num_bind_ctls = i; + + /* We'll do the actual binding after checking the card exists */ } else { alsa_set_param(state, k, s); } @@ -558,6 +815,8 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) state->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; state->rate_limit.burst = 1; + bind_ctls_for_params(state); + return 0; } @@ -578,6 +837,26 @@ int spa_alsa_clear(struct state *state) free(state->tag[0]); free(state->tag[1]); + if (state->ctl) { + for (int i = 0; i < state->ctl_n_fds; i++) { + spa_loop_remove_source(state->main_loop, &state->ctl_sources[i]); + } + + snd_ctl_close(state->ctl); + state->ctl = NULL; + + for (unsigned int i = 0; i < state->num_bind_ctls; i++) { + if (state->bound_ctls[i].info) { + snd_ctl_elem_info_free(state->bound_ctls[i].info); + state->bound_ctls[i].info = NULL; + } + if (state->bound_ctls[i].value) { + snd_ctl_elem_value_free(state->bound_ctls[i].value); + state->bound_ctls[i].value = NULL; + } + } + } + return err; } @@ -589,16 +868,20 @@ static int probe_pitch_ctl(struct state *state, const char* device_name) state->stream == SND_PCM_STREAM_CAPTURE ? "Capture Pitch 1000000" : "Playback Pitch 1000000"; + bool opened = false; int err; snd_lib_error_set_handler(silence_error_handler); - err = snd_ctl_open(&state->ctl, device_name, SND_CTL_NONBLOCK); - if (err < 0) { - spa_log_info(state->log, "%s could not find ctl device: %s", - device_name, snd_strerror(err)); - state->ctl = NULL; - goto error; + if (!state->ctl) { + err = snd_ctl_open(&state->ctl, device_name, SND_CTL_NONBLOCK); + if (err < 0) { + spa_log_info(state->log, "%s could not find ctl device: %s", + device_name, snd_strerror(err)); + state->ctl = NULL; + goto error; + } + opened = true; } snd_ctl_elem_id_alloca(&id); @@ -616,9 +899,11 @@ static int probe_pitch_ctl(struct state *state, const char* device_name) snd_ctl_elem_value_free(state->pitch_elem); state->pitch_elem = NULL; - snd_ctl_close(state->ctl); - state->ctl = NULL; - goto error; + if (opened) { + snd_ctl_close(state->ctl); + state->ctl = NULL; + goto error; + } } snd_ctl_elem_value_set_integer(state->pitch_elem, 0, 1000000); @@ -662,9 +947,7 @@ int spa_alsa_open(struct state *state, const char *params) if (state->opened) return 0; - spa_scnprintf(device_name, sizeof(device_name), "%s%s%s", - state->card->ucm_prefix ? state->card->ucm_prefix : "", - props->device, params ? params : ""); + fill_device_name(state, params, device_name, sizeof(device_name)); spa_scnprintf(state->name, sizeof(state->name), "%s%s", props->device, state->stream == SND_PCM_STREAM_CAPTURE ? "c" : "p"); @@ -757,8 +1040,11 @@ int spa_alsa_close(struct state *state) snd_ctl_elem_value_free(state->pitch_elem); state->pitch_elem = NULL; - snd_ctl_close(state->ctl); - state->ctl = NULL; + // Close it unless we've got some bind_ctls we're listening to + if (state->ctl_n_fds == 0) { + snd_ctl_close(state->ctl); + state->ctl = NULL; + } } return err; @@ -1982,6 +2268,7 @@ static void reset_buffers(struct state *this) spa_list_init(&this->free); spa_list_init(&this->ready); + this->ready_offset = 0; for (i = 0; i < this->n_buffers; i++) { struct buffer *b = &this->buffers[i]; @@ -2032,7 +2319,7 @@ static int do_prepare(struct state *state) static inline int do_drop(struct state *state) { int res; - spa_log_debug(state->log, "%p: snd_pcm_drop %u", state, state->linked); + spa_log_debug(state->log, "%p: snd_pcm_drop linked:%u", state, state->linked); if (!state->linked && (res = snd_pcm_drop(state->hndl)) < 0) { spa_log_error(state->log, "%s: snd_pcm_drop: %s", state->name, snd_strerror(res)); @@ -2045,7 +2332,7 @@ static inline int do_start(struct state *state) { int res; if (SPA_UNLIKELY(!state->alsa_started)) { - spa_log_debug(state->log, "%p: snd_pcm_start %u", state, state->linked); + spa_log_debug(state->log, "%p: snd_pcm_start linked:%u", state, state->linked); if (!state->linked && (res = snd_pcm_start(state->hndl)) < 0) { spa_log_error(state->log, "%s: snd_pcm_start: %s", state->name, snd_strerror(res)); @@ -2058,9 +2345,9 @@ static inline int do_start(struct state *state) static inline int check_position_config(struct state *state); -static int alsa_recover(struct state *state, int err) +static int alsa_recover(struct state *state) { - int res, st; + int res, st, retry = 0; snd_pcm_status_t *status; struct state *driver, *follower; @@ -2100,10 +2387,12 @@ static int alsa_recover(struct state *state, int err) case SND_PCM_STATE_SUSPENDED: spa_log_info(state->log, "%s: recover from state %s", state->name, snd_pcm_state_name(st)); - res = snd_pcm_resume(state->hndl); + while (retry++ < 5 && (res = snd_pcm_resume(state->hndl)) == -EAGAIN) + /* wait until suspend flag is released */ + poll(NULL, 0, 1000); if (res >= 0) - return res; - err = -ESTRPIPE; + return res; + /* try to drop and prepare below */ break; default: spa_log_error(state->log, "%s: recover from error state %s", @@ -2112,11 +2401,6 @@ static int alsa_recover(struct state *state, int err) } recover: - if (SPA_UNLIKELY((res = snd_pcm_recover(state->hndl, err, true)) < 0)) { - spa_log_error(state->log, "%s: snd_pcm_recover error: %s", - state->name, snd_strerror(res)); - return res; - } if (state->driver && state->linked) driver = state->driver; else @@ -2139,8 +2423,7 @@ recover: if (follower != driver && follower->linked) do_start(follower); } - - return res; + return 0; } static inline snd_pcm_sframes_t alsa_avail(struct state *state) @@ -2159,7 +2442,7 @@ static int get_avail(struct state *state, uint64_t current_time, snd_pcm_uframes snd_pcm_sframes_t avail; if (SPA_UNLIKELY((avail = alsa_avail(state)) < 0)) { - if ((res = alsa_recover(state, avail)) < 0) + if ((res = alsa_recover(state)) < 0) return res; if ((avail = alsa_avail(state)) < 0) { if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) >= 0) { @@ -2248,6 +2531,15 @@ static int get_status(struct state *state, uint64_t current_time, snd_pcm_uframe return 0; } + +static uint64_t get_time_ns(struct state *state) +{ + struct timespec now; + if (spa_system_clock_gettime(state->data_system, CLOCK_MONOTONIC, &now) < 0) + return 0; + return SPA_TIMESPEC_TO_NSEC(&now); +} + static int update_time(struct state *state, uint64_t current_time, snd_pcm_sframes_t delay, snd_pcm_sframes_t target, bool follower) { @@ -2255,8 +2547,15 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram int32_t diff; if (state->disable_tsched && !follower) { - err = (int64_t)(current_time - state->next_time); - err = err / 1e9 * state->rate; + uint64_t now = get_time_ns(state); + + if (SPA_UNLIKELY(state->dll.bw == 0.0)) { + current_time = now; + err = 0.0; + } else { + err = (int64_t)(now - current_time); + err = err / 1e9 * state->rate; + } } else { if (state->stream == SND_PCM_STREAM_PLAYBACK) err = delay - target; @@ -2351,7 +2650,8 @@ static int setup_matching(struct state *state) if (spa_streq(state->position->clock.name, state->clock_name)) state->matching = false; - state->resample = !state->pitch_elem && (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); + state->resample = !state->pitch_elem && + (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); recalc_headroom(state); spa_log_info(state->log, "driver clock:'%s'@%d our clock:'%s'@%d matching:%d resample:%d", @@ -2404,7 +2704,8 @@ static inline int check_position_config(struct state *state) state->threshold = SPA_SCALE32_UP(state->driver_duration, state->rate, state->driver_rate.denom); state->max_error = SPA_MAX(256.0f, state->threshold / 2.0f); state->max_resync = SPA_MIN(state->threshold, state->max_error); - state->resample = ((uint32_t)state->rate != state->driver_rate.denom) || state->matching; + state->resample = !state->pitch_elem && + (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); state->alsa_sync = true; } return 0; @@ -2420,7 +2721,7 @@ static int alsa_write_sync(struct state *state, uint64_t current_time) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { - spa_log_error(state->log, "get_status error"); + spa_log_error(state->log, "get_status error: %s", spa_strerror(res)); state->next_time += state->threshold * 1e9 / state->rate; return res; } @@ -2482,7 +2783,7 @@ again: if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0)) { spa_log_error(state->log, "%s: snd_pcm_mmap_begin error: %s", state->name, snd_strerror(res)); - alsa_recover(state, res); + alsa_recover(state); return res; } spa_log_trace_fp(state->log, "%p: begin offset:%ld avail:%ld threshold:%d", @@ -2681,7 +2982,7 @@ static int alsa_read_sync(struct state *state, uint64_t current_time) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { - spa_log_error(state->log, "get_status error"); + spa_log_error(state->log, "get_status error: %s", spa_strerror(res)); state->next_time += state->threshold * 1e9 / state->rate; return res; } @@ -2748,7 +3049,7 @@ static int alsa_read_frames(struct state *state) if ((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &avail)) < 0) { spa_log_error(state->log, "%s: snd_pcm_mmap_begin error: %s", state->name, snd_strerror(res)); - alsa_recover(state, res); + alsa_recover(state); return res; } spa_log_trace_fp(state->log, "%p: begin offs:%ld frames:%ld avail:%ld thres:%d", state, @@ -2881,14 +3182,6 @@ static int capture_ready(struct state *state) return 0; } -static uint64_t get_time_ns(struct state *state) -{ - struct timespec now; - if (spa_system_clock_gettime(state->data_system, CLOCK_MONOTONIC, &now) < 0) - return 0; - return SPA_TIMESPEC_TO_NSEC(&now); -} - static void alsa_wakeup_event(struct spa_source *source) { struct state *state = source->data, *follower; @@ -2900,8 +3193,6 @@ static void alsa_wakeup_event(struct spa_source *source) int err; unsigned short revents; - current_time = get_time_ns(state); - for (int i = 0; i < state->n_fds; i++) { state->pfds[i].revents = state->source[i].rmask; /* Reset so that we only handle all our sources' events once */ @@ -2919,6 +3210,11 @@ static void alsa_wakeup_event(struct spa_source *source) spa_log_trace_fp(state->log, "Woken up with no work to do"); return; } + if (revents & POLLERR) { + spa_log_trace_fp(state->log, "poll error"); + if ((res = alsa_recover(state)) < 0) + return; + } } else { if (SPA_LIKELY(state->started)) { if (SPA_UNLIKELY((res = spa_system_timerfd_read(state->data_system, @@ -2932,8 +3228,8 @@ static void alsa_wakeup_event(struct spa_source *source) return; } } - current_time = state->next_time; } + current_time = state->next_time; /* first do all the sync */ if (state->stream == SND_PCM_STREAM_CAPTURE) @@ -3227,3 +3523,74 @@ int spa_alsa_pause(struct state *state) return 0; } + +void spa_alsa_emit_node_info(struct state *state, bool full) +{ + uint64_t old = full ? state->info.change_mask : 0; + + if (full) + state->info.change_mask = state->info_all; + if (state->info.change_mask) { + struct spa_dict_item items[7]; + uint32_t i, n_items = 0; + char latency[64], period[64], nperiods[64], headroom[64]; + + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, + state->stream == SND_PCM_STREAM_PLAYBACK ? "Audio/Sink" : "Audio/Source"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); + if (state->have_format) { + snprintf(latency, sizeof(latency), "%lu/%d", state->buffer_frames / 2, state->rate); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency); + snprintf(period, sizeof(period), "%lu", state->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period); + snprintf(nperiods, sizeof(nperiods), "%lu", + state->period_frames != 0 ? state->buffer_frames / state->period_frames : 0); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods); + snprintf(headroom, sizeof(headroom), "%u", state->headroom); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom); + } else { + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, NULL); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", NULL); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", NULL); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", NULL); + } + state->info.props = &SPA_DICT_INIT(items, n_items); + + if (state->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < state->info.n_params; i++) { + if (state->params[i].user > 0) { + state->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + state->params[i].user = 0; + } + } + } + spa_node_emit_info(&state->hooks, &state->info); + + state->info.change_mask = old; + } +} + +void spa_alsa_emit_port_info(struct state *state, bool full) +{ + uint64_t old = full ? state->port_info.change_mask : 0; + + if (full) + state->port_info.change_mask = state->port_info_all; + if (state->port_info.change_mask) { + uint32_t i; + + if (state->port_info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < state->port_info.n_params; i++) { + if (state->port_params[i].user > 0) { + state->port_params[i].flags ^= SPA_PARAM_INFO_SERIAL; + state->port_params[i].user = 0; + } + } + } + spa_node_emit_port_info(&state->hooks, + state->stream == SND_PCM_STREAM_PLAYBACK ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT, + 0, &state->port_info); + state->port_info.change_mask = old; + } +} diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 92b35579..c3cfe0e5 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -92,6 +92,12 @@ struct rt_state { unsigned int following:1; }; +struct bound_ctl { + char name[256]; + snd_ctl_elem_info_t *info; + snd_ctl_elem_value_t *value; +}; + struct state { struct spa_handle handle; struct spa_node node; @@ -99,6 +105,7 @@ struct state { struct spa_log *log; struct spa_system *data_system; struct spa_loop *data_loop; + struct spa_loop *main_loop; FILE *log_file; struct spa_ratelimit rate_limit; @@ -240,11 +247,19 @@ struct state { struct spa_pod *tag[2]; - /* Rate match via an ALSA ctl */ + /* for rate match and bind ctls */ snd_ctl_t *ctl; + + /* Rate match via an ALSA ctl */ snd_ctl_elem_value_t *pitch_elem; double last_rate; + /* ALSA ctls exposed as params */ + unsigned int num_bind_ctls; + struct bound_ctl bound_ctls[16]; + struct spa_source ctl_sources[MAX_POLL]; + int ctl_n_fds; + struct spa_list link; struct spa_list followers; @@ -282,6 +297,9 @@ int spa_alsa_skip(struct state *state); void spa_alsa_recycle_buffer(struct state *state, uint32_t buffer_id); +void spa_alsa_emit_node_info(struct state *state, bool full); +void spa_alsa_emit_port_info(struct state *state, bool full); + static inline uint32_t spa_alsa_format_from_name(const char *name, size_t len) { int i; diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 19e0af6b..1cffbedd 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -14,6 +14,7 @@ #include <spa/param/audio/format.h> #include <spa/param/audio/format-utils.h> #include <spa/utils/string.h> +#include <spa/debug/log.h> #include <lc3.h> @@ -22,6 +23,11 @@ #define MAX_PACS 64 +static struct spa_log *log; +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs.lc3"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + struct impl { lc3_encoder_t enc[LC3_MAX_CHANNELS]; lc3_decoder_t dec[LC3_MAX_CHANNELS]; @@ -38,6 +44,7 @@ struct impl { struct pac_data { const uint8_t *data; size_t size; + int index; uint32_t locations; }; @@ -126,7 +133,31 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, return data - caps; } -static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS]) +static void debugc_ltv(struct spa_debug_context *debug_ctx, int pac, struct ltv *ltv) +{ + switch (ltv->len) { + case 0: + spa_debugc(debug_ctx, "PAC %d: --", pac); + break; + case 2: + spa_debugc(debug_ctx, "PAC %d: 0x%02x %x", pac, ltv->type, ltv->value[0]); + break; + case 3: + spa_debugc(debug_ctx, "PAC %d: 0x%02x %x %x", pac, ltv->type, ltv->value[0], ltv->value[1]); + break; + case 5: + spa_debugc(debug_ctx, "PAC %d: 0x%02x %x %x %x %x", pac, ltv->type, + ltv->value[0], ltv->value[1], ltv->value[2], ltv->value[3]); + break; + default: + spa_debugc(debug_ctx, "PAC %d: 0x%02x", pac, ltv->type); + spa_debugc_mem(debug_ctx, 7, ltv->value, ltv->len - 1); + break; + } +} + +static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS], + struct spa_debug_context *debug_ctx) { /* * BlueZ capabilites for the same codec may contain multiple @@ -145,10 +176,11 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da break; ++pac; - pacs[pac] = (struct pac_data){ data + 1, 0 }; + pacs[pac] = (struct pac_data){ data + 1, 0, pac }; } else if (ltv->len >= data_size) { return -EINVAL; } else { + debugc_ltv(debug_ctx, pac, ltv); pacs[pac].size += ltv->len + 1; } data_size -= ltv->len + 1; @@ -172,13 +204,13 @@ static uint8_t get_num_channels(uint32_t channels) return num; } -static int select_channels(uint8_t channels, uint32_t locations, uint32_t *mapping) +static int select_channels(uint8_t channels, uint32_t locations, uint32_t *mapping, unsigned int max_channels) { - unsigned int i, num; + unsigned int i, num = 0; - if (channels & LC3_CHAN_2) + if ((channels & LC3_CHAN_2) && max_channels >= 2) num = 2; - else if (channels & LC3_CHAN_1) + else if ((channels & LC3_CHAN_1) && max_channels >= 1) num = 1; else return -1; @@ -202,12 +234,13 @@ static int select_channels(uint8_t channels, uint32_t locations, uint32_t *mappi return 0; } -static bool select_config(bap_lc3_t *conf, const struct pac_data *pac) +static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx) { const uint8_t *data = pac->data; size_t data_size = pac->size; uint16_t framelen_min = 0, framelen_max = 0; int max_frames = -1; + uint8_t channels = 0; if (!data_size) return false; @@ -221,8 +254,10 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac) while (data_size > 0) { struct ltv *ltv = (struct ltv *)data; - if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size) + if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size) { + spa_debugc(debug_ctx, "invalid LTV data"); return false; + } switch (ltv->type) { case LC3_TYPE_FREQ: @@ -237,8 +272,10 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac) conf->rate = LC3_CONFIG_FREQ_16KHZ; else if (rate & LC3_FREQ_8KHZ) conf->rate = LC3_CONFIG_FREQ_8KHZ; - else + else { + spa_debugc(debug_ctx, "unsupported rate: 0x%04x", rate); return false; + } } break; case LC3_TYPE_DUR: @@ -249,17 +286,16 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac) conf->frame_duration = LC3_CONFIG_DURATION_10; else if (duration & LC3_DUR_7_5) conf->frame_duration = LC3_CONFIG_DURATION_7_5; - else + else { + spa_debugc(debug_ctx, "unsupported duration: 0x%02x", duration); return false; + } } break; case LC3_TYPE_CHAN: spa_return_val_if_fail(ltv->len == 2, false); { - uint8_t channels = ltv->value[0]; - - if (select_channels(channels, pac->locations, &conf->channels) < 0) - return false; + channels = ltv->value[0]; } break; case LC3_TYPE_FRAMELEN: @@ -272,22 +308,35 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac) max_frames = ltv->value[0]; break; default: - return false; + spa_debugc(debug_ctx, "unknown LTV type: 0x%02x", ltv->type); + break; } data_size -= ltv->len + 1; data += ltv->len + 1; } + if (select_channels(channels, pac->locations, &conf->channels, max_frames) < 0) { + spa_debugc(debug_ctx, "invalid channel configuration: 0x%02x %u", + channels, max_frames); + return false; + } + /* Default: 1 per channel (BAP v1.0.1 Sec 4.3.1) */ if (max_frames < 0) max_frames = get_num_channels(conf->channels); - if (max_frames < get_num_channels(conf->channels)) + if (max_frames < get_num_channels(conf->channels)) { + spa_debugc(debug_ctx, "invalid max frames per SDU: %u", max_frames); return false; + } - if (framelen_min < LC3_MIN_FRAME_BYTES || framelen_max > LC3_MAX_FRAME_BYTES) + if (framelen_min < LC3_MIN_FRAME_BYTES || framelen_max > LC3_MAX_FRAME_BYTES) { + spa_debugc(debug_ctx, "invalid framelen: %u %u", framelen_min, framelen_max); return false; - if (conf->frame_duration == 0xFF || !conf->rate) + } + if (conf->frame_duration == 0xFF || !conf->rate) { + spa_debugc(debug_ctx, "no frame duration or rate"); return false; + } /* BAP v1.0.1 Table 5.2; high-reliability */ switch (conf->rate) { @@ -316,7 +365,8 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac) conf->framelen = 30; /* 8_2_2 */ break; default: - return false; + spa_debugc(debug_ctx, "invalid rate"); + return false; } return true; @@ -400,8 +450,12 @@ static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, in return b - a; PREFER_BOOL(conf->channels & LC3_CHAN_2); + PREFER_BOOL(conf->channels & LC3_CHAN_1); PREFER_BOOL(conf->rate & (LC3_CONFIG_FREQ_48KHZ | LC3_CONFIG_FREQ_24KHZ | LC3_CONFIG_FREQ_16KHZ | LC3_CONFIG_FREQ_8KHZ)); PREFER_BOOL(conf->rate & LC3_CONFIG_FREQ_48KHZ); + PREFER_BOOL(conf->rate & LC3_CONFIG_FREQ_24KHZ); + PREFER_BOOL(conf->rate & LC3_CONFIG_FREQ_16KHZ); + PREFER_BOOL(conf->rate & LC3_CONFIG_FREQ_8KHZ); return 0; @@ -413,11 +467,12 @@ static int pac_cmp(const void *p1, const void *p2) { const struct pac_data *pac1 = p1; const struct pac_data *pac2 = p2; + struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log, SPA_LOG_LEVEL_TRACE); bap_lc3_t conf1, conf2; int res1, res2; - res1 = select_config(&conf1, pac1) ? (int)sizeof(bap_lc3_t) : -EINVAL; - res2 = select_config(&conf2, pac2) ? (int)sizeof(bap_lc3_t) : -EINVAL; + res1 = select_config(&conf1, pac1, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; + res2 = select_config(&conf2, pac2, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; return conf_cmp(&conf1, res1, &conf2, res2); } @@ -432,6 +487,7 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, bap_lc3_t conf; uint8_t *data = config; uint32_t locations = 0; + struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log, SPA_LOG_LEVEL_TRACE); int i; if (caps == NULL) @@ -441,21 +497,29 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, for (i = 0; i < (int)settings->n_items; ++i) if (spa_streq(settings->items[i].key, "bluez5.bap.locations")) sscanf(settings->items[i].value, "%"PRIu32, &locations); + + if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug"))) + debug_ctx = SPA_LOG_DEBUG_INIT(log, SPA_LOG_LEVEL_DEBUG); } /* Select best conf from those possible */ - npacs = parse_bluez_pacs(caps, caps_size, pacs); - if (npacs < 0) + npacs = parse_bluez_pacs(caps, caps_size, pacs, &debug_ctx.ctx); + if (npacs < 0) { + spa_debugc(&debug_ctx.ctx, "malformed PACS"); return npacs; - else if (npacs == 0) + } else if (npacs == 0) { + spa_debugc(&debug_ctx.ctx, "no PACS"); return -EINVAL; + } for (i = 0; i < npacs; ++i) pacs[i].locations = locations; qsort(pacs, npacs, sizeof(struct pac_data), pac_cmp); - if (!select_config(&conf, &pacs[0])) + spa_debugc(&debug_ctx.ctx, "selected PAC %d", pacs[0].index); + + if (!select_config(&conf, &pacs[0], &debug_ctx.ctx)) return -ENOTSUP; data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate); @@ -556,12 +620,13 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, spa_pod_builder_int(b, 8000); spa_pod_builder_int(b, 8000); } - if (i == 0) - return -EINVAL; if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); + if (i == 0) + return -EINVAL; + res = channels_to_positions(conf.channels, position); if (res == 0) return -EINVAL; @@ -674,6 +739,8 @@ static int codec_get_qos(const struct media_codec *codec, if (endpoint_qos->latency >= 0x0005 && endpoint_qos->latency <= 0x0FA0) /* Values outside the range are RFU */ qos->latency = endpoint_qos->latency; + if (endpoint_qos->retransmission) + qos->retransmission = endpoint_qos->retransmission; if (endpoint_qos->delay_min) qos->delay = SPA_MAX(qos->delay, endpoint_qos->delay_min); if (endpoint_qos->delay_max) @@ -889,6 +956,12 @@ static int codec_increase_bitpool(void *data) return -ENOTSUP; } +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &log_topic); +} + const struct media_codec bap_codec_lc3 = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3, .name = "lc3", @@ -910,7 +983,8 @@ const struct media_codec bap_codec_lc3 = { .start_decode = codec_start_decode, .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, - .increase_bitpool = codec_increase_bitpool + .increase_bitpool = codec_increase_bitpool, + .set_log = codec_set_log, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index e5207d98..bb6e0a8b 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -876,7 +876,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe bool sink; const char *err_msg = "Unknown error"; struct spa_dict settings; - struct spa_dict_item setting_items[SPA_N_ELEMENTS(monitor->global_setting_items) + 1]; + struct spa_dict_item setting_items[SPA_N_ELEMENTS(monitor->global_setting_items) + 2]; int i; const char *endpoint_path = NULL; @@ -933,8 +933,9 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe for (i = 0; i < (int)monitor->global_settings.n_items; ++i) setting_items[i] = monitor->global_settings.items[i]; - setting_items[i] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations); - settings = SPA_DICT_INIT(setting_items, monitor->global_settings.n_items + 1); + setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations); + setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true"); + settings = SPA_DICT_INIT(setting_items, i); conf_size = codec->select_config(codec, 0, caps, caps_size, &monitor->default_audio_info, &settings, config); if (conf_size < 0) { @@ -971,6 +972,11 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe goto error_invalid; } + spa_log_debug(monitor->log, "select qos: interval:%d framing:%d phy:%d sdu:%d " + "rtn:%d latency:%d delay:%d target_latency:%d", + qos.interval, qos.framing, qos.phy, qos.sdu, qos.retransmission, + qos.latency, (int)qos.delay, qos.target_latency); + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &entry_key); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "a{sv}", &variant); @@ -3465,6 +3471,9 @@ static int transport_create_iso_io(struct spa_bt_transport *transport) SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE))) continue; + if (t->device->adapter != transport->device->adapter) + continue; + if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { if (t->bap_big != transport->bap_big) @@ -3587,10 +3596,10 @@ finish: /* For broadcast there initiator moves the transport state to SPA_BT_TRANSPORT_STATE_ACTIVE */ if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { - spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); + spa_bt_transport_set_state(t_linked, SPA_BT_TRANSPORT_STATE_ACTIVE); } else { if (!transport->bap_initiator) - spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); + spa_bt_transport_set_state(t_linked, SPA_BT_TRANSPORT_STATE_ACTIVE); } } diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index 6ac2301e..3e7e139e 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -173,7 +173,7 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo #undef MEDIA_CODEC_FACTORY_LIB }; - impl = calloc(sizeof(struct impl), 1); + impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return NULL; diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index 68ceb6b6..01391695 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -37,7 +37,7 @@ struct group { struct spa_source source; struct spa_list streams; int timerfd; - uint8_t cig; + uint8_t id; uint64_t next; uint64_t duration; uint32_t paused; @@ -155,7 +155,7 @@ static void group_on_timeout(struct spa_source *source) if ((res = spa_system_timerfd_read(group->data_system, group->timerfd, &exp)) < 0) { if (res != -EAGAIN) spa_log_warn(group->log, "%p: ISO group:%u error reading timerfd: %s", - group, group->cig, spa_strerror(res)); + group, group->id, spa_strerror(res)); return; } @@ -178,7 +178,7 @@ static void group_on_timeout(struct spa_source *source) if (group->paused) { --group->paused; - spa_log_debug(group->log, "%p: ISO group:%d paused:%u", group, group->cig, group->paused); + spa_log_debug(group->log, "%p: ISO group:%u paused:%u", group, group->id, group->paused); } /* Produce output */ @@ -194,7 +194,7 @@ static void group_on_timeout(struct spa_source *source) } if (stream->this.size == 0) { spa_log_debug(group->log, "%p: ISO group:%u miss fd:%d", - group, group->cig, stream->fd); + group, group->id, stream->fd); if (stream_silence(stream) < 0) { fail = true; continue; @@ -208,7 +208,7 @@ static void group_on_timeout(struct spa_source *source) } spa_log_trace(group->log, "%p: ISO group:%u sent fd:%d size:%u ts:%u idle:%d res:%d", - group, group->cig, stream->fd, (unsigned)stream->this.size, + group, group->id, stream->fd, (unsigned)stream->this.size, (unsigned)stream->this.timestamp, stream->idle, res); stream->this.size = 0; @@ -243,19 +243,29 @@ static struct group *group_create(struct spa_bt_transport *t, struct spa_log *log, struct spa_loop *data_loop, struct spa_system *data_system) { struct group *group; + uint8_t id; if (t->bap_interval <= 5000) { errno = EINVAL; return NULL; } + if (t->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) { + id = t->bap_cig; + } else if (t->profile & (SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { + id = t->bap_big; + } else { + errno = EINVAL; + return NULL; + } + group = calloc(1, sizeof(struct group)); if (group == NULL) return NULL; spa_log_topic_init(log, &log_topic); - group->cig = t->bap_cig; + group->id = id; group->log = log; group->data_loop = data_loop; group->data_system = data_system; diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index df8a2e0b..63d9cd76 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1405,6 +1405,21 @@ static int impl_node_send_command(void *object, const struct spa_command *comman static void emit_node_info(struct impl *this, bool full) { + char node_group_buf[256]; + char *node_group = NULL; + + if (this->transport && (this->transport->profile & SPA_BT_PROFILE_BAP_SINK)) { + spa_scnprintf(node_group_buf, sizeof(node_group_buf), "bluez-iso-%s-cig-%d", + this->transport->device->adapter->address, + this->transport->bap_cig); + node_group = node_group_buf; + } else if (this->transport && (this->transport->profile & SPA_BT_PROFILE_BAP_BROADCAST_SINK)) { + spa_scnprintf(node_group_buf, sizeof(node_group_buf), "bluez-iso-%s-big-%d", + this->transport->device->adapter->address, + this->transport->bap_big); + node_group = node_group_buf; + } + struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Sink/Internal" : @@ -1412,6 +1427,7 @@ static void emit_node_info(struct impl *this, bool full) { "media.name", ((this->transport && this->transport->device->name) ? this->transport->device->name : this->codec->bap ? "BAP" : "A2DP" ) }, { SPA_KEY_NODE_DRIVER, this->is_output ? "true" : "false" }, + { "node.group", node_group }, }; uint64_t old = full ? this->info.change_mask : 0; if (full) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 70b7c144..0abf2f61 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -106,7 +106,7 @@ static int emit_info(struct impl *impl, bool full) uint32_t n_items = 0; struct spa_device_info info; struct spa_param_info params[2]; - char path[256], model[256], name[256], devices_str[128]; + char path[256], name[256], devices_str[128]; struct spa_strbuf buf; info = SPA_DEVICE_INFO_INIT(); @@ -123,9 +123,10 @@ static int emit_info(struct impl *impl, bool full) if (auto location = cameraLoc(impl->camera.get())) ADD_ITEM(SPA_KEY_API_LIBCAMERA_LOCATION, location); - snprintf(model, sizeof(model), "%s", cameraModel(impl->camera.get()).c_str()); - ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_NAME, model); - ADD_ITEM(SPA_KEY_DEVICE_DESCRIPTION, model); + const auto model = cameraModel(impl->camera.get()); + ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_NAME, model.c_str()); + ADD_ITEM(SPA_KEY_DEVICE_DESCRIPTION, model.c_str()); + snprintf(name, sizeof(name), "libcamera_device.%s", impl->device_id.c_str()); ADD_ITEM(SPA_KEY_DEVICE_NAME, name); diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 8a08dca2..3d927341 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -26,6 +26,7 @@ #include <spa/node/keys.h> #include <spa/param/video/format-utils.h> #include <spa/param/param.h> +#include <spa/param/latency-utils.h> #include <spa/control/control.h> #include <spa/pod/filter.h> @@ -96,7 +97,8 @@ struct port { #define PORT_IO 3 #define PORT_Format 4 #define PORT_Buffers 5 -#define N_PORT_PARAMS 6 +#define PORT_Latency 6 +#define N_PORT_PARAMS 7 struct spa_param_info params[N_PORT_PARAMS]; uint32_t fmt_index = 0; @@ -114,6 +116,7 @@ struct port { params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READ); info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; info.params = params; @@ -152,6 +155,8 @@ struct impl { struct spa_io_position *position = nullptr; struct spa_io_clock *clock = nullptr; + struct spa_latency_info latency[2]; + std::shared_ptr<CameraManager> manager; std::shared_ptr<Camera> camera; @@ -601,6 +606,15 @@ next: return 0; } break; + case SPA_PARAM_Latency: + switch (result.index) { + case 0: case 1: + param = spa_latency_build(&b, id, &impl->latency[result.index]); + break; + default: + return 0; + } + break; default: return -ENOENT; } @@ -961,6 +975,9 @@ impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, info.flags = SPA_NODE_FLAG_RT; info.params = params; info.n_params = N_NODE_PARAMS; + + latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); } static size_t diff --git a/spa/plugins/support/evl-system.c b/spa/plugins/support/evl-system.c index 336b1080..a5cb9fd5 100644 --- a/spa/plugins/support/evl-system.c +++ b/spa/plugins/support/evl-system.c @@ -11,6 +11,7 @@ #include <fcntl.h> #include <sys/eventfd.h> #include <sys/signalfd.h> +#include <pthread.h> #include <evl/evl.h> #include <evl/timer.h> @@ -32,6 +33,7 @@ struct poll_entry { int fd; uint32_t events; void *data; + unsigned attached:1; }; struct impl { @@ -45,6 +47,7 @@ struct impl { uint32_t n_xbuf; int attached; + pthread_t thread; int pid; }; @@ -110,6 +113,21 @@ static int impl_pollfd_create(void *object, int flags) return retval; } +static inline struct poll_entry *find_free(struct impl *impl) +{ + uint32_t i; + for (i = 0; i < impl->n_entries; i++) { + struct poll_entry *e = &impl->entries[i]; + if (e->fd == -1) + return e; + } + if (impl->n_entries == MAX_POLL) { + errno = ENOSPC; + return NULL; + } + return &impl->entries[impl->n_entries++]; +} + static inline struct poll_entry *find_entry(struct impl *impl, int pfd, int fd) { uint32_t i; @@ -121,20 +139,36 @@ static inline struct poll_entry *find_entry(struct impl *impl, int pfd, int fd) return NULL; } +static int attach_entry(struct impl *impl, struct poll_entry *e) +{ + if (!e->attached && e->fd != -1) { + int res; + + res = evl_add_pollfd(e->pfd, e->fd, e->events, evl_nil); + if (res < 0) + return res; + + e->attached = true; + } + return 0; +} + static int impl_pollfd_add(void *object, int pfd, int fd, uint32_t events, void *data) { struct impl *impl = object; struct poll_entry *e; + int res = 0; - if (impl->n_entries == MAX_POLL) - return -ENOSPC; + if ((e = find_free(impl)) == NULL) + return -errno; - e = &impl->entries[impl->n_entries++]; e->pfd = pfd; e->fd = fd; e->events = events; e->data = data; - return evl_add_pollfd(pfd, fd, e->events, evl_nil); + if (impl->attached != 0) + attach_entry(impl, e); + return res; } static int impl_pollfd_mod(void *object, int pfd, int fd, uint32_t events, void *data) @@ -162,6 +196,7 @@ static int impl_pollfd_del(void *object, int pfd, int fd) e->pfd = -1; e->fd = -1; + e->attached = false; return evl_del_pollfd(pfd, fd); } @@ -178,6 +213,12 @@ static int impl_pollfd_wait(void *object, int pfd, if (res < 0) return res; impl->attached = res; + impl->thread = pthread_self(); + + for (i = 0; i < (int)impl->n_entries; i++) { + struct poll_entry *e = &impl->entries[i]; + attach_entry(impl, e); + } } if (timeout == -1) { @@ -259,12 +300,14 @@ static int impl_eventfd_create(void *object, int flags) { struct impl *impl = object; int res, fl; + struct evl_flags flg; - fl = EVL_CLONE_PRIVATE; + fl = EVL_CLONE_PUBLIC; if (flags & SPA_FD_NONBLOCK) fl |= EVL_CLONE_NONBLOCK; - res = evl_create_xbuf(1024, 1024, fl, "xbuf-%d-%p-%d", impl->pid, impl, impl->n_xbuf); + res = evl_create_flags(&flg, EVL_CLOCK_MONOTONIC, 0, fl, + "flags-%d-%p-%d", impl->pid, impl, impl->n_xbuf); if (res < 0) return res; @@ -275,14 +318,35 @@ static int impl_eventfd_create(void *object, int flags) static int impl_eventfd_write(void *object, int fd, uint64_t count) { - if (write(fd, &count, sizeof(uint64_t)) != sizeof(uint64_t)) - return -errno; - return 0; + int res; + int flags = count; + struct impl *impl = object; + pthread_t tid = pthread_self(); + + if (impl->thread != tid) + res = write(fd, &flags, sizeof(flags)); + else + res = oob_write(fd, &flags, sizeof(flags)); + + if (res != sizeof(flags)) + res = -errno; + return res; } static int impl_eventfd_read(void *object, int fd, uint64_t *count) { - if (oob_read(fd, count, sizeof(uint64_t)) != sizeof(uint64_t)) + int res; + int flags; + struct impl *impl = object; + pthread_t tid = pthread_self(); + + if (impl->thread != tid) + res = read(fd, &flags, sizeof(flags)); + else + res = oob_read(fd, &flags, sizeof(flags)); + + *count = flags; + if (res != sizeof(flags)) return -errno; return 0; } @@ -400,12 +464,12 @@ impl_init(const struct spa_handle_factory *factory, impl->pid = getpid(); - if ((res = evl_attach_self("evl-system-%d-%p", impl->pid, impl)) < 0) { + if ((res = evl_init()) < 0) { spa_log_error(impl->log, NAME " %p: init failed: %s", impl, spa_strerror(res)); return res; } - spa_log_debug(impl->log, NAME " %p: initialized", impl); + spa_log_info(impl->log, NAME " %p: initialized", impl); return 0; } diff --git a/src/daemon/meson.build b/src/daemon/meson.build index 8629a99f..95f0948f 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -2,10 +2,6 @@ pipewire_daemon_sources = [ 'pipewire.c', ] -pipewire_c_args = [ - '-DG_LOG_DOMAIN=g_log_domain_pipewire', -] - conf_config = configuration_data() conf_config.set('VERSION', '"@0@"'.format(pipewire_version)) conf_config.set('PIPEWIRE_CONFIG_DIR', pipewire_configdir) @@ -106,7 +102,6 @@ endforeach pipewire_exec = executable('pipewire', pipewire_daemon_sources, install: true, - c_args : pipewire_c_args, include_directories : [ configinc ], dependencies : [ spa_dep, pipewire_dep, ], ) diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 4d2de241..6f7feb98 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -31,6 +31,8 @@ context.modules = [ rt.prio = 65 #rt.time.soft = -1 #rt.time.hard = -1 + #uclamp.min = 0 + #uclamp.max = 1024 } flags = [ ifexists nofail ] } diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 776c167d..e4eb8e5a 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -90,13 +90,16 @@ context.modules = [ # Uses realtime scheduling to boost the audio thread priorities. This uses # RTKit if the user doesn't have permission to use regular realtime - # scheduling. + # scheduling. You can also clamp utilisation values to improve scheduling + # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices. { name = libpipewire-module-rt args = { nice.level = -11 #rt.prio = 88 #rt.time.soft = -1 #rt.time.hard = -1 + #uclamp.min = 0 + #uclamp.max = 1024 } flags = [ ifexists nofail ] } diff --git a/src/daemon/systemd/system/meson.build b/src/daemon/systemd/system/meson.build index 84ca0b06..d06d3adf 100644 --- a/src/daemon/systemd/system/meson.build +++ b/src/daemon/systemd/system/meson.build @@ -3,7 +3,7 @@ if get_option('systemd-system-unit-dir') != '' systemd_system_services_dir = get_option('systemd-system-unit-dir') endif -install_data(sources : 'pipewire.socket', +install_data(sources : ['pipewire.socket', 'pipewire-manager.socket'], install_dir : systemd_system_services_dir) systemd_config = configuration_data() diff --git a/src/daemon/systemd/system/pipewire-manager.socket b/src/daemon/systemd/system/pipewire-manager.socket new file mode 100644 index 00000000..235f3db6 --- /dev/null +++ b/src/daemon/systemd/system/pipewire-manager.socket @@ -0,0 +1,13 @@ +[Unit] +Description=PipeWire Multimedia System Manager Socket + +[Socket] +Service=pipewire.service +Priority=6 +ListenStream=%t/pipewire/pipewire-0-manager +SocketUser=pipewire +SocketGroup=pipewire +SocketMode=0600 + +[Install] +WantedBy=sockets.target diff --git a/src/daemon/systemd/system/pipewire.service.in b/src/daemon/systemd/system/pipewire.service.in index 8b75ba28..dc8db3f8 100644 --- a/src/daemon/systemd/system/pipewire.service.in +++ b/src/daemon/systemd/system/pipewire.service.in @@ -31,5 +31,5 @@ User=pipewire Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire [Install] -Also=pipewire.socket +Also=pipewire.socket pipewire-manager.socket WantedBy=default.target diff --git a/src/daemon/systemd/system/pipewire.socket b/src/daemon/systemd/system/pipewire.socket index 296fd374..2e3cb719 100644 --- a/src/daemon/systemd/system/pipewire.socket +++ b/src/daemon/systemd/system/pipewire.socket @@ -1,10 +1,9 @@ [Unit] -Description=PipeWire Multimedia System Sockets +Description=PipeWire Multimedia System Socket [Socket] Priority=6 ListenStream=%t/pipewire/pipewire-0 -ListenStream=%t/pipewire/pipewire-0-manager SocketUser=pipewire SocketGroup=pipewire SocketMode=0660 diff --git a/src/examples/audio-capture.c b/src/examples/audio-capture.c index b5f9d82b..3a6658b9 100644 --- a/src/examples/audio-capture.c +++ b/src/examples/audio-capture.c @@ -143,6 +143,7 @@ int main(int argc, char *argv[]) * the data. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_CONFIG_NAME, "client-rt.conf", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", NULL); diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 36d15809..001ede9d 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -532,8 +532,13 @@ on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state sta pw_stream_trigger_process (pwsink->stream); break; case PW_STREAM_STATE_ERROR: - GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, - ("stream error: %s", error), (NULL)); + /* make the error permanent, if it is not already; + pw_stream_set_error() will recursively call us again */ + if (pw_stream_get_state (pwsink->stream, NULL) != PW_STREAM_STATE_ERROR) + pw_stream_set_error (pwsink->stream, -EPIPE, "%s", error); + else + GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, + ("stream error: %s", error), (NULL)); break; } pw_thread_loop_signal (pwsink->core->loop, FALSE); diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 0514e4ca..e473338b 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -681,9 +681,13 @@ on_state_changed (void *data, case PW_STREAM_STATE_STREAMING: break; case PW_STREAM_STATE_ERROR: - pw_stream_set_error (pwsrc->stream, -EPIPE, "%s", error); - GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, - ("stream error: %s", error), (NULL)); + /* make the error permanent, if it is not already; + pw_stream_set_error() will recursively call us again */ + if (pw_stream_get_state (pwsrc->stream, NULL) != PW_STREAM_STATE_ERROR) + pw_stream_set_error (pwsrc->stream, -EPIPE, "%s", error); + else + GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, + ("stream error: %s", error), (NULL)); break; } pw_thread_loop_signal (pwsrc->core->loop, FALSE); diff --git a/src/modules/meson.build b/src/modules/meson.build index a60b85ee..1b434b7f 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -218,8 +218,10 @@ endif summary({'ffado-driver': build_module_ffado_driver}, bool_yn: true, section: 'Optional Modules') opus_custom_h = cc.has_header('opus/opus_custom.h', dependencies: opus_dep) +opus_custom_lib = cc.has_function('opus_custom_encoder_ctl', dependencies: opus_dep) + # One would imagine that opus_dep is a requirement but for some reason it's not, so we need to manually check that -if opus_dep.found() and opus_custom_h +if opus_dep.found() and opus_custom_h and opus_custom_lib opus_custom_dep = declare_dependency(compile_args: ['-DHAVE_OPUS_CUSTOM'], dependencies: opus_dep) else opus_custom_dep = dependency('', required: false) @@ -622,7 +624,7 @@ if build_module_raop endif summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules') -roc_dep = dependency('roc', required: get_option('roc')) +roc_dep = dependency('roc', version: '>= 0.3.0', required: get_option('roc')) summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons') pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', diff --git a/src/modules/module-access.c b/src/modules/module-access.c index 87dda86c..fd43c8ff 100644 --- a/src/modules/module-access.c +++ b/src/modules/module-access.c @@ -29,7 +29,7 @@ #include "flatpak-utils.h" -/** \page page_module_access PipeWire Module: Access +/** \page page_module_access Access * * * The `access` module performs access checks on clients. The access check @@ -63,6 +63,10 @@ * `pipewire.access.portal.app_id` property is to the Flatpak application ID, if * found. In addition, `pipewire.sec.flatpak` is set to `true`. * + * ## Module Name + * + * `libpipewire-module-access` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-adapter.c b/src/modules/module-adapter.c index 2c26dd3f..d92bc965 100644 --- a/src/modules/module-adapter.c +++ b/src/modules/module-adapter.c @@ -18,7 +18,11 @@ #include "modules/spa/spa-node.h" #include "module-adapter/adapter.h" -/** \page page_module_adapter PipeWire Module: Adapter +/** \page page_module_adapter Adapter + * + * ## Module Name + * + * `libpipewire-module-adapter` */ #define NAME "adapter" diff --git a/src/modules/module-avb.c b/src/modules/module-avb.c index ac7a70ca..b730b17c 100644 --- a/src/modules/module-avb.c +++ b/src/modules/module-avb.c @@ -21,7 +21,11 @@ #include "module-avb/avb.h" -/** \page page_module_avb PipeWire Module: AVB +/** \page page_module_avb AVB + * + * ## Module Name + * + * `libpipewire-module-avb` */ #define NAME "avb" diff --git a/src/modules/module-client-device.c b/src/modules/module-client-device.c index dccdbebd..e2450e81 100644 --- a/src/modules/module-client-device.c +++ b/src/modules/module-client-device.c @@ -15,7 +15,11 @@ #include "module-client-device/client-device.h" -/** \page page_module_client_device PipeWire Module: Client Device +/** \page page_module_client_device Client Device + * + * ## Module Name + * + * `libpipewire-module-client-device` */ #define NAME "client-device" diff --git a/src/modules/module-client-node.c b/src/modules/module-client-node.c index b3673bf6..9e8922db 100644 --- a/src/modules/module-client-node.c +++ b/src/modules/module-client-node.c @@ -16,7 +16,11 @@ #include "module-client-node/v0/client-node.h" #include "module-client-node/client-node.h" -/** \page page_module_client_node PipeWire Module: Client Node +/** \page page_module_client_node Client Node + * + * ## Module Name + * + * `libpipewire-module-client-node` */ #define NAME "client-node" diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index fbaa8ece..c493e24c 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -49,7 +49,6 @@ struct buffer { }; struct mix { - unsigned int valid:1; uint32_t mix_id; struct port *port; uint32_t peer_id; @@ -79,7 +78,7 @@ struct port { unsigned int removed:1; unsigned int destroyed:1; - struct pw_array mix; + struct pw_map mix; }; struct impl { @@ -191,42 +190,62 @@ do_port_use_buffers(struct impl *impl, static struct mix *find_mix(struct port *p, uint32_t mix_id) { - struct mix *mix; + if (mix_id == SPA_ID_INVALID) + mix_id = 0; + else + mix_id++; + + return pw_map_lookup(&p->mix, mix_id); +} + +static struct mix *create_mix(struct port *p, uint32_t mix_id) +{ + struct mix *mix = NULL; size_t len; + int res; if (mix_id == SPA_ID_INVALID) mix_id = 0; else mix_id++; - len = pw_array_get_len(&p->mix, struct mix); - if (mix_id >= len) { - size_t need = sizeof(struct mix) * (mix_id + 1 - len); - void *ptr = pw_array_add(&p->mix, need); - if (ptr == NULL) - return NULL; - memset(ptr, 0, need); + if (pw_map_lookup(&p->mix, mix_id) != NULL) { + errno = EEXIST; + return NULL; } - mix = pw_array_get_unchecked(&p->mix, mix_id, struct mix); - return mix; -} -static void mix_init(struct mix *mix, struct port *p, uint32_t mix_id) -{ - mix->valid = true; + /* pad map size */ + for (len = pw_map_get_size(&p->mix); len < mix_id; ++len) + if ((res = pw_map_insert_at(&p->mix, len, NULL)) < 0) + goto fail; + + mix = calloc(1, sizeof(struct mix)); + if (mix == NULL) + return NULL; + if ((res = pw_map_insert_at(&p->mix, mix_id, mix)) < 0) + goto fail; + mix->mix_id = mix_id; mix->port = p; mix->n_buffers = 0; + return mix; + +fail: + free(mix); + errno = -res; + return NULL; } -static struct mix *create_mix(struct impl *impl, struct port *p, uint32_t mix_id) +static void free_mix(struct port *p, struct mix *mix) { - struct mix *mix; + if (mix == NULL) + return; - if ((mix = find_mix(p, mix_id)) == NULL || mix->valid) - return NULL; - mix_init(mix, p, mix_id); - return mix; + /* never realloc so it's safe to call from pw_map_foreach */ + if (mix->mix_id < pw_map_get_size(&p->mix)) + pw_map_insert_at(&p->mix, mix->mix_id, NULL); + + free(mix); } static void clear_data(struct impl *impl, struct spa_data *d) @@ -255,19 +274,21 @@ static void clear_data(struct impl *impl, struct spa_data *d) } } -static int clear_buffers(struct impl *impl, struct mix *mix) +static void clear_buffer(struct impl *impl, struct spa_buffer *b) { - uint32_t i, j; + uint32_t i; + for (i = 0; i < b->n_datas; i++) + clear_data(impl, &b->datas[i]); +} +static int clear_buffers(struct impl *impl, struct mix *mix) +{ + uint32_t i; for (i = 0; i < mix->n_buffers; i++) { struct buffer *b = &mix->buffers[i]; spa_log_debug(impl->log, "%p: clear buffer %d", impl, i); - - for (j = 0; j < b->buffer.n_datas; j++) { - struct spa_data *d = &b->datas[j]; - clear_data(impl, d); - } + clear_buffer(impl, &b->buffer); pw_memblock_unref(b->mem); } mix->n_buffers = 0; @@ -278,11 +299,10 @@ static void mix_clear(struct impl *impl, struct mix *mix) { struct port *port = mix->port; - if (!mix->valid) - return; do_port_use_buffers(impl, port->direction, port->id, mix->mix_id, 0, NULL, 0); - mix->valid = false; + + free_mix(port, mix); } static int impl_node_enum_params(void *object, int seq, @@ -496,21 +516,25 @@ do_update_port(struct impl *impl, } } +static int mix_clear_cb(void *item, void *data) +{ + if (item) + mix_clear(data, item); + return 0; +} + static void clear_port(struct impl *impl, struct port *port) { - struct mix *mix; - spa_log_debug(impl->log, "%p: clear port %p", impl, port); do_update_port(impl, port, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, NULL); - pw_array_for_each(mix, &port->mix) - mix_clear(impl, mix); - pw_array_clear(&port->mix); - pw_array_init(&port->mix, sizeof(struct mix) * 2); + pw_map_for_each(&port->mix, mix_clear_cb, impl); + pw_map_clear(&port->mix); + pw_map_init(&port->mix, 0, 2); pw_map_insert_at(&impl->ports[port->direction], port->id, NULL); @@ -604,6 +628,13 @@ impl_node_port_enum_params(void *object, int seq, return found ? 0 : -ENOENT; } +static int clear_buffers_cb(void *item, void *data) +{ + if (item) + clear_buffers(data, item); + return 0; +} + static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, @@ -612,7 +643,6 @@ impl_node_port_set_param(void *object, { struct impl *impl = object; struct port *port; - struct mix *mix; spa_return_val_if_fail(impl != NULL, -EINVAL); @@ -624,10 +654,9 @@ impl_node_port_set_param(void *object, direction, port_id, spa_debug_type_find_name(spa_type_param, id), id); - if (id == SPA_PARAM_Format) { - pw_array_for_each(mix, &port->mix) - clear_buffers(impl, mix); - } + if (id == SPA_PARAM_Format) + pw_map_for_each(&port->mix, clear_buffers_cb, impl); + if (impl->resource == NULL) return param == NULL ? 0 : -EIO; @@ -656,7 +685,7 @@ static int do_port_set_io(struct impl *impl, if (port == NULL) return data == NULL ? 0 : -EINVAL; - if ((mix = find_mix(port, mix_id)) == NULL || !mix->valid) + if ((mix = find_mix(port, mix_id)) == NULL) return -EINVAL; old = pw_mempool_find_tag(impl->client_pool, tag, sizeof(tag)); @@ -729,7 +758,7 @@ do_port_use_buffers(struct impl *impl, if (direction == SPA_DIRECTION_OUTPUT) mix_id = SPA_ID_INVALID; - if ((mix = find_mix(p, mix_id)) == NULL || !mix->valid) + if ((mix = find_mix(p, mix_id)) == NULL) return -EINVAL; clear_buffers(impl, mix); @@ -1017,11 +1046,16 @@ static int client_node_port_buffers(void *data, if (direction == SPA_DIRECTION_OUTPUT) mix_id = SPA_ID_INVALID; - if ((mix = find_mix(p, mix_id)) == NULL || !mix->valid) - return -EINVAL; + if ((mix = find_mix(p, mix_id)) == NULL) + goto invalid; if (mix->n_buffers != n_buffers) - return -EINVAL; + goto invalid; + + for (i = 0; i < n_buffers; i++) { + if (mix->buffers[i].outbuf->n_datas != buffers[i]->n_datas) + goto invalid; + } for (i = 0; i < n_buffers; i++) { struct spa_buffer *oldbuf, *newbuf; @@ -1032,9 +1066,6 @@ static int client_node_port_buffers(void *data, spa_log_debug(impl->log, "buffer %d n_datas:%d", i, newbuf->n_datas); - if (oldbuf->n_datas != newbuf->n_datas) - return -EINVAL; - for (j = 0; j < b->buffer.n_datas; j++) { struct spa_chunk *oldchunk = oldbuf->datas[j].chunk; struct spa_data *d = &newbuf->datas[j]; @@ -1051,9 +1082,11 @@ static int client_node_port_buffers(void *data, d->maxsize); } } - mix->n_buffers = n_buffers; - return 0; +invalid: + for (i = 0; i < n_buffers; i++) + clear_buffer(impl, buffers[i]); + return -EINVAL; } static const struct pw_client_node_methods client_node_methods = { @@ -1377,12 +1410,12 @@ static int port_init_mix(void *data, struct pw_impl_port_mix *mix) uint32_t idx, pos, len; struct pw_memblock *area; - if ((m = create_mix(impl, port, mix->port.port_id)) == NULL) + if ((m = create_mix(port, mix->port.port_id)) == NULL) return -ENOMEM; mix->id = pw_map_insert_new(&impl->io_map, NULL); if (mix->id == SPA_ID_INVALID) { - m->valid = false; + free_mix(port, m); return -errno; } @@ -1416,7 +1449,7 @@ static int port_init_mix(void *data, struct pw_impl_port_mix *mix) return 0; no_mem: pw_map_remove(&impl->io_map, mix->id); - m->valid = false; + free_mix(port, m); return -ENOMEM; } @@ -1429,7 +1462,7 @@ static int port_release_mix(void *data, struct pw_impl_port_mix *mix) pw_log_debug("%p: remove mix id:%d io:%p", impl, mix->id, mix->io); - if ((m = find_mix(port, mix->port.port_id)) == NULL || !m->valid) + if ((m = find_mix(port, mix->port.port_id)) == NULL) return -EINVAL; if (impl->resource && impl->resource->version >= 4) @@ -1438,7 +1471,7 @@ static int port_release_mix(void *data, struct pw_impl_port_mix *mix) mix->port.port_id, SPA_ID_INVALID, NULL); pw_map_remove(&impl->io_map, mix->id); - m->valid = false; + free_mix(port, m); return 0; } @@ -1565,12 +1598,12 @@ static void node_port_init(void *data, struct pw_impl_port *port) p->direction = port->direction; p->id = port->port_id; p->impl = impl; - pw_array_init(&p->mix, sizeof(struct mix) * 2); + pw_map_init(&p->mix, 2, 2); p->mix_node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_port_mix, p); - create_mix(impl, p, SPA_ID_INVALID); + create_mix(p, SPA_ID_INVALID); pw_map_insert_at(&impl->ports[p->direction], p->id, p); return; diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 141115c8..22a9fc8b 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -29,7 +29,7 @@ #include <pipewire/impl.h> #include <pipewire/i18n.h> -/** \page page_module_combine_stream PipeWire Module: Combine Stream +/** \page page_module_combine_stream Combine Stream * * The combine stream can make: * @@ -40,6 +40,10 @@ * rules. This makes it possible to combine static nodes or nodes based on certain * properties. * + * ## Module Name + * + * `libpipewire-module-combine-stream` + * * ## Module Options * * - `node.name`: a unique name for the stream diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index ab369830..7b33c47e 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -40,7 +40,7 @@ #include <pipewire/extensions/profiler.h> -/** \page page_module_echo_cancel PipeWire Module: Echo Cancel +/** \page page_module_echo_cancel Echo Cancel * * The `echo-cancel` module performs echo cancellation. The module creates * virtual `echo-cancel-capture` source and `echo-cancel-playback` sink @@ -80,6 +80,10 @@ * This data then goes into the application (the conference application) and * does not contain the echo from the other participants anymore. * + * ## Module Name + * + * `libpipewire-module-echo-cancel` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index fa8eb544..3de26cae 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -22,11 +22,15 @@ #include <pipewire/impl.h> #include <pipewire/extensions/profiler.h> -/** \page page_module_example_filter PipeWire Module: Example Filter +/** \page page_module_example_filter Example Filter * * The example filter is a good starting point for writing a custom * filter. We refer to the source code for more information. * + * ## Module Name + * + * `libpipewire-module-example-filter` + * * ## Module Options * * - `node.description`: a human readable name for the filter streams diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c index 7413933c..772a45d6 100644 --- a/src/modules/module-example-sink.c +++ b/src/modules/module-example-sink.c @@ -28,11 +28,15 @@ #include <pipewire/impl.h> #include <pipewire/i18n.h> -/** \page page_module_example_sink PipeWire Module: Example Sink +/** \page page_module_example_sink Example Sink * * The example sink is a good starting point for writing a custom * sink. We refer to the source code for more information. * + * ## Module Name + * + * `libpipewire-module-example-sink` + * * ## Module Options * * - `node.name`: a unique name for the stream diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c index e6be6717..0b06a06c 100644 --- a/src/modules/module-example-source.c +++ b/src/modules/module-example-source.c @@ -28,11 +28,15 @@ #include <pipewire/impl.h> #include <pipewire/i18n.h> -/** \page page_module_example_source PipeWire Module: Example Source +/** \page page_module_example_source Example Source * * The example source is a good starting point for writing a custom * source. We refer to the source code for more information. * + * ## Module Name + * + * `libpipewire-module-example-source` + * * ## Module Options * * - `node.name`: a unique name for the stream diff --git a/src/modules/module-fallback-sink.c b/src/modules/module-fallback-sink.c index 17f0f505..86982e62 100644 --- a/src/modules/module-fallback-sink.c +++ b/src/modules/module-fallback-sink.c @@ -19,10 +19,19 @@ #include <pipewire/impl.h> #include <pipewire/i18n.h> -/** \page page_module_fallback_sink PipeWire Module: Fallback Sink +/** \page page_module_fallback_sink Fallback Sink * * Fallback sink, which appear dynamically when no other sinks are * present. This is only useful for Pulseaudio compatibility. + * + * ## Module Name + * + * `libpipewire-module-fallback-sink` + * + * ## Module Options + * + * - `sink.name`: sink name + * - `sink.description`: sink description */ #define NAME "fallback-sink" diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 22dc3562..61c11fb6 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -31,11 +31,15 @@ #include <libffado/ffado.h> -/** \page page_module_ffado_driver PipeWire Module: FFADO firewire audio driver +/** \page page_module_ffado_driver FFADO firewire audio driver * * The ffado-driver module provides a source or sink using the libffado library for * reading and writing to firewire audio devices. * + * ## Module Name + * + * `libpipewire-module-ffado-driver` + * * ## Module Options * * - `driver.mode`: the driver mode, sink|source|duplex, default duplex diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 7a60b289..b0d5888b 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -34,7 +34,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic /** - * \page page_module_filter_chain PipeWire Module: Filter-Chain + * \page page_module_filter_chain Filter-Chain * * The filter-chain allows you to create an arbitrary processing graph * from LADSPA, LV2 and builtin filters. This filter can be made into a @@ -48,6 +48,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); * manager can manage the configuration and connection with the sinks and * sources automatically. * + * ## Module Name + * + * `libpipewire-module-filter-chain` + * * ## Module Options * * - `node.description`: a human readable name for the filter chain @@ -2854,6 +2858,9 @@ static void impl_destroy(struct impl *impl) graph_free(&impl->graph); spa_list_consume(pl, &impl->plugin_func_list, link) free_plugin_func(pl); + + free(impl->silence_data); + free(impl->discard_data); free(impl); } @@ -2974,8 +2981,18 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->quantum_limit = pw_properties_get_uint32( pw_context_get_properties(impl->context), "default.clock.quantum-limit", 8192u); + impl->silence_data = calloc(impl->quantum_limit, sizeof(float)); + if (impl->silence_data == NULL) { + res = -errno; + goto error; + } + impl->discard_data = calloc(impl->quantum_limit, sizeof(float)); + if (impl->discard_data == NULL) { + res = -errno; + goto error; + } cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); impl->dsp.cpu_flags = cpu_iface ? spa_cpu_get_flags(cpu_iface) : 0; diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 7eff9144..51008601 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -30,7 +30,7 @@ #include "module-jack-tunnel/weakjack.h" -/** \page page_module_jack_tunnel PipeWire Module: JACK Tunnel +/** \page page_module_jack_tunnel JACK Tunnel * * The jack-tunnel module provides a source or sink that tunnels all audio to * a JACK server. @@ -39,6 +39,10 @@ * automatically load the tunnel with the right parameters based on dbus * information. * + * ## Module Name + * + * `libpipewire-module-jack-tunnel` + * * ## Module Options * * - `jack.library`: the libjack to load, by default libjack.so.0 is searched in diff --git a/src/modules/module-jackdbus-detect.c b/src/modules/module-jackdbus-detect.c index 750212ab..c4b1e01e 100644 --- a/src/modules/module-jackdbus-detect.c +++ b/src/modules/module-jackdbus-detect.c @@ -25,11 +25,15 @@ #include "pipewire/module.h" #include "pipewire/utils.h" -/** \page page_module_jackdbus_detect PipeWire Module: JACK DBus detect +/** \page page_module_jackdbus_detect JACK DBus detect * * Automaticall creates a sink/source when a jackdbus server is started * and connect to JACK. * + * ## Module Name + * + * `libpipewire-module-jackdbus-detect` + * * ## Module Options * * There are no module-specific options, all arguments are passed to diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c index eaddf654..56a0b963 100644 --- a/src/modules/module-link-factory.c +++ b/src/modules/module-link-factory.c @@ -14,7 +14,11 @@ #include <pipewire/impl.h> -/** \page page_module_link_factory PipeWire Module: Link Factory +/** \page page_module_link_factory Link Factory + * + * ## Module Name + * + * `libpipewire-module-link-factory` */ #define NAME "link-factory" diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index d1ad5258..c3f30a75 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -22,7 +22,7 @@ #include <pipewire/impl.h> #include <pipewire/extensions/profiler.h> -/** \page page_module_loopback PipeWire Module: Loopback +/** \page page_module_loopback Loopback * * The loopback module passes the output of a capture stream unmodified to a playback stream. * It can be used to construct a link between a source and sink but also to @@ -31,6 +31,10 @@ * Because both ends of the loopback are built with streams, the session manager can * manage the configuration and connection with the sinks and sources. * + * ## Module Name + * + * `libpipewire-module-loopback` + * * ## Module Options * * - `node.description`: a human readable name for the loopback streams diff --git a/src/modules/module-metadata.c b/src/modules/module-metadata.c index 33ec61c8..2d05fb83 100644 --- a/src/modules/module-metadata.c +++ b/src/modules/module-metadata.c @@ -15,7 +15,11 @@ #include <pipewire/impl.h> #include <pipewire/extensions/metadata.h> -/** \page page_module_metadata PipeWire Module: Metadata +/** \page page_module_metadata Metadata + * + * ## Module Name + * + * `libpipewire-module-metadata` */ #define NAME "metadata" diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index d3b4adde..88e6f84c 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -40,10 +40,14 @@ #define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK) #endif -/** \page page_module_netjack2_driver PipeWire Module: Netjack2 driver +/** \page page_module_netjack2_driver Netjack2 driver * * The netjack2-driver module provides a source or sink that is following a - * netjack2 driver. + * netjack2 manager. + * + * ## Module Name + * + * `libpipewire-module-netjack2-driver` * * ## Module Options * diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 586e2cc4..3afe6a88 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -42,11 +42,15 @@ #define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK) #endif -/** \page page_module_netjack2_manager PipeWire Module: Netjack2 manager +/** \page page_module_netjack2_manager Netjack2 manager * * The netjack2 manager module listens for new netjack2 driver messages and will * start a communication channel with them. * + * ## Module Name + * + * `libpipewire-module-netjack2-manager` + * * ## Module Options * * - `local.ifname = <str>`: interface name to use diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index c95edbfc..0fce7eb8 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -32,14 +32,19 @@ #include <pipewire/impl.h> #include <pipewire/i18n.h> -/** \page page_module_pipe_tunnel PipeWire Module: Unix Pipe Tunnel +/** \page page_module_pipe_tunnel Unix Pipe Tunnel * * The pipe-tunnel module provides a source or sink that tunnels all audio to * or from a unix pipe respectively. * + * ## Module Name + * + * `libpipewire-module-pipe-tunnel` + * * ## Module Options * * - `tunnel.mode`: the desired tunnel to create. (Default `playback`) + * - `tunnel.may-pause`: if the tunnel stream is allowed to pause on xrun * - `pipe.filename`: the filename of the pipe. * - `stream.props`: Extra properties for the local stream. * @@ -55,6 +60,12 @@ * When `tunnel.mode` is `source`, a source node is created. Samples read from * the the pipe will be made available on the source. * + * `tunnel.may-pause` allows the tunnel stream to become inactive (paused) when + * there is no data in the fifo or when the fifo is full. For `capture` and + * `playback` `tunnel.mode` this is by default true. For `source` and `sink` + * `tunnel.mode`, this is by default false. A paused stream will consume no + * CPU and will resume when the fifo becomes readable or writable again. + * * When `pipe.filename` is not given, a default fifo in `/tmp/fifo_input` or * `/tmp/fifo_output` will be created that can be written and read respectively, * depending on the selected `tunnel.mode`. @@ -86,6 +97,7 @@ * { name = libpipewire-module-pipe-tunnel * args = { * tunnel.mode = playback + * #tunnel.may-pause = true * # Set the pipe name to tunnel to * pipe.filename = "/tmp/fifo_output" * #audio.format=<sample format> @@ -128,6 +140,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( audio.channels=<number of channels> ) " \ "( audio.position=<channel map> ) " \ "( tunnel.mode=capture|playback|sink|source )" \ + "( tunnel.may-pause=<bool, if the stream can pause> )" \ "( pipe.filename=<filename> )" \ "( stream.props=<properties> ) " @@ -141,6 +154,7 @@ static const struct spa_dict_item module_props[] = { struct impl { struct pw_context *context; + struct pw_loop *main_loop; struct pw_loop *data_loop; #define MODE_PLAYBACK 0 @@ -174,6 +188,8 @@ struct impl { unsigned int do_disconnect:1; unsigned int driving:1; unsigned int have_sync:1; + unsigned int may_pause:1; + unsigned int paused:1; struct spa_ringbuffer ring; void *buffer; @@ -262,7 +278,7 @@ static void stream_state_changed(void *d, enum pw_stream_state old, break; case PW_STREAM_STATE_PAUSED: if (impl->direction == PW_DIRECTION_OUTPUT) { - pw_loop_update_io(impl->data_loop, impl->socket, 0); + pw_loop_update_io(impl->data_loop, impl->socket, impl->paused ? SPA_IO_IN : 0); set_timeout(impl, 0); } break; @@ -281,6 +297,26 @@ static void stream_state_changed(void *d, enum pw_stream_state old, } } +static int do_pause(struct spa_loop *loop, bool async, uint32_t seq, const void *data, + size_t size, void *user_data) +{ + struct impl *impl = user_data; + const bool *paused = data; + pw_log_info("set paused: %d", *paused); + impl->paused = *paused; + pw_stream_set_active(impl->stream, !*paused); + return 0; +} + +static void pause_stream(struct impl *impl, bool paused) +{ + if (!impl->may_pause) + return; + if (impl->direction == PW_DIRECTION_INPUT) + pw_loop_update_io(impl->data_loop, impl->socket, paused ? SPA_IO_OUT : 0); + pw_loop_invoke(impl->main_loop, do_pause, 1, &paused, sizeof(bool), false, impl); +} + static void playback_stream_process(void *data) { struct impl *impl = data; @@ -308,8 +344,8 @@ static void playback_stream_process(void *data) continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { /* Don't continue writing */ - pw_log_debug("pipe (%s) overrun: %m", - impl->filename); + pw_log_debug("pipe (%s) overrun: %m", impl->filename); + pause_stream(impl, true); break; } else { pw_log_warn("Failed to write to pipe (%s): %m", @@ -370,8 +406,10 @@ static void capture_stream_process(void *data) if (avail < (int32_t)size) { memset(bd->data, 0, size); - if (avail > 0) + if (avail >= 0) { pw_log_warn("underrun %d < %u", avail, size); + pause_stream(impl, true); + } impl->have_sync = false; } if (avail > (int32_t)RINGBUFFER_SIZE) { @@ -456,6 +494,8 @@ static int create_stream(struct impl *impl) params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &impl->info); + impl->paused = false; + if ((res = pw_stream_connect(impl->stream, impl->direction, PW_ID_ANY, @@ -550,6 +590,8 @@ static void on_pipe_io(void *data, int fd, uint32_t mask) pw_loop_update_io(impl->data_loop, impl->socket, 0); return; } + if (impl->paused) + pause_stream(impl, false); if (mask & SPA_IO_IN) handle_pipe_read(impl); } @@ -839,6 +881,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; + impl->main_loop = pw_context_get_main_loop(context); data_loop = pw_context_get_data_loop(context); impl->data_loop = pw_data_loop_get_loop(data_loop); @@ -848,22 +891,28 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (spa_streq(str, "capture")) { impl->mode = MODE_CAPTURE; impl->direction = PW_DIRECTION_INPUT; + impl->may_pause = true; } else if (spa_streq(str, "playback")) { impl->mode = MODE_PLAYBACK; impl->direction = PW_DIRECTION_OUTPUT; + impl->may_pause = true; }else if (spa_streq(str, "sink")) { impl->mode = MODE_SINK; impl->direction = PW_DIRECTION_INPUT; + impl->may_pause = false; media_class = "Audio/Sink"; } else if (spa_streq(str, "source")) { impl->mode = MODE_SOURCE; impl->direction = PW_DIRECTION_OUTPUT; + impl->may_pause = false; media_class = "Audio/Source"; } else { pw_log_error("invalid tunnel.mode '%s'", str); res = -EINVAL; goto error; } + if ((str = pw_properties_get(props, "tunnel.may-pause")) != NULL) + impl->may_pause = spa_atob(str); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); diff --git a/src/modules/module-portal.c b/src/modules/module-portal.c index de45e445..f37db6b7 100644 --- a/src/modules/module-portal.c +++ b/src/modules/module-portal.c @@ -25,7 +25,7 @@ #include "pipewire/module.h" #include "pipewire/utils.h" -/** \page page_module_portal PipeWire Module: Portal +/** \page page_module_portal Portal * * The `portal` module performs access control management for clients started * inside an XDG portal. @@ -42,6 +42,10 @@ * * Clients connecting from other PIDs are ignored by this module. * + * ## Module Name + * + * `libpipewire-module-portal` + * * ## Module Options * * There are no module-specific options. diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c index 490e031a..04ba2677 100644 --- a/src/modules/module-profiler.c +++ b/src/modules/module-profiler.c @@ -22,7 +22,7 @@ #include <pipewire/impl.h> #include <pipewire/extensions/profiler.h> -/** \page page_module_profiler PipeWire Module: Profiler +/** \page page_module_profiler Profiler * * The profiler module provides a Profiler interface for applications that * can be used to receive profiling information. @@ -30,6 +30,10 @@ * Use tools like pw-top and pw-profiler to collect profiling information * about the pipewire graph. * + * ## Module Name + * + * `libpipewire-module-profiler` + * * ## Example configuration * * The module has no arguments and is usually added to the config file of diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index 486eb556..63cc149d 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -63,7 +63,7 @@ PW_LOG_TOPIC(mod_topic_connection, "conn." NAME); #include <spa/debug/pod.h> #include <spa/debug/types.h> -/** \page page_module_protocol_native PipeWire Module: Protocol Native +/** \page page_module_protocol_native Protocol Native * * The native protocol module implements the PipeWire communication between * a client and a server using unix local sockets. @@ -71,6 +71,10 @@ PW_LOG_TOPIC(mod_topic_connection, "conn." NAME); * Normally this module is loaded in both client and server config files * so that they cam communicate. * + * ## Module Name + * + * `libpipewire-module-protocol-native` + * * ## Module Options * * The module supports the following arguments: @@ -1489,6 +1493,7 @@ static int impl_ext_end_proxy(struct pw_proxy *proxy, { struct pw_core *core = proxy->core; struct client *impl = SPA_CONTAINER_OF(core->conn, struct client, this); + ensure_loop(impl->context->main_loop); assert_single_pod(builder); marshal_core_footers(&impl->footer_state, core, builder); return core->send_seq = pw_protocol_native_connection_end(impl->connection, builder); @@ -1518,6 +1523,7 @@ static int impl_ext_end_resource(struct pw_resource *resource, { struct client_data *data = resource->client->user_data; struct pw_impl_client *client = resource->client; + ensure_loop(client->context->main_loop); assert_single_pod(builder); marshal_client_footers(&data->footer_state, client, builder); return client->send_seq = pw_protocol_native_connection_end(data->connection, builder); diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c index ab5f8f92..e50dc752 100644 --- a/src/modules/module-protocol-pulse.c +++ b/src/modules/module-protocol-pulse.c @@ -18,7 +18,7 @@ #include "module-protocol-pulse/pulse-server.h" -/** \page page_module_protocol_pulse PipeWire Module: Protocol Pulse +/** \page page_module_protocol_pulse Protocol Pulse * * This module implements a complete PulseAudio server on top of * PipeWire. This is only the server implementation, client are expected @@ -33,6 +33,10 @@ * The pulse server implements a sample cache that is otherwise not * available in PipeWire. * + * ## Module Name + * + * `libpipewire-module-protocol-pulse` + * * ## Module Options * * The module arguments can be the contents of the pulse.properties but @@ -86,7 +90,7 @@ *\endcode * * The addresses the server listens on when starting. Uncomment the `tcp:4713` entry to also - * make the server listen on a tcp socket. This is equivalent to loading `module-native-protocol-tcp`. + * make the server listen on a tcp socket. This is equivalent to loading `libpipewire-module-native-protocol-tcp`. * * There is also a slightly more verbose syntax with more options: * diff --git a/src/modules/module-protocol-pulse/collect.c b/src/modules/module-protocol-pulse/collect.c index f7c18214..bf05122f 100644 --- a/src/modules/module-protocol-pulse/collect.c +++ b/src/modules/module-protocol-pulse/collect.c @@ -65,7 +65,7 @@ uint32_t id_to_index(struct pw_manager *m, uint32_t id) return SPA_ID_INVALID; } -bool collect_is_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction) +static bool collect_is_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction) { struct pw_manager_object *o; uint32_t in_node, out_node; @@ -291,7 +291,7 @@ static void collect_device_info(struct pw_manager_object *device, struct pw_mana } static void update_device_info(struct pw_manager *manager, struct pw_manager_object *o, - enum pw_direction direction, bool monitor, struct defs *defs) + enum pw_direction direction, bool monitor, struct defs *defs, bool stream) { const char *str; const char *key = monitor ? "device.info.monitor" : "device.info"; @@ -313,6 +313,12 @@ static void update_device_info(struct pw_manager *manager, struct pw_manager_obj } collect_device_info(o, card, &di, monitor, defs); + di.state = node_state(info->state); + /* running sink/source that is not linked is reported as idle */ + if (!stream && di.state == STATE_RUNNING && + !collect_is_linked(manager, o->id, pw_direction_reverse(direction))) + di.state = STATE_IDLE; + dev_info = pw_manager_object_get_data(o, key); if (dev_info) { if (memcmp(dev_info, &di, sizeof(di)) != 0) { @@ -586,16 +592,16 @@ void update_object_info(struct pw_manager *manager, struct pw_manager_object *o, struct defs *defs) { if (pw_manager_object_is_sink(o)) { - update_device_info(manager, o, PW_DIRECTION_OUTPUT, false, defs); - update_device_info(manager, o, PW_DIRECTION_OUTPUT, true, defs); + update_device_info(manager, o, PW_DIRECTION_OUTPUT, false, defs, false); + update_device_info(manager, o, PW_DIRECTION_OUTPUT, true, defs, false); } if (pw_manager_object_is_source(o)) { - update_device_info(manager, o, PW_DIRECTION_INPUT, false, defs); + update_device_info(manager, o, PW_DIRECTION_INPUT, false, defs, false); } if (pw_manager_object_is_source_output(o)) { - update_device_info(manager, o, PW_DIRECTION_INPUT, false, defs); + update_device_info(manager, o, PW_DIRECTION_INPUT, false, defs, true); } if (pw_manager_object_is_sink_input(o)) { - update_device_info(manager, o, PW_DIRECTION_OUTPUT, false, defs); + update_device_info(manager, o, PW_DIRECTION_OUTPUT, false, defs, true); } } diff --git a/src/modules/module-protocol-pulse/collect.h b/src/modules/module-protocol-pulse/collect.h index 40b08d62..d5cfe953 100644 --- a/src/modules/module-protocol-pulse/collect.h +++ b/src/modules/module-protocol-pulse/collect.h @@ -42,6 +42,7 @@ void update_object_info(struct pw_manager *manager, struct pw_manager_object *o, struct device_info { uint32_t direction; + int state; struct sample_spec ss; struct channel_map map; @@ -59,6 +60,7 @@ struct device_info { #define DEVICE_INFO_INIT(_dir) \ (struct device_info) { \ .direction = _dir, \ + .state = STATE_INIT, \ .ss = SAMPLE_SPEC_INIT, \ .map = CHANNEL_MAP_INIT, \ .volume_info = VOLUME_INFO_INIT, \ @@ -146,6 +148,5 @@ uint32_t find_port_index(struct pw_manager_object *card, uint32_t direction, con struct pw_manager_object *find_peer_for_link(struct pw_manager *m, struct pw_manager_object *o, uint32_t id, enum pw_direction direction); struct pw_manager_object *find_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction); -bool collect_is_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction); #endif diff --git a/src/modules/module-protocol-pulse/modules/module-alsa-sink.c b/src/modules/module-protocol-pulse/modules/module-alsa-sink.c index e07662ad..49ca9404 100644 --- a/src/modules/module-protocol-pulse/modules/module-alsa-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-alsa-sink.c @@ -7,6 +7,43 @@ #include "../manager.h" #include "../module.h" +/** \page page_pulse_module_alsa_sink ALSA Sink + * + * ## Module Name + * + * `module-alsa-sink` + * + * ## Module Options + * + * @pulse_module_options@ + */ + +static const char *const pulse_module_options = + "name=<name of the sink, to be prefixed> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "namereg_fail=<when false attempt to synthesise new sink_name if it is already taken> " + "device=<ALSA device> " + "device_id=<ALSA card index> " + "format=<sample format> " + "rate=<sample rate> " + "alternate_rate=<alternate sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "fragments=<number of fragments> " + "fragment_size=<fragment size> " + "mmap=<enable memory mapping?> " + "tsched=<enable system timer based scheduling mode?> " + "tsched_buffer_size=<buffer size when using timer based scheduling> " + "tsched_buffer_watermark=<lower fill watermark> " + "ignore_dB=<ignore dB information from the device?> " + "control=<name of mixer control, or name and index separated by a comma> " + "rewind_safeguard=<number of bytes that cannot be rewound> " + "deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> " + "deferred_volume_safety_margin=<usec adjustment depending on volume direction> " + "deferred_volume_extra_delay=<usec adjustment to HW volume changes> " + "fixed_latency_range=<disable latency range changes on underrun?> "; + #define NAME "alsa-sink" #define DEFAULT_DEVICE "default" @@ -131,31 +168,7 @@ static int module_alsa_sink_unload(struct module *module) static const struct spa_dict_item module_alsa_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "An ALSA sink" }, - { PW_KEY_MODULE_USAGE, - "name=<name of the sink, to be prefixed> " - "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "namereg_fail=<when false attempt to synthesise new sink_name if it is already taken> " - "device=<ALSA device> " - "device_id=<ALSA card index> " - "format=<sample format> " - "rate=<sample rate> " - "alternate_rate=<alternate sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "fragments=<number of fragments> " - "fragment_size=<fragment size> " - "mmap=<enable memory mapping?> " - "tsched=<enable system timer based scheduling mode?> " - "tsched_buffer_size=<buffer size when using timer based scheduling> " - "tsched_buffer_watermark=<lower fill watermark> " - "ignore_dB=<ignore dB information from the device?> " - "control=<name of mixer control, or name and index separated by a comma> " - "rewind_safeguard=<number of bytes that cannot be rewound> " - "deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> " - "deferred_volume_safety_margin=<usec adjustment depending on volume direction> " - "deferred_volume_extra_delay=<usec adjustment to HW volume changes> " - "fixed_latency_range=<disable latency range changes on underrun?>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-alsa-source.c b/src/modules/module-protocol-pulse/modules/module-alsa-source.c index 4fe5f51e..1ffce175 100644 --- a/src/modules/module-protocol-pulse/modules/module-alsa-source.c +++ b/src/modules/module-protocol-pulse/modules/module-alsa-source.c @@ -7,6 +7,43 @@ #include "../manager.h" #include "../module.h" +/** \page page_pulse_module_alsa_source ALSA Source + * + * ## Module Name + * + * `module-alsa-source` + * + * ## Module Options + * + * @pulse_module_options@ + */ + +static const char *const pulse_module_options = + "name=<name of the source, to be prefixed> " + "source_name=<name for the source> " + "source_properties=<properties for the source> " + "namereg_fail=<when false attempt to synthesise new source_name if it is already taken> " + "device=<ALSA device> " + "device_id=<ALSA card index> " + "format=<sample format> " + "rate=<sample rate> " + "alternate_rate=<alternate sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "fragments=<number of fragments> " + "fragment_size=<fragment size> " + "mmap=<enable memory mapping?> " + "tsched=<enable system timer based scheduling mode?> " + "tsched_buffer_size=<buffer size when using timer based scheduling> " + "tsched_buffer_watermark=<lower fill watermark> " + "ignore_dB=<ignore dB information from the device?> " + "control=<name of mixer control, or name and index separated by a comma> " + "rewind_safeguard=<number of bytes that cannot be rewound> " + "deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> " + "deferred_volume_safety_margin=<usec adjustment depending on volume direction> " + "deferred_volume_extra_delay=<usec adjustment to HW volume changes> " + "fixed_latency_range=<disable latency range changes on underrun?>"; + #define NAME "alsa-source" #define DEFAULT_DEVICE "default" @@ -131,31 +168,7 @@ static int module_alsa_source_unload(struct module *module) static const struct spa_dict_item module_alsa_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "An ALSA source" }, - { PW_KEY_MODULE_USAGE, - "name=<name of the source, to be prefixed> " - "source_name=<name for the source> " - "source_properties=<properties for the source> " - "namereg_fail=<when false attempt to synthesise new source_name if it is already taken> " - "device=<ALSA device> " - "device_id=<ALSA card index> " - "format=<sample format> " - "rate=<sample rate> " - "alternate_rate=<alternate sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "fragments=<number of fragments> " - "fragment_size=<fragment size> " - "mmap=<enable memory mapping?> " - "tsched=<enable system timer based scheduling mode?> " - "tsched_buffer_size=<buffer size when using timer based scheduling> " - "tsched_buffer_watermark=<lower fill watermark> " - "ignore_dB=<ignore dB information from the device?> " - "control=<name of mixer control, or name and index separated by a comma> " - "rewind_safeguard=<number of bytes that cannot be rewound> " - "deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> " - "deferred_volume_safety_margin=<usec adjustment depending on volume direction> " - "deferred_volume_extra_delay=<usec adjustment to HW volume changes> " - "fixed_latency_range=<disable latency range changes on underrun?>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-always-sink.c b/src/modules/module-protocol-pulse/modules/module-always-sink.c index 041f7672..e0ba258f 100644 --- a/src/modules/module-protocol-pulse/modules/module-always-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-always-sink.c @@ -6,6 +6,19 @@ #include "../module.h" +/** \page page_pulse_module_always_sink Always Sink + * + * ## Module Name + * + * `module-always-sink` + * + * ## Module Options + * + * @pulse_module_options@ + */ + +static const char *const pulse_module_options = "sink_name=<name of sink>"; + #define NAME "always-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -77,7 +90,7 @@ static int module_always_sink_unload(struct module *module) static const struct spa_dict_item module_always_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen <pav@iki.fi>" }, { PW_KEY_MODULE_DESCRIPTION, "Always keeps at least one sink loaded even if it's a null one" }, - { PW_KEY_MODULE_USAGE, "sink_name=<name of sink>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-combine-sink.c b/src/modules/module-protocol-pulse/modules/module-combine-sink.c index 62c544be..4951b4e6 100644 --- a/src/modules/module-protocol-pulse/modules/module-combine-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-combine-sink.c @@ -12,6 +12,31 @@ #include "../manager.h" #include "../module.h" +/** \page page_pulse_module_combine_sink Combine Sink + * + * ## Module Name + * + * `module-combine-sink` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_combine_stream "libpipewire-module-combine-stream" + */ + +static const char *const pulse_module_options = + "sink_name=<name of the sink> " + "sink_properties=<properties for the sink> " + "sinks=<sinks to combine> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "remix=<remix channels> " + "latency_compensate=<bool> "; + #define NAME "combine-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -24,15 +49,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); static const struct spa_dict_item module_combine_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "Combine multiple sinks into a single sink" }, - { PW_KEY_MODULE_USAGE, "sink_name=<name of the sink> " - "sink_properties=<properties for the sink> " - /* not a great name, but for backwards compatibility... */ - "slaves=<sinks to combine> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "remix=<remix channels> " - "latency_compensate=<bool> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -268,8 +285,10 @@ static int module_combine_sink_prepare(struct module * const module) if ((str = pw_properties_get(props, "sink_properties")) != NULL) module_args_add_props(combine_props, str); - if ((str = pw_properties_get(props, "slaves")) != NULL) { + if ((str = pw_properties_get(props, "sinks")) != NULL || + (str = pw_properties_get(props, "slaves")) != NULL) { sink_names = pw_split_strv(str, ",", MAX_SINKS, &num_sinks); + pw_properties_set(props, "sinks", NULL); pw_properties_set(props, "slaves", NULL); } if ((str = pw_properties_get(props, "remix")) != NULL) { diff --git a/src/modules/module-protocol-pulse/modules/module-echo-cancel.c b/src/modules/module-protocol-pulse/modules/module-echo-cancel.c index cd089af2..9737266d 100644 --- a/src/modules/module-protocol-pulse/modules/module-echo-cancel.c +++ b/src/modules/module-protocol-pulse/modules/module-echo-cancel.c @@ -11,6 +11,45 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_echo_cancel Echo Cancel + * + * ## Module Name + * + * `module-echo-cancel` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_echo_cancel "libpipewire-module-echo-cancel" + */ + +static const char *const pulse_module_options = + "source_name=<name for the source> " + "source_properties=<properties for the source> " + "source_master=<name of source to filter> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "sink_master=<name of sink to filter> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "aec_method=<implementation to use> " + "aec_args=<parameters for the AEC engine> "; +#if 0 + /* These are not implemented because they don't + * really make sense in the PipeWire context */ + "format=<sample format> " + "adjust_time=<how often to readjust rates in s> " + "adjust_threshold=<how much drift to readjust after in ms> " + "autoloaded=<set if this module is being loaded automatically> " + "save_aec=<save AEC data in /tmp> " + "use_volume_sharing=<yes or no> " + "use_master_format=<yes or no> " +#endif + #define NAME "echo-cancel" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -114,29 +153,7 @@ static int module_echo_cancel_unload(struct module *module) static const struct spa_dict_item module_echo_cancel_info[] = { { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "Acoustic echo canceller" }, - { PW_KEY_MODULE_USAGE, "source_name=<name for the source> " - "source_properties=<properties for the source> " - "source_master=<name of source to filter> " - "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "sink_master=<name of sink to filter> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "aec_method=<implementation to use> " - "aec_args=<parameters for the AEC engine> " -#if 0 - /* These are not implemented because they don't - * really make sense in the PipeWire context */ - "format=<sample format> " - "adjust_time=<how often to readjust rates in s> " - "adjust_threshold=<how much drift to readjust after in ms> " - "autoloaded=<set if this module is being loaded automatically> " - "save_aec=<save AEC data in /tmp> " - "use_volume_sharing=<yes or no> " - "use_master_format=<yes or no> " -#endif - }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-gsettings.c b/src/modules/module-protocol-pulse/modules/module-gsettings.c index f9f713a4..1968cc10 100644 --- a/src/modules/module-protocol-pulse/modules/module-gsettings.c +++ b/src/modules/module-protocol-pulse/modules/module-gsettings.c @@ -11,6 +11,17 @@ #include "../module.h" +/** \page page_pulse_module_gsettings GSettings + * + * ## Module Name + * + * `module-gsettings` + * + * ## Module Options + * + * No options. + */ + #define NAME "gsettings" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); diff --git a/src/modules/module-protocol-pulse/modules/module-jackdbus-detect.c b/src/modules/module-protocol-pulse/modules/module-jackdbus-detect.c index 33d7ba34..c015673f 100644 --- a/src/modules/module-protocol-pulse/modules/module-jackdbus-detect.c +++ b/src/modules/module-protocol-pulse/modules/module-jackdbus-detect.c @@ -8,6 +8,35 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_jackdbus_detect JackDBus Detect + * + * ## Module Name + * + * `module-jackdbus-detect` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_jackdbus_detect "libpipewire-module-jackdbus-detect" + */ + +static const char *const pulse_module_options = + "channels=<number of channels> " + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "sink_client_name=<jack client name> " + "sink_channels=<number of channels> " + "sink_channel_map=<channel map> " + "source_name=<name for the source> " + "source_properties=<properties for the source> " + "source_client_name=<jack client name> " + "source_channels=<number of channels> " + "source_channel_map=<channel map> " + "connect=<connect ports?>"; + #define NAME "jackdbus-detect" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -93,19 +122,7 @@ static int module_jackdbus_detect_unload(struct module *module) static const struct spa_dict_item module_jackdbus_detect_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.con>" }, { PW_KEY_MODULE_DESCRIPTION, "Creates a JACK client when jackdbus is started" }, - { PW_KEY_MODULE_USAGE, - "channels=<number of channels> " - "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "sink_client_name=<jack client name> " - "sink_channels=<number of channels> " - "sink_channel_map=<channel map> " - "source_name=<name for the source> " - "source_properties=<properties for the source> " - "source_client_name=<jack client name> " - "source_channels=<number of channels> " - "source_channel_map=<channel map> " - "connect=<connect ports?>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c b/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c index 5dd26567..50c36839 100644 --- a/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c @@ -11,6 +11,37 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_ladspa_sink LADSPA Sink + * + * ## Module Name + * + * `module-ladspa-sink` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_filter_chain "libpipewire-module-filter-chain" + */ + +static const char *const pulse_module_options = + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "sink_input_properties=<properties for the sink input> " + "master=<name of sink to filter> " + "sink_master=<name of sink to filter> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<input channel map> " + "plugin=<ladspa plugin name> " + "label=<ladspa plugin label> " + "control=<comma separated list of input control values> " + "input_ladspaport_map=<comma separated list of input LADSPA port names> " + "output_ladspaport_map=<comma separated list of output LADSPA port names> "; + #define NAME "ladspa-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -126,21 +157,7 @@ static int module_ladspa_sink_unload(struct module *module) static const struct spa_dict_item module_ladspa_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Virtual LADSPA sink" }, - { PW_KEY_MODULE_USAGE, - "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "sink_input_properties=<properties for the sink input> " - "master=<name of sink to filter> " - "sink_master=<name of sink to filter> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<input channel map> " - "plugin=<ladspa plugin name> " - "label=<ladspa plugin label> " - "control=<comma separated list of input control values> " - "input_ladspaport_map=<comma separated list of input LADSPA port names> " - "output_ladspaport_map=<comma separated list of output LADSPA port names> "}, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-ladspa-source.c b/src/modules/module-protocol-pulse/modules/module-ladspa-source.c index 2e876ae4..09eb11ce 100644 --- a/src/modules/module-protocol-pulse/modules/module-ladspa-source.c +++ b/src/modules/module-protocol-pulse/modules/module-ladspa-source.c @@ -11,6 +11,37 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_ladspa_source LADSPA Source + * + * ## Module Name + * + * `module-ladspa-source` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_filter_chain "libpipewire-module-filter-chain" + */ + +static const char *const pulse_module_options = + "source_name=<name for the source> " + "source_properties=<properties for the source> " + "source_output_properties=<properties for the source output> " + "master=<name of source to filter> " + "source_master=<name of source to filter> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<input channel map> " + "plugin=<ladspa plugin name> " + "label=<ladspa plugin label> " + "control=<comma separated list of input control values> " + "input_ladspaport_map=<comma separated list of input LADSPA port names> " + "output_ladspaport_map=<comma separated list of output LADSPA port names> "; + #define NAME "ladspa-source" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -126,21 +157,7 @@ static int module_ladspa_source_unload(struct module *module) static const struct spa_dict_item module_ladspa_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Virtual LADSPA source" }, - { PW_KEY_MODULE_USAGE, - "source_name=<name for the source> " - "source_properties=<properties for the source> " - "source_output_properties=<properties for the source output> " - "master=<name of source to filter> " - "source_master=<name of source to filter> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<input channel map> " - "plugin=<ladspa plugin name> " - "label=<ladspa plugin label> " - "control=<comma separated list of input control values> " - "input_ladspaport_map=<comma separated list of input LADSPA port names> " - "output_ladspaport_map=<comma separated list of output LADSPA port names> "}, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-loopback.c b/src/modules/module-protocol-pulse/modules/module-loopback.c index 49507f63..2ef85fa5 100644 --- a/src/modules/module-protocol-pulse/modules/module-loopback.c +++ b/src/modules/module-protocol-pulse/modules/module-loopback.c @@ -11,6 +11,34 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_loopback Loopback + * + * ## Module Name + * + * `module-loopback` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_loopback "libpipewire-module-loopback" + */ + +static const char *const pulse_module_options = + "source=<source to connect to> " + "sink=<sink to connect to> " + "latency_msec=<latency in ms> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "sink_input_properties=<proplist> " + "source_output_properties=<proplist> " + "source_dont_move=<boolean> " + "sink_dont_move=<boolean> " + "remix=<remix channels?> "; + #define NAME "loopback" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -99,17 +127,7 @@ static int module_loopback_unload(struct module *module) static const struct spa_dict_item module_loopback_info[] = { { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "Loopback from source to sink" }, - { PW_KEY_MODULE_USAGE, "source=<source to connect to> " - "sink=<sink to connect to> " - "latency_msec=<latency in ms> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "sink_input_properties=<proplist> " - "source_output_properties=<proplist> " - "source_dont_move=<boolean> " - "sink_dont_move=<boolean> " - "remix=<remix channels?> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c b/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c index 9536945e..6700ad45 100644 --- a/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c +++ b/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c @@ -8,6 +8,22 @@ #include "../pulse-server.h" #include "../server.h" +/** \page page_pulse_module_native_protocol_tcp Pulseaudio TCP Protocol + * + * ## Module Name + * + * `module-native-protocol-tcp` + * + * ## Module Options + * + * @pulse_module_options@ + */ + +static const char *const pulse_module_options = + "port=<TCP port number> " + "listen=<address to listen on> " + "auth-anonymous=<don't check for cookies?>"; + #define NAME "protocol-tcp" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -53,9 +69,7 @@ static int module_native_protocol_tcp_unload(struct module *module) static const struct spa_dict_item module_native_protocol_tcp_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Native protocol (TCP sockets)" }, - { PW_KEY_MODULE_USAGE, "port=<TCP port number> " - "listen=<address to listen on> " - "auth-anonymous=<don't check for cookies?>"}, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-null-sink.c b/src/modules/module-protocol-pulse/modules/module-null-sink.c index d132c651..2d85cd48 100644 --- a/src/modules/module-protocol-pulse/modules/module-null-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-null-sink.c @@ -7,6 +7,25 @@ #include "../manager.h" #include "../module.h" +/** \page page_pulse_module_null_sink Null Sink + * + * ## Module Name + * + * `module-null-sink` + * + * ## Module Options + * + * @pulse_module_options@ + */ + +static const char *const pulse_module_options = + "sink_name=<name of sink> " + "sink_properties=<properties for the sink> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map>"; + #define NAME "null-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -129,12 +148,7 @@ static int module_null_sink_unload(struct module *module) static const struct spa_dict_item module_null_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "A NULL sink" }, - { PW_KEY_MODULE_USAGE, "sink_name=<name of sink> " - "sink_properties=<properties for the sink> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-pipe-sink.c b/src/modules/module-protocol-pulse/modules/module-pipe-sink.c index b3a60f75..0f910ac6 100644 --- a/src/modules/module-protocol-pulse/modules/module-pipe-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-pipe-sink.c @@ -14,6 +14,31 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_pipe_sink Pipe Sink + * + * ## Module Name + * + * `module-pipe-sink` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_pipe_tunnel "libpipewire-module-pipe-tunnel" + */ + +static const char *const pulse_module_options = + "file=<name of the FIFO special file to use> " + "sink_name=<name for the sink> " + "sink_properties=<sink properties> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "use_system_clock_for_timing=<yes or no> "; + #define NAME "pipe-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -94,14 +119,7 @@ static int module_pipe_sink_unload(struct module *module) static const struct spa_dict_item module_pipe_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "Pipe sink" }, - { PW_KEY_MODULE_USAGE, "file=<name of the FIFO special file to use> " - "sink_name=<name for the sink> " - "sink_properties=<sink properties> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "use_system_clock_for_timing=<yes or no> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-pipe-source.c b/src/modules/module-protocol-pulse/modules/module-pipe-source.c index 1261c4ec..c9c31f10 100644 --- a/src/modules/module-protocol-pulse/modules/module-pipe-source.c +++ b/src/modules/module-protocol-pulse/modules/module-pipe-source.c @@ -14,6 +14,30 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_pipe_source Pipe Source + * + * ## Module Name + * + * `module-pipe-source` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_pipe_tunnel "libpipewire-module-pipe-tunnel" + */ + +static const char *const pulse_module_options = + "file=<name of the FIFO special file to use> " + "source_name=<name for the source> " + "source_properties=<source properties> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> "; + #define NAME "pipe-source" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -94,13 +118,7 @@ static int module_pipe_source_unload(struct module *module) static const struct spa_dict_item module_pipe_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "Pipe source" }, - { PW_KEY_MODULE_USAGE, "file=<name of the FIFO special file to use> " - "source_name=<name for the source> " - "source_properties=<source properties> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-raop-discover.c b/src/modules/module-protocol-pulse/modules/module-raop-discover.c index c4391837..4e1cf8ad 100644 --- a/src/modules/module-protocol-pulse/modules/module-raop-discover.c +++ b/src/modules/module-protocol-pulse/modules/module-raop-discover.c @@ -8,6 +8,21 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_raop_discover RAOP Discover + * + * ## Module Name + * + * `module-raop-discover` + * + * ## Module Options + * + * No options. + * + * ## See Also + * + * \ref page_module_raop_discover "libpipewire-module-raop-discover" + */ + #define NAME "raop-discover" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); diff --git a/src/modules/module-protocol-pulse/modules/module-remap-sink.c b/src/modules/module-protocol-pulse/modules/module-remap-sink.c index 1f40c652..103579fe 100644 --- a/src/modules/module-protocol-pulse/modules/module-remap-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-remap-sink.c @@ -10,6 +10,33 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_remap_sink Remap Sink + * + * ## Module Name + * + * `module-remap-sink` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_loopback "libpipewire-module-loopback" + */ + +static const char *const pulse_module_options = + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "master=<name of sink to remap> " + "master_channel_map=<channel map> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "resample_method=<resampler> " + "remix=<remix channels?>"; + #define NAME "remap-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -96,16 +123,7 @@ static int module_remap_sink_unload(struct module *module) static const struct spa_dict_item module_remap_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Remap sink channels" }, - { PW_KEY_MODULE_USAGE, "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "master=<name of sink to remap> " - "master_channel_map=<channel map> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "resample_method=<resampler> " - "remix=<remix channels?>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-remap-source.c b/src/modules/module-protocol-pulse/modules/module-remap-source.c index 6e806d1b..eaff91b0 100644 --- a/src/modules/module-protocol-pulse/modules/module-remap-source.c +++ b/src/modules/module-protocol-pulse/modules/module-remap-source.c @@ -10,6 +10,33 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_remap_source Remap Source + * + * ## Module Name + * + * `module-remap-source` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_loopback "libpipewire-module-loopback" + */ + +static const char *const pulse_module_options = + "source_name=<name for the source> " + "source_properties=<properties for the source> " + "master=<name of source to filter> " + "master_channel_map=<channel map> " + "format=<sample format> " + "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " + "resample_method=<resampler> " + "remix=<remix channels?>"; + #define NAME "remap-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -96,16 +123,7 @@ static int module_remap_source_unload(struct module *module) static const struct spa_dict_item module_remap_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Remap source channels" }, - { PW_KEY_MODULE_USAGE, "source_name=<name for the source> " - "source_properties=<properties for the source> " - "master=<name of source to filter> " - "master_channel_map=<channel map> " - "format=<sample format> " - "rate=<sample rate> " - "channels=<number of channels> " - "channel_map=<channel map> " - "resample_method=<resampler> " - "remix=<remix channels?>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c b/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c index 09df1a5e..2200a7bf 100644 --- a/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c +++ b/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c @@ -9,7 +9,34 @@ #include "../defs.h" #include "../module.h" -#define NAME "roc-source" +/** \page page_pulse_module_roc_sink_input ROC Sink Input + * + * ## Module Name + * + * `module-roc-sink-input` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_roc_source "libpipewire-module-roc-source" + */ + +static const char *const pulse_module_options = + "sink=<name for the sink> " + "sink_input_properties=<properties for the sink_input> " + "resampler_profile=<empty>|high|medium|low " + "fec_code=<empty>|disable|rs8m|ldpc " + "sess_latency_msec=<target network latency in milliseconds> " + "local_ip=<local receiver ip> " + "local_source_port=<local receiver port for source packets> " + "local_repair_port=<local receiver port for repair packets> " + "local_control_port=<local receiver port for control packets> " + ; + +#define NAME "roc-sink-input" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic @@ -92,14 +119,7 @@ static int module_roc_sink_input_unload(struct module *module) static const struct spa_dict_item module_roc_sink_input_info[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "roc sink-input" }, - { PW_KEY_MODULE_USAGE, "sink=<name for the sink> " - "sink_input_properties=<properties for the sink_input> " - "resampler_profile=<empty>|disable|high|medium|low " - "fec_code=<empty>|disable|rs8m|ldpc " - "sess_latency_msec=<target network latency in milliseconds> " - "local_ip=<local receiver ip> " - "local_source_port=<local receiver port for source packets> " - "local_repair_port=<local receiver port for repair packets> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -144,6 +164,11 @@ static int module_roc_sink_input_prepare(struct module * const module) pw_properties_set(props, "local_repair_port", NULL); } + if ((str = pw_properties_get(props, "local_control_port")) != NULL) { + pw_properties_set(roc_props, "local.control.port", str); + pw_properties_set(props, "local_control_port", NULL); + } + if ((str = pw_properties_get(props, "sess_latency_msec")) != NULL) { pw_properties_set(roc_props, "sess.latency.msec", str); pw_properties_set(props, "sess_latency_msec", NULL); diff --git a/src/modules/module-protocol-pulse/modules/module-roc-sink.c b/src/modules/module-protocol-pulse/modules/module-roc-sink.c index 0a4420d3..6b03006a 100644 --- a/src/modules/module-protocol-pulse/modules/module-roc-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-roc-sink.c @@ -9,6 +9,31 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_roc_sink ROC Sink + * + * ## Module Name + * + * `module-roc-sink` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_roc_sink "libpipewire-module-roc-sink" + */ + +static const char *const pulse_module_options = + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "fec_code=<empty>|disable|rs8m|ldpc " + "remote_ip=<remote receiver ip> " + "remote_source_port=<remote receiver port for source packets> " + "remote_repair_port=<remote receiver port for repair packets> " + "remote_control_port=<remote receiver port for control packets> " + ; + #define NAME "roc-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -96,17 +121,13 @@ static const char* const valid_args[] = { "remote_ip", "remote_source_port", "remote_repair_port", + "remote_control_port", NULL }; static const struct spa_dict_item module_roc_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "roc sink" }, - { PW_KEY_MODULE_USAGE, "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "fec_code=<empty>|disable|rs8m|ldpc " - "remote_ip=<remote receiver ip> " - "remote_source_port=<remote receiver port for source packets> " - "remote_repair_port=<remote receiver port for repair packets> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -159,6 +180,12 @@ static int module_roc_sink_prepare(struct module * const module) pw_properties_set(roc_props, "remote.repair.port", str); pw_properties_set(props, "remote_repair_port", NULL); } + + if ((str = pw_properties_get(props, "remote_control_port")) != NULL) { + pw_properties_set(roc_props, "remote.control.port", str); + pw_properties_set(props, "remote_control_port", NULL); + } + if ((str = pw_properties_get(props, "fec_code")) != NULL) { pw_properties_set(roc_props, "fec.code", str); pw_properties_set(props, "fec_code", NULL); diff --git a/src/modules/module-protocol-pulse/modules/module-roc-source.c b/src/modules/module-protocol-pulse/modules/module-roc-source.c index 9f23c421..661153d1 100644 --- a/src/modules/module-protocol-pulse/modules/module-roc-source.c +++ b/src/modules/module-protocol-pulse/modules/module-roc-source.c @@ -9,6 +9,33 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_roc_source ROC Source + * + * ## Module Name + * + * `module-roc-source` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_roc_source "libpipewire-module-roc-source" + */ + +static const char *const pulse_module_options = + "source_name=<name for the source> " + "source_properties=<properties for the source> " + "resampler_profile=<empty>|high|medium|low " + "fec_code=<empty>|disable|rs8m|ldpc " + "sess_latency_msec=<target network latency in milliseconds> " + "local_ip=<local receiver ip> " + "local_source_port=<local receiver port for source packets> " + "local_repair_port=<local receiver port for repair packets> " + "local_control_port=<local receiver port for control packets> " + ; + #define NAME "roc-source" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -98,20 +125,14 @@ static const char* const valid_args[] = { "local_ip", "local_source_port", "local_repair_port", + "local_control_port", NULL }; static const struct spa_dict_item module_roc_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "roc source" }, - { PW_KEY_MODULE_USAGE, "source_name=<name for the source> " - "source_properties=<properties for the source> " - "resampler_profile=<empty>|disable|high|medium|low " - "fec_code=<empty>|disable|rs8m|ldpc " - "sess_latency_msec=<target network latency in milliseconds> " - "local_ip=<local receiver ip> " - "local_source_port=<local receiver port for source packets> " - "local_repair_port=<local receiver port for repair packets> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -161,6 +182,11 @@ static int module_roc_source_prepare(struct module * const module) pw_properties_set(props, "local_repair_port", NULL); } + if ((str = pw_properties_get(props, "local_control_port")) != NULL) { + pw_properties_set(roc_props, "local.control.port", str); + pw_properties_set(props, "local_control_port", NULL); + } + if ((str = pw_properties_get(props, "sess_latency_msec")) != NULL) { pw_properties_set(roc_props, "sess.latency.msec", str); pw_properties_set(props, "sess_latency_msec", NULL); diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c index 2581e78c..1777caf6 100644 --- a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c +++ b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c @@ -8,6 +8,26 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_rtp_recv RTP Receiver + * + * ## Module Name + * + * `module-rtp-recv` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_rtp_sap "libpipewire-module-rtp-sap" + */ + +static const char *const pulse_module_options = + "sink=<name of the sink> " + "sap_address=<multicast address to listen on> " + "latency_msec=<latency in ms> "; + #define NAME "rtp-recv" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -94,9 +114,7 @@ static int module_rtp_recv_unload(struct module *module) static const struct spa_dict_item module_rtp_recv_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Receive data from a network via RTP/SAP/SDP" }, - { PW_KEY_MODULE_USAGE, "sink=<name of the sink> " - "sap_address=<multicast address to listen on> " - "latency_msec=<latency in ms> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-send.c b/src/modules/module-protocol-pulse/modules/module-rtp-send.c index 3afa8ae4..e8496295 100644 --- a/src/modules/module-protocol-pulse/modules/module-rtp-send.c +++ b/src/modules/module-protocol-pulse/modules/module-rtp-send.c @@ -8,6 +8,36 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_rtp_send RTP Sender + * + * ## Module Name + * + * `module-rtp-send` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_rtp_sink "libpipewire-module-rtp-sink" + */ + +static const char *const pulse_module_options = + "source=<name of the source> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "destination_ip=<destination IP address> " + "source_ip=<source IP address> " + "port=<port number> " + "mtu=<maximum transfer unit> " + "loop=<loopback to local host?> " + "ttl=<ttl value> " + "inhibit_auto_suspend=<always|never|only_with_non_monitor_sources> " + "stream_name=<name of the stream> " + "enable_opus=<enable OPUS codec>"; + #define NAME "rtp-send" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -136,19 +166,7 @@ static int module_rtp_send_unload(struct module *module) static const struct spa_dict_item module_rtp_send_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Read data from source and send it to the network via RTP/SAP/SDP" }, - { PW_KEY_MODULE_USAGE, "source=<name of the source> " - "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate> " - "destination_ip=<destination IP address> " - "source_ip=<source IP address> " - "port=<port number> " - "mtu=<maximum transfer unit> " - "loop=<loopback to local host?> " - "ttl=<ttl value> " - "inhibit_auto_suspend=<always|never|only_with_non_monitor_sources> " - "stream_name=<name of the stream> " - "enable_opus=<enable OPUS codec>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c b/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c index d0479a78..4ca4156c 100644 --- a/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c +++ b/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c @@ -8,6 +8,33 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_simple_protocol_tcp Simple TCP Protocol + * + * ## Module Name + * + * `module-simple-protocol-tcp` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_protocol_simple "libpipewire-module-protocol-simple" + */ + +static const char *const pulse_module_options = + "rate=<sample rate> " + "format=<sample format> " + "channels=<number of channels> " + "channel_map=<number of channels> " + "sink=<sink to connect to> " + "source=<source to connect to> " + "playback=<enable playback?> " + "record=<enable record?> " + "port=<TCP port number> " + "listen=<address to listen on>"; + #define NAME "simple-protocol-tcp" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -84,16 +111,7 @@ static int module_simple_protocol_tcp_unload(struct module *module) static const struct spa_dict_item module_simple_protocol_tcp_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Simple protocol (TCP sockets)" }, - { PW_KEY_MODULE_USAGE, "rate=<sample rate> " - "format=<sample format> " - "channels=<number of channels> " - "channel_map=<number of channels> " - "sink=<sink to connect to> " - "source=<source to connect to> " - "playback=<enable playback?> " - "record=<enable record?> " - "port=<TCP port number> " - "listen=<address to listen on>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c b/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c index 76c0100c..e4dc2dea 100644 --- a/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c +++ b/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c @@ -15,6 +15,22 @@ #include "../manager.h" #include "../collect.h" +/** \page page_pulse_module_switch_on_connect Switch on Connect + * + * ## Module Name + * + * `module-switch-on-connect` + * + * ## Module Options + * + * @pulse_module_options@ + */ + +static const char *const pulse_module_options = + "only_from_unavailable=<boolean, only switch from unavailable ports (not implemented yet)> " + "ignore_virtual=<boolean, ignore new virtual sinks and sources, defaults to true> " + "blocklist=<regex, ignore matching devices, default=hdmi> "; + #define NAME "switch-on-connect" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -215,9 +231,7 @@ static const struct spa_dict_item module_switch_on_connect_info[] = { "This module exists for Pulseaudio compatibility, and is useful only when some applications " "try to manage the default sinks/sources themselves and interfere with PipeWire's builtin " "default device switching." }, - { PW_KEY_MODULE_USAGE, "only_from_unavailable=<boolean, only switch from unavailable ports (not implemented yet)> " - "ignore_virtual=<boolean, ignore new virtual sinks and sources, defaults to true> " - "blocklist=<regex, ignore matching devices, default=hdmi> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c b/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c index c2c14417..b779a3e5 100644 --- a/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c @@ -12,6 +12,33 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_tunnel_sink Tunnel Sink + * + * ## Module Name + * + * `module-tunnel-sink` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_pulse_tunnel "libpipewire-module-pulse-tunnel" + */ + +static const char *const pulse_module_options = + "server=<address> " + "sink=<name of the remote sink> " + "sink_name=<name for the local sink> " + "sink_properties=<properties for the local sink> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map> " + "latency_msec=<fixed latency in ms> " + "cookie=<cookie file path>"; + #define NAME "tunnel-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -92,17 +119,7 @@ static int module_tunnel_sink_unload(struct module *module) static const struct spa_dict_item module_tunnel_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Create a network sink which connects to a remote PulseAudio server" }, - { PW_KEY_MODULE_USAGE, - "server=<address> " - "sink=<name of the remote sink> " - "sink_name=<name for the local sink> " - "sink_properties=<properties for the local sink> " - "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate> " - "channel_map=<channel map> " - "latency_msec=<fixed latency in ms> " - "cookie=<cookie file path>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-tunnel-source.c b/src/modules/module-protocol-pulse/modules/module-tunnel-source.c index 5e7eda3c..b67efd5b 100644 --- a/src/modules/module-protocol-pulse/modules/module-tunnel-source.c +++ b/src/modules/module-protocol-pulse/modules/module-tunnel-source.c @@ -12,6 +12,33 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_tunnel_source Tunnel Source + * + * ## Module Name + * + * `module-tunnel-source` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_pulse_tunnel "libpipewire-module-pulse-tunnel" + */ + +static const char *const pulse_module_options = + "server=<address> " + "source=<name of the remote source> " + "source_name=<name for the local source> " + "source_properties=<properties for the local source> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map> " + "latency_msec=<fixed latency in ms> " + "cookie=<cookie file path>"; + #define NAME "tunnel-source" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -92,17 +119,7 @@ static int module_tunnel_source_unload(struct module *module) static const struct spa_dict_item module_tunnel_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Create a network source which connects to a remote PulseAudio server" }, - { PW_KEY_MODULE_USAGE, - "server=<address> " - "source=<name of the remote source> " - "source_name=<name for the local source> " - "source_properties=<properties for the local source> " - "format=<sample format> " - "channels=<number of channels> " - "rate=<sample rate> " - "channel_map=<channel map> " - "latency_msec=<fixed latency in ms> " - "cookie=<cookie file path>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-virtual-sink.c b/src/modules/module-protocol-pulse/modules/module-virtual-sink.c index a9ed6f35..e3193f2f 100644 --- a/src/modules/module-protocol-pulse/modules/module-virtual-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-virtual-sink.c @@ -10,6 +10,30 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_virtual_sink Virtual Sink + * + * ## Module Name + * + * `module-virtual-sink` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_loopback "libpipewire-module-loopback" + */ + +static const char *const pulse_module_options = + "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "master=<name of sink to filter> " + "channels=<number of channels> " + "channel_map=<channel map> " + "use_volume_sharing=<yes or no> " + "force_flat_volume=<yes or no> "; + #define NAME "virtual-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -98,13 +122,7 @@ static int module_virtual_sink_unload(struct module *module) static const struct spa_dict_item module_virtual_sink_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Virtual sink" }, - { PW_KEY_MODULE_USAGE, "sink_name=<name for the sink> " - "sink_properties=<properties for the sink> " - "master=<name of sink to filter> " - "channels=<number of channels> " - "channel_map=<channel map> " - "use_volume_sharing=<yes or no> " - "force_flat_volume=<yes or no> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-virtual-source.c b/src/modules/module-protocol-pulse/modules/module-virtual-source.c index 731822b9..fe3f1064 100644 --- a/src/modules/module-protocol-pulse/modules/module-virtual-source.c +++ b/src/modules/module-protocol-pulse/modules/module-virtual-source.c @@ -11,6 +11,31 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_virtual_source Virtual Source + * + * ## Module Name + * + * `module-virtual-source` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_loopback "libpipewire-module-loopback" + */ + +static const char *const pulse_module_options = + "source_name=<name for the source> " + "source_properties=<properties for the source> " + "master=<name of source to filter> " + "uplink_sink=<name> (optional)" + "channels=<number of channels> " + "channel_map=<channel map> " + "use_volume_sharing=<yes or no> " + "force_flat_volume=<yes or no> "; + #define NAME "virtual-source" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -99,14 +124,7 @@ static int module_virtual_source_unload(struct module *module) static const struct spa_dict_item module_virtual_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "Loopback from source to sink" }, - { PW_KEY_MODULE_USAGE, "source_name=<name for the source> " - "source_properties=<properties for the source> " - "master=<name of source to filter> " - "uplink_sink=<name> (optional)" - "channels=<number of channels> " - "channel_map=<channel map> " - "use_volume_sharing=<yes or no> " - "force_flat_volume=<yes or no> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-x11-bell.c b/src/modules/module-protocol-pulse/modules/module-x11-bell.c index 6e6781c1..a29e2e85 100644 --- a/src/modules/module-protocol-pulse/modules/module-x11-bell.c +++ b/src/modules/module-protocol-pulse/modules/module-x11-bell.c @@ -6,6 +6,27 @@ #include "../module.h" +/** \page page_pulse_module_x11_bell X11 Bell + * + * ## Module Name + * + * `module-x11-bell` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_x11_bell "libpipewire-module-x11-bell" + */ + +static const char *const pulse_module_options = + "sink=<sink to connect to> " + "sample=<the sample to play> " + "display=<X11 display> " + "xauthority=<X11 Authority>"; + #define NAME "x11-bell" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -93,10 +114,7 @@ static int module_x11_bell_prepare(struct module * const module) static const struct spa_dict_item module_x11_bell_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "X11 bell interceptor" }, - { PW_KEY_MODULE_USAGE, "sink=<sink to connect to> " - "sample=<the sample to play> " - "display=<X11 display> " - "xauthority=<X11 Authority>" }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c index c42e01fd..5b1763eb 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c @@ -8,6 +8,24 @@ #include "../defs.h" #include "../module.h" +/** \page page_pulse_module_zeroconf_discover Zeroconf Discover + * + * ## Module Name + * + * `module-zeroconf-discover` + * + * ## Module Options + * + * @pulse_module_options@ + * + * ## See Also + * + * \ref page_module_zeroconf_discover "libpipewire-module-zeroconf-discover" + */ + +static const char *const pulse_module_options = + "latency_msec=<fixed latency in ms> "; + #define NAME "zeroconf-discover" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); @@ -84,8 +102,7 @@ static int module_zeroconf_discover_unload(struct module *module) static const struct spa_dict_item module_zeroconf_discover_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.con>" }, { PW_KEY_MODULE_DESCRIPTION, "mDNS/DNS-SD Service Discovery" }, - { PW_KEY_MODULE_USAGE, - "latency_msec=<fixed latency in ms> " }, + { PW_KEY_MODULE_USAGE, pulse_module_options }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c index e83ccb90..21c41233 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c @@ -24,6 +24,17 @@ #include <avahi-common/domain.h> #include <avahi-common/malloc.h> +/** \page page_pulse_module_zeroconf_publish Zeroconf Publish + * + * ## Module Name + * + * `module-zeroconf-publish` + * + * ## Module Options + * + * No options. + */ + #define NAME "zeroconf-publish" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 0f654cdc..c922ca0d 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -3770,17 +3770,9 @@ static int fill_sink_info(struct client *client, struct message *m, TAG_INVALID); } if (client->version >= 15) { - bool is_linked = collect_is_linked(manager, o->id, SPA_DIRECTION_INPUT); - int state = node_state(info->state); - - /* running with nothing linked is probably the monitor that is - * keeping this sink busy */ - if (state == STATE_RUNNING && !is_linked) - state = STATE_IDLE; - message_put(m, TAG_VOLUME, dev_info.volume_info.base, /* base volume */ - TAG_U32, state, /* state */ + TAG_U32, dev_info.state, /* state */ TAG_U32, dev_info.volume_info.steps, /* n_volume_steps */ TAG_U32, card ? card->index : SPA_ID_INVALID, /* card index */ TAG_INVALID); @@ -3974,17 +3966,9 @@ static int fill_source_info(struct client *client, struct message *m, TAG_INVALID); } if (client->version >= 15) { - bool is_linked = collect_is_linked(manager, o->id, SPA_DIRECTION_OUTPUT); - int state = node_state(info->state); - - /* running with nothing linked is probably the sink that is - * keeping this source busy */ - if (state == STATE_RUNNING && !is_linked) - state = STATE_IDLE; - message_put(m, TAG_VOLUME, dev_info.volume_info.base, /* base volume */ - TAG_U32, state, /* state */ + TAG_U32, dev_info.state, /* state */ TAG_U32, dev_info.volume_info.steps, /* n_volume_steps */ TAG_U32, card ? card->index : SPA_ID_INVALID, /* card index */ TAG_INVALID); @@ -4104,7 +4088,7 @@ static int fill_sink_input_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 19) message_put(m, - TAG_BOOLEAN, info->state != PW_NODE_STATE_RUNNING, /* corked */ + TAG_BOOLEAN, dev_info.state != STATE_RUNNING, /* corked */ TAG_INVALID); if (client->version >= 20) message_put(m, @@ -4178,7 +4162,7 @@ static int fill_source_output_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 19) message_put(m, - TAG_BOOLEAN, info->state != PW_NODE_STATE_RUNNING, /* corked */ + TAG_BOOLEAN, dev_info.state != STATE_RUNNING, /* corked */ TAG_INVALID); if (client->version >= 22) { struct format_info fi; diff --git a/src/modules/module-protocol-simple.c b/src/modules/module-protocol-simple.c index 697a6b84..e6f54a90 100644 --- a/src/modules/module-protocol-simple.c +++ b/src/modules/module-protocol-simple.c @@ -29,7 +29,7 @@ #include <pipewire/impl.h> -/** \page page_module_protocol_simple PipeWire Module: Protocol Simple +/** \page page_module_protocol_simple Protocol Simple * * The simple protocol provides a bidirectional audio stream on a network * socket. @@ -40,6 +40,10 @@ * Each client that connects will create a capture and/or playback stream, * depending on the configuration options. * + * ## Module Name + * + * `libpipewire-module-protocol-simple` + * * ## Module Options * * - `capture`: boolean if capture is enabled. This will create a capture stream diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 82be905e..a11936f5 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -35,7 +35,7 @@ #include "module-protocol-pulse/defs.h" #include "module-protocol-pulse/format.h" -/** \page page_module_pulse_tunnel PipeWire Module: Pulse Tunnel +/** \page page_module_pulse_tunnel Pulse Tunnel * * The pulse-tunnel module provides a source or sink that tunnels all audio to * a remote PulseAudio connection. @@ -47,6 +47,10 @@ * automatically load the tunnel with the right parameters based on zeroconf * information. * + * ## Module Name + * + * `libpipewire-module-pulse-tunnel` + * * ## Module Options * * - `tunnel.mode`: the desired tunnel to create, must be `source` or `sink`. diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index b157e571..b5837446 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -26,7 +26,7 @@ #include "module-protocol-pulse/format.h" #include "module-zeroconf-discover/avahi-poll.h" -/** \page page_module_raop_discover PipeWire Module: RAOP Discover +/** \page page_module_raop_discover RAOP Discover * * Automatically creates RAOP (Airplay) sink devices based on zeroconf * information. @@ -37,6 +37,10 @@ * If no stream.rules are given, it will create a sink for all announced * streams. * + * ## Module Name + * + * `libpipewire-module-raop-discover` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index c91f80a9..fee6d520 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -47,7 +47,7 @@ #include "module-rtp/rtp.h" #include "module-rtp/stream.h" -/** \page page_module_raop_sink PipeWire Module: AirPlay Sink +/** \page page_module_raop_sink AirPlay Sink * * Creates a new Sink to stream to an Airplay device. * @@ -55,6 +55,10 @@ * with the right parameters but it is possible to manually create a RAOP sink * as well. * + * ## Module Name + * + * `libpipewire-module-raop-sink` + * * ## Module Options * * Options specific to the behavior of this module @@ -1854,6 +1858,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(props, PW_KEY_NODE_NAME, "raop_sink.%s.%s.%s", hostname, ip, port); + if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RAOP to %s", name); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s", name); if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL) @@ -1891,6 +1897,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); copy_props(impl, props, PW_KEY_MEDIA_FORMAT); + copy_props(impl, props, PW_KEY_MEDIA_NAME); copy_props(impl, props, "net.mtu"); copy_props(impl, props, "rtp.sender-ts-offset"); copy_props(impl, props, "sess.media"); diff --git a/src/modules/module-roc-sink.c b/src/modules/module-roc-sink.c index 177877e3..a93dbc78 100644 --- a/src/modules/module-roc-sink.c +++ b/src/modules/module-roc-sink.c @@ -19,17 +19,22 @@ #include <roc/log.h> #include <roc/sender.h> +#include <pipewire/cleanup.h> #include <pipewire/pipewire.h> #include <pipewire/impl.h> #include "module-roc/common.h" -/** \page page_module_roc_sink PipeWire Module: ROC sink +/** \page page_module_roc_sink ROC sink * * The `roc-sink` module creates a PipeWire sink that sends samples to * a preconfigured receiver address. One can then connect an audio stream * of any running application to that sink or make it the default sink. * + * ## Module Name + * + * `libpipewire-module-roc-sink` + * * ## Module Options * * Options specific to the behavior of this module @@ -39,6 +44,7 @@ * - `remote.ip = <str>`: remote receiver ip * - `remote.source.port = <str>`: remote receiver TCP/UDP port for source packets * - `remote.repair.port = <str>`: remote receiver TCP/UDP port for receiver packets + * - `remote.control.port = <str>`: remote receiver TCP/UDP port for control packets * - `fec.code = <str>`: Possible values: `disable`, `rs8m`, `ldpc` * * ## General options @@ -58,6 +64,7 @@ * remote.ip = 192.168.0.244 * remote.source.port = 10001 * remote.repair.port = 10002 + * remote.control.port = 10003 * sink.name = "ROC Sink" * sink.props = { * node.name = "roc-sink" @@ -77,7 +84,6 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); struct module_roc_sink_data { struct pw_impl_module *module; struct spa_hook module_listener; - struct pw_properties *props; struct pw_context *module_context; struct pw_core *core; @@ -100,6 +106,9 @@ struct module_roc_sink_data { char *remote_ip; int remote_source_port; int remote_repair_port; + + roc_endpoint *remote_control_addr; + int remote_control_port; }; static void stream_destroy(void *d) @@ -207,17 +216,13 @@ static void impl_destroy(struct module_roc_sink_data *data) pw_core_disconnect(data->core); pw_properties_free(data->capture_props); - pw_properties_free(data->props); - if (data->sender) - roc_sender_close(data->sender); - if (data->context) - roc_context_close(data->context); + spa_clear_ptr(data->sender, roc_sender_close); + spa_clear_ptr(data->context, roc_context_close); - if (data->remote_source_addr) - (void) roc_endpoint_deallocate(data->remote_source_addr); - if (data->remote_repair_addr) - (void) roc_endpoint_deallocate(data->remote_repair_addr); + spa_clear_ptr(data->remote_source_addr, roc_endpoint_deallocate); + spa_clear_ptr(data->remote_repair_addr, roc_endpoint_deallocate); + spa_clear_ptr(data->remote_control_addr, roc_endpoint_deallocate); free(data->remote_ip); free(data); @@ -255,11 +260,11 @@ static int roc_sink_setup(struct module_roc_sink_data *data) return -EINVAL; } - memset(&sender_config, 0, sizeof(sender_config)); + spa_zero(sender_config); - sender_config.frame_sample_rate = data->rate; - sender_config.frame_channels = ROC_CHANNEL_SET_STEREO; - sender_config.frame_encoding = ROC_FRAME_ENCODING_PCM_FLOAT; + sender_config.frame_encoding.rate = data->rate; + sender_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO; + sender_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32; sender_config.fec_encoding = data->fec_code; info.rate = data->rate; @@ -278,21 +283,7 @@ static int roc_sink_setup(struct module_roc_sink_data *data) return -EINVAL; } - switch (data->fec_code) { - case ROC_FEC_ENCODING_DEFAULT: - case ROC_FEC_ENCODING_RS8M: - audio_proto = ROC_PROTO_RTP_RS8M_SOURCE; - repair_proto = ROC_PROTO_RS8M_REPAIR; - break; - case ROC_FEC_ENCODING_LDPC_STAIRCASE: - audio_proto = ROC_PROTO_RTP_LDPC_SOURCE; - repair_proto = ROC_PROTO_LDPC_REPAIR; - break; - default: - audio_proto = ROC_PROTO_RTP; - repair_proto = 0; - break; - } + pw_roc_fec_encoding_to_proto(data->fec_code, &audio_proto, &repair_proto); res = pw_roc_create_endpoint(&data->remote_source_addr, audio_proto, data->remote_ip, data->remote_source_port); if (res < 0) { @@ -320,6 +311,18 @@ static int roc_sink_setup(struct module_roc_sink_data *data) } } + res = pw_roc_create_endpoint(&data->remote_control_addr, PW_ROC_DEFAULT_CONTROL_PROTO, data->remote_ip, data->remote_control_port); + if (res < 0) { + pw_log_error("failed to create control endpoint: %s", spa_strerror(res)); + return res; + } + + if (roc_sender_connect(data->sender, ROC_SLOT_DEFAULT, ROC_INTERFACE_AUDIO_CONTROL, + data->remote_control_addr) != 0) { + pw_log_error("can't connect roc sender to remote control address"); + return -EINVAL; + } + data->capture = pw_stream_new(data->core, "roc-sink capture", data->capture_props); data->capture_props = NULL; @@ -354,6 +357,7 @@ static const struct spa_dict_item module_roc_sink_info[] = { "remote.ip=<remote receiver ip> " "( remote.source.port=<remote receiver port for source packets> ) " "( remote.repair.port=<remote receiver port for repair packets> ) " + "( remote.control.port=<remote receiver port for control packets> ) " "( sink.props= { key=val ... } ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -363,7 +367,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct module_roc_sink_data *data; - struct pw_properties *props = NULL, *capture_props = NULL; + struct pw_properties *capture_props = NULL; const char *str; int res = 0; @@ -376,13 +380,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (args == NULL) args = ""; - props = pw_properties_new_string(args); + spa_autoptr(pw_properties) props = pw_properties_new_string(args); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto out; } - data->props = props; capture_props = pw_properties_new(NULL, NULL); if (capture_props == NULL) { @@ -397,7 +400,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if ((str = pw_properties_get(props, "sink.name")) != NULL) { pw_properties_set(capture_props, PW_KEY_NODE_NAME, str); - pw_properties_set(props, "sink.name", NULL); } if ((str = pw_properties_get(props, "sink.props")) != NULL) @@ -414,13 +416,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if ((str = pw_properties_get(capture_props, PW_KEY_MEDIA_CLASS)) == NULL) pw_properties_set(capture_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); - data->rate = pw_properties_get_uint32(capture_props, PW_KEY_AUDIO_RATE, data->rate); + data->rate = pw_properties_get_uint32(capture_props, PW_KEY_AUDIO_RATE, 0); if (data->rate == 0) data->rate = PW_ROC_DEFAULT_RATE; if ((str = pw_properties_get(props, "remote.ip")) != NULL) { data->remote_ip = strdup(str); - pw_properties_set(props, "remote.ip", NULL); } else { pw_log_error("Remote IP not specified"); res = -EINVAL; @@ -429,24 +430,28 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if ((str = pw_properties_get(props, "remote.source.port")) != NULL) { data->remote_source_port = pw_properties_parse_int(str); - pw_properties_set(props, "remote.source.port", NULL); } else { data->remote_source_port = PW_ROC_DEFAULT_SOURCE_PORT; } if ((str = pw_properties_get(props, "remote.repair.port")) != NULL) { data->remote_repair_port = pw_properties_parse_int(str); - pw_properties_set(props, "remote.repair.port", NULL); } else { data->remote_repair_port = PW_ROC_DEFAULT_REPAIR_PORT; } + + if ((str = pw_properties_get(props, "remote.control.port")) != NULL) { + data->remote_control_port = pw_properties_parse_int(str); + } else { + data->remote_control_port = PW_ROC_DEFAULT_CONTROL_PORT; + } + if ((str = pw_properties_get(props, "fec.code")) != NULL) { if (pw_roc_parse_fec_encoding(&data->fec_code, str)) { pw_log_error("Invalid fec code %s, using default", str); data->fec_code = ROC_FEC_ENCODING_DEFAULT; } pw_log_info("using fec.code %s %d", str, data->fec_code); - pw_properties_set(props, "fec.code", NULL); } else { data->fec_code = ROC_FEC_ENCODING_DEFAULT; } diff --git a/src/modules/module-roc-source.c b/src/modules/module-roc-source.c index 10245089..aca3cd3e 100644 --- a/src/modules/module-roc-source.c +++ b/src/modules/module-roc-source.c @@ -19,17 +19,22 @@ #include <roc/log.h> #include <roc/receiver.h> +#include <pipewire/cleanup.h> #include <pipewire/pipewire.h> #include <pipewire/impl.h> #include "module-roc/common.h" -/** \page page_module_roc_source PipeWire Module: ROC source +/** \page page_module_roc_source ROC source * * The `roc-source` module creates a PipeWire source that receives samples * from ROC sender and passes them to the sink it is connected to. One can * then connect it to any audio device. * + * ## Module Name + * + * `libpipewire-module-roc-source` + * * ## Module Options * * Options specific to the behavior of this module @@ -39,6 +44,7 @@ * - `local.ip = <str>`: local sender ip * - `local.source.port = <str>`: local receiver TCP/UDP port for source packets * - `local.repair.port = <str>`: local receiver TCP/UDP port for receiver packets + * - `local.control.port = <str>`: local receiver TCP/UDP port for control packets * - `sess.latency.msec = <str>`: target network latency in milliseconds * - `resampler.profile = <str>`: Possible values: `disable`, `high`, * `medium`, `low`. @@ -63,6 +69,7 @@ * sess.latency.msec = 5000 * local.source.port = 10001 * local.repair.port = 10002 + * local.control.port = 10003 * source.name = "ROC Source" * source.props = { * node.name = "roc-source" @@ -82,7 +89,6 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); struct module_roc_source_data { struct pw_impl_module *module; struct spa_hook module_listener; - struct pw_properties *props; struct pw_context *module_context; struct pw_core *core; @@ -108,6 +114,9 @@ struct module_roc_source_data { int local_source_port; int local_repair_port; int sess_latency_msec; + + roc_endpoint *local_control_addr; + int local_control_port; }; static void stream_destroy(void *d) @@ -216,17 +225,13 @@ static void impl_destroy(struct module_roc_source_data *data) pw_core_disconnect(data->core); pw_properties_free(data->playback_props); - pw_properties_free(data->props); - if (data->receiver) - roc_receiver_close(data->receiver); - if (data->context) - roc_context_close(data->context); + spa_clear_ptr(data->receiver, roc_receiver_close); + spa_clear_ptr(data->context, roc_context_close); - if (data->local_source_addr) - (void) roc_endpoint_deallocate(data->local_source_addr); - if (data->local_repair_addr) - (void) roc_endpoint_deallocate(data->local_repair_addr); + spa_clear_ptr(data->local_source_addr, roc_endpoint_deallocate); + spa_clear_ptr(data->local_repair_addr, roc_endpoint_deallocate); + spa_clear_ptr(data->local_control_addr, roc_endpoint_deallocate); free(data->local_ip); free(data); @@ -264,9 +269,10 @@ static int roc_source_setup(struct module_roc_source_data *data) } spa_zero(receiver_config); - receiver_config.frame_sample_rate = data->rate; - receiver_config.frame_channels = ROC_CHANNEL_SET_STEREO; - receiver_config.frame_encoding = ROC_FRAME_ENCODING_PCM_FLOAT; + + receiver_config.frame_encoding.rate = data->rate; + receiver_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO; + receiver_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32; receiver_config.resampler_profile = data->resampler_profile; info.rate = data->rate; @@ -291,7 +297,7 @@ static int roc_source_setup(struct module_roc_source_data *data) * See API reference: * https://roc-streaming.org/toolkit/docs/api/reference.html */ - receiver_config.target_latency = (unsigned long long)data->sess_latency_msec * 1000000ULL; + receiver_config.target_latency = (unsigned long long)data->sess_latency_msec * SPA_NSEC_PER_MSEC; res = roc_receiver_open(data->context, &receiver_config, &data->receiver); if (res) { @@ -299,21 +305,7 @@ static int roc_source_setup(struct module_roc_source_data *data) return -EINVAL; } - switch (data->fec_code) { - case ROC_FEC_ENCODING_DEFAULT: - case ROC_FEC_ENCODING_RS8M: - audio_proto = ROC_PROTO_RTP_RS8M_SOURCE; - repair_proto = ROC_PROTO_RS8M_REPAIR; - break; - case ROC_FEC_ENCODING_LDPC_STAIRCASE: - audio_proto = ROC_PROTO_RTP_LDPC_SOURCE; - repair_proto = ROC_PROTO_LDPC_REPAIR; - break; - default: - audio_proto = ROC_PROTO_RTP; - repair_proto = 0; - break; - } + pw_roc_fec_encoding_to_proto(data->fec_code, &audio_proto, &repair_proto); res = pw_roc_create_endpoint(&data->local_source_addr, audio_proto, data->local_ip, data->local_source_port); if (res < 0) { @@ -341,6 +333,18 @@ static int roc_source_setup(struct module_roc_source_data *data) } } + res = pw_roc_create_endpoint(&data->local_control_addr, PW_ROC_DEFAULT_CONTROL_PROTO, data->local_ip, data->local_control_port); + if (res < 0) { + pw_log_error("failed to create control endpoint: %s", spa_strerror(res)); + return res; + } + + if (roc_receiver_bind(data->receiver, ROC_SLOT_DEFAULT, ROC_INTERFACE_AUDIO_CONTROL, + data->local_control_addr) != 0) { + pw_log_error("can't connect roc receiver to local control address"); + return -EINVAL; + } + data->playback = pw_stream_new(data->core, "roc-source playback", data->playback_props); data->playback_props = NULL; @@ -378,6 +382,7 @@ static const struct spa_dict_item module_roc_source_info[] = { "( local.ip=<local receiver ip> ) " "( local.source.port=<local receiver port for source packets> ) " "( local.repair.port=<local receiver port for repair packets> ) " + "( local.control.port=<local receiver port for control packets> ) " "( source.props= { key=value ... } ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -387,7 +392,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct module_roc_source_data *data; - struct pw_properties *props = NULL, *playback_props = NULL; + struct pw_properties *playback_props = NULL; const char *str; int res = 0; @@ -400,13 +405,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (args == NULL) args = ""; - props = pw_properties_new_string(args); + spa_autoptr(pw_properties) props = pw_properties_new_string(args); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto out; } - data->props = props; playback_props = pw_properties_new(NULL, NULL); if (playback_props == NULL) { @@ -421,7 +425,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if ((str = pw_properties_get(props, "source.name")) != NULL) { pw_properties_set(playback_props, PW_KEY_NODE_NAME, str); - pw_properties_set(props, "source.name", NULL); } if ((str = pw_properties_get(props, "source.props")) != NULL) @@ -436,34 +439,36 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(playback_props, PW_KEY_NODE_NETWORK) == NULL) pw_properties_set(playback_props, PW_KEY_NODE_NETWORK, "true"); - data->rate = pw_properties_get_uint32(playback_props, PW_KEY_AUDIO_RATE, data->rate); + data->rate = pw_properties_get_uint32(playback_props, PW_KEY_AUDIO_RATE, 0); if (data->rate == 0) data->rate = PW_ROC_DEFAULT_RATE; if ((str = pw_properties_get(props, "local.ip")) != NULL) { data->local_ip = strdup(str); - pw_properties_set(props, "local.ip", NULL); } else { data->local_ip = strdup(PW_ROC_DEFAULT_IP); } if ((str = pw_properties_get(props, "local.source.port")) != NULL) { data->local_source_port = pw_properties_parse_int(str); - pw_properties_set(props, "local.source.port", NULL); } else { data->local_source_port = PW_ROC_DEFAULT_SOURCE_PORT; } if ((str = pw_properties_get(props, "local.repair.port")) != NULL) { data->local_repair_port = pw_properties_parse_int(str); - pw_properties_set(props, "local.repair.port", NULL); } else { data->local_repair_port = PW_ROC_DEFAULT_REPAIR_PORT; } + if ((str = pw_properties_get(props, "local.control.port")) != NULL) { + data->local_control_port = pw_properties_parse_int(str); + } else { + data->local_control_port = PW_ROC_DEFAULT_CONTROL_PORT; + } + if ((str = pw_properties_get(props, "sess.latency.msec")) != NULL) { data->sess_latency_msec = pw_properties_parse_int(str); - pw_properties_set(props, "sess.latency.msec", NULL); } else { data->sess_latency_msec = PW_ROC_DEFAULT_SESS_LATENCY; } @@ -473,7 +478,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_log_warn("Invalid resampler profile %s, using default", str); data->resampler_profile = ROC_RESAMPLER_PROFILE_DEFAULT; } - pw_properties_set(props, "resampler.profile", NULL); } else { data->resampler_profile = ROC_RESAMPLER_PROFILE_DEFAULT; } @@ -482,7 +486,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_log_error("Invalid fec code %s, using default", str); data->fec_code = ROC_FEC_ENCODING_DEFAULT; } - pw_properties_set(props, "fec.code", NULL); } else { data->fec_code = ROC_FEC_ENCODING_DEFAULT; } diff --git a/src/modules/module-roc/common.h b/src/modules/module-roc/common.h index 248c66eb..2164a342 100644 --- a/src/modules/module-roc/common.h +++ b/src/modules/module-roc/common.h @@ -9,8 +9,10 @@ #define PW_ROC_DEFAULT_IP "0.0.0.0" #define PW_ROC_DEFAULT_SOURCE_PORT 10001 #define PW_ROC_DEFAULT_REPAIR_PORT 10002 +#define PW_ROC_DEFAULT_CONTROL_PORT 10003 #define PW_ROC_DEFAULT_SESS_LATENCY 200 #define PW_ROC_DEFAULT_RATE 44100 +#define PW_ROC_DEFAULT_CONTROL_PROTO ROC_PROTO_RTCP static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *str) { @@ -31,8 +33,6 @@ static inline int pw_roc_parse_resampler_profile(roc_resampler_profile *out, con { if (!str || !*str) *out = ROC_RESAMPLER_PROFILE_DEFAULT; - else if (spa_streq(str, "disable")) - *out = ROC_RESAMPLER_PROFILE_DISABLE; else if (spa_streq(str, "high")) *out = ROC_RESAMPLER_PROFILE_HIGH; else if (spa_streq(str, "medium")) @@ -68,4 +68,23 @@ out_error_free_ep: return -EINVAL; } +static inline void pw_roc_fec_encoding_to_proto(roc_fec_encoding fec_code, roc_protocol *audio, roc_protocol *repair) +{ + switch (fec_code) { + case ROC_FEC_ENCODING_DEFAULT: + case ROC_FEC_ENCODING_RS8M: + *audio = ROC_PROTO_RTP_RS8M_SOURCE; + *repair = ROC_PROTO_RS8M_REPAIR; + break; + case ROC_FEC_ENCODING_LDPC_STAIRCASE: + *audio = ROC_PROTO_RTP_LDPC_SOURCE; + *repair = ROC_PROTO_LDPC_REPAIR; + break; + default: + *audio = ROC_PROTO_RTP; + *repair = 0; + break; + } +} + #endif /* MODULE_ROC_COMMON_H */ diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 41ec9caa..65350588 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -57,7 +57,7 @@ #include <dbus/dbus.h> #endif -/** \page page_module_rt PipeWire Module: RT +/** \page page_module_rt RT * * The `rt` modules can give real-time priorities to processing threads. * @@ -69,6 +69,10 @@ * up and DBus is available, then this module will fall back to using the Portal * Realtime DBus API or RTKit. * + * ## Module Name + * + * `libpipewire-module-rt` + * * ## Module Options * * - `nice.level`: The nice value set for the application thread. It improves @@ -82,6 +86,8 @@ * - `rlimits.enabled`: enable the use of rtlimits, default true. * - `rtportal.enabled`: enable the use of realtime portal, default true * - `rtkit.enabled`: enable the use of rtkit, default true + * - `uclamp.min`: the minimum utilisation value the scheduler should consider + * - `uclamp.max`: the maximum utilisation value the scheduler should consider * The nice level is by default set to an invalid value so that clients don't * automatically have the nice level raised. @@ -101,6 +107,8 @@ * #rlimits.enabled = true * #rtportal.enabled = true * #rtkit.enabled = true + * #uclamp.min = 0 + * #uclamp.max = 1024 * } * flags = [ ifexists nofail ] * } @@ -131,13 +139,18 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_RT_TIME_SOFT -1 #define DEFAULT_RT_TIME_HARD -1 +#define DEFAULT_UCLAMP_MIN 0 +#define DEFAULT_UCLAMP_MAX 1024 + #define MODULE_USAGE "( nice.level=<priority: default "SPA_STRINGIFY(DEFAULT_NICE_LEVEL)"(don't change)> ) " \ "( rt.prio=<priority: default "SPA_STRINGIFY(DEFAULT_RT_PRIO)"> ) " \ "( rt.time.soft=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_SOFT)"> ) " \ "( rt.time.hard=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_HARD)"> ) " \ "( rlimits.enabled=<default true> ) " \ "( rtportal.enabled=<default true> ) " \ - "( rtkit.enabled=<default true> ) " + "( rtkit.enabled=<default true> ) " \ + "( uclamp.min=<default "SPA_STRINGIFY(DEFAULT_UCLAMP_MIN)"> ) " \ + "( uclamp.max=<default "SPA_STRINGIFY(DEFAULT_UCLAMP_MAX)"> )" static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, @@ -183,6 +196,9 @@ struct impl { rlim_t rt_time_soft; rlim_t rt_time_hard; + int uclamp_min; + int uclamp_max; + struct spa_hook module_listener; unsigned rlimits_enabled:1; @@ -214,6 +230,8 @@ struct impl { #define RLIMIT_RTTIME 15 #endif +static pthread_mutex_t rlimit_lock = PTHREAD_MUTEX_INITIALIZER; + static pid_t _gettid(void) { #if defined(HAVE_GETTID) @@ -537,12 +555,15 @@ static bool check_realtime_privileges(struct impl *impl) int err, old_policy, new_policy, min, max; struct sched_param old_sched_params; struct sched_param new_sched_params; + struct rlimit old_rlim; + struct rlimit no_rlim = { -1, -1 }; int try = 0; + bool ret = false; if (!impl->rlimits_enabled) - return false; + return ret; - while (try++ < 2) { + while (!ret && try++ < 2) { /* We could check `RLIMIT_RTPRIO`, but the BSDs generally don't have * that available, and there are also other ways to use realtime * scheduling without that rlimit being set such as `CAP_SYS_NICE` or @@ -550,11 +571,11 @@ static bool check_realtime_privileges(struct impl *impl) * just try if setting realtime scheduling works or not. */ if ((err = pthread_getschedparam(pthread_self(), &old_policy, &old_sched_params)) != 0) { pw_log_warn("Failed to check RLIMIT_RTPRIO: %s", strerror(err)); - return false; + break; } if ((err = get_rt_priority_range(&min, &max)) < 0) { pw_log_warn("Failed to get priority range: %s", strerror(err)); - return false; + break; } if (try == 2) { #ifdef RLIMIT_RTPRIO @@ -570,7 +591,7 @@ static bool check_realtime_privileges(struct impl *impl) } if (max < DEFAULT_RT_PRIO_MIN) { pw_log_info("Priority max (%d) must be at least %d", max, DEFAULT_RT_PRIO_MIN); - return false; + break; } /* If the current scheduling policy has `SCHED_RESET_ON_FORK` set, then @@ -584,14 +605,29 @@ static bool check_realtime_privileges(struct impl *impl) if ((old_policy & PW_SCHED_RESET_ON_FORK) != 0) new_policy |= PW_SCHED_RESET_ON_FORK; - if (pthread_setschedparam(pthread_self(), new_policy, &new_sched_params) == 0) { + /* Disable RLIMIT_RTTIME in a thread safe way and hope that the application + * doesn't also set RLIMIT_RTTIME while trying new_policy. */ + pthread_mutex_lock(&rlimit_lock); + if (getrlimit(RLIMIT_RTTIME, &old_rlim) < 0) + pw_log_info("getrlimit() failed: %m"); + if (setrlimit(RLIMIT_RTTIME, &no_rlim) < 0) + pw_log_info("setrlimit() failed: %m"); + if ((err = pthread_setschedparam(pthread_self(), new_policy, &new_sched_params)) == 0) { impl->rt_prio = new_sched_params.sched_priority; pthread_setschedparam(pthread_self(), old_policy, &old_sched_params); - return true; - } + ret = true; + } else + pw_log_info("failed to set realtime policy: %s", strerror(err)); + if (setrlimit(RLIMIT_RTTIME, &old_rlim) < 0) + pw_log_info("setrlimit() failed: %m"); + pthread_mutex_unlock(&rlimit_lock); } - pw_log_info("Can't set rt prio to %d: %m (try increasing rlimits)", (int)priority); - return false; + + if (ret) + pw_log_debug("can set rt prio to %d", (int)priority); + else + pw_log_info("can't set rt prio to %d (try increasing rlimits)", (int)priority); + return ret; } static int sched_set_nice(pid_t pid, int nice_level) @@ -636,18 +672,20 @@ static int set_nice(struct impl *impl, int nice_level, bool warn) return res; } -static int set_rlimit(struct impl *impl) +static int set_rlimit(struct rlimit *rlim) { int res = 0; - if (setrlimit(RLIMIT_RTTIME, &impl->rl) < 0) + pthread_mutex_lock(&rlimit_lock); + if (setrlimit(RLIMIT_RTTIME, rlim) < 0) res = -errno; + pthread_mutex_unlock(&rlimit_lock); if (res < 0) - pw_log_debug("setrlimit() failed: %s", spa_strerror(res)); + pw_log_info("setrlimit() failed: %s", spa_strerror(res)); else pw_log_debug("rt.time.soft:%"PRIi64" rt.time.hard:%"PRIi64, - (int64_t)impl->rl.rlim_cur, (int64_t)impl->rl.rlim_max); + (int64_t)rlim->rlim_cur, (int64_t)rlim->rlim_max); return res; } @@ -1011,12 +1049,56 @@ static int do_rtkit_setup(struct spa_loop *loop, bool async, uint32_t seq, impl->rl.rlim_cur = SPA_MIN(impl->rl.rlim_cur, impl->rttime_max); impl->rl.rlim_max = SPA_MIN(impl->rl.rlim_max, impl->rttime_max); - set_rlimit(impl); + set_rlimit(&impl->rl); return 0; } #endif /* HAVE_DBUS */ +int set_uclamp(int uclamp_min, int uclamp_max, pid_t pid) { +#ifdef __linux__ + int ret; + struct sched_attr { + uint32_t size; + uint32_t sched_policy; + uint64_t sched_flags; + int32_t sched_nice; + uint32_t sched_priority; + uint64_t sched_runtime; + uint64_t sched_deadline; + uint64_t sched_period; + uint32_t sched_util_min; + uint32_t sched_util_max; + } attr; + + ret = syscall(SYS_sched_getattr, pid, &attr, sizeof(struct sched_attr), 0); + if (ret) { + pw_log_warn("Could not retrieve scheduler attributes: %d", -errno); + return -errno; + } + + /* SCHED_FLAG_KEEP_POLICY | + * SCHED_FLAG_KEEP_PARAMS | + * SCHED_FLAG_UTIL_CLAMP_MIN | + * SCHED_FLAG_UTIL_CLAMP_MAX */ + attr.sched_flags = 0x8 | 0x10 | 0x20 | 0x40; + attr.sched_util_min = uclamp_min; + attr.sched_util_max = uclamp_max; + + ret = syscall(SYS_sched_setattr, pid, &attr, 0); + + if (ret) { + pw_log_warn("Could not set scheduler attributes: %d", -errno); + return -errno; + } + return 0; +#else + pw_log_warn("Setting UCLAMP values is only supported on Linux"); + return -EOPNOTSUPP; +#endif /* __linux__ */ +} + + SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { @@ -1047,6 +1129,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->rlimits_enabled = pw_properties_get_bool(props, "rlimits.enabled", true); impl->rtportal_enabled = pw_properties_get_bool(props, "rtportal.enabled", true); impl->rtkit_enabled = pw_properties_get_bool(props, "rtkit.enabled", true); + impl->uclamp_min = pw_properties_get_int32(props, "uclamp.min", DEFAULT_UCLAMP_MIN); + impl->uclamp_max = pw_properties_get_int32(props, "uclamp.max", DEFAULT_UCLAMP_MAX); impl->rl.rlim_cur = impl->rt_time_soft; impl->rl.rlim_max = impl->rt_time_hard; @@ -1086,7 +1170,15 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) use_rtkit = can_use_rtkit; } if (!use_rtkit) - set_rlimit(impl); + set_rlimit(&impl->rl); + + if (impl->uclamp_max > 1024) { + pw_log_warn("uclamp.max out of bounds. Got %d, clamping to 1024.", impl->uclamp_max); + impl->uclamp_max = 1024; + } + + if (impl->uclamp_min || impl->uclamp_max < 1024) + set_uclamp(impl->uclamp_min, impl->uclamp_max, impl->main_pid); #ifdef HAVE_DBUS impl->use_rtkit = use_rtkit; diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index d5b887e5..839c131a 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -28,7 +28,7 @@ #define ifr_ifindex ifr_index #endif -/** \page page_module_rtp_sap PipeWire Module: SAP Announce and create RTP streams +/** \page page_module_rtp_sap SAP Announce and create RTP streams * * The `rtp-sap` module announces RTP streams that match the rules with the * announce-stream action. @@ -40,6 +40,10 @@ * sess.sap.announce = true and it will create a receiver for all announced * streams. * + * ## Module Name + * + * `libpipewire-module-rtp-sap` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c index 9733f16f..fad1b21a 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -41,7 +41,7 @@ #define ifr_ifindex ifr_index #endif -/** \page page_module_rtp_session PipeWire Module: RTP session +/** \page page_module_rtp_session RTP session * * The `rtp-session` module creates a media session that is announced * with avahi/mDNS/Bonjour. @@ -52,6 +52,10 @@ * The session setup is based on apple-midi and is compatible with * apple-midi when the session is using midi. * + * ## Module Name + * + * `libpipewire-module-rtp-session` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c index 35595f99..0290e383 100644 --- a/src/modules/module-rtp-sink.c +++ b/src/modules/module-rtp-sink.c @@ -32,11 +32,15 @@ #define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK) #endif -/** \page page_module_rtp_sink PipeWire Module: RTP sink +/** \page page_module_rtp_sink RTP sink * * The `rtp-sink` module creates a PipeWire sink that sends audio * RTP packets. * + * ## Module Name + * + * `libpipewire-module-rtp-sink` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 3659ec9c..825d9477 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -35,7 +35,7 @@ #define ifr_ifindex ifr_index #endif -/** \page page_module_rtp_source PipeWire Module: RTP source +/** \page page_module_rtp_source RTP source * * The `rtp-source` module creates a PipeWire source that receives audio * and midi RTP packets. @@ -43,6 +43,10 @@ * This module is usually loaded from the \ref page_module_rtp_sap so that the * source.ip and source.port and format parameters matches that of the sender. * + * ## Module Name + * + * `libpipewire-module-rtp-source` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 217c9a87..1893f79b 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -420,7 +420,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, } else { impl->psamples = impl->mtu / impl->stride; impl->psamples = SPA_CLAMP(impl->psamples, min_samples, max_samples); - if (direction == PW_DIRECTION_OUTPUT) + if (direction == PW_DIRECTION_INPUT) pw_properties_setf(props, "rtp.ptime", "%f", impl->psamples * 1000.0 / impl->rate); } diff --git a/src/modules/module-session-manager.c b/src/modules/module-session-manager.c index 66f5b5ef..e0db896e 100644 --- a/src/modules/module-session-manager.c +++ b/src/modules/module-session-manager.c @@ -7,10 +7,14 @@ #include <pipewire/impl.h> -/** \page page_module_session_manager PipeWire Module: Session Manager +/** \page page_module_session_manager Session Manager * * This module implements some usefull objects for implementing a session * manager. It is not yet actively used. + * + * ## Module Name + * + * `libpipewire-module-session-manager` */ /* client-endpoint.c */ diff --git a/src/modules/module-vban-recv.c b/src/modules/module-vban-recv.c index 72be2e72..da8cfde9 100644 --- a/src/modules/module-vban-recv.c +++ b/src/modules/module-vban-recv.c @@ -35,11 +35,15 @@ #define ifr_ifindex ifr_index #endif -/** \page page_module_vban_recv PipeWire Module: VBAN receiver +/** \page page_module_vban_recv VBAN receiver * * The `vban-recv` module creates a PipeWire source that receives audio * and midi [VBAN](https://vb-audio.com) packets. * + * ## Module Name + * + * `libpipewire-module-vban-recv` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-vban-send.c b/src/modules/module-vban-send.c index 26961b81..896d82aa 100644 --- a/src/modules/module-vban-send.c +++ b/src/modules/module-vban-send.c @@ -32,11 +32,15 @@ #define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK) #endif -/** \page page_module_vban_send PipeWire Module: VBAN sender +/** \page page_module_vban_send VBAN sender * * The `vban-send` module creates a PipeWire sink that sends * audio and midi [VBAN](https://vb-audio.com) packets. * + * ## Module Name + * + * `libpipewire-module-vban-send` + * * ## Module Options * * Options specific to the behavior of this module diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index 3359ce84..0e3caae0 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -27,11 +27,15 @@ #include <pipewire/pipewire.h> #include <pipewire/impl.h> -/** \page page_module_x11_bell PipeWire Module: X11 Bell +/** \page page_module_x11_bell X11 Bell * * The `x11-bell` module intercept the X11 bell events and uses libcanberra to * play a sound. * + * ## Module Name + * + * `libpipewire-module-x11-bell` + * * ## Module Options * * - `sink.name = <str>`: node.name of the sink to connect to diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 4c4421b9..15c5e46b 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -27,13 +27,17 @@ #include "module-protocol-pulse/format.h" #include "module-zeroconf-discover/avahi-poll.h" -/** \page page_module_zeroconf_discover PipeWire Module: Zeroconf Discover +/** \page page_module_zeroconf_discover Zeroconf Discover * * Use zeroconf to detect and load module-pulse-tunnel with the right * parameters. This will automatically create sinks and sources to stream * audio to/from remote PulseAudio servers. It also works with * module-protocol-pulse. * + * ## Module Name + * + * `libpipewire-module-zeroconf-discover` + * * ## Module Options * * - `pulse.latency`: the latency to end-to-end latency in milliseconds to diff --git a/src/pipewire/context.c b/src/pipewire/context.c index b73dcc9c..1046fc9f 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -32,6 +32,8 @@ PW_LOG_TOPIC_EXTERN(log_context); #define PW_LOG_TOPIC_DEFAULT log_context +#define MAX_HOPS 64 + /** \cond */ struct impl { struct pw_context this; @@ -791,12 +793,17 @@ static int ensure_state(struct pw_impl_node *node, bool running) * and groups to active nodes and make them recursively runnable as well. */ static inline int run_nodes(struct pw_context *context, struct pw_impl_node *node, - struct spa_list *nodes, enum pw_direction direction) + struct spa_list *nodes, enum pw_direction direction, int hop) { struct pw_impl_node *t; struct pw_impl_port *p; struct pw_impl_link *l; + if (hop == MAX_HOPS) { + pw_log_warn("exceeded hops (%d)", hop); + return -EIO; + } + pw_log_debug("node %p: '%s' direction:%s", node, node->name, pw_direction_as_string(direction)); @@ -810,10 +817,12 @@ static inline int run_nodes(struct pw_context *context, struct pw_impl_node *nod if (!t->active || !l->prepared || (!t->driving && SPA_FLAG_IS_SET(t->checked, 1u<<direction))) continue; + if (t->driving && p->node == t) + continue; pw_log_debug(" peer %p: '%s'", t, t->name); t->runnable = true; - run_nodes(context, t, nodes, direction); + run_nodes(context, t, nodes, direction, hop + 1); } } } else { @@ -824,10 +833,12 @@ static inline int run_nodes(struct pw_context *context, struct pw_impl_node *nod if (!t->active || !l->prepared || (!t->driving && SPA_FLAG_IS_SET(t->checked, 1u<<direction))) continue; + if (t->driving && p->node == t) + continue; pw_log_debug(" peer %p: '%s'", t, t->name); t->runnable = true; - run_nodes(context, t, nodes, direction); + run_nodes(context, t, nodes, direction, hop + 1); } } } @@ -847,7 +858,7 @@ static inline int run_nodes(struct pw_context *context, struct pw_impl_node *nod pw_log_debug(" group %p: '%s'", t, t->name); t->runnable = true; if (!t->driving) - run_nodes(context, t, nodes, direction); + run_nodes(context, t, nodes, direction, hop + 1); } } return 0; @@ -950,8 +961,8 @@ static int collect_nodes(struct pw_context *context, struct pw_impl_node *node, } spa_list_for_each(n, collect, sort_link) if (!n->driving && n->runnable) { - run_nodes(context, n, collect, PW_DIRECTION_OUTPUT); - run_nodes(context, n, collect, PW_DIRECTION_INPUT); + run_nodes(context, n, collect, PW_DIRECTION_OUTPUT, 0); + run_nodes(context, n, collect, PW_DIRECTION_INPUT, 0); } return 0; diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 0561b9c8..6dbf050e 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1334,7 +1334,7 @@ pw_filter_new_simple(struct pw_loop *loop, if (props == NULL) return NULL; - context = pw_context_new(loop, NULL, 0); + context = pw_context_new(loop, pw_properties_copy(props), 0); if (context == NULL) { res = -errno; goto error_cleanup; diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c index ae5a1b1f..d5d865a6 100644 --- a/src/pipewire/impl-client.c +++ b/src/pipewire/impl-client.c @@ -165,7 +165,7 @@ static bool check_client_property_update(struct pw_impl_client *client, /* Refuse specific restricted keys */ if (has_key(ignored, key)) - goto deny; + goto ignore; /* Refuse all security keys */ if (spa_strstartswith(key, "pipewire.sec.")) @@ -184,6 +184,7 @@ deny: if (!spa_streq(old, new)) pw_log_warn("%p: refuse property update '%s' from '%s' to '%s'", client, key, old ? old : "<unset>", new ? new : "<unset>"); +ignore: return false; } diff --git a/src/pipewire/impl-client.h b/src/pipewire/impl-client.h index f9278f5f..de9325cb 100644 --- a/src/pipewire/impl-client.h +++ b/src/pipewire/impl-client.h @@ -12,6 +12,8 @@ extern "C" { #include <spa/utils/hook.h> /** \page page_client_impl Client Implementation + * + * \see \ref pw_impl_client * * \section sec_page_client_impl_overview Overview * @@ -51,7 +53,7 @@ extern "C" { * Each client has its own list of resources it is bound to along with * a mapping between the client types and server types. * - * See: \ref page_client_impl + * \see \ref page_client_impl */ /** diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index d1984ac1..b8224753 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -884,9 +884,11 @@ int pw_impl_link_deactivate(struct pw_impl_link *this) impl->activated = false; pw_log_info("(%s) deactivated", this->name); - link_update_state(this, this->destroyed ? - PW_LINK_STATE_INIT : PW_LINK_STATE_PAUSED, - 0, NULL); + + if (this->info.state < PW_LINK_STATE_PAUSED || this->destroyed) + link_update_state(this, PW_LINK_STATE_INIT, 0, NULL); + else + link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); return 0; } diff --git a/src/pipewire/impl-module.c b/src/pipewire/impl-module.c index 23df06cc..561f05c7 100644 --- a/src/pipewire/impl-module.c +++ b/src/pipewire/impl-module.c @@ -323,7 +323,7 @@ void pw_impl_module_destroy(struct pw_impl_module *module) pw_work_queue_cancel(pw_context_get_work_queue(module->context), module, SPA_ID_INVALID); - if (!pw_in_valgrind() && dlclose(impl->hnd) != 0) + if (pw_should_dlclose() && dlclose(impl->hnd) != 0) pw_log_warn("%p: dlclose failed: %s", module, dlerror()); free(impl); } diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c index ada0d13b..62e9ca57 100644 --- a/src/pipewire/pipewire.c +++ b/src/pipewire/pipewire.c @@ -149,7 +149,7 @@ unref_plugin(struct plugin *plugin) if (--plugin->ref == 0) { spa_list_remove(&plugin->link); pw_log_debug("unloaded plugin:'%s'", plugin->filename); - if (global_support.do_dlclose) + if (pw_should_dlclose()) dlclose(plugin->hnd); free(plugin->filename); free(plugin); @@ -785,10 +785,10 @@ const char *pw_get_host_name(void) return hname; } -SPA_EXPORT -bool pw_in_valgrind(void) +bool +pw_should_dlclose(void) { - return global_support.in_valgrind; + return global_support.do_dlclose; } SPA_EXPORT diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h index e5d078fd..0c495ed3 100644 --- a/src/pipewire/pipewire.h +++ b/src/pipewire/pipewire.h @@ -72,8 +72,6 @@ pw_get_host_name(void); const char * pw_get_client_name(void); -bool pw_in_valgrind(void); - bool pw_check_option(const char *option, const char *value); enum pw_direction diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 58b2b608..021a5d5f 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -1292,6 +1292,8 @@ void pw_settings_init(struct pw_context *context); int pw_settings_expose(struct pw_context *context); void pw_settings_clean(struct pw_context *context); +bool pw_should_dlclose(void); + /** \endcond */ #ifdef __cplusplus diff --git a/src/pipewire/proxy.h b/src/pipewire/proxy.h index e92a44e4..b46c4172 100644 --- a/src/pipewire/proxy.h +++ b/src/pipewire/proxy.h @@ -12,6 +12,8 @@ extern "C" { #include <spa/utils/hook.h> /** \page page_proxy Proxy + * + * \see \ref pw_proxy * * \section sec_page_proxy_overview Overview * @@ -76,7 +78,7 @@ extern "C" { * invoked by the client to PipeWire messages. Events will call the handlers * set in listener. * - * See \ref page_proxy + * \see \ref page_proxy */ /** diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index db2c3f7b..39e603b2 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -1595,7 +1595,7 @@ pw_stream_new_simple(struct pw_loop *loop, if (props == NULL) return NULL; - context = pw_context_new(loop, NULL, 0); + context = pw_context_new(loop, pw_properties_copy(props), 0); if (context == NULL) { res = -errno; goto error_cleanup; diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h index f394eaf0..bea90b45 100644 --- a/src/pipewire/stream.h +++ b/src/pipewire/stream.h @@ -10,6 +10,8 @@ extern "C" { #endif /** \page page_streams Streams + * + * \see \ref pw_stream * * \section sec_overview Overview * @@ -162,7 +164,7 @@ extern "C" { * The stream object provides a convenient way to send and * receive data streams from/to PipeWire. * - * See also \ref page_streams and \ref api_pw_core + * \see \ref page_streams, \ref api_pw_core */ /** diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h index 1d5725f0..f1eb1910 100644 --- a/src/pipewire/thread-loop.h +++ b/src/pipewire/thread-loop.h @@ -12,6 +12,8 @@ extern "C" { #include <pipewire/loop.h> /** \page page_thread_loop Thread Loop + * + * \see \ref pw_thread_loop * * \section sec_thread_loop_overview Overview * @@ -68,7 +70,7 @@ extern "C" { * All of the loop callbacks will be executed with the loop * lock held. * - * See also \ref page_thread_loop + * \see \ref page_thread_loop */ /** diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c index 3bbbb170..aeb0f780 100644 --- a/src/pipewire/utils.c +++ b/src/pipewire/utils.c @@ -162,7 +162,7 @@ char **pw_strv_parse(const char *val, size_t len, int max_tokens, int *n_tokens) * \since 0.3.84 */ SPA_EXPORT -int pw_strv_find(char **a, char *b) +int pw_strv_find(char **a, const char *b) { int i; if (a == NULL || b == NULL) diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h index 92f8d004..9889fd7f 100644 --- a/src/pipewire/utils.h +++ b/src/pipewire/utils.h @@ -48,7 +48,7 @@ pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]); char **pw_strv_parse(const char *val, size_t len, int max_tokens, int *n_tokens); -int pw_strv_find(char **a, char *b); +int pw_strv_find(char **a, const char *b); int pw_strv_find_common(char **a, char **b); diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index 50a0a5a9..aafa0d5e 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -282,7 +282,7 @@ done: } static const struct pw_node_events node_events = { - PW_VERSION_NODE, + PW_VERSION_NODE_EVENTS, .info = node_info, .param = node_param, }; -- GitLab