From c5216920b3087351125c98e813eae266f32c0c28 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 25 Aug 2025 19:30:57 +0000 Subject: [PATCH] refactor(audio): centralize config constants and update usage Move hardcoded values to config constants and update all references to use centralized configuration. Includes: - Audio processing timeouts and intervals - CGO sleep durations - Batch processing parameters - Event formatting and timeouts - Process monitor calculations --- internal/audio/batch_audio.go | 6 +- internal/audio/cgo_audio.go | 12 ++-- internal/audio/config_constants.go | 100 +++++++++++++++++++++++++++++ internal/audio/events.go | 6 +- internal/audio/input.go | 4 +- internal/audio/output_streaming.go | 2 +- internal/audio/process_monitor.go | 8 +-- internal/audio/zero_copy.go | 8 +-- 8 files changed, 124 insertions(+), 22 deletions(-) diff --git a/internal/audio/batch_audio.go b/internal/audio/batch_audio.go index 9e9dc29..83d27ef 100644 --- a/internal/audio/batch_audio.go +++ b/internal/audio/batch_audio.go @@ -274,7 +274,8 @@ func GetBatchAudioProcessor() *BatchAudioProcessor { // Initialize on first use if atomic.CompareAndSwapInt32(&batchProcessorInitialized, 0, 1) { - processor := NewBatchAudioProcessor(4, 5*time.Millisecond) // 4 frames per batch, 5ms timeout + config := GetConfig() + processor := NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout) atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor)) return processor } @@ -286,7 +287,8 @@ func GetBatchAudioProcessor() *BatchAudioProcessor { } // Fallback: create a new processor (should rarely happen) - return NewBatchAudioProcessor(4, 5*time.Millisecond) + config := GetConfig() + return NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout) } // EnableBatchAudioProcessing enables the global batch processor diff --git a/internal/audio/cgo_audio.go b/internal/audio/cgo_audio.go index f480b9a..9a83a12 100644 --- a/internal/audio/cgo_audio.go +++ b/internal/audio/cgo_audio.go @@ -34,7 +34,7 @@ static int sample_rate = 48000; // Will be set from GetConfig().CGOSampl static int channels = 2; // Will be set from GetConfig().CGOChannels static int frame_size = 960; // Will be set from GetConfig().CGOFrameSize static int max_packet_size = 1500; // Will be set from GetConfig().CGOMaxPacketSize -static int sleep_microseconds = 50000; // Will be set from GetConfig().CGOSleepMicroseconds +static int sleep_microseconds = 1000; // Will be set from GetConfig().CGOUsleepMicroseconds // Function to update constants from Go configuration void update_audio_constants(int bitrate, int complexity, int vbr, int vbr_constraint, @@ -244,7 +244,7 @@ int jetkvm_audio_read_encode(void *opus_buf) { } else if (pcm_rc == -ESTRPIPE) { // Device suspended, try to resume while ((err = snd_pcm_resume(pcm_handle)) == -EAGAIN) { - usleep(1000); // 1ms + usleep(sleep_microseconds); // Use centralized constant } if (err < 0) { err = snd_pcm_prepare(pcm_handle); @@ -358,7 +358,7 @@ int jetkvm_audio_decode_write(void *opus_buf, int opus_size) { } else if (pcm_rc == -ESTRPIPE) { // Device suspended, try to resume while ((err = snd_pcm_resume(pcm_playback_handle)) == -EAGAIN) { - usleep(1000); // 1ms + usleep(sleep_microseconds); // Use centralized constant } if (err < 0) { err = snd_pcm_prepare(pcm_playback_handle); @@ -376,7 +376,7 @@ int jetkvm_audio_decode_write(void *opus_buf, int opus_size) { void jetkvm_audio_playback_close() { // Wait for any ongoing operations to complete while (playback_initializing) { - usleep(1000); // 1ms + usleep(sleep_microseconds); // Use centralized constant } // Atomic check and set to prevent double cleanup @@ -399,7 +399,7 @@ void jetkvm_audio_playback_close() { void jetkvm_audio_close() { // Wait for any ongoing operations to complete while (capture_initializing) { - usleep(1000); // 1ms + usleep(sleep_microseconds); // Use centralized constant } capture_initialized = 0; @@ -448,7 +448,7 @@ func cgoAudioInit() error { C.int(config.CGOChannels), C.int(config.CGOFrameSize), C.int(config.CGOMaxPacketSize), - C.int(config.CGOSleepMicroseconds), + C.int(config.CGOUsleepMicroseconds), ) result := C.jetkvm_audio_init() diff --git a/internal/audio/config_constants.go b/internal/audio/config_constants.go index 116aa42..b34d345 100644 --- a/internal/audio/config_constants.go +++ b/internal/audio/config_constants.go @@ -830,6 +830,56 @@ type AudioConfigConstants struct { PoolGrowthMultiplier int // 2x growth multiplier for pool sizes LatencyScalingFactor float64 // 2.0 for latency ratio scaling OptimizerAggressiveness float64 // 0.7 for optimizer aggressiveness + + // CGO Audio Processing Constants + CGOUsleepMicroseconds int // 1000 microseconds (1ms) for CGO usleep calls + CGOPCMBufferSize int // 1920 samples for PCM buffer (max 2ch*960) + CGONanosecondsPerSecond float64 // 1000000000.0 for nanosecond conversions + + // Frontend Constants + FrontendOperationDebounceMS int // 1000ms debounce for frontend operations + FrontendSyncDebounceMS int // 1000ms debounce for sync operations + FrontendSampleRate int // 48000Hz sample rate for frontend audio + FrontendRetryDelayMS int // 500ms retry delay + FrontendShortDelayMS int // 200ms short delay + FrontendLongDelayMS int // 300ms long delay + FrontendSyncDelayMS int // 500ms sync delay + FrontendMaxRetryAttempts int // 3 maximum retry attempts + FrontendAudioLevelUpdateMS int // 100ms audio level update interval + FrontendFFTSize int // 256 FFT size for audio analysis + FrontendAudioLevelMax int // 100 maximum audio level + FrontendReconnectIntervalMS int // 3000ms reconnect interval + FrontendSubscriptionDelayMS int // 100ms subscription delay + FrontendDebugIntervalMS int // 5000ms debug interval + + // Process Monitor Constants + ProcessMonitorDefaultMemoryGB int // 4GB default memory for fallback + ProcessMonitorKBToBytes int // 1024 conversion factor + ProcessMonitorDefaultClockHz float64 // 250.0 Hz default for ARM systems + ProcessMonitorFallbackClockHz float64 // 1000.0 Hz fallback clock + ProcessMonitorTraditionalHz float64 // 100.0 Hz traditional clock + + // Batch Processing Constants + BatchProcessorFramesPerBatch int // 4 frames per batch + BatchProcessorTimeout time.Duration // 5ms timeout + + // Output Streaming Constants + OutputStreamingFrameIntervalMS int // 20ms frame interval (50 FPS) + + // IPC Constants + IPCInitialBufferFrames int // 500 frames for initial buffer + + // Event Constants + EventTimeoutSeconds int // 2 seconds for event timeout + EventTimeFormatString string // "2006-01-02T15:04:05.000Z" time format + EventSubscriptionDelayMS int // 100ms subscription delay + + // Input Processing Constants + InputProcessingTimeoutMS int // 10ms processing timeout threshold + + // Adaptive Buffer Constants + AdaptiveBufferCPUMultiplier int // 100 multiplier for CPU percentage + AdaptiveBufferMemoryMultiplier int // 100 multiplier for memory percentage } // DefaultAudioConfig returns the default configuration constants @@ -1247,6 +1297,56 @@ func DefaultAudioConfig() *AudioConfigConstants { PoolGrowthMultiplier: 2, // Pool growth multiplier LatencyScalingFactor: 2.0, // Latency ratio scaling factor OptimizerAggressiveness: 0.7, // Optimizer aggressiveness factor + + // CGO Audio Processing Constants + CGOUsleepMicroseconds: 1000, // 1000 microseconds (1ms) for CGO usleep calls + CGOPCMBufferSize: 1920, // 1920 samples for PCM buffer (max 2ch*960) + CGONanosecondsPerSecond: 1000000000.0, // 1000000000.0 for nanosecond conversions + + // Frontend Constants + FrontendOperationDebounceMS: 1000, // 1000ms debounce for frontend operations + FrontendSyncDebounceMS: 1000, // 1000ms debounce for sync operations + FrontendSampleRate: 48000, // 48000Hz sample rate for frontend audio + FrontendRetryDelayMS: 500, // 500ms retry delay + FrontendShortDelayMS: 200, // 200ms short delay + FrontendLongDelayMS: 300, // 300ms long delay + FrontendSyncDelayMS: 500, // 500ms sync delay + FrontendMaxRetryAttempts: 3, // 3 maximum retry attempts + FrontendAudioLevelUpdateMS: 100, // 100ms audio level update interval + FrontendFFTSize: 256, // 256 FFT size for audio analysis + FrontendAudioLevelMax: 100, // 100 maximum audio level + FrontendReconnectIntervalMS: 3000, // 3000ms reconnect interval + FrontendSubscriptionDelayMS: 100, // 100ms subscription delay + FrontendDebugIntervalMS: 5000, // 5000ms debug interval + + // Process Monitor Constants + ProcessMonitorDefaultMemoryGB: 4, // 4GB default memory for fallback + ProcessMonitorKBToBytes: 1024, // 1024 conversion factor + ProcessMonitorDefaultClockHz: 250.0, // 250.0 Hz default for ARM systems + ProcessMonitorFallbackClockHz: 1000.0, // 1000.0 Hz fallback clock + ProcessMonitorTraditionalHz: 100.0, // 100.0 Hz traditional clock + + // Batch Processing Constants + BatchProcessorFramesPerBatch: 4, // 4 frames per batch + BatchProcessorTimeout: 5 * time.Millisecond, // 5ms timeout + + // Output Streaming Constants + OutputStreamingFrameIntervalMS: 20, // 20ms frame interval (50 FPS) + + // IPC Constants + IPCInitialBufferFrames: 500, // 500 frames for initial buffer + + // Event Constants + EventTimeoutSeconds: 2, // 2 seconds for event timeout + EventTimeFormatString: "2006-01-02T15:04:05.000Z", // "2006-01-02T15:04:05.000Z" time format + EventSubscriptionDelayMS: 100, // 100ms subscription delay + + // Input Processing Constants + InputProcessingTimeoutMS: 10, // 10ms processing timeout threshold + + // Adaptive Buffer Constants + AdaptiveBufferCPUMultiplier: 100, // 100 multiplier for CPU percentage + AdaptiveBufferMemoryMultiplier: 100, // 100 multiplier for memory percentage } } diff --git a/internal/audio/events.go b/internal/audio/events.go index ce3dc2f..e4fe635 100644 --- a/internal/audio/events.go +++ b/internal/audio/events.go @@ -226,7 +226,7 @@ func convertAudioMetricsToEventDataWithLatencyMs(metrics AudioMetrics) AudioMetr FramesReceived: metrics.FramesReceived, FramesDropped: metrics.FramesDropped, BytesProcessed: metrics.BytesProcessed, - LastFrameTime: metrics.LastFrameTime.Format("2006-01-02T15:04:05.000Z"), + LastFrameTime: metrics.LastFrameTime.Format(GetConfig().EventTimeFormatString), ConnectionDrops: metrics.ConnectionDrops, AverageLatency: fmt.Sprintf("%.1fms", float64(metrics.AverageLatency.Nanoseconds())/1e6), } @@ -238,7 +238,7 @@ func convertAudioInputMetricsToEventDataWithLatencyMs(metrics AudioInputMetrics) FramesSent: metrics.FramesSent, FramesDropped: metrics.FramesDropped, BytesProcessed: metrics.BytesProcessed, - LastFrameTime: metrics.LastFrameTime.Format("2006-01-02T15:04:05.000Z"), + LastFrameTime: metrics.LastFrameTime.Format(GetConfig().EventTimeFormatString), ConnectionDrops: metrics.ConnectionDrops, AverageLatency: fmt.Sprintf("%.1fms", float64(metrics.AverageLatency.Nanoseconds())/1e6), } @@ -463,7 +463,7 @@ func (aeb *AudioEventBroadcaster) sendToSubscriber(subscriber *AudioEventSubscri return false } - ctx, cancel := context.WithTimeout(subscriber.ctx, 2*time.Second) + ctx, cancel := context.WithTimeout(subscriber.ctx, time.Duration(GetConfig().EventTimeoutSeconds)*time.Second) defer cancel() err := wsjson.Write(ctx, subscriber.conn, event) diff --git a/internal/audio/input.go b/internal/audio/input.go index 3aaef2c..f1beb60 100644 --- a/internal/audio/input.go +++ b/internal/audio/input.go @@ -80,7 +80,7 @@ func (aim *AudioInputManager) WriteOpusFrame(frame []byte) error { processingTime := time.Since(startTime) // Log high latency warnings - if processingTime > 10*time.Millisecond { + if processingTime > time.Duration(GetConfig().InputProcessingTimeoutMS)*time.Millisecond { aim.logger.Warn(). Dur("latency_ms", processingTime). Msg("High audio processing latency detected") @@ -116,7 +116,7 @@ func (aim *AudioInputManager) WriteOpusFrameZeroCopy(frame *ZeroCopyAudioFrame) processingTime := time.Since(startTime) // Log high latency warnings - if processingTime > 10*time.Millisecond { + if processingTime > time.Duration(GetConfig().InputProcessingTimeoutMS)*time.Millisecond { aim.logger.Warn(). Dur("latency_ms", processingTime). Msg("High audio processing latency detected") diff --git a/internal/audio/output_streaming.go b/internal/audio/output_streaming.go index 265e535..99d8f7f 100644 --- a/internal/audio/output_streaming.go +++ b/internal/audio/output_streaming.go @@ -122,7 +122,7 @@ func (s *OutputStreamer) streamLoop() { defer runtime.UnlockOSThread() // Adaptive timing for frame reading - frameInterval := time.Duration(20) * time.Millisecond // 50 FPS base rate + frameInterval := time.Duration(GetConfig().OutputStreamingFrameIntervalMS) * time.Millisecond // 50 FPS base rate ticker := time.NewTicker(frameInterval) defer ticker.Stop() diff --git a/internal/audio/process_monitor.go b/internal/audio/process_monitor.go index d131e83..20c90d8 100644 --- a/internal/audio/process_monitor.go +++ b/internal/audio/process_monitor.go @@ -316,7 +316,7 @@ func (pm *ProcessMonitor) getClockTicks() float64 { if len(fields) >= 2 { if period, err := strconv.ParseInt(fields[1], 10, 64); err == nil && period > 0 { // Convert nanoseconds to Hz - hz := 1000000000.0 / float64(period) + hz := GetConfig().CGONanosecondsPerSecond / float64(period) if hz >= minValidClockTicks && hz <= maxValidClockTicks { pm.clockTicks = hz return @@ -344,7 +344,7 @@ func (pm *ProcessMonitor) getTotalMemory() int64 { pm.memoryOnce.Do(func() { file, err := os.Open("/proc/meminfo") if err != nil { - pm.totalMemory = int64(defaultMemoryGB) * 1024 * 1024 * 1024 + pm.totalMemory = int64(defaultMemoryGB) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) return } defer file.Close() @@ -356,14 +356,14 @@ func (pm *ProcessMonitor) getTotalMemory() int64 { fields := strings.Fields(line) if len(fields) >= 2 { if kb, err := strconv.ParseInt(fields[1], 10, 64); err == nil { - pm.totalMemory = kb * 1024 + pm.totalMemory = kb * int64(GetConfig().ProcessMonitorKBToBytes) return } } break } } - pm.totalMemory = int64(defaultMemoryGB) * 1024 * 1024 * 1024 // Fallback + pm.totalMemory = int64(defaultMemoryGB) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) // Fallback }) return pm.totalMemory } diff --git a/internal/audio/zero_copy.go b/internal/audio/zero_copy.go index 7b542f3..ee1967c 100644 --- a/internal/audio/zero_copy.go +++ b/internal/audio/zero_copy.go @@ -20,9 +20,9 @@ type ZeroCopyAudioFrame struct { // ZeroCopyFramePool manages reusable zero-copy audio frames type ZeroCopyFramePool struct { // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) - counter int64 // Frame counter (atomic) - hitCount int64 // Pool hit counter (atomic) - missCount int64 // Pool miss counter (atomic) + counter int64 // Frame counter (atomic) + hitCount int64 // Pool hit counter (atomic) + missCount int64 // Pool miss counter (atomic) allocationCount int64 // Total allocations counter (atomic) // Other fields @@ -94,7 +94,7 @@ func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame { frame.mutex.Unlock() return frame } - + // First try pre-allocated frames for fastest access p.mutex.Lock() if len(p.preallocated) > 0 {