diff --git a/content/concepts/audio-management.md b/content/concepts/audio-management.md index 7ba9c3e6845b61b3590457ba755685e24cc116a8..c1091d8cb3483fd834d352b0a3f8d5f4a51e16ce 100644 --- a/content/concepts/audio-management.md +++ b/content/concepts/audio-management.md @@ -454,8 +454,9 @@ the user will not hear. An internal priority module can be written. This module would associate a priority to all differents streams' metadata. It is loaded statically from the -config file. See [Routing data structure example] for an example of data -structure. +config file. See +[Routing data structure example]( {{< ref "#routing-data-structure-example" >}} ) +for an example of data structure. #### Hybrid routing module maps stream metadata to external audio router calls @@ -580,6 +581,120 @@ Since the CE domain do not know what is happening in the automotive domain. | traffic_info | INFO1 | alert_sink | mix | | gps | INFO2 | main_sink | mix | +### WirePlumber policy samples + +All the policies in WirePlumber are completely scriptable and written in Lua. +The Lua API Documentation can be found +[here](https://pipewire.pages.freedesktop.org/wireplumber/lua_api.html). + +The default roles, priorities and related actions are defined in +`/etc/wireconfig/policy.lua.d/50-endpoints-config.lua` and +can be re-written to support the standalone setup defined in +[Routing data structure example]( {{< ref "#routing-data-structure-example" >}} ): +``` +default_policy.policy.roles = { + -- main sink + ["Multimedia"] = { ["priority"] = 0, ["action.default"] = "cork", ["alias"] = { "Movie", "Music", "Game" }, }, + ["GPS"] = { ["priority"] = 5, ["action.default"] = "duck", }, + ["Phone"] = { ["priority"] = 7, ["action.default"] = "cork", ["alias"] = { "CustomRingtone" }, }, + + -- alert sink + ["New_email"] = { ["priority"] = 1, ["action.default"] = "mix", }, + ["Traffic_info"] = { ["priority"] = 6, ["action.default"] = "mix", }, + ["Ringtone"] = { ["priority"] = 7, ["action.default"] = "mix", }, +} + +default_policy.endpoints = { + ["endpoint.multimedia"] = { ["media.class"] = "Audio/Sink", ["role"] = "Multimedia", }, + ["endpoint.gps"] = { ["media.class"] = "Audio/Sink", ["role"] = "GPS", }, + ["endpoint.phone"] = { ["media.class"] = "Audio/Sink", ["role"] = "Phone", }, + ["endpoint.ringtone"] = { ["media.class"] = "Audio/Sink", ["role"] = "Ringtone", }, + ["endpoint.new_email"] = { ["media.class"] = "Audio/Sink", ["role"] = "New_email", }, + ["endpoint.traffic_info"] = { ["media.class"] = "Audio/Sink", ["role"] = "Traffic_info", }, +} +``` +And, for example, a policy to automatically switch Bluetooth from A2DP to +HSP/HFP profile when a specific application starts, e.g. Zoom, could be defined +like: +``` +#!/usr/bin/wpexec +-- +-- WirePlumber +-- +-- Copyright © 2021 Collabora Ltd. +-- @author George Kiagiadakis <george.kiagiadakis@collabora.com> +-- +-- SPDX-License-Identifier: MIT +-- +-- This is an example of a standalone policy making script. It can be executed +-- either on top of another instance of wireplumber or pipewire-media-session, +-- as a standalone executable, or it can be placed in WirePlumber's scripts +-- directory and loaded together with other scripts. +-- +-- The script basically watches for a client application called +-- "ZOOM VoiceEngine", and when it appears (i.e. Zoom starts), it switches +-- the profile of all connected bluetooth devices to the "headset-head-unit" +-- (a.k.a HSP Headset Audio) profile. When Zoom exits, it switches again the +-- profile of all bluetooth devices to A2DP Sink. +-- +-- The script can be customized further to look for other clients and/or +-- change the profile of a specific device, by customizing the constraints. +----------------------------------------------------------------------------- + +devices_om = ObjectManager { + Interest { type = "device", + Constraint { "device.api", "=", "bluez5" }, + } +} + +clients_om = ObjectManager { + Interest { type = "client", + Constraint { "application.name", "=", "ZOOM VoiceEngine" }, + } +} + +function set_profile(profile_name) + for device in devices_om:iterate() do + local index = nil + local desc = nil + + for profile in device:iterate_params("EnumProfile") do + local p = profile:parse() + if p.properties.name == profile_name then + index = p.properties.index + desc = p.properties.description + break + end + end + + if index then + local pod = Pod.Object { + "Spa:Pod:Object:Param:Profile", "Profile", + index = index + } + + print("Setting profile of '" + .. device.properties["device.description"] + .. "' to: " .. desc) + device:set_params("Profile", pod) + end + end +end + +clients_om:connect("object-added", function (om, client) + print("Client '" .. client.properties["application.name"] .. "' connected") + set_profile("headset-head-unit") +end) + +clients_om:connect("object-removed", function (om, client) + print("Client '" .. client.properties["application.name"] .. "' disconnected") + set_profile("a2dp-sink") +end) + +devices_om:activate() +clients_om:activate() +``` + ### Testability The key point to keep in mind for testing is that several applications can