mirror of https://github.com/jetkvm/kvm.git
Fix USB Audio Gadget sample rate constraints
USB Audio Gadget (hw:1,0) hardware only supports 48kHz for both capture and playback due to configfs p_srate/c_srate being hardcoded. This commit ensures both audio paths respect this hardware limitation: - Output path: Force 48kHz when using hw:1,0, allow configurable rates for HDMI - Input path: Always use 48kHz regardless of UI configuration - Calculate frame size dynamically based on actual sample rate used Also removes redundant comments that don't add debugging or maintainability value.
This commit is contained in:
parent
57baa14ee6
commit
2040db6094
6
audio.go
6
audio.go
|
|
@ -49,28 +49,24 @@ func initAudio() {
|
||||||
func getAudioConfig() audio.AudioConfig {
|
func getAudioConfig() audio.AudioConfig {
|
||||||
cfg := audio.DefaultAudioConfig()
|
cfg := audio.DefaultAudioConfig()
|
||||||
|
|
||||||
// Apply bitrate (64-256 kbps)
|
|
||||||
if config.AudioBitrate >= 64 && config.AudioBitrate <= 256 {
|
if config.AudioBitrate >= 64 && config.AudioBitrate <= 256 {
|
||||||
cfg.Bitrate = uint16(config.AudioBitrate)
|
cfg.Bitrate = uint16(config.AudioBitrate)
|
||||||
} else if config.AudioBitrate != 0 {
|
} else if config.AudioBitrate != 0 {
|
||||||
audioLogger.Warn().Int("bitrate", config.AudioBitrate).Msg("Invalid audio bitrate, using default")
|
audioLogger.Warn().Int("bitrate", config.AudioBitrate).Msg("Invalid audio bitrate, using default")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply complexity (0-10)
|
|
||||||
if config.AudioComplexity >= 0 && config.AudioComplexity <= 10 {
|
if config.AudioComplexity >= 0 && config.AudioComplexity <= 10 {
|
||||||
cfg.Complexity = uint8(config.AudioComplexity)
|
cfg.Complexity = uint8(config.AudioComplexity)
|
||||||
} else if config.AudioComplexity != 0 {
|
} else if config.AudioComplexity != 0 {
|
||||||
audioLogger.Warn().Int("complexity", config.AudioComplexity).Msg("Invalid audio complexity, using default")
|
audioLogger.Warn().Int("complexity", config.AudioComplexity).Msg("Invalid audio complexity, using default")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply buffer periods (2-24)
|
|
||||||
if config.AudioBufferPeriods >= 2 && config.AudioBufferPeriods <= 24 {
|
if config.AudioBufferPeriods >= 2 && config.AudioBufferPeriods <= 24 {
|
||||||
cfg.BufferPeriods = uint8(config.AudioBufferPeriods)
|
cfg.BufferPeriods = uint8(config.AudioBufferPeriods)
|
||||||
} else if config.AudioBufferPeriods != 0 {
|
} else if config.AudioBufferPeriods != 0 {
|
||||||
audioLogger.Warn().Int("buffer_periods", config.AudioBufferPeriods).Msg("Invalid buffer periods, using default")
|
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 {
|
switch config.AudioSampleRate {
|
||||||
case 8000, 12000, 16000, 24000, 48000:
|
case 8000, 12000, 16000, 24000, 48000:
|
||||||
cfg.SampleRate = uint32(config.AudioSampleRate)
|
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 {
|
if config.AudioPacketLossPerc >= 0 && config.AudioPacketLossPerc <= 100 {
|
||||||
cfg.PacketLossPerc = uint8(config.AudioPacketLossPerc)
|
cfg.PacketLossPerc = uint8(config.AudioPacketLossPerc)
|
||||||
} else if config.AudioPacketLossPerc != 0 {
|
} else if config.AudioPacketLossPerc != 0 {
|
||||||
|
|
@ -118,7 +113,6 @@ func startAudio() error {
|
||||||
inputErr = startInputAudioUnderMutex(getAlsaDevice("usb"))
|
inputErr = startInputAudioUnderMutex(getAlsaDevice("usb"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simplified error handling - both errors are worth reporting
|
|
||||||
if outputErr != nil || inputErr != nil {
|
if outputErr != nil || inputErr != nil {
|
||||||
if outputErr != nil && inputErr != nil {
|
if outputErr != nil && inputErr != nil {
|
||||||
return fmt.Errorf("audio start failed - output: %w, input: %v", outputErr, inputErr)
|
return fmt.Errorf("audio start failed - output: %w, input: %v", outputErr, inputErr)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,13 @@ func (c *CgoSource) Connect() error {
|
||||||
func (c *CgoSource) connectOutput() error {
|
func (c *CgoSource) connectOutput() error {
|
||||||
os.Setenv("ALSA_CAPTURE_DEVICE", c.alsaDevice)
|
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().
|
c.logger.Debug().
|
||||||
Uint16("bitrate_kbps", c.config.Bitrate).
|
Uint16("bitrate_kbps", c.config.Bitrate).
|
||||||
|
|
@ -91,7 +97,7 @@ func (c *CgoSource) connectOutput() error {
|
||||||
Bool("dtx", c.config.DTXEnabled).
|
Bool("dtx", c.config.DTXEnabled).
|
||||||
Bool("fec", c.config.FECEnabled).
|
Bool("fec", c.config.FECEnabled).
|
||||||
Uint8("buffer_periods", c.config.BufferPeriods).
|
Uint8("buffer_periods", c.config.BufferPeriods).
|
||||||
Uint32("sample_rate", c.config.SampleRate).
|
Uint32("sample_rate", sampleRate).
|
||||||
Uint16("frame_size", frameSize).
|
Uint16("frame_size", frameSize).
|
||||||
Uint8("packet_loss_perc", c.config.PacketLossPerc).
|
Uint8("packet_loss_perc", c.config.PacketLossPerc).
|
||||||
Msg("Initializing audio capture")
|
Msg("Initializing audio capture")
|
||||||
|
|
@ -99,7 +105,7 @@ func (c *CgoSource) connectOutput() error {
|
||||||
C.update_audio_constants(
|
C.update_audio_constants(
|
||||||
C.uint(uint32(c.config.Bitrate)*1000),
|
C.uint(uint32(c.config.Bitrate)*1000),
|
||||||
C.uchar(c.config.Complexity),
|
C.uchar(c.config.Complexity),
|
||||||
C.uint(c.config.SampleRate),
|
C.uint(sampleRate),
|
||||||
C.uchar(2),
|
C.uchar(2),
|
||||||
C.ushort(frameSize),
|
C.ushort(frameSize),
|
||||||
C.ushort(1500),
|
C.ushort(1500),
|
||||||
|
|
@ -125,10 +131,13 @@ func (c *CgoSource) connectOutput() error {
|
||||||
func (c *CgoSource) connectInput() error {
|
func (c *CgoSource) connectInput() error {
|
||||||
os.Setenv("ALSA_PLAYBACK_DEVICE", c.alsaDevice)
|
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.update_audio_decoder_constants(
|
||||||
C.uint(c.config.SampleRate),
|
C.uint(inputSampleRate),
|
||||||
C.uchar(1),
|
C.uchar(1),
|
||||||
C.ushort(frameSize),
|
C.ushort(frameSize),
|
||||||
C.ushort(1500),
|
C.ushort(1500),
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,15 @@ interface AudioConfigResult {
|
||||||
packet_loss_perc: number;
|
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 = {
|
const AUDIO_DEFAULTS = {
|
||||||
bitrate: 192,
|
bitrate: 192,
|
||||||
complexity: 8,
|
complexity: 8,
|
||||||
packetLossPerc: 20, // Backend default is 20, not 0
|
packetLossPerc: 20,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default function SettingsAudioRoute() {
|
export default function SettingsAudioRoute() {
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
|
|
||||||
// Helper function to handle RPC errors consistently
|
|
||||||
const handleRpcError = (resp: JsonRpcResponse, defaultMsg?: string) => {
|
const handleRpcError = (resp: JsonRpcResponse, defaultMsg?: string) => {
|
||||||
if ("error" in resp) {
|
if ("error" in resp) {
|
||||||
notifications.error(String(resp.error.data || defaultMsg || m.unknown_error()));
|
notifications.error(String(resp.error.data || defaultMsg || m.unknown_error()));
|
||||||
|
|
@ -64,7 +61,6 @@ export default function SettingsAudioRoute() {
|
||||||
} = useSettingsStore();
|
} = useSettingsStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load boolean settings
|
|
||||||
send("getAudioOutputEnabled", {}, (resp: JsonRpcResponse) => {
|
send("getAudioOutputEnabled", {}, (resp: JsonRpcResponse) => {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
setAudioOutputEnabled(resp.result as boolean);
|
setAudioOutputEnabled(resp.result as boolean);
|
||||||
|
|
@ -80,7 +76,6 @@ export default function SettingsAudioRoute() {
|
||||||
setAudioOutputSource(resp.result as string);
|
setAudioOutputSource(resp.result as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load complex audio configuration
|
|
||||||
send("getAudioConfig", {}, (resp: JsonRpcResponse) => {
|
send("getAudioConfig", {}, (resp: JsonRpcResponse) => {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
const config = resp.result as AudioConfigResult;
|
const config = resp.result as AudioConfigResult;
|
||||||
|
|
@ -137,7 +132,6 @@ export default function SettingsAudioRoute() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a configuration object from current state
|
|
||||||
const getCurrentConfig = () => ({
|
const getCurrentConfig = () => ({
|
||||||
bitrate: audioBitrate,
|
bitrate: audioBitrate,
|
||||||
complexity: audioComplexity,
|
complexity: audioComplexity,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue