Skip to content

Instantly share code, notes, and snippets.

@zandeez
Created August 29, 2023 08:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zandeez/27939a66c067a25a8a3d61532c29f431 to your computer and use it in GitHub Desktop.
Save zandeez/27939a66c067a25a8a3d61532c29f431 to your computer and use it in GitHub Desktop.
Pipewire / Wireplumber Scripts to use with OBS using Virtual audio devices to isolate Game and Voice chat audio
context.objects = [
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = Game
media.class = Audio/Sink
device.icon_name = audio-card-analog-pci
object.linger = 1
stream.props = {
audio.position = [ FL FR ]
}
}
}
{ factory = adapter
args = {
factory.name = support.null-audio-sink
node.name = Chat
media.class = Audio/Sink
device.icon_name = audio-card-analog-pci
object.linger = 1
stream.props = {
audio.position = [ FL FR ]
}
}
}
]
load_script("policy-obs.lua")

Wireplumber / Pipewire configuration for OBS recording with separate Game and Chat audio channels

This script monitors of device connection, so unplugging and re-plugging a device will automatically re-connect the links, or monitoring for application start if you add the applications to the node_map.

(If any of these directories don't exist, create them)

  1. Put 10-obs.conf in ~/.config/pipewire/pipewire.conf.d/
  2. Put policy-obs.lua in ~/.config/wireplumber/scripts/
  3. Put 51-obs.lua in ~/.config/wireplumber/main.lua.d/
  4. Modify the node_map table to include the links you need for your specific devices
  5. Configure OBS to record Game and Chat for Desktop Audio and Desktop Audio 2
  6. Configure your microphone as an input in OBS
  7. If playing games on your PC, use either PULSE_SINK=Game in the launch options or create the application itself in the map table
#!/usr/bin/wpexec
-- .config/wireplumber/scripts/policy-obs.lua
-- Table of devices, and the outgoing links to other devices.
-- All links are listed twice becuase we can't guarantee the order that devices will appear.
-- I have an Elgato HD60 S+ that I use as an input (first item) and output to a Corsair USB headset. Update this table
-- as required.
node_map = {
["alsa_input.usb-Elgato_Game_Capture_HD60_S__0005C0D38A000-03.iec958-stereo"] = {
["capture_FL"] = { "Game:playback_FL" },
["capture_FR"] = { "Game:playback_FR" },
},
["Game"] = {
["playback_FL"] = { "alsa_input.usb-Elgato_Game_Capture_HD60_S__0005C0D38A000-03.iec958-stereo:capture_FL" },
["playback_FR"] = { "alsa_input.usb-Elgato_Game_Capture_HD60_S__0005C0D38A000-03.iec958-stereo:capture_FR" },
["monitor_FL"] = { "alsa_output.usb-Corsair_CORSAIR_HS70_Pro_Wireless_Gaming_Headset-00.iec958-stereo:playback_FL" },
["monitor_FR"] = { "alsa_output.usb-Corsair_CORSAIR_HS70_Pro_Wireless_Gaming_Headset-00.iec958-stereo:playback_FR" },
},
["Chat"] = {
["monitor_FL"] = { "alsa_output.usb-Corsair_CORSAIR_HS70_Pro_Wireless_Gaming_Headset-00.iec958-stereo:playback_FL" },
["monitor_FR"] = { "alsa_output.usb-Corsair_CORSAIR_HS70_Pro_Wireless_Gaming_Headset-00.iec958-stereo:playback_FR" },
},
["alsa_output.usb-Corsair_CORSAIR_HS70_Pro_Wireless_Gaming_Headset-00.iec958-stereo"] = {
["playback_FL"] = { "Game:monitor_FL", "Chat:monitor_FL" },
["playback_FR"] = { "Game:monitor_FR", "Chat:monitor_FR" },
},
}
-- Local table of port names to port IDs for linking
ports = {}
-- Table of managers to keep them alive
managers = {}
-- Iterate over the map
for node_name, node_data in pairs(node_map) do
-- Create an object manager for this node
local manager = ObjectManager {
Interest {
type = "node",
Constraint { "node.name", "equals", node_name },
},
}
managers[node_name] = manager
-- Add a handler for when the device is connected
manager:connect("object-added", function (om, node)
print("Device " .. node_name .. " found!")
-- Iterate over the port list in the node_map table for this node
for port_name, targets in pairs(node_data) do
-- Set up the name we'll use for indexing in the ports table
local port_key = node_name .. ":" .. port_name
-- Create an object manager for this port
local port_om = ObjectManager {
Interest {
type = "port",
Constraint { "node.id", "equals", node.properties["object.id"] },
Constraint { "port.name", "equals", port_name },
},
}
managers[port_key] = port_om
print("Adding handler for port " .. port_key)
-- Set up a handler for when the ports become active
port_om:connect("object-added", function(om, port)
print("Adding port " .. port_key .. " with ID " .. port["bound-id"])
-- Save the port ID in the table
ports[port_key] = port["bound-id"]
-- Iterate of the targets for this port and create the links
for i, target in pairs(targets) do
local target_port = ports[target]
-- The remote port might not have been discovered yet
if target_port then
print("Linking port " .. port_key .. " to " .. target)
-- Try adding the link both ways as we might be coming from the destination device. Anything incorrect will silently fail.
link = Link("link-factory", {
["link.output.port"] = target_port,
["link.input.port"] = port["bound-id"],
["object.linger"] = 1,
})
link:activate(Features.ALL)
link = Link("link-factory", {
["link.output.port"] = port["bound-id"],
["link.input.port"] = target_port,
["object.linger"] = 1,
})
link:activate(Features.ALL)
else
print("Target port " .. target .. " not found")
end
end
end)
port_om:activate()
end
end)
manager:activate()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment