Compare commits

...

15 Commits

Author SHA1 Message Date
Alex 74ed4eb7b3
Merge 9c72db913b into 317218a682 2025-10-06 22:38:55 +00:00
Alex P 9c72db913b 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.
2025-10-07 01:38:42 +03:00
Alex P 19fe908426 refactor: Simplify audio implementation
Remove dynamic gain code and rely on Opus encoder quality improvements:
- Increase Opus complexity from 2 to 5 for better quality
- Change bandwidth from FULLBAND (20kHz) to SUPERWIDEBAND (16kHz) for better quality at 128kbps
- Disable FEC to allocate all bits to audio quality
- Increase ALSA buffer from 40ms to 80ms for stability

The dynamic gain code was adding complexity without solving the underlying
issue: TC358743 HDMI chip captures digital audio at whatever volume the
source outputs. Users should adjust volume at the source or in their browser.
2025-10-07 00:25:45 +03:00
Marc Brooks 317218a682
docs: debugging UI builds because of ui symlink (#873)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-04 12:05:14 +02:00
Aveline 62b8dee170
chore: downgrade gin to v1.10.1 (#869) 2025-10-03 08:48:51 +02:00
Alex bdd6f4247b
fix: segfault in cGo 2025-10-02 19:15:03 +02:00
dependabot[bot] 63aa940f42
build(deps): bump github.com/prometheus/client_golang (#851)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:44:24 +02:00
dependabot[bot] 043ef9ddfc
build(deps): bump github.com/gin-gonic/gin from 1.10.1 to 1.11.0 (#852)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:44:08 +02:00
Marc Brooks 437f0b854a
upgrade ui packages (#861) 2025-10-01 21:43:46 +02:00
dependabot[bot] a45d55123c
build(deps): bump github.com/prometheus/common from 0.66.0 to 0.66.1 (#855)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:42:08 +02:00
dependabot[bot] 213e750e04
build(deps): bump github.com/coder/websocket from 1.8.13 to 1.8.14 (#854)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:41:48 +02:00
dependabot[bot] 6dcb0286e3
build(deps): bump golang.org/x/net from 0.43.0 to 0.44.0 (#856)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:40:57 +02:00
dependabot[bot] 74ccca0b1a
build(deps): bump golang.org/x/crypto from 0.41.0 to 0.42.0 (#849)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:39:31 +02:00
dependabot[bot] 0ad435475b
build(deps): bump github.com/go-co-op/gocron/v2 from 2.16.5 to 2.16.6 (#859)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:39:15 +02:00
dependabot[bot] 23bf3978fa
build(deps): bump actions/setup-go from 5 to 6 (#848)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 21:35:35 +02:00
5 changed files with 12 additions and 77 deletions

View File

@ -80,7 +80,7 @@ func startAudioSubprocesses() error {
[]string{
"ALSA_CAPTURE_DEVICE=" + alsaDevice,
"OPUS_BITRATE=128000",
"OPUS_COMPLEXITY=2",
"OPUS_COMPLEXITY=5",
},
)

View File

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

View File

@ -55,20 +55,20 @@ static uint8_t channels = 2;
static uint16_t frame_size = 960; // 20ms frames at 48kHz
static uint32_t opus_bitrate = 128000;
static uint8_t opus_complexity = 2;
static uint8_t opus_complexity = 5; // Higher complexity for better quality
static uint16_t max_packet_size = 1500;
// Opus encoder constants (hardcoded for production)
#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_BANDWIDTH 1105 // OPUS_BANDWIDTH_FULLBAND (20kHz, enabled by 128kbps bitrate)
#define OPUS_DTX 0 // DTX disabled (prevents audio drops)
#define OPUS_BANDWIDTH 1104 // OPUS_BANDWIDTH_SUPERWIDEBAND (16kHz)
#define OPUS_DTX 1 // DTX enabled (bandwidth optimization)
#define OPUS_LSB_DEPTH 16 // 16-bit depth
// ALSA retry configuration
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 uint32_t max_backoff_us_global = 500000;
@ -283,7 +283,7 @@ static int configure_alsa_device(snd_pcm_t *handle, const char *device_name) {
err = snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, 0);
if (err < 0) return err;
snd_pcm_uframes_t buffer_size = period_size * 2; // Optimized: minimal buffer for low latency
snd_pcm_uframes_t buffer_size = period_size * 4; // 4 periods = 80ms buffer for stability
err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
if (err < 0) return err;
@ -471,72 +471,6 @@ retry_read:
simd_clear_samples_s16(&pcm_buffer[pcm_rc * channels], remaining_samples);
}
// Find peak amplitude with NEON SIMD
uint32_t total_samples = frame_size * channels;
int16x8_t vmax = vdupq_n_s16(0);
uint32_t i;
for (i = 0; i + 8 <= total_samples; i += 8) {
int16x8_t v = vld1q_s16(&pcm_buffer[i]);
int16x8_t vabs = vabsq_s16(v);
vmax = vmaxq_s16(vmax, vabs);
}
// Horizontal max reduction (manual for ARMv7)
int16x4_t vmax_low = vget_low_s16(vmax);
int16x4_t vmax_high = vget_high_s16(vmax);
int16x4_t vmax_reduced = vmax_s16(vmax_low, vmax_high);
vmax_reduced = vpmax_s16(vmax_reduced, vmax_reduced);
vmax_reduced = vpmax_s16(vmax_reduced, vmax_reduced);
int16_t peak = vget_lane_s16(vmax_reduced, 0);
// Handle remaining samples
for (; i < total_samples; i++) {
int16_t abs_val = (pcm_buffer[i] < 0) ? -pcm_buffer[i] : pcm_buffer[i];
if (abs_val > peak) peak = abs_val;
}
// Apply gain if signal is weak (below -18dB = 4096) but above noise floor
// Noise gate: only apply gain if peak > 256 (below this is likely just noise)
// Target: boost to ~50% of range (16384) to improve SNR
if (peak > 256 && peak < 4096) {
float gain = 16384.0f / peak;
if (gain > 8.0f) gain = 8.0f; // Max 18dB boost
// Apply gain with NEON and saturation
float32x4_t vgain = vdupq_n_f32(gain);
for (i = 0; i + 8 <= total_samples; i += 8) {
int16x8_t v = vld1q_s16(&pcm_buffer[i]);
// Convert to float, apply gain, saturate back to int16
int32x4_t v_low = vmovl_s16(vget_low_s16(v));
int32x4_t v_high = vmovl_s16(vget_high_s16(v));
float32x4_t f_low = vcvtq_f32_s32(v_low);
float32x4_t f_high = vcvtq_f32_s32(v_high);
f_low = vmulq_f32(f_low, vgain);
f_high = vmulq_f32(f_high, vgain);
v_low = vcvtq_s32_f32(f_low);
v_high = vcvtq_s32_f32(f_high);
// Saturate to int16 range
int16x4_t result_low = vqmovn_s32(v_low);
int16x4_t result_high = vqmovn_s32(v_high);
vst1q_s16(&pcm_buffer[i], vcombine_s16(result_low, result_high));
}
// Handle remaining samples
for (; i < total_samples; i++) {
int32_t boosted = (int32_t)(pcm_buffer[i] * gain);
if (boosted > 32767) boosted = 32767;
if (boosted < -32768) boosted = -32768;
pcm_buffer[i] = (int16_t)boosted;
}
}
nb_bytes = opus_encode(encoder, pcm_buffer, frame_size, out, max_packet_size);
return nb_bytes;
}

View File

@ -10,7 +10,7 @@ import notifications from "@/notifications";
export default function AudioPopover() {
const { send } = useJsonRpc();
const [audioOutputSource, setAudioOutputSource] = useState<string>("hdmi");
const [audioOutputSource, setAudioOutputSource] = useState<string>("usb");
const [audioOutputEnabled, setAudioOutputEnabled] = useState<boolean>(true);
const [audioInputEnabled, setAudioInputEnabled] = useState<boolean>(true);
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");
} else {
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 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");
} else {
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 fmtpMatch = offer.sdp.match(fmtpRegex);