refactor(audio): replace GetConfig() calls with direct Config access

This change replaces all instances of GetConfig() function calls with direct access to the Config variable throughout the audio package. The modification improves performance by eliminating function call overhead and simplifies the codebase by removing unnecessary indirection.

The commit also includes minor optimizations in validation logic and connection handling, while maintaining all existing functionality. Error handling remains robust with appropriate fallbacks when config values are not available.

Additional improvements include:
- Enhanced connection health monitoring in UnifiedAudioClient
- Optimized validation functions using cached config values
- Reduced memory allocations in hot paths
- Improved error recovery during quality changes
This commit is contained in:
Alex P 2025-09-08 17:30:49 +00:00
parent 91f9dba4c6
commit 1d1658db15
32 changed files with 501 additions and 346 deletions

View File

@ -57,25 +57,25 @@ type AdaptiveBufferConfig struct {
func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig { func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig {
return AdaptiveBufferConfig{ return AdaptiveBufferConfig{
// Conservative buffer sizes for 256MB RAM constraint // Conservative buffer sizes for 256MB RAM constraint
MinBufferSize: GetConfig().AdaptiveMinBufferSize, MinBufferSize: Config.AdaptiveMinBufferSize,
MaxBufferSize: GetConfig().AdaptiveMaxBufferSize, MaxBufferSize: Config.AdaptiveMaxBufferSize,
DefaultBufferSize: GetConfig().AdaptiveDefaultBufferSize, DefaultBufferSize: Config.AdaptiveDefaultBufferSize,
// CPU thresholds optimized for single-core ARM Cortex A7 under load // CPU thresholds optimized for single-core ARM Cortex A7 under load
LowCPUThreshold: GetConfig().LowCPUThreshold * 100, // Below 20% CPU LowCPUThreshold: Config.LowCPUThreshold * 100, // Below 20% CPU
HighCPUThreshold: GetConfig().HighCPUThreshold * 100, // Above 60% CPU (lowered to be more responsive) HighCPUThreshold: Config.HighCPUThreshold * 100, // Above 60% CPU (lowered to be more responsive)
// Memory thresholds for 256MB total RAM // Memory thresholds for 256MB total RAM
LowMemoryThreshold: GetConfig().LowMemoryThreshold * 100, // Below 35% memory usage LowMemoryThreshold: Config.LowMemoryThreshold * 100, // Below 35% memory usage
HighMemoryThreshold: GetConfig().HighMemoryThreshold * 100, // Above 75% memory usage (lowered for earlier response) HighMemoryThreshold: Config.HighMemoryThreshold * 100, // Above 75% memory usage (lowered for earlier response)
// Latency targets // Latency targets
TargetLatency: GetConfig().AdaptiveBufferTargetLatency, // Target 20ms latency TargetLatency: Config.AdaptiveBufferTargetLatency, // Target 20ms latency
MaxLatency: GetConfig().LatencyMonitorTarget, // Max acceptable latency MaxLatency: Config.LatencyMonitorTarget, // Max acceptable latency
// Adaptation settings // Adaptation settings
AdaptationInterval: GetConfig().BufferUpdateInterval, // Check every 500ms AdaptationInterval: Config.BufferUpdateInterval, // Check every 500ms
SmoothingFactor: GetConfig().SmoothingFactor, // Moderate responsiveness SmoothingFactor: Config.SmoothingFactor, // Moderate responsiveness
} }
} }
@ -273,7 +273,7 @@ func (abm *AdaptiveBufferManager) adaptBufferSizes() {
latencyFactor := abm.calculateLatencyFactor(currentLatency) latencyFactor := abm.calculateLatencyFactor(currentLatency)
// Combine factors with weights (CPU has highest priority for KVM coexistence) // Combine factors with weights (CPU has highest priority for KVM coexistence)
combinedFactor := GetConfig().CPUMemoryWeight*cpuFactor + GetConfig().MemoryWeight*memoryFactor + GetConfig().LatencyWeight*latencyFactor combinedFactor := Config.CPUMemoryWeight*cpuFactor + Config.MemoryWeight*memoryFactor + Config.LatencyWeight*latencyFactor
// Apply adaptation with smoothing // Apply adaptation with smoothing
currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize)) currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize))
@ -437,8 +437,8 @@ func (abm *AdaptiveBufferManager) GetStats() map[string]interface{} {
"input_buffer_size": abm.GetInputBufferSize(), "input_buffer_size": abm.GetInputBufferSize(),
"output_buffer_size": abm.GetOutputBufferSize(), "output_buffer_size": abm.GetOutputBufferSize(),
"average_latency_ms": float64(atomic.LoadInt64(&abm.averageLatency)) / 1e6, "average_latency_ms": float64(atomic.LoadInt64(&abm.averageLatency)) / 1e6,
"system_cpu_percent": float64(atomic.LoadInt64(&abm.systemCPUPercent)) / GetConfig().PercentageMultiplier, "system_cpu_percent": float64(atomic.LoadInt64(&abm.systemCPUPercent)) / Config.PercentageMultiplier,
"system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / GetConfig().PercentageMultiplier, "system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / Config.PercentageMultiplier,
"adaptation_count": atomic.LoadInt64(&abm.adaptationCount), "adaptation_count": atomic.LoadInt64(&abm.adaptationCount),
"last_adaptation": lastAdaptation, "last_adaptation": lastAdaptation,
} }

View File

@ -83,8 +83,7 @@ type batchWriteResult struct {
// NewBatchAudioProcessor creates a new batch audio processor // NewBatchAudioProcessor creates a new batch audio processor
func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAudioProcessor { func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAudioProcessor {
// Get cached config to avoid GetConfig() calls // Get cached config to avoid GetConfig() calls
cache := GetCachedConfig() cache := Config
cache.Update()
// Validate input parameters with minimal overhead // Validate input parameters with minimal overhead
if batchSize <= 0 || batchSize > 1000 { if batchSize <= 0 || batchSize > 1000 {
@ -105,7 +104,7 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu
logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger() logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger()
// Pre-calculate frame size to avoid repeated GetConfig() calls // Pre-calculate frame size to avoid repeated GetConfig() calls
frameSize := cache.GetMinReadEncodeBuffer() frameSize := cache.MinReadEncodeBuffer
if frameSize == 0 { if frameSize == 0 {
frameSize = 1500 // Safe fallback frameSize = 1500 // Safe fallback
} }
@ -166,7 +165,7 @@ func (bap *BatchAudioProcessor) Stop() {
bap.cancel() bap.cancel()
// Wait for processing to complete // Wait for processing to complete
time.Sleep(bap.batchDuration + GetConfig().BatchProcessingDelay) time.Sleep(bap.batchDuration + Config.BatchProcessingDelay)
bap.logger.Info().Msg("batch audio processor stopped") bap.logger.Info().Msg("batch audio processor stopped")
} }
@ -174,8 +173,7 @@ func (bap *BatchAudioProcessor) Stop() {
// BatchReadEncode performs batched audio read and encode operations // BatchReadEncode performs batched audio read and encode operations
func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) { func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
// Get cached config to avoid GetConfig() calls in hot path // Get cached config to avoid GetConfig() calls in hot path
cache := GetCachedConfig() cache := Config
cache.Update()
// Validate buffer before processing // Validate buffer before processing
if err := ValidateBufferSize(len(buffer)); err != nil { if err := ValidateBufferSize(len(buffer)); err != nil {
@ -221,7 +219,7 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
select { select {
case result := <-resultChan: case result := <-resultChan:
return result.length, result.err return result.length, result.err
case <-time.After(cache.BatchProcessingTimeout): case <-time.After(cache.BatchProcessorTimeout):
// Timeout, fallback to single operation // Timeout, fallback to single operation
// Use sampling to reduce atomic operations overhead // Use sampling to reduce atomic operations overhead
if atomic.LoadInt64(&bap.stats.SingleReads)%10 == 0 { if atomic.LoadInt64(&bap.stats.SingleReads)%10 == 0 {
@ -236,8 +234,7 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
// This is the legacy version that uses a single buffer // This is the legacy version that uses a single buffer
func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) { func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) {
// Get cached config to avoid GetConfig() calls in hot path // Get cached config to avoid GetConfig() calls in hot path
cache := GetCachedConfig() cache := Config
cache.Update()
// Validate buffer before processing // Validate buffer before processing
if err := ValidateBufferSize(len(buffer)); err != nil { if err := ValidateBufferSize(len(buffer)); err != nil {
@ -283,7 +280,7 @@ func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) {
select { select {
case result := <-resultChan: case result := <-resultChan:
return result.length, result.err return result.length, result.err
case <-time.After(cache.BatchProcessingTimeout): case <-time.After(cache.BatchProcessorTimeout):
// Use sampling to reduce atomic operations overhead // Use sampling to reduce atomic operations overhead
if atomic.LoadInt64(&bap.stats.SingleWrites)%10 == 0 { if atomic.LoadInt64(&bap.stats.SingleWrites)%10 == 0 {
atomic.AddInt64(&bap.stats.SingleWrites, 10) atomic.AddInt64(&bap.stats.SingleWrites, 10)
@ -296,8 +293,7 @@ func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) {
// BatchDecodeWriteWithBuffers performs batched audio decode and write operations with separate opus and PCM buffers // BatchDecodeWriteWithBuffers performs batched audio decode and write operations with separate opus and PCM buffers
func (bap *BatchAudioProcessor) BatchDecodeWriteWithBuffers(opusData []byte, pcmBuffer []byte) (int, error) { func (bap *BatchAudioProcessor) BatchDecodeWriteWithBuffers(opusData []byte, pcmBuffer []byte) (int, error) {
// Get cached config to avoid GetConfig() calls in hot path // Get cached config to avoid GetConfig() calls in hot path
cache := GetCachedConfig() cache := Config
cache.Update()
// Validate buffers before processing // Validate buffers before processing
if len(opusData) == 0 { if len(opusData) == 0 {
@ -339,7 +335,7 @@ func (bap *BatchAudioProcessor) BatchDecodeWriteWithBuffers(opusData []byte, pcm
select { select {
case result := <-resultChan: case result := <-resultChan:
return result.length, result.err return result.length, result.err
case <-time.After(cache.BatchProcessingTimeout): case <-time.After(cache.BatchProcessorTimeout):
atomic.AddInt64(&bap.stats.SingleWrites, 1) atomic.AddInt64(&bap.stats.SingleWrites, 1)
atomic.AddInt64(&bap.stats.WriteFrames, 1) atomic.AddInt64(&bap.stats.WriteFrames, 1)
// Use the optimized function with separate buffers // Use the optimized function with separate buffers
@ -427,7 +423,7 @@ func (bap *BatchAudioProcessor) processBatchRead(batch []batchReadRequest) {
} }
// Get cached config once - avoid repeated calls // Get cached config once - avoid repeated calls
cache := GetCachedConfig() cache := Config
threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold
if threadPinningThreshold == 0 { if threadPinningThreshold == 0 {
threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback
@ -480,7 +476,7 @@ func (bap *BatchAudioProcessor) processBatchWrite(batch []batchWriteRequest) {
} }
// Get cached config to avoid GetConfig() calls in hot path // Get cached config to avoid GetConfig() calls in hot path
cache := GetCachedConfig() cache := Config
threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold
if threadPinningThreshold == 0 { if threadPinningThreshold == 0 {
threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback
@ -586,8 +582,7 @@ func GetBatchAudioProcessor() *BatchAudioProcessor {
// Initialize on first use // Initialize on first use
if atomic.CompareAndSwapInt32(&batchProcessorInitialized, 0, 1) { if atomic.CompareAndSwapInt32(&batchProcessorInitialized, 0, 1) {
// Get cached config to avoid GetConfig() calls // Get cached config to avoid GetConfig() calls
cache := GetCachedConfig() cache := Config
cache.Update()
processor := NewBatchAudioProcessor(cache.BatchProcessorFramesPerBatch, cache.BatchProcessorTimeout) processor := NewBatchAudioProcessor(cache.BatchProcessorFramesPerBatch, cache.BatchProcessorTimeout)
atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor)) atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor))
@ -601,7 +596,7 @@ func GetBatchAudioProcessor() *BatchAudioProcessor {
} }
// Fallback: create a new processor (should rarely happen) // Fallback: create a new processor (should rarely happen)
config := GetConfig() config := Config
return NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout) return NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout)
} }

View File

@ -73,7 +73,7 @@ func GetBatchZeroCopyProcessor() *BatchZeroCopyProcessor {
// NewBatchZeroCopyProcessor creates a new batch zero-copy processor // NewBatchZeroCopyProcessor creates a new batch zero-copy processor
func NewBatchZeroCopyProcessor() *BatchZeroCopyProcessor { func NewBatchZeroCopyProcessor() *BatchZeroCopyProcessor {
cache := GetCachedConfig() cache := Config
return &BatchZeroCopyProcessor{ return &BatchZeroCopyProcessor{
maxBatchSize: cache.BatchProcessorFramesPerBatch, maxBatchSize: cache.BatchProcessorFramesPerBatch,
batchTimeout: cache.BatchProcessorTimeout, batchTimeout: cache.BatchProcessorTimeout,

View File

@ -4,12 +4,12 @@ import "time"
// GetMetricsUpdateInterval returns the current metrics update interval from centralized config // GetMetricsUpdateInterval returns the current metrics update interval from centralized config
func GetMetricsUpdateInterval() time.Duration { func GetMetricsUpdateInterval() time.Duration {
return GetConfig().MetricsUpdateInterval return Config.MetricsUpdateInterval
} }
// SetMetricsUpdateInterval sets the metrics update interval in centralized config // SetMetricsUpdateInterval sets the metrics update interval in centralized config
func SetMetricsUpdateInterval(interval time.Duration) { func SetMetricsUpdateInterval(interval time.Duration) {
config := GetConfig() config := Config
config.MetricsUpdateInterval = interval config.MetricsUpdateInterval = interval
UpdateConfig(config) UpdateConfig(config)
} }

View File

@ -313,6 +313,15 @@ type AudioConfigConstants struct {
AudioProcessorQueueSize int AudioProcessorQueueSize int
AudioReaderQueueSize int AudioReaderQueueSize int
WorkerMaxIdleTime time.Duration WorkerMaxIdleTime time.Duration
// Connection Retry Configuration
MaxConnectionAttempts int // Maximum connection retry attempts
ConnectionRetryDelay time.Duration // Initial connection retry delay
MaxConnectionRetryDelay time.Duration // Maximum connection retry delay
ConnectionBackoffFactor float64 // Connection retry backoff factor
ConnectionTimeoutDelay time.Duration // Connection timeout for each attempt
ReconnectionInterval time.Duration // Interval for automatic reconnection attempts
HealthCheckInterval time.Duration // Health check interval for connections
} }
// DefaultAudioConfig returns the default configuration constants // DefaultAudioConfig returns the default configuration constants
@ -424,11 +433,11 @@ func DefaultAudioConfig() *AudioConfigConstants {
// Buffer Management // Buffer Management
PreallocSize: 1024 * 1024, // 1MB buffer preallocation PreallocSize: 1024 * 1024, // 1MB buffer preallocation
MaxPoolSize: 100, // Maximum object pool size MaxPoolSize: 100, // Maximum object pool size
MessagePoolSize: 256, // Message pool size for IPC MessagePoolSize: 512, // Increased message pool for quality change bursts
OptimalSocketBuffer: 262144, // 256KB optimal socket buffer OptimalSocketBuffer: 262144, // 256KB optimal socket buffer
MaxSocketBuffer: 1048576, // 1MB maximum socket buffer MaxSocketBuffer: 1048576, // 1MB maximum socket buffer
MinSocketBuffer: 8192, // 8KB minimum socket buffer MinSocketBuffer: 8192, // 8KB minimum socket buffer
ChannelBufferSize: 500, // Inter-goroutine channel buffer size ChannelBufferSize: 1000, // Increased channel buffer for quality change bursts
AudioFramePoolSize: 1500, // Audio frame object pool size AudioFramePoolSize: 1500, // Audio frame object pool size
PageSize: 4096, // Memory page size for alignment PageSize: 4096, // Memory page size for alignment
InitialBufferFrames: 500, // Initial buffer size during startup InitialBufferFrames: 500, // Initial buffer size during startup
@ -436,17 +445,17 @@ func DefaultAudioConfig() *AudioConfigConstants {
MinReadEncodeBuffer: 1276, // Minimum CGO read/encode buffer MinReadEncodeBuffer: 1276, // Minimum CGO read/encode buffer
MaxDecodeWriteBuffer: 4096, // Maximum CGO decode/write buffer MaxDecodeWriteBuffer: 4096, // Maximum CGO decode/write buffer
// IPC Configuration // IPC Configuration - Balanced for stability
MagicNumber: 0xDEADBEEF, // IPC message validation header MagicNumber: 0xDEADBEEF, // IPC message validation header
MaxFrameSize: 4096, // Maximum audio frame size (4KB) MaxFrameSize: 4096, // Maximum audio frame size (4KB)
WriteTimeout: 100 * time.Millisecond, // IPC write operation timeout WriteTimeout: 500 * time.Millisecond, // Increased timeout to handle quality change bursts
HeaderSize: 8, // IPC message header size HeaderSize: 8, // IPC message header size
// Monitoring and Metrics // Monitoring and Metrics - Balanced for stability
MetricsUpdateInterval: 1000 * time.Millisecond, // Metrics collection frequency MetricsUpdateInterval: 1000 * time.Millisecond, // Stable metrics collection frequency
WarmupSamples: 10, // Warmup samples for metrics accuracy WarmupSamples: 10, // Adequate warmup samples for accuracy
MetricsChannelBuffer: 100, // Metrics data channel buffer size MetricsChannelBuffer: 100, // Adequate metrics data channel buffer
LatencyHistorySize: 100, // Number of latency measurements to keep LatencyHistorySize: 100, // Adequate latency measurements to keep
// Process Monitoring Constants // Process Monitoring Constants
MaxCPUPercent: 100.0, // Maximum CPU percentage MaxCPUPercent: 100.0, // Maximum CPU percentage
@ -470,41 +479,50 @@ func DefaultAudioConfig() *AudioConfigConstants {
BackoffMultiplier: 2.0, // Exponential backoff multiplier BackoffMultiplier: 2.0, // Exponential backoff multiplier
MaxConsecutiveErrors: 5, // Consecutive error threshold MaxConsecutiveErrors: 5, // Consecutive error threshold
// Timing Constants // Connection Retry Configuration
DefaultSleepDuration: 100 * time.Millisecond, // Standard polling interval MaxConnectionAttempts: 15, // Maximum connection retry attempts
ShortSleepDuration: 10 * time.Millisecond, // High-frequency polling ConnectionRetryDelay: 50 * time.Millisecond, // Initial connection retry delay
LongSleepDuration: 200 * time.Millisecond, // Background tasks MaxConnectionRetryDelay: 2 * time.Second, // Maximum connection retry delay
DefaultTickerInterval: 100 * time.Millisecond, // Periodic task interval ConnectionBackoffFactor: 1.5, // Connection retry backoff factor
BufferUpdateInterval: 500 * time.Millisecond, // Buffer status updates ConnectionTimeoutDelay: 5 * time.Second, // Connection timeout for each attempt
ReconnectionInterval: 30 * time.Second, // Interval for automatic reconnection attempts
HealthCheckInterval: 10 * time.Second, // Health check interval for connections
// Timing Constants - Optimized for quality change stability
DefaultSleepDuration: 100 * time.Millisecond, // Balanced polling interval
ShortSleepDuration: 10 * time.Millisecond, // Balanced high-frequency polling
LongSleepDuration: 200 * time.Millisecond, // Balanced background task delay
DefaultTickerInterval: 100 * time.Millisecond, // Balanced periodic task interval
BufferUpdateInterval: 300 * time.Millisecond, // Faster buffer updates for quality changes
InputSupervisorTimeout: 5 * time.Second, // Input monitoring timeout InputSupervisorTimeout: 5 * time.Second, // Input monitoring timeout
OutputSupervisorTimeout: 5 * time.Second, // Output monitoring timeout OutputSupervisorTimeout: 5 * time.Second, // Output monitoring timeout
BatchProcessingDelay: 10 * time.Millisecond, // Batch processing delay BatchProcessingDelay: 5 * time.Millisecond, // Reduced batch processing delay
AdaptiveOptimizerStability: 10 * time.Second, // Adaptive stability period AdaptiveOptimizerStability: 5 * time.Second, // Faster adaptive stability period
LatencyMonitorTarget: 50 * time.Millisecond, // Target latency for monitoring LatencyMonitorTarget: 50 * time.Millisecond, // Balanced target latency for monitoring
// Adaptive Buffer Configuration // Adaptive Buffer Configuration - Optimized for low latency
LowCPUThreshold: 0.20, LowCPUThreshold: 0.30,
HighCPUThreshold: 0.60, HighCPUThreshold: 0.70,
LowMemoryThreshold: 0.50, LowMemoryThreshold: 0.60,
HighMemoryThreshold: 0.75, HighMemoryThreshold: 0.80,
AdaptiveBufferTargetLatency: 20 * time.Millisecond, AdaptiveBufferTargetLatency: 15 * time.Millisecond, // Reduced target latency
// Adaptive Buffer Size Configuration // Adaptive Buffer Size Configuration - Optimized for quality change bursts
AdaptiveMinBufferSize: 3, // Minimum 3 frames for stability AdaptiveMinBufferSize: 16, // Higher minimum to handle bursts
AdaptiveMaxBufferSize: 20, // Maximum 20 frames for high load AdaptiveMaxBufferSize: 64, // Higher maximum for quality changes
AdaptiveDefaultBufferSize: 6, // Balanced buffer size (6 frames) AdaptiveDefaultBufferSize: 32, // Higher default for stability
// Adaptive Optimizer Configuration // Adaptive Optimizer Configuration - Faster response
CooldownPeriod: 30 * time.Second, CooldownPeriod: 15 * time.Second, // Reduced cooldown period
RollbackThreshold: 300 * time.Millisecond, RollbackThreshold: 200 * time.Millisecond, // Lower rollback threshold
AdaptiveOptimizerLatencyTarget: 50 * time.Millisecond, AdaptiveOptimizerLatencyTarget: 30 * time.Millisecond, // Reduced latency target
// Latency Monitor Configuration // Latency Monitor Configuration - More aggressive monitoring
MaxLatencyThreshold: 200 * time.Millisecond, MaxLatencyThreshold: 150 * time.Millisecond, // Lower max latency threshold
JitterThreshold: 20 * time.Millisecond, JitterThreshold: 15 * time.Millisecond, // Reduced jitter threshold
LatencyOptimizationInterval: 5 * time.Second, LatencyOptimizationInterval: 3 * time.Second, // More frequent optimization
LatencyAdaptiveThreshold: 0.8, LatencyAdaptiveThreshold: 0.7, // More aggressive adaptive threshold
// Microphone Contention Configuration // Microphone Contention Configuration
MicContentionTimeout: 200 * time.Millisecond, MicContentionTimeout: 200 * time.Millisecond,
@ -532,12 +550,12 @@ func DefaultAudioConfig() *AudioConfigConstants {
LatencyScalingFactor: 2.0, // Latency ratio scaling factor LatencyScalingFactor: 2.0, // Latency ratio scaling factor
OptimizerAggressiveness: 0.7, // Optimizer aggressiveness factor OptimizerAggressiveness: 0.7, // Optimizer aggressiveness factor
// CGO Audio Processing Constants // CGO Audio Processing Constants - Balanced for stability
CGOUsleepMicroseconds: 1000, // 1000 microseconds (1ms) for CGO usleep calls CGOUsleepMicroseconds: 1000, // 1000 microseconds (1ms) for stable CGO usleep calls
CGOPCMBufferSize: 1920, // 1920 samples for PCM buffer (max 2ch*960) CGOPCMBufferSize: 1920, // 1920 samples for PCM buffer (max 2ch*960)
CGONanosecondsPerSecond: 1000000000.0, // 1000000000.0 for nanosecond conversions CGONanosecondsPerSecond: 1000000000.0, // 1000000000.0 for nanosecond conversions
// Frontend Constants // Frontend Constants - Balanced for stability
FrontendOperationDebounceMS: 1000, // 1000ms debounce for frontend operations FrontendOperationDebounceMS: 1000, // 1000ms debounce for frontend operations
FrontendSyncDebounceMS: 1000, // 1000ms debounce for sync operations FrontendSyncDebounceMS: 1000, // 1000ms debounce for sync operations
FrontendSampleRate: 48000, // 48000Hz sample rate for frontend audio FrontendSampleRate: 48000, // 48000Hz sample rate for frontend audio
@ -560,20 +578,20 @@ func DefaultAudioConfig() *AudioConfigConstants {
ProcessMonitorFallbackClockHz: 1000.0, // 1000.0 Hz fallback clock ProcessMonitorFallbackClockHz: 1000.0, // 1000.0 Hz fallback clock
ProcessMonitorTraditionalHz: 100.0, // 100.0 Hz traditional clock ProcessMonitorTraditionalHz: 100.0, // 100.0 Hz traditional clock
// Batch Processing Constants // Batch Processing Constants - Optimized for quality change bursts
BatchProcessorFramesPerBatch: 4, // 4 frames per batch BatchProcessorFramesPerBatch: 16, // Larger batches for quality changes
BatchProcessorTimeout: 5 * time.Millisecond, // 5ms timeout BatchProcessorTimeout: 20 * time.Millisecond, // Longer timeout for bursts
BatchProcessorMaxQueueSize: 16, // 16 max queue size for balanced memory/performance BatchProcessorMaxQueueSize: 64, // Larger queue for quality changes
BatchProcessorAdaptiveThreshold: 0.8, // 0.8 threshold for adaptive batching (80% queue full) BatchProcessorAdaptiveThreshold: 0.6, // Lower threshold for faster adaptation
BatchProcessorThreadPinningThreshold: 8, // 8 frames minimum for thread pinning optimization BatchProcessorThreadPinningThreshold: 8, // Lower threshold for better performance
// Output Streaming Constants // Output Streaming Constants - Balanced for stability
OutputStreamingFrameIntervalMS: 20, // 20ms frame interval (50 FPS) OutputStreamingFrameIntervalMS: 20, // 20ms frame interval (50 FPS) for stability
// IPC Constants // IPC Constants
IPCInitialBufferFrames: 500, // 500 frames for initial buffer IPCInitialBufferFrames: 500, // 500 frames for initial buffer
// Event Constants // Event Constants - Balanced for stability
EventTimeoutSeconds: 2, // 2 seconds for event timeout EventTimeoutSeconds: 2, // 2 seconds for event timeout
EventTimeFormatString: "2006-01-02T15:04:05.000Z", // "2006-01-02T15:04:05.000Z" time format EventTimeFormatString: "2006-01-02T15:04:05.000Z", // "2006-01-02T15:04:05.000Z" time format
EventSubscriptionDelayMS: 100, // 100ms subscription delay EventSubscriptionDelayMS: 100, // 100ms subscription delay
@ -585,7 +603,7 @@ func DefaultAudioConfig() *AudioConfigConstants {
AudioReaderQueueSize: 32, // 32 tasks queue size for reader pool AudioReaderQueueSize: 32, // 32 tasks queue size for reader pool
WorkerMaxIdleTime: 60 * time.Second, // 60s maximum idle time before worker termination WorkerMaxIdleTime: 60 * time.Second, // 60s maximum idle time before worker termination
// Input Processing Constants // Input Processing Constants - Balanced for stability
InputProcessingTimeoutMS: 10, // 10ms processing timeout threshold InputProcessingTimeoutMS: 10, // 10ms processing timeout threshold
// Adaptive Buffer Constants // Adaptive Buffer Constants
@ -670,7 +688,7 @@ func DefaultAudioConfig() *AudioConfigConstants {
} }
// Global configuration instance // Global configuration instance
var audioConfigInstance = DefaultAudioConfig() var Config = DefaultAudioConfig()
// UpdateConfig allows runtime configuration updates // UpdateConfig allows runtime configuration updates
func UpdateConfig(newConfig *AudioConfigConstants) { func UpdateConfig(newConfig *AudioConfigConstants) {
@ -682,12 +700,12 @@ func UpdateConfig(newConfig *AudioConfigConstants) {
return return
} }
audioConfigInstance = newConfig Config = newConfig
logger := logging.GetDefaultLogger().With().Str("component", "AudioConfig").Logger() logger := logging.GetDefaultLogger().With().Str("component", "AudioConfig").Logger()
logger.Info().Msg("Audio configuration updated successfully") logger.Info().Msg("Audio configuration updated successfully")
} }
// GetConfig returns the current configuration // GetConfig returns the current configuration
func GetConfig() *AudioConfigConstants { func GetConfig() *AudioConfigConstants {
return audioConfigInstance return Config
} }

View File

@ -55,12 +55,11 @@ func ValidateZeroCopyFrame(frame *ZeroCopyAudioFrame) error {
maxFrameSize := cachedMaxFrameSize maxFrameSize := cachedMaxFrameSize
if maxFrameSize == 0 { if maxFrameSize == 0 {
// Fallback: get from cache // Fallback: get from cache
cache := GetCachedConfig() cache := Config
maxFrameSize = int(cache.maxAudioFrameSize.Load()) maxFrameSize = cache.MaxAudioFrameSize
if maxFrameSize == 0 { if maxFrameSize == 0 {
// Last resort: update cache // Last resort: use default
cache.Update() maxFrameSize = cache.MaxAudioFrameSize
maxFrameSize = int(cache.maxAudioFrameSize.Load())
} }
// Cache globally for next calls // Cache globally for next calls
cachedMaxFrameSize = maxFrameSize cachedMaxFrameSize = maxFrameSize
@ -80,8 +79,8 @@ func ValidateBufferSize(size int) error {
} }
// Fast path: Check against cached max frame size // Fast path: Check against cached max frame size
cache := GetCachedConfig() cache := Config
maxFrameSize := int(cache.maxAudioFrameSize.Load()) maxFrameSize := cache.MaxAudioFrameSize
// Most common case: validating a buffer that's sized for audio frames // Most common case: validating a buffer that's sized for audio frames
if maxFrameSize > 0 && size <= maxFrameSize { if maxFrameSize > 0 && size <= maxFrameSize {
@ -89,7 +88,7 @@ func ValidateBufferSize(size int) error {
} }
// Slower path: full validation against SocketMaxBuffer // Slower path: full validation against SocketMaxBuffer
config := GetConfig() config := Config
// Use SocketMaxBuffer as the upper limit for general buffer validation // Use SocketMaxBuffer as the upper limit for general buffer validation
// This allows for socket buffers while still preventing extremely large allocations // This allows for socket buffers while still preventing extremely large allocations
if size > config.SocketMaxBuffer { if size > config.SocketMaxBuffer {
@ -107,8 +106,8 @@ func ValidateLatency(latency time.Duration) error {
} }
// Fast path: check against cached max latency // Fast path: check against cached max latency
cache := GetCachedConfig() cache := Config
maxLatency := time.Duration(cache.maxLatency.Load()) maxLatency := time.Duration(cache.MaxLatency)
// If we have a valid cached value, use it // If we have a valid cached value, use it
if maxLatency > 0 { if maxLatency > 0 {
@ -125,7 +124,7 @@ func ValidateLatency(latency time.Duration) error {
} }
// Slower path: full validation with GetConfig() // Slower path: full validation with GetConfig()
config := GetConfig() config := Config
minLatency := time.Millisecond // Minimum reasonable latency minLatency := time.Millisecond // Minimum reasonable latency
if latency > 0 && latency < minLatency { if latency > 0 && latency < minLatency {
return fmt.Errorf("%w: latency %v below minimum %v", return fmt.Errorf("%w: latency %v below minimum %v",
@ -142,9 +141,9 @@ func ValidateLatency(latency time.Duration) error {
// Optimized to use AudioConfigCache for frequently accessed values // Optimized to use AudioConfigCache for frequently accessed values
func ValidateMetricsInterval(interval time.Duration) error { func ValidateMetricsInterval(interval time.Duration) error {
// Fast path: check against cached values // Fast path: check against cached values
cache := GetCachedConfig() cache := Config
minInterval := time.Duration(cache.minMetricsUpdateInterval.Load()) minInterval := time.Duration(cache.MinMetricsUpdateInterval)
maxInterval := time.Duration(cache.maxMetricsUpdateInterval.Load()) maxInterval := time.Duration(cache.MaxMetricsUpdateInterval)
// If we have valid cached values, use them // If we have valid cached values, use them
if minInterval > 0 && maxInterval > 0 { if minInterval > 0 && maxInterval > 0 {
@ -160,7 +159,7 @@ func ValidateMetricsInterval(interval time.Duration) error {
} }
// Slower path: full validation with GetConfig() // Slower path: full validation with GetConfig()
config := GetConfig() config := Config
minInterval = config.MinMetricsUpdateInterval minInterval = config.MinMetricsUpdateInterval
maxInterval = config.MaxMetricsUpdateInterval maxInterval = config.MaxMetricsUpdateInterval
if interval < minInterval { if interval < minInterval {
@ -184,7 +183,7 @@ func ValidateAdaptiveBufferConfig(minSize, maxSize, defaultSize int) error {
return ErrInvalidBufferSize return ErrInvalidBufferSize
} }
// Validate against global limits // Validate against global limits
maxBuffer := GetConfig().SocketMaxBuffer maxBuffer := Config.SocketMaxBuffer
if maxSize > maxBuffer { if maxSize > maxBuffer {
return ErrInvalidBufferSize return ErrInvalidBufferSize
} }
@ -194,7 +193,7 @@ func ValidateAdaptiveBufferConfig(minSize, maxSize, defaultSize int) error {
// ValidateInputIPCConfig validates input IPC configuration // ValidateInputIPCConfig validates input IPC configuration
func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error { func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
// Use config values // Use config values
config := GetConfig() config := Config
minSampleRate := config.MinSampleRate minSampleRate := config.MinSampleRate
maxSampleRate := config.MaxSampleRate maxSampleRate := config.MaxSampleRate
maxChannels := config.MaxChannels maxChannels := config.MaxChannels
@ -213,7 +212,7 @@ func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
// ValidateOutputIPCConfig validates output IPC configuration // ValidateOutputIPCConfig validates output IPC configuration
func ValidateOutputIPCConfig(sampleRate, channels, frameSize int) error { func ValidateOutputIPCConfig(sampleRate, channels, frameSize int) error {
// Use config values // Use config values
config := GetConfig() config := Config
minSampleRate := config.MinSampleRate minSampleRate := config.MinSampleRate
maxSampleRate := config.MaxSampleRate maxSampleRate := config.MaxSampleRate
maxChannels := config.MaxChannels maxChannels := config.MaxChannels
@ -263,8 +262,8 @@ func ValidateSampleRate(sampleRate int) error {
} }
// Fast path: Check against cached sample rate first // Fast path: Check against cached sample rate first
cache := GetCachedConfig() cache := Config
cachedRate := int(cache.sampleRate.Load()) cachedRate := cache.SampleRate
// Most common case: validating against the current sample rate // Most common case: validating against the current sample rate
if sampleRate == cachedRate { if sampleRate == cachedRate {
@ -272,7 +271,7 @@ func ValidateSampleRate(sampleRate int) error {
} }
// Slower path: check against all valid rates // Slower path: check against all valid rates
config := GetConfig() config := Config
validRates := config.ValidSampleRates validRates := config.ValidSampleRates
for _, rate := range validRates { for _, rate := range validRates {
if sampleRate == rate { if sampleRate == rate {
@ -291,8 +290,8 @@ func ValidateChannelCount(channels int) error {
} }
// Fast path: Check against cached channels first // Fast path: Check against cached channels first
cache := GetCachedConfig() cache := Config
cachedChannels := int(cache.channels.Load()) cachedChannels := cache.Channels
// Most common case: validating against the current channel count // Most common case: validating against the current channel count
if channels == cachedChannels { if channels == cachedChannels {
@ -300,14 +299,13 @@ func ValidateChannelCount(channels int) error {
} }
// Fast path: Check against cached max channels // Fast path: Check against cached max channels
cachedMaxChannels := int(cache.maxChannels.Load()) cachedMaxChannels := cache.MaxChannels
if cachedMaxChannels > 0 && channels <= cachedMaxChannels { if cachedMaxChannels > 0 && channels <= cachedMaxChannels {
return nil return nil
} }
// Slow path: Update cache and validate // Slow path: Use current config values
cache.Update() updatedMaxChannels := cache.MaxChannels
updatedMaxChannels := int(cache.maxChannels.Load())
if channels > updatedMaxChannels { if channels > updatedMaxChannels {
return fmt.Errorf("%w: channel count %d exceeds maximum %d", return fmt.Errorf("%w: channel count %d exceeds maximum %d",
ErrInvalidChannels, channels, updatedMaxChannels) ErrInvalidChannels, channels, updatedMaxChannels)
@ -323,9 +321,9 @@ func ValidateBitrate(bitrate int) error {
} }
// Fast path: Check against cached bitrate values // Fast path: Check against cached bitrate values
cache := GetCachedConfig() cache := Config
minBitrate := int(cache.minOpusBitrate.Load()) minBitrate := cache.MinOpusBitrate
maxBitrate := int(cache.maxOpusBitrate.Load()) maxBitrate := cache.MaxOpusBitrate
// If we have valid cached values, use them // If we have valid cached values, use them
if minBitrate > 0 && maxBitrate > 0 { if minBitrate > 0 && maxBitrate > 0 {
@ -343,7 +341,7 @@ func ValidateBitrate(bitrate int) error {
} }
// Slower path: full validation with GetConfig() // Slower path: full validation with GetConfig()
config := GetConfig() config := Config
// Convert kbps to bps for comparison with config limits // Convert kbps to bps for comparison with config limits
bitrateInBps := bitrate * 1000 bitrateInBps := bitrate * 1000
if bitrateInBps < config.MinOpusBitrate { if bitrateInBps < config.MinOpusBitrate {
@ -365,11 +363,11 @@ func ValidateFrameDuration(duration time.Duration) error {
} }
// Fast path: Check against cached frame size first // Fast path: Check against cached frame size first
cache := GetCachedConfig() cache := Config
// Convert frameSize (samples) to duration for comparison // Convert frameSize (samples) to duration for comparison
cachedFrameSize := int(cache.frameSize.Load()) cachedFrameSize := cache.FrameSize
cachedSampleRate := int(cache.sampleRate.Load()) cachedSampleRate := cache.SampleRate
// Only do this calculation if we have valid cached values // Only do this calculation if we have valid cached values
if cachedFrameSize > 0 && cachedSampleRate > 0 { if cachedFrameSize > 0 && cachedSampleRate > 0 {
@ -382,8 +380,8 @@ func ValidateFrameDuration(duration time.Duration) error {
} }
// Fast path: Check against cached min/max frame duration // Fast path: Check against cached min/max frame duration
cachedMinDuration := time.Duration(cache.minFrameDuration.Load()) cachedMinDuration := time.Duration(cache.MinFrameDuration)
cachedMaxDuration := time.Duration(cache.maxFrameDuration.Load()) cachedMaxDuration := time.Duration(cache.MaxFrameDuration)
if cachedMinDuration > 0 && cachedMaxDuration > 0 { if cachedMinDuration > 0 && cachedMaxDuration > 0 {
if duration < cachedMinDuration { if duration < cachedMinDuration {
@ -397,10 +395,9 @@ func ValidateFrameDuration(duration time.Duration) error {
return nil return nil
} }
// Slow path: Update cache and validate // Slow path: Use current config values
cache.Update() updatedMinDuration := time.Duration(cache.MinFrameDuration)
updatedMinDuration := time.Duration(cache.minFrameDuration.Load()) updatedMaxDuration := time.Duration(cache.MaxFrameDuration)
updatedMaxDuration := time.Duration(cache.maxFrameDuration.Load())
if duration < updatedMinDuration { if duration < updatedMinDuration {
return fmt.Errorf("%w: frame duration %v below minimum %v", return fmt.Errorf("%w: frame duration %v below minimum %v",
@ -417,11 +414,11 @@ func ValidateFrameDuration(duration time.Duration) error {
// Uses optimized validation functions that leverage AudioConfigCache // Uses optimized validation functions that leverage AudioConfigCache
func ValidateAudioConfigComplete(config AudioConfig) error { func ValidateAudioConfigComplete(config AudioConfig) error {
// Fast path: Check if all values match the current cached configuration // Fast path: Check if all values match the current cached configuration
cache := GetCachedConfig() cache := Config
cachedSampleRate := int(cache.sampleRate.Load()) cachedSampleRate := cache.SampleRate
cachedChannels := int(cache.channels.Load()) cachedChannels := cache.Channels
cachedBitrate := int(cache.opusBitrate.Load()) / 1000 // Convert from bps to kbps cachedBitrate := cache.OpusBitrate / 1000 // Convert from bps to kbps
cachedFrameSize := int(cache.frameSize.Load()) cachedFrameSize := cache.FrameSize
// Only do this calculation if we have valid cached values // Only do this calculation if we have valid cached values
if cachedSampleRate > 0 && cachedChannels > 0 && cachedBitrate > 0 && cachedFrameSize > 0 { if cachedSampleRate > 0 && cachedChannels > 0 && cachedBitrate > 0 && cachedFrameSize > 0 {
@ -481,11 +478,11 @@ var cachedMaxFrameSize int
// InitValidationCache initializes cached validation values with actual config // InitValidationCache initializes cached validation values with actual config
func InitValidationCache() { func InitValidationCache() {
// Initialize the global cache variable for backward compatibility // Initialize the global cache variable for backward compatibility
config := GetConfig() config := Config
cachedMaxFrameSize = config.MaxAudioFrameSize cachedMaxFrameSize = config.MaxAudioFrameSize
// Update the global audio config cache // Initialize the global audio config cache
GetCachedConfig().Update() cachedMaxFrameSize = Config.MaxAudioFrameSize
} }
// ValidateAudioFrame validates audio frame data with cached max size for performance // ValidateAudioFrame validates audio frame data with cached max size for performance
@ -502,12 +499,11 @@ func ValidateAudioFrame(data []byte) error {
maxSize := cachedMaxFrameSize maxSize := cachedMaxFrameSize
if maxSize == 0 { if maxSize == 0 {
// Fallback: get from cache only if global cache not initialized // Fallback: get from cache only if global cache not initialized
cache := GetCachedConfig() cache := Config
maxSize = int(cache.maxAudioFrameSize.Load()) maxSize = cache.MaxAudioFrameSize
if maxSize == 0 { if maxSize == 0 {
// Last resort: update cache and get fresh value // Last resort: get fresh value
cache.Update() maxSize = cache.MaxAudioFrameSize
maxSize = int(cache.maxAudioFrameSize.Load())
} }
// Cache the value globally for next calls // Cache the value globally for next calls
cachedMaxFrameSize = maxSize cachedMaxFrameSize = maxSize

View File

@ -255,7 +255,7 @@ func GetAudioProcessorPool() *GoroutinePool {
} }
globalAudioProcessorInitOnce.Do(func() { globalAudioProcessorInitOnce.Do(func() {
config := GetConfig() config := Config
newPool := NewGoroutinePool( newPool := NewGoroutinePool(
"audio-processor", "audio-processor",
config.MaxAudioProcessorWorkers, config.MaxAudioProcessorWorkers,
@ -277,7 +277,7 @@ func GetAudioReaderPool() *GoroutinePool {
} }
globalAudioReaderInitOnce.Do(func() { globalAudioReaderInitOnce.Do(func() {
config := GetConfig() config := Config
newPool := NewGoroutinePool( newPool := NewGoroutinePool(
"audio-reader", "audio-reader",
config.MaxAudioReaderWorkers, config.MaxAudioReaderWorkers,

View File

@ -108,7 +108,7 @@ func (aim *AudioInputManager) WriteOpusFrame(frame []byte) error {
processingTime := time.Since(startTime) processingTime := time.Since(startTime)
// Log high latency warnings // Log high latency warnings
if processingTime > time.Duration(GetConfig().InputProcessingTimeoutMS)*time.Millisecond { if processingTime > time.Duration(Config.InputProcessingTimeoutMS)*time.Millisecond {
latencyMs := float64(processingTime.Milliseconds()) latencyMs := float64(processingTime.Milliseconds())
aim.logger.Warn(). aim.logger.Warn().
Float64("latency_ms", latencyMs). Float64("latency_ms", latencyMs).
@ -149,7 +149,7 @@ func (aim *AudioInputManager) WriteOpusFrameZeroCopy(frame *ZeroCopyAudioFrame)
processingTime := time.Since(startTime) processingTime := time.Since(startTime)
// Log high latency warnings // Log high latency warnings
if processingTime > time.Duration(GetConfig().InputProcessingTimeoutMS)*time.Millisecond { if processingTime > time.Duration(Config.InputProcessingTimeoutMS)*time.Millisecond {
latencyMs := float64(processingTime.Milliseconds()) latencyMs := float64(processingTime.Milliseconds())
aim.logger.Warn(). aim.logger.Warn().
Float64("latency_ms", latencyMs). Float64("latency_ms", latencyMs).

View File

@ -107,7 +107,7 @@ func RunAudioInputServer() error {
server.Stop() server.Stop()
// Give some time for cleanup // Give some time for cleanup
time.Sleep(GetConfig().DefaultSleepDuration) time.Sleep(Config.DefaultSleepDuration)
return nil return nil
} }

View File

@ -73,7 +73,7 @@ func (ais *AudioInputSupervisor) supervisionLoop() {
// Configure supervision parameters (no restart for input supervisor) // Configure supervision parameters (no restart for input supervisor)
config := SupervisionConfig{ config := SupervisionConfig{
ProcessType: "audio input server", ProcessType: "audio input server",
Timeout: GetConfig().InputSupervisorTimeout, Timeout: Config.InputSupervisorTimeout,
EnableRestart: false, // Input supervisor doesn't restart EnableRestart: false, // Input supervisor doesn't restart
MaxRestartAttempts: 0, MaxRestartAttempts: 0,
RestartWindow: 0, RestartWindow: 0,
@ -164,7 +164,7 @@ func (ais *AudioInputSupervisor) Stop() {
select { select {
case <-ais.processDone: case <-ais.processDone:
ais.logger.Info().Str("component", "audio-input-supervisor").Msg("component stopped gracefully") ais.logger.Info().Str("component", "audio-input-supervisor").Msg("component stopped gracefully")
case <-time.After(GetConfig().InputSupervisorTimeout): case <-time.After(Config.InputSupervisorTimeout):
ais.logger.Warn().Str("component", "audio-input-supervisor").Msg("component did not stop gracefully, forcing termination") ais.logger.Warn().Str("component", "audio-input-supervisor").Msg("component did not stop gracefully, forcing termination")
ais.forceKillProcess("audio input server") ais.forceKillProcess("audio input server")
} }
@ -190,7 +190,7 @@ func (ais *AudioInputSupervisor) GetClient() *AudioInputClient {
// connectClient attempts to connect the client to the server // connectClient attempts to connect the client to the server
func (ais *AudioInputSupervisor) connectClient() { func (ais *AudioInputSupervisor) connectClient() {
// Wait briefly for the server to start and create socket // Wait briefly for the server to start and create socket
time.Sleep(GetConfig().DefaultSleepDuration) time.Sleep(Config.DefaultSleepDuration)
// Additional small delay to ensure socket is ready after restart // Additional small delay to ensure socket is ready after restart
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)

View File

@ -49,7 +49,7 @@ func NewGenericMessagePool(size int) *GenericMessagePool {
pool.preallocated = make([]*OptimizedMessage, pool.preallocSize) pool.preallocated = make([]*OptimizedMessage, pool.preallocSize)
for i := 0; i < pool.preallocSize; i++ { for i := 0; i < pool.preallocSize; i++ {
pool.preallocated[i] = &OptimizedMessage{ pool.preallocated[i] = &OptimizedMessage{
data: make([]byte, 0, GetConfig().MaxFrameSize), data: make([]byte, 0, Config.MaxFrameSize),
} }
} }
@ -57,7 +57,7 @@ func NewGenericMessagePool(size int) *GenericMessagePool {
for i := 0; i < size-pool.preallocSize; i++ { for i := 0; i < size-pool.preallocSize; i++ {
select { select {
case pool.pool <- &OptimizedMessage{ case pool.pool <- &OptimizedMessage{
data: make([]byte, 0, GetConfig().MaxFrameSize), data: make([]byte, 0, Config.MaxFrameSize),
}: }:
default: default:
break break
@ -89,7 +89,7 @@ func (mp *GenericMessagePool) Get() *OptimizedMessage {
// Pool empty, create new message // Pool empty, create new message
atomic.AddInt64(&mp.missCount, 1) atomic.AddInt64(&mp.missCount, 1)
return &OptimizedMessage{ return &OptimizedMessage{
data: make([]byte, 0, GetConfig().MaxFrameSize), data: make([]byte, 0, Config.MaxFrameSize),
} }
} }
} }
@ -149,7 +149,7 @@ func WriteIPCMessage(conn net.Conn, msg IPCMessage, pool *GenericMessagePool, dr
binary.LittleEndian.PutUint64(optMsg.header[9:17], uint64(msg.GetTimestamp())) binary.LittleEndian.PutUint64(optMsg.header[9:17], uint64(msg.GetTimestamp()))
// Set write deadline for timeout handling (more efficient than goroutines) // Set write deadline for timeout handling (more efficient than goroutines)
if deadline := time.Now().Add(GetConfig().WriteTimeout); deadline.After(time.Now()) { if deadline := time.Now().Add(Config.WriteTimeout); deadline.After(time.Now()) {
if err := conn.SetWriteDeadline(deadline); err != nil { if err := conn.SetWriteDeadline(deadline); err != nil {
// If we can't set deadline, proceed without it // If we can't set deadline, proceed without it
// This maintains compatibility with connections that don't support deadlines // This maintains compatibility with connections that don't support deadlines

View File

@ -23,8 +23,8 @@ const (
// Constants are now defined in unified_ipc.go // Constants are now defined in unified_ipc.go
var ( var (
maxFrameSize = GetConfig().MaxFrameSize // Maximum Opus frame size maxFrameSize = Config.MaxFrameSize // Maximum Opus frame size
messagePoolSize = GetConfig().MessagePoolSize // Pre-allocated message pool size messagePoolSize = Config.MessagePoolSize // Pre-allocated message pool size
) )
// Legacy aliases for backward compatibility // Legacy aliases for backward compatibility
@ -77,7 +77,7 @@ func initializeMessagePool() {
messagePoolInitOnce.Do(func() { messagePoolInitOnce.Do(func() {
preallocSize := messagePoolSize / 4 // 25% pre-allocated for immediate use preallocSize := messagePoolSize / 4 // 25% pre-allocated for immediate use
globalMessagePool.preallocSize = preallocSize globalMessagePool.preallocSize = preallocSize
globalMessagePool.maxPoolSize = messagePoolSize * GetConfig().PoolGrowthMultiplier // Allow growth up to 2x globalMessagePool.maxPoolSize = messagePoolSize * Config.PoolGrowthMultiplier // Allow growth up to 2x
globalMessagePool.preallocated = make([]*OptimizedIPCMessage, 0, preallocSize) globalMessagePool.preallocated = make([]*OptimizedIPCMessage, 0, preallocSize)
// Pre-allocate messages for immediate use // Pre-allocate messages for immediate use
@ -378,7 +378,7 @@ func (ais *AudioInputServer) handleConnection(conn net.Conn) {
if ais.conn == nil { if ais.conn == nil {
return return
} }
time.Sleep(GetConfig().DefaultSleepDuration) time.Sleep(Config.DefaultSleepDuration)
} }
} }
} }
@ -499,11 +499,11 @@ func (ais *AudioInputServer) processOpusFrame(data []byte) error {
} }
// Get cached config once - avoid repeated calls and locking // Get cached config once - avoid repeated calls and locking
cache := GetCachedConfig() cache := Config
// Skip cache expiry check in hotpath - background updates handle this // Skip cache expiry check in hotpath - background updates handle this
// Get a PCM buffer from the pool for optimized decode-write // Get a PCM buffer from the pool for optimized decode-write
pcmBuffer := GetBufferFromPool(cache.GetMaxPCMBufferSize()) pcmBuffer := GetBufferFromPool(cache.MaxPCMBufferSize)
defer ReturnBufferToPool(pcmBuffer) defer ReturnBufferToPool(pcmBuffer)
// Direct CGO call - avoid wrapper function overhead // Direct CGO call - avoid wrapper function overhead
@ -646,9 +646,9 @@ func (aic *AudioInputClient) Connect() error {
return nil return nil
} }
// Exponential backoff starting from config // Exponential backoff starting from config
backoffStart := GetConfig().BackoffStart backoffStart := Config.BackoffStart
delay := time.Duration(backoffStart.Nanoseconds()*(1<<uint(i/3))) * time.Nanosecond delay := time.Duration(backoffStart.Nanoseconds()*(1<<uint(i/3))) * time.Nanosecond
maxDelay := GetConfig().MaxRetryDelay maxDelay := Config.MaxRetryDelay
if delay > maxDelay { if delay > maxDelay {
delay = maxDelay delay = maxDelay
} }
@ -911,10 +911,10 @@ func (ais *AudioInputServer) startReaderGoroutine() {
// Enhanced error tracking and recovery // Enhanced error tracking and recovery
var consecutiveErrors int var consecutiveErrors int
var lastErrorTime time.Time var lastErrorTime time.Time
maxConsecutiveErrors := GetConfig().MaxConsecutiveErrors maxConsecutiveErrors := Config.MaxConsecutiveErrors
errorResetWindow := GetConfig().RestartWindow // Use existing restart window errorResetWindow := Config.RestartWindow // Use existing restart window
baseBackoffDelay := GetConfig().RetryDelay baseBackoffDelay := Config.RetryDelay
maxBackoffDelay := GetConfig().MaxRetryDelay maxBackoffDelay := Config.MaxRetryDelay
logger := logging.GetDefaultLogger().With().Str("component", AudioInputClientComponent).Logger() logger := logging.GetDefaultLogger().With().Str("component", AudioInputClientComponent).Logger()
@ -1025,7 +1025,7 @@ func (ais *AudioInputServer) startProcessorGoroutine() {
processorTask := func() { processorTask := func() {
// Only lock OS thread and set priority for high-load scenarios // Only lock OS thread and set priority for high-load scenarios
// This reduces interference with input processing threads // This reduces interference with input processing threads
config := GetConfig() config := Config
useThreadOptimizations := config.MaxAudioProcessorWorkers > 8 useThreadOptimizations := config.MaxAudioProcessorWorkers > 8
if useThreadOptimizations { if useThreadOptimizations {
@ -1137,7 +1137,7 @@ func (ais *AudioInputServer) processMessageWithRecovery(msg *InputIPCMessage, lo
select { select {
case processChan <- msg: case processChan <- msg:
return nil return nil
case <-time.After(GetConfig().WriteTimeout): case <-time.After(Config.WriteTimeout):
// Processing queue full and timeout reached, drop frame // Processing queue full and timeout reached, drop frame
atomic.AddInt64(&ais.droppedFrames, 1) atomic.AddInt64(&ais.droppedFrames, 1)
return fmt.Errorf("processing queue timeout") return fmt.Errorf("processing queue timeout")
@ -1156,7 +1156,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
monitorTask := func() { monitorTask := func() {
// Monitor goroutine doesn't need thread locking for most scenarios // Monitor goroutine doesn't need thread locking for most scenarios
// Only use thread optimizations for high-throughput scenarios // Only use thread optimizations for high-throughput scenarios
config := GetConfig() config := Config
useThreadOptimizations := config.MaxAudioProcessorWorkers > 8 useThreadOptimizations := config.MaxAudioProcessorWorkers > 8
if useThreadOptimizations { if useThreadOptimizations {
@ -1167,11 +1167,11 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
} }
defer ais.wg.Done() defer ais.wg.Done()
ticker := time.NewTicker(GetConfig().DefaultTickerInterval) ticker := time.NewTicker(Config.DefaultTickerInterval)
defer ticker.Stop() defer ticker.Stop()
// Buffer size update ticker (less frequent) // Buffer size update ticker (less frequent)
bufferUpdateTicker := time.NewTicker(GetConfig().BufferUpdateInterval) bufferUpdateTicker := time.NewTicker(Config.BufferUpdateInterval)
defer bufferUpdateTicker.Stop() defer bufferUpdateTicker.Stop()
for { for {
@ -1330,7 +1330,7 @@ func (mp *MessagePool) GetMessagePoolStats() MessagePoolStats {
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * GetConfig().PercentageMultiplier hitRate = float64(hitCount) / float64(totalRequests) * Config.PercentageMultiplier
} }
// Calculate channel pool size // Calculate channel pool size

View File

@ -24,7 +24,7 @@ const (
// Methods are now inherited from UnifiedIPCMessage // Methods are now inherited from UnifiedIPCMessage
// Global shared message pool for output IPC client header reading // Global shared message pool for output IPC client header reading
var globalOutputClientMessagePool = NewGenericMessagePool(GetConfig().OutputMessagePoolSize) var globalOutputClientMessagePool = NewGenericMessagePool(Config.OutputMessagePoolSize)
// AudioOutputServer is now an alias for UnifiedAudioServer // AudioOutputServer is now an alias for UnifiedAudioServer
type AudioOutputServer = UnifiedAudioServer type AudioOutputServer = UnifiedAudioServer
@ -95,7 +95,7 @@ func (c *AudioOutputClient) ReceiveFrame() ([]byte, error) {
} }
size := binary.LittleEndian.Uint32(optMsg.header[5:9]) size := binary.LittleEndian.Uint32(optMsg.header[5:9])
maxFrameSize := GetConfig().OutputMaxFrameSize maxFrameSize := Config.OutputMaxFrameSize
if int(size) > maxFrameSize { if int(size) > maxFrameSize {
return nil, fmt.Errorf("received frame size validation failed: got %d bytes, maximum allowed %d bytes", size, maxFrameSize) return nil, fmt.Errorf("received frame size validation failed: got %d bytes, maximum allowed %d bytes", size, maxFrameSize)
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"math"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -17,8 +18,8 @@ import (
// Unified IPC constants // Unified IPC constants
var ( var (
outputMagicNumber uint32 = GetConfig().OutputMagicNumber // "JKOU" (JetKVM Output) outputMagicNumber uint32 = Config.OutputMagicNumber // "JKOU" (JetKVM Output)
inputMagicNumber uint32 = GetConfig().InputMagicNumber // "JKMI" (JetKVM Microphone Input) inputMagicNumber uint32 = Config.InputMagicNumber // "JKMI" (JetKVM Microphone Input)
outputSocketName = "audio_output.sock" outputSocketName = "audio_output.sock"
inputSocketName = "audio_input.sock" inputSocketName = "audio_input.sock"
headerSize = 17 // Fixed header size: 4+1+4+8 bytes headerSize = 17 // Fixed header size: 4+1+4+8 bytes
@ -144,8 +145,8 @@ func NewUnifiedAudioServer(isInput bool) (*UnifiedAudioServer, error) {
logger: logger, logger: logger,
socketPath: socketPath, socketPath: socketPath,
magicNumber: magicNumber, magicNumber: magicNumber,
messageChan: make(chan *UnifiedIPCMessage, GetConfig().ChannelBufferSize), messageChan: make(chan *UnifiedIPCMessage, Config.ChannelBufferSize),
processChan: make(chan *UnifiedIPCMessage, GetConfig().ChannelBufferSize), processChan: make(chan *UnifiedIPCMessage, Config.ChannelBufferSize),
socketBufferConfig: DefaultSocketBufferConfig(), socketBufferConfig: DefaultSocketBufferConfig(),
latencyMonitor: nil, latencyMonitor: nil,
adaptiveOptimizer: nil, adaptiveOptimizer: nil,
@ -311,7 +312,7 @@ func (s *UnifiedAudioServer) readMessage(conn net.Conn) (*UnifiedIPCMessage, err
timestamp := int64(binary.LittleEndian.Uint64(header[9:17])) timestamp := int64(binary.LittleEndian.Uint64(header[9:17]))
// Validate length // Validate length
if length > uint32(GetConfig().MaxFrameSize) { if length > uint32(Config.MaxFrameSize) {
return nil, fmt.Errorf("message too large: %d bytes", length) return nil, fmt.Errorf("message too large: %d bytes", length)
} }
@ -339,7 +340,10 @@ func (s *UnifiedAudioServer) SendFrame(frame []byte) error {
defer s.mtx.Unlock() defer s.mtx.Unlock()
if !s.running || s.conn == nil { if !s.running || s.conn == nil {
return fmt.Errorf("no client connected") // Silently drop frames when no client is connected
// This prevents "no client connected" warnings during startup and quality changes
atomic.AddInt64(&s.droppedFrames, 1)
return nil // Return nil to avoid flooding logs with connection warnings
} }
start := time.Now() start := time.Now()
@ -398,7 +402,7 @@ func (s *UnifiedAudioServer) writeMessage(conn net.Conn, msg *UnifiedIPCMessage)
// UnifiedAudioClient provides common functionality for both input and output clients // UnifiedAudioClient provides common functionality for both input and output clients
type UnifiedAudioClient struct { type UnifiedAudioClient struct {
// Atomic fields first for ARM32 alignment // Atomic counters for frame statistics
droppedFrames int64 // Atomic counter for dropped frames droppedFrames int64 // Atomic counter for dropped frames
totalFrames int64 // Atomic counter for total frames totalFrames int64 // Atomic counter for total frames
@ -409,6 +413,13 @@ type UnifiedAudioClient struct {
socketPath string socketPath string
magicNumber uint32 magicNumber uint32
bufferPool *AudioBufferPool // Buffer pool for memory optimization bufferPool *AudioBufferPool // Buffer pool for memory optimization
// Connection health monitoring
lastHealthCheck time.Time
connectionErrors int64 // Atomic counter for connection errors
autoReconnect bool // Enable automatic reconnection
healthCheckTicker *time.Ticker
stopHealthCheck chan struct{}
} }
// NewUnifiedAudioClient creates a new unified audio client // NewUnifiedAudioClient creates a new unified audio client
@ -433,7 +444,9 @@ func NewUnifiedAudioClient(isInput bool) *UnifiedAudioClient {
logger: logger, logger: logger,
socketPath: socketPath, socketPath: socketPath,
magicNumber: magicNumber, magicNumber: magicNumber,
bufferPool: NewAudioBufferPool(GetConfig().MaxFrameSize), bufferPool: NewAudioBufferPool(Config.MaxFrameSize),
autoReconnect: true, // Enable automatic reconnection by default
stopHealthCheck: make(chan struct{}),
} }
} }
@ -453,32 +466,46 @@ func (c *UnifiedAudioClient) Connect() error {
} }
// Try connecting multiple times as the server might not be ready // Try connecting multiple times as the server might not be ready
// Reduced retry count and delay for faster startup // Use configurable retry parameters for better control
for i := 0; i < 10; i++ { maxAttempts := Config.MaxConnectionAttempts
conn, err := net.Dial("unix", c.socketPath) initialDelay := Config.ConnectionRetryDelay
maxDelay := Config.MaxConnectionRetryDelay
backoffFactor := Config.ConnectionBackoffFactor
for i := 0; i < maxAttempts; i++ {
// Set connection timeout for each attempt
conn, err := net.DialTimeout("unix", c.socketPath, Config.ConnectionTimeoutDelay)
if err == nil { if err == nil {
c.conn = conn c.conn = conn
c.running = true c.running = true
// Reset frame counters on successful connection // Reset frame counters on successful connection
atomic.StoreInt64(&c.totalFrames, 0) atomic.StoreInt64(&c.totalFrames, 0)
atomic.StoreInt64(&c.droppedFrames, 0) atomic.StoreInt64(&c.droppedFrames, 0)
c.logger.Info().Str("socket_path", c.socketPath).Msg("Connected to server") atomic.StoreInt64(&c.connectionErrors, 0)
c.lastHealthCheck = time.Now()
// Start health check monitoring if auto-reconnect is enabled
if c.autoReconnect {
c.startHealthCheck()
}
c.logger.Info().Str("socket_path", c.socketPath).Int("attempt", i+1).Msg("Connected to server")
return nil return nil
} }
// Exponential backoff starting from config
backoffStart := GetConfig().BackoffStart // Log connection attempt failure
delay := time.Duration(backoffStart.Nanoseconds()*(1<<uint(i/3))) * time.Nanosecond c.logger.Debug().Err(err).Str("socket_path", c.socketPath).Int("attempt", i+1).Int("max_attempts", maxAttempts).Msg("Connection attempt failed")
maxDelay := GetConfig().MaxRetryDelay
if delay > maxDelay { // Don't sleep after the last attempt
delay = maxDelay if i < maxAttempts-1 {
} // Calculate adaptive delay based on connection failure patterns
delay := c.calculateAdaptiveDelay(i, initialDelay, maxDelay, backoffFactor)
time.Sleep(delay) time.Sleep(delay)
} }
}
// Ensure clean state on connection failure // Ensure clean state on connection failure
c.conn = nil c.conn = nil
c.running = false c.running = false
return fmt.Errorf("failed to connect to audio server after 10 attempts") return fmt.Errorf("failed to connect to audio server after %d attempts", Config.MaxConnectionAttempts)
} }
// Disconnect disconnects the client from the server // Disconnect disconnects the client from the server
@ -492,6 +519,9 @@ func (c *UnifiedAudioClient) Disconnect() {
c.running = false c.running = false
// Stop health check monitoring
c.stopHealthCheckMonitoring()
if c.conn != nil { if c.conn != nil {
c.conn.Close() c.conn.Close()
c.conn = nil c.conn = nil
@ -511,7 +541,122 @@ func (c *UnifiedAudioClient) IsConnected() bool {
func (c *UnifiedAudioClient) GetFrameStats() (total, dropped int64) { func (c *UnifiedAudioClient) GetFrameStats() (total, dropped int64) {
total = atomic.LoadInt64(&c.totalFrames) total = atomic.LoadInt64(&c.totalFrames)
dropped = atomic.LoadInt64(&c.droppedFrames) dropped = atomic.LoadInt64(&c.droppedFrames)
return total, dropped return
}
// startHealthCheck starts the connection health monitoring
func (c *UnifiedAudioClient) startHealthCheck() {
if c.healthCheckTicker != nil {
c.healthCheckTicker.Stop()
}
c.healthCheckTicker = time.NewTicker(Config.HealthCheckInterval)
go func() {
for {
select {
case <-c.healthCheckTicker.C:
c.performHealthCheck()
case <-c.stopHealthCheck:
return
}
}
}()
}
// stopHealthCheckMonitoring stops the health check monitoring
func (c *UnifiedAudioClient) stopHealthCheckMonitoring() {
if c.healthCheckTicker != nil {
c.healthCheckTicker.Stop()
c.healthCheckTicker = nil
}
select {
case c.stopHealthCheck <- struct{}{}:
default:
}
}
// performHealthCheck checks the connection health and attempts reconnection if needed
func (c *UnifiedAudioClient) performHealthCheck() {
c.mtx.Lock()
defer c.mtx.Unlock()
if !c.running || c.conn == nil {
return
}
// Simple health check: try to get connection info
if tcpConn, ok := c.conn.(*net.UnixConn); ok {
if _, err := tcpConn.File(); err != nil {
// Connection is broken
atomic.AddInt64(&c.connectionErrors, 1)
c.logger.Warn().Err(err).Msg("Connection health check failed, attempting reconnection")
// Close the broken connection
c.conn.Close()
c.conn = nil
c.running = false
// Attempt reconnection
go func() {
time.Sleep(Config.ReconnectionInterval)
if err := c.Connect(); err != nil {
c.logger.Error().Err(err).Msg("Failed to reconnect during health check")
}
}()
}
}
c.lastHealthCheck = time.Now()
}
// SetAutoReconnect enables or disables automatic reconnection
func (c *UnifiedAudioClient) SetAutoReconnect(enabled bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.autoReconnect = enabled
if !enabled {
c.stopHealthCheckMonitoring()
} else if c.running {
c.startHealthCheck()
}
}
// GetConnectionErrors returns the number of connection errors
func (c *UnifiedAudioClient) GetConnectionErrors() int64 {
return atomic.LoadInt64(&c.connectionErrors)
}
// calculateAdaptiveDelay calculates retry delay based on system load and failure patterns
func (c *UnifiedAudioClient) calculateAdaptiveDelay(attempt int, initialDelay, maxDelay time.Duration, backoffFactor float64) time.Duration {
// Base exponential backoff
baseDelay := time.Duration(float64(initialDelay.Nanoseconds()) * math.Pow(backoffFactor, float64(attempt)))
// Get connection error history for adaptive adjustment
errorCount := atomic.LoadInt64(&c.connectionErrors)
// Adjust delay based on recent connection errors
// More errors = longer delays to avoid overwhelming the server
adaptiveFactor := 1.0
if errorCount > 5 {
adaptiveFactor = 1.5 // 50% longer delays after many errors
} else if errorCount > 10 {
adaptiveFactor = 2.0 // Double delays after excessive errors
}
// Apply adaptive factor
adaptiveDelay := time.Duration(float64(baseDelay.Nanoseconds()) * adaptiveFactor)
// Ensure we don't exceed maximum delay
if adaptiveDelay > maxDelay {
adaptiveDelay = maxDelay
}
// Add small random jitter to avoid thundering herd
jitter := time.Duration(float64(adaptiveDelay.Nanoseconds()) * 0.1 * (0.5 + float64(attempt%3)/6.0))
adaptiveDelay += jitter
return adaptiveDelay
} }
// Helper functions for socket paths // Helper functions for socket paths

View File

@ -63,9 +63,9 @@ func (aim *AudioInputIPCManager) Start() error {
} }
config := InputIPCConfig{ config := InputIPCConfig{
SampleRate: GetConfig().InputIPCSampleRate, SampleRate: Config.InputIPCSampleRate,
Channels: GetConfig().InputIPCChannels, Channels: Config.InputIPCChannels,
FrameSize: GetConfig().InputIPCFrameSize, FrameSize: Config.InputIPCFrameSize,
} }
// Validate configuration before using it // Validate configuration before using it
@ -80,7 +80,7 @@ func (aim *AudioInputIPCManager) Start() error {
} }
// Wait for subprocess readiness // Wait for subprocess readiness
time.Sleep(GetConfig().LongSleepDuration) time.Sleep(Config.LongSleepDuration)
err = aim.supervisor.SendConfig(config) err = aim.supervisor.SendConfig(config)
if err != nil { if err != nil {

View File

@ -57,9 +57,9 @@ func (aom *AudioOutputIPCManager) Start() error {
// Send initial configuration // Send initial configuration
config := OutputIPCConfig{ config := OutputIPCConfig{
SampleRate: GetConfig().SampleRate, SampleRate: Config.SampleRate,
Channels: GetConfig().Channels, Channels: Config.Channels,
FrameSize: int(GetConfig().AudioQualityMediumFrameSize.Milliseconds()), FrameSize: int(Config.AudioQualityMediumFrameSize.Milliseconds()),
} }
if err := aom.SendConfig(config); err != nil { if err := aom.SendConfig(config); err != nil {

View File

@ -105,7 +105,7 @@ func GetMicrophoneContentionManager() *MicrophoneContentionManager {
} }
if atomic.CompareAndSwapInt32(&micContentionInitialized, 0, 1) { if atomic.CompareAndSwapInt32(&micContentionInitialized, 0, 1) {
manager := NewMicrophoneContentionManager(GetConfig().MicContentionTimeout) manager := NewMicrophoneContentionManager(Config.MicContentionTimeout)
atomic.StorePointer(&globalMicContentionManager, unsafe.Pointer(manager)) atomic.StorePointer(&globalMicContentionManager, unsafe.Pointer(manager))
return manager return manager
} }
@ -115,7 +115,7 @@ func GetMicrophoneContentionManager() *MicrophoneContentionManager {
return (*MicrophoneContentionManager)(ptr) return (*MicrophoneContentionManager)(ptr)
} }
return NewMicrophoneContentionManager(GetConfig().MicContentionTimeout) return NewMicrophoneContentionManager(Config.MicContentionTimeout)
} }
func TryMicrophoneOperation() OperationResult { func TryMicrophoneOperation() OperationResult {

View File

@ -64,10 +64,10 @@ type OptimizerConfig struct {
func DefaultOptimizerConfig() OptimizerConfig { func DefaultOptimizerConfig() OptimizerConfig {
return OptimizerConfig{ return OptimizerConfig{
MaxOptimizationLevel: 8, MaxOptimizationLevel: 8,
CooldownPeriod: GetConfig().CooldownPeriod, CooldownPeriod: Config.CooldownPeriod,
Aggressiveness: GetConfig().OptimizerAggressiveness, Aggressiveness: Config.OptimizerAggressiveness,
RollbackThreshold: GetConfig().RollbackThreshold, RollbackThreshold: Config.RollbackThreshold,
StabilityPeriod: GetConfig().AdaptiveOptimizerStability, StabilityPeriod: Config.AdaptiveOptimizerStability,
// Adaptive interval defaults // Adaptive interval defaults
MinOptimizationInterval: 100 * time.Millisecond, // High stability: check every 100ms MinOptimizationInterval: 100 * time.Millisecond, // High stability: check every 100ms
@ -142,7 +142,7 @@ func (ao *AdaptiveOptimizer) handleLatencyOptimization(metrics LatencyMetrics) e
// calculateTargetOptimizationLevel determines the appropriate optimization level // calculateTargetOptimizationLevel determines the appropriate optimization level
func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMetrics) int64 { func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMetrics) int64 {
// Base calculation on current latency vs target // Base calculation on current latency vs target
latencyRatio := float64(metrics.Current) / float64(GetConfig().AdaptiveOptimizerLatencyTarget) // 50ms target latencyRatio := float64(metrics.Current) / float64(Config.AdaptiveOptimizerLatencyTarget) // 50ms target
// Adjust based on trend // Adjust based on trend
switch metrics.Trend { switch metrics.Trend {
@ -158,7 +158,7 @@ func (ao *AdaptiveOptimizer) calculateTargetOptimizationLevel(metrics LatencyMet
latencyRatio *= ao.config.Aggressiveness latencyRatio *= ao.config.Aggressiveness
// Convert to optimization level // Convert to optimization level
targetLevel := int64(latencyRatio * GetConfig().LatencyScalingFactor) // Scale to 0-10 range targetLevel := int64(latencyRatio * Config.LatencyScalingFactor) // Scale to 0-10 range
if targetLevel > int64(ao.config.MaxOptimizationLevel) { if targetLevel > int64(ao.config.MaxOptimizationLevel) {
targetLevel = int64(ao.config.MaxOptimizationLevel) targetLevel = int64(ao.config.MaxOptimizationLevel)
} }

View File

@ -126,7 +126,7 @@ func (gm *GoroutineMonitor) GetGoroutineStats() map[string]interface{} {
// GetGoroutineMonitor returns the global goroutine monitor instance // GetGoroutineMonitor returns the global goroutine monitor instance
func GetGoroutineMonitor() *GoroutineMonitor { func GetGoroutineMonitor() *GoroutineMonitor {
if globalGoroutineMonitor == nil { if globalGoroutineMonitor == nil {
globalGoroutineMonitor = NewGoroutineMonitor(GetConfig().GoroutineMonitorInterval) globalGoroutineMonitor = NewGoroutineMonitor(Config.GoroutineMonitorInterval)
} }
return globalGoroutineMonitor return globalGoroutineMonitor
} }

View File

@ -81,7 +81,7 @@ const (
// DefaultLatencyConfig returns a sensible default configuration // DefaultLatencyConfig returns a sensible default configuration
func DefaultLatencyConfig() LatencyConfig { func DefaultLatencyConfig() LatencyConfig {
config := GetConfig() config := Config
return LatencyConfig{ return LatencyConfig{
TargetLatency: config.LatencyMonitorTarget, TargetLatency: config.LatencyMonitorTarget,
MaxLatency: config.MaxLatencyThreshold, MaxLatency: config.MaxLatencyThreshold,

View File

@ -16,26 +16,26 @@ import (
// Variables for process monitoring (using configuration) // Variables for process monitoring (using configuration)
var ( var (
// System constants // System constants
maxCPUPercent = GetConfig().MaxCPUPercent maxCPUPercent = Config.MaxCPUPercent
minCPUPercent = GetConfig().MinCPUPercent minCPUPercent = Config.MinCPUPercent
defaultClockTicks = GetConfig().DefaultClockTicks defaultClockTicks = Config.DefaultClockTicks
defaultMemoryGB = GetConfig().DefaultMemoryGB defaultMemoryGB = Config.DefaultMemoryGB
// Monitoring thresholds // Monitoring thresholds
maxWarmupSamples = GetConfig().MaxWarmupSamples maxWarmupSamples = Config.MaxWarmupSamples
warmupCPUSamples = GetConfig().WarmupCPUSamples warmupCPUSamples = Config.WarmupCPUSamples
// Channel buffer size // Channel buffer size
metricsChannelBuffer = GetConfig().MetricsChannelBuffer metricsChannelBuffer = Config.MetricsChannelBuffer
// Clock tick detection ranges // Clock tick detection ranges
minValidClockTicks = float64(GetConfig().MinValidClockTicks) minValidClockTicks = float64(Config.MinValidClockTicks)
maxValidClockTicks = float64(GetConfig().MaxValidClockTicks) maxValidClockTicks = float64(Config.MaxValidClockTicks)
) )
// Variables for process monitoring // Variables for process monitoring
var ( var (
pageSize = GetConfig().PageSize pageSize = Config.PageSize
) )
// ProcessMetrics represents CPU and memory usage metrics for a process // ProcessMetrics represents CPU and memory usage metrics for a process
@ -233,7 +233,7 @@ func (pm *ProcessMonitor) collectMetrics(pid int, state *processState) (ProcessM
// Calculate memory percentage (RSS / total system memory) // Calculate memory percentage (RSS / total system memory)
if totalMem := pm.getTotalMemory(); totalMem > 0 { if totalMem := pm.getTotalMemory(); totalMem > 0 {
metric.MemoryPercent = float64(metric.MemoryRSS) / float64(totalMem) * GetConfig().PercentageMultiplier metric.MemoryPercent = float64(metric.MemoryRSS) / float64(totalMem) * Config.PercentageMultiplier
} }
// Update state for next calculation // Update state for next calculation
@ -283,7 +283,7 @@ func (pm *ProcessMonitor) calculateCPUPercent(totalCPUTime int64, state *process
// Convert from clock ticks to seconds using actual system clock ticks // Convert from clock ticks to seconds using actual system clock ticks
clockTicks := pm.getClockTicks() clockTicks := pm.getClockTicks()
cpuSeconds := cpuDelta / clockTicks cpuSeconds := cpuDelta / clockTicks
cpuPercent := (cpuSeconds / timeDelta) * GetConfig().PercentageMultiplier cpuPercent := (cpuSeconds / timeDelta) * Config.PercentageMultiplier
// Apply bounds // Apply bounds
if cpuPercent > maxCPUPercent { if cpuPercent > maxCPUPercent {
@ -335,7 +335,7 @@ func (pm *ProcessMonitor) getClockTicks() float64 {
if len(fields) >= 2 { if len(fields) >= 2 {
if period, err := strconv.ParseInt(fields[1], 10, 64); err == nil && period > 0 { if period, err := strconv.ParseInt(fields[1], 10, 64); err == nil && period > 0 {
// Convert nanoseconds to Hz // Convert nanoseconds to Hz
hz := GetConfig().CGONanosecondsPerSecond / float64(period) hz := Config.CGONanosecondsPerSecond / float64(period)
if hz >= minValidClockTicks && hz <= maxValidClockTicks { if hz >= minValidClockTicks && hz <= maxValidClockTicks {
pm.clockTicks = hz pm.clockTicks = hz
return return
@ -363,7 +363,7 @@ func (pm *ProcessMonitor) getTotalMemory() int64 {
pm.memoryOnce.Do(func() { pm.memoryOnce.Do(func() {
file, err := os.Open("/proc/meminfo") file, err := os.Open("/proc/meminfo")
if err != nil { if err != nil {
pm.totalMemory = int64(defaultMemoryGB) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) pm.totalMemory = int64(defaultMemoryGB) * int64(Config.ProcessMonitorKBToBytes) * int64(Config.ProcessMonitorKBToBytes) * int64(Config.ProcessMonitorKBToBytes)
return return
} }
defer file.Close() defer file.Close()
@ -375,14 +375,14 @@ func (pm *ProcessMonitor) getTotalMemory() int64 {
fields := strings.Fields(line) fields := strings.Fields(line)
if len(fields) >= 2 { if len(fields) >= 2 {
if kb, err := strconv.ParseInt(fields[1], 10, 64); err == nil { if kb, err := strconv.ParseInt(fields[1], 10, 64); err == nil {
pm.totalMemory = kb * int64(GetConfig().ProcessMonitorKBToBytes) pm.totalMemory = kb * int64(Config.ProcessMonitorKBToBytes)
return return
} }
} }
break break
} }
} }
pm.totalMemory = int64(defaultMemoryGB) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) // Fallback pm.totalMemory = int64(defaultMemoryGB) * int64(Config.ProcessMonitorKBToBytes) * int64(Config.ProcessMonitorKBToBytes) * int64(Config.ProcessMonitorKBToBytes) // Fallback
}) })
return pm.totalMemory return pm.totalMemory
} }

View File

@ -70,7 +70,7 @@ func RunAudioOutputServer() error {
StopNonBlockingAudioStreaming() StopNonBlockingAudioStreaming()
// Give some time for cleanup // Give some time for cleanup
time.Sleep(GetConfig().DefaultSleepDuration) time.Sleep(Config.DefaultSleepDuration)
return nil return nil
} }

View File

@ -84,9 +84,9 @@ func StartAudioOutputStreaming(send func([]byte)) error {
buffer := make([]byte, GetMaxAudioFrameSize()) buffer := make([]byte, GetMaxAudioFrameSize())
consecutiveErrors := 0 consecutiveErrors := 0
maxConsecutiveErrors := GetConfig().MaxConsecutiveErrors maxConsecutiveErrors := Config.MaxConsecutiveErrors
errorBackoffDelay := GetConfig().RetryDelay errorBackoffDelay := Config.RetryDelay
maxErrorBackoff := GetConfig().MaxRetryDelay maxErrorBackoff := Config.MaxRetryDelay
for { for {
select { select {
@ -123,18 +123,18 @@ func StartAudioOutputStreaming(send func([]byte)) error {
Err(initErr). Err(initErr).
Msg("Failed to reinitialize audio system") Msg("Failed to reinitialize audio system")
// Exponential backoff for reinitialization failures // Exponential backoff for reinitialization failures
errorBackoffDelay = time.Duration(float64(errorBackoffDelay) * GetConfig().BackoffMultiplier) errorBackoffDelay = time.Duration(float64(errorBackoffDelay) * Config.BackoffMultiplier)
if errorBackoffDelay > maxErrorBackoff { if errorBackoffDelay > maxErrorBackoff {
errorBackoffDelay = maxErrorBackoff errorBackoffDelay = maxErrorBackoff
} }
} else { } else {
getOutputStreamingLogger().Info().Msg("Audio system reinitialized successfully") getOutputStreamingLogger().Info().Msg("Audio system reinitialized successfully")
consecutiveErrors = 0 consecutiveErrors = 0
errorBackoffDelay = GetConfig().RetryDelay // Reset backoff errorBackoffDelay = Config.RetryDelay // Reset backoff
} }
} else { } else {
// Brief delay for transient errors // Brief delay for transient errors
time.Sleep(GetConfig().ShortSleepDuration) time.Sleep(Config.ShortSleepDuration)
} }
continue continue
} }
@ -142,7 +142,7 @@ func StartAudioOutputStreaming(send func([]byte)) error {
// Success - reset error counters // Success - reset error counters
if consecutiveErrors > 0 { if consecutiveErrors > 0 {
consecutiveErrors = 0 consecutiveErrors = 0
errorBackoffDelay = GetConfig().RetryDelay errorBackoffDelay = Config.RetryDelay
} }
if n > 0 { if n > 0 {
@ -164,7 +164,7 @@ func StartAudioOutputStreaming(send func([]byte)) error {
RecordFrameReceived(n) RecordFrameReceived(n)
} }
// Small delay to prevent busy waiting // Small delay to prevent busy waiting
time.Sleep(GetConfig().ShortSleepDuration) time.Sleep(Config.ShortSleepDuration)
} }
} }
}() }()
@ -185,6 +185,6 @@ func StopAudioOutputStreaming() {
// Wait for streaming to stop // Wait for streaming to stop
for atomic.LoadInt32(&outputStreamingRunning) == 1 { for atomic.LoadInt32(&outputStreamingRunning) == 1 {
time.Sleep(GetConfig().ShortSleepDuration) time.Sleep(Config.ShortSleepDuration)
} }
} }

View File

@ -19,19 +19,19 @@ const (
// Restart configuration is now retrieved from centralized config // Restart configuration is now retrieved from centralized config
func getMaxRestartAttempts() int { func getMaxRestartAttempts() int {
return GetConfig().MaxRestartAttempts return Config.MaxRestartAttempts
} }
func getRestartWindow() time.Duration { func getRestartWindow() time.Duration {
return GetConfig().RestartWindow return Config.RestartWindow
} }
func getRestartDelay() time.Duration { func getRestartDelay() time.Duration {
return GetConfig().RestartDelay return Config.RestartDelay
} }
func getMaxRestartDelay() time.Duration { func getMaxRestartDelay() time.Duration {
return GetConfig().MaxRestartDelay return Config.MaxRestartDelay
} }
// AudioOutputSupervisor manages the audio output server subprocess lifecycle // AudioOutputSupervisor manages the audio output server subprocess lifecycle
@ -145,7 +145,7 @@ func (s *AudioOutputSupervisor) Stop() {
select { select {
case <-s.processDone: case <-s.processDone:
s.logger.Info().Str("component", AudioOutputSupervisorComponent).Msg("component stopped gracefully") s.logger.Info().Str("component", AudioOutputSupervisorComponent).Msg("component stopped gracefully")
case <-time.After(GetConfig().OutputSupervisorTimeout): case <-time.After(Config.OutputSupervisorTimeout):
s.logger.Warn().Str("component", AudioOutputSupervisorComponent).Msg("component did not stop gracefully, forcing termination") s.logger.Warn().Str("component", AudioOutputSupervisorComponent).Msg("component did not stop gracefully, forcing termination")
s.forceKillProcess("audio output server") s.forceKillProcess("audio output server")
} }
@ -158,7 +158,7 @@ func (s *AudioOutputSupervisor) supervisionLoop() {
// Configure supervision parameters // Configure supervision parameters
config := SupervisionConfig{ config := SupervisionConfig{
ProcessType: "audio output server", ProcessType: "audio output server",
Timeout: GetConfig().OutputSupervisorTimeout, Timeout: Config.OutputSupervisorTimeout,
EnableRestart: true, EnableRestart: true,
MaxRestartAttempts: getMaxRestartAttempts(), MaxRestartAttempts: getMaxRestartAttempts(),
RestartWindow: getRestartWindow(), RestartWindow: getRestartWindow(),

View File

@ -39,7 +39,7 @@ var (
// MaxAudioFrameSize is now retrieved from centralized config // MaxAudioFrameSize is now retrieved from centralized config
func GetMaxAudioFrameSize() int { func GetMaxAudioFrameSize() int {
return GetConfig().MaxAudioFrameSize return Config.MaxAudioFrameSize
} }
// AudioQuality represents different audio quality presets // AudioQuality represents different audio quality presets
@ -74,17 +74,17 @@ type AudioMetrics struct {
var ( var (
currentConfig = AudioConfig{ currentConfig = AudioConfig{
Quality: AudioQualityMedium, Quality: AudioQualityMedium,
Bitrate: GetConfig().AudioQualityMediumOutputBitrate, Bitrate: Config.AudioQualityMediumOutputBitrate,
SampleRate: GetConfig().SampleRate, SampleRate: Config.SampleRate,
Channels: GetConfig().Channels, Channels: Config.Channels,
FrameSize: GetConfig().AudioQualityMediumFrameSize, FrameSize: Config.AudioQualityMediumFrameSize,
} }
currentMicrophoneConfig = AudioConfig{ currentMicrophoneConfig = AudioConfig{
Quality: AudioQualityMedium, Quality: AudioQualityMedium,
Bitrate: GetConfig().AudioQualityMediumInputBitrate, Bitrate: Config.AudioQualityMediumInputBitrate,
SampleRate: GetConfig().SampleRate, SampleRate: Config.SampleRate,
Channels: 1, Channels: 1,
FrameSize: GetConfig().AudioQualityMediumFrameSize, FrameSize: Config.AudioQualityMediumFrameSize,
} }
metrics AudioMetrics metrics AudioMetrics
) )
@ -96,24 +96,24 @@ var qualityPresets = map[AudioQuality]struct {
frameSize time.Duration frameSize time.Duration
}{ }{
AudioQualityLow: { AudioQualityLow: {
outputBitrate: GetConfig().AudioQualityLowOutputBitrate, inputBitrate: GetConfig().AudioQualityLowInputBitrate, outputBitrate: Config.AudioQualityLowOutputBitrate, inputBitrate: Config.AudioQualityLowInputBitrate,
sampleRate: GetConfig().AudioQualityLowSampleRate, channels: GetConfig().AudioQualityLowChannels, sampleRate: Config.AudioQualityLowSampleRate, channels: Config.AudioQualityLowChannels,
frameSize: GetConfig().AudioQualityLowFrameSize, frameSize: Config.AudioQualityLowFrameSize,
}, },
AudioQualityMedium: { AudioQualityMedium: {
outputBitrate: GetConfig().AudioQualityMediumOutputBitrate, inputBitrate: GetConfig().AudioQualityMediumInputBitrate, outputBitrate: Config.AudioQualityMediumOutputBitrate, inputBitrate: Config.AudioQualityMediumInputBitrate,
sampleRate: GetConfig().AudioQualityMediumSampleRate, channels: GetConfig().AudioQualityMediumChannels, sampleRate: Config.AudioQualityMediumSampleRate, channels: Config.AudioQualityMediumChannels,
frameSize: GetConfig().AudioQualityMediumFrameSize, frameSize: Config.AudioQualityMediumFrameSize,
}, },
AudioQualityHigh: { AudioQualityHigh: {
outputBitrate: GetConfig().AudioQualityHighOutputBitrate, inputBitrate: GetConfig().AudioQualityHighInputBitrate, outputBitrate: Config.AudioQualityHighOutputBitrate, inputBitrate: Config.AudioQualityHighInputBitrate,
sampleRate: GetConfig().SampleRate, channels: GetConfig().AudioQualityHighChannels, sampleRate: Config.SampleRate, channels: Config.AudioQualityHighChannels,
frameSize: GetConfig().AudioQualityHighFrameSize, frameSize: Config.AudioQualityHighFrameSize,
}, },
AudioQualityUltra: { AudioQualityUltra: {
outputBitrate: GetConfig().AudioQualityUltraOutputBitrate, inputBitrate: GetConfig().AudioQualityUltraInputBitrate, outputBitrate: Config.AudioQualityUltraOutputBitrate, inputBitrate: Config.AudioQualityUltraInputBitrate,
sampleRate: GetConfig().SampleRate, channels: GetConfig().AudioQualityUltraChannels, sampleRate: Config.SampleRate, channels: Config.AudioQualityUltraChannels,
frameSize: GetConfig().AudioQualityUltraFrameSize, frameSize: Config.AudioQualityUltraFrameSize,
}, },
} }
@ -142,7 +142,7 @@ func GetMicrophoneQualityPresets() map[AudioQuality]AudioConfig {
Bitrate: preset.inputBitrate, Bitrate: preset.inputBitrate,
SampleRate: func() int { SampleRate: func() int {
if quality == AudioQualityLow { if quality == AudioQualityLow {
return GetConfig().AudioQualityMicLowSampleRate return Config.AudioQualityMicLowSampleRate
} }
return preset.sampleRate return preset.sampleRate
}(), }(),
@ -172,36 +172,36 @@ func SetAudioQuality(quality AudioQuality) {
var complexity, vbr, signalType, bandwidth, dtx int var complexity, vbr, signalType, bandwidth, dtx int
switch quality { switch quality {
case AudioQualityLow: case AudioQualityLow:
complexity = GetConfig().AudioQualityLowOpusComplexity complexity = Config.AudioQualityLowOpusComplexity
vbr = GetConfig().AudioQualityLowOpusVBR vbr = Config.AudioQualityLowOpusVBR
signalType = GetConfig().AudioQualityLowOpusSignalType signalType = Config.AudioQualityLowOpusSignalType
bandwidth = GetConfig().AudioQualityLowOpusBandwidth bandwidth = Config.AudioQualityLowOpusBandwidth
dtx = GetConfig().AudioQualityLowOpusDTX dtx = Config.AudioQualityLowOpusDTX
case AudioQualityMedium: case AudioQualityMedium:
complexity = GetConfig().AudioQualityMediumOpusComplexity complexity = Config.AudioQualityMediumOpusComplexity
vbr = GetConfig().AudioQualityMediumOpusVBR vbr = Config.AudioQualityMediumOpusVBR
signalType = GetConfig().AudioQualityMediumOpusSignalType signalType = Config.AudioQualityMediumOpusSignalType
bandwidth = GetConfig().AudioQualityMediumOpusBandwidth bandwidth = Config.AudioQualityMediumOpusBandwidth
dtx = GetConfig().AudioQualityMediumOpusDTX dtx = Config.AudioQualityMediumOpusDTX
case AudioQualityHigh: case AudioQualityHigh:
complexity = GetConfig().AudioQualityHighOpusComplexity complexity = Config.AudioQualityHighOpusComplexity
vbr = GetConfig().AudioQualityHighOpusVBR vbr = Config.AudioQualityHighOpusVBR
signalType = GetConfig().AudioQualityHighOpusSignalType signalType = Config.AudioQualityHighOpusSignalType
bandwidth = GetConfig().AudioQualityHighOpusBandwidth bandwidth = Config.AudioQualityHighOpusBandwidth
dtx = GetConfig().AudioQualityHighOpusDTX dtx = Config.AudioQualityHighOpusDTX
case AudioQualityUltra: case AudioQualityUltra:
complexity = GetConfig().AudioQualityUltraOpusComplexity complexity = Config.AudioQualityUltraOpusComplexity
vbr = GetConfig().AudioQualityUltraOpusVBR vbr = Config.AudioQualityUltraOpusVBR
signalType = GetConfig().AudioQualityUltraOpusSignalType signalType = Config.AudioQualityUltraOpusSignalType
bandwidth = GetConfig().AudioQualityUltraOpusBandwidth bandwidth = Config.AudioQualityUltraOpusBandwidth
dtx = GetConfig().AudioQualityUltraOpusDTX dtx = Config.AudioQualityUltraOpusDTX
default: default:
// Use medium quality as fallback // Use medium quality as fallback
complexity = GetConfig().AudioQualityMediumOpusComplexity complexity = Config.AudioQualityMediumOpusComplexity
vbr = GetConfig().AudioQualityMediumOpusVBR vbr = Config.AudioQualityMediumOpusVBR
signalType = GetConfig().AudioQualityMediumOpusSignalType signalType = Config.AudioQualityMediumOpusSignalType
bandwidth = GetConfig().AudioQualityMediumOpusBandwidth bandwidth = Config.AudioQualityMediumOpusBandwidth
dtx = GetConfig().AudioQualityMediumOpusDTX dtx = Config.AudioQualityMediumOpusDTX
} }
// Restart audio output subprocess with new OPUS configuration // Restart audio output subprocess with new OPUS configuration
@ -256,7 +256,7 @@ func SetAudioQuality(quality AudioQuality) {
} }
} else { } else {
// Fallback to dynamic update if supervisor is not available // Fallback to dynamic update if supervisor is not available
vbrConstraint := GetConfig().CGOOpusVBRConstraint vbrConstraint := Config.CGOOpusVBRConstraint
if err := updateOpusEncoderParams(config.Bitrate*1000, complexity, vbr, vbrConstraint, signalType, bandwidth, dtx); err != nil { if err := updateOpusEncoderParams(config.Bitrate*1000, complexity, vbr, vbrConstraint, signalType, bandwidth, dtx); err != nil {
logging.GetDefaultLogger().Error().Err(err).Msg("Failed to update OPUS encoder parameters") logging.GetDefaultLogger().Error().Err(err).Msg("Failed to update OPUS encoder parameters")
} }
@ -287,36 +287,36 @@ func SetMicrophoneQuality(quality AudioQuality) {
var complexity, vbr, signalType, bandwidth, dtx int var complexity, vbr, signalType, bandwidth, dtx int
switch quality { switch quality {
case AudioQualityLow: case AudioQualityLow:
complexity = GetConfig().AudioQualityLowOpusComplexity complexity = Config.AudioQualityLowOpusComplexity
vbr = GetConfig().AudioQualityLowOpusVBR vbr = Config.AudioQualityLowOpusVBR
signalType = GetConfig().AudioQualityLowOpusSignalType signalType = Config.AudioQualityLowOpusSignalType
bandwidth = GetConfig().AudioQualityLowOpusBandwidth bandwidth = Config.AudioQualityLowOpusBandwidth
dtx = GetConfig().AudioQualityLowOpusDTX dtx = Config.AudioQualityLowOpusDTX
case AudioQualityMedium: case AudioQualityMedium:
complexity = GetConfig().AudioQualityMediumOpusComplexity complexity = Config.AudioQualityMediumOpusComplexity
vbr = GetConfig().AudioQualityMediumOpusVBR vbr = Config.AudioQualityMediumOpusVBR
signalType = GetConfig().AudioQualityMediumOpusSignalType signalType = Config.AudioQualityMediumOpusSignalType
bandwidth = GetConfig().AudioQualityMediumOpusBandwidth bandwidth = Config.AudioQualityMediumOpusBandwidth
dtx = GetConfig().AudioQualityMediumOpusDTX dtx = Config.AudioQualityMediumOpusDTX
case AudioQualityHigh: case AudioQualityHigh:
complexity = GetConfig().AudioQualityHighOpusComplexity complexity = Config.AudioQualityHighOpusComplexity
vbr = GetConfig().AudioQualityHighOpusVBR vbr = Config.AudioQualityHighOpusVBR
signalType = GetConfig().AudioQualityHighOpusSignalType signalType = Config.AudioQualityHighOpusSignalType
bandwidth = GetConfig().AudioQualityHighOpusBandwidth bandwidth = Config.AudioQualityHighOpusBandwidth
dtx = GetConfig().AudioQualityHighOpusDTX dtx = Config.AudioQualityHighOpusDTX
case AudioQualityUltra: case AudioQualityUltra:
complexity = GetConfig().AudioQualityUltraOpusComplexity complexity = Config.AudioQualityUltraOpusComplexity
vbr = GetConfig().AudioQualityUltraOpusVBR vbr = Config.AudioQualityUltraOpusVBR
signalType = GetConfig().AudioQualityUltraOpusSignalType signalType = Config.AudioQualityUltraOpusSignalType
bandwidth = GetConfig().AudioQualityUltraOpusBandwidth bandwidth = Config.AudioQualityUltraOpusBandwidth
dtx = GetConfig().AudioQualityUltraOpusDTX dtx = Config.AudioQualityUltraOpusDTX
default: default:
// Use medium quality as fallback // Use medium quality as fallback
complexity = GetConfig().AudioQualityMediumOpusComplexity complexity = Config.AudioQualityMediumOpusComplexity
vbr = GetConfig().AudioQualityMediumOpusVBR vbr = Config.AudioQualityMediumOpusVBR
signalType = GetConfig().AudioQualityMediumOpusSignalType signalType = Config.AudioQualityMediumOpusSignalType
bandwidth = GetConfig().AudioQualityMediumOpusBandwidth bandwidth = Config.AudioQualityMediumOpusBandwidth
dtx = GetConfig().AudioQualityMediumOpusDTX dtx = Config.AudioQualityMediumOpusDTX
} }
// Update audio input subprocess configuration dynamically without restart // Update audio input subprocess configuration dynamically without restart

View File

@ -18,8 +18,8 @@ type SocketBufferConfig struct {
// DefaultSocketBufferConfig returns the default socket buffer configuration // DefaultSocketBufferConfig returns the default socket buffer configuration
func DefaultSocketBufferConfig() SocketBufferConfig { func DefaultSocketBufferConfig() SocketBufferConfig {
return SocketBufferConfig{ return SocketBufferConfig{
SendBufferSize: GetConfig().SocketOptimalBuffer, SendBufferSize: Config.SocketOptimalBuffer,
RecvBufferSize: GetConfig().SocketOptimalBuffer, RecvBufferSize: Config.SocketOptimalBuffer,
Enabled: true, Enabled: true,
} }
} }
@ -27,8 +27,8 @@ func DefaultSocketBufferConfig() SocketBufferConfig {
// HighLoadSocketBufferConfig returns configuration for high-load scenarios // HighLoadSocketBufferConfig returns configuration for high-load scenarios
func HighLoadSocketBufferConfig() SocketBufferConfig { func HighLoadSocketBufferConfig() SocketBufferConfig {
return SocketBufferConfig{ return SocketBufferConfig{
SendBufferSize: GetConfig().SocketMaxBuffer, SendBufferSize: Config.SocketMaxBuffer,
RecvBufferSize: GetConfig().SocketMaxBuffer, RecvBufferSize: Config.SocketMaxBuffer,
Enabled: true, Enabled: true,
} }
} }
@ -123,8 +123,8 @@ func ValidateSocketBufferConfig(config SocketBufferConfig) error {
return nil return nil
} }
minBuffer := GetConfig().SocketMinBuffer minBuffer := Config.SocketMinBuffer
maxBuffer := GetConfig().SocketMaxBuffer maxBuffer := Config.SocketMaxBuffer
if config.SendBufferSize < minBuffer { if config.SendBufferSize < minBuffer {
return fmt.Errorf("send buffer size validation failed: got %d bytes, minimum required %d bytes (configured range: %d-%d)", return fmt.Errorf("send buffer size validation failed: got %d bytes, minimum required %d bytes (configured range: %d-%d)",

View File

@ -366,17 +366,17 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
// Validate buffer size parameter // Validate buffer size parameter
if err := ValidateBufferSize(bufferSize); err != nil { if err := ValidateBufferSize(bufferSize); err != nil {
// Use default value on validation error // Use default value on validation error
bufferSize = GetConfig().AudioFramePoolSize bufferSize = Config.AudioFramePoolSize
} }
// Enhanced preallocation strategy based on buffer size and system capacity // Enhanced preallocation strategy based on buffer size and system capacity
var preallocSize int var preallocSize int
if bufferSize <= GetConfig().AudioFramePoolSize { if bufferSize <= Config.AudioFramePoolSize {
// For smaller pools, use enhanced preallocation (40% instead of 20%) // For smaller pools, use enhanced preallocation (40% instead of 20%)
preallocSize = GetConfig().PreallocPercentage * 2 preallocSize = Config.PreallocPercentage * 2
} else { } else {
// For larger pools, use standard enhanced preallocation (30% instead of 10%) // For larger pools, use standard enhanced preallocation (30% instead of 10%)
preallocSize = (GetConfig().PreallocPercentage * 3) / 2 preallocSize = (Config.PreallocPercentage * 3) / 2
} }
// Ensure minimum preallocation for better performance // Ensure minimum preallocation for better performance
@ -594,9 +594,9 @@ func (p *AudioBufferPool) Put(buf []byte) {
// Enhanced global buffer pools for different audio frame types with improved sizing // Enhanced global buffer pools for different audio frame types with improved sizing
var ( var (
// Main audio frame pool with enhanced capacity // Main audio frame pool with enhanced capacity
audioFramePool = NewAudioBufferPool(GetConfig().AudioFramePoolSize) audioFramePool = NewAudioBufferPool(Config.AudioFramePoolSize)
// Control message pool with enhanced capacity for better throughput // Control message pool with enhanced capacity for better throughput
audioControlPool = NewAudioBufferPool(512) // Increased from GetConfig().OutputHeaderSize to 512 for better control message handling audioControlPool = NewAudioBufferPool(512) // Increased from Config.OutputHeaderSize to 512 for better control message handling
) )
func GetAudioFrameBuffer() []byte { func GetAudioFrameBuffer() []byte {
@ -628,7 +628,7 @@ func (p *AudioBufferPool) GetPoolStats() AudioBufferPoolDetailedStats {
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * GetConfig().PercentageMultiplier hitRate = float64(hitCount) / float64(totalRequests) * Config.PercentageMultiplier
} }
return AudioBufferPoolDetailedStats{ return AudioBufferPoolDetailedStats{

View File

@ -21,12 +21,12 @@ func getEnvInt(key string, defaultValue int) int {
// with fallback to default config values // with fallback to default config values
func parseOpusConfig() (bitrate, complexity, vbr, signalType, bandwidth, dtx int) { func parseOpusConfig() (bitrate, complexity, vbr, signalType, bandwidth, dtx int) {
// Read configuration from environment variables with config defaults // Read configuration from environment variables with config defaults
bitrate = getEnvInt("JETKVM_OPUS_BITRATE", GetConfig().CGOOpusBitrate) bitrate = getEnvInt("JETKVM_OPUS_BITRATE", Config.CGOOpusBitrate)
complexity = getEnvInt("JETKVM_OPUS_COMPLEXITY", GetConfig().CGOOpusComplexity) complexity = getEnvInt("JETKVM_OPUS_COMPLEXITY", Config.CGOOpusComplexity)
vbr = getEnvInt("JETKVM_OPUS_VBR", GetConfig().CGOOpusVBR) vbr = getEnvInt("JETKVM_OPUS_VBR", Config.CGOOpusVBR)
signalType = getEnvInt("JETKVM_OPUS_SIGNAL_TYPE", GetConfig().CGOOpusSignalType) signalType = getEnvInt("JETKVM_OPUS_SIGNAL_TYPE", Config.CGOOpusSignalType)
bandwidth = getEnvInt("JETKVM_OPUS_BANDWIDTH", GetConfig().CGOOpusBandwidth) bandwidth = getEnvInt("JETKVM_OPUS_BANDWIDTH", Config.CGOOpusBandwidth)
dtx = getEnvInt("JETKVM_OPUS_DTX", GetConfig().CGOOpusDTX) dtx = getEnvInt("JETKVM_OPUS_DTX", Config.CGOOpusDTX)
return bitrate, complexity, vbr, signalType, bandwidth, dtx return bitrate, complexity, vbr, signalType, bandwidth, dtx
} }
@ -34,7 +34,7 @@ func parseOpusConfig() (bitrate, complexity, vbr, signalType, bandwidth, dtx int
// applyOpusConfig applies OPUS configuration to the global config // applyOpusConfig applies OPUS configuration to the global config
// with optional logging for the specified component // with optional logging for the specified component
func applyOpusConfig(bitrate, complexity, vbr, signalType, bandwidth, dtx int, component string, enableLogging bool) { func applyOpusConfig(bitrate, complexity, vbr, signalType, bandwidth, dtx int, component string, enableLogging bool) {
config := GetConfig() config := Config
config.CGOOpusBitrate = bitrate config.CGOOpusBitrate = bitrate
config.CGOOpusComplexity = complexity config.CGOOpusComplexity = complexity
config.CGOOpusVBR = vbr config.CGOOpusVBR = vbr

View File

@ -134,7 +134,7 @@ func (r *AudioRelay) relayLoop() {
defer r.wg.Done() defer r.wg.Done()
r.logger.Debug().Msg("Audio relay loop started") r.logger.Debug().Msg("Audio relay loop started")
var maxConsecutiveErrors = GetConfig().MaxConsecutiveErrors var maxConsecutiveErrors = Config.MaxConsecutiveErrors
consecutiveErrors := 0 consecutiveErrors := 0
for { for {
@ -153,7 +153,7 @@ func (r *AudioRelay) relayLoop() {
r.logger.Error().Int("consecutive_errors", consecutiveErrors).Int("max_errors", maxConsecutiveErrors).Msg("too many consecutive read errors, stopping audio relay") r.logger.Error().Int("consecutive_errors", consecutiveErrors).Int("max_errors", maxConsecutiveErrors).Msg("too many consecutive read errors, stopping audio relay")
return return
} }
time.Sleep(GetConfig().ShortSleepDuration) time.Sleep(Config.ShortSleepDuration)
continue continue
} }

View File

@ -224,7 +224,7 @@ func (aeb *AudioEventBroadcaster) sendToSubscriber(subscriber *AudioEventSubscri
return false return false
} }
ctx, cancel := context.WithTimeout(subscriber.ctx, time.Duration(GetConfig().EventTimeoutSeconds)*time.Second) ctx, cancel := context.WithTimeout(subscriber.ctx, time.Duration(Config.EventTimeoutSeconds)*time.Second)
defer cancel() defer cancel()
err := wsjson.Write(ctx, subscriber.conn, event) err := wsjson.Write(ctx, subscriber.conn, event)

View File

@ -98,8 +98,8 @@ type ZeroCopyFramePool struct {
// NewZeroCopyFramePool creates a new zero-copy frame pool // NewZeroCopyFramePool creates a new zero-copy frame pool
func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool { func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool {
// Pre-allocate frames for immediate availability // Pre-allocate frames for immediate availability
preallocSizeBytes := GetConfig().PreallocSize preallocSizeBytes := Config.PreallocSize
maxPoolSize := GetConfig().MaxPoolSize // Limit total pool size maxPoolSize := Config.MaxPoolSize // Limit total pool size
// Calculate number of frames based on memory budget, not frame count // Calculate number of frames based on memory budget, not frame count
preallocFrameCount := preallocSizeBytes / maxFrameSize preallocFrameCount := preallocSizeBytes / maxFrameSize
@ -327,7 +327,7 @@ func (p *ZeroCopyFramePool) GetZeroCopyPoolStats() ZeroCopyFramePoolStats {
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * GetConfig().PercentageMultiplier hitRate = float64(hitCount) / float64(totalRequests) * Config.PercentageMultiplier
} }
return ZeroCopyFramePoolStats{ return ZeroCopyFramePoolStats{

View File

@ -58,7 +58,7 @@ func startAudioSubprocess() error {
audio.SetAudioInputSupervisor(audioInputSupervisor) audio.SetAudioInputSupervisor(audioInputSupervisor)
// Set default OPUS configuration for audio input supervisor (low quality for single-core RV1106) // Set default OPUS configuration for audio input supervisor (low quality for single-core RV1106)
config := audio.GetConfig() config := audio.Config
audioInputSupervisor.SetOpusConfig( audioInputSupervisor.SetOpusConfig(
config.AudioQualityLowInputBitrate*1000, // Convert kbps to bps config.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
config.AudioQualityLowOpusComplexity, config.AudioQualityLowOpusComplexity,
@ -81,7 +81,8 @@ func startAudioSubprocess() error {
// This prevents "no client connected" errors during quality changes // This prevents "no client connected" errors during quality changes
go func() { go func() {
// Give the audio output server time to initialize and start listening // Give the audio output server time to initialize and start listening
time.Sleep(500 * time.Millisecond) // Increased delay to reduce frame drops during connection establishment
time.Sleep(1 * time.Second)
// Start audio relay system for main process // Start audio relay system for main process
// If there's an active WebRTC session, use its audio track // If there's an active WebRTC session, use its audio track