From ca58c68ef988e57f5bec232fe6c9ab86d5f0ea19 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Wed, 13 Sep 2023 16:02:55 +0300 Subject: [PATCH 01/14] config: alsa: increase VM period-size to 1024 Fixes: #507 --- src/config/main.lua.d/50-alsa-config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/main.lua.d/50-alsa-config.lua b/src/config/main.lua.d/50-alsa-config.lua index 61a0aefa..80fe684a 100644 --- a/src/config/main.lua.d/50-alsa-config.lua +++ b/src/config/main.lua.d/50-alsa-config.lua @@ -31,7 +31,7 @@ alsa_monitor.properties = { -- These properties override node defaults when running in a virtual machine. -- The rules below still override those. ["vm.node.defaults"] = { - ["api.alsa.period-size"] = 256, + ["api.alsa.period-size"] = 1024, ["api.alsa.headroom"] = 8192, }, } From 92e53bb7ba7e55125846e53adac0a81584ae3d82 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Fri, 15 Sep 2023 17:52:12 +0200 Subject: [PATCH 02/14] policy-device-profile: Use device.profile if set Don't set best profile if device.profile property has been set with alsa_monitor.rules. --- src/scripts/policy-device-profile.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/scripts/policy-device-profile.lua b/src/scripts/policy-device-profile.lua index b591cb7d..49ab4be0 100644 --- a/src/scripts/policy-device-profile.lua +++ b/src/scripts/policy-device-profile.lua @@ -152,6 +152,11 @@ function handleProfiles (device, new_device) Log.info ("Default profile not found for " .. dev_name) end + -- Do not set best profile if device.profile has been set + if device.properties["device.profile"] ~= nil then + return + end + local best_profile = findBestProfile (device) if best_profile ~= nil then Log.info ("Found best profile " .. best_profile.name .. " for " .. dev_name) From 2ae1b3cbd9438c408d5d8f7e1d7a43b532308a41 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sun, 16 Jul 2023 16:26:06 +0300 Subject: [PATCH 03/14] api: module: support loading arguments from file --- docs/rst/lua_api/lua_local_module_api.rst | 3 +- lib/wp/module.c | 64 +++++++++++++++++++++++ lib/wp/module.h | 3 ++ modules/module-lua-scripting/api/api.c | 17 +++++- 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/docs/rst/lua_api/lua_local_module_api.rst b/docs/rst/lua_api/lua_local_module_api.rst index 8390bb1a..c7f122f5 100644 --- a/docs/rst/lua_api/lua_local_module_api.rst +++ b/docs/rst/lua_api/lua_local_module_api.rst @@ -11,7 +11,7 @@ dropped, the module is unloaded. Constructors ~~~~~~~~~~~~ -.. function:: LocalModule(name, arguments, properties) +.. function:: LocalModule(name, arguments, properties, [load_args_from_file]) Loads the named module with the provided arguments and properties (either of which can be ``nil``). @@ -21,6 +21,7 @@ Constructors module arguments :param table properties: can be ``nil`` or a table that can be :ref:`converted ` to :c:struct:`WpProperties` + :param load_args_from_file: (since 0.4.15) optional. if true, arguments param is treated as a file path to load args from :returns: a new LocalModule :rtype: LocalModule (:c:struct:`WpImplModule`) :since: 0.4.2 diff --git a/lib/wp/module.c b/lib/wp/module.c index b51f56d7..c4a9cbad 100644 --- a/lib/wp/module.c +++ b/lib/wp/module.c @@ -9,6 +9,10 @@ #define G_LOG_DOMAIN "wp-module" #include +#include +#include +#include +#include #include "module.h" @@ -260,3 +264,63 @@ wp_impl_module_load (WpCore * core, const gchar * name, return module; } + +/*! + * \brief Loads a PipeWire module with arguments from file into the WirePlumber process + * + * \ingroup wpimplmodule + * \since 0.4.15 + * \param core (transfer none): The WirePlumber core + * \param name (transfer none): the name of the module to load + * \param file (transfer none): filename to be used as arguments + * \param properties (nullable) (transfer none): additional properties to be + * provided to the module + * \returns (nullable) (transfer full): the WpImplModule for the module that + * was loaded on success, %NULL on failure. + */ +WpImplModule * +wp_impl_module_load_file (WpCore * core, const gchar * name, + const gchar * filename, WpProperties * properties) +{ + char *config = ""; + int fd = open(filename, O_RDONLY); + if (fd < 0) { + g_warning("Failed to open config file %s: %m", filename); + return NULL; + } + + struct stat stats; + int err = fstat(fd, &stats); + if (err < 0) { + g_warning("Failed to stat config file %s: %m", filename); + close(fd); + return NULL; + } + + config = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (config == MAP_FAILED){ + g_warning("Failed to mmap config file %s: %m", filename); + close(fd); + return NULL; + } + close(fd); + + WpImplModule *module = WP_IMPL_MODULE ( + g_object_new (WP_TYPE_IMPL_MODULE, + "core", core, + "name", name, + "arguments", config, + "properties", properties, + NULL) + ); + + munmap(config, stats.st_size); + + if (!module->pw_impl_module) { + /* Module loading failed, free and return */ + g_object_unref (module); + return NULL; + } + + return module; +} diff --git a/lib/wp/module.h b/lib/wp/module.h index 6abf1a4e..88ca1265 100644 --- a/lib/wp/module.h +++ b/lib/wp/module.h @@ -29,6 +29,9 @@ G_DECLARE_FINAL_TYPE (WpImplModule, wp_impl_module, WP, IMPL_MODULE, GObject); WP_API WpImplModule * wp_impl_module_load (WpCore * core, const gchar * name, const gchar * arguments, WpProperties * properties); +WP_API +WpImplModule * wp_impl_module_load_file (WpCore * core, const gchar * name, + const gchar * filename, WpProperties * properties); G_END_DECLS diff --git a/modules/module-lua-scripting/api/api.c b/modules/module-lua-scripting/api/api.c index 79129a53..a3ab36ef 100644 --- a/modules/module-lua-scripting/api/api.c +++ b/modules/module-lua-scripting/api/api.c @@ -6,6 +6,7 @@ * SPDX-License-Identifier: MIT */ +#include "lua.h" #include #include #include @@ -1461,8 +1462,20 @@ impl_module_new (lua_State *L) properties = wplua_table_to_properties (L, 3); } - WpImplModule *m = wp_impl_module_load (get_wp_export_core (L), - name, args, properties); + bool load_file = false; // Load args as file path + if (lua_type (L, 4) != LUA_TNONE && lua_type (L, 4) != LUA_TNIL) { + luaL_checktype (L, 4, LUA_TBOOLEAN); + load_file = lua_toboolean(L, 4); + } + + WpImplModule *m = NULL; + if (load_file) { + m = wp_impl_module_load_file (get_wp_export_core (L), + name, args, properties); + } else { + m = wp_impl_module_load (get_wp_export_core (L), + name, args, properties); + } if (m) { wplua_pushobject (L, m); From 11b3803edc9377145e616e99bf6eb8ad03450037 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Tue, 16 Aug 2022 18:02:55 +0300 Subject: [PATCH 04/14] policy-dsp: add a policy for loading filter chains Signed-off-by: Dmitry Sharshakov --- docs/rst/configuration/dsp.rst | 6 + docs/rst/configuration/meson.build | 1 + src/config/policy.lua.d/10-default-policy.lua | 17 +++ src/scripts/policy-dsp.lua | 119 ++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 docs/rst/configuration/dsp.rst create mode 100644 src/scripts/policy-dsp.lua diff --git a/docs/rst/configuration/dsp.rst b/docs/rst/configuration/dsp.rst new file mode 100644 index 00000000..69a93809 --- /dev/null +++ b/docs/rst/configuration/dsp.rst @@ -0,0 +1,6 @@ +.. _config_dsp: + +DSP configuration +================== + +This is an empty page diff --git a/docs/rst/configuration/meson.build b/docs/rst/configuration/meson.build index a4c73e09..e123033d 100644 --- a/docs/rst/configuration/meson.build +++ b/docs/rst/configuration/meson.build @@ -5,6 +5,7 @@ sphinx_files += files( 'config_lua.rst', 'multi_instance.rst', 'alsa.rst', + 'dsp.rst', 'bluetooth.rst', 'policy.rst', 'access.rst', diff --git a/src/config/policy.lua.d/10-default-policy.lua b/src/config/policy.lua.d/10-default-policy.lua index 1b11e08b..83d0a3b2 100644 --- a/src/config/policy.lua.d/10-default-policy.lua +++ b/src/config/policy.lua.d/10-default-policy.lua @@ -42,6 +42,20 @@ bluetooth_policy.policy = { }, } +dsp_policy = {} + +dsp_policy.policy = {} + +dsp_policy.policy.properties = {} + +-- An array of matches/filters to apply. +-- `matches` are rules for matching a sink node. It is an array of +-- properties that all need to match the regexp. If any of the +-- matches in an array work, the filters are executed for the sink. +-- `filter_chain` is a JSON string of parameters to filter-chain module +-- `properties` table only has `pro_audio` boolean, which enables Pro Audio mode on the sink when applying DSP +dsp_policy.policy.rules = {} + function default_policy.enable() if default_policy.enabled == false then return @@ -78,4 +92,7 @@ function default_policy.enable() -- Switch bluetooth profile based on media.role load_script("policy-bluetooth.lua", bluetooth_policy.policy) + + -- Load filter chains for hardware requiring DSP + load_script("policy-dsp.lua", dsp_policy.policy) end diff --git a/src/scripts/policy-dsp.lua b/src/scripts/policy-dsp.lua new file mode 100644 index 00000000..d575ea95 --- /dev/null +++ b/src/scripts/policy-dsp.lua @@ -0,0 +1,119 @@ +-- WirePlumber +-- +-- Copyright © 2022-2023 The WirePlumber project contributors +-- @author Dmitry Sharshakov +-- +-- SPDX-License-Identifier: MIT + +local config = ... or {} +config.properties = config.properties or {} +config.rules = config.rules or {} + +for _, r in ipairs(config.rules) do + r.interests = {} + for _, i in ipairs(r.matches) do + local interest_desc = { type = "properties" } + + for _, c in ipairs(i) do + c.type = "pw" + table.insert(interest_desc, Constraint(c)) + end + + local interest = Interest(interest_desc) + table.insert(r.interests, interest) + end + + if r.device_matches then + r.device_interests = {} + for _, i in ipairs(r.device_matches) do + local interest_desc = { type = "properties" } + + for _, c in ipairs(i) do + c.type = "pw" + table.insert(interest_desc, Constraint(c)) + end + + local interest = Interest(interest_desc) + table.insert(r.device_interests, interest) + end + end +end + +-- Look up device which owns the sink (for profile switching) +devices_om = ObjectManager { + Interest { type = "device" } +} + +-- TODO: only check for hotplug of devices with known DSP rules +nodes_om = ObjectManager { + Interest { type = "node" }, +} + +filter_chains = {} + +-- Check if the device matches any of the interests +function checkDevice (device, device_interests) + for _, interest in ipairs(device_interests) do + if interest:matches(device["global-properties"]) then + return true + end + end + return false +end + +nodes_om:connect("object-added", function (om, node) + for _, r in ipairs(config.rules or {}) do + for _, interest in ipairs(r.interests) do + if interest:matches(node["global-properties"]) then + local id = node["global-properties"]["object.id"] + + local device = devices_om:lookup(Interest { + type = "device", + Constraint { "object.id", "=", node["global-properties"]["device.id"] } + }) + + if r.device_interests and not checkDevice(device, r.device_interests) then + -- This node belongs to another device rather than the specified one + return + end + + if r.properties and r.properties.profile then + local index = nil + for profile in device:iterate_params("EnumProfile") do + local p = profile:parse() + if p.properties.name == r.properties.profile then + local pod = Pod.Object { + "Spa:Pod:Object:Param:Profile", "Profile", + index = p.properties.index + } + device:set_param("Profile", pod) + + break + end + end + end + + if r.filter_chain then + if filter_chains[id] then + Log.warning("Sink " .. id .. " has been plugged now, but has a filter chain loaded. Skipping") + else + filter_chains[id] = LocalModule("libpipewire-module-filter-chain", r.filter_chain, {}) + end + end + end + end + end +end) + +nodes_om:connect("object-removed", function (om, node) + local id = node["global-properties"]["object.id"] + if filter_chains[id] then + Log.debug("Unloading filter chain associated with sink " .. id) + filter_chains[id] = nil + else + Log.debug("Disconnected sink " .. id .. " does not have any filters to be removed") + end +end) + +devices_om:activate() +nodes_om:activate() From 2dd28c001556e259d1d7cf6144d5bf430b07db99 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Fri, 22 Sep 2023 19:27:13 +0300 Subject: [PATCH 05/14] policy-dsp: load filters from file --- src/scripts/policy-dsp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/policy-dsp.lua b/src/scripts/policy-dsp.lua index d575ea95..ac6fe8fe 100644 --- a/src/scripts/policy-dsp.lua +++ b/src/scripts/policy-dsp.lua @@ -97,7 +97,7 @@ nodes_om:connect("object-added", function (om, node) if filter_chains[id] then Log.warning("Sink " .. id .. " has been plugged now, but has a filter chain loaded. Skipping") else - filter_chains[id] = LocalModule("libpipewire-module-filter-chain", r.filter_chain, {}) + filter_chains[id] = LocalModule("libpipewire-module-filter-chain", r.filter_chain, {}, true) end end end From 2970ee26340416b56b8fb8f6c28c516a5f2eb70d Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Fri, 22 Sep 2023 19:08:38 +0300 Subject: [PATCH 06/14] policy-dsp: expect policy-device-profile to manage profiles --- src/scripts/policy-dsp.lua | 60 +------------------------------------- 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/src/scripts/policy-dsp.lua b/src/scripts/policy-dsp.lua index ac6fe8fe..55f86c68 100644 --- a/src/scripts/policy-dsp.lua +++ b/src/scripts/policy-dsp.lua @@ -6,7 +6,6 @@ -- SPDX-License-Identifier: MIT local config = ... or {} -config.properties = config.properties or {} config.rules = config.rules or {} for _, r in ipairs(config.rules) do @@ -22,77 +21,21 @@ for _, r in ipairs(config.rules) do local interest = Interest(interest_desc) table.insert(r.interests, interest) end - - if r.device_matches then - r.device_interests = {} - for _, i in ipairs(r.device_matches) do - local interest_desc = { type = "properties" } - - for _, c in ipairs(i) do - c.type = "pw" - table.insert(interest_desc, Constraint(c)) - end - - local interest = Interest(interest_desc) - table.insert(r.device_interests, interest) - end - end end --- Look up device which owns the sink (for profile switching) -devices_om = ObjectManager { - Interest { type = "device" } -} - --- TODO: only check for hotplug of devices with known DSP rules +-- TODO: only check for hotplug of nodes with known DSP rules nodes_om = ObjectManager { Interest { type = "node" }, } filter_chains = {} --- Check if the device matches any of the interests -function checkDevice (device, device_interests) - for _, interest in ipairs(device_interests) do - if interest:matches(device["global-properties"]) then - return true - end - end - return false -end - nodes_om:connect("object-added", function (om, node) for _, r in ipairs(config.rules or {}) do for _, interest in ipairs(r.interests) do if interest:matches(node["global-properties"]) then local id = node["global-properties"]["object.id"] - local device = devices_om:lookup(Interest { - type = "device", - Constraint { "object.id", "=", node["global-properties"]["device.id"] } - }) - - if r.device_interests and not checkDevice(device, r.device_interests) then - -- This node belongs to another device rather than the specified one - return - end - - if r.properties and r.properties.profile then - local index = nil - for profile in device:iterate_params("EnumProfile") do - local p = profile:parse() - if p.properties.name == r.properties.profile then - local pod = Pod.Object { - "Spa:Pod:Object:Param:Profile", "Profile", - index = p.properties.index - } - device:set_param("Profile", pod) - - break - end - end - end - if r.filter_chain then if filter_chains[id] then Log.warning("Sink " .. id .. " has been plugged now, but has a filter chain loaded. Skipping") @@ -115,5 +58,4 @@ nodes_om:connect("object-removed", function (om, node) end end) -devices_om:activate() nodes_om:activate() From 5faab4e8c2d059f2e579b5d8e1f33f92eca2935e Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Fri, 22 Sep 2023 19:07:56 +0300 Subject: [PATCH 07/14] policy-device-profile: set default and best by device.profile --- src/scripts/policy-device-profile.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/scripts/policy-device-profile.lua b/src/scripts/policy-device-profile.lua index 49ab4be0..7d8637e2 100644 --- a/src/scripts/policy-device-profile.lua +++ b/src/scripts/policy-device-profile.lua @@ -90,13 +90,17 @@ function findDefaultProfile (device) end function findBestProfile (device) + -- Takes absolute priority if available or unknown + local profile_prop = device.properties["device.profile"] local off_profile = nil local best_profile = nil local unk_profile = nil for p in device:iterate_params("EnumProfile") do profile = parseParam(p, "EnumProfile") - if profile and profile.name ~= "pro-audio" then + if profile and profile.name == profile_prop and profile.available ~= "no" then + return profile + elseif profile and profile.name ~= "pro-audio" then if profile.name == "off" then off_profile = profile elseif profile.available == "yes" then @@ -152,11 +156,6 @@ function handleProfiles (device, new_device) Log.info ("Default profile not found for " .. dev_name) end - -- Do not set best profile if device.profile has been set - if device.properties["device.profile"] ~= nil then - return - end - local best_profile = findBestProfile (device) if best_profile ~= nil then Log.info ("Found best profile " .. best_profile.name .. " for " .. dev_name) From b2bfb1b91754c5728654a318a9b17cae8aad37cb Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Sun, 17 Sep 2023 13:55:25 -0700 Subject: [PATCH 08/14] wpctl: add zsh completions --- src/tools/meson.build | 5 +++ src/tools/shell-completion/wpctl.zsh | 49 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/tools/shell-completion/wpctl.zsh diff --git a/src/tools/meson.build b/src/tools/meson.build index e4d2e730..d77f3208 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -9,6 +9,11 @@ executable('wpctl', dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep], ) +install_data('shell-completion/wpctl.zsh', + install_dir: get_option('datadir') / 'zsh/site-functions', + rename: '_wpctl' +) + executable('wpexec', 'wpexec.c', c_args : [ diff --git a/src/tools/shell-completion/wpctl.zsh b/src/tools/shell-completion/wpctl.zsh new file mode 100644 index 00000000..3f9e53c8 --- /dev/null +++ b/src/tools/shell-completion/wpctl.zsh @@ -0,0 +1,49 @@ +#compdef wpctl + +(( $+functions[_wpctl_pw_nodes] )) || +_wpctl_pw_nodes() { + local -a pw_objects + if (( $+commands[pw-dump] )) && (( $+commands[jq] )); then + local -a pw_objects=(${(@f)"$(2>/dev/null { + command pw-dump | + command jq -r '.[] | select( + .type == "PipeWire:Interface:Node" + ) | + {id, type, name: ( + .info.name // + (.info.props | ( + ."application.name" // + ."node.name") + ) // + .type) + } | + "\(.id):\(.name | gsub(":"; "\\:"))"' + })"}) + fi + _wpctl_describe_nodes() {_describe "node id" pw_objects "$@"} + _alternative \ + 'pw-defaults:defaults:(@DEFAULT_SINK@ @DEFAULT_SOURCE@)' \ + 'pw-node-id:node id:_wpctl_describe_nodes' +} + +local -a node_id=(/$'[^\0]#\0'/ ':pw-node-id:node id:_wpctl_pw_nodes') +local -a volume=(/$'[0-9]##(%|)([+-]|)\0'/ ':volume:volume:( )') +local -a toggle=(/$'[^\0]#\0'/ ':(0 1 toggle)') +local -a set_volume=( "$node_id[@]" "$volume[@]" ) +local -a set_mute=( "$node_id[@]" "$toggle[@]" ) + +_regex_words options 'wpctl options' \ + {-h,--help}':show help message and exit' +local -a options=( "$reply[@]" ) + +_regex_words wpctl-commands 'wpctl commands' \ + 'status:show wireplumber status' \ + 'get-volume:get object volume:$node_id' \ + 'set-default:set a default sink:$node_id' \ + 'set-volume:set object volume:$set_volume' \ + 'set-mute:set object mute:$set_mute' \ + 'set-profile:set object profile:$node_id' \ + 'clear-default:unset default sink:$node_id' +local -a wpctlcmd=( /$'[^\0]#\0'/ "$options[@]" "#" "$reply[@]") +_regex_arguments _wpctl "$wpctlcmd[@]" +_wpctl "$@" From 69ed77042f9ab464aa7484a09dbf31e9fdc5a28e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Oct 2023 12:01:05 +0200 Subject: [PATCH 09/14] config: set priority. keys on midi-bridge So that it can be used as a fallback driver. See pipewire#3562 --- src/config/main.lua.d/50-alsa-config.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config/main.lua.d/50-alsa-config.lua b/src/config/main.lua.d/50-alsa-config.lua index 80fe684a..e47ca36f 100644 --- a/src/config/main.lua.d/50-alsa-config.lua +++ b/src/config/main.lua.d/50-alsa-config.lua @@ -26,6 +26,8 @@ alsa_monitor.properties = { ["node.name"] = "Midi-Bridge", -- Removes longname/number from MIDI port names --["api.alsa.disable-longname"] = true, + ["priority.session"] = 100, + ["priority.driver"] = 1, }, -- These properties override node defaults when running in a virtual machine. From ffd6c0dfb96377325a2721731e45a59d55646ae8 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Tue, 10 Oct 2023 13:42:05 +0300 Subject: [PATCH 10/14] docs: fix warnings related to recent policy-dsp changes - remove dsp.rst, since it's empty and not in the toctree - fix g-i function param annotation --- docs/rst/configuration/dsp.rst | 6 ------ docs/rst/configuration/meson.build | 1 - lib/wp/module.c | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 docs/rst/configuration/dsp.rst diff --git a/docs/rst/configuration/dsp.rst b/docs/rst/configuration/dsp.rst deleted file mode 100644 index 69a93809..00000000 --- a/docs/rst/configuration/dsp.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _config_dsp: - -DSP configuration -================== - -This is an empty page diff --git a/docs/rst/configuration/meson.build b/docs/rst/configuration/meson.build index e123033d..a4c73e09 100644 --- a/docs/rst/configuration/meson.build +++ b/docs/rst/configuration/meson.build @@ -5,7 +5,6 @@ sphinx_files += files( 'config_lua.rst', 'multi_instance.rst', 'alsa.rst', - 'dsp.rst', 'bluetooth.rst', 'policy.rst', 'access.rst', diff --git a/lib/wp/module.c b/lib/wp/module.c index c4a9cbad..6984662b 100644 --- a/lib/wp/module.c +++ b/lib/wp/module.c @@ -272,7 +272,7 @@ wp_impl_module_load (WpCore * core, const gchar * name, * \since 0.4.15 * \param core (transfer none): The WirePlumber core * \param name (transfer none): the name of the module to load - * \param file (transfer none): filename to be used as arguments + * \param filename (transfer none): filename to be used as arguments * \param properties (nullable) (transfer none): additional properties to be * provided to the module * \returns (nullable) (transfer full): the WpImplModule for the module that From d67b48e595cb4612fd7fd47f97df6b8883ef7f60 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Thu, 12 Oct 2023 19:24:07 +0300 Subject: [PATCH 11/14] 0.4.15 --- NEWS.rst | 64 +++++++++++++++++++++++++++++++++++++++++++++++------ meson.build | 2 +- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 494a7bc0..409156f6 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,6 +1,62 @@ -WirePlumber 0.4.14 +WirePlumber 0.4.15 ~~~~~~~~~~~~~~~~~~ +Additions: + + - A new "DSP policy" module has been added; its purpose is to automatically + load a filter-chain when a certain hardware device is present, so that + audio always goes through this software DSP before reaching the device. + This is mainly to support Apple M1/M2 devices, which require a software + DSP to be always present + + - WpImplModule now supports loading module arguments directly from a SPA-JSON + config file; this is mainly to support DSP configuration for Apple M1/M2 + and will likely be reworked for 0.5 + + - Added support for automatically combining Bluetooth LE Audio device sets + (e.g. pairs of earbuds) (!500) + + - Added command line options in ``wpctl`` to display device/node names and + nicknames instead of descriptions + + - Added zsh completions file for ``wpctl`` + + - The device profile selection policy now respects the ``device.profile`` + property if it is set on the device; this is useful to hand-pick a profile + based on static configuration rules (alsa_monitor.rules) + +Changes/Fixes: + + - Linking policy now sends an error to the client before destroying the node, + if it determines that the node cannot be linked to any target; this fixes + error reporting on the client side + + - Fixed a crash in suspend-node that could happen when destroying virtual + sinks that were loaded from another process such as pw-loopback (#467) + + - Virtual machine default period size has been bumped to 1024 (#507) + + - Updated bluez5 default configuration, using ``bluez5.roles`` instead of + ``bluez5.headset-roles`` now (!498) + + - Disabled Bluetooth autoconnect by default (!514) + + - Removed ``RestrictNamespaces`` option from the systemd services in order to + allow libcamera to load sandboxed IPA modules (#466) + + - Fixed a JSON encoding bug with empty strings (#471) + + - Lua code can now parse strings without quotes from SPA-JSON + + - Added some missing `\since` annotations and made them show up in the + generated gobject-introspection file, to help bindings generators + +Past releases +~~~~~~~~~~~~~ + +WirePlumber 0.4.14 +.................. + Additions: - Added support for managing Bluetooth-MIDI, complimenting the parts that @@ -24,9 +80,6 @@ Additions: - Added support for disabling libcamera nodes & devices with ``node.disabled`` and ``device.disabled``, like it works for ALSA and V4L2 (#418) -Past releases -~~~~~~~~~~~~~ - WirePlumber 0.4.13 .................. @@ -76,9 +129,6 @@ Packaging: - Added pkg-config and header information in the gir file -Past releases -~~~~~~~~~~~~~ - WirePlumber 0.4.12 .................. diff --git a/meson.build b/meson.build index 927a1ad5..8a4d6564 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wireplumber', ['c'], - version : '0.4.14', + version : '0.4.15', license : 'MIT', meson_version : '>= 0.59.0', default_options : [ From 686048d6fa91419b77d0ff46bc3289c93046820b Mon Sep 17 00:00:00 2001 From: Matt Horan Date: Thu, 19 Oct 2023 11:20:22 -0400 Subject: [PATCH 12/14] docs: Provide example for iec958.codecs config --- docs/rst/configuration/alsa.rst | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/rst/configuration/alsa.rst b/docs/rst/configuration/alsa.rst index a89e0133..8340795b 100644 --- a/docs/rst/configuration/alsa.rst +++ b/docs/rst/configuration/alsa.rst @@ -469,10 +469,24 @@ on the ALSA device. This can be done in 3 different ways: - 1. Use pavucontrol and toggle the codecs in the output advanced section + 1. Use pavucontrol and toggle the codecs in the output advanced section. - 2. Modify the ``["iec958.codecs"] = "[ PCM DTS AC3 MPEG MPEG2-AAC EAC3 TrueHD DTS-HD ]"`` - node property to something. + 2. Modify the ``["iec958.codecs"]`` node property to contain suported codecs. + + Example ``~/.config/wireplumber/main.lua.d/51-alsa-spdif.lua``: + + .. code-block:: lua + + table.insert (alsa_monitor.rules, { + matches = { + { + { "node.name", "matches", "alsa_output.*" }, + }, + }, + apply_properties = { + ["iec958.codecs"] = "[ PCM DTS AC3 EAC3 TrueHD DTS-HD ]", + } + }) 3. Use ``pw-cli s Props '{ iec958Codecs : [ PCM ] }'`` to modify the codecs at runtime. From 5fc7e68d109b646c550e3fdeddebadc5047137a2 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Mon, 23 Oct 2023 23:04:02 +0300 Subject: [PATCH 13/14] object-manager: reduce the amount of globals that initially match the interest With the previous check, any global matching either the type or the global properties of the interest would be considered for inclusion in the object manager and would be prepared only to fail the same check later. The correct way to check is (variable & (X|Y) == (X|Y)), which is what SPA_FLAG_IS_SET() expands to. Fixes #517 --- lib/wp/object-manager.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wp/object-manager.c b/lib/wp/object-manager.c index 6f58653b..dea7857e 100644 --- a/lib/wp/object-manager.c +++ b/lib/wp/object-manager.c @@ -638,8 +638,8 @@ wp_object_manager_is_interested_in_global (WpObjectManager * self, /* and consider the manager interested if the type and the globals match... if pw_properties / g_properties fail, that's ok because they are not known yet (the proxy is likely NULL and properties not yet retrieved) */ - if (match & (WP_INTEREST_MATCH_GTYPE | - WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES)) { + if (SPA_FLAG_IS_SET (match, (WP_INTEREST_MATCH_GTYPE | + WP_INTEREST_MATCH_PW_GLOBAL_PROPERTIES))) { gpointer ft = g_hash_table_lookup (self->features, GSIZE_TO_POINTER (global->type)); *wanted_features = (WpObjectFeatures) GPOINTER_TO_UINT (ft); From 23ba01970f2520fb30fddc4ee911d77cdda9ac22 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Mon, 23 Oct 2023 23:08:45 +0300 Subject: [PATCH 14/14] object-manager: use an idle callback to expose tmp globals instead of pw_core_sync A core sync is not really necessary here because whatever objects the remote pipewire daemon has to announce have already been sent to us on a message and this message is already being processed at this point. This means, we are not going to be returning to the main loop until all the new objects have been announced and therefore placed into the tmp globals array. So, we can also use an idle callback and achieve the same effect of slightly delaying until all new globals have been announced. With an idle callback, we can be more agile and add those new objects immediately after the message has been processed instead of waiting for a pw_core_sync() reply, which will come in the next message. This fixes an odd failure of the si-standard-link test after applying the fix for #517, which was caused by the fact that the test was previously relying on a delay caused by some unrelated globals being prepared in the object manager that tries to verify the graph state. After those globals were removed from the internal preparation queue, the test would fail to detect the link objects because they were stuck in the tmp_globals array for too long. --- lib/wp/object-manager.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/wp/object-manager.c b/lib/wp/object-manager.c index dea7857e..816db6dc 100644 --- a/lib/wp/object-manager.c +++ b/lib/wp/object-manager.c @@ -1047,18 +1047,15 @@ wp_registry_detach (WpRegistry *self) } } -static void -expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self) +static gboolean +expose_tmp_globals (WpCore *core) { - g_autoptr (GError) error = NULL; + WpRegistry *self = wp_core_get_registry (core); g_autoptr (GPtrArray) tmp_globals = NULL; - if (!wp_core_sync_finish (core, res, &error)) - wp_warning_object (core, "core sync error: %s", error->message); - /* in case the registry was cleared in the meantime... */ if (G_UNLIKELY (!self->tmp_globals)) - return; + return G_SOURCE_REMOVE; /* steal the tmp_globals list and replace it with an empty one */ tmp_globals = self->tmp_globals; @@ -1082,8 +1079,8 @@ expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self) wp_global_rm_flag (old_g, WP_GLOBAL_FLAG_OWNED_BY_PROXY); } - g_return_if_fail (self->globals->len <= g->id || - g_ptr_array_index (self->globals, g->id) == NULL); + g_return_val_if_fail (self->globals->len <= g->id || + g_ptr_array_index (self->globals, g->id) == NULL, G_SOURCE_REMOVE); /* set the registry, so that wp_global_rm_flag() can work full-scale */ g->registry = self; @@ -1109,6 +1106,8 @@ expose_tmp_globals (WpCore *core, GAsyncResult *res, WpRegistry *self) } wp_object_manager_maybe_objects_changed (om); } + + return G_SOURCE_REMOVE; } /* @@ -1159,7 +1158,8 @@ wp_registry_prepare_new_global (WpRegistry * self, guint32 id, /* schedule exposing when adding the first global */ if (self->tmp_globals->len == 1) { - wp_core_sync (core, NULL, (GAsyncReadyCallback) expose_tmp_globals, self); + wp_core_idle_add_closure (core, NULL, + g_cclosure_new_object (G_CALLBACK (expose_tmp_globals), G_OBJECT (core))); } } else { /* store the most permissive permissions */