1

I'm trying to create a script to monitor a pipewire audio source, and provide outputs relating to the "percentage" of the volume. Since I'm using pipewire and wireplumber, I decided to use pw-mon, provided by pipewire for monitoring, and wpctl, provided by wireplumber to accomplish this.

pw-mon | while read; do
    # wpctl returns output as Volume: 0.00 [MUTED] where 0.00 is the volume, and [MUTED] is irrelevant for our case.
    # I process via cut to get the second field.
    awk '{print $1 * 100}' <<< $(wpctl get-volume @DEFAULT_AUDIO_SINK@ | cut -d" " -f2)
done

The main issue with this is that pw-mon returns many stdout responses about their device state, and as a side-effect, awk has to process tens of times because while read gets a lot of lines from pw-mon stdout. I want to prevent this and only print out once, so essentially squash all the stdout from pw-mon, have the while run only once because of that, and then in-turn process my awk only once as well. Problem is I'm not sure how to accomplish this.

I tried to use tr -d "\r\n" from https://stackoverflow.com/a/68091645/9091276, but while read no longer runs (I made sure of this by doing while read l; do echo $l; done). I thought to add a sleep in the while loop, but that doesn't make sense because it'll just delay the reading of the pw-mon stdout lines rather than hold back on reading all of them.

Any thoughts or ideas of what I can do? For clarity, here's some output from pw-mon so you can see what happens per event callback from it: https://hastebin.com/share/agajayosoq.rust (its not rust idk why it says that).

EDIT: Let me clarify what I'm trying to do

pw-mon fires some audio device data into the stdout every time it detects a change to the audio device (volume up/down/mute). Otherwise, pw-mon does NOT output any data. The problem is every time a change occurs, pw-mon dumps a ton of lines of data to stdout, which means, with my current script, itll fire the while read multiple times. That is not what I want, I want to only run the "awk" portion once every time a change occurs. My idea was to squash all my stdout into one line, so while read would only fire once, but doing pw-mon | tr -d "\r\n" | while read did not work, for some reason the while read would not fire.

EDIT 2: sample output of pw-mon:

    type: PipeWire:Interface:Core
    cookie: 2956247259
    user-name: "frontear"
    host-name: "frontear-net"
    version: "0.3.81"
    name: "pipewire-0"
*   properties:
*       config.name = "pipewire.conf"
*       link.max-buffers = "16"
*       core.daemon = "true"
*       core.name = "pipewire-0"
*       module.jackdbus-detect = "true"
*       module.x11.bell = "true"
*       module.access = "true"
*       cpu.max-align = "64"
*       default.clock.rate = "48000"
*       default.clock.quantum = "1024"
*       default.clock.min-quantum = "32"
*       default.clock.max-quantum = "2048"
*       default.clock.quantum-limit = "8192"
*       default.video.width = "640"
*       default.video.height = "480"
*       default.video.rate.num = "25"
*       default.video.rate.denom = "1"
*       log.level = "2"
*       clock.power-of-two-quantum = "true"
*       mem.warn-mlock = "false"
*       mem.allow-mlock = "true"
*       settings.check-quantum = "false"
*       settings.check-rate = "false"
*       object.id = "0"
*       object.serial = "0"
added:
    id: 0
    permissions: r-xm-
    type: PipeWire:Interface:Core (version 4)
    properties:
        object.serial = "0"
        core.name = "pipewire-0"
added:
    ... (omitted for brevity)

*     id:15 (Spa:Enum:ParamId:Latency)
          Object: size 176, type Spa:Pod:Object:Param:Latency (262155), id Spa:Enum:ParamId:Latency (15)
            Prop: key Spa:Pod:Object:Param:Latency:direction (1), flags 00000000
              Id 0        (Spa:Enum:Direction:Input)
            Prop: key Spa:Pod:Object:Param:Latency:minQuantum (2), flags 00000000
              Float 0.000000
            Prop: key Spa:Pod:Object:Param:Latency:maxQuantum (3), flags 00000000
              Float 0.000000
            Prop: key Spa:Pod:Object:Param:Latency:minRate (4), flags 00000000
              Int 0
            Prop: key Spa:Pod:Object:Param:Latency:maxRate (5), flags 00000000
              Int 0
            Prop: key Spa:Pod:Object:Param:Latency:minNs (6), flags 00000000
              Long 0
            Prop: key Spa:Pod:Object:Param:Latency:maxNs (7), flags 00000000
              Long 0
    properties:
        port.id = "0"
        port.direction = "out"
        object.path = "xdg-desktop-portal-hyprland:capture_0"
        port.name = "capture_1"
        port.alias = "xdg-desktop-portal-hyprland:capture_1"
        node.id = "74"
        object.id = "75"
        object.serial = "377"
12
  • I don't really understand your problem. Better if you can include some dummy data like pw-mon would generate. Given this is audio data, it seems there would be a time component, but that is not clear. So you want squash 24.secs/mins/hrs(+?) into one final output? I would go for a sqwashed output that also included the time range, and possibly the number of samples squashed. Also, try just tr -d "\r". Good luck! Commented Oct 13, 2023 at 23:06
  • I think you may be misunderstanding my post, let me add something to clarify. Commented Oct 13, 2023 at 23:11
  • @shellter wpctl is not input for while read, its input for awk, because I take the decimal string and process it into a percentage. Commented Oct 13, 2023 at 23:16
  • while read is expecting a \n line ending to trigger the read. I read your description the first time, and to MHO, your Edit doesn't add much to that description. "Avoid verbal descriptions"! show us your input data (just enough for one "iteration") and the required output from that same data. Commented Oct 13, 2023 at 23:42
  • I attached some data that pw-mon gives in a hastebin link above. This entire blurb of data contains information for when the monitoring app first runs + waits for ONE event Commented Oct 14, 2023 at 0:05

1 Answer 1

1

Had some ideas from the questions, such as finding data specific to both initial call and each callbacks, and I found it. Since pipewire is going to print the device id for my device that changes, I can simply match that line.

I first used wpctl inspect @DEFAULT_AUDIO_SINK@, then piped it through awk via awk -F"\"" "/device.id/{print \$2}" to find the device.id output and pull the number. Then, using this, I run pw-mon -oa to remove way extra output (just learned about this command), leaving device.id as one of the few props it prints out since that's the device that changes. Finally, I pipe it via awk once again to find the line with my id, reducing while invocations.

The full code looks like this:

DEVICE_ID=$(wpctl inspect @DEFAULT_AUDIO_SINK@ | awk -F"\"" "/device.id/{print \$2}")
pw-mon -oa | awk "/id: $DEVICE_ID/{print; fflush()}" | while read; do
    wpctl get-volume @DEFAULT_AUDIO_SINK@ | awk "{print \$2 * 100}"
done
Sign up to request clarification or add additional context in comments.

1 Comment

I was following this question on my "other" computer and then couldn't find it on my "main" computer. Doah! ... Seems you have solved your problem. Good show! ( I do like jhnc's solution too)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.