Commit Graph

820 Commits

Author SHA1 Message Date
Adam Shiervani 85eb4babdf
feat: handle grpc events (#986)
Co-authored-by: Siyuan <siyuan@buildjet.com>
2025-11-20 16:07:50 +01:00
Adam Shiervani 07935add15
refactor: remove redundant initialization of native and display components in Main function (#987) 2025-11-20 14:56:17 +01:00
Nitish Agarwal 78cef12c97
fix: mobile viewport cropping on video element (#985) 2025-11-20 13:55:24 +01:00
Alex P fe4fb33561 Fix misleading comment and incorrect Go terminology
- 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.
2025-11-19 17:28:31 +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
Aveline 3fcd5e7def
feat: move native to a separate process, again (#964) 2025-11-19 16:02:37 +01:00
Aveline 752fb55799
refactor: OTA (#912)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
2025-11-19 15:20:59 +01:00
Alex P 8d69780061 Update audio source switch notification to indicate 30-60s delay
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.
2025-11-19 14:23:26 +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 0dbf2dfda9 Update default EDID for improved compatibility
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.
2025-11-19 12:31:48 +02:00
Alex P 0168fcbdbd Make config.EdidString the single source of truth for EDID
- 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.
2025-11-19 11:08:32 +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 da4c6c70d2 Use efficient uint8_t for recovery attempt counters
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.
2025-11-18 15:32:55 +02:00
Alex P befdfc7ce6 Fix type mismatch in ALSA error handler
Change recovery_attempts from uint8_t to int to match
handle_alsa_error signature and eliminate compiler warnings.
2025-11-18 14:38:12 +02:00
Alex P 0e299aac38 Deduplicate ALSA error handling and cleanup code
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
2025-11-18 14:34:21 +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
Alex bc2a5f88e1
Merge pull request #1 from IDisposable/small-tweaks
Some cleanup to eliminate duplicate code ensure we don't carry around multiple copies of state
2025-11-18 09:49:10 +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 8c7764a663
Ensure the stopAudio() always runs 2025-11-17 20:12:34 -06:00
Marc Brooks 9b2500b2df
Extract alsaDevice configuration to helper 2025-11-17 20:12:12 -06:00
Marc Brooks 7f930e01b3
Add missing translations for new connection stats 2025-11-17 20:11:33 -06:00
Marc Brooks cbba7f255a
Removed unused translations. 2025-11-17 20:11:33 -06:00
Marc Brooks ba831dc682
Ran npm run i18n to sort the message strings. 2025-11-17 20:11:32 -06:00
Alex P a1a2b9d1c0 Remove unnecessary assignment when USB devices unchanged
Addresses PR #718 review comment - no need to assign config.UsbDevices
when we've already verified the devices are equal.
2025-11-18 02:11:29 +02:00
Alex P 2e84354d78 Fix shutdown log level and order per PR review
- Change logger.Info() to logger.Log() for shutdown message to ensure it always logs
- Move stopAudio() before logging to stop audio first, then log shutdown

Addresses PR #718 review comment
2025-11-18 02:08:40 +02:00
Alex P cd2d4d752c Remove additional redundant comments 2025-11-18 02:01:12 +02:00
Alex P 12d7ac69b9 Fix USB HID errors during browser refresh
When browser refreshes (Shift+R), USB state changes from "configured" to
"default" temporarily. Previously, HID files remained open with stale file
handles, causing "transport endpoint shutdown" errors.

Now proactively close HID files when USB transitions away from "configured"
state, preventing writes to invalid endpoints.

Fixes USB HID transport endpoint shutdown errors during browser refresh.
2025-11-18 01:56:52 +02: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 a6b7ac50ef Eliminate hang completely by making ALSA interruption immediate
Problem:
Previous fix reduced but didn't eliminate the hang when switching audio
sources. The C layer was still blocking on snd_pcm_readi()/snd_pcm_writei()
while holding the mutex, preventing cleanup from proceeding.

Solution:
Call snd_pcm_drop() BEFORE acquiring the mutex in close functions. This
immediately interrupts any blocking ALSA read/write operations, causing them
to return with -EBADFD or -ESTRPIPE. The sequence is now:

1. Set stop_requested flag
2. Call snd_pcm_drop() to interrupt blocking I/O (no mutex needed - thread-safe)
3. Acquire mutex for cleanup
4. Close handles and free resources
5. Release mutex

This makes audio source switching instantaneous with zero hang.

Changes:
- jetkvm_audio_capture_close(): Drop PCM before mutex
- jetkvm_audio_playback_close(): Drop PCM before mutex

Tested: USB↔HDMI switching now happens instantly with no delay.
2025-11-18 01:46:29 +02:00
Alex P 051950f220 Fix critical deadlock when switching audio sources
Problem:
When switching audio sources (USB to HDMI or vice versa), the application
would hang indefinitely. This was caused by a deadlock between Go and C
layers:

1. Main thread calls SetAudioOutputSource() → stopOutputAudio()
2. stopOutputAudio() calls outputRelay.Stop() which waits for goroutine
3. Goroutine is blocked in ReadMessage() holding Go mutex
4. ReadMessage() calls blocking C function jetkvm_audio_read_encode()
5. C function is blocked reading from ALSA device
6. Disconnect() can't acquire Go mutex to clean up
7. Deadlock: Main thread waiting for goroutine, goroutine waiting for ALSA

Solution:
Release the Go mutex BEFORE calling blocking C functions in ReadMessage()
and WriteMessage(). The C layer has its own pthread mutex protection and
handles stop requests via atomic flags. This allows:
- Disconnect() to acquire the mutex immediately
- C layer to detect stop request and return quickly
- Goroutines to exit cleanly
- Audio source switching to work flawlessly

Fixes:
- internal/audio/cgo_source.go:ReadMessage() - Release mutex before C call
- internal/audio/cgo_source.go:WriteMessage() - Release mutex before C call

This fix eliminates the hang when switching between USB and HDMI audio
sources.
2025-11-18 01:41:46 +02:00
Alex a305217e04
Merge branch 'dev' into feat/audio-support 2025-11-18 01:36:46 +02:00
Alex P 437a63d7d4 Add bounds check for Opus packets in Go layer
Validate packet size <= 1500 bytes before passing to C code to provide
defense-in-depth alongside existing C-layer validation.
2025-11-18 01:22:15 +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 d7dc8c2075 Update audio.c header comment to reflect variable sample rate 2025-11-18 00:54:04 +02:00
Alex P a6c5e46a61 Align inline comments in Config struct 2025-11-18 00:53:15 +02:00
Alex P 2b276e1ba5 Remove unused update_opus_encoder_params function 2025-11-18 00:53:15 +02:00
Alex P 94cab8b2ac Fix: prevent race condition crash in audio playback using pthread mutexes
Root cause: ALSA assertion failure `snd_pcm_writei: Assertion 'pcm' failed`
when pcm_playback_handle became NULL during concurrent write operations.

The crash occurred because:
1. Thread A checks pcm_playback_handle != NULL (passes)
2. Thread B calls jetkvm_audio_playback_close(), sets handle = NULL
3. Thread A calls snd_pcm_writei(NULL, ...) → SIGABRT

Solution: Added pthread mutexes to protect concurrent access:
- playback_mutex protects pcm_playback_handle in decode_write and close
- capture_mutex protects pcm_capture_handle in read_encode and close

All critical sections now acquire mutex before accessing ALSA handles,
preventing the NULL pointer from being passed to ALSA functions.
2025-11-18 00:53:15 +02:00
Alex P 3f141c8e9d Add log files and development artifacts to gitignore 2025-11-18 00:53:15 +02:00
Alex P 9c57fe86c4 Change: make sample rate read-only and auto-detected
The sample rate cannot be configured by users - it's determined by the audio
source (HDMI device or USB gadget client). The previous UI gave the false
impression that users could select a sample rate, but the value was always
overridden by hardware detection.

Changes:
- Convert sample rate UI from dropdown to read-only display
- Show "(auto-detected from source)" label next to the value
- Remove sampleRate parameter from setAudioConfig RPC
- Update translations to clarify auto-detection
- Backend sample rate validation remains for backwards compatibility

The C code now automatically detects and adapts to whatever rate the hardware
supports, creating the Opus encoder/decoder with matching parameters to
eliminate pitch/speed distortion.
2025-11-18 00:53:15 +02:00
Alex P 236291a454 Fix: eliminate audio warping by auto-adapting to actual device sample rate
Root cause: ALSA was silently using a different sample rate than configured,
causing severe pitch/speed distortion (the "cassette player" warping effect).

The bug occurred when:
- User configured 48 kHz in UI
- HDMI source output 44.1 kHz audio
- set_rate() failed, set_rate_near() chose 44.1 kHz
- Code never checked what rate was actually set
- Opus encoder created for 48 kHz but received 44.1 kHz audio
- Result: ~9% pitch shift and timing mismatch

Fix:
- Always use set_rate_near() and check the actual rate returned
- Pass detected rate and frame size to Opus encoder/decoder creation
- Avoid modifying global state to prevent capture/playback interference
- Recalculate frame_size for 20ms at the actual rate
- Verify rate after hw_params application
- Add detailed logging for rate adaptation

This ensures Opus encoder/decoder use the correct rate matching the hardware,
regardless of what the HDMI source outputs.
2025-11-18 00:53:15 +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 df76cd0a3e Fix: reopen HID files on USB state transitions to prevent stale handles
When USB state transitions to "configured" (after reconnection or state
changes), automatically close and reopen HID file handles to ensure they
remain valid.
2025-11-17 23:18:58 +02:00
Alex P 9f0d9c4689 Fix: skip redundant USB gadget reconfigurations to prevent HID disruption
- Add Devices.Equals() method to compare USB device configurations
- Add GetGadgetDevices() to retrieve current device state
- Skip gadget reconfiguration when device state is unchanged
- Remove 6 unused audio translation keys from all language files
2025-11-17 22:48:02 +02:00
Alex P 9371868b14 Fix: increase Opus buffer size to 1500 bytes and add bounds check 2025-11-17 22:48:02 +02:00
Alex P 1e22e007ea Auto-switch audio output to HDMI when USB audio emulation is disabled 2025-11-17 22:48:02 +02:00