feat: Optimize audio quality and default to USB audio

Audio quality improvements:
- Enable constrained VBR to prevent bitrate starvation at low volumes
- Increase Opus complexity from 2 to 5 for better quality
- Enable DTX for bandwidth optimization
- Enable FEC (Forward Error Correction)
- Add DTX and FEC signaling in SDP (usedtx=1;useinbandfec=1)

Default configuration changes:
- Change default audio output source from HDMI to USB
- Enable USB Audio device by default
- USB audio works on current stable image (HDMI requires newer device tree)

These changes fix crackling issues at low volumes and provide better
overall audio quality for both USB and HDMI audio paths.
This commit is contained in:
Alex P 2025-10-07 01:38:42 +03:00
parent 19fe908426
commit 9c72db913b
4 changed files with 11 additions and 10 deletions

View File

@ -160,10 +160,11 @@ var defaultConfig = &Config{
RelativeMouse: true, RelativeMouse: true,
Keyboard: true, Keyboard: true,
MassStorage: true, MassStorage: true,
Audio: true,
}, },
NetworkConfig: &network.NetworkConfig{}, NetworkConfig: &network.NetworkConfig{},
DefaultLogLevel: "INFO", DefaultLogLevel: "INFO",
AudioOutputSource: "hdmi", AudioOutputSource: "usb",
} }
var ( var (

View File

@ -55,20 +55,20 @@ static uint8_t channels = 2;
static uint16_t frame_size = 960; // 20ms frames at 48kHz static uint16_t frame_size = 960; // 20ms frames at 48kHz
static uint32_t opus_bitrate = 128000; static uint32_t opus_bitrate = 128000;
static uint8_t opus_complexity = 5; // Higher complexity for better quality on RV1106 static uint8_t opus_complexity = 5; // Higher complexity for better quality
static uint16_t max_packet_size = 1500; static uint16_t max_packet_size = 1500;
// Opus encoder constants (hardcoded for production) // Opus encoder constants (hardcoded for production)
#define OPUS_VBR 1 // VBR enabled #define OPUS_VBR 1 // VBR enabled
#define OPUS_VBR_CONSTRAINT 0 // Unconstrained VBR (better for low-volume signals) #define OPUS_VBR_CONSTRAINT 1 // Constrained VBR (prevents bitrate starvation at low volumes)
#define OPUS_SIGNAL_TYPE 3002 // OPUS_SIGNAL_MUSIC (better transient handling) #define OPUS_SIGNAL_TYPE 3002 // OPUS_SIGNAL_MUSIC (better transient handling)
#define OPUS_BANDWIDTH 1104 // OPUS_BANDWIDTH_SUPERWIDEBAND (16kHz, better quality at 128kbps) #define OPUS_BANDWIDTH 1104 // OPUS_BANDWIDTH_SUPERWIDEBAND (16kHz)
#define OPUS_DTX 0 // DTX disabled (prevents audio drops) #define OPUS_DTX 1 // DTX enabled (bandwidth optimization)
#define OPUS_LSB_DEPTH 16 // 16-bit depth #define OPUS_LSB_DEPTH 16 // 16-bit depth
// ALSA retry configuration // ALSA retry configuration
static uint32_t sleep_microseconds = 1000; static uint32_t sleep_microseconds = 1000;
static uint32_t sleep_milliseconds = 1; // Precomputed: sleep_microseconds / 1000 static uint32_t sleep_milliseconds = 1;
static uint8_t max_attempts_global = 5; static uint8_t max_attempts_global = 5;
static uint32_t max_backoff_us_global = 500000; static uint32_t max_backoff_us_global = 500000;
@ -373,7 +373,7 @@ int jetkvm_audio_capture_init() {
opus_encoder_ctl(encoder, OPUS_SET_DTX(OPUS_DTX)); opus_encoder_ctl(encoder, OPUS_SET_DTX(OPUS_DTX));
opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(OPUS_LSB_DEPTH)); opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(OPUS_LSB_DEPTH));
opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(0)); opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(1));
opus_encoder_ctl(encoder, OPUS_SET_PACKET_LOSS_PERC(20)); opus_encoder_ctl(encoder, OPUS_SET_PACKET_LOSS_PERC(20));
capture_initialized = 1; capture_initialized = 1;

View File

@ -10,7 +10,7 @@ import notifications from "@/notifications";
export default function AudioPopover() { export default function AudioPopover() {
const { send } = useJsonRpc(); const { send } = useJsonRpc();
const [audioOutputSource, setAudioOutputSource] = useState<string>("hdmi"); const [audioOutputSource, setAudioOutputSource] = useState<string>("usb");
const [audioOutputEnabled, setAudioOutputEnabled] = useState<boolean>(true); const [audioOutputEnabled, setAudioOutputEnabled] = useState<boolean>(true);
const [audioInputEnabled, setAudioInputEnabled] = useState<boolean>(true); const [audioInputEnabled, setAudioInputEnabled] = useState<boolean>(true);
const [usbAudioEnabled, setUsbAudioEnabled] = useState<boolean>(false); const [usbAudioEnabled, setUsbAudioEnabled] = useState<boolean>(false);

View File

@ -191,7 +191,7 @@ export default function KvmIdRoute() {
console.warn("[SDP] Opus 48kHz stereo not found in answer - stereo may not work"); console.warn("[SDP] Opus 48kHz stereo not found in answer - stereo may not work");
} else { } else {
const pt = opusMatch[1]; const pt = opusMatch[1];
const stereoParams = 'stereo=1;sprop-stereo=1;maxaveragebitrate=128000'; const stereoParams = 'stereo=1;sprop-stereo=1;maxaveragebitrate=128000;usedtx=1;useinbandfec=1';
const fmtpRegex = new RegExp(`a=fmtp:${pt}\\s+(.+)`, 'i'); const fmtpRegex = new RegExp(`a=fmtp:${pt}\\s+(.+)`, 'i');
const fmtpMatch = remoteDescription.sdp.match(fmtpRegex); const fmtpMatch = remoteDescription.sdp.match(fmtpRegex);
@ -463,7 +463,7 @@ export default function KvmIdRoute() {
console.warn("[SDP] Opus 48kHz stereo not found in offer - stereo may not work"); console.warn("[SDP] Opus 48kHz stereo not found in offer - stereo may not work");
} else { } else {
const pt = opusMatch[1]; const pt = opusMatch[1];
const stereoParams = 'stereo=1;sprop-stereo=1;maxaveragebitrate=128000'; const stereoParams = 'stereo=1;sprop-stereo=1;maxaveragebitrate=128000;usedtx=1;useinbandfec=1';
const fmtpRegex = new RegExp(`a=fmtp:${pt}\\s+(.+)`, 'i'); const fmtpRegex = new RegExp(`a=fmtp:${pt}\\s+(.+)`, 'i');
const fmtpMatch = offer.sdp.match(fmtpRegex); const fmtpMatch = offer.sdp.match(fmtpRegex);