diff --git a/Makefile b/Makefile index 0390bd06..cb170b76 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,10 @@ BIN_DIR := $(shell pwd)/bin TEST_DIRS := $(shell find . -name "*_test.go" -type f -exec dirname {} \; | sort -u) +# Build ALSA and Opus static libs for ARM in /opt/jetkvm-audio-libs +build_audio_deps: + bash .devcontainer/install_audio_deps.sh + build_native: @if [ "$(SKIP_NATIVE_IF_EXISTS)" = "1" ] && [ -f "internal/native/cgo/lib/libjknative.a" ]; then \ echo "libjknative.a already exists, skipping native build..."; \ @@ -136,4 +140,32 @@ release: @echo "Uploading release..." @shasum -a 256 bin/jetkvm_app | cut -d ' ' -f 1 > bin/jetkvm_app.sha256 rclone copyto bin/jetkvm_app r2://jetkvm-update/app/$(VERSION)/jetkvm_app - rclone copyto bin/jetkvm_app.sha256 r2://jetkvm-update/app/$(VERSION)/jetkvm_app.sha256 \ No newline at end of file + rclone copyto bin/jetkvm_app.sha256 r2://jetkvm-update/app/$(VERSION)/jetkvm_app.sha256 +# Run golangci-lint locally with the same configuration as CI +lint-go: build_audio_deps + @echo "Running golangci-lint..." + @mkdir -p static && touch static/.gitkeep + golangci-lint run --verbose + +# Run both Go and UI linting with auto-fix +lint-fix: lint-go-fix lint-ui-fix + @echo "All linting with auto-fix completed successfully!" + +# Run golangci-lint with auto-fix +lint-go-fix: build_audio_deps + @echo "Running golangci-lint with auto-fix..." + @mkdir -p static && touch static/.gitkeep + golangci-lint run --fix --verbose + +# Run UI linting locally (mirrors GitHub workflow ui-lint.yml) +lint-ui: + @echo "Running UI lint..." + @cd ui && npm ci && npm run lint + +# Run UI linting with auto-fix +lint-ui-fix: + @echo "Running UI lint with auto-fix..." + @cd ui && npm ci && npm run lint:fix + +# Legacy alias for UI linting (for backward compatibility) +ui-lint: lint-ui diff --git a/audio.go b/audio.go index 1553e3b4..a827358f 100644 --- a/audio.go +++ b/audio.go @@ -13,10 +13,10 @@ import ( ) var ( - audioMutex sync.Mutex - inputSourceMutex sync.Mutex // Prevents concurrent WebRTC packets from racing during lazy connect + write - outputSource atomic.Pointer[audio.AudioSource] - inputSource atomic.Pointer[audio.AudioSource] + audioMutex sync.Mutex + inputSourceMutex sync.Mutex // Prevents concurrent WebRTC packets from racing during lazy connect + write + outputSource atomic.Pointer[audio.AudioSource] + inputSource atomic.Pointer[audio.AudioSource] outputRelay atomic.Pointer[audio.OutputRelay] inputRelay atomic.Pointer[audio.InputRelay] audioInitialized bool @@ -257,6 +257,9 @@ func setPendingInputTrack(track *webrtc.TrackRemote) { go handleInputTrackForSession(track) } +// SetAudioOutputEnabled enables or disables audio output capture. +// Returns immediately; when enabling, audio starts asynchronously to prevent UI blocking. +// Check logs for async operation status. func SetAudioOutputEnabled(enabled bool) error { if audioOutputEnabled.Swap(enabled) == enabled { return nil @@ -274,6 +277,9 @@ func SetAudioOutputEnabled(enabled bool) error { return nil } +// SetAudioInputEnabled enables or disables audio input playback. +// Returns immediately; when enabling, audio starts asynchronously to prevent UI blocking. +// Check logs for async operation status. func SetAudioInputEnabled(enabled bool) error { if audioInputEnabled.Swap(enabled) == enabled { return nil @@ -329,6 +335,9 @@ func SetAudioOutputSource(source string) error { return nil } +// RestartAudioOutput stops and restarts the audio output capture. +// Returns immediately; restart happens asynchronously to prevent UI blocking. +// Check logs for async operation status. func RestartAudioOutput() error { audioMutex.Lock() hasActiveOutput := audioOutputEnabled.Load() && currentAudioTrack != nil && outputSource.Load() != nil diff --git a/internal/audio/c/audio.c b/internal/audio/c/audio.c index 7bff4002..70bf093c 100644 --- a/internal/audio/c/audio.c +++ b/internal/audio/c/audio.c @@ -11,7 +11,8 @@ * Key features: * - ARM NEON SIMD optimization for all audio operations * - Opus in-band FEC for packet loss resilience - * - S16_LE stereo, 20ms frames at 48kHz (ALSA resamples non-48kHz sources) + * - S16_LE stereo, 20ms frames (sample rate configurable: 8k/12k/16k/24k/48kHz) + * - ALSA rate plugin resamples hardware output to match requested Opus-compatible rate */ #include @@ -75,11 +76,11 @@ static uint32_t max_backoff_us_global = 500000; static atomic_int capture_stop_requested = 0; static atomic_int playback_stop_requested = 0; -// Mutexes to protect concurrent access to ALSA handles and codecs +// Mutexes to protect concurrent access to ALSA handles and codecs throughout their lifecycle // These prevent race conditions when jetkvm_audio_*_close() is called while // jetkvm_audio_read_encode() or jetkvm_audio_decode_write() are executing. -// The mutexes are held during ALSA I/O and codec operations to ensure -// handles remain valid throughout the operation. +// The mutexes protect initialization, cleanup, ALSA I/O, codec operations, and handle validation +// to ensure handles remain valid from acquisition through release. static pthread_mutex_t capture_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t playback_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -606,7 +607,7 @@ retry_read: /** * Initialize INPUT path (Opus decoder → device speakers) * Opens ALSA playback device from ALSA_PLAYBACK_DEVICE env (default: hw:1,0) - * and creates Opus decoder + * and creates Opus decoder. Returns immediately on device open failure (no fallback). * @return 0 on success, -EBUSY if initializing, -1/-2 on errors */ int jetkvm_audio_playback_init() {