Search code examples
pythonpipewire

PipeWire volume-change with pw-cli doesn't notify other programs


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 ],

Solution

  • 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}}"