Previous EDID was configured for 29.95Hz (30 FPS). Updated to standard
1920x1080@60Hz using CEA timing (148.5 MHz pixel clock, 2200x1125 total)
for smoother video capture.
- Add input validation: NULL checks, bounds checking (max 7680 samples)
- Change return type to int for error propagation
- Use saturating NEON arithmetic (vqaddq_s16, vqsubq_s16) to prevent overflow
- Fix type consistency: use int16_t instead of short throughout
- Update documentation: precise threshold (0.9375 or 15/16), describe 4:1 compression
- Remove redundant clamping operations (mathematically proven unnecessary)
- Add stdbool.h include for bool type support
- Handle soft-clip errors at call site to prevent encoding corrupted audio
Implements SIMD-optimized soft-clipping before Opus encoding to prevent
digital clipping distortion on sharp transient attacks (e.g., plastic cup
impacts, percussive sounds). Uses smooth saturation curve starting at
±30720 (~94% of max amplitude) to preserve audio quality while eliminating
crackles and pops.
Processes 8 samples per iteration using ARM NEON intrinsics for optimal
performance on the ARM Cortex-A7 platform.
We use direct hw: device access with SpeexDSP for resampling, so the
ALSA plugin layer (plug) and rate conversion plugin are not needed.
This reduces library size while maintaining all required functionality.
Query the ALSA channel map (snd_pcm_get_chmap) to detect hardware that
reports non-standard channel ordering (R,L instead of L,R). When detected,
swap channels after capture to ensure correct left/right positioning.
This properly handles hardware quirks (like TC358743 HDMI audio) without
hardcoding device names, making the solution portable and correct.
Changes default EDID to JetKVM branded display configuration:
- Display name: JetKVM
- Full HD resolution (1920x1080@60Hz)
- Digital RGB 8-bit color support
- CEA-861 extension with PCM audio capability
- Broader compatibility with source devices
This EDID declares audio support which may improve HDMI audio detection
on certain source hardware.
When HDMI is unplugged during active audio capture, the blocking
snd_pcm_readi() call was holding the mutex, preventing clean shutdown.
This caused snd_pcm_drop() to race with the blocking read, leading to
undefined behavior and crashes.
Solution mirrors PiKVM's approach:
- Release mutex before snd_pcm_readi()/snd_pcm_writei()
- Reacquire mutex after I/O completes
- Verify handle and stop flag before proceeding
This allows snd_pcm_drop() to immediately abort pending I/O when the
device is closed, ensuring clean shutdown during HDMI hotplug events.
Replace ALSA plugin layer resampling with libspeexdsp for improved audio
quality and reliability. This implementation uses direct hardware access
(hw:) instead of ALSA plugins (plughw:) and handles sample rate conversion
with SpeexDSP's high-quality sinc-based resampler.
Key changes:
- Add libspeexdsp 1.2.1 with ARM NEON optimizations to build dependencies
- Switch from plughw: to hw: device access for lower latency
- Implement conditional resampling (only when hardware rate ≠ 48kHz)
- Use SPEEX_RESAMPLER_QUALITY_DESKTOP for high-quality interpolation
- Add automatic audio dependency building in dev_deploy.sh
Quality improvements:
- Fix race condition in resampler cleanup with mutex protection
- Fix memory leak on resampler re-initialization
- Add buffer overflow validation (3840 frame limit for 192kHz)
- Improve error logging for resampling, encoding, and ALSA configuration
- Simplify code structure while maintaining all functionality
Technical details:
- Hardware negotiates actual sample rate (e.g., HDMI may vary)
- SpeexDSP converts hardware rate → 48kHz for Opus encoding
- USB Audio Gadget hardcoded to 48kHz (no resampling overhead)
- Static buffer allocation for zero allocation in hot path
- WebRTC requires 48kHz RTP clock rate per RFC 7587
Changes the audio subsystem from hw: (direct hardware access) to plughw:
(plugin layer with rate conversion) to enable configurable sample rates.
Changes:
- Update ALSA build to include plug,rate,linear,copy plugins
- Change device names from hw: to plughw: in C and Go code
- Remove 48kHz hardcoding for HDMI audio output
- Keep USB at 48kHz since hardware is fixed at that rate
- Update all comments to reflect plughw usage
Technical details:
- hw: devices bypass all ALSA plugins and require exact hardware rate match
- plughw: devices enable the ALSA plugin layer for automatic rate conversion
- Hardware still receives at native rate (48kHz), resampling happens in userspace
- HDMI can now use 8k/12k/16k/24k/48kHz, USB remains at 48kHz
- NEON-optimized resampling provides good performance on Cortex-A7
Requires rebuilding ALSA library with updated plugin configuration.
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.
- 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)
- Clarify sample rate is configurable (8k/12k/16k/24k/48k), not fixed at 48kHz
- Expand mutex comment to include full lifecycle protection scope
- Document that ALSA playback init fails immediately with no fallback
- Add async behavior documentation to audio enable/restart functions
- Restore build_audio_deps target lost during merge
- Restore lint-fix, lint-go, lint-ui Makefile targets
- Fix variable alignment per linter
- Remove silent fallback to ALSA 'default' device on playback init failure
- Return error from SetAudioOutputSource for invalid source values
- Fix misleading comment about mutex scope in C audio code
- Clarify inputSourceMutex purpose for WebRTC packet serialization
- Replace helper function in getAudioConfig with explicit validation
- Consolidate audio default application in LoadConfig
- Streamline relay retry logic with inline conditions
- Extract closeFile and openHidFile helpers in USB gadget
- Simplify setPendingInputTrack pointer handling
- Improve error handling clarity in startAudio and updateUsbRelatedConfig
- Clean up processInputPacket mutex usage
Use snd_pcm_hw_params_set_rate_resample(1) to enable ALSA's rate plugin,
which provides software resampling even with hw: device interface.
This fixes audio distortion when HDMI sources output non-48kHz rates
(e.g., 44.1kHz from SBCs). ALSA now automatically resamples any input
rate to the configured 48kHz that Opus expects.
The rate plugin is available because ALSA is compiled with
--with-pcm-plugins=rate in install_audio_deps.sh
Make audio start asynchronous to prevent blocking the RPC response.
Previously, enabling audio would block until ALSA initialization completed,
which can take 30-60 seconds for HDMI audio due to TC358743 hardware.
This also fixes the -1 decode errors that occurred when packets arrived
during the synchronous restart window.
Matches the existing async pattern used in SetAudioOutputSource().
ALSA now forces the configured sample rate (default 48kHz) instead of
auto-detecting the source rate. This prevents Opus encoder initialization
failures when HDMI sources output 44.1kHz audio, which Opus doesn't support.
Changes:
- Use snd_pcm_hw_params_set_rate() to force exact rate (48kHz by default)
- ALSA performs software resampling if hardware rate differs
- Update valid rates to Opus-compatible only (8k, 12k, 16k, 24k, 48k)
- Remove auto-adaptation logic that caused Opus failures with 44.1kHz
This ensures audio capture works reliably with any HDMI source rate.
Integrated latest dev branch changes including:
- Native process refactoring with gRPC architecture
- OTA update system refactor with new component-based updates
- Updated build system and dependencies
- UI improvements and bug fixes
Post-merge fixes applied:
- Remove duplicate OTA RPC function declarations (now in ota.go)
- Fix GetDefaultEDID reference to use native.DefaultEDID constant
- Fix IsUpdatePending to use otaState.IsUpdatePending() method
- Add missing OTA RPC handler registrations for new update system
All audio functionality from feat/audio-support preserved.
All dev branch functionality preserved.
- config.go: Clarify that package-level defaults are for efficiency, not temporary
- jsonrpc.go: Correct "thread" to "goroutine" (Go uses goroutines, not threads)
After thorough review of all reported issues:
- processInputPacket early nil check is correct double-checked locking (not a race)
- Async audio source switching is intentional design for 30-60s HDMI init time
- TypeScript JSON.parse is safe (backend controls data, React catches errors)
Only actual terminology issues needed fixing.
- Fix validateAndApply comment to clarify it returns values, doesn't apply them
- Correct capture_channels comment about hardware capabilities
- Fix opus_packet_loss_perc default value from 0 to 20 (matches backend default)
- Fix handle_alsa_error return value documentation (return 0 also unlocks mutex)
The HDMI audio device can take 30-60 seconds to initialize due to
TC358743 hardware characteristics. Updated success notification in
all languages to inform users that audio will start shortly.
When switching audio output source between HDMI and USB, the HDMI
audio device (hw:0,0) can take 18-31 seconds to initialize due to
hardware characteristics of the TC358743 chip. This caused the UI
to freeze during source changes.
Changes:
- Move startAudio() to background goroutine in SetAudioOutputSource
- Move SaveConfig() to background goroutine to avoid blocking on disk I/O
- Return immediately after updating in-memory config
- Audio will initialize in background while UI remains responsive
The in-memory config is updated synchronously so subsequent calls
see the new source immediately. Both async operations are protected
by their respective mutexes (audioMutex, configLock).
Changes:
- Consolidate duplicate stop logic into helper functions
- Fix RPC getAudioConfig to return actual runtime values instead of
inconsistent defaults (bitrate was returning 128 vs actual 192)
- Improve setAudioTrack mutex handling to eliminate nested locking
- Simplify ALSA error retry logic by reorganizing conditional branches
- Split CGO Connect() into separate input/output methods for clarity
- Use map lookup for sample rate validation instead of long if-chain
- Add inline comments documenting validation steps
All changes preserve existing functionality while reducing code
duplication and improving readability. Tested with both HDMI and
USB audio sources.
Changes:
- Switch manufacturer ID from DEL to LNX for better open-source alignment
- Add dual audio sample rate support (44.1kHz + 48kHz) to eliminate
resampling quality loss on MacBooks and other devices
- Declare 640×480p60 in established timings and CEA video block (VIC-1)
- Use 1920×1200p60 as secondary timing to meet validator requirements
- Fix white point coordinates to D65 standard (0.313, 0.329)
This EDID now passes edidtool.com validation and provides universal
compatibility across macOS, Linux, and Windows systems.
- Set DefaultEDID in config defaults instead of empty string
- Pass config EDID to Native.Start() to fix initialization race condition
- Update DefaultEDID to MacBook-compatible value (2ch, 48kHz, 16/20/24-bit)
- Add getDefaultEDID RPC endpoint for UI to fetch backend constant
- Update UI to dynamically fetch default EDID instead of hardcoding
- Remove all EDID fallback logic now that config always has a value
- Simplify rpcGetEDID to return config value directly
This ensures the configured EDID is used from startup and eliminates
sync issues between backend constant, config, and UI.
Critical Fixes:
- Fix race condition in handleInputTrackForSession by reloading source inside mutex
- Fix ALSA handle cleanup atomicity (nullify before close to prevent use-after-free)
- Bounds check for opus buffer already present (verified)
Configuration Alignment:
- Align audio bitrate default to 192 kbps across all layers (C, Go defaults, config)
- Align audio complexity default to 8 across all layers
- Align DTX default to enabled (true/1) across all layers for bandwidth efficiency
Documentation Improvements:
- Update C header comment to reflect accurate 192 kbps default
- Clarify NEON requirement (not just "always available")
- Fix ALSA device mapping comments to reflect environment variable usage
- Document fallback behavior in playback init
Code Quality:
- Add validation logging for out-of-range audio configuration values
- Improve error visibility for configuration issues
All changes thoroughly analyzed before implementation.
Change recovery_attempts from int to uint8_t for better efficiency:
- Reduces memory footprint (1 byte vs 4 bytes)
- Better cache utilization on ARM
- Matches max_attempts type (uint8_t)
- Values never exceed 3, fits perfectly in uint8_t range
Updated function signature and all call sites for consistency.
Extract shared error recovery logic:
- Create handle_alsa_error() for EPIPE, EAGAIN, ESTRPIPE, EIO errors
- Consolidates ~180 lines of duplicate error handling code
- Used by both capture and playback paths
Extract shared close logic:
- Create close_audio_stream() for safe shutdown sequence
- Handles CAS synchronization, delay, mutex protection
- Used by both jetkvm_audio_capture_close and jetkvm_audio_playback_close
Remove all TRACE_LOG dead code:
- TRACE_LOG was compiled to ((void)0) with zero runtime value
- Eliminates ~30 statements cluttering the codebase
Result: 87 lines removed (9% reduction), improved maintainability
- Separate capture_channels (stereo HDMI) from playback_channels (mono mic)
to prevent initialization conflicts that were breaking stereo output
- Optimize defaults for LAN use: 192kbps bitrate, complexity 8, 0% packet
loss compensation, DTX disabled (eliminates static and improves clarity)
- Add comprehensive race condition protection in C audio layer with handle
validity checks and mutex-protected cleanup operations
- Enable USB audio volume control and configure microphone as mono
- Add centralized AUDIO_DEFAULTS constant in UI with localized labels
- Add missing time import to fix compilation
This resolves audio quality issues and crash scenarios when switching
between HDMI and USB audio sources.