diff --git a/internal/audio/batch_audio.go b/internal/audio/batch_audio.go index f2417608..3887e591 100644 --- a/internal/audio/batch_audio.go +++ b/internal/audio/batch_audio.go @@ -82,19 +82,16 @@ type batchWriteResult struct { // NewBatchAudioProcessor creates a new batch audio processor func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAudioProcessor { - // Get cached config to avoid GetConfig() calls - cache := Config - // Validate input parameters with minimal overhead if batchSize <= 0 || batchSize > 1000 { - batchSize = cache.BatchProcessorFramesPerBatch + batchSize = Config.BatchProcessorFramesPerBatch } if batchDuration <= 0 { - batchDuration = cache.BatchProcessingDelay + batchDuration = Config.BatchProcessingDelay } // Use optimized queue sizes from configuration - queueSize := cache.BatchProcessorMaxQueueSize + queueSize := Config.BatchProcessorMaxQueueSize if queueSize <= 0 { queueSize = batchSize * 2 // Fallback to double batch size } @@ -103,8 +100,7 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu // Pre-allocate logger to avoid repeated allocations logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger() - // Pre-calculate frame size to avoid repeated GetConfig() calls - frameSize := cache.MinReadEncodeBuffer + frameSize := Config.MinReadEncodeBuffer if frameSize == 0 { frameSize = 1500 // Safe fallback } @@ -119,13 +115,11 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu writeQueue: make(chan batchWriteRequest, queueSize), readBufPool: &sync.Pool{ New: func() interface{} { - // Use pre-calculated frame size to avoid GetConfig() calls return make([]byte, 0, frameSize) }, }, writeBufPool: &sync.Pool{ New: func() interface{} { - // Use pre-calculated frame size to avoid GetConfig() calls return make([]byte, 0, frameSize) }, }, @@ -172,9 +166,6 @@ func (bap *BatchAudioProcessor) Stop() { // BatchReadEncode performs batched audio read and encode operations func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) { - // Get cached config to avoid GetConfig() calls in hot path - cache := Config - // Validate buffer before processing if err := ValidateBufferSize(len(buffer)); err != nil { // Only log validation errors in debug mode to reduce overhead @@ -219,7 +210,7 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) { select { case result := <-resultChan: return result.length, result.err - case <-time.After(cache.BatchProcessorTimeout): + case <-time.After(Config.BatchProcessorTimeout): // Timeout, fallback to single operation // Use sampling to reduce atomic operations overhead if atomic.LoadInt64(&bap.stats.SingleReads)%10 == 0 { @@ -233,9 +224,6 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) { // BatchDecodeWrite performs batched audio decode and write operations // This is the legacy version that uses a single buffer func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) { - // Get cached config to avoid GetConfig() calls in hot path - cache := Config - // Validate buffer before processing if err := ValidateBufferSize(len(buffer)); err != nil { // Only log validation errors in debug mode to reduce overhead @@ -280,7 +268,7 @@ func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) { select { case result := <-resultChan: return result.length, result.err - case <-time.After(cache.BatchProcessorTimeout): + case <-time.After(Config.BatchProcessorTimeout): // Use sampling to reduce atomic operations overhead if atomic.LoadInt64(&bap.stats.SingleWrites)%10 == 0 { atomic.AddInt64(&bap.stats.SingleWrites, 10) @@ -292,9 +280,6 @@ func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) { // BatchDecodeWriteWithBuffers performs batched audio decode and write operations with separate opus and PCM buffers func (bap *BatchAudioProcessor) BatchDecodeWriteWithBuffers(opusData []byte, pcmBuffer []byte) (int, error) { - // Get cached config to avoid GetConfig() calls in hot path - cache := Config - // Validate buffers before processing if len(opusData) == 0 { return 0, fmt.Errorf("empty opus data buffer") @@ -335,7 +320,7 @@ func (bap *BatchAudioProcessor) BatchDecodeWriteWithBuffers(opusData []byte, pcm select { case result := <-resultChan: return result.length, result.err - case <-time.After(cache.BatchProcessorTimeout): + case <-time.After(Config.BatchProcessorTimeout): atomic.AddInt64(&bap.stats.SingleWrites, 1) atomic.AddInt64(&bap.stats.WriteFrames, 1) // Use the optimized function with separate buffers @@ -422,11 +407,9 @@ func (bap *BatchAudioProcessor) processBatchRead(batch []batchReadRequest) { return } - // Get cached config once - avoid repeated calls - cache := Config - threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold + threadPinningThreshold := Config.BatchProcessorThreadPinningThreshold if threadPinningThreshold == 0 { - threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback + threadPinningThreshold = Config.MinBatchSizeForThreadPinning // Fallback } // Only pin to OS thread for large batches to reduce thread contention @@ -475,11 +458,9 @@ func (bap *BatchAudioProcessor) processBatchWrite(batch []batchWriteRequest) { return } - // Get cached config to avoid GetConfig() calls in hot path - cache := Config - threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold + threadPinningThreshold := Config.BatchProcessorThreadPinningThreshold if threadPinningThreshold == 0 { - threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback + threadPinningThreshold = Config.MinBatchSizeForThreadPinning // Fallback } // Only pin to OS thread for large batches to reduce thread contention @@ -581,10 +562,7 @@ func GetBatchAudioProcessor() *BatchAudioProcessor { // Initialize on first use if atomic.CompareAndSwapInt32(&batchProcessorInitialized, 0, 1) { - // Get cached config to avoid GetConfig() calls - cache := Config - - processor := NewBatchAudioProcessor(cache.BatchProcessorFramesPerBatch, cache.BatchProcessorTimeout) + processor := NewBatchAudioProcessor(Config.BatchProcessorFramesPerBatch, Config.BatchProcessorTimeout) atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor)) return processor } @@ -596,8 +574,7 @@ func GetBatchAudioProcessor() *BatchAudioProcessor { } // Fallback: create a new processor (should rarely happen) - config := Config - return NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout) + return NewBatchAudioProcessor(Config.BatchProcessorFramesPerBatch, Config.BatchProcessorTimeout) } // EnableBatchAudioProcessing enables the global batch processor diff --git a/internal/audio/cgo_audio.go b/internal/audio/cgo_audio.go index e9a205ee..7ce55bd0 100644 --- a/internal/audio/cgo_audio.go +++ b/internal/audio/cgo_audio.go @@ -30,21 +30,21 @@ static snd_pcm_t *pcm_playback_handle = NULL; static OpusEncoder *encoder = NULL; static OpusDecoder *decoder = NULL; // Opus encoder settings - initialized from Go configuration -static int opus_bitrate = 96000; // Will be set from GetConfig().CGOOpusBitrate -static int opus_complexity = 3; // Will be set from GetConfig().CGOOpusComplexity -static int opus_vbr = 1; // Will be set from GetConfig().CGOOpusVBR -static int opus_vbr_constraint = 1; // Will be set from GetConfig().CGOOpusVBRConstraint -static int opus_signal_type = 3; // Will be set from GetConfig().CGOOpusSignalType +static int opus_bitrate = 96000; // Will be set from Config.CGOOpusBitrate +static int opus_complexity = 3; // Will be set from Config.CGOOpusComplexity +static int opus_vbr = 1; // Will be set from Config.CGOOpusVBR +static int opus_vbr_constraint = 1; // Will be set from Config.CGOOpusVBRConstraint +static int opus_signal_type = 3; // Will be set from Config.CGOOpusSignalType static int opus_bandwidth = 1105; // OPUS_BANDWIDTH_WIDEBAND for compatibility (was 1101) -static int opus_dtx = 0; // Will be set from GetConfig().CGOOpusDTX +static int opus_dtx = 0; // Will be set from Config.CGOOpusDTX static int opus_lsb_depth = 16; // LSB depth for improved bit allocation on constrained hardware -static int sample_rate = 48000; // Will be set from GetConfig().CGOSampleRate -static int channels = 2; // Will be set from GetConfig().CGOChannels -static int frame_size = 960; // Will be set from GetConfig().CGOFrameSize -static int max_packet_size = 1500; // Will be set from GetConfig().CGOMaxPacketSize -static int sleep_microseconds = 1000; // Will be set from GetConfig().CGOUsleepMicroseconds -static int max_attempts_global = 5; // Will be set from GetConfig().CGOMaxAttempts -static int max_backoff_us_global = 500000; // Will be set from GetConfig().CGOMaxBackoffMicroseconds +static int sample_rate = 48000; // Will be set from Config.CGOSampleRate +static int channels = 2; // Will be set from Config.CGOChannels +static int frame_size = 960; // Will be set from Config.CGOFrameSize +static int max_packet_size = 1500; // Will be set from Config.CGOMaxPacketSize +static int sleep_microseconds = 1000; // Will be set from Config.CGOUsleepMicroseconds +static int max_attempts_global = 5; // Will be set from Config.CGOMaxAttempts +static int max_backoff_us_global = 500000; // Will be set from Config.CGOMaxBackoffMicroseconds // Hardware optimization flags for constrained environments static int use_mmap_access = 0; // Disable MMAP for compatibility (was 1) static int optimized_buffer_size = 0; // Disable optimized buffer sizing for stability (was 1) @@ -709,9 +709,9 @@ func cgoAudioInit() error { C.int(cache.channels.Load()), C.int(cache.frameSize.Load()), C.int(cache.maxPacketSize.Load()), - C.int(GetConfig().CGOUsleepMicroseconds), - C.int(GetConfig().CGOMaxAttempts), - C.int(GetConfig().CGOMaxBackoffMicroseconds), + C.int(Config.CGOUsleepMicroseconds), + C.int(Config.CGOMaxAttempts), + C.int(Config.CGOMaxBackoffMicroseconds), ) result := C.jetkvm_audio_init() @@ -726,7 +726,6 @@ func cgoAudioClose() { } // AudioConfigCache provides a comprehensive caching system for audio configuration -// to minimize GetConfig() calls in the hot path type AudioConfigCache struct { // Atomic int64 fields MUST be first for ARM32 alignment (8-byte alignment required) minFrameDuration atomic.Int64 // Store as nanoseconds @@ -815,52 +814,50 @@ func (c *AudioConfigCache) Update() { // Double-check after acquiring lock if !c.initialized.Load() || time.Since(c.lastUpdate) > c.cacheExpiry { - config := GetConfig() // Call GetConfig() only once - // Update atomic values for lock-free access - CGO values - c.minReadEncodeBuffer.Store(int32(config.MinReadEncodeBuffer)) - c.maxDecodeWriteBuffer.Store(int32(config.MaxDecodeWriteBuffer)) - c.maxPacketSize.Store(int32(config.CGOMaxPacketSize)) - c.maxPCMBufferSize.Store(int32(config.MaxPCMBufferSize)) - c.opusBitrate.Store(int32(config.CGOOpusBitrate)) - c.opusComplexity.Store(int32(config.CGOOpusComplexity)) - c.opusVBR.Store(int32(config.CGOOpusVBR)) - c.opusVBRConstraint.Store(int32(config.CGOOpusVBRConstraint)) - c.opusSignalType.Store(int32(config.CGOOpusSignalType)) - c.opusBandwidth.Store(int32(config.CGOOpusBandwidth)) - c.opusDTX.Store(int32(config.CGOOpusDTX)) - c.sampleRate.Store(int32(config.CGOSampleRate)) - c.channels.Store(int32(config.CGOChannels)) - c.frameSize.Store(int32(config.CGOFrameSize)) + c.minReadEncodeBuffer.Store(int32(Config.MinReadEncodeBuffer)) + c.maxDecodeWriteBuffer.Store(int32(Config.MaxDecodeWriteBuffer)) + c.maxPacketSize.Store(int32(Config.CGOMaxPacketSize)) + c.maxPCMBufferSize.Store(int32(Config.MaxPCMBufferSize)) + c.opusBitrate.Store(int32(Config.CGOOpusBitrate)) + c.opusComplexity.Store(int32(Config.CGOOpusComplexity)) + c.opusVBR.Store(int32(Config.CGOOpusVBR)) + c.opusVBRConstraint.Store(int32(Config.CGOOpusVBRConstraint)) + c.opusSignalType.Store(int32(Config.CGOOpusSignalType)) + c.opusBandwidth.Store(int32(Config.CGOOpusBandwidth)) + c.opusDTX.Store(int32(Config.CGOOpusDTX)) + c.sampleRate.Store(int32(Config.CGOSampleRate)) + c.channels.Store(int32(Config.CGOChannels)) + c.frameSize.Store(int32(Config.CGOFrameSize)) // Update additional validation values - c.maxAudioFrameSize.Store(int32(config.MaxAudioFrameSize)) - c.maxChannels.Store(int32(config.MaxChannels)) - c.minFrameDuration.Store(int64(config.MinFrameDuration)) - c.maxFrameDuration.Store(int64(config.MaxFrameDuration)) - c.minOpusBitrate.Store(int32(config.MinOpusBitrate)) - c.maxOpusBitrate.Store(int32(config.MaxOpusBitrate)) + c.maxAudioFrameSize.Store(int32(Config.MaxAudioFrameSize)) + c.maxChannels.Store(int32(Config.MaxChannels)) + c.minFrameDuration.Store(int64(Config.MinFrameDuration)) + c.maxFrameDuration.Store(int64(Config.MaxFrameDuration)) + c.minOpusBitrate.Store(int32(Config.MinOpusBitrate)) + c.maxOpusBitrate.Store(int32(Config.MaxOpusBitrate)) // Update batch processing related values c.BatchProcessingTimeout = 100 * time.Millisecond // Fixed timeout for batch processing - c.BatchProcessorFramesPerBatch = config.BatchProcessorFramesPerBatch - c.BatchProcessorTimeout = config.BatchProcessorTimeout - c.BatchProcessingDelay = config.BatchProcessingDelay - c.MinBatchSizeForThreadPinning = config.MinBatchSizeForThreadPinning - c.BatchProcessorMaxQueueSize = config.BatchProcessorMaxQueueSize - c.BatchProcessorAdaptiveThreshold = config.BatchProcessorAdaptiveThreshold - c.BatchProcessorThreadPinningThreshold = config.BatchProcessorThreadPinningThreshold + c.BatchProcessorFramesPerBatch = Config.BatchProcessorFramesPerBatch + c.BatchProcessorTimeout = Config.BatchProcessorTimeout + c.BatchProcessingDelay = Config.BatchProcessingDelay + c.MinBatchSizeForThreadPinning = Config.MinBatchSizeForThreadPinning + c.BatchProcessorMaxQueueSize = Config.BatchProcessorMaxQueueSize + c.BatchProcessorAdaptiveThreshold = Config.BatchProcessorAdaptiveThreshold + c.BatchProcessorThreadPinningThreshold = Config.BatchProcessorThreadPinningThreshold // Pre-allocate common errors - c.bufferTooSmallReadEncode = newBufferTooSmallError(0, config.MinReadEncodeBuffer) - c.bufferTooLargeDecodeWrite = newBufferTooLargeError(config.MaxDecodeWriteBuffer+1, config.MaxDecodeWriteBuffer) + c.bufferTooSmallReadEncode = newBufferTooSmallError(0, Config.MinReadEncodeBuffer) + c.bufferTooLargeDecodeWrite = newBufferTooLargeError(Config.MaxDecodeWriteBuffer+1, Config.MaxDecodeWriteBuffer) c.lastUpdate = time.Now() c.initialized.Store(true) // Update the global validation cache as well if cachedMaxFrameSize != 0 { - cachedMaxFrameSize = config.MaxAudioFrameSize + cachedMaxFrameSize = Config.MaxAudioFrameSize } } } diff --git a/internal/audio/core_validation.go b/internal/audio/core_validation.go index 03b44adb..4f5edb09 100644 --- a/internal/audio/core_validation.go +++ b/internal/audio/core_validation.go @@ -87,13 +87,11 @@ func ValidateBufferSize(size int) error { return nil } - // Slower path: full validation against SocketMaxBuffer - config := Config // Use SocketMaxBuffer as the upper limit for general buffer validation // This allows for socket buffers while still preventing extremely large allocations - if size > config.SocketMaxBuffer { + if size > Config.SocketMaxBuffer { return fmt.Errorf("%w: buffer size %d exceeds maximum %d", - ErrInvalidBufferSize, size, config.SocketMaxBuffer) + ErrInvalidBufferSize, size, Config.SocketMaxBuffer) } return nil } @@ -123,16 +121,14 @@ func ValidateLatency(latency time.Duration) error { return nil } - // Slower path: full validation with GetConfig() - config := Config minLatency := time.Millisecond // Minimum reasonable latency if latency > 0 && latency < minLatency { return fmt.Errorf("%w: latency %v below minimum %v", ErrInvalidLatency, latency, minLatency) } - if latency > config.MaxLatency { + if latency > Config.MaxLatency { return fmt.Errorf("%w: latency %v exceeds maximum %v", - ErrInvalidLatency, latency, config.MaxLatency) + ErrInvalidLatency, latency, Config.MaxLatency) } return nil } @@ -158,10 +154,8 @@ func ValidateMetricsInterval(interval time.Duration) error { return nil } - // Slower path: full validation with GetConfig() - config := Config - minInterval = config.MinMetricsUpdateInterval - maxInterval = config.MaxMetricsUpdateInterval + minInterval = Config.MinMetricsUpdateInterval + maxInterval = Config.MaxMetricsUpdateInterval if interval < minInterval { return ErrInvalidMetricsInterval } @@ -192,11 +186,9 @@ func ValidateAdaptiveBufferConfig(minSize, maxSize, defaultSize int) error { // ValidateInputIPCConfig validates input IPC configuration func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error { - // Use config values - config := Config - minSampleRate := config.MinSampleRate - maxSampleRate := config.MaxSampleRate - maxChannels := config.MaxChannels + minSampleRate := Config.MinSampleRate + maxSampleRate := Config.MaxSampleRate + maxChannels := Config.MaxChannels if sampleRate < minSampleRate || sampleRate > maxSampleRate { return ErrInvalidSampleRate } @@ -211,11 +203,9 @@ func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error { // ValidateOutputIPCConfig validates output IPC configuration func ValidateOutputIPCConfig(sampleRate, channels, frameSize int) error { - // Use config values - config := Config - minSampleRate := config.MinSampleRate - maxSampleRate := config.MaxSampleRate - maxChannels := config.MaxChannels + minSampleRate := Config.MinSampleRate + maxSampleRate := Config.MaxSampleRate + maxChannels := Config.MaxChannels if sampleRate < minSampleRate || sampleRate > maxSampleRate { return ErrInvalidSampleRate } @@ -236,7 +226,7 @@ func ValidateLatencyConfig(config LatencyConfig) error { if err := ValidateLatency(config.MaxLatency); err != nil { return err } - if config.TargetLatency >= config.MaxLatency { + if config.TargetLatency >= Config.MaxLatency { return ErrInvalidLatency } if err := ValidateMetricsInterval(config.OptimizationInterval); err != nil { @@ -271,8 +261,7 @@ func ValidateSampleRate(sampleRate int) error { } // Slower path: check against all valid rates - config := Config - validRates := config.ValidSampleRates + validRates := Config.ValidSampleRates for _, rate := range validRates { if sampleRate == rate { return nil @@ -340,17 +329,15 @@ func ValidateBitrate(bitrate int) error { return nil } - // Slower path: full validation with GetConfig() - config := Config // Convert kbps to bps for comparison with config limits bitrateInBps := bitrate * 1000 - if bitrateInBps < config.MinOpusBitrate { + if bitrateInBps < Config.MinOpusBitrate { return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps", - ErrInvalidBitrate, bitrate, bitrateInBps, config.MinOpusBitrate) + ErrInvalidBitrate, bitrate, bitrateInBps, Config.MinOpusBitrate) } - if bitrateInBps > config.MaxOpusBitrate { + if bitrateInBps > Config.MaxOpusBitrate { return fmt.Errorf("%w: bitrate %d kbps (%d bps) exceeds maximum %d bps", - ErrInvalidBitrate, bitrate, bitrateInBps, config.MaxOpusBitrate) + ErrInvalidBitrate, bitrate, bitrateInBps, Config.MaxOpusBitrate) } return nil } @@ -462,11 +449,11 @@ func ValidateAudioConfigConstants(config *AudioConfigConstants) error { } // Validate configuration values if config is provided if config != nil { - if config.MaxFrameSize <= 0 { - return fmt.Errorf("invalid MaxFrameSize: %d", config.MaxFrameSize) + if Config.MaxFrameSize <= 0 { + return fmt.Errorf("invalid MaxFrameSize: %d", Config.MaxFrameSize) } - if config.SampleRate <= 0 { - return fmt.Errorf("invalid SampleRate: %d", config.SampleRate) + if Config.SampleRate <= 0 { + return fmt.Errorf("invalid SampleRate: %d", Config.SampleRate) } } return nil @@ -478,8 +465,7 @@ var cachedMaxFrameSize int // InitValidationCache initializes cached validation values with actual config func InitValidationCache() { // Initialize the global cache variable for backward compatibility - config := Config - cachedMaxFrameSize = config.MaxAudioFrameSize + cachedMaxFrameSize = Config.MaxAudioFrameSize // Initialize the global audio config cache cachedMaxFrameSize = Config.MaxAudioFrameSize