mirror of https://github.com/jetkvm/kvm.git
refactor(audio): standardize log levels and messages across components
- Change Info logs to Debug for routine operations - Standardize log message formatting to lowercase - Improve error message clarity and consistency - Add new metrics for device health and memory monitoring - Simplify config constants documentation
This commit is contained in:
parent
dc2db8ed2d
commit
50e04192bf
|
@ -108,7 +108,7 @@ func NewAdaptiveBufferManager(config AdaptiveBufferConfig) *AdaptiveBufferManage
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "adaptive-buffer").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "adaptive-buffer").Logger()
|
||||||
|
|
||||||
if err := ValidateAdaptiveBufferConfig(config.MinBufferSize, config.MaxBufferSize, config.DefaultBufferSize); err != nil {
|
if err := ValidateAdaptiveBufferConfig(config.MinBufferSize, config.MaxBufferSize, config.DefaultBufferSize); err != nil {
|
||||||
logger.Error().Err(err).Msg("Invalid adaptive buffer config, using defaults")
|
logger.Warn().Err(err).Msg("invalid adaptive buffer config, using defaults")
|
||||||
config = DefaultAdaptiveBufferConfig()
|
config = DefaultAdaptiveBufferConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,14 +130,14 @@ func NewAdaptiveBufferManager(config AdaptiveBufferConfig) *AdaptiveBufferManage
|
||||||
func (abm *AdaptiveBufferManager) Start() {
|
func (abm *AdaptiveBufferManager) Start() {
|
||||||
abm.wg.Add(1)
|
abm.wg.Add(1)
|
||||||
go abm.adaptationLoop()
|
go abm.adaptationLoop()
|
||||||
abm.logger.Info().Msg("Adaptive buffer manager started")
|
abm.logger.Info().Msg("adaptive buffer manager started")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the adaptive buffer management
|
// Stop stops the adaptive buffer management
|
||||||
func (abm *AdaptiveBufferManager) Stop() {
|
func (abm *AdaptiveBufferManager) Stop() {
|
||||||
abm.cancel()
|
abm.cancel()
|
||||||
abm.wg.Wait()
|
abm.wg.Wait()
|
||||||
abm.logger.Info().Msg("Adaptive buffer manager stopped")
|
abm.logger.Info().Msg("adaptive buffer manager stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInputBufferSize returns the current recommended input buffer size
|
// GetInputBufferSize returns the current recommended input buffer size
|
||||||
|
|
|
@ -72,14 +72,14 @@ func NewAdaptiveOptimizer(latencyMonitor *LatencyMonitor, bufferManager *Adaptiv
|
||||||
func (ao *AdaptiveOptimizer) Start() {
|
func (ao *AdaptiveOptimizer) Start() {
|
||||||
ao.wg.Add(1)
|
ao.wg.Add(1)
|
||||||
go ao.optimizationLoop()
|
go ao.optimizationLoop()
|
||||||
ao.logger.Info().Msg("Adaptive optimizer started")
|
ao.logger.Debug().Msg("adaptive optimizer started")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the adaptive optimizer
|
// Stop stops the adaptive optimizer
|
||||||
func (ao *AdaptiveOptimizer) Stop() {
|
func (ao *AdaptiveOptimizer) Stop() {
|
||||||
ao.cancel()
|
ao.cancel()
|
||||||
ao.wg.Wait()
|
ao.wg.Wait()
|
||||||
ao.logger.Info().Msg("Adaptive optimizer stopped")
|
ao.logger.Debug().Msg("adaptive optimizer stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializeStrategies sets up the available optimization strategies
|
// initializeStrategies sets up the available optimization strategies
|
||||||
|
@ -178,9 +178,9 @@ func (ao *AdaptiveOptimizer) checkStability() {
|
||||||
if metrics.Current > ao.config.RollbackThreshold {
|
if metrics.Current > ao.config.RollbackThreshold {
|
||||||
currentLevel := int(atomic.LoadInt64(&ao.optimizationLevel))
|
currentLevel := int(atomic.LoadInt64(&ao.optimizationLevel))
|
||||||
if currentLevel > 0 {
|
if currentLevel > 0 {
|
||||||
ao.logger.Warn().Dur("current_latency", metrics.Current).Dur("threshold", ao.config.RollbackThreshold).Msg("Rolling back optimizations due to excessive latency")
|
ao.logger.Warn().Dur("current_latency", metrics.Current).Dur("threshold", ao.config.RollbackThreshold).Msg("rolling back optimizations due to excessive latency")
|
||||||
if err := ao.decreaseOptimization(currentLevel - 1); err != nil {
|
if err := ao.decreaseOptimization(currentLevel - 1); err != nil {
|
||||||
ao.logger.Error().Err(err).Msg("Failed to decrease optimization level")
|
ao.logger.Error().Err(err).Msg("failed to decrease optimization level")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,8 +230,8 @@ func SetAudioQuality(quality AudioQuality) {
|
||||||
// Validate audio quality parameter
|
// Validate audio quality parameter
|
||||||
if err := ValidateAudioQuality(quality); err != nil {
|
if err := ValidateAudioQuality(quality); err != nil {
|
||||||
// Log validation error but don't fail - maintain backward compatibility
|
// Log validation error but don't fail - maintain backward compatibility
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "AudioConfig").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio").Logger()
|
||||||
logger.Error().Err(err).Int("quality", int(quality)).Msg("Invalid audio quality provided, ignoring")
|
logger.Warn().Err(err).Int("quality", int(quality)).Msg("invalid audio quality, using current config")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,8 +251,8 @@ func SetMicrophoneQuality(quality AudioQuality) {
|
||||||
// Validate audio quality parameter
|
// Validate audio quality parameter
|
||||||
if err := ValidateAudioQuality(quality); err != nil {
|
if err := ValidateAudioQuality(quality); err != nil {
|
||||||
// Log validation error but don't fail - maintain backward compatibility
|
// Log validation error but don't fail - maintain backward compatibility
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "MicrophoneConfig").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio").Logger()
|
||||||
logger.Error().Err(err).Int("quality", int(quality)).Msg("Invalid microphone quality provided, ignoring")
|
logger.Warn().Err(err).Int("quality", int(quality)).Msg("invalid microphone quality, using current config")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,22 +96,22 @@ func (bam *BaseAudioManager) updateLatency(latency time.Duration) {
|
||||||
|
|
||||||
// logComponentStart logs component start with consistent format
|
// logComponentStart logs component start with consistent format
|
||||||
func (bam *BaseAudioManager) logComponentStart(component string) {
|
func (bam *BaseAudioManager) logComponentStart(component string) {
|
||||||
bam.logger.Info().Str("component", component).Msg("starting component")
|
bam.logger.Debug().Str("component", component).Msg("starting component")
|
||||||
}
|
}
|
||||||
|
|
||||||
// logComponentStarted logs component started with consistent format
|
// logComponentStarted logs component started with consistent format
|
||||||
func (bam *BaseAudioManager) logComponentStarted(component string) {
|
func (bam *BaseAudioManager) logComponentStarted(component string) {
|
||||||
bam.logger.Info().Str("component", component).Msg("component started successfully")
|
bam.logger.Debug().Str("component", component).Msg("component started successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
// logComponentStop logs component stop with consistent format
|
// logComponentStop logs component stop with consistent format
|
||||||
func (bam *BaseAudioManager) logComponentStop(component string) {
|
func (bam *BaseAudioManager) logComponentStop(component string) {
|
||||||
bam.logger.Info().Str("component", component).Msg("stopping component")
|
bam.logger.Debug().Str("component", component).Msg("stopping component")
|
||||||
}
|
}
|
||||||
|
|
||||||
// logComponentStopped logs component stopped with consistent format
|
// logComponentStopped logs component stopped with consistent format
|
||||||
func (bam *BaseAudioManager) logComponentStopped(component string) {
|
func (bam *BaseAudioManager) logComponentStopped(component string) {
|
||||||
bam.logger.Info().Str("component", component).Msg("component stopped")
|
bam.logger.Debug().Str("component", component).Msg("component stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// logComponentError logs component error with consistent format
|
// logComponentError logs component error with consistent format
|
||||||
|
|
|
@ -63,12 +63,12 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu
|
||||||
// Validate input parameters
|
// Validate input parameters
|
||||||
if err := ValidateBufferSize(batchSize); err != nil {
|
if err := ValidateBufferSize(batchSize); err != nil {
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger()
|
||||||
logger.Error().Err(err).Int("batchSize", batchSize).Msg("Invalid batch size provided, using default")
|
logger.Warn().Err(err).Int("batchSize", batchSize).Msg("invalid batch size, using default")
|
||||||
batchSize = GetConfig().BatchProcessorFramesPerBatch
|
batchSize = GetConfig().BatchProcessorFramesPerBatch
|
||||||
}
|
}
|
||||||
if batchDuration <= 0 {
|
if batchDuration <= 0 {
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger()
|
||||||
logger.Error().Dur("batchDuration", batchDuration).Msg("Invalid batch duration provided, using default")
|
logger.Warn().Dur("batchDuration", batchDuration).Msg("invalid batch duration, using default")
|
||||||
batchDuration = GetConfig().BatchProcessingDelay
|
batchDuration = GetConfig().BatchProcessingDelay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ func (bap *BatchAudioProcessor) Stop() {
|
||||||
func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
|
func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
|
||||||
// Validate buffer before processing
|
// Validate buffer before processing
|
||||||
if err := ValidateBufferSize(len(buffer)); err != nil {
|
if err := ValidateBufferSize(len(buffer)); err != nil {
|
||||||
bap.logger.Debug().Err(err).Msg("Invalid buffer for batch processing")
|
bap.logger.Debug().Err(err).Msg("invalid buffer for batch processing")
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,12 +220,12 @@ func (bap *BatchAudioProcessor) processBatchRead(batch []batchReadRequest) {
|
||||||
|
|
||||||
// Set high priority for batch audio processing
|
// Set high priority for batch audio processing
|
||||||
if err := SetAudioThreadPriority(); err != nil {
|
if err := SetAudioThreadPriority(); err != nil {
|
||||||
bap.logger.Warn().Err(err).Msg("Failed to set batch audio processing priority")
|
bap.logger.Warn().Err(err).Msg("failed to set batch audio processing priority")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := ResetThreadPriority(); err != nil {
|
if err := ResetThreadPriority(); err != nil {
|
||||||
bap.logger.Warn().Err(err).Msg("Failed to reset thread priority")
|
bap.logger.Warn().Err(err).Msg("failed to reset thread priority")
|
||||||
}
|
}
|
||||||
runtime.UnlockOSThread()
|
runtime.UnlockOSThread()
|
||||||
atomic.StoreInt32(&bap.threadPinned, 0)
|
atomic.StoreInt32(&bap.threadPinned, 0)
|
||||||
|
|
|
@ -29,7 +29,7 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
|
||||||
if err := ValidateBufferSize(bufferSize); err != nil {
|
if err := ValidateBufferSize(bufferSize); err != nil {
|
||||||
// Log validation error and use default value
|
// Log validation error and use default value
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "AudioBufferPool").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "AudioBufferPool").Logger()
|
||||||
logger.Error().Err(err).Int("bufferSize", bufferSize).Msg("Invalid buffer size provided, using default")
|
logger.Warn().Err(err).Int("bufferSize", bufferSize).Msg("invalid buffer size, using default")
|
||||||
bufferSize = GetConfig().AudioFramePoolSize
|
bufferSize = GetConfig().AudioFramePoolSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,210 +8,53 @@ import (
|
||||||
|
|
||||||
// AudioConfigConstants centralizes all hardcoded values used across audio components.
|
// AudioConfigConstants centralizes all hardcoded values used across audio components.
|
||||||
// This configuration system allows runtime tuning of audio performance, quality, and resource usage.
|
// This configuration system allows runtime tuning of audio performance, quality, and resource usage.
|
||||||
// Each constant is documented with its purpose, usage location, and impact on system behavior.
|
|
||||||
type AudioConfigConstants struct {
|
type AudioConfigConstants struct {
|
||||||
// Audio Quality Presets
|
// Audio Quality Presets
|
||||||
// MaxAudioFrameSize defines the maximum size of an audio frame in bytes.
|
MaxAudioFrameSize int // Maximum audio frame size in bytes (default: 4096)
|
||||||
// Used in: buffer_pool.go, adaptive_buffer.go
|
|
||||||
// Impact: Higher values allow larger audio chunks but increase memory usage and latency.
|
|
||||||
// Typical range: 1024-8192 bytes. Default 4096 provides good balance.
|
|
||||||
MaxAudioFrameSize int
|
|
||||||
|
|
||||||
// Opus Encoding Parameters - Core codec settings for audio compression
|
// Opus Encoding Parameters
|
||||||
// OpusBitrate sets the target bitrate for Opus encoding in bits per second.
|
OpusBitrate int // Target bitrate for Opus encoding in bps (default: 128000)
|
||||||
// Used in: cgo_audio.go for encoder initialization
|
OpusComplexity int // Computational complexity 0-10 (default: 10 for best quality)
|
||||||
// Impact: Higher bitrates improve audio quality but increase bandwidth usage.
|
OpusVBR int // Variable Bit Rate: 0=CBR, 1=VBR (default: 1)
|
||||||
// Range: 6000-510000 bps. 128000 (128kbps) provides high quality for most use cases.
|
OpusVBRConstraint int // VBR constraint: 0=unconstrained, 1=constrained (default: 0)
|
||||||
OpusBitrate int
|
OpusDTX int // Discontinuous Transmission: 0=disabled, 1=enabled (default: 0)
|
||||||
|
|
||||||
// OpusComplexity controls the computational complexity of Opus encoding (0-10).
|
// Audio Parameters
|
||||||
// Used in: cgo_audio.go for encoder configuration
|
SampleRate int // Audio sampling frequency in Hz (default: 48000)
|
||||||
// Impact: Higher values improve quality but increase CPU usage and encoding latency.
|
Channels int // Number of audio channels: 1=mono, 2=stereo (default: 2)
|
||||||
// Range: 0-10. Value 10 provides best quality, 0 fastest encoding.
|
FrameSize int // Samples per audio frame (default: 960 for 20ms at 48kHz)
|
||||||
OpusComplexity int
|
MaxPacketSize int // Maximum encoded packet size in bytes (default: 4000)
|
||||||
|
|
||||||
// OpusVBR enables Variable Bit Rate encoding (0=CBR, 1=VBR).
|
// Audio Quality Bitrates (kbps)
|
||||||
// Used in: cgo_audio.go for encoder mode selection
|
AudioQualityLowOutputBitrate int // Low-quality output bitrate (default: 32)
|
||||||
// Impact: VBR (1) adapts bitrate to content complexity, improving efficiency.
|
AudioQualityLowInputBitrate int // Low-quality input bitrate (default: 16)
|
||||||
// CBR (0) maintains constant bitrate for predictable bandwidth usage.
|
AudioQualityMediumOutputBitrate int // Medium-quality output bitrate (default: 64)
|
||||||
OpusVBR int
|
AudioQualityMediumInputBitrate int // Medium-quality input bitrate (default: 32)
|
||||||
|
AudioQualityHighOutputBitrate int // High-quality output bitrate (default: 128)
|
||||||
|
AudioQualityHighInputBitrate int // High-quality input bitrate (default: 64)
|
||||||
|
AudioQualityUltraOutputBitrate int // Ultra-quality output bitrate (default: 192)
|
||||||
|
AudioQualityUltraInputBitrate int // Ultra-quality input bitrate (default: 96)
|
||||||
|
|
||||||
// OpusVBRConstraint enables constrained VBR mode (0=unconstrained, 1=constrained).
|
// Audio Quality Sample Rates (Hz)
|
||||||
// Used in: cgo_audio.go when VBR is enabled
|
AudioQualityLowSampleRate int // Low-quality sample rate (default: 22050)
|
||||||
// Impact: Constrained VBR (1) limits bitrate variation for more predictable bandwidth.
|
AudioQualityMediumSampleRate int // Medium-quality sample rate (default: 44100)
|
||||||
// Unconstrained (0) allows full bitrate adaptation for optimal quality.
|
AudioQualityMicLowSampleRate int // Low-quality microphone sample rate (default: 16000)
|
||||||
OpusVBRConstraint int
|
|
||||||
|
|
||||||
// OpusDTX enables Discontinuous Transmission (0=disabled, 1=enabled).
|
// Audio Quality Frame Sizes
|
||||||
// Used in: cgo_audio.go for encoder optimization
|
AudioQualityLowFrameSize time.Duration // Low-quality frame duration (default: 40ms)
|
||||||
// Impact: DTX (1) reduces bandwidth during silence but may cause audio artifacts.
|
AudioQualityMediumFrameSize time.Duration // Medium-quality frame duration (default: 20ms)
|
||||||
// Disabled (0) maintains constant transmission for consistent quality.
|
AudioQualityHighFrameSize time.Duration // High-quality frame duration (default: 20ms)
|
||||||
OpusDTX int
|
|
||||||
|
|
||||||
// Audio Parameters - Fundamental audio stream characteristics
|
AudioQualityUltraFrameSize time.Duration // Ultra-quality frame duration (default: 10ms)
|
||||||
// SampleRate defines the number of audio samples per second in Hz.
|
|
||||||
// Used in: All audio processing components
|
|
||||||
// Impact: Higher rates improve frequency response but increase processing load.
|
|
||||||
// Common values: 16000 (voice), 44100 (CD quality), 48000 (professional).
|
|
||||||
SampleRate int
|
|
||||||
|
|
||||||
// Channels specifies the number of audio channels (1=mono, 2=stereo).
|
// Audio Quality Channels
|
||||||
// Used in: All audio processing and encoding/decoding operations
|
AudioQualityLowChannels int // Low-quality channel count (default: 1)
|
||||||
// Impact: Stereo (2) provides spatial audio but doubles bandwidth and processing.
|
AudioQualityMediumChannels int // Medium-quality channel count (default: 2)
|
||||||
// Mono (1) reduces resource usage but loses spatial information.
|
AudioQualityHighChannels int // High-quality channel count (default: 2)
|
||||||
Channels int
|
AudioQualityUltraChannels int // Ultra-quality channel count (default: 2)
|
||||||
|
|
||||||
// FrameSize defines the number of samples per audio frame.
|
// CGO Audio Constants
|
||||||
// Used in: Opus encoding/decoding, buffer management
|
CGOOpusBitrate int // Native Opus encoder bitrate in bps (default: 96000)
|
||||||
// Impact: Larger frames reduce overhead but increase latency.
|
|
||||||
// Must match Opus frame sizes: 120, 240, 480, 960, 1920, 2880 samples.
|
|
||||||
FrameSize int
|
|
||||||
|
|
||||||
// MaxPacketSize sets the maximum size of encoded audio packets in bytes.
|
|
||||||
// Used in: Network transmission, buffer allocation
|
|
||||||
// Impact: Larger packets reduce network overhead but increase burst bandwidth.
|
|
||||||
// Should accommodate worst-case Opus output plus protocol headers.
|
|
||||||
MaxPacketSize int
|
|
||||||
|
|
||||||
// Audio Quality Bitrates - Predefined quality presets for different use cases
|
|
||||||
// These bitrates are used in audio.go for quality level selection
|
|
||||||
// Impact: Higher bitrates improve audio fidelity but increase bandwidth usage
|
|
||||||
|
|
||||||
// AudioQualityLowOutputBitrate defines bitrate for low-quality audio output (kbps).
|
|
||||||
// Used in: audio.go for bandwidth-constrained scenarios
|
|
||||||
// Impact: Minimal bandwidth usage but reduced audio quality. Suitable for voice-only.
|
|
||||||
// Default 32kbps provides acceptable voice quality with very low bandwidth.
|
|
||||||
AudioQualityLowOutputBitrate int
|
|
||||||
|
|
||||||
// AudioQualityLowInputBitrate defines bitrate for low-quality audio input (kbps).
|
|
||||||
// Used in: audio.go for microphone input in low-bandwidth scenarios
|
|
||||||
// Impact: Reduces upload bandwidth but may affect voice clarity.
|
|
||||||
// Default 16kbps suitable for basic voice communication.
|
|
||||||
AudioQualityLowInputBitrate int
|
|
||||||
|
|
||||||
// AudioQualityMediumOutputBitrate defines bitrate for medium-quality audio output (kbps).
|
|
||||||
// Used in: audio.go for balanced quality/bandwidth scenarios
|
|
||||||
// Impact: Good balance between quality and bandwidth usage.
|
|
||||||
// Default 64kbps provides clear voice and acceptable music quality.
|
|
||||||
AudioQualityMediumOutputBitrate int
|
|
||||||
|
|
||||||
// AudioQualityMediumInputBitrate defines bitrate for medium-quality audio input (kbps).
|
|
||||||
// Used in: audio.go for microphone input with balanced quality
|
|
||||||
// Impact: Better voice quality than low setting with moderate bandwidth usage.
|
|
||||||
// Default 32kbps suitable for clear voice communication.
|
|
||||||
AudioQualityMediumInputBitrate int
|
|
||||||
|
|
||||||
// AudioQualityHighOutputBitrate defines bitrate for high-quality audio output (kbps).
|
|
||||||
// Used in: audio.go for high-fidelity audio scenarios
|
|
||||||
// Impact: Excellent audio quality but higher bandwidth requirements.
|
|
||||||
// Default 128kbps provides near-CD quality for music and crystal-clear voice.
|
|
||||||
AudioQualityHighOutputBitrate int
|
|
||||||
|
|
||||||
// AudioQualityHighInputBitrate defines bitrate for high-quality audio input (kbps).
|
|
||||||
// Used in: audio.go for high-quality microphone capture
|
|
||||||
// Impact: Superior voice quality but increased upload bandwidth usage.
|
|
||||||
// Default 64kbps suitable for professional voice communication.
|
|
||||||
AudioQualityHighInputBitrate int
|
|
||||||
|
|
||||||
// AudioQualityUltraOutputBitrate defines bitrate for ultra-high-quality audio output (kbps).
|
|
||||||
// Used in: audio.go for maximum quality scenarios
|
|
||||||
// Impact: Maximum audio fidelity but highest bandwidth consumption.
|
|
||||||
// Default 192kbps provides studio-quality audio for critical applications.
|
|
||||||
AudioQualityUltraOutputBitrate int
|
|
||||||
|
|
||||||
// AudioQualityUltraInputBitrate defines bitrate for ultra-high-quality audio input (kbps).
|
|
||||||
// Used in: audio.go for maximum quality microphone capture
|
|
||||||
// Impact: Best possible voice quality but maximum upload bandwidth usage.
|
|
||||||
// Default 96kbps suitable for broadcast-quality voice communication.
|
|
||||||
AudioQualityUltraInputBitrate int
|
|
||||||
|
|
||||||
// Audio Quality Sample Rates - Frequency sampling rates for different quality levels
|
|
||||||
// Used in: audio.go for configuring audio capture and playback sample rates
|
|
||||||
// Impact: Higher sample rates capture more frequency detail but increase processing load
|
|
||||||
|
|
||||||
// AudioQualityLowSampleRate defines sample rate for low-quality audio (Hz).
|
|
||||||
// Used in: audio.go for bandwidth-constrained scenarios
|
|
||||||
// Impact: Reduces frequency response but minimizes processing and bandwidth.
|
|
||||||
// Default 22050Hz captures frequencies up to 11kHz, adequate for voice.
|
|
||||||
AudioQualityLowSampleRate int
|
|
||||||
|
|
||||||
// AudioQualityMediumSampleRate defines sample rate for medium-quality audio (Hz).
|
|
||||||
// Used in: audio.go for balanced quality scenarios
|
|
||||||
// Impact: Good frequency response with moderate processing requirements.
|
|
||||||
// Default 44100Hz (CD quality) captures frequencies up to 22kHz.
|
|
||||||
AudioQualityMediumSampleRate int
|
|
||||||
|
|
||||||
// AudioQualityMicLowSampleRate defines sample rate for low-quality microphone input (Hz).
|
|
||||||
// Used in: audio.go for microphone capture in constrained scenarios
|
|
||||||
// Impact: Optimized for voice communication with minimal processing overhead.
|
|
||||||
// Default 16000Hz captures voice frequencies (300-3400Hz) efficiently.
|
|
||||||
AudioQualityMicLowSampleRate int
|
|
||||||
|
|
||||||
// Audio Quality Frame Sizes - Duration of audio frames for different quality levels
|
|
||||||
// Used in: audio.go for configuring Opus frame duration
|
|
||||||
// Impact: Larger frames reduce overhead but increase latency and memory usage
|
|
||||||
|
|
||||||
// AudioQualityLowFrameSize defines frame duration for low-quality audio.
|
|
||||||
// Used in: audio.go for low-latency scenarios with minimal processing
|
|
||||||
// Impact: Longer frames reduce CPU overhead but increase audio latency.
|
|
||||||
// Default 40ms provides good efficiency for voice communication.
|
|
||||||
AudioQualityLowFrameSize time.Duration
|
|
||||||
|
|
||||||
// AudioQualityMediumFrameSize defines frame duration for medium-quality audio.
|
|
||||||
// Used in: audio.go for balanced latency and efficiency
|
|
||||||
// Impact: Moderate frame size balances latency and processing efficiency.
|
|
||||||
// Default 20ms provides good balance for most applications.
|
|
||||||
AudioQualityMediumFrameSize time.Duration
|
|
||||||
|
|
||||||
// AudioQualityHighFrameSize defines frame duration for high-quality audio.
|
|
||||||
// Used in: audio.go for high-quality scenarios
|
|
||||||
// Impact: Optimized frame size for high-quality encoding efficiency.
|
|
||||||
// Default 20ms maintains low latency while supporting high bitrates.
|
|
||||||
AudioQualityHighFrameSize time.Duration
|
|
||||||
|
|
||||||
// AudioQualityUltraFrameSize defines frame duration for ultra-quality audio.
|
|
||||||
// Used in: audio.go for maximum quality scenarios
|
|
||||||
// Impact: Smaller frames reduce latency but increase processing overhead.
|
|
||||||
// Default 10ms provides minimal latency for real-time applications.
|
|
||||||
AudioQualityUltraFrameSize time.Duration
|
|
||||||
|
|
||||||
// Audio Quality Channels - Channel configuration for different quality levels
|
|
||||||
// Used in: audio.go for configuring mono/stereo audio
|
|
||||||
// Impact: Stereo doubles bandwidth and processing but provides spatial audio
|
|
||||||
|
|
||||||
// AudioQualityLowChannels defines channel count for low-quality audio.
|
|
||||||
// Used in: audio.go for bandwidth-constrained scenarios
|
|
||||||
// Impact: Mono (1) minimizes bandwidth and processing for voice communication.
|
|
||||||
// Default 1 (mono) suitable for voice-only applications.
|
|
||||||
AudioQualityLowChannels int
|
|
||||||
|
|
||||||
// AudioQualityMediumChannels defines channel count for medium-quality audio.
|
|
||||||
// Used in: audio.go for balanced quality scenarios
|
|
||||||
// Impact: Stereo (2) provides spatial audio with moderate bandwidth increase.
|
|
||||||
// Default 2 (stereo) suitable for general audio applications.
|
|
||||||
AudioQualityMediumChannels int
|
|
||||||
|
|
||||||
// AudioQualityHighChannels defines channel count for high-quality audio.
|
|
||||||
// Used in: audio.go for high-fidelity scenarios
|
|
||||||
// Impact: Stereo (2) essential for high-quality music and spatial audio.
|
|
||||||
// Default 2 (stereo) required for full audio experience.
|
|
||||||
AudioQualityHighChannels int
|
|
||||||
|
|
||||||
// AudioQualityUltraChannels defines channel count for ultra-quality audio.
|
|
||||||
// Used in: audio.go for maximum quality scenarios
|
|
||||||
// Impact: Stereo (2) mandatory for studio-quality audio reproduction.
|
|
||||||
// Default 2 (stereo) provides full spatial audio fidelity.
|
|
||||||
AudioQualityUltraChannels int
|
|
||||||
|
|
||||||
// CGO Audio Constants - Low-level C library configuration for audio processing
|
|
||||||
// These constants are passed to C code in cgo_audio.go for native audio operations
|
|
||||||
// Impact: Direct control over native audio library behavior and performance
|
|
||||||
|
|
||||||
// CGOOpusBitrate sets the bitrate for native Opus encoder (bits per second).
|
|
||||||
// Used in: cgo_audio.go update_audio_constants() function
|
|
||||||
// Impact: Controls quality vs bandwidth tradeoff in native encoding.
|
|
||||||
// Default 96000 (96kbps) provides good quality for real-time applications.
|
|
||||||
CGOOpusBitrate int
|
|
||||||
|
|
||||||
// CGOOpusComplexity sets computational complexity for native Opus encoder (0-10).
|
// CGOOpusComplexity sets computational complexity for native Opus encoder (0-10).
|
||||||
// Used in: cgo_audio.go for native encoder configuration
|
// Used in: cgo_audio.go for native encoder configuration
|
||||||
|
|
|
@ -130,7 +130,7 @@ func (dhm *DeviceHealthMonitor) Start() error {
|
||||||
return fmt.Errorf("device health monitor already running")
|
return fmt.Errorf("device health monitor already running")
|
||||||
}
|
}
|
||||||
|
|
||||||
dhm.logger.Info().Msg("starting device health monitor")
|
dhm.logger.Debug().Msg("device health monitor starting")
|
||||||
atomic.StoreInt32(&dhm.monitoringEnabled, 1)
|
atomic.StoreInt32(&dhm.monitoringEnabled, 1)
|
||||||
|
|
||||||
go dhm.monitoringLoop()
|
go dhm.monitoringLoop()
|
||||||
|
@ -143,7 +143,7 @@ func (dhm *DeviceHealthMonitor) Stop() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dhm.logger.Info().Msg("stopping device health monitor")
|
dhm.logger.Debug().Msg("device health monitor stopping")
|
||||||
atomic.StoreInt32(&dhm.monitoringEnabled, 0)
|
atomic.StoreInt32(&dhm.monitoringEnabled, 0)
|
||||||
|
|
||||||
close(dhm.stopChan)
|
close(dhm.stopChan)
|
||||||
|
@ -152,7 +152,7 @@ func (dhm *DeviceHealthMonitor) Stop() {
|
||||||
// Wait for monitoring loop to finish
|
// Wait for monitoring loop to finish
|
||||||
select {
|
select {
|
||||||
case <-dhm.doneChan:
|
case <-dhm.doneChan:
|
||||||
dhm.logger.Info().Msg("device health monitor stopped")
|
dhm.logger.Debug().Msg("device health monitor stopped")
|
||||||
case <-time.After(time.Duration(dhm.config.SupervisorTimeout)):
|
case <-time.After(time.Duration(dhm.config.SupervisorTimeout)):
|
||||||
dhm.logger.Warn().Msg("device health monitor stop timeout")
|
dhm.logger.Warn().Msg("device health monitor stop timeout")
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ func (dhm *DeviceHealthMonitor) RegisterRecoveryCallback(component string, callb
|
||||||
dhm.callbackMutex.Lock()
|
dhm.callbackMutex.Lock()
|
||||||
defer dhm.callbackMutex.Unlock()
|
defer dhm.callbackMutex.Unlock()
|
||||||
dhm.recoveryCallbacks[component] = callback
|
dhm.recoveryCallbacks[component] = callback
|
||||||
dhm.logger.Info().Str("component", component).Msg("registered recovery callback")
|
dhm.logger.Debug().Str("component", component).Msg("registered recovery callback")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordError records an error for health tracking
|
// RecordError records an error for health tracking
|
||||||
|
|
|
@ -144,7 +144,7 @@ func (aeb *AudioEventBroadcaster) Subscribe(connectionID string, conn *websocket
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
aeb.logger.Info().Str("connectionID", connectionID).Msg("audio events subscription added")
|
aeb.logger.Debug().Str("connectionID", connectionID).Msg("audio events subscription added")
|
||||||
|
|
||||||
// Send initial state to new subscriber
|
// Send initial state to new subscriber
|
||||||
go aeb.sendInitialState(connectionID)
|
go aeb.sendInitialState(connectionID)
|
||||||
|
@ -156,7 +156,7 @@ func (aeb *AudioEventBroadcaster) Unsubscribe(connectionID string) {
|
||||||
defer aeb.mutex.Unlock()
|
defer aeb.mutex.Unlock()
|
||||||
|
|
||||||
delete(aeb.subscribers, connectionID)
|
delete(aeb.subscribers, connectionID)
|
||||||
aeb.logger.Info().Str("connectionID", connectionID).Msg("audio events subscription removed")
|
aeb.logger.Debug().Str("connectionID", connectionID).Msg("audio events subscription removed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastAudioMuteChanged broadcasts audio mute state changes
|
// BroadcastAudioMuteChanged broadcasts audio mute state changes
|
||||||
|
|
|
@ -301,8 +301,8 @@ func (ais *AudioInputServer) acceptConnections() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ais.running {
|
if ais.running {
|
||||||
// Log error and continue accepting
|
// Log error and continue accepting
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", AudioInputServerComponent).Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio-input").Logger()
|
||||||
logger.Warn().Err(err).Msg("Failed to accept connection, retrying")
|
logger.Warn().Err(err).Msg("failed to accept connection, retrying")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -311,8 +311,8 @@ func (ais *AudioInputServer) acceptConnections() {
|
||||||
// Configure socket buffers for optimal performance
|
// Configure socket buffers for optimal performance
|
||||||
if err := ConfigureSocketBuffers(conn, ais.socketBufferConfig); err != nil {
|
if err := ConfigureSocketBuffers(conn, ais.socketBufferConfig); err != nil {
|
||||||
// Log warning but don't fail - socket buffer optimization is not critical
|
// Log warning but don't fail - socket buffer optimization is not critical
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", AudioInputServerComponent).Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio-input").Logger()
|
||||||
logger.Warn().Err(err).Msg("Failed to configure socket buffers, continuing with defaults")
|
logger.Warn().Err(err).Msg("failed to configure socket buffers, using defaults")
|
||||||
} else {
|
} else {
|
||||||
// Record socket buffer metrics for monitoring
|
// Record socket buffer metrics for monitoring
|
||||||
RecordSocketBufferMetrics(conn, "audio-input")
|
RecordSocketBufferMetrics(conn, "audio-input")
|
||||||
|
|
|
@ -31,7 +31,7 @@ func (aim *AudioInputIPCManager) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
aim.logger.Info().Str("component", AudioInputIPCComponent).Msg("starting component")
|
aim.logger.Debug().Str("component", AudioInputIPCComponent).Msg("starting component")
|
||||||
|
|
||||||
err := aim.supervisor.Start()
|
err := aim.supervisor.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -51,7 +51,7 @@ func (aim *AudioInputIPCManager) Start() error {
|
||||||
|
|
||||||
// Validate configuration before using it
|
// Validate configuration before using it
|
||||||
if err := ValidateInputIPCConfig(config.SampleRate, config.Channels, config.FrameSize); err != nil {
|
if err := ValidateInputIPCConfig(config.SampleRate, config.Channels, config.FrameSize); err != nil {
|
||||||
aim.logger.Error().Err(err).Msg("Invalid input IPC config from constants, using defaults")
|
aim.logger.Warn().Err(err).Msg("invalid input IPC config from constants, using defaults")
|
||||||
// Use safe defaults if config validation fails
|
// Use safe defaults if config validation fails
|
||||||
config = InputIPCConfig{
|
config = InputIPCConfig{
|
||||||
SampleRate: 48000,
|
SampleRate: 48000,
|
||||||
|
@ -69,7 +69,7 @@ func (aim *AudioInputIPCManager) Start() error {
|
||||||
aim.logger.Warn().Err(err).Str("component", AudioInputIPCComponent).Msg("failed to send initial config, will retry later")
|
aim.logger.Warn().Err(err).Str("component", AudioInputIPCComponent).Msg("failed to send initial config, will retry later")
|
||||||
}
|
}
|
||||||
|
|
||||||
aim.logger.Info().Str("component", AudioInputIPCComponent).Msg("component started successfully")
|
aim.logger.Debug().Str("component", AudioInputIPCComponent).Msg("component started successfully")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,9 +79,9 @@ func (aim *AudioInputIPCManager) Stop() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aim.logger.Info().Str("component", AudioInputIPCComponent).Msg("stopping component")
|
aim.logger.Debug().Str("component", AudioInputIPCComponent).Msg("stopping component")
|
||||||
aim.supervisor.Stop()
|
aim.supervisor.Stop()
|
||||||
aim.logger.Info().Str("component", AudioInputIPCComponent).Msg("component stopped")
|
aim.logger.Debug().Str("component", AudioInputIPCComponent).Msg("component stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetMetrics resets all metrics to zero
|
// resetMetrics resets all metrics to zero
|
||||||
|
@ -105,7 +105,7 @@ func (aim *AudioInputIPCManager) WriteOpusFrame(frame []byte) error {
|
||||||
// Validate frame data
|
// Validate frame data
|
||||||
if err := ValidateFrameData(frame); err != nil {
|
if err := ValidateFrameData(frame); err != nil {
|
||||||
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
||||||
aim.logger.Debug().Err(err).Msg("Invalid frame data")
|
aim.logger.Debug().Err(err).Msg("invalid frame data")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ func (aim *AudioInputIPCManager) WriteOpusFrame(frame []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Count as dropped frame
|
// Count as dropped frame
|
||||||
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
||||||
aim.logger.Debug().Err(err).Msg("Failed to send frame via IPC")
|
aim.logger.Debug().Err(err).Msg("failed to send frame via IPC")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ func (aim *AudioInputIPCManager) WriteOpusFrameZeroCopy(frame *ZeroCopyAudioFram
|
||||||
// Validate zero-copy frame
|
// Validate zero-copy frame
|
||||||
if err := ValidateZeroCopyFrame(frame); err != nil {
|
if err := ValidateZeroCopyFrame(frame); err != nil {
|
||||||
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
||||||
aim.logger.Debug().Err(err).Msg("Invalid zero-copy frame")
|
aim.logger.Debug().Err(err).Msg("invalid zero-copy frame")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ func (aim *AudioInputIPCManager) WriteOpusFrameZeroCopy(frame *ZeroCopyAudioFram
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Count as dropped frame
|
// Count as dropped frame
|
||||||
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
|
||||||
aim.logger.Debug().Err(err).Msg("Failed to send zero-copy frame via IPC")
|
aim.logger.Debug().Err(err).Msg("failed to send zero-copy frame via IPC")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
// This should be called from main() when the subprocess is detected
|
// This should be called from main() when the subprocess is detected
|
||||||
func RunAudioInputServer() error {
|
func RunAudioInputServer() error {
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-server").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-server").Logger()
|
||||||
logger.Info().Msg("Starting audio input server subprocess")
|
logger.Debug().Msg("audio input server subprocess starting")
|
||||||
|
|
||||||
// Start adaptive buffer management for optimal performance
|
// Start adaptive buffer management for optimal performance
|
||||||
StartAdaptiveBuffering()
|
StartAdaptiveBuffering()
|
||||||
|
@ -23,7 +23,7 @@ func RunAudioInputServer() error {
|
||||||
// Initialize CGO audio system
|
// Initialize CGO audio system
|
||||||
err := CGOAudioPlaybackInit()
|
err := CGOAudioPlaybackInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("Failed to initialize CGO audio playback")
|
logger.Error().Err(err).Msg("failed to initialize CGO audio playback")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer CGOAudioPlaybackClose()
|
defer CGOAudioPlaybackClose()
|
||||||
|
@ -31,18 +31,18 @@ func RunAudioInputServer() error {
|
||||||
// Create and start the IPC server
|
// Create and start the IPC server
|
||||||
server, err := NewAudioInputServer()
|
server, err := NewAudioInputServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("Failed to create audio input server")
|
logger.Error().Err(err).Msg("failed to create audio input server")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error().Err(err).Msg("Failed to start audio input server")
|
logger.Error().Err(err).Msg("failed to start audio input server")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info().Msg("Audio input server started, waiting for connections")
|
logger.Debug().Msg("audio input server started, waiting for connections")
|
||||||
|
|
||||||
// Set up signal handling for graceful shutdown
|
// Set up signal handling for graceful shutdown
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
@ -54,18 +54,18 @@ func RunAudioInputServer() error {
|
||||||
// Wait for shutdown signal
|
// Wait for shutdown signal
|
||||||
select {
|
select {
|
||||||
case sig := <-sigChan:
|
case sig := <-sigChan:
|
||||||
logger.Info().Str("signal", sig.String()).Msg("Received shutdown signal")
|
logger.Info().Str("signal", sig.String()).Msg("received shutdown signal")
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logger.Info().Msg("Context cancelled")
|
logger.Debug().Msg("context cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
logger.Info().Msg("Shutting down audio input server")
|
logger.Debug().Msg("shutting down audio input server")
|
||||||
server.Stop()
|
server.Stop()
|
||||||
|
|
||||||
// Give some time for cleanup
|
// Give some time for cleanup
|
||||||
time.Sleep(GetConfig().DefaultSleepDuration)
|
time.Sleep(GetConfig().DefaultSleepDuration)
|
||||||
|
|
||||||
logger.Info().Msg("Audio input server subprocess stopped")
|
logger.Debug().Msg("audio input server subprocess stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,14 +117,14 @@ func NewLatencyMonitor(config LatencyConfig, logger zerolog.Logger) *LatencyMoni
|
||||||
func (lm *LatencyMonitor) Start() {
|
func (lm *LatencyMonitor) Start() {
|
||||||
lm.wg.Add(1)
|
lm.wg.Add(1)
|
||||||
go lm.monitoringLoop()
|
go lm.monitoringLoop()
|
||||||
lm.logger.Info().Msg("Latency monitor started")
|
lm.logger.Debug().Msg("latency monitor started")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the latency monitor
|
// Stop stops the latency monitor
|
||||||
func (lm *LatencyMonitor) Stop() {
|
func (lm *LatencyMonitor) Stop() {
|
||||||
lm.cancel()
|
lm.cancel()
|
||||||
lm.wg.Wait()
|
lm.wg.Wait()
|
||||||
lm.logger.Info().Msg("Latency monitor stopped")
|
lm.logger.Debug().Msg("latency monitor stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordLatency records a new latency measurement
|
// RecordLatency records a new latency measurement
|
||||||
|
@ -263,20 +263,20 @@ func (lm *LatencyMonitor) runOptimization() {
|
||||||
// Check if current latency exceeds threshold
|
// Check if current latency exceeds threshold
|
||||||
if metrics.Current > lm.config.MaxLatency {
|
if metrics.Current > lm.config.MaxLatency {
|
||||||
needsOptimization = true
|
needsOptimization = true
|
||||||
lm.logger.Warn().Dur("current_latency", metrics.Current).Dur("max_latency", lm.config.MaxLatency).Msg("Latency exceeds maximum threshold")
|
lm.logger.Warn().Dur("current_latency", metrics.Current).Dur("max_latency", lm.config.MaxLatency).Msg("latency exceeds maximum threshold")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if average latency is above adaptive threshold
|
// Check if average latency is above adaptive threshold
|
||||||
adaptiveThreshold := time.Duration(float64(lm.config.TargetLatency.Nanoseconds()) * (1.0 + lm.config.AdaptiveThreshold))
|
adaptiveThreshold := time.Duration(float64(lm.config.TargetLatency.Nanoseconds()) * (1.0 + lm.config.AdaptiveThreshold))
|
||||||
if metrics.Average > adaptiveThreshold {
|
if metrics.Average > adaptiveThreshold {
|
||||||
needsOptimization = true
|
needsOptimization = true
|
||||||
lm.logger.Info().Dur("average_latency", metrics.Average).Dur("threshold", adaptiveThreshold).Msg("Average latency above adaptive threshold")
|
lm.logger.Debug().Dur("average_latency", metrics.Average).Dur("threshold", adaptiveThreshold).Msg("average latency above adaptive threshold")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if jitter is too high
|
// Check if jitter is too high
|
||||||
if metrics.Jitter > lm.config.JitterThreshold {
|
if metrics.Jitter > lm.config.JitterThreshold {
|
||||||
needsOptimization = true
|
needsOptimization = true
|
||||||
lm.logger.Info().Dur("jitter", metrics.Jitter).Dur("threshold", lm.config.JitterThreshold).Msg("Jitter above threshold")
|
lm.logger.Debug().Dur("jitter", metrics.Jitter).Dur("threshold", lm.config.JitterThreshold).Msg("jitter above threshold")
|
||||||
}
|
}
|
||||||
|
|
||||||
if needsOptimization {
|
if needsOptimization {
|
||||||
|
@ -290,11 +290,11 @@ func (lm *LatencyMonitor) runOptimization() {
|
||||||
|
|
||||||
for _, callback := range callbacks {
|
for _, callback := range callbacks {
|
||||||
if err := callback(metrics); err != nil {
|
if err := callback(metrics); err != nil {
|
||||||
lm.logger.Error().Err(err).Msg("Optimization callback failed")
|
lm.logger.Error().Err(err).Msg("optimization callback failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lm.logger.Info().Interface("metrics", metrics).Msg("Latency optimization triggered")
|
lm.logger.Debug().Interface("metrics", metrics).Msg("latency optimization triggered")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,10 @@ func HandleMemoryMetrics(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
if err := json.NewEncoder(w).Encode(metrics); err != nil {
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
|
||||||
|
if err := encoder.Encode(metrics); err != nil {
|
||||||
logger.Error().Err(err).Msg("failed to encode memory metrics")
|
logger.Error().Err(err).Msg("failed to encode memory metrics")
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -185,7 +188,7 @@ func LogMemoryMetrics() {
|
||||||
// StartMemoryMetricsLogging starts periodic memory metrics logging
|
// StartMemoryMetricsLogging starts periodic memory metrics logging
|
||||||
func StartMemoryMetricsLogging(interval time.Duration) {
|
func StartMemoryMetricsLogging(interval time.Duration) {
|
||||||
logger := getMemoryMetricsLogger()
|
logger := getMemoryMetricsLogger()
|
||||||
logger.Info().Dur("interval", interval).Msg("starting memory metrics logging")
|
logger.Debug().Dur("interval", interval).Msg("memory metrics logging started")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package audio
|
package audio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -286,6 +287,141 @@ var (
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Device health metrics
|
||||||
|
deviceHealthStatus = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_device_health_status",
|
||||||
|
Help: "Current device health status (0=Healthy, 1=Degraded, 2=Failing, 3=Critical)",
|
||||||
|
},
|
||||||
|
[]string{"device_type"}, // device_type: capture, playback
|
||||||
|
)
|
||||||
|
|
||||||
|
deviceHealthScore = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_device_health_score",
|
||||||
|
Help: "Device health score (0.0-1.0, higher is better)",
|
||||||
|
},
|
||||||
|
[]string{"device_type"}, // device_type: capture, playback
|
||||||
|
)
|
||||||
|
|
||||||
|
deviceConsecutiveErrors = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_device_consecutive_errors",
|
||||||
|
Help: "Number of consecutive errors for device",
|
||||||
|
},
|
||||||
|
[]string{"device_type"}, // device_type: capture, playback
|
||||||
|
)
|
||||||
|
|
||||||
|
deviceTotalErrors = promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "jetkvm_audio_device_total_errors",
|
||||||
|
Help: "Total number of errors for device",
|
||||||
|
},
|
||||||
|
[]string{"device_type"}, // device_type: capture, playback
|
||||||
|
)
|
||||||
|
|
||||||
|
deviceLatencySpikes = promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "jetkvm_audio_device_latency_spikes_total",
|
||||||
|
Help: "Total number of latency spikes for device",
|
||||||
|
},
|
||||||
|
[]string{"device_type"}, // device_type: capture, playback
|
||||||
|
)
|
||||||
|
|
||||||
|
// Memory metrics
|
||||||
|
memoryHeapAllocBytes = promauto.NewGauge(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_memory_heap_alloc_bytes",
|
||||||
|
Help: "Current heap allocation in bytes",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
memoryHeapSysBytes = promauto.NewGauge(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_memory_heap_sys_bytes",
|
||||||
|
Help: "Total heap system memory in bytes",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
memoryHeapObjects = promauto.NewGauge(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_memory_heap_objects",
|
||||||
|
Help: "Number of heap objects",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
memoryGCCount = promauto.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "jetkvm_audio_memory_gc_total",
|
||||||
|
Help: "Total number of garbage collections",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
memoryGCCPUFraction = promauto.NewGauge(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_memory_gc_cpu_fraction",
|
||||||
|
Help: "Fraction of CPU time spent in garbage collection",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Buffer pool efficiency metrics
|
||||||
|
bufferPoolHitRate = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_buffer_pool_hit_rate_percent",
|
||||||
|
Help: "Buffer pool hit rate percentage",
|
||||||
|
},
|
||||||
|
[]string{"pool_name"}, // pool_name: frame_pool, control_pool, zero_copy_pool
|
||||||
|
)
|
||||||
|
|
||||||
|
bufferPoolMissRate = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_buffer_pool_miss_rate_percent",
|
||||||
|
Help: "Buffer pool miss rate percentage",
|
||||||
|
},
|
||||||
|
[]string{"pool_name"}, // pool_name: frame_pool, control_pool, zero_copy_pool
|
||||||
|
)
|
||||||
|
|
||||||
|
bufferPoolUtilization = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_buffer_pool_utilization_percent",
|
||||||
|
Help: "Buffer pool utilization percentage",
|
||||||
|
},
|
||||||
|
[]string{"pool_name"}, // pool_name: frame_pool, control_pool, zero_copy_pool
|
||||||
|
)
|
||||||
|
|
||||||
|
bufferPoolThroughput = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_buffer_pool_throughput_ops_per_sec",
|
||||||
|
Help: "Buffer pool throughput in operations per second",
|
||||||
|
},
|
||||||
|
[]string{"pool_name"}, // pool_name: frame_pool, control_pool, zero_copy_pool
|
||||||
|
)
|
||||||
|
|
||||||
|
bufferPoolGetLatency = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_buffer_pool_get_latency_seconds",
|
||||||
|
Help: "Average buffer pool get operation latency in seconds",
|
||||||
|
},
|
||||||
|
[]string{"pool_name"}, // pool_name: frame_pool, control_pool, zero_copy_pool
|
||||||
|
)
|
||||||
|
|
||||||
|
bufferPoolPutLatency = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_buffer_pool_put_latency_seconds",
|
||||||
|
Help: "Average buffer pool put operation latency in seconds",
|
||||||
|
},
|
||||||
|
[]string{"pool_name"}, // pool_name: frame_pool, control_pool, zero_copy_pool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Latency percentile metrics
|
||||||
|
latencyPercentile = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "jetkvm_audio_latency_percentile_seconds",
|
||||||
|
Help: "Audio latency percentiles in seconds",
|
||||||
|
},
|
||||||
|
[]string{"source", "percentile"}, // source: input, output, processing; percentile: p50, p95, p99, min, max, avg
|
||||||
|
)
|
||||||
|
|
||||||
// Metrics update tracking
|
// Metrics update tracking
|
||||||
metricsUpdateMutex sync.RWMutex
|
metricsUpdateMutex sync.RWMutex
|
||||||
lastMetricsUpdate int64
|
lastMetricsUpdate int64
|
||||||
|
@ -299,6 +435,15 @@ var (
|
||||||
micFramesDroppedValue int64
|
micFramesDroppedValue int64
|
||||||
micBytesProcessedValue int64
|
micBytesProcessedValue int64
|
||||||
micConnectionDropsValue int64
|
micConnectionDropsValue int64
|
||||||
|
|
||||||
|
// Atomic counters for device health metrics
|
||||||
|
deviceCaptureErrorsValue int64
|
||||||
|
devicePlaybackErrorsValue int64
|
||||||
|
deviceCaptureSpikesValue int64
|
||||||
|
devicePlaybackSpikesValue int64
|
||||||
|
|
||||||
|
// Atomic counter for memory GC
|
||||||
|
memoryGCCountValue uint32
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnifiedAudioMetrics provides a common structure for both input and output audio streams
|
// UnifiedAudioMetrics provides a common structure for both input and output audio streams
|
||||||
|
@ -479,6 +624,95 @@ func UpdateAdaptiveBufferMetrics(inputBufferSize, outputBufferSize int, cpuPerce
|
||||||
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateSocketBufferMetrics updates socket buffer metrics
|
||||||
|
func UpdateSocketBufferMetrics(component, bufferType string, size, utilization float64, overflowOccurred bool) {
|
||||||
|
metricsUpdateMutex.Lock()
|
||||||
|
defer metricsUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
socketBufferSizeGauge.WithLabelValues(component, bufferType).Set(size)
|
||||||
|
socketBufferUtilizationGauge.WithLabelValues(component, bufferType).Set(utilization)
|
||||||
|
|
||||||
|
if overflowOccurred {
|
||||||
|
socketBufferOverflowCounter.WithLabelValues(component, bufferType).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDeviceHealthMetrics updates device health metrics
|
||||||
|
func UpdateDeviceHealthMetrics(deviceType string, status int, healthScore float64, consecutiveErrors, totalErrors, latencySpikes int64) {
|
||||||
|
metricsUpdateMutex.Lock()
|
||||||
|
defer metricsUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
deviceHealthStatus.WithLabelValues(deviceType).Set(float64(status))
|
||||||
|
deviceHealthScore.WithLabelValues(deviceType).Set(healthScore)
|
||||||
|
deviceConsecutiveErrors.WithLabelValues(deviceType).Set(float64(consecutiveErrors))
|
||||||
|
|
||||||
|
// Update error counters with delta calculation
|
||||||
|
var prevErrors, prevSpikes int64
|
||||||
|
if deviceType == "capture" {
|
||||||
|
prevErrors = atomic.SwapInt64(&deviceCaptureErrorsValue, totalErrors)
|
||||||
|
prevSpikes = atomic.SwapInt64(&deviceCaptureSpikesValue, latencySpikes)
|
||||||
|
} else {
|
||||||
|
prevErrors = atomic.SwapInt64(&devicePlaybackErrorsValue, totalErrors)
|
||||||
|
prevSpikes = atomic.SwapInt64(&devicePlaybackSpikesValue, latencySpikes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevErrors > 0 && totalErrors > prevErrors {
|
||||||
|
deviceTotalErrors.WithLabelValues(deviceType).Add(float64(totalErrors - prevErrors))
|
||||||
|
}
|
||||||
|
if prevSpikes > 0 && latencySpikes > prevSpikes {
|
||||||
|
deviceLatencySpikes.WithLabelValues(deviceType).Add(float64(latencySpikes - prevSpikes))
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMemoryMetrics updates memory metrics
|
||||||
|
func UpdateMemoryMetrics() {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
|
memoryHeapAllocBytes.Set(float64(m.HeapAlloc))
|
||||||
|
memoryHeapSysBytes.Set(float64(m.HeapSys))
|
||||||
|
memoryHeapObjects.Set(float64(m.HeapObjects))
|
||||||
|
memoryGCCPUFraction.Set(m.GCCPUFraction)
|
||||||
|
|
||||||
|
// Update GC count with delta calculation
|
||||||
|
currentGCCount := uint32(m.NumGC)
|
||||||
|
prevGCCount := atomic.SwapUint32(&memoryGCCountValue, currentGCCount)
|
||||||
|
if prevGCCount > 0 && currentGCCount > prevGCCount {
|
||||||
|
memoryGCCount.Add(float64(currentGCCount - prevGCCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBufferPoolMetrics updates buffer pool efficiency metrics
|
||||||
|
func UpdateBufferPoolMetrics(poolName string, hitRate, missRate, utilization, throughput, getLatency, putLatency float64) {
|
||||||
|
metricsUpdateMutex.Lock()
|
||||||
|
defer metricsUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
bufferPoolHitRate.WithLabelValues(poolName).Set(hitRate * 100)
|
||||||
|
bufferPoolMissRate.WithLabelValues(poolName).Set(missRate * 100)
|
||||||
|
bufferPoolUtilization.WithLabelValues(poolName).Set(utilization * 100)
|
||||||
|
bufferPoolThroughput.WithLabelValues(poolName).Set(throughput)
|
||||||
|
bufferPoolGetLatency.WithLabelValues(poolName).Set(getLatency)
|
||||||
|
bufferPoolPutLatency.WithLabelValues(poolName).Set(putLatency)
|
||||||
|
|
||||||
|
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLatencyMetrics updates latency percentile metrics
|
||||||
|
func UpdateLatencyMetrics(source, percentile string, latencySeconds float64) {
|
||||||
|
metricsUpdateMutex.Lock()
|
||||||
|
defer metricsUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
latencyPercentile.WithLabelValues(source, percentile).Set(latencySeconds)
|
||||||
|
|
||||||
|
atomic.StoreInt64(&lastMetricsUpdate, time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
// GetLastMetricsUpdate returns the timestamp of the last metrics update
|
// GetLastMetricsUpdate returns the timestamp of the last metrics update
|
||||||
func GetLastMetricsUpdate() time.Time {
|
func GetLastMetricsUpdate() time.Time {
|
||||||
timestamp := atomic.LoadInt64(&lastMetricsUpdate)
|
timestamp := atomic.LoadInt64(&lastMetricsUpdate)
|
||||||
|
@ -488,7 +722,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(GetConfig().StatsUpdateInterval) // Update every 5 seconds
|
ticker := time.NewTicker(5 * time.Second) // Update every 5 seconds
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
|
@ -500,18 +734,14 @@ func StartMetricsUpdater() {
|
||||||
micMetrics := GetAudioInputMetrics()
|
micMetrics := GetAudioInputMetrics()
|
||||||
UpdateMicrophoneMetrics(convertAudioInputMetricsToUnified(micMetrics))
|
UpdateMicrophoneMetrics(convertAudioInputMetricsToUnified(micMetrics))
|
||||||
|
|
||||||
// Update microphone subprocess process metrics
|
|
||||||
if inputSupervisor := GetAudioInputIPCSupervisor(); inputSupervisor != nil {
|
|
||||||
if processMetrics := inputSupervisor.GetProcessMetrics(); processMetrics != nil {
|
|
||||||
UpdateMicrophoneProcessMetrics(*processMetrics, inputSupervisor.IsRunning())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update audio configuration metrics
|
// Update audio configuration metrics
|
||||||
audioConfig := GetAudioConfig()
|
audioConfig := GetAudioConfig()
|
||||||
UpdateAudioConfigMetrics(audioConfig)
|
UpdateAudioConfigMetrics(audioConfig)
|
||||||
micConfig := GetMicrophoneConfig()
|
micConfig := GetMicrophoneConfig()
|
||||||
UpdateMicrophoneConfigMetrics(micConfig)
|
UpdateMicrophoneConfigMetrics(micConfig)
|
||||||
|
|
||||||
|
// Update memory metrics
|
||||||
|
UpdateMemoryMetrics()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
// This should be called from main() when the subprocess is detected
|
// This should be called from main() when the subprocess is detected
|
||||||
func RunAudioOutputServer() error {
|
func RunAudioOutputServer() error {
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger()
|
logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger()
|
||||||
logger.Info().Msg("Starting audio output server subprocess")
|
logger.Debug().Msg("audio output server subprocess starting")
|
||||||
|
|
||||||
// Create audio server
|
// Create audio server
|
||||||
server, err := NewAudioOutputServer()
|
server, err := NewAudioOutputServer()
|
||||||
|
@ -42,7 +42,7 @@ func RunAudioOutputServer() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info().Msg("Audio output server started, waiting for connections")
|
logger.Debug().Msg("audio output server started, waiting for connections")
|
||||||
|
|
||||||
// Set up signal handling for graceful shutdown
|
// Set up signal handling for graceful shutdown
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
@ -54,18 +54,18 @@ func RunAudioOutputServer() error {
|
||||||
// Wait for shutdown signal
|
// Wait for shutdown signal
|
||||||
select {
|
select {
|
||||||
case sig := <-sigChan:
|
case sig := <-sigChan:
|
||||||
logger.Info().Str("signal", sig.String()).Msg("Received shutdown signal")
|
logger.Info().Str("signal", sig.String()).Msg("received shutdown signal")
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logger.Info().Msg("Context cancelled")
|
logger.Debug().Msg("context cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
logger.Info().Msg("Shutting down audio output server")
|
logger.Debug().Msg("shutting down audio output server")
|
||||||
StopNonBlockingAudioStreaming()
|
StopNonBlockingAudioStreaming()
|
||||||
|
|
||||||
// Give some time for cleanup
|
// Give some time for cleanup
|
||||||
time.Sleep(GetConfig().DefaultSleepDuration)
|
time.Sleep(GetConfig().DefaultSleepDuration)
|
||||||
|
|
||||||
logger.Info().Msg("Audio output server subprocess stopped")
|
logger.Debug().Msg("audio output server subprocess stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,13 +69,13 @@ func (ps *PriorityScheduler) SetThreadPriority(priority int, policy int) error {
|
||||||
// If we can't set real-time priority, try nice value instead
|
// If we can't set real-time priority, try nice value instead
|
||||||
schedNormal, _, _ := getSchedulingPolicies()
|
schedNormal, _, _ := getSchedulingPolicies()
|
||||||
if policy != schedNormal {
|
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)
|
||||||
}
|
}
|
||||||
return errno
|
return errno
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.logger.Debug().Int("tid", tid).Int("priority", priority).Int("policy", policy).Msg("Thread priority set")
|
ps.logger.Debug().Int("tid", tid).Int("priority", priority).Int("policy", policy).Msg("thread priority set")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,11 +93,11 @@ func (ps *PriorityScheduler) setNicePriority(rtPriority int) error {
|
||||||
|
|
||||||
err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, niceValue)
|
err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, niceValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ps.logger.Warn().Err(err).Int("nice", niceValue).Msg("Failed to set nice priority")
|
ps.logger.Warn().Err(err).Int("nice", niceValue).Msg("failed to set nice priority")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.logger.Debug().Int("nice", niceValue).Msg("Nice priority set as fallback")
|
ps.logger.Debug().Int("nice", niceValue).Msg("nice priority set as fallback")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,13 +132,13 @@ func (ps *PriorityScheduler) ResetPriority() error {
|
||||||
// Disable disables priority scheduling (useful for testing or fallback)
|
// Disable disables priority scheduling (useful for testing or fallback)
|
||||||
func (ps *PriorityScheduler) Disable() {
|
func (ps *PriorityScheduler) Disable() {
|
||||||
ps.enabled = false
|
ps.enabled = false
|
||||||
ps.logger.Info().Msg("Priority scheduling disabled")
|
ps.logger.Debug().Msg("priority scheduling disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable enables priority scheduling
|
// Enable enables priority scheduling
|
||||||
func (ps *PriorityScheduler) Enable() {
|
func (ps *PriorityScheduler) Enable() {
|
||||||
ps.enabled = true
|
ps.enabled = true
|
||||||
ps.logger.Info().Msg("Priority scheduling enabled")
|
ps.logger.Debug().Msg("priority scheduling enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global priority scheduler instance
|
// Global priority scheduler instance
|
||||||
|
|
|
@ -95,7 +95,7 @@ func (pm *ProcessMonitor) Start() {
|
||||||
|
|
||||||
pm.running = true
|
pm.running = true
|
||||||
go pm.monitorLoop()
|
go pm.monitorLoop()
|
||||||
pm.logger.Info().Msg("Process monitor started")
|
pm.logger.Debug().Msg("process monitor started")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops monitoring processes
|
// Stop stops monitoring processes
|
||||||
|
@ -109,7 +109,7 @@ func (pm *ProcessMonitor) Stop() {
|
||||||
|
|
||||||
pm.running = false
|
pm.running = false
|
||||||
close(pm.stopChan)
|
close(pm.stopChan)
|
||||||
pm.logger.Info().Msg("Process monitor stopped")
|
pm.logger.Debug().Msg("process monitor stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddProcess adds a process to monitor
|
// AddProcess adds a process to monitor
|
||||||
|
|
|
@ -140,17 +140,17 @@ func (r *AudioRelay) relayLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-r.ctx.Done():
|
case <-r.ctx.Done():
|
||||||
r.logger.Debug().Msg("Audio relay loop stopping")
|
r.logger.Debug().Msg("audio relay loop stopping")
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
frame, err := r.client.ReceiveFrame()
|
frame, err := r.client.ReceiveFrame()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
consecutiveErrors++
|
consecutiveErrors++
|
||||||
r.logger.Error().Err(err).Int("consecutive_errors", consecutiveErrors).Msg("Error reading frame from audio output server")
|
r.logger.Error().Err(err).Int("consecutive_errors", consecutiveErrors).Msg("error reading frame from audio output server")
|
||||||
r.incrementDropped()
|
r.incrementDropped()
|
||||||
|
|
||||||
if consecutiveErrors >= maxConsecutiveErrors {
|
if consecutiveErrors >= maxConsecutiveErrors {
|
||||||
r.logger.Error().Msgf("Too many consecutive read errors (%d/%d), stopping audio relay", consecutiveErrors, maxConsecutiveErrors)
|
r.logger.Error().Int("consecutive_errors", consecutiveErrors).Int("max_errors", maxConsecutiveErrors).Msg("too many consecutive read errors, stopping audio relay")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(GetConfig().ShortSleepDuration)
|
time.Sleep(GetConfig().ShortSleepDuration)
|
||||||
|
@ -159,7 +159,7 @@ func (r *AudioRelay) relayLoop() {
|
||||||
|
|
||||||
consecutiveErrors = 0
|
consecutiveErrors = 0
|
||||||
if err := r.forwardToWebRTC(frame); err != nil {
|
if err := r.forwardToWebRTC(frame); err != nil {
|
||||||
r.logger.Warn().Err(err).Msg("Failed to forward frame to WebRTC")
|
r.logger.Warn().Err(err).Msg("failed to forward frame to webrtc")
|
||||||
r.incrementDropped()
|
r.incrementDropped()
|
||||||
} else {
|
} else {
|
||||||
r.incrementRelayed()
|
r.incrementRelayed()
|
||||||
|
@ -173,7 +173,7 @@ func (r *AudioRelay) forwardToWebRTC(frame []byte) error {
|
||||||
// Validate frame data before processing
|
// Validate frame data before processing
|
||||||
if err := ValidateFrameData(frame); err != nil {
|
if err := ValidateFrameData(frame); err != nil {
|
||||||
r.incrementDropped()
|
r.incrementDropped()
|
||||||
r.logger.Debug().Err(err).Msg("Invalid frame data in relay")
|
r.logger.Debug().Err(err).Msg("invalid frame data in relay")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -288,10 +288,10 @@ func (s *AudioOutputSupervisor) waitForProcessExit() {
|
||||||
s.processMonitor.RemoveProcess(pid)
|
s.processMonitor.RemoveProcess(pid)
|
||||||
|
|
||||||
if crashed {
|
if crashed {
|
||||||
s.logger.Error().Int("pid", pid).Int("exit_code", exitCode).Msg("audio server process crashed")
|
s.logger.Error().Int("pid", pid).Int("exit_code", exitCode).Msg("audio output server process crashed")
|
||||||
s.recordRestartAttempt()
|
s.recordRestartAttempt()
|
||||||
} else {
|
} else {
|
||||||
s.logger.Info().Int("pid", pid).Msg("audio server process exited gracefully")
|
s.logger.Info().Int("pid", pid).Msg("audio output server process exited gracefully")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.onProcessExit != nil {
|
if s.onProcessExit != nil {
|
||||||
|
@ -310,11 +310,11 @@ func (s *AudioOutputSupervisor) terminateProcess() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info().Int("pid", pid).Msg("terminating audio server process")
|
s.logger.Info().Int("pid", pid).Msg("terminating audio output server process")
|
||||||
|
|
||||||
// Send SIGTERM first
|
// Send SIGTERM first
|
||||||
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||||
s.logger.Warn().Err(err).Int("pid", pid).Msg("failed to send SIGTERM")
|
s.logger.Warn().Err(err).Int("pid", pid).Msg("failed to send SIGTERM to audio output server process")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for graceful shutdown
|
// Wait for graceful shutdown
|
||||||
|
|
Loading…
Reference in New Issue