I am trying to change PipeWire audio volumes via my Python script. Since there isn't, at the time of writing this, a simple Python library for PipeWire, I am doing that by calling the pw-cli utility via Python
The problem is that while it works in changing the volume, other programs that monitor volume changes don't reflect that (wpctl's status output, my tray volume indicator or volume monitor program like Pavucontrol). When I inspect the PipeWire properties of my sink via pw-dump or pw-cli, it shows the changed volume.
When I use WirePlumber's wpctl to change the volume, it works as expected. The change gets picked up by my tray volume indicator, Pavucontrol or wpctl's status report itself.
What does wpctl do differently than pw-cli? What's the proper way to change the volume with pw-cli? I could call wpctl via Python, but that would create another dependency (WirePlumber) which I thought I could avoid.
Here are the exact lines I'm using:
# changes volume, but not picked up by other programs
pw-cli s 54 Props "{channelVolumes: ["0.2", "0.2"]}"
Here's the relevant pw-dump and pw-cli output showing the change:
$ pw-cli e 54 Props
Object: size 1136, type Spa:Pod:Object:Param:Props (262146), id Spa:Enum:ParamId:Props (2)
Prop: key Spa:Pod:Object:Param:Props:volume (65539), flags 00000000
Float 1,000000
Prop: key Spa:Pod:Object:Param:Props:mute (65540), flags 00000000
Bool false
Prop: key Spa:Pod:Object:Param:Props:channelVolumes (65544), flags 00000000
Array: child.size 4, child.type Spa:Float
Float 0,200000
Float 0,200000
$ pw-dump 54
...
"Props": [
{
"volume": 1.000000,
"mute": false,
"channelVolumes": [ 0.200000, 0.200000 ],
Does ID 54 correspond to a node? If so, with pw-cli s 54 Props "{channelVolumes: ["0.2", "0.2"]}"
you are setting the volume relative to the device volume
To change the volume you need to change the Route parameters using the route index and route device.
# pw-cli e <device-id> Route
...
Prop: key Spa:Pod:Object:Param:Route:index (1), flags 00000000
Int <route-index>
...
Prop: key Spa:Pod:Object:Param:Route:device (3), flags 00000000
Int <route-device>
...
the volume goes between 0 and 1.0 and you have to apply the cubic root. for example to set the volume to 50% = 0.5^3 = 0.125
pw-cli s <device-id> Route '{index: <route-index>, device: <route-device>, props: {channelVolumes: [0.125, 0.125], mute: false}}'
# wpctl get-volume <node-id>
Volume: 0.50
Script to set volume (jq utility required)
#!/bin/bash
# Extract the partial name of the default device
device_partial_name=$(pw-metadata | grep "key:'default.audio.source'" | awk -F"'" '{print $4}' | sed 's/\\//g' | jq .name | awk -F"." '{print $2}')
# Retrieve the device ID using pw-dump and jq
device_id=$(pw-dump Device | jq '.[].info.props|select(."device.name" | contains('\"$device_partial_name\"'))' | jq '."object.id"')
# Dump route information and extract index and device for the specified device ID
route_values=($(pw-dump $device_id | jq '.[0].info.params.Route[0]| (.index, .device)'))
# Extract route index and profile
route_index=${route_values[0]}
route_device=${route_values[1]}
# Set the desired volume level
VOLUME=0.125 # 0.5^3 = 50%
# Use pw-cli to set the route properties including channel volumes and mute status
pw-cli s $device_id Route "{index: $route_device, device: $route_device, props: {channelVolumes: [$VOLUME, $VOLUME], mute: false}}"