Audio Router

This commit is contained in:
media
2026-06-28 14:28:25 -05:00
parent 42a504c77d
commit f60ec71e78
3 changed files with 257 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
mqtt:
host: 10.40.0.2
port: 1883
base_topic: audio/player
routes:
mixer_to_hdmi:
source: mixer
output: hdmi
mixer_to_analog:
source: mixer
output: analog
bgmusic_to_hdmi:
source: bgmusic
output: hdmi
bgmusic_to_analog:
source: bgmusic
output: analog
ports:
mixer:
left: Scarlett 2i2 3rd Gen Analog Stereo:capture_FL
right: Scarlett 2i2 3rd Gen Analog Stereo:capture_FR
bgmusic:
left: SnapBGM:monitor_FL
right: SnapBGM:monitor_FR
hdmi:
left: Built-in Audio Pro 3:playback_AUX0
right: Built-in Audio Pro 3:playback_AUX1
analog:
left: Built-in Audio Pro:playback_AUX0
right: Built-in Audio Pro:playback_AUX1
presets:
mute_all:
mixer_to_hdmi: false
mixer_to_analog: false
bgmusic_to_hdmi: false
bgmusic_to_analog: false
service:
mixer_to_hdmi: true
mixer_to_analog: false
bgmusic_to_hdmi: false
bgmusic_to_analog: false
announcements:
mixer_to_hdmi: false
mixer_to_analog: false
bgmusic_to_hdmi: true
bgmusic_to_analog: true
aux_only:
mixer_to_hdmi: false
mixer_to_analog: false
bgmusic_to_hdmi: false
bgmusic_to_analog: true
video:
mixer_to_hdmi: false
mixer_to_analog: false
bgmusic_to_hdmi: true
bgmusic_to_analog: false
+59
View File
@@ -0,0 +1,59 @@
attrs==22.2.0
Brotli==1.0.9
certifi==2022.9.24
cffi==1.15.1
chardet==5.1.0
charset-normalizer==3.0.1
crit==3.17.1
cupshelpers==1.0
dbus-python==1.3.2
distro==1.8.0
distro-info==1.5+deb12u1
docker==5.0.3
docker-compose==1.29.2
dockerpty==0.4.1
docopt==0.6.2
gyp==0.1
httplib2==0.20.4
idna==3.3
JACK-Client==0.5.3
jsonpointer==2.3
jsonschema==4.10.3
Mako==1.2.4.dev0
Markdown==3.4.1
MarkupSafe==2.1.2
mutagen==1.46.0
numpy==1.24.2
paho-mqtt==2.1.0
ply==3.11
protobuf==4.21.12
pycairo==1.20.1
pycparser==2.21
pycryptodomex==3.11.0
pycups==2.0.1
pycurl==7.45.2
Pygments==2.14.0
PyGObject==3.42.2
pyjack==0.3.2
pyparsing==3.0.9
pyrsistent==0.18.1
PySimpleSOAP==1.16.2
pysmbc==1.0.23
python-apt==2.6.0
python-debian==0.1.49
python-debianbts==4.0.1
python-dotenv==0.21.0
pyxattr==0.8.1
PyYAML==6.0.3
reportbug==12.0.0
requests==2.28.1
rfc3987==1.3.8
six==1.16.0
texttable==1.6.7
unattended-upgrades==0.1
uritemplate==4.1.1
urllib3==1.26.12
webcolors==1.11.1
websocket-client==1.2.3
websockets==10.4
yt-dlp==2025.9.26
Executable
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
import json
import signal
import time
import jack
import yaml
import paho.mqtt.client as mqtt
CONFIG_FILE = "config.yaml"
class JackRouter:
def __init__(self):
with open(CONFIG_FILE, "r") as f:
self.cfg = yaml.safe_load(f)
self.client = jack.Client("AudioRouter")
self.mode = "mute_all"
self.base_topic = self.cfg["mqtt"]["base_topic"].rstrip("/")
def connect(self, src, dst):
try:
if not self.client.get_all_connections(dst).count(src):
self.client.connect(src, dst)
print(f"Connected: {src} -> {dst}")
except jack.JackError as e:
print(f"Connect failed: {src} -> {dst}: {e}")
def disconnect(self, src, dst):
try:
self.client.disconnect(src, dst)
print(f"Disconnected: {src} -> {dst}")
except jack.JackError:
pass
def clear_routes(self):
all_routes = self.cfg["routes"]
for route in all_routes.values():
source = self.cfg["ports"][route["source"]]
output = self.cfg["ports"][route["output"]]
pairs = [
(source["left"], output["left"]),
(source["right"], output["right"]),
]
for src, dst in pairs:
self.disconnect(src, dst)
def routes_for_mode(self, mode):
preset = self.cfg["presets"].get(mode)
if preset is None:
return None
resolved_routes = []
for route_name, enabled in preset.items():
if not enabled:
continue
route = self.cfg["routes"][route_name]
source_name = route["source"]
output_name = route["output"]
source = self.cfg["ports"][source_name]
output = self.cfg["ports"][output_name]
resolved_routes.append((source["left"], output["left"]))
resolved_routes.append((source["right"], output["right"]))
return resolved_routes
def set_mode(self, mode):
routes = self.routes_for_mode(mode)
if routes is None:
print(f"Unknown mode: {mode}")
return False
print(f"Setting mode: {mode}")
self.clear_routes()
for src, dst in routes:
self.connect(src, dst)
self.mode = mode
return True
def state(self):
return {
"mode": self.mode,
"running": True,
"timestamp": int(time.time()),
}
def main():
router = JackRouter()
mqtt_cfg = router.cfg["mqtt"]
client = mqtt.Client()
def publish_state():
client.publish(f"{router.base_topic}/state", json.dumps(router.state()), retain=True)
client.publish(f"{router.base_topic}/mode/state", router.mode, retain=True)
def on_connect(client, userdata, flags, rc):
print(f"MQTT connected: {rc}")
client.subscribe(f"{router.base_topic}/mode/set")
router.set_mode("aux_only")
publish_state()
def on_message(client, userdata, msg):
mode = msg.payload.decode().strip()
print(f"MQTT received: {msg.topic} = {mode}")
if router.set_mode(mode):
publish_state()
def shutdown(*args):
print("Stopping router...")
router.set_mode("mute_all")
publish_state()
raise SystemExit
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
client.on_connect = on_connect
client.on_message = on_message
with router.client:
client.connect(mqtt_cfg["host"], int(mqtt_cfg["port"]), 60)
client.loop_forever()
if __name__ == "__main__":
main()