refactor(audio): rename audio-server flag to audio-output-server for clarity

docs: update development documentation with new make targets
refactor: simplify audio quality presets implementation
style: remove redundant comments and align error handling
chore: add lint-ui-fix target to Makefile
This commit is contained in:
Alex P 2025-08-23 12:18:33 +00:00
parent 5e28a6c429
commit 2082b1a671
8 changed files with 184 additions and 103 deletions

View File

@ -231,22 +231,102 @@ systemctl restart jetkvm
cd ui && npm run lint
```
### Local Code Quality Tools
### Essential Makefile Targets
The project includes several Makefile targets for local code quality checks that mirror the GitHub Actions workflows:
The project includes several essential Makefile targets for development environment setup, building, and code quality:
#### Development Environment Setup
```bash
# Run Go linting (mirrors .github/workflows/lint.yml)
make lint
# Set up complete development environment (recommended first step)
make dev_env
# This runs setup_toolchain + build_audio_deps + installs Go tools
# - Clones rv1106-system toolchain to $HOME/.jetkvm/rv1106-system
# - Builds ALSA and Opus static libraries for ARM
# - Installs goimports and other Go development tools
# Run Go linting with auto-fix
make lint-fix
# Set up only the cross-compiler toolchain
make setup_toolchain
# Run UI linting (mirrors .github/workflows/ui-lint.yml)
make ui-lint
# Build only the audio dependencies (requires setup_toolchain)
make build_audio_deps
```
**Note:** The `lint` and `lint-fix` targets require audio dependencies. Run `make dev_env` first if you haven't already.
#### Building
```bash
# Build development version with debug symbols
make build_dev
# Builds jetkvm_app with version like 0.4.7-dev20241222
# Requires: make dev_env (for toolchain and audio dependencies)
# Build release version (production)
make build_release
# Builds optimized release version
# Requires: make dev_env and frontend build
# Build test binaries for device testing
make build_dev_test
# Creates device-tests.tar.gz with all test binaries
```
#### Code Quality and Linting
```bash
# Run both Go and UI linting
make lint
# Run both Go and UI linting with auto-fix
make lint-fix
# Run only Go linting
make lint-go
# Run only Go linting with auto-fix
make lint-go-fix
# Run only UI linting
make lint-ui
# Run only UI linting with auto-fix
make lint-ui-fix
```
**Note:** The Go linting targets (`lint-go`, `lint-go-fix`, and the combined `lint`/`lint-fix` targets) require audio dependencies. Run `make dev_env` first if you haven't already.
### Development Deployment Script
The `dev_deploy.sh` script is the primary tool for deploying your development changes to a JetKVM device:
```bash
# Basic deployment (builds and deploys everything)
./dev_deploy.sh -r 192.168.1.100
# Skip UI build for faster backend-only deployment
./dev_deploy.sh -r 192.168.1.100 --skip-ui-build
# Run Go tests on the device after deployment
./dev_deploy.sh -r 192.168.1.100 --run-go-tests
# Deploy with release build and install
./dev_deploy.sh -r 192.168.1.100 -i
# View all available options
./dev_deploy.sh --help
```
**Key features:**
- Automatically builds the Go backend with proper cross-compilation
- Optionally builds the React frontend (unless `--skip-ui-build`)
- Deploys binaries to the device via SSH/SCP
- Restarts the JetKVM service
- Can run tests on the device
- Supports custom SSH user and various deployment options
**Requirements:**
- SSH access to your JetKVM device
- `make dev_env` must be run first (for toolchain and audio dependencies)
- Device IP address or hostname
### API Testing

View File

@ -1,5 +1,5 @@
# --- JetKVM Audio/Toolchain Dev Environment Setup ---
.PHONY: setup_toolchain build_audio_deps dev_env lint lint-fix ui-lint
.PHONY: setup_toolchain build_audio_deps dev_env lint lint-go lint-ui lint-fix lint-go-fix lint-ui-fix ui-lint
# Clone the rv1106-system toolchain to $HOME/.jetkvm/rv1106-system
setup_toolchain:
@ -9,8 +9,10 @@ setup_toolchain:
build_audio_deps: setup_toolchain
bash tools/build_audio_deps.sh $(ALSA_VERSION) $(OPUS_VERSION)
# Prepare everything needed for local development (toolchain + audio deps)
# Prepare everything needed for local development (toolchain + audio deps + Go tools)
dev_env: build_audio_deps
@echo "Installing Go development tools..."
go install golang.org/x/tools/cmd/goimports@latest
@echo "Development environment ready."
JETKVM_HOME ?= $(HOME)/.jetkvm
TOOLCHAIN_DIR ?= $(JETKVM_HOME)/rv1106-system
@ -127,8 +129,12 @@ release:
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
# Run both Go and UI linting
lint: lint-go lint-ui
@echo "All linting completed successfully!"
# Run golangci-lint locally with the same configuration as CI
lint: build_audio_deps
lint-go: build_audio_deps
@echo "Running golangci-lint..."
@mkdir -p static && touch static/.gitkeep
CGO_ENABLED=1 \
@ -136,8 +142,12 @@ lint: build_audio_deps
CGO_LDFLAGS="-L$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/src/.libs -lasound -L$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/.libs -lopus -lm -ldl -static" \
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-fix: build_audio_deps
lint-go-fix: build_audio_deps
@echo "Running golangci-lint with auto-fix..."
@mkdir -p static && touch static/.gitkeep
CGO_ENABLED=1 \
@ -146,7 +156,16 @@ lint-fix: build_audio_deps
golangci-lint run --fix --verbose
# Run UI linting locally (mirrors GitHub workflow ui-lint.yml)
ui-lint:
lint-ui:
@echo "Running UI lint..."
@cd ui && npm ci
@cd ui && npm run lint
# Run UI linting with auto-fix
lint-ui-fix:
@echo "Running UI lint with auto-fix..."
@cd ui && npm ci
@cd ui && npm run lint:fix
# Legacy alias for UI linting (for backward compatibility)
ui-lint: lint-ui

View File

@ -11,7 +11,7 @@ import (
func main() {
versionPtr := flag.Bool("version", false, "print version and exit")
versionJsonPtr := flag.Bool("version-json", false, "print version as json and exit")
audioServerPtr := flag.Bool("audio-server", false, "Run as audio server subprocess")
audioServerPtr := flag.Bool("audio-output-server", false, "Run as audio server subprocess")
audioInputServerPtr := flag.Bool("audio-input-server", false, "Run as audio input server subprocess")
flag.Parse()

View File

@ -15,7 +15,7 @@ var (
// isAudioServerProcess detects if we're running as the audio server subprocess
func isAudioServerProcess() bool {
for _, arg := range os.Args {
if strings.Contains(arg, "--audio-server") {
if strings.Contains(arg, "--audio-output-server") {
return true
}
}

View File

@ -4,7 +4,6 @@ import (
"errors"
"sync/atomic"
"time"
// Explicit import for CGO audio stream glue
)
var (
@ -33,7 +32,7 @@ type AudioConfig struct {
}
// AudioMetrics tracks audio performance metrics
// Note: 64-bit fields must be first for proper alignment on 32-bit ARM
type AudioMetrics struct {
FramesReceived int64
FramesDropped int64
@ -61,72 +60,67 @@ var (
metrics AudioMetrics
)
// GetAudioQualityPresets returns predefined quality configurations
// qualityPresets defines the base quality configurations
var qualityPresets = map[AudioQuality]struct {
outputBitrate, inputBitrate int
sampleRate, channels int
frameSize time.Duration
}{
AudioQualityLow: {
outputBitrate: 32, inputBitrate: 16,
sampleRate: 22050, channels: 1,
frameSize: 40 * time.Millisecond,
},
AudioQualityMedium: {
outputBitrate: 64, inputBitrate: 32,
sampleRate: 44100, channels: 2,
frameSize: 20 * time.Millisecond,
},
AudioQualityHigh: {
outputBitrate: 128, inputBitrate: 64,
sampleRate: 48000, channels: 2,
frameSize: 20 * time.Millisecond,
},
AudioQualityUltra: {
outputBitrate: 192, inputBitrate: 96,
sampleRate: 48000, channels: 2,
frameSize: 10 * time.Millisecond,
},
}
// GetAudioQualityPresets returns predefined quality configurations for audio output
func GetAudioQualityPresets() map[AudioQuality]AudioConfig {
return map[AudioQuality]AudioConfig{
AudioQualityLow: {
Quality: AudioQualityLow,
Bitrate: 32,
SampleRate: 22050,
Channels: 1,
FrameSize: 40 * time.Millisecond,
},
AudioQualityMedium: {
Quality: AudioQualityMedium,
Bitrate: 64,
SampleRate: 44100,
Channels: 2,
FrameSize: 20 * time.Millisecond,
},
AudioQualityHigh: {
Quality: AudioQualityHigh,
Bitrate: 128,
SampleRate: 48000,
Channels: 2,
FrameSize: 20 * time.Millisecond,
},
AudioQualityUltra: {
Quality: AudioQualityUltra,
Bitrate: 192,
SampleRate: 48000,
Channels: 2,
FrameSize: 10 * time.Millisecond,
},
result := make(map[AudioQuality]AudioConfig)
for quality, preset := range qualityPresets {
result[quality] = AudioConfig{
Quality: quality,
Bitrate: preset.outputBitrate,
SampleRate: preset.sampleRate,
Channels: preset.channels,
FrameSize: preset.frameSize,
}
}
return result
}
// GetMicrophoneQualityPresets returns predefined quality configurations for microphone input
func GetMicrophoneQualityPresets() map[AudioQuality]AudioConfig {
return map[AudioQuality]AudioConfig{
AudioQualityLow: {
Quality: AudioQualityLow,
Bitrate: 16,
SampleRate: 16000,
Channels: 1,
FrameSize: 40 * time.Millisecond,
},
AudioQualityMedium: {
Quality: AudioQualityMedium,
Bitrate: 32,
SampleRate: 22050,
Channels: 1,
FrameSize: 20 * time.Millisecond,
},
AudioQualityHigh: {
Quality: AudioQualityHigh,
Bitrate: 64,
SampleRate: 44100,
Channels: 1,
FrameSize: 20 * time.Millisecond,
},
AudioQualityUltra: {
Quality: AudioQualityUltra,
Bitrate: 96,
SampleRate: 48000,
Channels: 1,
FrameSize: 10 * time.Millisecond,
},
result := make(map[AudioQuality]AudioConfig)
for quality, preset := range qualityPresets {
result[quality] = AudioConfig{
Quality: quality,
Bitrate: preset.inputBitrate,
SampleRate: func() int {
if quality == AudioQualityLow {
return 16000
}
return preset.sampleRate
}(),
Channels: 1, // Microphone is always mono
FrameSize: preset.frameSize,
}
}
return result
}
// SetAudioQuality updates the current audio quality configuration

View File

@ -410,9 +410,7 @@ func cgoAudioClose() {
C.jetkvm_audio_close()
}
// Optimized read and encode with pre-allocated error objects and reduced checks
func cgoAudioReadEncode(buf []byte) (int, error) {
// Fast path: check minimum buffer size (reduced from 1500 to 1276 for 10ms frames)
if len(buf) < 1276 {
return 0, errBufferTooSmall
}
@ -427,11 +425,11 @@ func cgoAudioReadEncode(buf []byte) (int, error) {
return int(n), nil
}
// Go wrappers for audio playback (microphone input)
// Audio playback functions
func cgoAudioPlaybackInit() error {
ret := C.jetkvm_audio_playback_init()
if ret != 0 {
return errors.New("failed to init ALSA playback/Opus decoder")
return errAudioPlaybackInit
}
return nil
}
@ -440,44 +438,36 @@ func cgoAudioPlaybackClose() {
C.jetkvm_audio_playback_close()
}
// Decodes Opus frame and writes to playback device
func cgoAudioDecodeWrite(buf []byte) (int, error) {
if len(buf) == 0 {
return 0, errors.New("empty buffer")
return 0, errEmptyBuffer
}
// Additional safety check to prevent segfault
if buf == nil {
return 0, errors.New("nil buffer")
return 0, errNilBuffer
}
if len(buf) > 4096 {
return 0, errBufferTooLarge
}
// Validate buffer size to prevent potential overruns
if len(buf) > 4096 { // Maximum reasonable Opus frame size
return 0, errors.New("buffer too large")
}
// Ensure buffer is not deallocated by keeping a reference
bufPtr := unsafe.Pointer(&buf[0])
if bufPtr == nil {
return 0, errors.New("invalid buffer pointer")
return 0, errInvalidBufferPtr
}
// Add recovery mechanism for C function crashes
defer func() {
if r := recover(); r != nil {
// Log the panic but don't crash the entire program
// This should not happen with proper validation, but provides safety
_ = r // Explicitly ignore the panic value
_ = r
}
}()
n := C.jetkvm_audio_decode_write(bufPtr, C.int(len(buf)))
if n < 0 {
return 0, errors.New("audio decode/write error")
return 0, errAudioDecodeWrite
}
return int(n), nil
}
// Wrapper functions for non-blocking audio manager
// CGO function aliases
var (
CGOAudioInit = cgoAudioInit
CGOAudioClose = cgoAudioClose

View File

@ -9,9 +9,8 @@ import (
)
// AudioInputMetrics holds metrics for microphone input
// Note: int64 fields must be 64-bit aligned for atomic operations on ARM
type AudioInputMetrics struct {
FramesSent int64 // Must be first for alignment
FramesSent int64
FramesDropped int64
BytesProcessed int64
ConnectionDrops int64
@ -21,7 +20,6 @@ type AudioInputMetrics struct {
// AudioInputManager manages microphone input stream using IPC mode only
type AudioInputManager struct {
// metrics MUST be first for ARM32 alignment (contains int64 fields)
metrics AudioInputMetrics
ipcManager *AudioInputIPCManager

View File

@ -248,7 +248,7 @@ func (s *AudioServerSupervisor) startProcess() error {
defer s.mutex.Unlock()
// Create new command
s.cmd = exec.CommandContext(s.ctx, execPath, "--audio-server")
s.cmd = exec.CommandContext(s.ctx, execPath, "--audio-output-server")
s.cmd.Stdout = os.Stdout
s.cmd.Stderr = os.Stderr
@ -261,7 +261,7 @@ func (s *AudioServerSupervisor) startProcess() error {
s.logger.Info().Int("pid", s.processPID).Msg("audio server process started")
// Add process to monitoring
s.processMonitor.AddProcess(s.processPID, "audio-server")
s.processMonitor.AddProcess(s.processPID, "audio-output-server")
if s.onProcessStart != nil {
s.onProcessStart(s.processPID)