Audio Router
This commit is contained in:
+56
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user