From 1dfb4ab77f7b049d829a504f3be997d6aea8c854 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 21 Nov 2025 01:33:38 +0200 Subject: [PATCH] Make audio sample rate user-configurable - Add sample rate dropdown in UI with Opus-supported rates (8k/12k/16k/24k/48kHz) - Add sampleRate parameter to setAudioConfig RPC handler - Validate sample rate is one of the 5 Opus-compatible values - Configuration takes effect on next audio restart (Apply button) --- jsonrpc.go | 9 ++++-- ui/src/routes/devices.$id.settings.audio.tsx | 29 ++++++++++---------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/jsonrpc.go b/jsonrpc.go index 6dac64c2..0865fe6d 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -1000,7 +1000,7 @@ func rpcGetAudioConfig() (AudioConfigResponse, error) { }, nil } -func rpcSetAudioConfig(bitrate int, complexity int, dtxEnabled bool, fecEnabled bool, bufferPeriods int, packetLossPerc int) error { +func rpcSetAudioConfig(bitrate int, complexity int, dtxEnabled bool, fecEnabled bool, bufferPeriods int, sampleRate int, packetLossPerc int) error { ensureConfigLoaded() if bitrate < 64 || bitrate > 256 { @@ -1012,6 +1012,10 @@ func rpcSetAudioConfig(bitrate int, complexity int, dtxEnabled bool, fecEnabled if bufferPeriods < 2 || bufferPeriods > 24 { return fmt.Errorf("buffer periods must be between 2 and 24") } + validSampleRates := map[int]bool{8000: true, 12000: true, 16000: true, 24000: true, 48000: true} + if !validSampleRates[sampleRate] { + return fmt.Errorf("sample rate must be one of: 8000, 12000, 16000, 24000, 48000 Hz") + } if packetLossPerc < 0 || packetLossPerc > 100 { return fmt.Errorf("packet loss percentage must be between 0 and 100") } @@ -1021,6 +1025,7 @@ func rpcSetAudioConfig(bitrate int, complexity int, dtxEnabled bool, fecEnabled config.AudioDTXEnabled = dtxEnabled config.AudioFECEnabled = fecEnabled config.AudioBufferPeriods = bufferPeriods + config.AudioSampleRate = sampleRate config.AudioPacketLossPerc = packetLossPerc return SaveConfig() @@ -1375,7 +1380,7 @@ var rpcHandlers = map[string]RPCHandler{ "setAudioOutputSource": {Func: rpcSetAudioOutputSource, Params: []string{"source"}}, "refreshHdmiConnection": {Func: rpcRefreshHdmiConnection}, "getAudioConfig": {Func: rpcGetAudioConfig}, - "setAudioConfig": {Func: rpcSetAudioConfig, Params: []string{"bitrate", "complexity", "dtxEnabled", "fecEnabled", "bufferPeriods", "packetLossPerc"}}, + "setAudioConfig": {Func: rpcSetAudioConfig, Params: []string{"bitrate", "complexity", "dtxEnabled", "fecEnabled", "bufferPeriods", "sampleRate", "packetLossPerc"}}, "restartAudioOutput": {Func: rpcRestartAudioOutput}, "getAudioInputAutoEnable": {Func: rpcGetAudioInputAutoEnable}, "setAudioInputAutoEnable": {Func: rpcSetAudioInputAutoEnable, Params: []string{"enabled"}}, diff --git a/ui/src/routes/devices.$id.settings.audio.tsx b/ui/src/routes/devices.$id.settings.audio.tsx index afbc23b1..2a6909cc 100644 --- a/ui/src/routes/devices.$id.settings.audio.tsx +++ b/ui/src/routes/devices.$id.settings.audio.tsx @@ -144,6 +144,7 @@ export default function SettingsAudioRoute() { dtxEnabled: audioDTXEnabled, fecEnabled: audioFECEnabled, bufferPeriods: audioBufferPeriods, + sampleRate: audioSampleRate, packetLossPerc: audioPacketLossPerc, }); @@ -153,12 +154,12 @@ export default function SettingsAudioRoute() { send("setAudioConfig", config, (resp: JsonRpcResponse) => { if (handleRpcError(resp)) return; - // Update all state values from the config setAudioBitrate(config.bitrate); setAudioComplexity(config.complexity); setAudioDTXEnabled(config.dtxEnabled); setAudioFECEnabled(config.fecEnabled); setAudioBufferPeriods(config.bufferPeriods); + setAudioSampleRate(config.sampleRate); setAudioPacketLossPerc(config.packetLossPerc); notifications.success(m.audio_settings_config_updated()); }); @@ -292,20 +293,18 @@ export default function SettingsAudioRoute() { title={m.audio_settings_sample_rate_title()} description={m.audio_settings_sample_rate_description()} > -
- {(() => { - const rateMap: Record = { - 32000: "32 kHz", - 44100: "44.1 kHz", - 48000: "48 kHz", - 96000: "96 kHz" - }; - return rateMap[audioSampleRate] || `${audioSampleRate} Hz`; - })()} - - (auto-detected from source) - -
+ handleAudioConfigChange({ sampleRate: parseInt(e.target.value) })} + />