refactor(audio): centralize config constants and update usage

Move hardcoded values to config constants and update all references to use centralized configuration. Includes:
- Audio processing timeouts and intervals
- CGO sleep durations
- Batch processing parameters
- Event formatting and timeouts
- Process monitor calculations
This commit is contained in:
Alex P 2025-08-25 19:30:57 +00:00
parent 9e343b3cc7
commit c5216920b3
8 changed files with 124 additions and 22 deletions

View File

@ -274,7 +274,8 @@ func GetBatchAudioProcessor() *BatchAudioProcessor {
// Initialize on first use // Initialize on first use
if atomic.CompareAndSwapInt32(&batchProcessorInitialized, 0, 1) { if atomic.CompareAndSwapInt32(&batchProcessorInitialized, 0, 1) {
processor := NewBatchAudioProcessor(4, 5*time.Millisecond) // 4 frames per batch, 5ms timeout config := GetConfig()
processor := NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout)
atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor)) atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor))
return processor return processor
} }
@ -286,7 +287,8 @@ func GetBatchAudioProcessor() *BatchAudioProcessor {
} }
// Fallback: create a new processor (should rarely happen) // Fallback: create a new processor (should rarely happen)
return NewBatchAudioProcessor(4, 5*time.Millisecond) config := GetConfig()
return NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout)
} }
// EnableBatchAudioProcessing enables the global batch processor // EnableBatchAudioProcessing enables the global batch processor

View File

@ -34,7 +34,7 @@ static int sample_rate = 48000; // Will be set from GetConfig().CGOSampl
static int channels = 2; // Will be set from GetConfig().CGOChannels static int channels = 2; // Will be set from GetConfig().CGOChannels
static int frame_size = 960; // Will be set from GetConfig().CGOFrameSize 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 max_packet_size = 1500; // Will be set from GetConfig().CGOMaxPacketSize
static int sleep_microseconds = 50000; // Will be set from GetConfig().CGOSleepMicroseconds static int sleep_microseconds = 1000; // Will be set from GetConfig().CGOUsleepMicroseconds
// Function to update constants from Go configuration // Function to update constants from Go configuration
void update_audio_constants(int bitrate, int complexity, int vbr, int vbr_constraint, void update_audio_constants(int bitrate, int complexity, int vbr, int vbr_constraint,
@ -244,7 +244,7 @@ int jetkvm_audio_read_encode(void *opus_buf) {
} else if (pcm_rc == -ESTRPIPE) { } else if (pcm_rc == -ESTRPIPE) {
// Device suspended, try to resume // Device suspended, try to resume
while ((err = snd_pcm_resume(pcm_handle)) == -EAGAIN) { while ((err = snd_pcm_resume(pcm_handle)) == -EAGAIN) {
usleep(1000); // 1ms usleep(sleep_microseconds); // Use centralized constant
} }
if (err < 0) { if (err < 0) {
err = snd_pcm_prepare(pcm_handle); err = snd_pcm_prepare(pcm_handle);
@ -358,7 +358,7 @@ int jetkvm_audio_decode_write(void *opus_buf, int opus_size) {
} else if (pcm_rc == -ESTRPIPE) { } else if (pcm_rc == -ESTRPIPE) {
// Device suspended, try to resume // Device suspended, try to resume
while ((err = snd_pcm_resume(pcm_playback_handle)) == -EAGAIN) { while ((err = snd_pcm_resume(pcm_playback_handle)) == -EAGAIN) {
usleep(1000); // 1ms usleep(sleep_microseconds); // Use centralized constant
} }
if (err < 0) { if (err < 0) {
err = snd_pcm_prepare(pcm_playback_handle); err = snd_pcm_prepare(pcm_playback_handle);
@ -376,7 +376,7 @@ int jetkvm_audio_decode_write(void *opus_buf, int opus_size) {
void jetkvm_audio_playback_close() { void jetkvm_audio_playback_close() {
// Wait for any ongoing operations to complete // Wait for any ongoing operations to complete
while (playback_initializing) { while (playback_initializing) {
usleep(1000); // 1ms usleep(sleep_microseconds); // Use centralized constant
} }
// Atomic check and set to prevent double cleanup // Atomic check and set to prevent double cleanup
@ -399,7 +399,7 @@ void jetkvm_audio_playback_close() {
void jetkvm_audio_close() { void jetkvm_audio_close() {
// Wait for any ongoing operations to complete // Wait for any ongoing operations to complete
while (capture_initializing) { while (capture_initializing) {
usleep(1000); // 1ms usleep(sleep_microseconds); // Use centralized constant
} }
capture_initialized = 0; capture_initialized = 0;
@ -448,7 +448,7 @@ func cgoAudioInit() error {
C.int(config.CGOChannels), C.int(config.CGOChannels),
C.int(config.CGOFrameSize), C.int(config.CGOFrameSize),
C.int(config.CGOMaxPacketSize), C.int(config.CGOMaxPacketSize),
C.int(config.CGOSleepMicroseconds), C.int(config.CGOUsleepMicroseconds),
) )
result := C.jetkvm_audio_init() result := C.jetkvm_audio_init()

View File

@ -830,6 +830,56 @@ type AudioConfigConstants struct {
PoolGrowthMultiplier int // 2x growth multiplier for pool sizes PoolGrowthMultiplier int // 2x growth multiplier for pool sizes
LatencyScalingFactor float64 // 2.0 for latency ratio scaling LatencyScalingFactor float64 // 2.0 for latency ratio scaling
OptimizerAggressiveness float64 // 0.7 for optimizer aggressiveness OptimizerAggressiveness float64 // 0.7 for optimizer aggressiveness
// CGO Audio Processing Constants
CGOUsleepMicroseconds int // 1000 microseconds (1ms) for CGO usleep calls
CGOPCMBufferSize int // 1920 samples for PCM buffer (max 2ch*960)
CGONanosecondsPerSecond float64 // 1000000000.0 for nanosecond conversions
// Frontend Constants
FrontendOperationDebounceMS int // 1000ms debounce for frontend operations
FrontendSyncDebounceMS int // 1000ms debounce for sync operations
FrontendSampleRate int // 48000Hz sample rate for frontend audio
FrontendRetryDelayMS int // 500ms retry delay
FrontendShortDelayMS int // 200ms short delay
FrontendLongDelayMS int // 300ms long delay
FrontendSyncDelayMS int // 500ms sync delay
FrontendMaxRetryAttempts int // 3 maximum retry attempts
FrontendAudioLevelUpdateMS int // 100ms audio level update interval
FrontendFFTSize int // 256 FFT size for audio analysis
FrontendAudioLevelMax int // 100 maximum audio level
FrontendReconnectIntervalMS int // 3000ms reconnect interval
FrontendSubscriptionDelayMS int // 100ms subscription delay
FrontendDebugIntervalMS int // 5000ms debug interval
// Process Monitor Constants
ProcessMonitorDefaultMemoryGB int // 4GB default memory for fallback
ProcessMonitorKBToBytes int // 1024 conversion factor
ProcessMonitorDefaultClockHz float64 // 250.0 Hz default for ARM systems
ProcessMonitorFallbackClockHz float64 // 1000.0 Hz fallback clock
ProcessMonitorTraditionalHz float64 // 100.0 Hz traditional clock
// Batch Processing Constants
BatchProcessorFramesPerBatch int // 4 frames per batch
BatchProcessorTimeout time.Duration // 5ms timeout
// Output Streaming Constants
OutputStreamingFrameIntervalMS int // 20ms frame interval (50 FPS)
// IPC Constants
IPCInitialBufferFrames int // 500 frames for initial buffer
// Event Constants
EventTimeoutSeconds int // 2 seconds for event timeout
EventTimeFormatString string // "2006-01-02T15:04:05.000Z" time format
EventSubscriptionDelayMS int // 100ms subscription delay
// Input Processing Constants
InputProcessingTimeoutMS int // 10ms processing timeout threshold
// Adaptive Buffer Constants
AdaptiveBufferCPUMultiplier int // 100 multiplier for CPU percentage
AdaptiveBufferMemoryMultiplier int // 100 multiplier for memory percentage
} }
// DefaultAudioConfig returns the default configuration constants // DefaultAudioConfig returns the default configuration constants
@ -1247,6 +1297,56 @@ func DefaultAudioConfig() *AudioConfigConstants {
PoolGrowthMultiplier: 2, // Pool growth multiplier PoolGrowthMultiplier: 2, // Pool growth multiplier
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
CGOUsleepMicroseconds: 1000, // 1000 microseconds (1ms) for CGO usleep calls
CGOPCMBufferSize: 1920, // 1920 samples for PCM buffer (max 2ch*960)
CGONanosecondsPerSecond: 1000000000.0, // 1000000000.0 for nanosecond conversions
// Frontend Constants
FrontendOperationDebounceMS: 1000, // 1000ms debounce for frontend operations
FrontendSyncDebounceMS: 1000, // 1000ms debounce for sync operations
FrontendSampleRate: 48000, // 48000Hz sample rate for frontend audio
FrontendRetryDelayMS: 500, // 500ms retry delay
FrontendShortDelayMS: 200, // 200ms short delay
FrontendLongDelayMS: 300, // 300ms long delay
FrontendSyncDelayMS: 500, // 500ms sync delay
FrontendMaxRetryAttempts: 3, // 3 maximum retry attempts
FrontendAudioLevelUpdateMS: 100, // 100ms audio level update interval
FrontendFFTSize: 256, // 256 FFT size for audio analysis
FrontendAudioLevelMax: 100, // 100 maximum audio level
FrontendReconnectIntervalMS: 3000, // 3000ms reconnect interval
FrontendSubscriptionDelayMS: 100, // 100ms subscription delay
FrontendDebugIntervalMS: 5000, // 5000ms debug interval
// Process Monitor Constants
ProcessMonitorDefaultMemoryGB: 4, // 4GB default memory for fallback
ProcessMonitorKBToBytes: 1024, // 1024 conversion factor
ProcessMonitorDefaultClockHz: 250.0, // 250.0 Hz default for ARM systems
ProcessMonitorFallbackClockHz: 1000.0, // 1000.0 Hz fallback clock
ProcessMonitorTraditionalHz: 100.0, // 100.0 Hz traditional clock
// Batch Processing Constants
BatchProcessorFramesPerBatch: 4, // 4 frames per batch
BatchProcessorTimeout: 5 * time.Millisecond, // 5ms timeout
// Output Streaming Constants
OutputStreamingFrameIntervalMS: 20, // 20ms frame interval (50 FPS)
// IPC Constants
IPCInitialBufferFrames: 500, // 500 frames for initial buffer
// Event Constants
EventTimeoutSeconds: 2, // 2 seconds for event timeout
EventTimeFormatString: "2006-01-02T15:04:05.000Z", // "2006-01-02T15:04:05.000Z" time format
EventSubscriptionDelayMS: 100, // 100ms subscription delay
// Input Processing Constants
InputProcessingTimeoutMS: 10, // 10ms processing timeout threshold
// Adaptive Buffer Constants
AdaptiveBufferCPUMultiplier: 100, // 100 multiplier for CPU percentage
AdaptiveBufferMemoryMultiplier: 100, // 100 multiplier for memory percentage
} }
} }

View File

@ -226,7 +226,7 @@ func convertAudioMetricsToEventDataWithLatencyMs(metrics AudioMetrics) AudioMetr
FramesReceived: metrics.FramesReceived, FramesReceived: metrics.FramesReceived,
FramesDropped: metrics.FramesDropped, FramesDropped: metrics.FramesDropped,
BytesProcessed: metrics.BytesProcessed, BytesProcessed: metrics.BytesProcessed,
LastFrameTime: metrics.LastFrameTime.Format("2006-01-02T15:04:05.000Z"), LastFrameTime: metrics.LastFrameTime.Format(GetConfig().EventTimeFormatString),
ConnectionDrops: metrics.ConnectionDrops, ConnectionDrops: metrics.ConnectionDrops,
AverageLatency: fmt.Sprintf("%.1fms", float64(metrics.AverageLatency.Nanoseconds())/1e6), AverageLatency: fmt.Sprintf("%.1fms", float64(metrics.AverageLatency.Nanoseconds())/1e6),
} }
@ -238,7 +238,7 @@ func convertAudioInputMetricsToEventDataWithLatencyMs(metrics AudioInputMetrics)
FramesSent: metrics.FramesSent, FramesSent: metrics.FramesSent,
FramesDropped: metrics.FramesDropped, FramesDropped: metrics.FramesDropped,
BytesProcessed: metrics.BytesProcessed, BytesProcessed: metrics.BytesProcessed,
LastFrameTime: metrics.LastFrameTime.Format("2006-01-02T15:04:05.000Z"), LastFrameTime: metrics.LastFrameTime.Format(GetConfig().EventTimeFormatString),
ConnectionDrops: metrics.ConnectionDrops, ConnectionDrops: metrics.ConnectionDrops,
AverageLatency: fmt.Sprintf("%.1fms", float64(metrics.AverageLatency.Nanoseconds())/1e6), AverageLatency: fmt.Sprintf("%.1fms", float64(metrics.AverageLatency.Nanoseconds())/1e6),
} }
@ -463,7 +463,7 @@ func (aeb *AudioEventBroadcaster) sendToSubscriber(subscriber *AudioEventSubscri
return false return false
} }
ctx, cancel := context.WithTimeout(subscriber.ctx, 2*time.Second) ctx, cancel := context.WithTimeout(subscriber.ctx, time.Duration(GetConfig().EventTimeoutSeconds)*time.Second)
defer cancel() defer cancel()
err := wsjson.Write(ctx, subscriber.conn, event) err := wsjson.Write(ctx, subscriber.conn, event)

View File

@ -80,7 +80,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 > 10*time.Millisecond { if processingTime > time.Duration(GetConfig().InputProcessingTimeoutMS)*time.Millisecond {
aim.logger.Warn(). aim.logger.Warn().
Dur("latency_ms", processingTime). Dur("latency_ms", processingTime).
Msg("High audio processing latency detected") Msg("High audio processing latency detected")
@ -116,7 +116,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 > 10*time.Millisecond { if processingTime > time.Duration(GetConfig().InputProcessingTimeoutMS)*time.Millisecond {
aim.logger.Warn(). aim.logger.Warn().
Dur("latency_ms", processingTime). Dur("latency_ms", processingTime).
Msg("High audio processing latency detected") Msg("High audio processing latency detected")

View File

@ -122,7 +122,7 @@ func (s *OutputStreamer) streamLoop() {
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
// Adaptive timing for frame reading // Adaptive timing for frame reading
frameInterval := time.Duration(20) * time.Millisecond // 50 FPS base rate frameInterval := time.Duration(GetConfig().OutputStreamingFrameIntervalMS) * time.Millisecond // 50 FPS base rate
ticker := time.NewTicker(frameInterval) ticker := time.NewTicker(frameInterval)
defer ticker.Stop() defer ticker.Stop()

View File

@ -316,7 +316,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 := 1000000000.0 / float64(period) hz := GetConfig().CGONanosecondsPerSecond / float64(period)
if hz >= minValidClockTicks && hz <= maxValidClockTicks { if hz >= minValidClockTicks && hz <= maxValidClockTicks {
pm.clockTicks = hz pm.clockTicks = hz
return return
@ -344,7 +344,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) * 1024 * 1024 * 1024 pm.totalMemory = int64(defaultMemoryGB) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes)
return return
} }
defer file.Close() defer file.Close()
@ -356,14 +356,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 * 1024 pm.totalMemory = kb * int64(GetConfig().ProcessMonitorKBToBytes)
return return
} }
} }
break break
} }
} }
pm.totalMemory = int64(defaultMemoryGB) * 1024 * 1024 * 1024 // Fallback pm.totalMemory = int64(defaultMemoryGB) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) * int64(GetConfig().ProcessMonitorKBToBytes) // Fallback
}) })
return pm.totalMemory return pm.totalMemory
} }

View File

@ -20,9 +20,9 @@ type ZeroCopyAudioFrame struct {
// ZeroCopyFramePool manages reusable zero-copy audio frames // ZeroCopyFramePool manages reusable zero-copy audio frames
type ZeroCopyFramePool struct { type ZeroCopyFramePool struct {
// Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment)
counter int64 // Frame counter (atomic) counter int64 // Frame counter (atomic)
hitCount int64 // Pool hit counter (atomic) hitCount int64 // Pool hit counter (atomic)
missCount int64 // Pool miss counter (atomic) missCount int64 // Pool miss counter (atomic)
allocationCount int64 // Total allocations counter (atomic) allocationCount int64 // Total allocations counter (atomic)
// Other fields // Other fields