diff --git a/audio.go b/audio.go index a827358f..15bb7a94 100644 --- a/audio.go +++ b/audio.go @@ -49,28 +49,24 @@ func initAudio() { func getAudioConfig() audio.AudioConfig { cfg := audio.DefaultAudioConfig() - // Apply bitrate (64-256 kbps) if config.AudioBitrate >= 64 && config.AudioBitrate <= 256 { cfg.Bitrate = uint16(config.AudioBitrate) } else if config.AudioBitrate != 0 { audioLogger.Warn().Int("bitrate", config.AudioBitrate).Msg("Invalid audio bitrate, using default") } - // Apply complexity (0-10) if config.AudioComplexity >= 0 && config.AudioComplexity <= 10 { cfg.Complexity = uint8(config.AudioComplexity) } else if config.AudioComplexity != 0 { audioLogger.Warn().Int("complexity", config.AudioComplexity).Msg("Invalid audio complexity, using default") } - // Apply buffer periods (2-24) if config.AudioBufferPeriods >= 2 && config.AudioBufferPeriods <= 24 { cfg.BufferPeriods = uint8(config.AudioBufferPeriods) } else if config.AudioBufferPeriods != 0 { audioLogger.Warn().Int("buffer_periods", config.AudioBufferPeriods).Msg("Invalid buffer periods, using default") } - // Apply sample rate (Opus supports: 8k, 12k, 16k, 24k, 48k) switch config.AudioSampleRate { case 8000, 12000, 16000, 24000, 48000: cfg.SampleRate = uint32(config.AudioSampleRate) @@ -80,7 +76,6 @@ func getAudioConfig() audio.AudioConfig { } } - // Apply packet loss percentage (0-100) if config.AudioPacketLossPerc >= 0 && config.AudioPacketLossPerc <= 100 { cfg.PacketLossPerc = uint8(config.AudioPacketLossPerc) } else if config.AudioPacketLossPerc != 0 { @@ -118,7 +113,6 @@ func startAudio() error { inputErr = startInputAudioUnderMutex(getAlsaDevice("usb")) } - // Simplified error handling - both errors are worth reporting if outputErr != nil || inputErr != nil { if outputErr != nil && inputErr != nil { return fmt.Errorf("audio start failed - output: %w, input: %v", outputErr, inputErr) diff --git a/internal/audio/cgo_source.go b/internal/audio/cgo_source.go index a94a6c91..29a4a577 100644 --- a/internal/audio/cgo_source.go +++ b/internal/audio/cgo_source.go @@ -83,7 +83,13 @@ func (c *CgoSource) Connect() error { func (c *CgoSource) connectOutput() error { os.Setenv("ALSA_CAPTURE_DEVICE", c.alsaDevice) - frameSize := uint16(c.config.SampleRate * 20 / 1000) + // USB Audio Gadget (hw:1,0) only supports 48kHz + // For HDMI (hw:0,0), use configured sample rate + sampleRate := c.config.SampleRate + if c.alsaDevice == "hw:1,0" { + sampleRate = 48000 + } + frameSize := uint16(sampleRate * 20 / 1000) c.logger.Debug(). Uint16("bitrate_kbps", c.config.Bitrate). @@ -91,7 +97,7 @@ func (c *CgoSource) connectOutput() error { Bool("dtx", c.config.DTXEnabled). Bool("fec", c.config.FECEnabled). Uint8("buffer_periods", c.config.BufferPeriods). - Uint32("sample_rate", c.config.SampleRate). + Uint32("sample_rate", sampleRate). Uint16("frame_size", frameSize). Uint8("packet_loss_perc", c.config.PacketLossPerc). Msg("Initializing audio capture") @@ -99,7 +105,7 @@ func (c *CgoSource) connectOutput() error { C.update_audio_constants( C.uint(uint32(c.config.Bitrate)*1000), C.uchar(c.config.Complexity), - C.uint(c.config.SampleRate), + C.uint(sampleRate), C.uchar(2), C.ushort(frameSize), C.ushort(1500), @@ -125,10 +131,13 @@ func (c *CgoSource) connectOutput() error { func (c *CgoSource) connectInput() error { os.Setenv("ALSA_PLAYBACK_DEVICE", c.alsaDevice) - frameSize := uint16(c.config.SampleRate * 20 / 1000) + // USB Audio Gadget (hw:1,0) is hardcoded to 48kHz in usbgadget/config.go + // Always use 48kHz for input path regardless of UI configuration + const inputSampleRate = 48000 + frameSize := uint16(inputSampleRate * 20 / 1000) C.update_audio_decoder_constants( - C.uint(c.config.SampleRate), + C.uint(inputSampleRate), C.uchar(1), C.ushort(frameSize), C.ushort(1500), diff --git a/ui/src/routes/devices.$id.settings.audio.tsx b/ui/src/routes/devices.$id.settings.audio.tsx index 2a6909cc..c33d4752 100644 --- a/ui/src/routes/devices.$id.settings.audio.tsx +++ b/ui/src/routes/devices.$id.settings.audio.tsx @@ -20,18 +20,15 @@ interface AudioConfigResult { packet_loss_perc: number; } -// UI display defaults - used to mark default options in dropdown menus -// Note: These should match backend defaults in config.go, but are fetched dynamically from API const AUDIO_DEFAULTS = { bitrate: 192, complexity: 8, - packetLossPerc: 20, // Backend default is 20, not 0 + packetLossPerc: 20, } as const; export default function SettingsAudioRoute() { const { send } = useJsonRpc(); - // Helper function to handle RPC errors consistently const handleRpcError = (resp: JsonRpcResponse, defaultMsg?: string) => { if ("error" in resp) { notifications.error(String(resp.error.data || defaultMsg || m.unknown_error())); @@ -64,7 +61,6 @@ export default function SettingsAudioRoute() { } = useSettingsStore(); useEffect(() => { - // Load boolean settings send("getAudioOutputEnabled", {}, (resp: JsonRpcResponse) => { if ("error" in resp) return; setAudioOutputEnabled(resp.result as boolean); @@ -80,7 +76,6 @@ export default function SettingsAudioRoute() { setAudioOutputSource(resp.result as string); }); - // Load complex audio configuration send("getAudioConfig", {}, (resp: JsonRpcResponse) => { if ("error" in resp) return; const config = resp.result as AudioConfigResult; @@ -137,7 +132,6 @@ export default function SettingsAudioRoute() { }); }; - // Create a configuration object from current state const getCurrentConfig = () => ({ bitrate: audioBitrate, complexity: audioComplexity,