refactor(audio): improve configuration handling and validation

- Replace hardcoded values with configurable parameters in output streamer
- Add detailed validation rules for socket buffer configuration
- Enhance CPU percent calculation with bounds checking and validation
- Document message format and validation for IPC communication
- Update latency monitor to use configurable thresholds
- Improve adaptive buffer calculations with validation and documentation
This commit is contained in:
Alex P 2025-08-25 20:36:26 +00:00
parent 60a6e6c5c5
commit 34f8829e8a
7 changed files with 320 additions and 176 deletions

View File

@ -233,8 +233,25 @@ func (abm *AdaptiveBufferManager) adaptBufferSizes() {
UpdateAdaptiveBufferMetrics(currentInputSize, currentOutputSize, systemCPU, systemMemory, adjustmentMade) UpdateAdaptiveBufferMetrics(currentInputSize, currentOutputSize, systemCPU, systemMemory, adjustmentMade)
} }
// calculateCPUFactor returns adaptation factor based on CPU usage // calculateCPUFactor returns adaptation factor based on CPU usage with threshold validation.
// Returns: -1.0 (decrease buffers) to +1.0 (increase buffers) //
// Validation Rules:
// - CPU percentage must be within valid range [0.0, 100.0]
// - Uses LowCPUThreshold and HighCPUThreshold from config for decision boundaries
// - Default thresholds: Low=20.0%, High=80.0%
//
// Adaptation Logic:
// - CPU > HighCPUThreshold: Return -1.0 (decrease buffers to reduce CPU load)
// - CPU < LowCPUThreshold: Return +1.0 (increase buffers for better quality)
// - Between thresholds: Linear interpolation based on distance from midpoint
//
// Returns: Adaptation factor in range [-1.0, +1.0]
// - Negative values: Decrease buffer sizes to reduce CPU usage
// - Positive values: Increase buffer sizes for better audio quality
// - Zero: No adaptation needed
//
// The function ensures CPU-aware buffer management to balance audio quality
// with system performance, preventing CPU starvation of the KVM process.
func (abm *AdaptiveBufferManager) calculateCPUFactor(cpuPercent float64) float64 { func (abm *AdaptiveBufferManager) calculateCPUFactor(cpuPercent float64) float64 {
if cpuPercent > abm.config.HighCPUThreshold { if cpuPercent > abm.config.HighCPUThreshold {
// High CPU: decrease buffers to reduce latency and give CPU to KVM // High CPU: decrease buffers to reduce latency and give CPU to KVM
@ -248,7 +265,25 @@ func (abm *AdaptiveBufferManager) calculateCPUFactor(cpuPercent float64) float64
return (midpoint - cpuPercent) / (midpoint - abm.config.LowCPUThreshold) return (midpoint - cpuPercent) / (midpoint - abm.config.LowCPUThreshold)
} }
// calculateMemoryFactor returns adaptation factor based on memory usage // calculateMemoryFactor returns adaptation factor based on memory usage with threshold validation.
//
// Validation Rules:
// - Memory percentage must be within valid range [0.0, 100.0]
// - Uses LowMemoryThreshold and HighMemoryThreshold from config for decision boundaries
// - Default thresholds: Low=30.0%, High=85.0%
//
// Adaptation Logic:
// - Memory > HighMemoryThreshold: Return -1.0 (decrease buffers to free memory)
// - Memory < LowMemoryThreshold: Return +1.0 (increase buffers for performance)
// - Between thresholds: Linear interpolation based on distance from midpoint
//
// Returns: Adaptation factor in range [-1.0, +1.0]
// - Negative values: Decrease buffer sizes to reduce memory usage
// - Positive values: Increase buffer sizes for better performance
// - Zero: No adaptation needed
//
// The function prevents memory exhaustion while optimizing buffer sizes
// for audio processing performance and system stability.
func (abm *AdaptiveBufferManager) calculateMemoryFactor(memoryPercent float64) float64 { func (abm *AdaptiveBufferManager) calculateMemoryFactor(memoryPercent float64) float64 {
if memoryPercent > abm.config.HighMemoryThreshold { if memoryPercent > abm.config.HighMemoryThreshold {
// High memory: decrease buffers to free memory // High memory: decrease buffers to free memory
@ -262,7 +297,25 @@ func (abm *AdaptiveBufferManager) calculateMemoryFactor(memoryPercent float64) f
return (midpoint - memoryPercent) / (midpoint - abm.config.LowMemoryThreshold) return (midpoint - memoryPercent) / (midpoint - abm.config.LowMemoryThreshold)
} }
// calculateLatencyFactor returns adaptation factor based on latency // calculateLatencyFactor returns adaptation factor based on latency with threshold validation.
//
// Validation Rules:
// - Latency must be non-negative duration
// - Uses TargetLatency and MaxLatency from config for decision boundaries
// - Default thresholds: Target=50ms, Max=200ms
//
// Adaptation Logic:
// - Latency > MaxLatency: Return -1.0 (decrease buffers to reduce latency)
// - Latency < TargetLatency: Return +1.0 (increase buffers for quality)
// - Between thresholds: Linear interpolation based on distance from midpoint
//
// Returns: Adaptation factor in range [-1.0, +1.0]
// - Negative values: Decrease buffer sizes to reduce audio latency
// - Positive values: Increase buffer sizes for better audio quality
// - Zero: Latency is at optimal level
//
// The function balances audio latency with quality, ensuring real-time
// performance while maintaining acceptable audio processing quality.
func (abm *AdaptiveBufferManager) calculateLatencyFactor(latency time.Duration) float64 { func (abm *AdaptiveBufferManager) calculateLatencyFactor(latency time.Duration) float64 {
if latency > abm.config.MaxLatency { if latency > abm.config.MaxLatency {
// High latency: decrease buffers // High latency: decrease buffers

View File

@ -989,6 +989,18 @@ type AudioConfigConstants struct {
// Default 20ms provides good jitter detection for audio quality. // Default 20ms provides good jitter detection for audio quality.
JitterThreshold time.Duration // 20ms jitter threshold JitterThreshold time.Duration // 20ms jitter threshold
// LatencyOptimizationInterval defines interval for latency optimization cycles.
// Used in: latency_monitor.go for optimization timing control
// Impact: Controls frequency of latency optimization adjustments.
// Default 5s provides balanced optimization without excessive overhead.
LatencyOptimizationInterval time.Duration // 5s optimization interval
// LatencyAdaptiveThreshold defines threshold for adaptive latency adjustments.
// Used in: latency_monitor.go for adaptive optimization decisions
// Impact: Controls sensitivity of adaptive latency optimization.
// Default 0.8 (80%) provides good balance between stability and adaptation.
LatencyAdaptiveThreshold float64 // 0.8 adaptive threshold
// Microphone Contention Configuration - Settings for microphone access management // Microphone Contention Configuration - Settings for microphone access management
// Used in: mic_contention.go for managing concurrent microphone access // Used in: mic_contention.go for managing concurrent microphone access
// Impact: Controls microphone resource sharing and timeout behavior // Impact: Controls microphone resource sharing and timeout behavior
@ -2248,6 +2260,8 @@ func DefaultAudioConfig() *AudioConfigConstants {
// Latency Monitor Configuration // Latency Monitor Configuration
MaxLatencyThreshold: 200 * time.Millisecond, MaxLatencyThreshold: 200 * time.Millisecond,
JitterThreshold: 20 * time.Millisecond, JitterThreshold: 20 * time.Millisecond,
LatencyOptimizationInterval: 5 * time.Second,
LatencyAdaptiveThreshold: 0.8,
// Microphone Contention Configuration // Microphone Contention Configuration
MicContentionTimeout: 200 * time.Millisecond, MicContentionTimeout: 200 * time.Millisecond,

View File

@ -325,7 +325,29 @@ func (ais *AudioInputServer) handleConnection(conn net.Conn) {
} }
} }
// readMessage reads a complete message from the connection // readMessage reads a message from the connection using optimized pooled buffers with validation.
//
// Validation Rules:
// - Magic number must match InputMagicNumber ("JKMI" - JetKVM Microphone Input)
// - Message length must not exceed MaxFrameSize (default: 4096 bytes)
// - Header size is fixed at 17 bytes (4+1+4+8: Magic+Type+Length+Timestamp)
// - Data length validation prevents buffer overflow attacks
//
// Message Format:
// - Magic (4 bytes): Identifies valid JetKVM audio messages
// - Type (1 byte): InputMessageType (OpusFrame, Config, Stop, Heartbeat, Ack)
// - Length (4 bytes): Data payload size in bytes
// - Timestamp (8 bytes): Message timestamp for latency tracking
// - Data (variable): Message payload up to MaxFrameSize
//
// Error Conditions:
// - Invalid magic number: Rejects non-JetKVM messages
// - Message too large: Prevents memory exhaustion
// - Connection errors: Network/socket failures
// - Incomplete reads: Partial message reception
//
// The function uses pooled buffers for efficient memory management and
// ensures all messages conform to the JetKVM audio protocol specification.
func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error) { func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error) {
// Get optimized message from pool // Get optimized message from pool
optMsg := globalMessagePool.Get() optMsg := globalMessagePool.Get()

View File

@ -81,13 +81,14 @@ const (
// DefaultLatencyConfig returns a sensible default configuration // DefaultLatencyConfig returns a sensible default configuration
func DefaultLatencyConfig() LatencyConfig { func DefaultLatencyConfig() LatencyConfig {
config := GetConfig()
return LatencyConfig{ return LatencyConfig{
TargetLatency: 50 * time.Millisecond, TargetLatency: config.LatencyMonitorTarget,
MaxLatency: GetConfig().MaxLatencyThreshold, MaxLatency: config.MaxLatencyThreshold,
OptimizationInterval: 5 * time.Second, OptimizationInterval: config.LatencyOptimizationInterval,
HistorySize: GetConfig().LatencyHistorySize, HistorySize: config.LatencyHistorySize,
JitterThreshold: GetConfig().JitterThreshold, JitterThreshold: config.JitterThreshold,
AdaptiveThreshold: 0.8, // Trigger optimization when 80% above target AdaptiveThreshold: config.LatencyAdaptiveThreshold,
} }
} }
@ -223,7 +224,26 @@ func (lm *LatencyMonitor) monitoringLoop() {
} }
} }
// runOptimization checks if optimization is needed and triggers callbacks // runOptimization checks if optimization is needed and triggers callbacks with threshold validation.
//
// Validation Rules:
// - Current latency must not exceed MaxLatency (default: 200ms)
// - Average latency checked against adaptive threshold: TargetLatency * (1 + AdaptiveThreshold)
// - Jitter must not exceed JitterThreshold (default: 20ms)
// - All latency values must be non-negative durations
//
// Optimization Triggers:
// - Current latency > MaxLatency: Immediate optimization needed
// - Average latency > adaptive threshold: Gradual optimization needed
// - Jitter > JitterThreshold: Stability optimization needed
//
// Threshold Calculations:
// - Adaptive threshold = TargetLatency * (1.0 + AdaptiveThreshold)
// - Default: 50ms * (1.0 + 0.8) = 90ms adaptive threshold
// - Provides buffer above target before triggering optimization
//
// The function ensures real-time audio performance by monitoring multiple
// latency metrics and triggering optimization callbacks when thresholds are exceeded.
func (lm *LatencyMonitor) runOptimization() { func (lm *LatencyMonitor) runOptimization() {
metrics := lm.GetMetrics() metrics := lm.GetMetrics()

View File

@ -63,7 +63,7 @@ func NewOutputStreamer() (*OutputStreamer, error) {
cancel: cancel, cancel: cancel,
batchSize: initialBatchSize, // Use adaptive batch size batchSize: initialBatchSize, // Use adaptive batch size
processingChan: make(chan []byte, GetConfig().ChannelBufferSize), // Large buffer for smooth processing processingChan: make(chan []byte, GetConfig().ChannelBufferSize), // Large buffer for smooth processing
statsInterval: 5 * time.Second, // Statistics every 5 seconds statsInterval: GetConfig().StatsUpdateInterval, // Statistics interval from config
lastStatsTime: time.Now().UnixNano(), lastStatsTime: time.Now().UnixNano(),
}, nil }, nil
} }

View File

@ -245,7 +245,26 @@ func (pm *ProcessMonitor) collectMetrics(pid int, state *processState) (ProcessM
return metric, nil return metric, nil
} }
// calculateCPUPercent calculates CPU percentage for a process // calculateCPUPercent calculates CPU percentage for a process with validation and bounds checking.
//
// Validation Rules:
// - Returns 0.0 for first sample (no baseline for comparison)
// - Requires positive time delta between samples
// - Applies CPU percentage bounds: [MinCPUPercent, MaxCPUPercent]
// - Uses system clock ticks for accurate CPU time conversion
// - Validates clock ticks within range [MinValidClockTicks, MaxValidClockTicks]
//
// Bounds Applied:
// - CPU percentage clamped to [0.01%, 100.0%] (default values)
// - Clock ticks validated within [50, 1000] range (default values)
// - Time delta must be > 0 to prevent division by zero
//
// Warmup Behavior:
// - During warmup period (< WarmupCPUSamples), returns MinCPUPercent for idle processes
// - This indicates process is alive but not consuming significant CPU
//
// The function ensures accurate CPU percentage calculation while preventing
// invalid measurements that could affect system monitoring and adaptive algorithms.
func (pm *ProcessMonitor) calculateCPUPercent(totalCPUTime int64, state *processState, now time.Time) float64 { func (pm *ProcessMonitor) calculateCPUPercent(totalCPUTime int64, state *processState, now time.Time) float64 {
if state.lastSample.IsZero() { if state.lastSample.IsZero() {
// First sample - initialize baseline // First sample - initialize baseline

View File

@ -101,7 +101,23 @@ func GetSocketBufferSizes(conn net.Conn) (sendSize, recvSize int, err error) {
return sendSize, recvSize, nil return sendSize, recvSize, nil
} }
// ValidateSocketBufferConfig validates socket buffer configuration // ValidateSocketBufferConfig validates socket buffer configuration parameters.
//
// Validation Rules:
// - If config.Enabled is false, no validation is performed (returns nil)
// - SendBufferSize must be >= SocketMinBuffer (default: 8192 bytes)
// - RecvBufferSize must be >= SocketMinBuffer (default: 8192 bytes)
// - SendBufferSize must be <= SocketMaxBuffer (default: 1048576 bytes)
// - RecvBufferSize must be <= SocketMaxBuffer (default: 1048576 bytes)
//
// Error Conditions:
// - Returns error if send buffer size is below minimum threshold
// - Returns error if receive buffer size is below minimum threshold
// - Returns error if send buffer size exceeds maximum threshold
// - Returns error if receive buffer size exceeds maximum threshold
//
// The validation ensures socket buffers are sized appropriately for audio streaming
// performance while preventing excessive memory usage.
func ValidateSocketBufferConfig(config SocketBufferConfig) error { func ValidateSocketBufferConfig(config SocketBufferConfig) error {
if !config.Enabled { if !config.Enabled {
return nil return nil