refactor(audio): centralize configuration constants in audio module

Replace hardcoded values with centralized config constants for better maintainability and flexibility. This includes sleep durations, buffer sizes, thresholds, and various audio processing parameters.

The changes affect multiple components including buffer pools, latency monitoring, IPC, and audio processing. This refactoring makes it easier to adjust parameters without modifying individual files.

Key changes:
- Replace hardcoded sleep durations with config values
- Centralize buffer sizes and pool configurations
- Move thresholds and limits to config
- Update audio quality presets to use config values
This commit is contained in:
Alex P 2025-08-25 18:08:12 +00:00
parent 7ec583ed6a
commit 35a666ed31
24 changed files with 1372 additions and 245 deletions

View File

@ -42,20 +42,20 @@ func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig {
DefaultBufferSize: 6, // Default 6 frames (increased for better stability) DefaultBufferSize: 6, // Default 6 frames (increased for better stability)
// CPU thresholds optimized for single-core ARM Cortex A7 under load // CPU thresholds optimized for single-core ARM Cortex A7 under load
LowCPUThreshold: 20.0, // Below 20% CPU LowCPUThreshold: GetConfig().LowCPUThreshold * 100, // Below 20% CPU
HighCPUThreshold: 60.0, // Above 60% CPU (lowered to be more responsive) HighCPUThreshold: GetConfig().HighCPUThreshold * 100, // Above 60% CPU (lowered to be more responsive)
// Memory thresholds for 256MB total RAM // Memory thresholds for 256MB total RAM
LowMemoryThreshold: 35.0, // Below 35% memory usage LowMemoryThreshold: GetConfig().LowMemoryThreshold * 100, // Below 35% memory usage
HighMemoryThreshold: 75.0, // Above 75% memory usage (lowered for earlier response) HighMemoryThreshold: GetConfig().HighMemoryThreshold * 100, // Above 75% memory usage (lowered for earlier response)
// Latency targets // Latency targets
TargetLatency: 20 * time.Millisecond, // Target 20ms latency TargetLatency: GetConfig().TargetLatency, // Target 20ms latency
MaxLatency: 50 * time.Millisecond, // Max acceptable 50ms MaxLatency: GetConfig().MaxLatencyTarget, // Max acceptable latency
// Adaptation settings // Adaptation settings
AdaptationInterval: 500 * time.Millisecond, // Check every 500ms AdaptationInterval: GetConfig().BufferUpdateInterval, // Check every 500ms
SmoothingFactor: 0.3, // Moderate responsiveness SmoothingFactor: GetConfig().SmoothingFactor, // Moderate responsiveness
} }
} }
@ -133,7 +133,7 @@ func (abm *AdaptiveBufferManager) UpdateLatency(latency time.Duration) {
atomic.StoreInt64(&abm.averageLatency, newLatency) atomic.StoreInt64(&abm.averageLatency, newLatency)
} else { } else {
// Exponential moving average: 70% historical, 30% current // Exponential moving average: 70% historical, 30% current
newAvg := int64(float64(currentAvg)*0.7 + float64(newLatency)*0.3) newAvg := int64(float64(currentAvg)*GetConfig().HistoricalWeight + float64(newLatency)*GetConfig().CurrentWeight)
atomic.StoreInt64(&abm.averageLatency, newAvg) atomic.StoreInt64(&abm.averageLatency, newAvg)
} }
} }
@ -195,7 +195,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 := 0.5*cpuFactor + 0.3*memoryFactor + 0.2*latencyFactor combinedFactor := GetConfig().CPUMemoryWeight*cpuFactor + GetConfig().MemoryWeight*memoryFactor + GetConfig().LatencyWeight*latencyFactor
// Apply adaptation with smoothing // Apply adaptation with smoothing
currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize)) currentInput := float64(atomic.LoadInt64(&abm.currentInputBufferSize))
@ -306,8 +306,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)) / 100, "system_cpu_percent": float64(atomic.LoadInt64(&abm.systemCPUPercent)) / GetConfig().PercentageMultiplier,
"system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / 100, "system_memory_percent": float64(atomic.LoadInt64(&abm.systemMemoryPercent)) / GetConfig().PercentageMultiplier,
"adaptation_count": atomic.LoadInt64(&abm.adaptationCount), "adaptation_count": atomic.LoadInt64(&abm.adaptationCount),
"last_adaptation": lastAdaptation, "last_adaptation": lastAdaptation,
} }

View File

@ -42,9 +42,9 @@ type OptimizerConfig struct {
func DefaultOptimizerConfig() OptimizerConfig { func DefaultOptimizerConfig() OptimizerConfig {
return OptimizerConfig{ return OptimizerConfig{
MaxOptimizationLevel: 8, MaxOptimizationLevel: 8,
CooldownPeriod: 30 * time.Second, CooldownPeriod: GetConfig().CooldownPeriod,
Aggressiveness: 0.7, Aggressiveness: GetConfig().OptimizerAggressiveness,
RollbackThreshold: 300 * time.Millisecond, RollbackThreshold: GetConfig().RollbackThreshold,
StabilityPeriod: 10 * time.Second, StabilityPeriod: 10 * time.Second,
} }
} }
@ -109,7 +109,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(50*time.Millisecond) // 50ms target latencyRatio := float64(metrics.Current) / float64(GetConfig().LatencyTarget) // 50ms target
// Adjust based on trend // Adjust based on trend
switch metrics.Trend { switch metrics.Trend {
@ -125,7 +125,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 * 2) // Scale to 0-10 range targetLevel := int64(latencyRatio * GetConfig().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

@ -47,17 +47,17 @@ type AudioMetrics struct {
var ( var (
currentConfig = AudioConfig{ currentConfig = AudioConfig{
Quality: AudioQualityMedium, Quality: AudioQualityMedium,
Bitrate: 64, Bitrate: GetConfig().AudioQualityMediumOutputBitrate,
SampleRate: GetConfig().SampleRate, SampleRate: GetConfig().SampleRate,
Channels: GetConfig().Channels, Channels: GetConfig().Channels,
FrameSize: 20 * time.Millisecond, FrameSize: GetConfig().AudioQualityMediumFrameSize,
} }
currentMicrophoneConfig = AudioConfig{ currentMicrophoneConfig = AudioConfig{
Quality: AudioQualityMedium, Quality: AudioQualityMedium,
Bitrate: 32, Bitrate: GetConfig().AudioQualityMediumInputBitrate,
SampleRate: GetConfig().SampleRate, SampleRate: GetConfig().SampleRate,
Channels: 1, Channels: 1,
FrameSize: 20 * time.Millisecond, FrameSize: GetConfig().AudioQualityMediumFrameSize,
} }
metrics AudioMetrics metrics AudioMetrics
) )
@ -69,24 +69,24 @@ var qualityPresets = map[AudioQuality]struct {
frameSize time.Duration frameSize time.Duration
}{ }{
AudioQualityLow: { AudioQualityLow: {
outputBitrate: 32, inputBitrate: 16, outputBitrate: GetConfig().AudioQualityLowOutputBitrate, inputBitrate: GetConfig().AudioQualityLowInputBitrate,
sampleRate: 22050, channels: 1, sampleRate: GetConfig().AudioQualityLowSampleRate, channels: GetConfig().AudioQualityLowChannels,
frameSize: 40 * time.Millisecond, frameSize: GetConfig().AudioQualityLowFrameSize,
}, },
AudioQualityMedium: { AudioQualityMedium: {
outputBitrate: 64, inputBitrate: 32, outputBitrate: GetConfig().AudioQualityMediumOutputBitrate, inputBitrate: GetConfig().AudioQualityMediumInputBitrate,
sampleRate: 44100, channels: 2, sampleRate: GetConfig().AudioQualityMediumSampleRate, channels: GetConfig().AudioQualityMediumChannels,
frameSize: 20 * time.Millisecond, frameSize: GetConfig().AudioQualityMediumFrameSize,
}, },
AudioQualityHigh: { AudioQualityHigh: {
outputBitrate: 128, inputBitrate: 64, outputBitrate: GetConfig().AudioQualityHighOutputBitrate, inputBitrate: GetConfig().AudioQualityHighInputBitrate,
sampleRate: GetConfig().SampleRate, channels: GetConfig().Channels, sampleRate: GetConfig().SampleRate, channels: GetConfig().AudioQualityHighChannels,
frameSize: 20 * time.Millisecond, frameSize: GetConfig().AudioQualityHighFrameSize,
}, },
AudioQualityUltra: { AudioQualityUltra: {
outputBitrate: 192, inputBitrate: 96, outputBitrate: GetConfig().AudioQualityUltraOutputBitrate, inputBitrate: GetConfig().AudioQualityUltraInputBitrate,
sampleRate: GetConfig().SampleRate, channels: GetConfig().Channels, sampleRate: GetConfig().SampleRate, channels: GetConfig().AudioQualityUltraChannels,
frameSize: 10 * time.Millisecond, frameSize: GetConfig().AudioQualityUltraFrameSize,
}, },
} }
@ -114,7 +114,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 16000 return GetConfig().AudioQualityMicLowSampleRate
} }
return preset.sampleRate return preset.sampleRate
}(), }(),

View File

@ -72,7 +72,7 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu
readQueue: make(chan batchReadRequest, batchSize*2), readQueue: make(chan batchReadRequest, batchSize*2),
readBufPool: &sync.Pool{ readBufPool: &sync.Pool{
New: func() interface{} { New: func() interface{} {
return make([]byte, 1500) // Max audio frame size return make([]byte, GetConfig().AudioFramePoolSize) // Max audio frame size
}, },
}, },
} }
@ -110,7 +110,7 @@ func (bap *BatchAudioProcessor) Stop() {
bap.cancel() bap.cancel()
// Wait for processing to complete // Wait for processing to complete
time.Sleep(bap.batchDuration + 10*time.Millisecond) time.Sleep(bap.batchDuration + GetConfig().BatchProcessingDelay)
bap.logger.Info().Msg("batch audio processor stopped") bap.logger.Info().Msg("batch audio processor stopped")
} }
@ -134,7 +134,7 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
select { select {
case bap.readQueue <- request: case bap.readQueue <- request:
// Successfully queued // Successfully queued
case <-time.After(5 * time.Millisecond): case <-time.After(GetConfig().ShortTimeout):
// Queue is full or blocked, fallback to single operation // Queue is full or blocked, fallback to single operation
atomic.AddInt64(&bap.stats.SingleReads, 1) atomic.AddInt64(&bap.stats.SingleReads, 1)
atomic.AddInt64(&bap.stats.SingleFrames, 1) atomic.AddInt64(&bap.stats.SingleFrames, 1)
@ -145,7 +145,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(50 * time.Millisecond): case <-time.After(GetConfig().MediumTimeout):
// Timeout, fallback to single operation // Timeout, fallback to single operation
atomic.AddInt64(&bap.stats.SingleReads, 1) atomic.AddInt64(&bap.stats.SingleReads, 1)
atomic.AddInt64(&bap.stats.SingleFrames, 1) atomic.AddInt64(&bap.stats.SingleFrames, 1)

View File

@ -23,7 +23,7 @@ type AudioBufferPool struct {
func NewAudioBufferPool(bufferSize int) *AudioBufferPool { func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
// Pre-allocate 20% of max pool size for immediate availability // Pre-allocate 20% of max pool size for immediate availability
preallocSize := 20 preallocSize := GetConfig().PreallocPercentage
preallocated := make([]*[]byte, 0, preallocSize) preallocated := make([]*[]byte, 0, preallocSize)
// Pre-allocate buffers to reduce initial allocation overhead // Pre-allocate buffers to reduce initial allocation overhead
@ -34,7 +34,7 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
return &AudioBufferPool{ return &AudioBufferPool{
bufferSize: bufferSize, bufferSize: bufferSize,
maxPoolSize: 100, // Limit pool size to prevent excessive memory usage maxPoolSize: GetConfig().MaxPoolSize, // Limit pool size to prevent excessive memory usage
preallocated: preallocated, preallocated: preallocated,
preallocSize: preallocSize, preallocSize: preallocSize,
pool: sync.Pool{ pool: sync.Pool{
@ -111,8 +111,8 @@ func (p *AudioBufferPool) Put(buf []byte) {
} }
var ( var (
audioFramePool = NewAudioBufferPool(1500) audioFramePool = NewAudioBufferPool(GetConfig().AudioFramePoolSize)
audioControlPool = NewAudioBufferPool(64) audioControlPool = NewAudioBufferPool(GetConfig().OutputHeaderSize)
) )
func GetAudioFrameBuffer() []byte { func GetAudioFrameBuffer() []byte {
@ -144,7 +144,7 @@ func (p *AudioBufferPool) GetPoolStats() AudioBufferPoolDetailedStats {
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * 100 hitRate = float64(hitCount) / float64(totalRequests) * GetConfig().PercentageMultiplier
} }
return AudioBufferPoolDetailedStats{ return AudioBufferPoolDetailedStats{

View File

@ -22,18 +22,37 @@ static snd_pcm_t *pcm_handle = NULL;
static snd_pcm_t *pcm_playback_handle = NULL; static snd_pcm_t *pcm_playback_handle = NULL;
static OpusEncoder *encoder = NULL; static OpusEncoder *encoder = NULL;
static OpusDecoder *decoder = NULL; static OpusDecoder *decoder = NULL;
// Optimized Opus encoder settings for ARM Cortex-A7 // Opus encoder settings - initialized from Go configuration
static int opus_bitrate = 96000; // Increased for better quality static int opus_bitrate = 96000; // Will be set from GetConfig().CGOOpusBitrate
static int opus_complexity = 3; // Reduced for ARM performance static int opus_complexity = 3; // Will be set from GetConfig().CGOOpusComplexity
static int opus_vbr = 1; // Variable bitrate enabled static int opus_vbr = 1; // Will be set from GetConfig().CGOOpusVBR
static int opus_vbr_constraint = 1; // Constrained VBR for consistent latency static int opus_vbr_constraint = 1; // Will be set from GetConfig().CGOOpusVBRConstraint
static int opus_signal_type = OPUS_SIGNAL_MUSIC; // Optimized for general audio static int opus_signal_type = 3; // Will be set from GetConfig().CGOOpusSignalType
static int opus_bandwidth = OPUS_BANDWIDTH_FULLBAND; // Full bandwidth static int opus_bandwidth = 1105; // Will be set from GetConfig().CGOOpusBandwidth
static int opus_dtx = 0; // Disable DTX for real-time audio static int opus_dtx = 0; // Will be set from GetConfig().CGOOpusDTX
static int sample_rate = 48000; static int sample_rate = 48000; // Will be set from GetConfig().CGOSampleRate
static int channels = 2; static int channels = 2; // Will be set from GetConfig().CGOChannels
static int frame_size = 960; // 20ms for 48kHz static int frame_size = 960; // Will be set from GetConfig().CGOFrameSize
static int max_packet_size = 1500; static int max_packet_size = 1500; // Will be set from GetConfig().CGOMaxPacketSize
static int sleep_microseconds = 50000; // Will be set from GetConfig().CGOSleepMicroseconds
// Function to update constants from Go configuration
void update_audio_constants(int bitrate, int complexity, int vbr, int vbr_constraint,
int signal_type, int bandwidth, int dtx, int sr, int ch,
int fs, int max_pkt, int sleep_us) {
opus_bitrate = bitrate;
opus_complexity = complexity;
opus_vbr = vbr;
opus_vbr_constraint = vbr_constraint;
opus_signal_type = signal_type;
opus_bandwidth = bandwidth;
opus_dtx = dtx;
sample_rate = sr;
channels = ch;
frame_size = fs;
max_packet_size = max_pkt;
sleep_microseconds = sleep_us;
}
// State tracking to prevent race conditions during rapid start/stop // State tracking to prevent race conditions during rapid start/stop
static volatile int capture_initializing = 0; static volatile int capture_initializing = 0;
@ -56,7 +75,7 @@ static int safe_alsa_open(snd_pcm_t **handle, const char *device, snd_pcm_stream
if (err == -EBUSY && attempts > 0) { if (err == -EBUSY && attempts > 0) {
// Device busy, wait and retry // Device busy, wait and retry
usleep(50000); // 50ms usleep(sleep_microseconds); // 50ms
continue; continue;
} }
break; break;
@ -415,8 +434,25 @@ var (
) )
func cgoAudioInit() error { func cgoAudioInit() error {
ret := C.jetkvm_audio_init() // Update C constants from Go configuration
if ret != 0 { config := GetConfig()
C.update_audio_constants(
C.int(config.CGOOpusBitrate),
C.int(config.CGOOpusComplexity),
C.int(config.CGOOpusVBR),
C.int(config.CGOOpusVBRConstraint),
C.int(config.CGOOpusSignalType),
C.int(config.CGOOpusBandwidth),
C.int(config.CGOOpusDTX),
C.int(config.CGOSampleRate),
C.int(config.CGOChannels),
C.int(config.CGOFrameSize),
C.int(config.CGOMaxPacketSize),
C.int(config.CGOSleepMicroseconds),
)
result := C.jetkvm_audio_init()
if result != 0 {
return errAudioInitFailed return errAudioInitFailed
} }
return nil return nil
@ -427,7 +463,7 @@ func cgoAudioClose() {
} }
func cgoAudioReadEncode(buf []byte) (int, error) { func cgoAudioReadEncode(buf []byte) (int, error) {
if len(buf) < 1276 { if len(buf) < GetConfig().MinReadEncodeBuffer {
return 0, errBufferTooSmall return 0, errBufferTooSmall
} }
@ -461,7 +497,7 @@ func cgoAudioDecodeWrite(buf []byte) (int, error) {
if buf == nil { if buf == nil {
return 0, errNilBuffer return 0, errNilBuffer
} }
if len(buf) > 4096 { if len(buf) > GetConfig().MaxDecodeWriteBuffer {
return 0, errBufferTooLarge return 0, errBufferTooLarge
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,14 +16,19 @@ import (
"github.com/jetkvm/kvm/internal/logging" "github.com/jetkvm/kvm/internal/logging"
) )
const ( var (
inputMagicNumber uint32 = 0x4A4B4D49 // "JKMI" (JetKVM Microphone Input) inputMagicNumber uint32 = GetConfig().InputMagicNumber // "JKMI" (JetKVM Microphone Input)
inputSocketName = "audio_input.sock" inputSocketName = "audio_input.sock"
maxFrameSize = 4096 // Maximum Opus frame size
writeTimeout = 15 * time.Millisecond // Non-blocking write timeout (increased for high load) writeTimeout = 15 * time.Millisecond // Non-blocking write timeout (increased for high load)
maxDroppedFrames = 100 // Maximum consecutive dropped frames before reconnect )
headerSize = 17 // Fixed header size: 4+1+4+8 bytes
messagePoolSize = 256 // Pre-allocated message pool size const (
headerSize = 17 // Fixed header size: 4+1+4+8 bytes
)
var (
maxFrameSize = GetConfig().MaxFrameSize // Maximum Opus frame size
messagePoolSize = GetConfig().MessagePoolSize // Pre-allocated message pool size
) )
// InputMessageType represents the type of IPC message // InputMessageType represents the type of IPC message
@ -79,9 +84,9 @@ var messagePoolInitOnce sync.Once
func initializeMessagePool() { func initializeMessagePool() {
messagePoolInitOnce.Do(func() { messagePoolInitOnce.Do(func() {
// Pre-allocate 30% of pool size for immediate availability // Pre-allocate 30% of pool size for immediate availability
preallocSize := messagePoolSize * 30 / 100 preallocSize := messagePoolSize * GetConfig().InputPreallocPercentage / 100
globalMessagePool.preallocSize = preallocSize globalMessagePool.preallocSize = preallocSize
globalMessagePool.maxPoolSize = messagePoolSize * 2 // Allow growth up to 2x globalMessagePool.maxPoolSize = messagePoolSize * GetConfig().PoolGrowthMultiplier // Allow growth up to 2x
globalMessagePool.preallocated = make([]*OptimizedIPCMessage, 0, preallocSize) globalMessagePool.preallocated = make([]*OptimizedIPCMessage, 0, preallocSize)
// Pre-allocate messages to reduce initial allocation overhead // Pre-allocate messages to reduce initial allocation overhead
@ -315,7 +320,7 @@ func (ais *AudioInputServer) handleConnection(conn net.Conn) {
if ais.conn == nil { if ais.conn == nil {
return return
} }
time.Sleep(100 * time.Millisecond) time.Sleep(GetConfig().DefaultSleepDuration)
} }
} }
} }
@ -345,7 +350,7 @@ func (ais *AudioInputServer) readMessage(conn net.Conn) (*InputIPCMessage, error
} }
// Validate message length // Validate message length
if msg.Length > maxFrameSize { if msg.Length > uint32(maxFrameSize) {
return nil, fmt.Errorf("message too large: %d bytes", msg.Length) return nil, fmt.Errorf("message too large: %d bytes", msg.Length)
} }
@ -711,7 +716,7 @@ func (aic *AudioInputClient) GetDropRate() float64 {
if total == 0 { if total == 0 {
return 0.0 return 0.0
} }
return float64(dropped) / float64(total) * 100.0 return float64(dropped) / float64(total) * GetConfig().PercentageMultiplier
} }
// ResetStats resets frame statistics // ResetStats resets frame statistics
@ -820,11 +825,11 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
}() }()
defer ais.wg.Done() defer ais.wg.Done()
ticker := time.NewTicker(100 * time.Millisecond) ticker := time.NewTicker(GetConfig().DefaultTickerInterval)
defer ticker.Stop() defer ticker.Stop()
// Buffer size update ticker (less frequent) // Buffer size update ticker (less frequent)
bufferUpdateTicker := time.NewTicker(500 * time.Millisecond) bufferUpdateTicker := time.NewTicker(GetConfig().BufferUpdateInterval)
defer bufferUpdateTicker.Stop() defer bufferUpdateTicker.Stop()
for { for {
@ -917,7 +922,7 @@ func (mp *MessagePool) GetMessagePoolStats() MessagePoolStats {
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * 100 hitRate = float64(hitCount) / float64(totalRequests) * GetConfig().PercentageMultiplier
} }
// Calculate channel pool size // Calculate channel pool size

View File

@ -41,13 +41,13 @@ func (aim *AudioInputIPCManager) Start() error {
} }
config := InputIPCConfig{ config := InputIPCConfig{
SampleRate: 48000, SampleRate: GetConfig().InputIPCSampleRate,
Channels: 2, Channels: GetConfig().InputIPCChannels,
FrameSize: 960, FrameSize: GetConfig().InputIPCFrameSize,
} }
// Wait for subprocess readiness // Wait for subprocess readiness
time.Sleep(200 * time.Millisecond) time.Sleep(GetConfig().LongSleepDuration)
err = aim.supervisor.SendConfig(config) err = aim.supervisor.SendConfig(config)
if err != nil { if err != nil {

View File

@ -64,7 +64,7 @@ func RunAudioInputServer() error {
server.Stop() server.Stop()
// Give some time for cleanup // Give some time for cleanup
time.Sleep(100 * time.Millisecond) time.Sleep(GetConfig().DefaultSleepDuration)
logger.Info().Msg("Audio input server subprocess stopped") logger.Info().Msg("Audio input server subprocess stopped")
return nil return nil

View File

@ -128,7 +128,7 @@ func (ais *AudioInputSupervisor) Stop() {
select { select {
case <-done: case <-done:
ais.logger.Info().Msg("Audio input server subprocess stopped gracefully") ais.logger.Info().Msg("Audio input server subprocess stopped gracefully")
case <-time.After(5 * time.Second): case <-time.After(GetConfig().InputSupervisorTimeout):
// Force kill if graceful shutdown failed // Force kill if graceful shutdown failed
ais.logger.Warn().Msg("Audio input server subprocess did not stop gracefully, force killing") ais.logger.Warn().Msg("Audio input server subprocess did not stop gracefully, force killing")
err := ais.cmd.Process.Kill() err := ais.cmd.Process.Kill()
@ -220,7 +220,7 @@ func (ais *AudioInputSupervisor) monitorSubprocess() {
// 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 (reduced from 500ms) // Wait briefly for the server to start (reduced from 500ms)
time.Sleep(100 * time.Millisecond) time.Sleep(GetConfig().DefaultSleepDuration)
err := ais.client.Connect() err := ais.client.Connect()
if err != nil { if err != nil {

View File

@ -16,16 +16,14 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
const ( var (
outputMagicNumber uint32 = 0x4A4B4F55 // "JKOU" (JetKVM Output) outputMagicNumber uint32 = GetConfig().OutputMagicNumber // "JKOU" (JetKVM Output)
outputSocketName = "audio_output.sock" outputSocketName = "audio_output.sock"
outputMaxFrameSize = 4096 // Maximum Opus frame size
outputWriteTimeout = 10 * time.Millisecond // Non-blocking write timeout (increased for high load)
outputMaxDroppedFrames = 50 // Maximum consecutive dropped frames
outputHeaderSize = 17 // Fixed header size: 4+1+4+8 bytes
outputMessagePoolSize = 128 // Pre-allocated message pool size
) )
// Output IPC constants are now centralized in config_constants.go
// outputMaxFrameSize, outputWriteTimeout, outputMaxDroppedFrames, outputHeaderSize, outputMessagePoolSize
// OutputMessageType represents the type of IPC message // OutputMessageType represents the type of IPC message
type OutputMessageType uint8 type OutputMessageType uint8
@ -48,8 +46,8 @@ type OutputIPCMessage struct {
// OutputOptimizedMessage represents a pre-allocated message for zero-allocation operations // OutputOptimizedMessage represents a pre-allocated message for zero-allocation operations
type OutputOptimizedMessage struct { type OutputOptimizedMessage struct {
header [outputHeaderSize]byte // Pre-allocated header buffer header [17]byte // Pre-allocated header buffer (using constant value since array size must be compile-time constant)
data []byte // Reusable data buffer data []byte // Reusable data buffer
} }
// OutputMessagePool manages pre-allocated messages for zero-allocation IPC // OutputMessagePool manages pre-allocated messages for zero-allocation IPC
@ -66,7 +64,7 @@ func NewOutputMessagePool(size int) *OutputMessagePool {
// Pre-allocate messages // Pre-allocate messages
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
msg := &OutputOptimizedMessage{ msg := &OutputOptimizedMessage{
data: make([]byte, outputMaxFrameSize), data: make([]byte, GetConfig().OutputMaxFrameSize),
} }
pool.pool <- msg pool.pool <- msg
} }
@ -82,7 +80,7 @@ func (p *OutputMessagePool) Get() *OutputOptimizedMessage {
default: default:
// Pool exhausted, create new message // Pool exhausted, create new message
return &OutputOptimizedMessage{ return &OutputOptimizedMessage{
data: make([]byte, outputMaxFrameSize), data: make([]byte, GetConfig().OutputMaxFrameSize),
} }
} }
} }
@ -98,7 +96,7 @@ func (p *OutputMessagePool) Put(msg *OutputOptimizedMessage) {
} }
// Global message pool for output IPC // Global message pool for output IPC
var globalOutputMessagePool = NewOutputMessagePool(outputMessagePoolSize) var globalOutputMessagePool = NewOutputMessagePool(GetConfig().OutputMessagePoolSize)
type AudioServer struct { type AudioServer struct {
// Atomic fields must be first for proper alignment on ARM // Atomic fields must be first for proper alignment on ARM
@ -135,7 +133,7 @@ func NewAudioServer() (*AudioServer, error) {
} }
// Initialize with adaptive buffer size (start with 500 frames) // Initialize with adaptive buffer size (start with 500 frames)
initialBufferSize := int64(500) initialBufferSize := int64(GetConfig().InitialBufferFrames)
// Initialize latency monitoring // Initialize latency monitoring
latencyConfig := DefaultLatencyConfig() latencyConfig := DefaultLatencyConfig()
@ -284,8 +282,8 @@ func (s *AudioServer) Close() error {
} }
func (s *AudioServer) SendFrame(frame []byte) error { func (s *AudioServer) SendFrame(frame []byte) error {
if len(frame) > outputMaxFrameSize { if len(frame) > GetConfig().OutputMaxFrameSize {
return fmt.Errorf("frame size %d exceeds maximum %d", len(frame), outputMaxFrameSize) return fmt.Errorf("frame size %d exceeds maximum %d", len(frame), GetConfig().OutputMaxFrameSize)
} }
start := time.Now() start := time.Now()
@ -340,7 +338,7 @@ func (s *AudioServer) sendFrameToClient(frame []byte) error {
binary.LittleEndian.PutUint64(optMsg.header[9:17], uint64(start.UnixNano())) binary.LittleEndian.PutUint64(optMsg.header[9:17], uint64(start.UnixNano()))
// Use non-blocking write with timeout // Use non-blocking write with timeout
ctx, cancel := context.WithTimeout(context.Background(), outputWriteTimeout) ctx, cancel := context.WithTimeout(context.Background(), GetConfig().OutputWriteTimeout)
defer cancel() defer cancel()
// Create a channel to signal write completion // Create a channel to signal write completion
@ -492,8 +490,8 @@ func (c *AudioClient) ReceiveFrame() ([]byte, error) {
} }
size := binary.LittleEndian.Uint32(optMsg.header[5:9]) size := binary.LittleEndian.Uint32(optMsg.header[5:9])
if size > outputMaxFrameSize { if int(size) > GetConfig().OutputMaxFrameSize {
return nil, fmt.Errorf("frame size %d exceeds maximum %d", size, outputMaxFrameSize) return nil, fmt.Errorf("frame size %d exceeds maximum %d", size, GetConfig().OutputMaxFrameSize)
} }
// Read frame data // Read frame data

View File

@ -83,10 +83,10 @@ const (
func DefaultLatencyConfig() LatencyConfig { func DefaultLatencyConfig() LatencyConfig {
return LatencyConfig{ return LatencyConfig{
TargetLatency: 50 * time.Millisecond, TargetLatency: 50 * time.Millisecond,
MaxLatency: 200 * time.Millisecond, MaxLatency: GetConfig().MaxLatencyThreshold,
OptimizationInterval: 5 * time.Second, OptimizationInterval: 5 * time.Second,
HistorySize: 100, HistorySize: GetConfig().LatencyHistorySize,
JitterThreshold: 20 * time.Millisecond, JitterThreshold: GetConfig().JitterThreshold,
AdaptiveThreshold: 0.8, // Trigger optimization when 80% above target AdaptiveThreshold: 0.8, // Trigger optimization when 80% above target
} }
} }

View File

@ -171,8 +171,8 @@ func LogMemoryMetrics() {
metrics := CollectMemoryMetrics() metrics := CollectMemoryMetrics()
logger.Info(). logger.Info().
Uint64("heap_alloc_mb", metrics.RuntimeStats.HeapAlloc/1024/1024). Uint64("heap_alloc_mb", metrics.RuntimeStats.HeapAlloc/uint64(GetConfig().BytesToMBDivisor)).
Uint64("heap_sys_mb", metrics.RuntimeStats.HeapSys/1024/1024). Uint64("heap_sys_mb", metrics.RuntimeStats.HeapSys/uint64(GetConfig().BytesToMBDivisor)).
Uint64("heap_objects", metrics.RuntimeStats.HeapObjects). Uint64("heap_objects", metrics.RuntimeStats.HeapObjects).
Uint32("num_gc", metrics.RuntimeStats.NumGC). Uint32("num_gc", metrics.RuntimeStats.NumGC).
Float64("gc_cpu_fraction", metrics.RuntimeStats.GCCPUFraction). Float64("gc_cpu_fraction", metrics.RuntimeStats.GCCPUFraction).

View File

@ -451,7 +451,7 @@ func GetLastMetricsUpdate() time.Time {
// StartMetricsUpdater starts a goroutine that periodically updates Prometheus metrics // StartMetricsUpdater starts a goroutine that periodically updates Prometheus metrics
func StartMetricsUpdater() { func StartMetricsUpdater() {
go func() { go func() {
ticker := time.NewTicker(5 * time.Second) // Update every 5 seconds ticker := time.NewTicker(GetConfig().StatsUpdateInterval) // Update every 5 seconds
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {

View File

@ -105,7 +105,7 @@ func GetMicrophoneContentionManager() *MicrophoneContentionManager {
} }
if atomic.CompareAndSwapInt32(&micContentionInitialized, 0, 1) { if atomic.CompareAndSwapInt32(&micContentionInitialized, 0, 1) {
manager := NewMicrophoneContentionManager(200 * time.Millisecond) manager := NewMicrophoneContentionManager(GetConfig().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(200 * time.Millisecond) return NewMicrophoneContentionManager(GetConfig().MicContentionTimeout)
} }
func TryMicrophoneOperation() OperationResult { func TryMicrophoneOperation() OperationResult {

View File

@ -64,7 +64,7 @@ func RunAudioOutputServer() error {
StopNonBlockingAudioStreaming() StopNonBlockingAudioStreaming()
// Give some time for cleanup // Give some time for cleanup
time.Sleep(100 * time.Millisecond) time.Sleep(GetConfig().DefaultSleepDuration)
logger.Info().Msg("Audio output server subprocess stopped") logger.Info().Msg("Audio output server subprocess stopped")
return nil return nil

View File

@ -61,9 +61,9 @@ func NewOutputStreamer() (*OutputStreamer, error) {
bufferPool: NewAudioBufferPool(GetMaxAudioFrameSize()), // Use existing buffer pool bufferPool: NewAudioBufferPool(GetMaxAudioFrameSize()), // Use existing buffer pool
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
batchSize: initialBatchSize, // Use adaptive batch size batchSize: initialBatchSize, // Use adaptive batch size
processingChan: make(chan []byte, 500), // Large buffer for smooth processing processingChan: make(chan []byte, GetConfig().ChannelBufferSize), // Large buffer for smooth processing
statsInterval: 5 * time.Second, // Statistics every 5 seconds statsInterval: 5 * time.Second, // Statistics every 5 seconds
lastStatsTime: time.Now().UnixNano(), lastStatsTime: time.Now().UnixNano(),
}, nil }, nil
} }
@ -127,7 +127,7 @@ func (s *OutputStreamer) streamLoop() {
defer ticker.Stop() defer ticker.Stop()
// Batch size update ticker // Batch size update ticker
batchUpdateTicker := time.NewTicker(500 * time.Millisecond) batchUpdateTicker := time.NewTicker(GetConfig().BufferUpdateInterval)
defer batchUpdateTicker.Stop() defer batchUpdateTicker.Stop()
for { for {
@ -233,7 +233,7 @@ func (s *OutputStreamer) reportStatistics() {
processingTime := atomic.LoadInt64(&s.processingTime) processingTime := atomic.LoadInt64(&s.processingTime)
if processed > 0 { if processed > 0 {
dropRate := float64(dropped) / float64(processed+dropped) * 100 dropRate := float64(dropped) / float64(processed+dropped) * GetConfig().PercentageMultiplier
avgProcessingTime := time.Duration(processingTime) avgProcessingTime := time.Duration(processingTime)
getOutputStreamingLogger().Info().Int64("processed", processed).Int64("dropped", dropped).Float64("drop_rate", dropRate).Dur("avg_processing", avgProcessingTime).Msg("Output Audio Stats") getOutputStreamingLogger().Info().Int64("processed", processed).Int64("dropped", dropped).Float64("drop_rate", dropRate).Dur("avg_processing", avgProcessingTime).Msg("Output Audio Stats")
@ -270,7 +270,7 @@ func (s *OutputStreamer) GetDetailedStats() map[string]interface{} {
} }
if processed+dropped > 0 { if processed+dropped > 0 {
stats["drop_rate_percent"] = float64(dropped) / float64(processed+dropped) * 100 stats["drop_rate_percent"] = float64(dropped) / float64(processed+dropped) * GetConfig().PercentageMultiplier
} }
// Add client statistics // Add client statistics
@ -343,7 +343,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(10 * time.Millisecond) time.Sleep(GetConfig().ShortSleepDuration)
} }
} }
}() }()
@ -364,6 +364,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(10 * time.Millisecond) time.Sleep(GetConfig().ShortSleepDuration)
} }
} }

View File

@ -16,23 +16,17 @@ type SchedParam struct {
Priority int32 Priority int32
} }
// Priority levels for audio processing // getPriorityConstants returns priority levels from centralized config
const ( func getPriorityConstants() (audioHigh, audioMedium, audioLow, normal int) {
// SCHED_FIFO priorities (1-99, higher = more priority) config := GetConfig()
AudioHighPriority = 80 // High priority for critical audio processing return config.AudioHighPriority, config.AudioMediumPriority, config.AudioLowPriority, config.NormalPriority
AudioMediumPriority = 60 // Medium priority for regular audio processing }
AudioLowPriority = 40 // Low priority for background audio tasks
// SCHED_NORMAL is the default (priority 0) // getSchedulingPolicies returns scheduling policies from centralized config
NormalPriority = 0 func getSchedulingPolicies() (schedNormal, schedFIFO, schedRR int) {
) config := GetConfig()
return config.SchedNormal, config.SchedFIFO, config.SchedRR
// Scheduling policies }
const (
SCHED_NORMAL = 0
SCHED_FIFO = 1
SCHED_RR = 2
)
// PriorityScheduler manages thread priorities for audio processing // PriorityScheduler manages thread priorities for audio processing
type PriorityScheduler struct { type PriorityScheduler struct {
@ -73,7 +67,8 @@ func (ps *PriorityScheduler) SetThreadPriority(priority int, policy int) error {
if errno != 0 { if errno != 0 {
// If we can't set real-time priority, try nice value instead // If we can't set real-time priority, try nice value instead
if policy != SCHED_NORMAL { schedNormal, _, _ := getSchedulingPolicies()
if policy != schedNormal {
ps.logger.Warn().Int("errno", int(errno)).Msg("Failed to set real-time priority, falling back to nice") ps.logger.Warn().Int("errno", int(errno)).Msg("Failed to set real-time priority, falling back to nice")
return ps.setNicePriority(priority) return ps.setNicePriority(priority)
} }
@ -89,11 +84,11 @@ func (ps *PriorityScheduler) setNicePriority(rtPriority int) error {
// Convert real-time priority to nice value (inverse relationship) // Convert real-time priority to nice value (inverse relationship)
// RT priority 80 -> nice -10, RT priority 40 -> nice 0 // RT priority 80 -> nice -10, RT priority 40 -> nice 0
niceValue := (40 - rtPriority) / 4 niceValue := (40 - rtPriority) / 4
if niceValue < -20 { if niceValue < GetConfig().MinNiceValue {
niceValue = -20 niceValue = GetConfig().MinNiceValue
} }
if niceValue > 19 { if niceValue > GetConfig().MaxNiceValue {
niceValue = 19 niceValue = GetConfig().MaxNiceValue
} }
err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, niceValue) err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, niceValue)
@ -108,22 +103,30 @@ func (ps *PriorityScheduler) setNicePriority(rtPriority int) error {
// SetAudioProcessingPriority sets high priority for audio processing threads // SetAudioProcessingPriority sets high priority for audio processing threads
func (ps *PriorityScheduler) SetAudioProcessingPriority() error { func (ps *PriorityScheduler) SetAudioProcessingPriority() error {
return ps.SetThreadPriority(AudioHighPriority, SCHED_FIFO) audioHigh, _, _, _ := getPriorityConstants()
_, schedFIFO, _ := getSchedulingPolicies()
return ps.SetThreadPriority(audioHigh, schedFIFO)
} }
// SetAudioIOPriority sets medium priority for audio I/O threads // SetAudioIOPriority sets medium priority for audio I/O threads
func (ps *PriorityScheduler) SetAudioIOPriority() error { func (ps *PriorityScheduler) SetAudioIOPriority() error {
return ps.SetThreadPriority(AudioMediumPriority, SCHED_FIFO) _, audioMedium, _, _ := getPriorityConstants()
_, schedFIFO, _ := getSchedulingPolicies()
return ps.SetThreadPriority(audioMedium, schedFIFO)
} }
// SetAudioBackgroundPriority sets low priority for background audio tasks // SetAudioBackgroundPriority sets low priority for background audio tasks
func (ps *PriorityScheduler) SetAudioBackgroundPriority() error { func (ps *PriorityScheduler) SetAudioBackgroundPriority() error {
return ps.SetThreadPriority(AudioLowPriority, SCHED_FIFO) _, _, audioLow, _ := getPriorityConstants()
_, schedFIFO, _ := getSchedulingPolicies()
return ps.SetThreadPriority(audioLow, schedFIFO)
} }
// ResetPriority resets thread to normal scheduling // ResetPriority resets thread to normal scheduling
func (ps *PriorityScheduler) ResetPriority() error { func (ps *PriorityScheduler) ResetPriority() error {
return ps.SetThreadPriority(NormalPriority, SCHED_NORMAL) _, _, _, normal := getPriorityConstants()
schedNormal, _, _ := getSchedulingPolicies()
return ps.SetThreadPriority(normal, schedNormal)
} }
// Disable disables priority scheduling (useful for testing or fallback) // Disable disables priority scheduling (useful for testing or fallback)

View File

@ -13,26 +13,29 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
// Constants for process monitoring // Variables for process monitoring (using configuration)
const ( var (
// System constants // System constants
pageSize = 4096 maxCPUPercent = GetConfig().MaxCPUPercent
maxCPUPercent = 100.0 minCPUPercent = GetConfig().MinCPUPercent
minCPUPercent = 0.01 defaultClockTicks = GetConfig().DefaultClockTicks
defaultClockTicks = 250.0 // Common for embedded ARM systems defaultMemoryGB = GetConfig().DefaultMemoryGB
defaultMemoryGB = 8
// Monitoring thresholds // Monitoring thresholds
maxWarmupSamples = 3 maxWarmupSamples = GetConfig().MaxWarmupSamples
warmupCPUSamples = 2 warmupCPUSamples = GetConfig().WarmupCPUSamples
logThrottleInterval = 10
// Channel buffer size // Channel buffer size
metricsChannelBuffer = 100 metricsChannelBuffer = GetConfig().MetricsChannelBuffer
// Clock tick detection ranges // Clock tick detection ranges
minValidClockTicks = 50 minValidClockTicks = float64(GetConfig().MinValidClockTicks)
maxValidClockTicks = 1000 maxValidClockTicks = float64(GetConfig().MaxValidClockTicks)
)
// Variables for process monitoring
var (
pageSize = GetConfig().PageSize
) )
// ProcessMetrics represents CPU and memory usage metrics for a process // ProcessMetrics represents CPU and memory usage metrics for a process
@ -217,7 +220,7 @@ func (pm *ProcessMonitor) collectMetrics(pid int, state *processState) (ProcessM
vsize, _ := strconv.ParseInt(fields[22], 10, 64) vsize, _ := strconv.ParseInt(fields[22], 10, 64)
rss, _ := strconv.ParseInt(fields[23], 10, 64) rss, _ := strconv.ParseInt(fields[23], 10, 64)
metric.MemoryRSS = rss * pageSize metric.MemoryRSS = rss * int64(pageSize)
metric.MemoryVMS = vsize metric.MemoryVMS = vsize
// Calculate CPU percentage // Calculate CPU percentage
@ -230,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) * 100.0 metric.MemoryPercent = float64(metric.MemoryRSS) / float64(totalMem) * GetConfig().PercentageMultiplier
} }
// Update state for next calculation // Update state for next calculation
@ -261,7 +264,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) * 100.0 cpuPercent := (cpuSeconds / timeDelta) * GetConfig().PercentageMultiplier
// Apply bounds // Apply bounds
if cpuPercent > maxCPUPercent { if cpuPercent > maxCPUPercent {
@ -341,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 = defaultMemoryGB * 1024 * 1024 * 1024 pm.totalMemory = int64(defaultMemoryGB) * 1024 * 1024 * 1024
return return
} }
defer file.Close() defer file.Close()
@ -360,7 +363,7 @@ func (pm *ProcessMonitor) getTotalMemory() int64 {
break break
} }
} }
pm.totalMemory = defaultMemoryGB * 1024 * 1024 * 1024 // Fallback pm.totalMemory = int64(defaultMemoryGB) * 1024 * 1024 * 1024 // Fallback
}) })
return pm.totalMemory return pm.totalMemory
} }

View File

@ -151,7 +151,7 @@ func (r *AudioRelay) relayLoop() {
r.logger.Error().Msg("Too many consecutive errors, stopping relay") r.logger.Error().Msg("Too many consecutive errors, stopping relay")
return return
} }
time.Sleep(10 * time.Millisecond) time.Sleep(GetConfig().ShortSleepDuration)
continue continue
} }

View File

@ -6,12 +6,7 @@ import (
"syscall" "syscall"
) )
const ( // Socket buffer sizes are now centralized in config_constants.go
// Socket buffer sizes optimized for JetKVM's audio workload
OptimalSocketBuffer = 128 * 1024 // 128KB (32 frames @ 4KB each)
MaxSocketBuffer = 256 * 1024 // 256KB for high-load scenarios
MinSocketBuffer = 32 * 1024 // 32KB minimum for basic functionality
)
// SocketBufferConfig holds socket buffer configuration // SocketBufferConfig holds socket buffer configuration
type SocketBufferConfig struct { type SocketBufferConfig struct {
@ -23,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: OptimalSocketBuffer, SendBufferSize: GetConfig().SocketOptimalBuffer,
RecvBufferSize: OptimalSocketBuffer, RecvBufferSize: GetConfig().SocketOptimalBuffer,
Enabled: true, Enabled: true,
} }
} }
@ -32,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: MaxSocketBuffer, SendBufferSize: GetConfig().SocketMaxBuffer,
RecvBufferSize: MaxSocketBuffer, RecvBufferSize: GetConfig().SocketMaxBuffer,
Enabled: true, Enabled: true,
} }
} }
@ -112,20 +107,20 @@ func ValidateSocketBufferConfig(config SocketBufferConfig) error {
return nil return nil
} }
if config.SendBufferSize < MinSocketBuffer { if config.SendBufferSize < GetConfig().SocketMinBuffer {
return fmt.Errorf("send buffer size %d is below minimum %d", config.SendBufferSize, MinSocketBuffer) return fmt.Errorf("send buffer size %d is below minimum %d", config.SendBufferSize, GetConfig().SocketMinBuffer)
} }
if config.RecvBufferSize < MinSocketBuffer { if config.RecvBufferSize < GetConfig().SocketMinBuffer {
return fmt.Errorf("receive buffer size %d is below minimum %d", config.RecvBufferSize, MinSocketBuffer) return fmt.Errorf("receive buffer size %d is below minimum %d", config.RecvBufferSize, GetConfig().SocketMinBuffer)
} }
if config.SendBufferSize > MaxSocketBuffer { if config.SendBufferSize > GetConfig().SocketMaxBuffer {
return fmt.Errorf("send buffer size %d exceeds maximum %d", config.SendBufferSize, MaxSocketBuffer) return fmt.Errorf("send buffer size %d exceeds maximum %d", config.SendBufferSize, GetConfig().SocketMaxBuffer)
} }
if config.RecvBufferSize > MaxSocketBuffer { if config.RecvBufferSize > GetConfig().SocketMaxBuffer {
return fmt.Errorf("receive buffer size %d exceeds maximum %d", config.RecvBufferSize, MaxSocketBuffer) return fmt.Errorf("receive buffer size %d exceeds maximum %d", config.RecvBufferSize, GetConfig().SocketMaxBuffer)
} }
return nil return nil

View File

@ -131,7 +131,7 @@ func (s *AudioServerSupervisor) Stop() error {
select { select {
case <-s.processDone: case <-s.processDone:
s.logger.Info().Msg("audio server process stopped gracefully") s.logger.Info().Msg("audio server process stopped gracefully")
case <-time.After(10 * time.Second): case <-time.After(GetConfig().SupervisorTimeout):
s.logger.Warn().Msg("audio server process did not stop gracefully, forcing termination") s.logger.Warn().Msg("audio server process did not stop gracefully, forcing termination")
s.forceKillProcess() s.forceKillProcess()
} }
@ -365,7 +365,7 @@ func (s *AudioServerSupervisor) terminateProcess() {
select { select {
case <-done: case <-done:
s.logger.Info().Int("pid", pid).Msg("audio server process terminated gracefully") s.logger.Info().Int("pid", pid).Msg("audio server process terminated gracefully")
case <-time.After(5 * time.Second): case <-time.After(GetConfig().InputSupervisorTimeout):
s.logger.Warn().Int("pid", pid).Msg("process did not terminate gracefully, sending SIGKILL") s.logger.Warn().Int("pid", pid).Msg("process did not terminate gracefully, sending SIGKILL")
s.forceKillProcess() s.forceKillProcess()
} }

View File

@ -234,7 +234,7 @@ func (p *ZeroCopyFramePool) GetZeroCopyPoolStats() ZeroCopyFramePoolStats {
var hitRate float64 var hitRate float64
if totalRequests > 0 { if totalRequests > 0 {
hitRate = float64(hitCount) / float64(totalRequests) * 100 hitRate = float64(hitCount) / float64(totalRequests) * GetConfig().PercentageMultiplier
} }
return ZeroCopyFramePoolStats{ return ZeroCopyFramePoolStats{