refactor(audio): remove redundant config variable assignments

Replace repeated local config variable assignments with direct Config access
to reduce memory allocations and improve code maintainability
This commit is contained in:
Alex P 2025-09-08 21:04:07 +00:00
parent 1d1658db15
commit 6f10010d71
3 changed files with 82 additions and 122 deletions

View File

@ -82,19 +82,16 @@ 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
cache := Config
// Validate input parameters with minimal overhead // Validate input parameters with minimal overhead
if batchSize <= 0 || batchSize > 1000 { if batchSize <= 0 || batchSize > 1000 {
batchSize = cache.BatchProcessorFramesPerBatch batchSize = Config.BatchProcessorFramesPerBatch
} }
if batchDuration <= 0 { if batchDuration <= 0 {
batchDuration = cache.BatchProcessingDelay batchDuration = Config.BatchProcessingDelay
} }
// Use optimized queue sizes from configuration // Use optimized queue sizes from configuration
queueSize := cache.BatchProcessorMaxQueueSize queueSize := Config.BatchProcessorMaxQueueSize
if queueSize <= 0 { if queueSize <= 0 {
queueSize = batchSize * 2 // Fallback to double batch size 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 // Pre-allocate logger to avoid repeated allocations
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 frameSize := Config.MinReadEncodeBuffer
frameSize := cache.MinReadEncodeBuffer
if frameSize == 0 { if frameSize == 0 {
frameSize = 1500 // Safe fallback frameSize = 1500 // Safe fallback
} }
@ -119,13 +115,11 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu
writeQueue: make(chan batchWriteRequest, queueSize), writeQueue: make(chan batchWriteRequest, queueSize),
readBufPool: &sync.Pool{ readBufPool: &sync.Pool{
New: func() interface{} { New: func() interface{} {
// Use pre-calculated frame size to avoid GetConfig() calls
return make([]byte, 0, frameSize) return make([]byte, 0, frameSize)
}, },
}, },
writeBufPool: &sync.Pool{ writeBufPool: &sync.Pool{
New: func() interface{} { New: func() interface{} {
// Use pre-calculated frame size to avoid GetConfig() calls
return make([]byte, 0, frameSize) return make([]byte, 0, frameSize)
}, },
}, },
@ -172,9 +166,6 @@ 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
cache := Config
// Validate buffer before processing // Validate buffer before processing
if err := ValidateBufferSize(len(buffer)); err != nil { if err := ValidateBufferSize(len(buffer)); err != nil {
// Only log validation errors in debug mode to reduce overhead // Only log validation errors in debug mode to reduce overhead
@ -219,7 +210,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.BatchProcessorTimeout): case <-time.After(Config.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 {
@ -233,9 +224,6 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
// BatchDecodeWrite performs batched audio decode and write operations // BatchDecodeWrite performs batched audio decode and write operations
// 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
cache := Config
// Validate buffer before processing // Validate buffer before processing
if err := ValidateBufferSize(len(buffer)); err != nil { if err := ValidateBufferSize(len(buffer)); err != nil {
// Only log validation errors in debug mode to reduce overhead // Only log validation errors in debug mode to reduce overhead
@ -280,7 +268,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.BatchProcessorTimeout): case <-time.After(Config.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)
@ -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 // 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
cache := Config
// Validate buffers before processing // Validate buffers before processing
if len(opusData) == 0 { if len(opusData) == 0 {
return 0, fmt.Errorf("empty opus data buffer") return 0, fmt.Errorf("empty opus data buffer")
@ -335,7 +320,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.BatchProcessorTimeout): case <-time.After(Config.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
@ -422,11 +407,9 @@ func (bap *BatchAudioProcessor) processBatchRead(batch []batchReadRequest) {
return return
} }
// Get cached config once - avoid repeated calls threadPinningThreshold := Config.BatchProcessorThreadPinningThreshold
cache := Config
threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold
if threadPinningThreshold == 0 { if threadPinningThreshold == 0 {
threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback threadPinningThreshold = Config.MinBatchSizeForThreadPinning // Fallback
} }
// Only pin to OS thread for large batches to reduce thread contention // Only pin to OS thread for large batches to reduce thread contention
@ -475,11 +458,9 @@ func (bap *BatchAudioProcessor) processBatchWrite(batch []batchWriteRequest) {
return return
} }
// Get cached config to avoid GetConfig() calls in hot path threadPinningThreshold := Config.BatchProcessorThreadPinningThreshold
cache := Config
threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold
if threadPinningThreshold == 0 { if threadPinningThreshold == 0 {
threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback threadPinningThreshold = Config.MinBatchSizeForThreadPinning // Fallback
} }
// Only pin to OS thread for large batches to reduce thread contention // Only pin to OS thread for large batches to reduce thread contention
@ -581,10 +562,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 processor := NewBatchAudioProcessor(Config.BatchProcessorFramesPerBatch, Config.BatchProcessorTimeout)
cache := Config
processor := NewBatchAudioProcessor(cache.BatchProcessorFramesPerBatch, cache.BatchProcessorTimeout)
atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor)) atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor))
return processor return processor
} }
@ -596,8 +574,7 @@ func GetBatchAudioProcessor() *BatchAudioProcessor {
} }
// Fallback: create a new processor (should rarely happen) // 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 // EnableBatchAudioProcessing enables the global batch processor

View File

@ -30,21 +30,21 @@ static snd_pcm_t *pcm_playback_handle = NULL;
static OpusEncoder *encoder = NULL; static OpusEncoder *encoder = NULL;
static OpusDecoder *decoder = NULL; static OpusDecoder *decoder = NULL;
// Opus encoder settings - initialized from Go configuration // Opus encoder settings - initialized from Go configuration
static int opus_bitrate = 96000; // Will be set from GetConfig().CGOOpusBitrate static int opus_bitrate = 96000; // Will be set from Config.CGOOpusBitrate
static int opus_complexity = 3; // Will be set from GetConfig().CGOOpusComplexity static int opus_complexity = 3; // Will be set from Config.CGOOpusComplexity
static int opus_vbr = 1; // Will be set from GetConfig().CGOOpusVBR static int opus_vbr = 1; // Will be set from Config.CGOOpusVBR
static int opus_vbr_constraint = 1; // Will be set from GetConfig().CGOOpusVBRConstraint static int opus_vbr_constraint = 1; // Will be set from Config.CGOOpusVBRConstraint
static int opus_signal_type = 3; // Will be set from GetConfig().CGOOpusSignalType 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_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 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 sample_rate = 48000; // Will be set from Config.CGOSampleRate
static int channels = 2; // Will be set from GetConfig().CGOChannels static int channels = 2; // Will be set from Config.CGOChannels
static int frame_size = 960; // Will be set from GetConfig().CGOFrameSize static int frame_size = 960; // Will be set from Config.CGOFrameSize
static int max_packet_size = 1500; // Will be set from GetConfig().CGOMaxPacketSize static int max_packet_size = 1500; // Will be set from Config.CGOMaxPacketSize
static int sleep_microseconds = 1000; // Will be set from GetConfig().CGOUsleepMicroseconds static int sleep_microseconds = 1000; // Will be set from Config.CGOUsleepMicroseconds
static int max_attempts_global = 5; // Will be set from GetConfig().CGOMaxAttempts static int max_attempts_global = 5; // Will be set from Config.CGOMaxAttempts
static int max_backoff_us_global = 500000; // Will be set from GetConfig().CGOMaxBackoffMicroseconds static int max_backoff_us_global = 500000; // Will be set from Config.CGOMaxBackoffMicroseconds
// Hardware optimization flags for constrained environments // Hardware optimization flags for constrained environments
static int use_mmap_access = 0; // Disable MMAP for compatibility (was 1) 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) 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.channels.Load()),
C.int(cache.frameSize.Load()), C.int(cache.frameSize.Load()),
C.int(cache.maxPacketSize.Load()), C.int(cache.maxPacketSize.Load()),
C.int(GetConfig().CGOUsleepMicroseconds), C.int(Config.CGOUsleepMicroseconds),
C.int(GetConfig().CGOMaxAttempts), C.int(Config.CGOMaxAttempts),
C.int(GetConfig().CGOMaxBackoffMicroseconds), C.int(Config.CGOMaxBackoffMicroseconds),
) )
result := C.jetkvm_audio_init() result := C.jetkvm_audio_init()
@ -726,7 +726,6 @@ func cgoAudioClose() {
} }
// AudioConfigCache provides a comprehensive caching system for audio configuration // AudioConfigCache provides a comprehensive caching system for audio configuration
// to minimize GetConfig() calls in the hot path
type AudioConfigCache struct { type AudioConfigCache struct {
// Atomic int64 fields MUST be first for ARM32 alignment (8-byte alignment required) // Atomic int64 fields MUST be first for ARM32 alignment (8-byte alignment required)
minFrameDuration atomic.Int64 // Store as nanoseconds minFrameDuration atomic.Int64 // Store as nanoseconds
@ -815,52 +814,50 @@ func (c *AudioConfigCache) Update() {
// Double-check after acquiring lock // Double-check after acquiring lock
if !c.initialized.Load() || time.Since(c.lastUpdate) > c.cacheExpiry { 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 // Update atomic values for lock-free access - CGO values
c.minReadEncodeBuffer.Store(int32(config.MinReadEncodeBuffer)) c.minReadEncodeBuffer.Store(int32(Config.MinReadEncodeBuffer))
c.maxDecodeWriteBuffer.Store(int32(config.MaxDecodeWriteBuffer)) c.maxDecodeWriteBuffer.Store(int32(Config.MaxDecodeWriteBuffer))
c.maxPacketSize.Store(int32(config.CGOMaxPacketSize)) c.maxPacketSize.Store(int32(Config.CGOMaxPacketSize))
c.maxPCMBufferSize.Store(int32(config.MaxPCMBufferSize)) c.maxPCMBufferSize.Store(int32(Config.MaxPCMBufferSize))
c.opusBitrate.Store(int32(config.CGOOpusBitrate)) c.opusBitrate.Store(int32(Config.CGOOpusBitrate))
c.opusComplexity.Store(int32(config.CGOOpusComplexity)) c.opusComplexity.Store(int32(Config.CGOOpusComplexity))
c.opusVBR.Store(int32(config.CGOOpusVBR)) c.opusVBR.Store(int32(Config.CGOOpusVBR))
c.opusVBRConstraint.Store(int32(config.CGOOpusVBRConstraint)) c.opusVBRConstraint.Store(int32(Config.CGOOpusVBRConstraint))
c.opusSignalType.Store(int32(config.CGOOpusSignalType)) c.opusSignalType.Store(int32(Config.CGOOpusSignalType))
c.opusBandwidth.Store(int32(config.CGOOpusBandwidth)) c.opusBandwidth.Store(int32(Config.CGOOpusBandwidth))
c.opusDTX.Store(int32(config.CGOOpusDTX)) c.opusDTX.Store(int32(Config.CGOOpusDTX))
c.sampleRate.Store(int32(config.CGOSampleRate)) c.sampleRate.Store(int32(Config.CGOSampleRate))
c.channels.Store(int32(config.CGOChannels)) c.channels.Store(int32(Config.CGOChannels))
c.frameSize.Store(int32(config.CGOFrameSize)) c.frameSize.Store(int32(Config.CGOFrameSize))
// Update additional validation values // Update additional validation values
c.maxAudioFrameSize.Store(int32(config.MaxAudioFrameSize)) c.maxAudioFrameSize.Store(int32(Config.MaxAudioFrameSize))
c.maxChannels.Store(int32(config.MaxChannels)) c.maxChannels.Store(int32(Config.MaxChannels))
c.minFrameDuration.Store(int64(config.MinFrameDuration)) c.minFrameDuration.Store(int64(Config.MinFrameDuration))
c.maxFrameDuration.Store(int64(config.MaxFrameDuration)) c.maxFrameDuration.Store(int64(Config.MaxFrameDuration))
c.minOpusBitrate.Store(int32(config.MinOpusBitrate)) c.minOpusBitrate.Store(int32(Config.MinOpusBitrate))
c.maxOpusBitrate.Store(int32(config.MaxOpusBitrate)) c.maxOpusBitrate.Store(int32(Config.MaxOpusBitrate))
// Update batch processing related values // Update batch processing related values
c.BatchProcessingTimeout = 100 * time.Millisecond // Fixed timeout for batch processing c.BatchProcessingTimeout = 100 * time.Millisecond // Fixed timeout for batch processing
c.BatchProcessorFramesPerBatch = config.BatchProcessorFramesPerBatch c.BatchProcessorFramesPerBatch = Config.BatchProcessorFramesPerBatch
c.BatchProcessorTimeout = config.BatchProcessorTimeout c.BatchProcessorTimeout = Config.BatchProcessorTimeout
c.BatchProcessingDelay = config.BatchProcessingDelay c.BatchProcessingDelay = Config.BatchProcessingDelay
c.MinBatchSizeForThreadPinning = config.MinBatchSizeForThreadPinning c.MinBatchSizeForThreadPinning = Config.MinBatchSizeForThreadPinning
c.BatchProcessorMaxQueueSize = config.BatchProcessorMaxQueueSize c.BatchProcessorMaxQueueSize = Config.BatchProcessorMaxQueueSize
c.BatchProcessorAdaptiveThreshold = config.BatchProcessorAdaptiveThreshold c.BatchProcessorAdaptiveThreshold = Config.BatchProcessorAdaptiveThreshold
c.BatchProcessorThreadPinningThreshold = config.BatchProcessorThreadPinningThreshold c.BatchProcessorThreadPinningThreshold = Config.BatchProcessorThreadPinningThreshold
// Pre-allocate common errors // Pre-allocate common errors
c.bufferTooSmallReadEncode = newBufferTooSmallError(0, config.MinReadEncodeBuffer) c.bufferTooSmallReadEncode = newBufferTooSmallError(0, Config.MinReadEncodeBuffer)
c.bufferTooLargeDecodeWrite = newBufferTooLargeError(config.MaxDecodeWriteBuffer+1, config.MaxDecodeWriteBuffer) c.bufferTooLargeDecodeWrite = newBufferTooLargeError(Config.MaxDecodeWriteBuffer+1, Config.MaxDecodeWriteBuffer)
c.lastUpdate = time.Now() c.lastUpdate = time.Now()
c.initialized.Store(true) c.initialized.Store(true)
// Update the global validation cache as well // Update the global validation cache as well
if cachedMaxFrameSize != 0 { if cachedMaxFrameSize != 0 {
cachedMaxFrameSize = config.MaxAudioFrameSize cachedMaxFrameSize = Config.MaxAudioFrameSize
} }
} }
} }

View File

@ -87,13 +87,11 @@ func ValidateBufferSize(size int) error {
return nil return nil
} }
// Slower path: full validation against SocketMaxBuffer
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 {
return fmt.Errorf("%w: buffer size %d exceeds maximum %d", return fmt.Errorf("%w: buffer size %d exceeds maximum %d",
ErrInvalidBufferSize, size, config.SocketMaxBuffer) ErrInvalidBufferSize, size, Config.SocketMaxBuffer)
} }
return nil return nil
} }
@ -123,16 +121,14 @@ func ValidateLatency(latency time.Duration) error {
return nil return nil
} }
// Slower path: full validation with 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",
ErrInvalidLatency, latency, minLatency) ErrInvalidLatency, latency, minLatency)
} }
if latency > config.MaxLatency { if latency > Config.MaxLatency {
return fmt.Errorf("%w: latency %v exceeds maximum %v", return fmt.Errorf("%w: latency %v exceeds maximum %v",
ErrInvalidLatency, latency, config.MaxLatency) ErrInvalidLatency, latency, Config.MaxLatency)
} }
return nil return nil
} }
@ -158,10 +154,8 @@ func ValidateMetricsInterval(interval time.Duration) error {
return nil return nil
} }
// Slower path: full validation with GetConfig() minInterval = Config.MinMetricsUpdateInterval
config := Config maxInterval = Config.MaxMetricsUpdateInterval
minInterval = config.MinMetricsUpdateInterval
maxInterval = config.MaxMetricsUpdateInterval
if interval < minInterval { if interval < minInterval {
return ErrInvalidMetricsInterval return ErrInvalidMetricsInterval
} }
@ -192,11 +186,9 @@ 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 minSampleRate := Config.MinSampleRate
config := Config maxSampleRate := Config.MaxSampleRate
minSampleRate := config.MinSampleRate maxChannels := Config.MaxChannels
maxSampleRate := config.MaxSampleRate
maxChannels := config.MaxChannels
if sampleRate < minSampleRate || sampleRate > maxSampleRate { if sampleRate < minSampleRate || sampleRate > maxSampleRate {
return ErrInvalidSampleRate return ErrInvalidSampleRate
} }
@ -211,11 +203,9 @@ 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 minSampleRate := Config.MinSampleRate
config := Config maxSampleRate := Config.MaxSampleRate
minSampleRate := config.MinSampleRate maxChannels := Config.MaxChannels
maxSampleRate := config.MaxSampleRate
maxChannels := config.MaxChannels
if sampleRate < minSampleRate || sampleRate > maxSampleRate { if sampleRate < minSampleRate || sampleRate > maxSampleRate {
return ErrInvalidSampleRate return ErrInvalidSampleRate
} }
@ -236,7 +226,7 @@ func ValidateLatencyConfig(config LatencyConfig) error {
if err := ValidateLatency(config.MaxLatency); err != nil { if err := ValidateLatency(config.MaxLatency); err != nil {
return err return err
} }
if config.TargetLatency >= config.MaxLatency { if config.TargetLatency >= Config.MaxLatency {
return ErrInvalidLatency return ErrInvalidLatency
} }
if err := ValidateMetricsInterval(config.OptimizationInterval); err != nil { if err := ValidateMetricsInterval(config.OptimizationInterval); err != nil {
@ -271,8 +261,7 @@ func ValidateSampleRate(sampleRate int) error {
} }
// Slower path: check against all valid rates // Slower path: check against all valid rates
config := Config validRates := Config.ValidSampleRates
validRates := config.ValidSampleRates
for _, rate := range validRates { for _, rate := range validRates {
if sampleRate == rate { if sampleRate == rate {
return nil return nil
@ -340,17 +329,15 @@ func ValidateBitrate(bitrate int) error {
return nil return nil
} }
// Slower path: full validation with 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 {
return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps", 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", 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 return nil
} }
@ -462,11 +449,11 @@ func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
} }
// Validate configuration values if config is provided // Validate configuration values if config is provided
if config != nil { if config != nil {
if config.MaxFrameSize <= 0 { if Config.MaxFrameSize <= 0 {
return fmt.Errorf("invalid MaxFrameSize: %d", config.MaxFrameSize) return fmt.Errorf("invalid MaxFrameSize: %d", Config.MaxFrameSize)
} }
if config.SampleRate <= 0 { if Config.SampleRate <= 0 {
return fmt.Errorf("invalid SampleRate: %d", config.SampleRate) return fmt.Errorf("invalid SampleRate: %d", Config.SampleRate)
} }
} }
return nil return nil
@ -478,8 +465,7 @@ 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 := Config cachedMaxFrameSize = Config.MaxAudioFrameSize
cachedMaxFrameSize = config.MaxAudioFrameSize
// Initialize the global audio config cache // Initialize the global audio config cache
cachedMaxFrameSize = Config.MaxAudioFrameSize cachedMaxFrameSize = Config.MaxAudioFrameSize