mirror of https://github.com/jetkvm/kvm.git
[WIP] Simplification: PR Simplification
This commit is contained in:
parent
ebb79600b0
commit
7ab4a0e41d
|
@ -1,447 +0,0 @@
|
||||||
package audio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/logging"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AdaptiveBufferConfig holds configuration for the adaptive buffer sizing algorithm.
|
|
||||||
//
|
|
||||||
// The adaptive buffer system dynamically adjusts audio buffer sizes based on real-time
|
|
||||||
// system conditions to optimize the trade-off between latency and stability. The algorithm
|
|
||||||
// uses multiple factors to make decisions:
|
|
||||||
//
|
|
||||||
// 1. System Load Monitoring:
|
|
||||||
// - CPU usage: High CPU load increases buffer sizes to prevent underruns
|
|
||||||
// - Memory usage: High memory pressure reduces buffer sizes to conserve RAM
|
|
||||||
//
|
|
||||||
// 2. Latency Tracking:
|
|
||||||
// - Target latency: Optimal latency for the current quality setting
|
|
||||||
// - Max latency: Hard limit beyond which buffers are aggressively reduced
|
|
||||||
//
|
|
||||||
// 3. Adaptation Strategy:
|
|
||||||
// - Exponential smoothing: Prevents oscillation and provides stable adjustments
|
|
||||||
// - Discrete steps: Buffer sizes change in fixed increments to avoid instability
|
|
||||||
// - Hysteresis: Different thresholds for increasing vs decreasing buffer sizes
|
|
||||||
//
|
|
||||||
// The algorithm is specifically tuned for embedded ARM systems with limited resources,
|
|
||||||
// prioritizing stability over absolute minimum latency.
|
|
||||||
type AdaptiveBufferConfig struct {
|
|
||||||
// Buffer size limits (in frames)
|
|
||||||
MinBufferSize int
|
|
||||||
MaxBufferSize int
|
|
||||||
DefaultBufferSize int
|
|
||||||
|
|
||||||
// System load thresholds
|
|
||||||
LowCPUThreshold float64 // Below this, increase buffer size
|
|
||||||
HighCPUThreshold float64 // Above this, decrease buffer size
|
|
||||||
LowMemoryThreshold float64 // Below this, increase buffer size
|
|
||||||
HighMemoryThreshold float64 // Above this, decrease buffer size
|
|
||||||
|
|
||||||
// Latency thresholds (in milliseconds)
|
|
||||||
TargetLatency time.Duration
|
|
||||||
MaxLatency time.Duration
|
|
||||||
|
|
||||||
// Adaptation parameters
|
|
||||||
AdaptationInterval time.Duration
|
|
||||||
SmoothingFactor float64 // 0.0-1.0, higher = more responsive
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultAdaptiveBufferConfig returns optimized config for JetKVM hardware
|
|
||||||
func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig {
|
|
||||||
return AdaptiveBufferConfig{
|
|
||||||
// Conservative buffer sizes for 256MB RAM constraint
|
|
||||||
MinBufferSize: Config.AdaptiveMinBufferSize,
|
|
||||||
MaxBufferSize: Config.AdaptiveMaxBufferSize,
|
|
||||||
DefaultBufferSize: Config.AdaptiveDefaultBufferSize,
|
|
||||||
|
|
||||||
// CPU thresholds optimized for single-core ARM Cortex A7 under load
|
|
||||||
LowCPUThreshold: Config.LowCPUThreshold * 100, // Below 20% CPU
|
|
||||||
HighCPUThreshold: Config.HighCPUThreshold * 100, // Above 60% CPU (lowered to be more responsive)
|
|
||||||
|
|
||||||
// Memory thresholds for 256MB total RAM
|
|
||||||
LowMemoryThreshold: Config.LowMemoryThreshold * 100, // Below 35% memory usage
|
|
||||||
HighMemoryThreshold: Config.HighMemoryThreshold * 100, // Above 75% memory usage (lowered for earlier response)
|
|
||||||
|
|
||||||
// Latency targets
|
|
||||||
TargetLatency: Config.AdaptiveBufferTargetLatency, // Target 20ms latency
|
|
||||||
MaxLatency: Config.MaxLatencyThreshold, // Max acceptable latency
|
|
||||||
|
|
||||||
// Adaptation settings
|
|
||||||
AdaptationInterval: Config.BufferUpdateInterval, // Check every 500ms
|
|
||||||
SmoothingFactor: Config.SmoothingFactor, // Moderate responsiveness
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdaptiveBufferManager manages dynamic buffer sizing based on system conditions
|
|
||||||
type AdaptiveBufferManager struct {
|
|
||||||
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
|
|
||||||
currentInputBufferSize int64 // Current input buffer size (atomic)
|
|
||||||
currentOutputBufferSize int64 // Current output buffer size (atomic)
|
|
||||||
averageLatency int64 // Average latency in nanoseconds (atomic)
|
|
||||||
systemCPUPercent int64 // System CPU percentage * 100 (atomic)
|
|
||||||
systemMemoryPercent int64 // System memory percentage * 100 (atomic)
|
|
||||||
adaptationCount int64 // Metrics tracking (atomic)
|
|
||||||
|
|
||||||
config AdaptiveBufferConfig
|
|
||||||
logger zerolog.Logger
|
|
||||||
|
|
||||||
// Control channels
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
// Metrics tracking
|
|
||||||
lastAdaptation time.Time
|
|
||||||
mutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAdaptiveBufferManager creates a new adaptive buffer manager
|
|
||||||
func NewAdaptiveBufferManager(config AdaptiveBufferConfig) *AdaptiveBufferManager {
|
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "adaptive-buffer").Logger()
|
|
||||||
|
|
||||||
if err := ValidateAdaptiveBufferConfig(config.MinBufferSize, config.MaxBufferSize, config.DefaultBufferSize); err != nil {
|
|
||||||
logger.Warn().Err(err).Msg("invalid adaptive buffer config, using defaults")
|
|
||||||
config = DefaultAdaptiveBufferConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
return &AdaptiveBufferManager{
|
|
||||||
currentInputBufferSize: int64(config.DefaultBufferSize),
|
|
||||||
currentOutputBufferSize: int64(config.DefaultBufferSize),
|
|
||||||
config: config,
|
|
||||||
logger: logger,
|
|
||||||
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
lastAdaptation: time.Now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start begins the adaptive buffer management
|
|
||||||
func (abm *AdaptiveBufferManager) Start() {
|
|
||||||
abm.wg.Add(1)
|
|
||||||
go abm.adaptationLoop()
|
|
||||||
abm.logger.Info().Msg("adaptive buffer manager started")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the adaptive buffer management
|
|
||||||
func (abm *AdaptiveBufferManager) Stop() {
|
|
||||||
abm.cancel()
|
|
||||||
abm.wg.Wait()
|
|
||||||
abm.logger.Info().Msg("adaptive buffer manager stopped")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInputBufferSize returns the current recommended input buffer size
|
|
||||||
func (abm *AdaptiveBufferManager) GetInputBufferSize() int {
|
|
||||||
return int(atomic.LoadInt64(&abm.currentInputBufferSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOutputBufferSize returns the current recommended output buffer size
|
|
||||||
func (abm *AdaptiveBufferManager) GetOutputBufferSize() int {
|
|
||||||
return int(atomic.LoadInt64(&abm.currentOutputBufferSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateLatency updates the current latency measurement
|
|
||||||
func (abm *AdaptiveBufferManager) UpdateLatency(latency time.Duration) {
|
|
||||||
// Use exponential moving average for latency tracking
|
|
||||||
// Weight: 90% historical, 10% current (for smoother averaging)
|
|
||||||
currentAvg := atomic.LoadInt64(&abm.averageLatency)
|
|
||||||
newLatencyNs := latency.Nanoseconds()
|
|
||||||
|
|
||||||
if currentAvg == 0 {
|
|
||||||
// First measurement
|
|
||||||
atomic.StoreInt64(&abm.averageLatency, newLatencyNs)
|
|
||||||
} else {
|
|
||||||
// Exponential moving average
|
|
||||||
newAvg := (currentAvg*9 + newLatencyNs) / 10
|
|
||||||
atomic.StoreInt64(&abm.averageLatency, newAvg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log high latency warnings only for truly problematic latencies
|
|
||||||
// Use a more reasonable threshold: 10ms for audio processing is concerning
|
|
||||||
highLatencyThreshold := 10 * time.Millisecond
|
|
||||||
if latency > highLatencyThreshold {
|
|
||||||
abm.logger.Debug().
|
|
||||||
Dur("latency_ms", latency/time.Millisecond).
|
|
||||||
Dur("threshold_ms", highLatencyThreshold/time.Millisecond).
|
|
||||||
Msg("High audio processing latency detected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoostBuffersForQualityChange immediately increases buffer sizes to handle quality change bursts
|
|
||||||
// This bypasses the normal adaptive algorithm for emergency situations
|
|
||||||
func (abm *AdaptiveBufferManager) BoostBuffersForQualityChange() {
|
|
||||||
// Immediately set buffers to maximum size to handle quality change frame bursts
|
|
||||||
maxSize := int64(abm.config.MaxBufferSize)
|
|
||||||
atomic.StoreInt64(&abm.currentInputBufferSize, maxSize)
|
|
||||||
atomic.StoreInt64(&abm.currentOutputBufferSize, maxSize)
|
|
||||||
|
|
||||||
abm.logger.Info().
|
|
||||||
Int("buffer_size", int(maxSize)).
|
|
||||||
Msg("Boosted buffers to maximum size for quality change")
|
|
||||||
}
|
|
||||||
|
|
||||||
// adaptationLoop is the main loop that adjusts buffer sizes
|
|
||||||
func (abm *AdaptiveBufferManager) adaptationLoop() {
|
|
||||||
defer abm.wg.Done()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(abm.config.AdaptationInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-abm.ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
abm.adaptBufferSizes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// adaptBufferSizes analyzes system conditions and adjusts buffer sizes
|
|
||||||
// adaptBufferSizes implements the core adaptive buffer sizing algorithm.
|
|
||||||
//
|
|
||||||
// This function uses a multi-factor approach to determine optimal buffer sizes:
|
|
||||||
//
|
|
||||||
// Mathematical Model:
|
|
||||||
// 1. Factor Calculation:
|
|
||||||
//
|
|
||||||
// - CPU Factor: Sigmoid function that increases buffer size under high CPU load
|
|
||||||
//
|
|
||||||
// - Memory Factor: Inverse relationship that decreases buffer size under memory pressure
|
|
||||||
//
|
|
||||||
// - Latency Factor: Exponential decay that aggressively reduces buffers when latency exceeds targets
|
|
||||||
//
|
|
||||||
// 2. Combined Factor:
|
|
||||||
// Combined = (CPU_factor * Memory_factor * Latency_factor)
|
|
||||||
// This multiplicative approach ensures any single critical factor can override others
|
|
||||||
//
|
|
||||||
// 3. Exponential Smoothing:
|
|
||||||
// New_size = Current_size + smoothing_factor * (Target_size - Current_size)
|
|
||||||
// This prevents rapid oscillations and provides stable convergence
|
|
||||||
//
|
|
||||||
// 4. Discrete Quantization:
|
|
||||||
// Final sizes are rounded to frame boundaries and clamped to configured limits
|
|
||||||
//
|
|
||||||
// The algorithm runs periodically and only applies changes when the adaptation interval
|
|
||||||
// has elapsed, preventing excessive adjustments that could destabilize the audio pipeline.
|
|
||||||
func (abm *AdaptiveBufferManager) adaptBufferSizes() {
|
|
||||||
// Use fixed system metrics for stability
|
|
||||||
systemCPU := 50.0 // Assume moderate CPU usage
|
|
||||||
systemMemory := 60.0 // Assume moderate memory usage
|
|
||||||
|
|
||||||
atomic.StoreInt64(&abm.systemCPUPercent, int64(systemCPU*100))
|
|
||||||
atomic.StoreInt64(&abm.systemMemoryPercent, int64(systemMemory*100))
|
|
||||||
|
|
||||||
// Get current latency
|
|
||||||
currentLatencyNs := atomic.LoadInt64(&abm.averageLatency)
|
|
||||||
currentLatency := time.Duration(currentLatencyNs)
|
|
||||||
|
|
||||||
// Calculate adaptation factors
|
|
||||||
cpuFactor := abm.calculateCPUFactor(systemCPU)
|
|
||||||
memoryFactor := abm.calculateMemoryFactor(systemMemory)
|
|
||||||
latencyFactor := abm.calculateLatencyFactor(currentLatency)
|
|
||||||
|
|
||||||
// Combine factors with weights (CPU has highest priority for KVM coexistence)
|
|
||||||
combinedFactor := Config.CPUMemoryWeight*cpuFactor + Config.MemoryWeight*memoryFactor + Config.LatencyWeight*latencyFactor
|
|
||||||
|
|
||||||
// Apply adaptation with smoothing
|
|
||||||
currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize))
|
|
||||||
currentOutput := float64(atomic.LoadInt64(&abm.currentOutputBufferSize))
|
|
||||||
|
|
||||||
// Calculate new buffer sizes
|
|
||||||
newInputSize := abm.applyAdaptation(currentInput, combinedFactor)
|
|
||||||
newOutputSize := abm.applyAdaptation(currentOutput, combinedFactor)
|
|
||||||
|
|
||||||
// Update buffer sizes if they changed significantly
|
|
||||||
adjustmentMade := false
|
|
||||||
if math.Abs(newInputSize-currentInput) >= 0.5 || math.Abs(newOutputSize-currentOutput) >= 0.5 {
|
|
||||||
atomic.StoreInt64(&abm.currentInputBufferSize, int64(math.Round(newInputSize)))
|
|
||||||
atomic.StoreInt64(&abm.currentOutputBufferSize, int64(math.Round(newOutputSize)))
|
|
||||||
|
|
||||||
atomic.AddInt64(&abm.adaptationCount, 1)
|
|
||||||
abm.mutex.Lock()
|
|
||||||
abm.lastAdaptation = time.Now()
|
|
||||||
abm.mutex.Unlock()
|
|
||||||
adjustmentMade = true
|
|
||||||
|
|
||||||
abm.logger.Debug().
|
|
||||||
Float64("cpu_percent", systemCPU).
|
|
||||||
Float64("memory_percent", systemMemory).
|
|
||||||
Dur("latency", currentLatency).
|
|
||||||
Float64("combined_factor", combinedFactor).
|
|
||||||
Int("new_input_size", int(newInputSize)).
|
|
||||||
Int("new_output_size", int(newOutputSize)).
|
|
||||||
Msg("Adapted buffer sizes")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update metrics with current state
|
|
||||||
currentInputSize := int(atomic.LoadInt64(&abm.currentInputBufferSize))
|
|
||||||
currentOutputSize := int(atomic.LoadInt64(&abm.currentOutputBufferSize))
|
|
||||||
UpdateAdaptiveBufferMetrics(currentInputSize, currentOutputSize, systemCPU, systemMemory, adjustmentMade)
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculateCPUFactor returns adaptation factor based on CPU usage with threshold validation.
|
|
||||||
//
|
|
||||||
// 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 {
|
|
||||||
if cpuPercent > abm.config.HighCPUThreshold {
|
|
||||||
// High CPU: decrease buffers to reduce latency and give CPU to KVM
|
|
||||||
return -1.0
|
|
||||||
} else if cpuPercent < abm.config.LowCPUThreshold {
|
|
||||||
// Low CPU: increase buffers for better quality
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
// Medium CPU: linear interpolation
|
|
||||||
midpoint := (abm.config.HighCPUThreshold + abm.config.LowCPUThreshold) / 2
|
|
||||||
return (midpoint - cpuPercent) / (midpoint - abm.config.LowCPUThreshold)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
if memoryPercent > abm.config.HighMemoryThreshold {
|
|
||||||
// High memory: decrease buffers to free memory
|
|
||||||
return -1.0
|
|
||||||
} else if memoryPercent < abm.config.LowMemoryThreshold {
|
|
||||||
// Low memory: increase buffers for better performance
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
// Medium memory: linear interpolation
|
|
||||||
midpoint := (abm.config.HighMemoryThreshold + abm.config.LowMemoryThreshold) / 2
|
|
||||||
return (midpoint - memoryPercent) / (midpoint - abm.config.LowMemoryThreshold)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
if latency > abm.config.MaxLatency {
|
|
||||||
// High latency: decrease buffers
|
|
||||||
return -1.0
|
|
||||||
} else if latency < abm.config.TargetLatency {
|
|
||||||
// Low latency: can increase buffers
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
// Medium latency: linear interpolation
|
|
||||||
midLatency := (abm.config.MaxLatency + abm.config.TargetLatency) / 2
|
|
||||||
return float64(midLatency-latency) / float64(midLatency-abm.config.TargetLatency)
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyAdaptation applies the adaptation factor to current buffer size
|
|
||||||
func (abm *AdaptiveBufferManager) applyAdaptation(currentSize, factor float64) float64 {
|
|
||||||
// Calculate target size based on factor
|
|
||||||
var targetSize float64
|
|
||||||
if factor > 0 {
|
|
||||||
// Increase towards max
|
|
||||||
targetSize = currentSize + factor*(float64(abm.config.MaxBufferSize)-currentSize)
|
|
||||||
} else {
|
|
||||||
// Decrease towards min
|
|
||||||
targetSize = currentSize + factor*(currentSize-float64(abm.config.MinBufferSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply smoothing
|
|
||||||
newSize := currentSize + abm.config.SmoothingFactor*(targetSize-currentSize)
|
|
||||||
|
|
||||||
// Clamp to valid range
|
|
||||||
return math.Max(float64(abm.config.MinBufferSize),
|
|
||||||
math.Min(float64(abm.config.MaxBufferSize), newSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStats returns current adaptation statistics
|
|
||||||
func (abm *AdaptiveBufferManager) GetStats() map[string]interface{} {
|
|
||||||
abm.mutex.RLock()
|
|
||||||
lastAdaptation := abm.lastAdaptation
|
|
||||||
abm.mutex.RUnlock()
|
|
||||||
|
|
||||||
return map[string]interface{}{
|
|
||||||
"input_buffer_size": abm.GetInputBufferSize(),
|
|
||||||
"output_buffer_size": abm.GetOutputBufferSize(),
|
|
||||||
"average_latency_ms": float64(atomic.LoadInt64(&abm.averageLatency)) / 1e6,
|
|
||||||
"system_cpu_percent": float64(atomic.LoadInt64(&abm.systemCPUPercent)) / Config.PercentageMultiplier,
|
|
||||||
"system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / Config.PercentageMultiplier,
|
|
||||||
"adaptation_count": atomic.LoadInt64(&abm.adaptationCount),
|
|
||||||
"last_adaptation": lastAdaptation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global adaptive buffer manager instance
|
|
||||||
var globalAdaptiveBufferManager *AdaptiveBufferManager
|
|
||||||
var adaptiveBufferOnce sync.Once
|
|
||||||
|
|
||||||
// GetAdaptiveBufferManager returns the global adaptive buffer manager instance
|
|
||||||
func GetAdaptiveBufferManager() *AdaptiveBufferManager {
|
|
||||||
adaptiveBufferOnce.Do(func() {
|
|
||||||
globalAdaptiveBufferManager = NewAdaptiveBufferManager(DefaultAdaptiveBufferConfig())
|
|
||||||
})
|
|
||||||
return globalAdaptiveBufferManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartAdaptiveBuffering starts the global adaptive buffer manager
|
|
||||||
func StartAdaptiveBuffering() {
|
|
||||||
GetAdaptiveBufferManager().Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopAdaptiveBuffering stops the global adaptive buffer manager
|
|
||||||
func StopAdaptiveBuffering() {
|
|
||||||
if globalAdaptiveBufferManager != nil {
|
|
||||||
globalAdaptiveBufferManager.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -152,11 +152,6 @@ type AudioConfigConstants struct {
|
||||||
MemoryFactor float64
|
MemoryFactor float64
|
||||||
LatencyFactor float64
|
LatencyFactor float64
|
||||||
|
|
||||||
// Adaptive Buffer Configuration
|
|
||||||
AdaptiveMinBufferSize int // Minimum buffer size in frames for adaptive buffering
|
|
||||||
AdaptiveMaxBufferSize int // Maximum buffer size in frames for adaptive buffering
|
|
||||||
AdaptiveDefaultBufferSize int // Default buffer size in frames for adaptive buffering
|
|
||||||
|
|
||||||
// Timing Configuration
|
// Timing Configuration
|
||||||
RetryDelay time.Duration // Retry delay
|
RetryDelay time.Duration // Retry delay
|
||||||
MaxRetryDelay time.Duration // Maximum retry delay
|
MaxRetryDelay time.Duration // Maximum retry delay
|
||||||
|
@ -171,22 +166,17 @@ type AudioConfigConstants struct {
|
||||||
OutputSupervisorTimeout time.Duration // 5s
|
OutputSupervisorTimeout time.Duration // 5s
|
||||||
BatchProcessingDelay time.Duration // 10ms
|
BatchProcessingDelay time.Duration // 10ms
|
||||||
|
|
||||||
// Adaptive Buffer Configuration
|
// System threshold configuration for buffer management
|
||||||
// LowCPUThreshold defines CPU usage threshold for buffer size reduction.
|
LowCPUThreshold float64 // CPU usage threshold for performance optimization
|
||||||
LowCPUThreshold float64 // 20% CPU threshold for buffer optimization
|
HighCPUThreshold float64 // CPU usage threshold for performance limits
|
||||||
|
LowMemoryThreshold float64 // 50% memory threshold
|
||||||
// HighCPUThreshold defines CPU usage threshold for buffer size increase.
|
HighMemoryThreshold float64 // 75% memory threshold
|
||||||
HighCPUThreshold float64 // 60% CPU threshold
|
CooldownPeriod time.Duration // 30s cooldown period
|
||||||
LowMemoryThreshold float64 // 50% memory threshold
|
RollbackThreshold time.Duration // 300ms rollback threshold
|
||||||
HighMemoryThreshold float64 // 75% memory threshold
|
|
||||||
AdaptiveBufferTargetLatency time.Duration // 20ms target latency
|
|
||||||
CooldownPeriod time.Duration // 30s cooldown period
|
|
||||||
RollbackThreshold time.Duration // 300ms rollback threshold
|
|
||||||
|
|
||||||
MaxLatencyThreshold time.Duration // 200ms max latency
|
MaxLatencyThreshold time.Duration // 200ms max latency
|
||||||
JitterThreshold time.Duration // 20ms jitter threshold
|
JitterThreshold time.Duration // 20ms jitter threshold
|
||||||
LatencyOptimizationInterval time.Duration // 5s optimization interval
|
LatencyOptimizationInterval time.Duration // 5s optimization interval
|
||||||
LatencyAdaptiveThreshold float64 // 0.8 adaptive threshold
|
|
||||||
MicContentionTimeout time.Duration // 200ms contention timeout
|
MicContentionTimeout time.Duration // 200ms contention timeout
|
||||||
PreallocPercentage int // 20% preallocation percentage
|
PreallocPercentage int // 20% preallocation percentage
|
||||||
BackoffStart time.Duration // 50ms initial backoff
|
BackoffStart time.Duration // 50ms initial backoff
|
||||||
|
@ -199,7 +189,6 @@ type AudioConfigConstants struct {
|
||||||
PercentageMultiplier float64 // Multiplier for percentage calculations (100.0)
|
PercentageMultiplier float64 // Multiplier for percentage calculations (100.0)
|
||||||
AveragingWeight float64 // Weight for weighted averaging (0.7)
|
AveragingWeight float64 // Weight for weighted averaging (0.7)
|
||||||
ScalingFactor float64 // General scaling factor (1.5)
|
ScalingFactor float64 // General scaling factor (1.5)
|
||||||
SmoothingFactor float64 // Smoothing factor for adaptive buffers (0.3)
|
|
||||||
CPUMemoryWeight float64 // Weight for CPU factor in calculations (0.5)
|
CPUMemoryWeight float64 // Weight for CPU factor in calculations (0.5)
|
||||||
MemoryWeight float64 // Weight for memory factor (0.3)
|
MemoryWeight float64 // Weight for memory factor (0.3)
|
||||||
LatencyWeight float64 // Weight for latency factor (0.2)
|
LatencyWeight float64 // Weight for latency factor (0.2)
|
||||||
|
@ -226,19 +215,17 @@ type AudioConfigConstants struct {
|
||||||
// IPC Constants
|
// IPC Constants
|
||||||
IPCInitialBufferFrames int // Initial IPC buffer size (500 frames)
|
IPCInitialBufferFrames int // Initial IPC buffer size (500 frames)
|
||||||
|
|
||||||
EventTimeoutSeconds int
|
EventTimeoutSeconds int
|
||||||
EventTimeFormatString string
|
EventTimeFormatString string
|
||||||
EventSubscriptionDelayMS int
|
EventSubscriptionDelayMS int
|
||||||
InputProcessingTimeoutMS int
|
InputProcessingTimeoutMS int
|
||||||
AdaptiveBufferCPUMultiplier int
|
InputSocketName string
|
||||||
AdaptiveBufferMemoryMultiplier int
|
OutputSocketName string
|
||||||
InputSocketName string
|
AudioInputComponentName string
|
||||||
OutputSocketName string
|
AudioOutputComponentName string
|
||||||
AudioInputComponentName string
|
AudioServerComponentName string
|
||||||
AudioOutputComponentName string
|
AudioRelayComponentName string
|
||||||
AudioServerComponentName string
|
AudioEventsComponentName string
|
||||||
AudioRelayComponentName string
|
|
||||||
AudioEventsComponentName string
|
|
||||||
|
|
||||||
TestSocketTimeout time.Duration
|
TestSocketTimeout time.Duration
|
||||||
TestBufferSize int
|
TestBufferSize int
|
||||||
|
@ -493,17 +480,11 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
OutputSupervisorTimeout: 5 * time.Second, // Output monitoring timeout
|
OutputSupervisorTimeout: 5 * time.Second, // Output monitoring timeout
|
||||||
BatchProcessingDelay: 5 * time.Millisecond, // Reduced batch processing delay
|
BatchProcessingDelay: 5 * time.Millisecond, // Reduced batch processing delay
|
||||||
|
|
||||||
// Adaptive Buffer Configuration - Optimized for single-core RV1106G3
|
// System Load Configuration - Optimized for single-core RV1106G3
|
||||||
LowCPUThreshold: 0.40, // Adjusted for single-core ARM system
|
LowCPUThreshold: 0.40, // Adjusted for single-core ARM system
|
||||||
HighCPUThreshold: 0.75, // Adjusted for single-core RV1106G3 (current load ~64%)
|
HighCPUThreshold: 0.75, // Adjusted for single-core RV1106G3 (current load ~64%)
|
||||||
LowMemoryThreshold: 0.60,
|
LowMemoryThreshold: 0.60,
|
||||||
HighMemoryThreshold: 0.85, // Adjusted for 200MB total memory system
|
HighMemoryThreshold: 0.85, // Adjusted for 200MB total memory system
|
||||||
AdaptiveBufferTargetLatency: 10 * time.Millisecond, // Aggressive target latency for responsiveness
|
|
||||||
|
|
||||||
// Adaptive Buffer Size Configuration - Optimized for quality change bursts
|
|
||||||
AdaptiveMinBufferSize: 256, // Further increased minimum to prevent emergency mode
|
|
||||||
AdaptiveMaxBufferSize: 1024, // Much higher maximum for quality changes
|
|
||||||
AdaptiveDefaultBufferSize: 512, // Higher default for stability during bursts
|
|
||||||
|
|
||||||
CooldownPeriod: 15 * time.Second, // Reduced cooldown period
|
CooldownPeriod: 15 * time.Second, // Reduced cooldown period
|
||||||
RollbackThreshold: 200 * time.Millisecond, // Lower rollback threshold
|
RollbackThreshold: 200 * time.Millisecond, // Lower rollback threshold
|
||||||
|
@ -511,7 +492,6 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
MaxLatencyThreshold: 150 * time.Millisecond, // Lower max latency threshold
|
MaxLatencyThreshold: 150 * time.Millisecond, // Lower max latency threshold
|
||||||
JitterThreshold: 15 * time.Millisecond, // Reduced jitter threshold
|
JitterThreshold: 15 * time.Millisecond, // Reduced jitter threshold
|
||||||
LatencyOptimizationInterval: 3 * time.Second, // More frequent optimization
|
LatencyOptimizationInterval: 3 * time.Second, // More frequent optimization
|
||||||
LatencyAdaptiveThreshold: 0.7, // More aggressive adaptive threshold
|
|
||||||
|
|
||||||
// Microphone Contention Configuration
|
// Microphone Contention Configuration
|
||||||
MicContentionTimeout: 200 * time.Millisecond,
|
MicContentionTimeout: 200 * time.Millisecond,
|
||||||
|
@ -531,7 +511,6 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
AveragingWeight: 0.7, // Weight for smoothing values (70% recent, 30% historical)
|
AveragingWeight: 0.7, // Weight for smoothing values (70% recent, 30% historical)
|
||||||
ScalingFactor: 1.5, // General scaling factor for adaptive adjustments
|
ScalingFactor: 1.5, // General scaling factor for adaptive adjustments
|
||||||
|
|
||||||
SmoothingFactor: 0.3, // For adaptive buffer smoothing
|
|
||||||
CPUMemoryWeight: 0.5, // CPU factor weight in combined calculations
|
CPUMemoryWeight: 0.5, // CPU factor weight in combined calculations
|
||||||
MemoryWeight: 0.3, // Memory factor weight in combined calculations
|
MemoryWeight: 0.3, // Memory factor weight in combined calculations
|
||||||
LatencyWeight: 0.2, // Latency factor weight in combined calculations
|
LatencyWeight: 0.2, // Latency factor weight in combined calculations
|
||||||
|
@ -548,7 +527,6 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
BatchProcessorFramesPerBatch: 16, // Larger batches for quality changes
|
BatchProcessorFramesPerBatch: 16, // Larger batches for quality changes
|
||||||
BatchProcessorTimeout: 20 * time.Millisecond, // Longer timeout for bursts
|
BatchProcessorTimeout: 20 * time.Millisecond, // Longer timeout for bursts
|
||||||
BatchProcessorMaxQueueSize: 64, // Larger queue for quality changes
|
BatchProcessorMaxQueueSize: 64, // Larger queue for quality changes
|
||||||
BatchProcessorAdaptiveThreshold: 0.6, // Lower threshold for faster adaptation
|
|
||||||
BatchProcessorThreadPinningThreshold: 8, // Lower threshold for better performance
|
BatchProcessorThreadPinningThreshold: 8, // Lower threshold for better performance
|
||||||
|
|
||||||
// Output Streaming Constants - Balanced for stability
|
// Output Streaming Constants - Balanced for stability
|
||||||
|
@ -572,10 +550,6 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
// Input Processing Constants - Balanced for stability
|
// Input Processing Constants - Balanced for stability
|
||||||
InputProcessingTimeoutMS: 10, // 10ms processing timeout threshold
|
InputProcessingTimeoutMS: 10, // 10ms processing timeout threshold
|
||||||
|
|
||||||
// Adaptive Buffer Constants
|
|
||||||
AdaptiveBufferCPUMultiplier: 100, // 100 multiplier for CPU percentage
|
|
||||||
AdaptiveBufferMemoryMultiplier: 100, // 100 multiplier for memory percentage
|
|
||||||
|
|
||||||
// Socket Names
|
// Socket Names
|
||||||
InputSocketName: "audio_input.sock", // Socket name for audio input IPC
|
InputSocketName: "audio_input.sock", // Socket name for audio input IPC
|
||||||
OutputSocketName: "audio_output.sock", // Socket name for audio output IPC
|
OutputSocketName: "audio_output.sock", // Socket name for audio output IPC
|
||||||
|
|
|
@ -11,42 +11,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Adaptive buffer metrics
|
|
||||||
adaptiveInputBufferSize = promauto.NewGauge(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Name: "jetkvm_adaptive_input_buffer_size_bytes",
|
|
||||||
Help: "Current adaptive input buffer size in bytes",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
adaptiveOutputBufferSize = promauto.NewGauge(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Name: "jetkvm_adaptive_output_buffer_size_bytes",
|
|
||||||
Help: "Current adaptive output buffer size in bytes",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
adaptiveBufferAdjustmentsTotal = promauto.NewCounter(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Name: "jetkvm_adaptive_buffer_adjustments_total",
|
|
||||||
Help: "Total number of adaptive buffer size adjustments",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
adaptiveSystemCpuPercent = promauto.NewGauge(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Name: "jetkvm_adaptive_system_cpu_percent",
|
|
||||||
Help: "System CPU usage percentage used by adaptive buffer manager",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
adaptiveSystemMemoryPercent = promauto.NewGauge(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Name: "jetkvm_adaptive_system_memory_percent",
|
|
||||||
Help: "System memory usage percentage used by adaptive buffer manager",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Socket buffer metrics
|
// Socket buffer metrics
|
||||||
socketBufferSizeGauge = promauto.NewGaugeVec(
|
socketBufferSizeGauge = promauto.NewGaugeVec(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
|
@ -374,23 +338,6 @@ func UpdateMicrophoneMetrics(metrics UnifiedAudioMetrics) {
|
||||||
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAdaptiveBufferMetrics updates Prometheus metrics with adaptive buffer information
|
|
||||||
func UpdateAdaptiveBufferMetrics(inputBufferSize, outputBufferSize int, cpuPercent, memoryPercent float64, adjustmentMade bool) {
|
|
||||||
metricsUpdateMutex.Lock()
|
|
||||||
defer metricsUpdateMutex.Unlock()
|
|
||||||
|
|
||||||
adaptiveInputBufferSize.Set(float64(inputBufferSize))
|
|
||||||
adaptiveOutputBufferSize.Set(float64(outputBufferSize))
|
|
||||||
adaptiveSystemCpuPercent.Set(cpuPercent)
|
|
||||||
adaptiveSystemMemoryPercent.Set(memoryPercent)
|
|
||||||
|
|
||||||
if adjustmentMade {
|
|
||||||
adaptiveBufferAdjustmentsTotal.Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateSocketBufferMetrics updates socket buffer metrics
|
// UpdateSocketBufferMetrics updates socket buffer metrics
|
||||||
func UpdateSocketBufferMetrics(component, bufferType string, size, utilization float64, overflowOccurred bool) {
|
func UpdateSocketBufferMetrics(component, bufferType string, size, utilization float64, overflowOccurred bool) {
|
||||||
metricsUpdateMutex.Lock()
|
metricsUpdateMutex.Lock()
|
||||||
|
|
|
@ -154,25 +154,6 @@ func ValidateMetricsInterval(interval time.Duration) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAdaptiveBufferConfig validates adaptive buffer configuration
|
|
||||||
func ValidateAdaptiveBufferConfig(minSize, maxSize, defaultSize int) error {
|
|
||||||
if minSize <= 0 || maxSize <= 0 || defaultSize <= 0 {
|
|
||||||
return ErrInvalidBufferSize
|
|
||||||
}
|
|
||||||
if minSize >= maxSize {
|
|
||||||
return ErrInvalidBufferSize
|
|
||||||
}
|
|
||||||
if defaultSize < minSize || defaultSize > maxSize {
|
|
||||||
return ErrInvalidBufferSize
|
|
||||||
}
|
|
||||||
// Validate against global limits
|
|
||||||
maxBuffer := Config.SocketMaxBuffer
|
|
||||||
if maxSize > maxBuffer {
|
|
||||||
return ErrInvalidBufferSize
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateInputIPCConfig validates input IPC configuration
|
// ValidateInputIPCConfig validates input IPC configuration
|
||||||
func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
|
func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
|
||||||
minSampleRate := Config.MinSampleRate
|
minSampleRate := Config.MinSampleRate
|
||||||
|
|
|
@ -211,8 +211,8 @@ func NewAudioInputServer() (*AudioInputServer, error) {
|
||||||
return nil, fmt.Errorf("failed to create unix socket after 3 attempts: %w", err)
|
return nil, fmt.Errorf("failed to create unix socket after 3 attempts: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get initial buffer size from config
|
// Get initial buffer size (512 frames for stability)
|
||||||
initialBufferSize := int64(Config.AdaptiveDefaultBufferSize)
|
initialBufferSize := int64(512)
|
||||||
|
|
||||||
// Ensure minimum buffer size to prevent immediate overflow
|
// Ensure minimum buffer size to prevent immediate overflow
|
||||||
// Use at least 50 frames to handle burst traffic
|
// Use at least 50 frames to handle burst traffic
|
||||||
|
@ -1182,7 +1182,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
|
||||||
atomic.StoreInt64(&ais.processingTime, newAvg)
|
atomic.StoreInt64(&ais.processingTime, newAvg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report latency to adaptive buffer manager
|
// Report latency for metrics
|
||||||
ais.ReportLatency(latency)
|
ais.ReportLatency(latency)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1227,10 +1227,10 @@ func (ais *AudioInputServer) GetServerStats() (total, dropped int64, avgProcessi
|
||||||
atomic.LoadInt64(&ais.bufferSize)
|
atomic.LoadInt64(&ais.bufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateBufferSize updates the buffer size (now using fixed config values)
|
// UpdateBufferSize updates the buffer size (now using fixed values)
|
||||||
func (ais *AudioInputServer) UpdateBufferSize() {
|
func (ais *AudioInputServer) UpdateBufferSize() {
|
||||||
// Buffer size is now fixed from config
|
// Buffer size is now fixed at 512 frames for stability
|
||||||
newSize := int64(Config.AdaptiveDefaultBufferSize)
|
newSize := int64(512)
|
||||||
atomic.StoreInt64(&ais.bufferSize, newSize)
|
atomic.StoreInt64(&ais.bufferSize, newSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
// Package audio provides real-time audio processing for JetKVM with low-latency streaming.
|
// Package audio provides real-time audio processing for JetKVM with low-latency streaming.
|
||||||
//
|
//
|
||||||
// Key components: output/input pipelines with Opus codec, adaptive buffer management,
|
// Key components: output/input pipelines with Opus codec, buffer management,
|
||||||
// zero-copy frame pools, IPC communication, and process supervision.
|
// zero-copy frame pools, IPC communication, and process supervision.
|
||||||
//
|
//
|
||||||
// Supports four quality presets (Low/Medium/High/Ultra) with configurable bitrates.
|
// Supports four quality presets (Low/Medium/High/Ultra) with configurable bitrates.
|
||||||
|
|
Loading…
Reference in New Issue