diff options
Diffstat (limited to 'src/daemon')
32 files changed, 2430 insertions, 0 deletions
diff --git a/src/daemon/client-rt.conf.in b/src/daemon/client-rt.conf.in new file mode 100644 index 0000000..b73a904 --- /dev/null +++ b/src/daemon/client-rt.conf.in @@ -0,0 +1,114 @@ +# Real-time Client config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/client-rt.conf.d/ for system-wide changes or in +# ~/.config/pipewire/client-rt.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + log.level = 0 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + # Uses realtime scheduling to boost the audio thread priorities + { name = libpipewire-module-rt + args = { + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Allows creating devices that run in the context of the + # client. Is used by the session manager. + { name = libpipewire-module-client-device } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + { name = libpipewire-module-metadata } + + # Provides factories to make session manager objects. + { name = libpipewire-module-session-manager } +] + +filter.properties = { + #node.latency = 1024/48000 +} + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.upmix-method = psd # none, simple + #channelmix.lfe-cutoff = 150 + #channelmix.fc-cutoff = 12000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.0 + #channelmix.hilbert-taps = 0 + #dither.noise = 0 +} + +alsa.properties = { + #alsa.format = 0 + #alsa.rate = 0 + #alsa.channels = 0 + #alsa.period-bytes = 0 + #alsa.buffer-bytes = 0 + #alsa.volume-method = cubic # linear, cubic +} + +# client specific properties +alsa.rules = [ + { matches = [ { application.process.binary = "resolve" } ] + actions = { + update-props = { + alsa.buffer-bytes = 131072 + } + } + } +] diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in new file mode 100644 index 0000000..b465eb6 --- /dev/null +++ b/src/daemon/client.conf.in @@ -0,0 +1,85 @@ +# Client config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/client.conf.d/ for system-wide changes or in +# ~/.config/pipewire/client.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + log.level = 0 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Allows creating devices that run in the context of the + # client. Is used by the session manager. + { name = libpipewire-module-client-device } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + { name = libpipewire-module-metadata } + + # Provides factories to make session manager objects. + { name = libpipewire-module-session-manager } +] + +filter.properties = { + #node.latency = 1024/48000 +} + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.upmix-method = psd # none, simple + #channelmix.lfe-cutoff = 150 + #channelmix.fc-cutoff = 12000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.0 + #channelmix.hilbert-taps = 0 + #dither.noise = 0 +} diff --git a/src/daemon/filter-chain.conf.in b/src/daemon/filter-chain.conf.in new file mode 100644 index 0000000..28d2a5a --- /dev/null +++ b/src/daemon/filter-chain.conf.in @@ -0,0 +1,62 @@ +# Filter-chain config file for PipeWire version @VERSION@ # +# +# This is a base config file for running filters. +# +# Place filter fragments in @PIPEWIRE_CONFIG_DIR@/filter-chain.conf.d/ +# for system-wide changes or in ~/.config/pipewire/filter-chain.conf.d/ +# for local changes. +# +# Run the filters with pipewire -c filter-chain.conf +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + log.level = 0 +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + # Uses realtime scheduling to boost the audio thread priorities + { name = libpipewire-module-rt + args = { + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } +] diff --git a/src/daemon/filter-chain/demonic.conf b/src/daemon/filter-chain/demonic.conf new file mode 100644 index 0000000..df52dba --- /dev/null +++ b/src/daemon/filter-chain/demonic.conf @@ -0,0 +1,63 @@ +# filter-chain example config file for PipeWire version @VERSION@ # +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + #audio.format = F32 + #audio.rate = 48000 + audio.channels = 2 + audio.position = [ FL FR ] + node.description = "Demonic example" + media.name = "Demonic example" + filter.graph = { + nodes = [ + { + name = rev + type = ladspa + plugin = revdelay_1605 + label = revdelay + control = { + "Delay Time (s)" = 2.0 + } + } + { + name = pitch + type = ladspa + plugin = am_pitchshift_1433 + label = amPitchshift + control = { + "Pitch shift" = 0.6 + } + } + { + name = rev2 + type = ladspa + plugin = g2reverb + label = G2reverb + control = { + "Reverb tail" = 0.5 + "Damping" = 0.9 + } + } + ] + links = [ + { output = "rev:Output" input = "pitch:Input" } + { output = "pitch:Output" input = "rev2:In L" } + ] + inputs = [ "rev:Input" ] + outputs = [ "rev2:Out L" ] + } + capture.props = { + node.name = "effect_input.filter-chain-demonic" + #media.class = Audio/Sink + } + playback.props = { + node.name = "effect_output.filter-chain-demonic" + #media.class = Audio/Source + } + } + } +] diff --git a/src/daemon/filter-chain/meson.build b/src/daemon/filter-chain/meson.build new file mode 100644 index 0000000..91d4458 --- /dev/null +++ b/src/daemon/filter-chain/meson.build @@ -0,0 +1,19 @@ +conf_files = [ + [ 'demonic.conf', 'demonic.conf' ], + [ 'source-duplicate-FL.conf', 'source-duplicate-FL.conf' ], + [ 'sink-mix-FL-FR.conf', 'sink-mix-FL-FR.conf' ], + [ 'sink-make-LFE.conf', 'sink-make-LFE.conf' ], + [ 'sink-virtual-surround-5.1-kemar.conf', 'sink-virtual-surround-5.1-kemar.conf' ], + [ 'sink-virtual-surround-7.1-hesuvi.conf', 'sink-virtual-surround-7.1-hesuvi.conf' ], + [ 'sink-dolby-surround.conf', 'sink-dolby-surround.conf' ], + [ 'sink-eq6.conf', 'sink-eq6.conf' ], + [ 'sink-matrix-spatialiser.conf', 'sink-matrix-spatialiser.conf' ], + [ 'source-rnnoise.conf', 'source-rnnoise.conf' ], +] + +foreach c : conf_files + configure_file(input : c.get(0), + output : c.get(1), + configuration : conf_config, + install_dir : pipewire_confdatadir / 'filter-chain') +endforeach diff --git a/src/daemon/filter-chain/sink-dolby-surround.conf b/src/daemon/filter-chain/sink-dolby-surround.conf new file mode 100644 index 0000000..a53009f --- /dev/null +++ b/src/daemon/filter-chain/sink-dolby-surround.conf @@ -0,0 +1,46 @@ +# Dolby Surround encoder sink +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Dolby Surround Sink" + media.name = "Dolby Surround Sink" + filter.graph = { + nodes = [ + { + type = builtin + name = mixer + label = mixer + control = { "Gain 1" = 0.5 "Gain 2" = 0.5 } + } + { + type = ladspa + name = enc + plugin = surround_encoder_1401 + label = surroundEncoder + } + ] + links = [ + { output = "mixer:Out" input = "enc:S" } + ] + inputs = [ "enc:L" "enc:R" "enc:C" null "mixer:In 1" "mixer:In 2" ] + outputs = [ "enc:Lt" "enc:Rt" ] + } + capture.props = { + node.name = "effect_input.dolby_surround" + media.class = Audio/Sink + audio.channels = 6 + audio.position = [ FL FR FC LFE SL SR ] + } + playback.props = { + node.name = "effect_output.dolby_surround" + node.passive = true + audio.channels = 2 + audio.position = [ FL FR ] + } + } + } +] diff --git a/src/daemon/filter-chain/sink-eq6.conf b/src/daemon/filter-chain/sink-eq6.conf new file mode 100644 index 0000000..4cdf21b --- /dev/null +++ b/src/daemon/filter-chain/sink-eq6.conf @@ -0,0 +1,70 @@ +# 6 band sink equalizer +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Equalizer Sink" + media.name = "Equalizer Sink" + filter.graph = { + nodes = [ + { + type = builtin + name = eq_band_1 + label = bq_lowshelf + control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_2 + label = bq_peaking + control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_3 + label = bq_peaking + control = { "Freq" = 500.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_4 + label = bq_peaking + control = { "Freq" = 2000.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_5 + label = bq_peaking + control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 } + } + { + type = builtin + name = eq_band_6 + label = bq_highshelf + control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 } + } + ] + links = [ + { output = "eq_band_1:Out" input = "eq_band_2:In" } + { output = "eq_band_2:Out" input = "eq_band_3:In" } + { output = "eq_band_3:Out" input = "eq_band_4:In" } + { output = "eq_band_4:Out" input = "eq_band_5:In" } + { output = "eq_band_5:Out" input = "eq_band_6:In" } + ] + } + audio.channels = 2 + audio.position = [ FL FR ] + capture.props = { + node.name = "effect_input.eq6" + media.class = Audio/Sink + } + playback.props = { + node.name = "effect_output.eq6" + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/sink-make-LFE.conf b/src/daemon/filter-chain/sink-make-LFE.conf new file mode 100644 index 0000000..4ab770e --- /dev/null +++ b/src/daemon/filter-chain/sink-make-LFE.conf @@ -0,0 +1,56 @@ +# An example filter chain that makes a stereo sink that mixes +# the FL and FR channels to FL, FR, LFE +# +# Copy this file into a conf.d/ directory +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "LFE example" + media.name = "LFE example" + filter.graph = { + nodes = [ + { name = copyIL type = builtin label = copy } + { name = copyOL type = builtin label = copy } + { name = copyIR type = builtin label = copy } + { name = copyOR type = builtin label = copy } + { + name = mix + type = builtin + label = mixer + control = { + "Gain 1" = 0.5 + "Gain 2" = 0.5 + } + } + { + type = builtin + name = lpLFE + label = bq_lowpass + control = { "Freq" = 150.0 } + } + ] + links = [ + { output = "copyIL:Out" input = "copyOL:In" } + { output = "copyIR:Out" input = "copyOR:In" } + { output = "copyIL:Out" input = "mix:In 1" } + { output = "copyIR:Out" input = "mix:In 2" } + { output = "mix:Out" input = "lpLFE:In" } + ] + inputs = [ "copyIL:In" "copyIR:In" ] + outputs = [ "copyOL:Out" "copyOR:Out" "lpLFE:Out"] + } + capture.props = { + node.name = "input_lfe" + audio.position = [ FL FR ] + media.class = "Audio/Sink" + } + playback.props = { + node.name = "output_lfe" + audio.position = [ FL FR LFE ] + stream.dont-remix = true + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/sink-matrix-spatialiser.conf b/src/daemon/filter-chain/sink-matrix-spatialiser.conf new file mode 100644 index 0000000..b39890c --- /dev/null +++ b/src/daemon/filter-chain/sink-matrix-spatialiser.conf @@ -0,0 +1,41 @@ +# Matrix Spatialiser sink +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +# ( Jean-Philippe Guillemin <hyp3ri0n@sfr.fr> ) +# + +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Matrix Spatialiser" + media.name = "Matrix Spatialiser" + filter.graph = { + nodes = [ + { + type = ladspa + name = matrix + plugin = matrix_spatialiser_1422 + label = matrixSpatialiser + control = { + "Width" = 80 + } + } + ] + inputs = [ "matrix:Input L" "matrix:Input R" ] + outputs = [ "matrix:Output L" "matrix:Output R" ] + } + audio.channels = 2 + audio.position = [ FL FR ] + capture.props = { + node.name = "effect_input.matrix_spatialiser" + media.class = Audio/Sink + } + playback.props = { + node.name = "effect_output.matrix_spatialiser" + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/sink-mix-FL-FR.conf b/src/daemon/filter-chain/sink-mix-FL-FR.conf new file mode 100644 index 0000000..8288fad --- /dev/null +++ b/src/daemon/filter-chain/sink-mix-FL-FR.conf @@ -0,0 +1,40 @@ +# An example filter chain that makes a stereo sink that mixes +# the FL and FR channels to a single FL channel +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Mix example" + media.name = "Mix example" + filter.graph = { + nodes = [ + { + name = mix + type = builtin + label = mixer + control = { + "Gain 1" = 0.5 + "Gain 2" = 0.5 + } + } + ] + inputs = [ "mix:In 1" "mix:In 2" ] + outputs = [ "mix:Out" ] + } + capture.props = { + node.name = "mix_input.mix-FL-FR-to-FL" + audio.position = [ FL FR ] + media.class = "Audio/Sink" + } + playback.props = { + node.name = "mix_output.mix-FL-FR-to-FL" + audio.position = [ FL ] + stream.dont-remix = true + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf b/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf new file mode 100644 index 0000000..ee8929b --- /dev/null +++ b/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf @@ -0,0 +1,177 @@ +# Convolver sink +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Virtual Surround Sink" + media.name = "Virtual Surround Sink" + filter.graph = { + nodes = [ + { + type = builtin + label = convolver + name = convFL_L + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 0 + } + } + { + type = builtin + label = convolver + name = convFL_R + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 1 + } + } + { + type = builtin + label = convolver + name = convFR_L + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 1 + } + } + { + type = builtin + label = convolver + name = convFR_R + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 0 + } + } + { + type = builtin + label = convolver + name = convFC + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 2 + } + } + { + type = builtin + label = convolver + name = convLFE + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 3 + } + } + { + type = builtin + label = convolver + name = convSL_L + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 4 + } + } + { + type = builtin + label = convolver + name = convSL_R + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 5 + } + } + { + type = builtin + label = convolver + name = convSR_L + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 5 + } + } + { + type = builtin + label = convolver + name = convSR_R + config = { + filename = "hrir_kemar/hrir-kemar.wav" + channel = 4 + } + } + { + type = builtin + label = mixer + name = mixL + } + { + type = builtin + label = mixer + name = mixR + } + { + type = builtin + label = copy + name = copyFL + } + { + type = builtin + label = copy + name = copyFR + } + { + type = builtin + label = copy + name = copySL + } + { + type = builtin + label = copy + name = copySR + } + ] + links = [ + { output = "copyFL:Out" input = "convFL_L:In" } + { output = "copyFL:Out" input = "convFL_R:In" } + { output = "copyFR:Out" input = "convFR_R:In" } + { output = "copyFR:Out" input = "convFR_L:In" } + + { output = "copySL:Out" input = "convSL_L:In" } + { output = "copySL:Out" input = "convSL_R:In" } + { output = "copySR:Out" input = "convSR_R:In" } + { output = "copySR:Out" input = "convSR_L:In" } + + { output = "convFL_L:Out" input = "mixL:In 1" } + { output = "convFR_L:Out" input = "mixL:In 2" } + { output = "convFC:Out" input = "mixL:In 3" } + { output = "convLFE:Out" input = "mixL:In 4" } + { output = "convSL_L:Out" input = "mixL:In 5" } + { output = "convSR_L:Out" input = "mixL:In 6" } + + { output = "convFL_R:Out" input = "mixR:In 1" } + { output = "convFR_R:Out" input = "mixR:In 2" } + { output = "convFC:Out" input = "mixR:In 3" } + { output = "convLFE:Out" input = "mixR:In 4" } + { output = "convSL_R:Out" input = "mixR:In 5" } + { output = "convSR_R:Out" input = "mixR:In 6" } + ] + inputs = [ "copyFL:In" "copyFR:In" "convFC:In" "convLFE:In" "copySL:In" "copySR:In" ] + outputs = [ "mixL:Out" "mixR:Out" ] + + } + capture.props = { + node.name = "effect_input.virtual-surround-5.1-kemar" + media.class = Audio/Sink + audio.channels = 6 + audio.position = [ FL FR FC LFE SL SR] + } + playback.props = { + node.name = "effect_output.virtual-surround-5.1-kemar" + node.passive = true + audio.channels = 2 + audio.position = [ FL FR ] + } + } + } +] diff --git a/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf b/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf new file mode 100644 index 0000000..4aad310 --- /dev/null +++ b/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf @@ -0,0 +1,101 @@ +# Convolver sink +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Virtual Surround Sink" + media.name = "Virtual Surround Sink" + filter.graph = { + nodes = [ + # duplicate inputs + { type = builtin label = copy name = copyFL } + { type = builtin label = copy name = copyFR } + { type = builtin label = copy name = copyFC } + { type = builtin label = copy name = copyRL } + { type = builtin label = copy name = copyRR } + { type = builtin label = copy name = copySL } + { type = builtin label = copy name = copySR } + { type = builtin label = copy name = copyLFE } + + # apply hrir - HeSuVi 14-channel WAV (not the *-.wav variants) (note: */44/* in HeSuVi are the same, but resampled to 44100) + { type = builtin label = convolver name = convFL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 0 } } + { type = builtin label = convolver name = convFL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 1 } } + { type = builtin label = convolver name = convSL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 2 } } + { type = builtin label = convolver name = convSL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 3 } } + { type = builtin label = convolver name = convRL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 4 } } + { type = builtin label = convolver name = convRL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 5 } } + { type = builtin label = convolver name = convFC_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 6 } } + { type = builtin label = convolver name = convFR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 7 } } + { type = builtin label = convolver name = convFR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 8 } } + { type = builtin label = convolver name = convSR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 9 } } + { type = builtin label = convolver name = convSR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 10 } } + { type = builtin label = convolver name = convRR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 11 } } + { type = builtin label = convolver name = convRR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 12 } } + { type = builtin label = convolver name = convFC_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 13 } } + + # treat LFE as FC + { type = builtin label = convolver name = convLFE_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 6 } } + { type = builtin label = convolver name = convLFE_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 13 } } + + # stereo output + { type = builtin label = mixer name = mixL } + { type = builtin label = mixer name = mixR } + ] + links = [ + # input + { output = "copyFL:Out" input="convFL_L:In" } + { output = "copyFL:Out" input="convFL_R:In" } + { output = "copySL:Out" input="convSL_L:In" } + { output = "copySL:Out" input="convSL_R:In" } + { output = "copyRL:Out" input="convRL_L:In" } + { output = "copyRL:Out" input="convRL_R:In" } + { output = "copyFC:Out" input="convFC_L:In" } + { output = "copyFR:Out" input="convFR_R:In" } + { output = "copyFR:Out" input="convFR_L:In" } + { output = "copySR:Out" input="convSR_R:In" } + { output = "copySR:Out" input="convSR_L:In" } + { output = "copyRR:Out" input="convRR_R:In" } + { output = "copyRR:Out" input="convRR_L:In" } + { output = "copyFC:Out" input="convFC_R:In" } + { output = "copyLFE:Out" input="convLFE_L:In" } + { output = "copyLFE:Out" input="convLFE_R:In" } + + # output + { output = "convFL_L:Out" input="mixL:In 1" } + { output = "convFL_R:Out" input="mixR:In 1" } + { output = "convSL_L:Out" input="mixL:In 2" } + { output = "convSL_R:Out" input="mixR:In 2" } + { output = "convRL_L:Out" input="mixL:In 3" } + { output = "convRL_R:Out" input="mixR:In 3" } + { output = "convFC_L:Out" input="mixL:In 4" } + { output = "convFC_R:Out" input="mixR:In 4" } + { output = "convFR_R:Out" input="mixR:In 5" } + { output = "convFR_L:Out" input="mixL:In 5" } + { output = "convSR_R:Out" input="mixR:In 6" } + { output = "convSR_L:Out" input="mixL:In 6" } + { output = "convRR_R:Out" input="mixR:In 7" } + { output = "convRR_L:Out" input="mixL:In 7" } + { output = "convLFE_R:Out" input="mixR:In 8" } + { output = "convLFE_L:Out" input="mixL:In 8" } + ] + inputs = [ "copyFL:In" "copyFR:In" "copyFC:In" "copyLFE:In" "copyRL:In" "copyRR:In", "copySL:In", "copySR:In" ] + outputs = [ "mixL:Out" "mixR:Out" ] + } + capture.props = { + node.name = "effect_input.virtual-surround-7.1-hesuvi" + media.class = Audio/Sink + audio.channels = 8 + audio.position = [ FL FR FC LFE RL RR SL SR ] + } + playback.props = { + node.name = "effect_output.virtual-surround-7.1-hesuvi" + node.passive = true + audio.channels = 2 + audio.position = [ FL FR ] + } + } + } +] diff --git a/src/daemon/filter-chain/source-duplicate-FL.conf b/src/daemon/filter-chain/source-duplicate-FL.conf new file mode 100644 index 0000000..7e0158f --- /dev/null +++ b/src/daemon/filter-chain/source-duplicate-FL.conf @@ -0,0 +1,52 @@ +# An example filter chain that makes a source that duplicates the FL channel +# to FL and FR. +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Remap example" + media.name = "Remap example" + filter.graph = { + nodes = [ + { + name = copyIL + type = builtin + label = copy + } + { + name = copyOL + type = builtin + label = copy + } + { + name = copyOR + type = builtin + label = copy + } + ] + links = [ + # we can only tee from nodes, not inputs so we need + # to copy the inputs and then tee. + { output = "copyIL:Out" input = "copyOL:In" } + { output = "copyIL:Out" input = "copyOR:In" } + ] + inputs = [ "copyIL:In" ] + outputs = [ "copyOL:Out" "copyOR:Out" ] + } + capture.props = { + node.name = "remap_input.remap-FL-to-FL-FR" + audio.position = [ FL ] + stream.dont-remix = true + node.passive = true + } + playback.props = { + node.name = "remap_output.remap-FL-to-FL-FR" + audio.position = [ FL FR ] + media.class = "Audio/Source" + } + } + } +] diff --git a/src/daemon/filter-chain/source-rnnoise.conf b/src/daemon/filter-chain/source-rnnoise.conf new file mode 100644 index 0000000..5babd81 --- /dev/null +++ b/src/daemon/filter-chain/source-rnnoise.conf @@ -0,0 +1,35 @@ +# Noise canceling source +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Noise Canceling source" + media.name = "Noise Canceling source" + filter.graph = { + nodes = [ + { + type = ladspa + name = rnnoise + plugin = librnnoise_ladspa + label = noise_suppressor_stereo + control = { + "VAD Threshold (%)" 50.0 + } + } + ] + } + audio.position = [ FL FR ] + capture.props = { + node.name = "effect_input.rnnoise" + node.passive = true + } + playback.props = { + node.name = "effect_output.rnnoise" + media.class = Audio/Source + } + } + } +] diff --git a/src/daemon/jack.conf.in b/src/daemon/jack.conf.in new file mode 100644 index 0000000..95a86cb --- /dev/null +++ b/src/daemon/jack.conf.in @@ -0,0 +1,128 @@ +# JACK client config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/jack.conf.d/ for system-wide changes or in +# ~/.config/pipewire/jack.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + log.level = 0 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + support.* = support/libspa-support +} + +context.modules = [ + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + # + # Boost the data thread priority. + { name = libpipewire-module-rt + args = { + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + { name = libpipewire-module-metadata } +] + +# global properties for all jack clients +jack.properties = { + #node.latency = 1024/48000 + #node.rate = 1/48000 + #node.quantum = 1024/48000 + #node.lock-quantum = true + #node.force-quantum = 0 + #jack.show-monitor = true + #jack.merge-monitor = true + #jack.short-name = false + #jack.filter-name = false + #jack.filter-char = " " + # + # allow: Don't restrict self connect requests + # fail-external: Fail self connect requests to external ports only + # ignore-external: Ignore self connect requests to external ports only + # fail-all: Fail all self connect requests + # ignore-all: Ignore all self connect requests + #jack.self-connect-mode = allow + #jack.locked-process = true + #jack.default-as-system = false + #jack.fix-midi-events = true + #jack.global-buffer-size = false +} + +# client specific properties +jack.rules = [ + { matches = [ + { + # all keys must match the value. ~ starts regex. + #client.name = "Carla" + #application.process.binary = "jack_simple_client" + #application.name = "~jack_simple_client.*" + } + ] + actions = { + update-props = { + #node.latency = 512/48000 + } + } + } + { matches = [ { application.process.binary = "jack_bufsize" } ] + actions = { + update-props = { + jack.global-buffer-size = true # quantum set globally using metadata + } + } + } + { matches = [ { application.process.binary = "qsynth" } ] + actions = { + update-props = { + node.pause-on-idle = false # makes audio fade out when idle + node.passive = true # makes the sink and qsynth suspend + } + } + } + { matches = [ { client.name = "Mixxx" } ] + actions = { + update-props = { + jack.merge-monitor = false + } + } + } +] diff --git a/src/daemon/meson.build b/src/daemon/meson.build new file mode 100644 index 0000000..5d5914e --- /dev/null +++ b/src/daemon/meson.build @@ -0,0 +1,141 @@ +pipewire_daemon_sources = [ + 'pipewire.c', +] + +pipewire_c_args = [ + '-DG_LOG_DOMAIN=g_log_domain_pipewire', +] + +conf_config = configuration_data() +conf_config.set('VERSION', '"@0@"'.format(pipewire_version)) +conf_config.set('PIPEWIRE_CONFIG_DIR', pipewire_configdir) +conf_config.set('session_manager_path', pipewire_bindir / 'pipewire-media-session') +conf_config.set('session_manager_args', '') +conf_config.set('pipewire_path', pipewire_bindir / 'pipewire') +conf_config.set('pipewire_pulse_path', pipewire_bindir / 'pipewire-pulse') +conf_config.set('sm_comment', '#') +conf_config.set('pulse_comment', '#') + +conf_config_uninstalled = conf_config +conf_config_uninstalled.set('pipewire_path', + meson.project_build_root() / 'src' / 'daemon' / 'pipewire') +conf_config_uninstalled.set('pipewire_pulse_path', + meson.project_build_root() / 'src' / 'daemon' / 'pipewire-pulse') +conf_config_uninstalled.set('pulse_comment', '') + +build_ms = 'media-session' in get_option('session-managers') +build_wp = 'wireplumber' in get_option('session-managers') +default_sm = get_option('session-managers').get(0, '') + +summary({'Build media-session': build_ms, + 'Build wireplumber': build_wp, + 'Default session-manager': default_sm}, + section: 'Session managers', + bool_yn: true) + +if build_wp + wp_proj = subproject('wireplumber', required : true) +endif +if build_ms + ms_proj = subproject('media-session', required : true) +endif + +if default_sm == '' + summary({'No session manager': 'pw-uninstalled.sh will not work out of the box!'}) +elif default_sm == 'media-session' + ms_bindir = ms_proj.get_variable('media_session_bin_dir', pipewire_bindir) + conf_config.set('session_manager_path', ms_bindir / 'pipewire-media-session') + + ms_uninstalled = ms_proj.get_variable('media_session_uninstalled') + conf_config_uninstalled.set('session_manager_path', ms_uninstalled.full_path()) + conf_config_uninstalled.set('session_manager_args', 'pipewire-media-session') + conf_config_uninstalled.set('sm_comment', '') +elif default_sm == 'wireplumber' + wp_bindir = wp_proj.get_variable('wireplumber_bin_dir', pipewire_bindir) + conf_config.set('session_manager_path', wp_bindir / 'wireplumber') + + wp_uninstalled = wp_proj.get_variable('wireplumber_uninstalled') + conf_config_uninstalled.set('session_manager_path', wp_uninstalled.full_path()) + conf_config_uninstalled.set('session_manager_args', 'wireplumber') + conf_config_uninstalled.set('sm_comment', '') +else + conf_config_uninstalled.set('session_manager_path', default_sm) + conf_config_uninstalled.set('sm_comment', '') +endif + +conf_files = [ + 'pipewire.conf', + 'client.conf', + 'client-rt.conf', + 'filter-chain.conf', + 'jack.conf', + 'minimal.conf', + 'pipewire-pulse.conf', + 'pipewire-avb.conf', +] + +foreach c : conf_files + configure_file(input : '@0@.in'.format(c), + output : c, + configuration : conf_config, + install_dir : pipewire_confdatadir) +endforeach + +configure_file(input : 'pipewire.conf.in', + output : 'pipewire-uninstalled.conf', + configuration : conf_config_uninstalled) + +pipewire_exec = executable('pipewire', + pipewire_daemon_sources, + install: true, + c_args : pipewire_c_args, + include_directories : [ configinc ], + dependencies : [ spa_dep, pipewire_dep, ], +) + +executable('pipewire-pulse', + pipewire_daemon_sources, + install: true, + c_args : pipewire_c_args, + include_directories : [ configinc ], + dependencies : [ spa_dep, pipewire_dep, ], +) + +executable('pipewire-avb', + pipewire_daemon_sources, + install: true, + c_args : pipewire_c_args, + include_directories : [ configinc ], + dependencies : [ spa_dep, pipewire_dep, ], +) + +ln = find_program('ln') + +custom_target('pipewire-uninstalled', + build_by_default: true, + install: false, + input: pipewire_exec, + output: 'pipewire-uninstalled', + command: [ln, '-fs', meson.project_build_root() + '/@INPUT@', '@OUTPUT@'], +) + +#desktop_file = i18n.merge_file( +# input : 'pipewire.desktop.in', +# output : 'pipewire.desktop', +# po_dir : po_dir, +# type : 'desktop', +# install : true, +# install_dir : pipewire_sysconfdir / 'xdg' / 'autostart' +#) +# +#desktop_utils = find_program('desktop-file-validate', required: false) +#if desktop_utils.found() +# test('Validate desktop file', desktop_utils, +# args: [ desktop_file ], +# ) +#endif + +subdir('filter-chain') +if systemd.found() + subdir('systemd') +endif diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in new file mode 100644 index 0000000..4a3a2cb --- /dev/null +++ b/src/daemon/minimal.conf.in @@ -0,0 +1,352 @@ +# Simple daemon config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/minimal.conf.d/ for system-wide changes or in +# ~/.config/pipewire/minimal.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #library.name.system = support/libspa-support + #context.data-loop.library.name.system = support/libspa-support + #support.dbus = true + #link.max-buffers = 64 + link.max-buffers = 16 # version < 3 clients can't handle more + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #clock.power-of-two-quantum = true + #log.level = 2 + #cpu.zero.denormals = false + + core.daemon = true # listening for socket connections + core.name = pipewire-0 # core name and socket name + + ## Properties for the DSP configuration. + #default.clock.rate = 48000 + #default.clock.allowed-rates = [ 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 + # + settings.check-quantum = true + settings.check-rate = true + # + # These overrides are only applied when running in a vm. + vm.overrides = { + default.clock.min-quantum = 1024 + } +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert.* = audioconvert/libspa-audioconvert + api.alsa.* = alsa/libspa-alsa + support.* = support/libspa-support +} + +context.modules = [ + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + + # Uses realtime scheduling to boost the audio thread priorities. This uses + # RTKit if the user doesn't have permission to use regular realtime + # scheduling. + { name = libpipewire-module-rt + args = { + nice.level = -11 + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # The profile module. Allows application to access profiler + # and performance data. It provides an interface that is used + # by pw-top and pw-profiler. + { name = libpipewire-module-profiler } + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + { name = libpipewire-module-metadata } + + # Creates a factory for making nodes that run in the + # context of the PipeWire server. + { name = libpipewire-module-spa-node-factory } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # The access module can perform access checks and block + # new clients. + { name = libpipewire-module-access + args = { + # access.allowed to list an array of paths of allowed + # apps. + #access.allowed = [ + # @session_manager_path@ + #] + + # An array of rejected paths. + #access.rejected = [ ] + + # An array of paths with restricted access. + #access.restricted = [ ] + + # Anything not in the above lists gets assigned the + # access.force permission. + #access.force = flatpak + } + } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } + + # Makes a factory for creating links between ports. + { name = libpipewire-module-link-factory } +] + +context.objects = [ + #{ factory = <factory-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ nofail ] ] + #} + # + # Creates an object from a PipeWire factory with the given parameters. + # If nofail is given, errors are ignored (and no object is created). + # + #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } } + #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } + #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } + #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } + #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test } } + #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } + + # Make a default metadata store + { factory = metadata args = { metadata.name = default } } + + # A default dummy driver. This handles nodes marked with the "node.always-driver" + # property when no other driver is currently active. JACK clients need this. + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Dummy-Driver + node.group = pipewire.dummy + priority.driver = 20000 + } + } + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Freewheel-Driver + priority.driver = 19000 + node.group = pipewire.freewheel + node.freewheel = true + } + } + + # This creates a single PCM source device for the given + # alsa device path hw:0. You can change source to sink + # to make a sink in the same way. + { factory = adapter + args = { + factory.name = api.alsa.pcm.source + node.name = "system" + node.description = "system" + media.class = "Audio/Source" + api.alsa.path = "hw:0" + #api.alsa.period-size = 0 + #api.alsa.period-num = 0 + #api.alsa.headroom = 0 + #api.alsa.start-delay = 0 + #api.alsa.disable-mmap = false + #api.alsa.disable-batch = false + #api.alsa.use-chmap = false + #api.alsa.multirate = true + #latency.internal.rate = 0 + #latency.internal.ns = 0 + #clock.name = api.alsa.0 + node.suspend-on-idle = true + #audio.format = "S32" + #audio.rate = 48000 + #audio.allowed-rates = [ ] + #audio.channels = 4 + #audio.position = [ FL FR RL RR ] + #resample.quality = 4 + resample.disable = true + #monitor.channel-volumes = false + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.upmix-method = psd # none, simple + #channelmix.lfe-cutoff = 150 + #channelmix.fc-cutoff = 12000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.0 + #channelmix.hilbert-taps = 0 + channelmix.disable = true + #dither.noise = 0 + #node.param.Props = { + # params = [ + # audio.channels 6 + # ] + #} + adapter.auto-port-config = { + mode = dsp + monitor = false + control = false + position = unknown # aux, preserve + } + #node.param.PortConfig = { + # direction = Output + # mode = dsp + # format = { + # mediaType = audio + # mediaSubtype = raw + # format = F32 + # rate = 48000 + # channels = 4 + # position = [ FL FR RL RR ] + # } + #} + } + } + { factory = adapter + args = { + factory.name = api.alsa.pcm.sink + node.name = "system" + node.description = "system" + media.class = "Audio/Sink" + api.alsa.path = "hw:0" + #api.alsa.period-size = 0 + #api.alsa.period-num = 0 + #api.alsa.headroom = 0 + #api.alsa.start-delay = 0 + #api.alsa.disable-mmap = false + #api.alsa.disable-batch = false + #api.alsa.use-chmap = false + #api.alsa.multirate = true + #latency.internal.rate = 0 + #latency.internal.ns = 0 + #clock.name = api.alsa.0 + node.suspend-on-idle = true + #audio.format = "S32" + #audio.rate = 48000 + #audio.allowed-rates = [ ] + #audio.channels = 2 + #audio.position = "FL,FR" + #resample.quality = 4 + resample.disable = true + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.upmix-method = psd # none, simple + #channelmix.lfe-cutoff = 150 + #channelmix.fc-cutoff = 12000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.0 + #channelmix.hilbert-taps = 0 + channelmix.disable = true + #dither.noise = 0 + #node.param.Props = { + # params = [ + # audio.format S16 + # ] + #} + adapter.auto-port-config = { + mode = dsp + monitor = false + control = false + position = unknown # aux, preserve + } + #node.param.PortConfig = { + # direction = Input + # mode = dsp + # monitor = true + # format = { + # mediaType = audio + # mediaSubtype = raw + # format = F32 + # rate = 48000 + # channels = 4 + # } + #} + } + } + # This creates a new Source node. It will have input ports + # that you can link, to provide audio for this source. + #{ factory = adapter + # args = { + # factory.name = support.null-audio-sink + # node.name = "my-mic" + # node.description = "Microphone" + # media.class = "Audio/Source/Virtual" + # audio.position = "FL,FR" + # adapter.auto-port-config = { + # mode = dsp + # monitor = true + # position = preserve # unknown, aux, preserve + # } + # } + #} + # This creates a new link between the source and the virtual + # source ports. + #{ factory = link-factory + # args = { + # link.output.node = system + # link.output.port = capture_1 + # link.input.node = my-mic + # link.input.port = input_FL + # link.passive = true + # } + #} + #{ factory = link-factory + # args = { + # link.output.node = system + # link.output.port = capture_2 + # link.input.node = my-mic + # link.input.port = input_FR + # link.passive = true + # } + #} +] + +context.exec = [ + #{ path = <program-name> [ args = "<arguments>" ] } + # + # Execute the given program with arguments. + # + # You can optionally start the pulseaudio-server here as well + # but it is better to start it as a systemd service. + # It can be interesting to start another daemon here that listens + # on another address with the -a option (eg. -a tcp:4713). + # + #@pulse_comment@{ path = "@pipewire_path@" args = "-c pipewire-pulse.conf" } +] diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in new file mode 100644 index 0000000..68f89ca --- /dev/null +++ b/src/daemon/pipewire-avb.conf.in @@ -0,0 +1,73 @@ +# PulseAudio config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #log.level = 2 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + { name = libpipewire-module-rt + args = { + nice.level = -11 + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-client-node } + { name = libpipewire-module-adapter } + { name = libpipewire-module-avb + args = { + # contents of avb.properties can also be placed here + # to have config per server. + } + } +] + +# Extra modules can be loaded here. Setup in default.pa can be moved here +context.exec = [ + #{ path = "pactl" args = "load-module module-always-sink" } +] + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.lfe-cutoff = 120 + #channelmix.fc-cutoff = 6000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.1 + #channelmix.hilbert-taps = 0 +} + +avb.properties = { + # the addresses this server listens on + #ifname = "eth0.2" + ifname = "enp3s0" + # These overrides are only applied when running in a vm. + vm.overrides = { + } +} diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in new file mode 100644 index 0000000..18bca3a --- /dev/null +++ b/src/daemon/pipewire-pulse.conf.in @@ -0,0 +1,160 @@ +# PulseAudio config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #log.level = 2 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + { name = libpipewire-module-rt + args = { + nice.level = -11 + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-client-node } + { name = libpipewire-module-adapter } + { name = libpipewire-module-metadata } + + { name = libpipewire-module-protocol-pulse + args = { + # contents of pulse.properties can also be placed here + # to have config per server. + } + } +] + +# Extra scripts can be started here. Setup in default.pa can be moved in +# a script or in pulse.cmd below +context.exec = [ + #{ path = "pactl" args = "load-module module-always-sink" } + #{ path = "pactl" args = "upload-sample my-sample.wav my-sample" } + #{ path = "/usr/bin/sh" args = "~/.config/pipewire/default.pw" } +] + +# Extra commands can be executed here. +# load-module : loads a module with args and flags +# args = "<module-name> <module-args>" +# flags = [ "no-fail" ] +pulse.cmd = [ + { cmd = "load-module" args = "module-always-sink" flags = [ ] } + #{ cmd = "load-module" args = "module-switch-on-connect" } + #{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] } +] + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = true + #channelmix.upmix = true + #channelmix.upmix-method = psd # none, simple + #channelmix.lfe-cutoff = 150 + #channelmix.fc-cutoff = 12000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.0 + #channelmix.hilbert-taps = 0 + #dither.noise = 0 +} + +pulse.properties = { + # the addresses this server listens on + server.address = [ + "unix:native" + #"unix:/tmp/something" # absolute paths may be used + #"tcp:4713" # IPv4 and IPv6 on all addresses + #"tcp:[::]:9999" # IPv6 on all addresses + #"tcp:127.0.0.1:8888" # IPv4 on a single address + # + #{ address = "tcp:4713" # address + # max-clients = 64 # maximum number of clients + # listen-backlog = 32 # backlog in the server listen queue + # client.access = "restricted" # permissions for clients + #} + ] + #pulse.min.req = 256/48000 # 5ms + #pulse.default.req = 960/48000 # 20 milliseconds + #pulse.min.frag = 256/48000 # 5ms + #pulse.default.frag = 96000/48000 # 2 seconds + #pulse.default.tlength = 96000/48000 # 2 seconds + #pulse.min.quantum = 256/48000 # 5ms + #pulse.idle.timeout = 0 # don't pause after underruns + #pulse.default.format = F32 + #pulse.default.position = [ FL FR ] + # These overrides are only applied when running in a vm. + vm.overrides = { + pulse.min.quantum = 1024/48000 # 22ms + } +} + +# client/stream specific properties +pulse.rules = [ + { + matches = [ + { + # all keys must match the value. ~ starts regex. + #client.name = "Firefox" + #application.process.binary = "teams" + #application.name = "~speech-dispatcher.*" + } + ] + actions = { + update-props = { + #node.latency = 512/48000 + } + # Possible quirks:" + # force-s16-info forces sink and source info as S16 format + # remove-capture-dont-move removes the capture DONT_MOVE flag + #quirks = [ ] + } + } + { + # skype does not want to use devices that don't have an S16 sample format. + matches = [ + { application.process.binary = "teams" } + { application.process.binary = "teams-insiders" } + { application.process.binary = "skypeforlinux" } + ] + actions = { quirks = [ force-s16-info ] } + } + { + # firefox marks the capture streams as don't move and then they + # can't be moved with pavucontrol or other tools. + matches = [ { application.process.binary = "firefox" } ] + actions = { quirks = [ remove-capture-dont-move ] } + } + { + # speech dispatcher asks for too small latency and then underruns. + matches = [ { application.name = "~speech-dispatcher.*" } ] + actions = { + update-props = { + pulse.min.req = 512/48000 # 10.6ms + pulse.min.quantum = 512/48000 # 10.6ms + pulse.idle.timeout = 5 # pause after 5 seconds of underrun + } + } + } +] diff --git a/src/daemon/pipewire.c b/src/daemon/pipewire.c new file mode 100644 index 0000000..97569c0 --- /dev/null +++ b/src/daemon/pipewire.c @@ -0,0 +1,143 @@ +/* PipeWire + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <limits.h> +#include <signal.h> +#include <getopt.h> +#include <libgen.h> +#include <locale.h> + +#include <spa/utils/result.h> +#include <pipewire/pipewire.h> + +#include <pipewire/i18n.h> + +#include "config.h" + +static void do_quit(void *data, int signal_number) +{ + struct pw_main_loop *loop = data; + pw_main_loop_quit(loop); +} + +static void show_help(const char *name, const char *config_name) +{ + fprintf(stdout, _("%s [options]\n" + " -h, --help Show this help\n" + " --version Show version\n" + " -c, --config Load config (Default %s)\n"), + name, + config_name); +} + +int main(int argc, char *argv[]) +{ + struct pw_context *context = NULL; + struct pw_main_loop *loop = NULL; + struct pw_properties *properties = NULL; + static const struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "config", required_argument, NULL, 'c' }, + { "verbose", no_argument, NULL, 'v' }, + + { NULL, 0, NULL, 0} + }; + int c, res = 0; + char path[PATH_MAX]; + const char *config_name; + enum spa_log_level level = pw_log_level; + + if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0) + fprintf(stderr, "can't set PIPEWIRE_INTERNAL env: %m"); + + snprintf(path, sizeof(path), "%s.conf", argv[0]); + config_name = basename(path); + + setlocale(LC_ALL, ""); + pw_init(&argc, &argv); + + while ((c = getopt_long(argc, argv, "hVc:v", long_options, NULL)) != -1) { + switch (c) { + case 'v': + if (level < SPA_LOG_LEVEL_TRACE) + pw_log_set_level(++level); + break; + case 'h': + show_help(argv[0], config_name); + return 0; + case 'V': + fprintf(stdout, "%s\n" + "Compiled with libpipewire %s\n" + "Linked with libpipewire %s\n", + argv[0], + pw_get_headers_version(), + pw_get_library_version()); + return 0; + case 'c': + config_name = optarg; + break; + default: + res = -EINVAL; + goto done; + } + } + + properties = pw_properties_new( + PW_KEY_CONFIG_NAME, config_name, + NULL); + + loop = pw_main_loop_new(&properties->dict); + if (loop == NULL) { + pw_log_error("failed to create main-loop: %m"); + res = -errno; + goto done; + } + + pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGINT, do_quit, loop); + pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGTERM, do_quit, loop); + + context = pw_context_new(pw_main_loop_get_loop(loop), properties, 0); + properties = NULL; + + if (context == NULL) { + pw_log_error("failed to create context: %m"); + res = -errno; + goto done; + } + + pw_log_info("start main loop"); + pw_main_loop_run(loop); + pw_log_info("leave main loop"); + +done: + pw_properties_free(properties); + if (context) + pw_context_destroy(context); + if (loop) + pw_main_loop_destroy(loop); + pw_deinit(); + + return res; +} diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in new file mode 100644 index 0000000..4aa30a4 --- /dev/null +++ b/src/daemon/pipewire.conf.in @@ -0,0 +1,259 @@ +# Daemon config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/pipewire.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #library.name.system = support/libspa-support + #context.data-loop.library.name.system = support/libspa-support + #support.dbus = true + #link.max-buffers = 64 + link.max-buffers = 16 # version < 3 clients can't handle more + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #clock.power-of-two-quantum = true + #log.level = 2 + #cpu.zero.denormals = false + + core.daemon = true # listening for socket connections + core.name = pipewire-0 # core name and socket name + + ## Properties for the DSP configuration. + #default.clock.rate = 48000 + #default.clock.allowed-rates = [ 48000 ] + #default.clock.quantum = 1024 + default.clock.min-quantum = 16 + #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 + # + #settings.check-quantum = false + #settings.check-rate = false + # + # These overrides are only applied when running in a vm. + vm.overrides = { + default.clock.min-quantum = 1024 + } +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert.* = audioconvert/libspa-audioconvert + avb.* = avb/libspa-avb + api.alsa.* = alsa/libspa-alsa + api.v4l2.* = v4l2/libspa-v4l2 + api.libcamera.* = libcamera/libspa-libcamera + api.bluez5.* = bluez5/libspa-bluez5 + api.vulkan.* = vulkan/libspa-vulkan + api.jack.* = jack/libspa-jack + support.* = support/libspa-support + #videotestsrc = videotestsrc/libspa-videotestsrc + #audiotestsrc = audiotestsrc/libspa-audiotestsrc +} + +context.modules = [ + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + + # Uses realtime scheduling to boost the audio thread priorities. This uses + # RTKit if the user doesn't have permission to use regular realtime + # scheduling. + { name = libpipewire-module-rt + args = { + nice.level = -11 + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # The profile module. Allows application to access profiler + # and performance data. It provides an interface that is used + # by pw-top and pw-profiler. + { name = libpipewire-module-profiler } + + # Allows applications to create metadata objects. It creates + # a factory for Metadata objects. + { name = libpipewire-module-metadata } + + # Creates a factory for making devices that run in the + # context of the PipeWire server. + { name = libpipewire-module-spa-device-factory } + + # Creates a factory for making nodes that run in the + # context of the PipeWire server. + { name = libpipewire-module-spa-node-factory } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Allows creating devices that run in the context of the + # client. Is used by the session manager. + { name = libpipewire-module-client-device } + + # The portal module monitors the PID of the portal process + # and tags connections with the same PID as portal + # connections. + { name = libpipewire-module-portal + flags = [ ifexists nofail ] + } + + # The access module can perform access checks and block + # new clients. + { name = libpipewire-module-access + args = { + # access.allowed to list an array of paths of allowed + # apps. + #access.allowed = [ + # @session_manager_path@ + #] + + # An array of rejected paths. + #access.rejected = [ ] + + # An array of paths with restricted access. + #access.restricted = [ ] + + # Anything not in the above lists gets assigned the + # access.force permission. + #access.force = flatpak + } + } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } + + # Makes a factory for creating links between ports. + { name = libpipewire-module-link-factory } + + # Provides factories to make session manager objects. + { name = libpipewire-module-session-manager } + + # Use libcanberra to play X11 Bell + { name = libpipewire-module-x11-bell + args = { + #sink.name = "@DEFAULT_SINK@" + #sample.name = "bell-window-system" + #x11.display = null + #x11.xauthority = null + } + flags = [ ifexists nofail ] + } +] + +context.objects = [ + #{ factory = <factory-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ nofail ] ] + #} + # + # Creates an object from a PipeWire factory with the given parameters. + # If nofail is given, errors are ignored (and no object is created). + # + #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } } + #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } + #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } + #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } + #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test } } + #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } + + # A default dummy driver. This handles nodes marked with the "node.always-driver" + # property when no other driver is currently active. JACK clients need this. + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Dummy-Driver + node.group = pipewire.dummy + priority.driver = 20000 + } + } + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Freewheel-Driver + priority.driver = 19000 + node.group = pipewire.freewheel + node.freewheel = true + } + } + # This creates a new Source node. It will have input ports + # that you can link, to provide audio for this source. + #{ factory = adapter + # args = { + # factory.name = support.null-audio-sink + # node.name = "my-mic" + # node.description = "Microphone" + # media.class = "Audio/Source/Virtual" + # audio.position = "FL,FR" + # } + #} + + # This creates a single PCM source device for the given + # alsa device path hw:0. You can change source to sink + # to make a sink in the same way. + #{ factory = adapter + # args = { + # factory.name = api.alsa.pcm.source + # node.name = "alsa-source" + # node.description = "PCM Source" + # media.class = "Audio/Source" + # api.alsa.path = "hw:0" + # api.alsa.period-size = 1024 + # api.alsa.headroom = 0 + # api.alsa.disable-mmap = false + # api.alsa.disable-batch = false + # audio.format = "S16LE" + # audio.rate = 48000 + # audio.channels = 2 + # audio.position = "FL,FR" + # } + #} +] + +context.exec = [ + #{ path = <program-name> [ args = "<arguments>" ] } + # + # Execute the given program with arguments. + # + # You can optionally start the session manager here, + # but it is better to start it as a systemd service. + # Run the session manager with -h for options. + # + @sm_comment@{ path = "@session_manager_path@" args = "@session_manager_args@" } + # + # You can optionally start the pulseaudio-server here as well + # but it is better to start it as a systemd service. + # It can be interesting to start another daemon here that listens + # on another address with the -a option (eg. -a tcp:4713). + # + @pulse_comment@{ path = "@pipewire_path@" args = "-c pipewire-pulse.conf" } +] diff --git a/src/daemon/pipewire.desktop.in b/src/daemon/pipewire.desktop.in new file mode 100644 index 0000000..ebf0e30 --- /dev/null +++ b/src/daemon/pipewire.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Name=PipeWire Media System +Comment=Start the PipeWire Media System +Exec=pipewire +Terminal=false +Type=Application +X-GNOME-Autostart-Phase=Initialization +X-KDE-autostart-phase=1 diff --git a/src/daemon/systemd/meson.build b/src/daemon/systemd/meson.build new file mode 100644 index 0000000..482a44c --- /dev/null +++ b/src/daemon/systemd/meson.build @@ -0,0 +1,6 @@ +if get_option('systemd-system-service').allowed() + subdir('system') +endif +if get_option('systemd-user-service').allowed() + subdir('user') +endif diff --git a/src/daemon/systemd/system/meson.build b/src/daemon/systemd/system/meson.build new file mode 100644 index 0000000..84ca0b0 --- /dev/null +++ b/src/daemon/systemd/system/meson.build @@ -0,0 +1,15 @@ +systemd_system_services_dir = systemd.get_variable('systemdsystemunitdir', pkgconfig_define : [ 'rootprefix', prefix]) +if get_option('systemd-system-unit-dir') != '' + systemd_system_services_dir = get_option('systemd-system-unit-dir') +endif + +install_data(sources : 'pipewire.socket', + install_dir : systemd_system_services_dir) + +systemd_config = configuration_data() +systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') + +configure_file(input : 'pipewire.service.in', + output : 'pipewire.service', + configuration : systemd_config, + install_dir : systemd_system_services_dir) diff --git a/src/daemon/systemd/system/pipewire.service.in b/src/daemon/systemd/system/pipewire.service.in new file mode 100644 index 0000000..8b75ba2 --- /dev/null +++ b/src/daemon/systemd/system/pipewire.service.in @@ -0,0 +1,35 @@ +[Unit] +Description=PipeWire Multimedia Service + +# We require pipewire.socket to be active before starting the daemon, because +# while it is possible to use the service without the socket, it is not clear +# why it would be desirable. +# +# Installing pipewire and doing `systemctl start pipewire` will not get the +# socket started, which might be confusing and problematic if the server is to +# be restarted later on, as the client autospawn feature might kick in. Also, a +# start of the socket unit will fail, adding to the confusion. +# +# After=pipewire.socket is not needed, as it is already implicit in the +# socket-service relationship, see systemd.socket(5). +Requires=pipewire.socket + +[Service] +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +RestrictNamespaces=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +Type=simple +AmbientCapabilities=CAP_SYS_NICE +ExecStart=@PW_BINARY@ +Restart=on-failure +RuntimeDirectory=pipewire +RuntimeDirectoryPreserve=yes +User=pipewire +Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire + +[Install] +Also=pipewire.socket +WantedBy=default.target diff --git a/src/daemon/systemd/system/pipewire.socket b/src/daemon/systemd/system/pipewire.socket new file mode 100644 index 0000000..2e3cb71 --- /dev/null +++ b/src/daemon/systemd/system/pipewire.socket @@ -0,0 +1,12 @@ +[Unit] +Description=PipeWire Multimedia System Socket + +[Socket] +Priority=6 +ListenStream=%t/pipewire/pipewire-0 +SocketUser=pipewire +SocketGroup=pipewire +SocketMode=0660 + +[Install] +WantedBy=sockets.target diff --git a/src/daemon/systemd/user/filter-chain.service.in b/src/daemon/systemd/user/filter-chain.service.in new file mode 100644 index 0000000..542cbd7 --- /dev/null +++ b/src/daemon/systemd/user/filter-chain.service.in @@ -0,0 +1,21 @@ +[Unit] +Description=PipeWire filter chain daemon + +After=pipewire.service pipewire-session-manager.service +BindsTo=pipewire.service + +[Service] +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +RestrictNamespaces=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +Type=simple +ExecStart=@PW_BINARY@ -c filter-chain.conf +Restart=on-failure +Slice=session.slice + +[Install] +Also=pipewire.socket +WantedBy=default.target diff --git a/src/daemon/systemd/user/meson.build b/src/daemon/systemd/user/meson.build new file mode 100644 index 0000000..1022762 --- /dev/null +++ b/src/daemon/systemd/user/meson.build @@ -0,0 +1,27 @@ +systemd_user_services_dir = systemd.get_variable('systemduserunitdir', pkgconfig_define : [ 'prefix', prefix]) +if get_option('systemd-user-unit-dir') != '' + systemd_user_services_dir = get_option('systemd-user-unit-dir') +endif + +install_data( + sources : ['pipewire.socket', 'pipewire-pulse.socket'], + install_dir : systemd_user_services_dir) + +systemd_config = configuration_data() +systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') +systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse') + +configure_file(input : 'pipewire.service.in', + output : 'pipewire.service', + configuration : systemd_config, + install_dir : systemd_user_services_dir) + +configure_file(input : 'pipewire-pulse.service.in', + output : 'pipewire-pulse.service', + configuration : systemd_config, + install_dir : systemd_user_services_dir) + +configure_file(input : 'filter-chain.service.in', + output : 'filter-chain.service', + configuration : systemd_config, + install_dir : systemd_user_services_dir) diff --git a/src/daemon/systemd/user/pipewire-pulse.service.in b/src/daemon/systemd/user/pipewire-pulse.service.in new file mode 100644 index 0000000..73d22e5 --- /dev/null +++ b/src/daemon/systemd/user/pipewire-pulse.service.in @@ -0,0 +1,36 @@ +[Unit] +Description=PipeWire PulseAudio + +# We require pipewire-pulse.socket to be active before starting the daemon, because +# while it is possible to use the service without the socket, it is not clear +# why it would be desirable. +# +# A user installing pipewire and doing `systemctl --user start pipewire-pulse` +# will not get the socket started, which might be confusing and problematic if +# the server is to be restarted later on, as the client autospawn feature +# might kick in. Also, a start of the socket unit will fail, adding to the +# confusion. +# +# After=pipewire-pulse.socket is not needed, as it is already implicit in the +# socket-service relationship, see systemd.socket(5). +Requires=pipewire-pulse.socket +ConditionUser=!root +Wants=pipewire.service pipewire-session-manager.service +After=pipewire.service pipewire-session-manager.service +Conflicts=pulseaudio.service + +[Service] +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +RestrictNamespaces=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +Type=simple +ExecStart=@PW_PULSE_BINARY@ +Restart=on-failure +Slice=session.slice + +[Install] +Also=pipewire-pulse.socket +WantedBy=default.target diff --git a/src/daemon/systemd/user/pipewire-pulse.socket b/src/daemon/systemd/user/pipewire-pulse.socket new file mode 100644 index 0000000..1ae5eda --- /dev/null +++ b/src/daemon/systemd/user/pipewire-pulse.socket @@ -0,0 +1,11 @@ +[Unit] +Description=PipeWire PulseAudio +ConditionUser=!root +Conflicts=pulseaudio.socket + +[Socket] +Priority=6 +ListenStream=%t/pulse/native + +[Install] +WantedBy=sockets.target diff --git a/src/daemon/systemd/user/pipewire.service.in b/src/daemon/systemd/user/pipewire.service.in new file mode 100644 index 0000000..b9b1373 --- /dev/null +++ b/src/daemon/systemd/user/pipewire.service.in @@ -0,0 +1,32 @@ +[Unit] +Description=PipeWire Multimedia Service + +# We require pipewire.socket to be active before starting the daemon, because +# while it is possible to use the service without the socket, it is not clear +# why it would be desirable. +# +# A user installing pipewire and doing `systemctl --user start pipewire` +# will not get the socket started, which might be confusing and problematic if +# the server is to be restarted later on, as the client autospawn feature +# might kick in. Also, a start of the socket unit will fail, adding to the +# confusion. +# +# After=pipewire.socket is not needed, as it is already implicit in the +# socket-service relationship, see systemd.socket(5). +Requires=pipewire.socket + +[Service] +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +RestrictNamespaces=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +Type=simple +ExecStart=@PW_BINARY@ +Restart=on-failure +Slice=session.slice + +[Install] +Also=pipewire.socket +WantedBy=default.target diff --git a/src/daemon/systemd/user/pipewire.socket b/src/daemon/systemd/user/pipewire.socket new file mode 100644 index 0000000..232bbb8 --- /dev/null +++ b/src/daemon/systemd/user/pipewire.socket @@ -0,0 +1,9 @@ +[Unit] +Description=PipeWire Multimedia System Socket + +[Socket] +Priority=6 +ListenStream=%t/pipewire-0 + +[Install] +WantedBy=sockets.target |