feat: media bitrate control, HDR (#346)

Fixes: #335 

Still a work-in-progress.

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Wanjohi <elviswanjohi47@gmail.com>
This commit is contained in:
Kristian Ollikainen
2026-09-25 12:13:34 +03:00
committed by GitHub
co-authored by DatCaptainHorse Claude Opus 5 Wanjohi
parent 1c721962f4
commit 0811f57f1a
64 changed files with 15151 additions and 2702 deletions
@@ -0,0 +1,66 @@
# pipewire-pulse, the PulseAudio protocol on top of PipeWire, for clients that
# speak nothing else. Wine's audio driver is one of them.
#
# The socket moves out of the service user's runtime directory. That directory
# is 0700 and the workload runs as a different user, so a client there finds
# nothing and plays silently, which is the failure PipeWire's own socket was
# moved for. The workload is pointed here with PULSE_SERVER, and nesinit starts
# this under a umask that lets a user who does not own the socket open it. This
# path is written in nesinit too, and a test there compares the two.
#
# A drop-in replaces a key of pulse.properties rather than appending to it, so
# the default "unix:native" socket is not created as well.
pulse.properties = {
server.address = [ "unix:/run/pipewire/pulse-native" ]
# module-always-sink creates a "Dummy Output" sink whenever no other sink
# exists, and while neswire's sink is not up yet that is always. A second
# sink is a second default-node candidate, and a game that picks it plays
# into nothing. neswire has to be the only sink; see the WirePlumber
# drop-in.
pulse.cmd.always-sink = false
# The root is read-only and every box starts from the same image, so there
# is nothing to restore and nowhere to save, which is the reasoning behind
# turning off WirePlumber's state hooks. Left on, these are one more place
# that could pin a default.
pulse.cmd.device-manager = false
pulse.cmd.device-restore = false
pulse.cmd.stream-restore = false
# Wine sizes its whole audio path from this number. At startup its driver
# opens a probe stream asking for a 1-frame request size, reads back what
# the server grants, which is this floor, and makes the device period ten
# times that. Streams then queue three periods. The default floor of 256
# frames therefore becomes a 53 ms period and about 160 ms of audio in
# flight, which is desktop playback and not a game being streamed.
#
# 48 frames (1 ms) makes that period 10 ms, the default period Windows
# itself uses, so a game gets the buffering it was written for. A stream
# then asks for 480-frame requests, which is what is granted, so the floor
# affects the probe and nothing else Wine does.
pulse.min.req = 48/48000
# What a client gets when it asks for no particular buffering. The shipped
# default is two seconds, which is only harmless where nobody is listening
# live. 10 ms requests and 40 ms of target buffer are enough for a client
# that does not say.
pulse.default.req = 480/48000
pulse.default.tlength = 1920/48000
}
# pipewire-pulse.conf raises the minimum node latency to 1024 frames (21.3 ms)
# inside a virtual machine, which a box is. That puts every Pulse client, and
# therefore Wine, at least 21 ms behind, whatever it asked for. Floor it where
# the graph itself is floored instead; see the pipewire.conf drop-in for why
# 256 frames. A drop-in appends to this array, and every matching rule applies
# in order, so this runs after the shipped rule and wins.
pulse.properties.rules = [
{ matches = [ { cpu.vm.name = !null } ]
actions = {
update-props = {
pulse.min.quantum = 256/48000
}
}
}
]
@@ -0,0 +1,24 @@
# The graph's clock, floored where the audio sender needs it.
#
# Inside a virtual machine, pipewire.conf raises the graph's minimum quantum to
# 1024 frames (21.3 ms), because a VM's timers jitter and small quanta then
# underrun on real audio hardware. A box is a VM, so that rule applies, and it
# silently overrides neswire's request for its 5 ms packet size. Every sample
# then waits for a 21 ms cycle before it can be encoded.
#
# The floor comes down to 256 frames (5.3 ms), the smallest power of two that
# holds one of neswire's packets. It is not lower than that on purpose: nothing
# here needs less, and the reason for the rule, a VM's timer driving the graph,
# still holds, because the Dummy-Driver is exactly that.
#
# A drop-in appends to a rules array, and every matching rule applies in order,
# so this one runs after the shipped one and wins.
context.properties.rules = [
{ matches = [ { cpu.vm.name = !null } ]
actions = {
update-props = {
default.clock.min-quantum = 256
}
}
}
]
@@ -3,14 +3,13 @@
# The goal is that `neswire` is the *only* Audio/Sink in the graph, so
# default-node selection has exactly one candidate and cannot pick wrong.
#
# The "Dummy Output" (`auto_null`) sink is not disabled here, and no longer
# needs to be: it came from pipewire-pulse's `module-always-sink`, and
# pipewire-pulse is no longer installed. WirePlumber ships
# scripts/fallback-sink.lua, which creates a node by the same name, but no
# component in wireplumber.conf references it, so it never loads. If auto_null
# ever comes back, it came back with pipewire-pulse -- the switch is
# `pulse.cmd.always-sink = false` in a pipewire-pulse.conf.d drop-in, not
# anything on this side.
# The "Dummy Output" (`auto_null`) sink is not disabled here, because it does
# not come from this side. pipewire-pulse creates it with `module-always-sink`,
# and the pipewire-pulse drop-in turns that off with
# `pulse.cmd.always-sink = false`. WirePlumber ships scripts/fallback-sink.lua,
# which creates a node by the same name, but no component in wireplumber.conf
# references it, so it never loads. If auto_null appears, look at the
# pipewire-pulse drop-in first.
#
# pipewire.conf's Dummy-Driver / Freewheel-Driver are a third thing again:
# support.node.driver objects with no ports -- drivers, not sinks, so nothing