mirror of https://github.com/jetkvm/kvm.git
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:
parent
5e28a6c429
commit
2082b1a671
|
@ -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
|
||||
|
||||
|
|
29
Makefile
29
Makefile
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue