Commit Graph

46 Commits

Author SHA1 Message Date
Alex P 0f8b368427 Fix critical error handling and race conditions in audio system
Address 5 critical issues found in comprehensive code review:

1. Opus Encoder Configuration Failures (CRITICAL)
   - Split encoder settings into critical vs non-critical
   - Critical settings (bitrate, VBR, FEC) now fail initialization on error
   - Non-critical settings (complexity, DTX) log warnings but continue
   - Prevents silent audio quality degradation from misconfigured encoder

2. V4L2 Sample Rate Detection Error Reporting (CRITICAL)
   - Add specific error messages for different failure modes
   - Distinguish permission errors, device not found, and no signal
   - Validate detected sample rates are in reasonable range (8-192kHz)
   - Improves debuggability when HDMI audio detection fails

3. Mutex Handling in ALSA Error Recovery (CRITICAL)
   - Refactor handle_alsa_error() to NEVER unlock mutex internally
   - Caller now always responsible for unlocking after checking return
   - Eliminates complex mutex ownership semantics that caused deadlocks
   - Consistent lock/unlock patterns prevent double-unlock bugs

4. Async Audio Start Error Propagation (CRITICAL)
   - Make SetAudioOutputEnabled/SetAudioInputEnabled synchronous
   - Add 5-second timeout for audio initialization
   - Return errors to caller instead of only logging
   - Revert state on failure to maintain consistency
   - Users now get immediate feedback if audio fails to start

5. CgoSource Race Condition (CRITICAL)
   - Hold c.mu mutex during C function calls in ReadMessage/WriteMessage
   - Prevents use-after-free when Disconnect() called concurrently
   - Lock order (c.mu -> capture_mutex) is consistent, no deadlock risk
   - Fixes potential crash from accessing freed ALSA/codec resources

These changes eliminate silent failures, improve error visibility, and
prevent race conditions that could cause crashes or audio degradation.
2025-11-24 20:23:14 +02:00
Alex P ac568c7bbf Fix HDMI hotplug crash by releasing mutex during blocking ALSA I/O
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.
2025-11-21 16:50:35 +02:00
Alex P 9d86b02e66 Integrate libspeexdsp for high-quality audio resampling
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
2025-11-21 16:29:02 +02:00
Alex P 0be9dbcc6c Enable ALSA rate resampling for configurable audio sample rates
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.
2025-11-21 02:22:22 +02:00
Alex P 2040db6094 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.
2025-11-21 01:56:56 +02:00
Alex P 584b9fe3bf Fix comment inaccuracies and restore lint targets
- 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
2025-11-21 01:31:33 +02:00
Alex P 4001ef651f Fix silent failures and improve documentation
- 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
2025-11-21 00:57:31 +02:00
Alex P 5f7c90649a Simplify audio configuration and error handling
- 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
2025-11-21 00:54:32 +02:00
Alex P 3cd5bdd16c Make RestartAudioOutput async to prevent RPC hanging
Consistent with other audio toggle functions, restart now happens
asynchronously to avoid blocking the RPC caller.
2025-11-21 00:15:38 +02:00
Alex P edd06e2346 Fix UI hanging when toggling audio output enable/disable
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().
2025-11-21 00:10:10 +02:00
Alex P a6cbf20f66 Fix audio capture to force Opus-compatible sample rates
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.
2025-11-21 00:02:29 +02:00
Alex P 41604626cf Remove inaccurate and redundant comments
Fix comment inaccuracies:
- NEON: Change from "required" to "optimizations" (scalar fallback exists)
- capture_channels: Remove "always stereo" claim (configurable via API)

Remove redundant comments that duplicate code:
- "Apply boolean flags directly" (line duplicates what code does)
- Mutex comments in setAudioTrack (visible from code structure)

Also remove accidentally committed review artifacts (PR_REVIEW.md, jetkvm_default_edid.bin).
2025-11-19 17:19:46 +02:00
Alex P 3897a61729 Fix critical comment inaccuracies in audio code
- 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)
2025-11-19 17:19:46 +02:00
Alex P 1d570a8cbf Fix critical audio race conditions and improve reliability
- Replace volatile with C11 atomics for proper ARM memory barriers
- Fix race condition in audio source swapping (swap to nil before cleanup)
- Prevent double-close of ALSA handles via atomic ownership claim
- Add exponential backoff with 10-retry circuit breaker to prevent infinite loops
- Improve error propagation to report dual failures
- Add defensive null checks for concurrent access safety
- Simplify UI error handling with helper functions
- Fix TypeScript compilation error in packet loss dropdown
2025-11-19 17:19:46 +02:00
Alex P 7fc90b86a8 Make audio source switching instant with async initialization
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).
2025-11-19 14:12:12 +02:00
Alex P ee23e3bf22 Refactor audio subsystem for improved maintainability
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.
2025-11-19 13:42:51 +02:00
Alex P c88b98c1f0 Fix critical audio race conditions and align configuration defaults
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.
2025-11-18 16:37:44 +02:00
Alex P 478302144f Fix USB audio channels and remove redundant synchronization
USB audio configuration:
- Set playback to mono (microphone input from remote PC)
- Set capture to stereo (audio output to remote PC)
- Fixes audio input initialization failures and stereo output

Audio management optimizations:
- Remove redundant mutex in stopInputAudio (C layer provides synchronization)
- Remove unnecessary 100ms delay when switching audio sources
- Simplify error handling (Disconnect is idempotent)
- Remove time import (no longer needed)
2025-11-18 14:25:33 +02:00
Alex P 0022599b03 Fix audio channel separation and improve quality defaults
- 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.
2025-11-18 13:38:06 +02:00
Alex P 2f622df28d Fix input audio source swap bug
startInputAudioUnderMutex was incorrectly swapping outputSource
instead of inputSource, breaking microphone functionality.
2025-11-18 10:03:42 +02:00
Marc Brooks 1ec9941103
Simplify audio management
Moved all start/stop of sources into audio (out of jsonrpc)
Clean up duplicated code, made direction a bool, more logging, made all source/relay atomics.
Eliminate SetConfig since we always set it during start.
Eliminate the extra initialized flag.
Properly detect when USB audio was previously active.
Relay has the pointer to the source, not a copy.
CgoSource (and stub) expose the AudioSource interface.
2025-11-17 22:21:47 -06:00
Marc Brooks 9b2500b2df
Extract alsaDevice configuration to helper 2025-11-17 20:12:12 -06:00
Alex P b15cbc5890 Clean up redundant comments for maintainability
Removed obvious comments that don't add value:
- cgo_source.go: Removed redundant status check comments
- audio.go: Consolidated mutex pattern comments

Kept important comments that explain non-obvious patterns:
- Why mutex is released before C calls (deadlock prevention)
- Why operations happen outside mutex (avoid blocking on CGO)
- Why single critical section is used (race condition prevention)
2025-11-18 01:48:45 +02:00
Alex P 6a7f9e9996 Fix race condition in setAudioTrack by using single critical section
Reduced mutex locking from 3 separate lock/unlock cycles to 1, eliminating
race window and improving performance. New relay is prepared within mutex,
then old resources are stopped and new relay started outside mutex to avoid
blocking during CGO calls.
2025-11-18 01:15:51 +02:00
Alex P 11dadebb93 Fix: improve EDID compatibility and add audio configuration options
- Update default EDID with registered manufacturer ID (Dell) and proper 24-inch display dimensions (52x32cm) for better macOS/OS compatibility
- Add configurable sample rate (32/44.1/48/96 kHz) to support different HDMI audio sources
- Add packet loss compensation percentage control for FEC overhead tuning
- Fix config migration to ensure new audio parameters get defaults for existing configs
- Update all language translations for new audio settings
2025-11-18 00:53:15 +02:00
Alex P 922a7158e7 Add runtime configurable audio parameters with UI controls
- Add config fields for bitrate, complexity, DTX, FEC, buffer periods
- Add RPC methods for get/set audio config and restart
- Add UI settings page with controls for all audio parameters
- Add Apply Settings button to restart audio with new config
- Add config migration for backwards compatibility
- Add translations for all 9 languages
- Clean up redundant comments and optimize log levels
2025-11-17 21:51:08 +02:00
Alex P e79c6f730e Add audio output source switching and improve shutdown handling
- Add HDMI/USB audio source selection in settings UI
- Add stop flags for graceful audio shutdown
- Use immediate PCM drop instead of drain for faster switching
- Add HDMI connection refresh RPC method
- Add GetDefaultEDID helper method
2025-11-17 20:45:34 +02:00
Alex P 31903c4b61 Fix: extend audio mutex to prevent concurrent initialization crashes
Previous fix protected WriteMessage() but missed Connect() calls. During
session transfers, multiple goroutines could simultaneously initialize the
audio playback system, causing SIGABRT crashes in jetkvm_audio_playback_init.

Extended inputSourceMutex to serialize both Connect() and WriteMessage()
operations, ensuring atomic state transitions and preventing concurrent
C library initialization.
2025-11-14 10:21:15 +02:00
Alex P 33ee474865 Fix: prevent concurrent Opus decoder access during session transfers
During session transfers, multiple handleInputTrackForSession goroutines
could call WriteMessage() concurrently on the shared inputSource, causing
Opus SILK decoder state corruption and assertion failures.

Added inputSourceMutex to serialize WriteMessage() calls and prevent
race conditions in both input and output audio paths.
2025-11-13 10:58:47 +02:00
Alex P abe61c8959 Fixes / Updates: Update outside mutex comment, move outputSource, outputRelay assignments 2025-11-12 09:07:22 +02:00
Alex P 22a16c7f17 Fix: coredump issue 2025-11-11 22:41:46 +02:00
Alex P 0e63e6d5ed Updates: add logging on error, revert Makefile lint targets 2025-11-11 11:25:49 +02:00
Alex P 0514a70913 Enhancement: Make the code more DRY 2025-11-11 11:18:12 +02:00
Alex P 82977ab76c Enhancement: Make the code more DRY 2025-11-10 19:09:22 +02:00
Alex P 3261efaeb7 fix: prevent audio disconnect from blocking new WebRTC sessions
When an old connection closed while a new one started, the audio cleanup
would hold audioMutex for up to 37 seconds during CGO disconnect calls,
blocking the new session from initializing.

Use capture-clear-release pattern to minimize mutex hold time:
- Capture references to sources/relays while holding mutex
- Clear globals immediately so new sessions can proceed
- Release mutex before calling blocking Stop/Disconnect operations

This eliminates the 37-second hang during connection transitions.
2025-11-10 16:54:31 +02:00
Alex P 522f6cf489 Revert "refactor: use atomic.Pointer for thread-safe inputSource access"
This reverts commit 41345b0527.
2025-11-10 15:42:10 +02:00
Alex P af0e0441a8 refactor: use atomic.Pointer for thread-safe inputSource access
- Replace mutex-protected inputSource with atomic.Pointer for lock-free reads
- Eliminate ~100 mutex operations per second in audio packet hot path
- Add defer pattern for safer mutex unlock in enable/disable functions
- Add error logging for audio write failures
- Consolidate Makefile lint commands for brevity
2025-11-08 00:43:09 +02:00
Alex P e26e962d94 fix: handle audio track recreation to prevent microphone failures
When browsers recreate audio tracks during long sessions, the backend
now properly handles the new track instead of ignoring it. Old track
handlers detect when they've been superseded and exit cleanly to
prevent goroutine leaks.
2025-11-04 10:40:35 +02:00
Alex P 6a60de519c feat: persist audio preferences to backend config
- Add AudioInputAutoEnable and AudioOutputEnabled fields to backend config
- Implement RPC methods for get/set audio input auto-enable preference
- Load audio output enabled state from config on startup
- Make manual microphone toggle call backend to enable audio pipeline
- Auto-enable preference now persists across incognito sessions
- Reset microphone state to off when reaching login pages
- Fix issue where manual mic toggle required auto-enable to be on
2025-11-04 08:52:27 +02:00
Alex P f40f5a7b39 temp: disable audio source selection while HDMI audio issues are diagnosed
Temporarily remove the ability to switch between HDMI and USB audio
output sources. The application now uses USB audio (hw:1,0) exclusively
until HDMI audio capture issues are resolved.

Changes:
- Remove AudioOutputSource config field
- Remove audio source switching logic and UI
- Hardcode USB audio output device
- Remove related RPC methods
2025-11-03 10:49:35 +02:00
Alex P 7635b97f03 fix: use atomic.Bool for audio source to prevent mutex contention during switching 2025-10-29 00:26:48 +02:00
Alex P 2e40a04c26 fix: prevent race condition in audio output source switching by reading from in-memory state with proper synchronization 2025-10-28 22:11:11 +02:00
Alex P 8caa5fc188 refactor: Remove subprocess audio infrastructure, use CGO-only
Remove all subprocess-based audio code to simplify the audio system and
reduce complexity. Audio now uses CGO in-process mode exclusively.

Changes:
- Remove subprocess mode: Deleted Supervisor, IPCSource, embed.go
- Remove audio mode selection from UI (Settings → Audio)
- Remove audio mode from backend config (AudioMode field)
- Remove JSON-RPC handlers: getAudioMode/setAudioMode
- Remove Makefile targets: build_audio_output/input/binaries
- Remove standalone C binaries: jetkvm_audio_{input,output}.c
- Remove IPC protocol implementation: ipc_protocol.{c,h}
- Remove unused IPC functions from audio_common.{c,h}
- Simplify audio.go: startAudio() instead of startAudioSubprocesses()
- Update all function calls and comments to remove subprocess references
- Add constants to cgo_source.go (ipcMaxFrameSize, ipcMsgTypeOpus)
- Keep update_opus_encoder_params() for potential future runtime config

Benefits:
- Simpler codebase: -1,734 lines of code
- Better performance: No IPC overhead on embedded hardware
- Easier maintenance: Single audio implementation
- Smaller binary: No embedded audio subprocess binaries

The audio system now works exclusively via CGO direct C function calls,
with ALSA device selection (HDMI vs USB) still configurable via settings.
2025-10-07 13:34:03 +03:00
Alex P dcc0851f2b [WIP] Updates: support in-process mode 2025-10-07 08:49:49 +03:00
Alex P 3b609d2b67 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
Alex P 78c3dc3a1e Refactor: Simplify / rewrite Audio 2025-10-06 21:59:44 +03:00