feat(audio): add pool hit tracking and optimize buffer management

refactor(ui): extract audio config display and status indicator components
refactor(audio): simplify validation and error handling
refactor(config): remove duplicate audio quality constants
perf(buffer): optimize buffer pool allocation and tracking
docs(audio): streamline package documentation
This commit is contained in:
Alex P 2025-08-27 19:37:34 +00:00
parent cb20956445
commit 6355dd87be
12 changed files with 363 additions and 448 deletions

View File

@ -1,82 +1,10 @@
// Package audio provides a comprehensive real-time audio processing system for JetKVM.
// Package audio provides real-time audio processing for JetKVM with low-latency streaming.
//
// # Architecture Overview
// Key components: output/input pipelines with Opus codec, adaptive buffer management,
// zero-copy frame pools, IPC communication, and process supervision.
//
// The audio package implements a multi-component architecture designed for low-latency,
// high-quality audio streaming in embedded ARM environments. The system consists of:
//
// - Audio Output Pipeline: Receives compressed audio frames, decodes via Opus, and
// outputs to ALSA-compatible audio devices
// - Audio Input Pipeline: Captures microphone input, encodes via Opus, and streams
// to connected clients
// - Adaptive Buffer Management: Dynamically adjusts buffer sizes based on system
// load and latency requirements
// - Zero-Copy Frame Pool: Minimizes memory allocations through frame reuse
// - IPC Communication: Unix domain sockets for inter-process communication
// - Process Supervision: Automatic restart and health monitoring of audio subprocesses
//
// # Key Components
//
// ## Buffer Pool System (buffer_pool.go)
// Implements a two-tier buffer pool with separate pools for audio frames and control
// messages. Uses sync.Pool for efficient memory reuse and tracks allocation statistics.
//
// ## Zero-Copy Frame Management (zero_copy.go)
// Provides reference-counted audio frames that can be shared between components
// without copying data. Includes automatic cleanup and pool-based allocation.
//
// ## Adaptive Buffering Algorithm (adaptive_buffer.go)
// Dynamically adjusts buffer sizes based on:
// - System CPU and memory usage
// - Audio latency measurements
// - Frame drop rates
// - Network conditions
//
// The algorithm uses exponential smoothing and configurable thresholds to balance
// latency and stability. Buffer sizes are adjusted in discrete steps to prevent
// oscillation.
//
// ## Latency Monitoring (latency_monitor.go)
// Tracks end-to-end audio latency using high-resolution timestamps. Implements
// adaptive optimization that adjusts system parameters when latency exceeds
// configured thresholds.
//
// ## Process Supervision (supervisor.go)
// Manages audio subprocess lifecycle with automatic restart capabilities.
// Monitors process health and implements exponential backoff for restart attempts.
//
// # Quality Levels
//
// The system supports four quality presets optimized for different use cases:
// - Low: 32kbps output, 16kbps input - minimal bandwidth, voice-optimized
// - Medium: 96kbps output, 64kbps input - balanced quality and bandwidth
// - High: 192kbps output, 128kbps input - high quality for music
// - Ultra: 320kbps output, 256kbps input - maximum quality
//
// # Configuration System
//
// All configuration is centralized in config_constants.go, allowing runtime
// tuning of performance parameters. Key configuration areas include:
// - Opus codec parameters (bitrate, complexity, VBR settings)
// - Buffer sizes and pool configurations
// - Latency thresholds and optimization parameters
// - Process monitoring and restart policies
//
// # Thread Safety
//
// All public APIs are thread-safe. Internal synchronization uses:
// - atomic operations for performance counters
// - sync.RWMutex for configuration updates
// - sync.Pool for buffer management
// - channel-based communication for IPC
//
// # Error Handling
//
// The system implements comprehensive error handling with:
// - Graceful degradation on component failures
// - Automatic retry with exponential backoff
// - Detailed error context for debugging
// - Metrics collection for monitoring
// Supports four quality presets (Low/Medium/High/Ultra) with configurable bitrates.
// All APIs are thread-safe with comprehensive error handling and metrics collection.
//
// # Performance Characteristics
//

View File

@ -33,23 +33,34 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
bufferSize = GetConfig().AudioFramePoolSize
}
// Pre-allocate 20% of max pool size for immediate availability
preallocSize := GetConfig().PreallocPercentage
// Optimize preallocation based on buffer size to reduce memory footprint
var preallocSize int
if bufferSize <= GetConfig().AudioFramePoolSize {
// For frame buffers, use configured percentage
preallocSize = GetConfig().PreallocPercentage
} else {
// For larger buffers, reduce preallocation to save memory
preallocSize = GetConfig().PreallocPercentage / 2
}
// Pre-allocate with exact capacity to avoid slice growth
preallocated := make([]*[]byte, 0, preallocSize)
// Pre-allocate buffers to reduce initial allocation overhead
// Pre-allocate buffers with optimized capacity
for i := 0; i < preallocSize; i++ {
// Use exact buffer size to prevent over-allocation
buf := make([]byte, 0, bufferSize)
preallocated = append(preallocated, &buf)
}
return &AudioBufferPool{
bufferSize: bufferSize,
maxPoolSize: GetConfig().MaxPoolSize, // Limit pool size to prevent excessive memory usage
maxPoolSize: GetConfig().MaxPoolSize,
preallocated: preallocated,
preallocSize: preallocSize,
pool: sync.Pool{
New: func() interface{} {
// Allocate exact size to minimize memory waste
buf := make([]byte, 0, bufferSize)
return &buf
},
@ -59,41 +70,52 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
func (p *AudioBufferPool) Get() []byte {
start := time.Now()
wasHit := false
defer func() {
latency := time.Since(start)
// Record metrics for frame pool (assuming this is the main usage)
if p.bufferSize >= GetConfig().AudioFramePoolSize {
GetGranularMetricsCollector().RecordFramePoolGet(latency, atomic.LoadInt64(&p.hitCount) > 0)
GetGranularMetricsCollector().RecordFramePoolGet(latency, wasHit)
} else {
GetGranularMetricsCollector().RecordControlPoolGet(latency, atomic.LoadInt64(&p.hitCount) > 0)
GetGranularMetricsCollector().RecordControlPoolGet(latency, wasHit)
}
}()
// First try pre-allocated buffers for fastest access
// First try to get from pre-allocated pool for fastest access
p.mutex.Lock()
if len(p.preallocated) > 0 {
buf := p.preallocated[len(p.preallocated)-1]
p.preallocated = p.preallocated[:len(p.preallocated)-1]
lastIdx := len(p.preallocated) - 1
buf := p.preallocated[lastIdx]
p.preallocated = p.preallocated[:lastIdx]
p.mutex.Unlock()
// Update hit counter
atomic.AddInt64(&p.hitCount, 1)
return (*buf)[:0] // Reset length but keep capacity
wasHit = true
// Ensure buffer is properly reset
*buf = (*buf)[:0]
return *buf
}
p.mutex.Unlock()
// Try sync.Pool next
if buf := p.pool.Get(); buf != nil {
bufPtr := buf.(*[]byte)
// Update pool size counter when retrieving from pool
p.mutex.Lock()
if p.currentSize > 0 {
p.currentSize--
}
p.mutex.Unlock()
if poolBuf := p.pool.Get(); poolBuf != nil {
buf := poolBuf.(*[]byte)
// Update hit counter
atomic.AddInt64(&p.hitCount, 1)
return (*bufPtr)[:0] // Reset length but keep capacity
// Ensure buffer is properly reset and check capacity
if cap(*buf) >= p.bufferSize {
wasHit = true
*buf = (*buf)[:0]
return *buf
} else {
// Buffer too small, allocate new one
atomic.AddInt64(&p.missCount, 1)
return make([]byte, 0, p.bufferSize)
}
}
// Last resort: allocate new buffer
// Pool miss - allocate new buffer with exact capacity
atomic.AddInt64(&p.missCount, 1)
return make([]byte, 0, p.bufferSize)
}
@ -110,11 +132,13 @@ func (p *AudioBufferPool) Put(buf []byte) {
}
}()
if cap(buf) < p.bufferSize {
return // Buffer too small, don't pool it
// Validate buffer capacity - reject buffers that are too small or too large
bufCap := cap(buf)
if bufCap < p.bufferSize || bufCap > p.bufferSize*2 {
return // Buffer size mismatch, don't pool it to prevent memory bloat
}
// Reset buffer for reuse
// Reset buffer for reuse - clear any sensitive data
resetBuf := buf[:0]
// First try to return to pre-allocated pool for fastest reuse
@ -127,10 +151,7 @@ func (p *AudioBufferPool) Put(buf []byte) {
p.mutex.Unlock()
// Check sync.Pool size limit to prevent excessive memory usage
p.mutex.RLock()
currentSize := p.currentSize
p.mutex.RUnlock()
currentSize := atomic.LoadInt64(&p.currentSize)
if currentSize >= int64(p.maxPoolSize) {
return // Pool is full, let GC handle this buffer
}
@ -138,10 +159,8 @@ func (p *AudioBufferPool) Put(buf []byte) {
// Return to sync.Pool
p.pool.Put(&resetBuf)
// Update pool size counter
p.mutex.Lock()
p.currentSize++
p.mutex.Unlock()
// Update pool size counter atomically
atomic.AddInt64(&p.currentSize, 1)
}
var (

View File

@ -236,31 +236,9 @@ int jetkvm_audio_init() {
return 0;
}
// jetkvm_audio_read_encode reads one audio frame from ALSA, encodes it with Opus, and handles errors.
//
// This function implements a robust audio capture pipeline with the following features:
// - ALSA PCM capture with automatic device recovery
// - Opus encoding with optimized settings for real-time processing
// - Progressive error recovery with exponential backoff
// - Buffer underrun and device suspension handling
//
// Error Recovery Strategy:
// 1. EPIPE (buffer underrun): Prepare device and retry with progressive delays
// 2. ESTRPIPE (device suspended): Resume device with timeout and fallback to prepare
// 3. Other errors: Log and attempt recovery up to max_recovery_attempts
//
// Performance Optimizations:
// - Stack-allocated PCM buffer to avoid heap allocations
// - Direct memory access for Opus encoding
// - Minimal system calls in the hot path
//
// Parameters:
// opus_buf: Output buffer for encoded Opus data (must be at least max_packet_size bytes)
//
// Returns:
// >0: Number of bytes written to opus_buf
// -1: Initialization error or safety check failure
// -2: Unrecoverable ALSA or Opus error after all retry attempts
// jetkvm_audio_read_encode captures audio from ALSA, encodes with Opus, and handles errors.
// Implements robust error recovery for buffer underruns and device suspension.
// Returns: >0 (bytes written), -1 (init error), -2 (unrecoverable error)
int jetkvm_audio_read_encode(void *opus_buf) {
short pcm_buffer[1920]; // max 2ch*960
unsigned char *out = (unsigned char*)opus_buf;
@ -610,29 +588,59 @@ var (
errInvalidBufferPtr = errors.New("invalid buffer pointer")
)
// Error creation functions with context
// Error creation functions with enhanced context
func newBufferTooSmallError(actual, required int) error {
return fmt.Errorf("buffer too small: got %d bytes, need at least %d bytes", actual, required)
baseErr := fmt.Errorf("buffer too small: got %d bytes, need at least %d bytes", actual, required)
return WrapWithMetadata(baseErr, "cgo_audio", "buffer_validation", map[string]interface{}{
"actual_size": actual,
"required_size": required,
"error_type": "buffer_undersize",
})
}
func newBufferTooLargeError(actual, max int) error {
return fmt.Errorf("buffer too large: got %d bytes, maximum allowed %d bytes", actual, max)
baseErr := fmt.Errorf("buffer too large: got %d bytes, maximum allowed %d bytes", actual, max)
return WrapWithMetadata(baseErr, "cgo_audio", "buffer_validation", map[string]interface{}{
"actual_size": actual,
"max_size": max,
"error_type": "buffer_oversize",
})
}
func newAudioInitError(cErrorCode int) error {
return fmt.Errorf("%w: C error code %d", errAudioInitFailed, cErrorCode)
baseErr := fmt.Errorf("%w: C error code %d", errAudioInitFailed, cErrorCode)
return WrapWithMetadata(baseErr, "cgo_audio", "initialization", map[string]interface{}{
"c_error_code": cErrorCode,
"error_type": "init_failure",
"severity": "critical",
})
}
func newAudioPlaybackInitError(cErrorCode int) error {
return fmt.Errorf("%w: C error code %d", errAudioPlaybackInit, cErrorCode)
baseErr := fmt.Errorf("%w: C error code %d", errAudioPlaybackInit, cErrorCode)
return WrapWithMetadata(baseErr, "cgo_audio", "playback_init", map[string]interface{}{
"c_error_code": cErrorCode,
"error_type": "playback_init_failure",
"severity": "high",
})
}
func newAudioReadEncodeError(cErrorCode int) error {
return fmt.Errorf("%w: C error code %d", errAudioReadEncode, cErrorCode)
baseErr := fmt.Errorf("%w: C error code %d", errAudioReadEncode, cErrorCode)
return WrapWithMetadata(baseErr, "cgo_audio", "read_encode", map[string]interface{}{
"c_error_code": cErrorCode,
"error_type": "read_encode_failure",
"severity": "medium",
})
}
func newAudioDecodeWriteError(cErrorCode int) error {
return fmt.Errorf("%w: C error code %d", errAudioDecodeWrite, cErrorCode)
baseErr := fmt.Errorf("%w: C error code %d", errAudioDecodeWrite, cErrorCode)
return WrapWithMetadata(baseErr, "cgo_audio", "decode_write", map[string]interface{}{
"c_error_code": cErrorCode,
"error_type": "decode_write_failure",
"severity": "medium",
})
}
func cgoAudioInit() error {

View File

@ -1513,105 +1513,27 @@ type AudioConfigConstants struct {
// real-time audio requirements, and extensive testing for optimal performance.
func DefaultAudioConfig() *AudioConfigConstants {
return &AudioConfigConstants{
// Audio Quality Presets - Core audio frame and packet size configuration
// Used in: Throughout audio pipeline for buffer allocation and frame processing
// Impact: Controls memory usage and prevents buffer overruns
// MaxAudioFrameSize defines maximum size for audio frames.
// Used in: Buffer allocation throughout audio pipeline
// Impact: Prevents buffer overruns while accommodating high-quality audio.
// Default 4096 bytes provides safety margin for largest expected frames.
// Audio Quality Presets
MaxAudioFrameSize: 4096,
// Opus Encoding Parameters - Configuration for Opus audio codec
// Used in: Audio encoding/decoding pipeline for quality control
// Impact: Controls audio quality, bandwidth usage, and encoding performance
// OpusBitrate defines target bitrate for Opus encoding.
// Used in: Opus encoder initialization and quality control
// Impact: Higher bitrates improve quality but increase bandwidth usage.
// Default 128kbps provides excellent quality with reasonable bandwidth.
OpusBitrate: 128000,
// OpusComplexity defines computational complexity for Opus encoding.
// Used in: Opus encoder for quality vs CPU usage balance
// Impact: Higher complexity improves quality but increases CPU usage.
// Default 10 (maximum) ensures best quality on modern ARM processors.
OpusComplexity: 10,
// OpusVBR enables variable bitrate encoding.
// Used in: Opus encoder for adaptive bitrate control
// Impact: Optimizes bandwidth based on audio content complexity.
// Default 1 (enabled) reduces bandwidth for simple audio content.
OpusVBR: 1,
// OpusVBRConstraint controls VBR constraint mode.
// Used in: Opus encoder for bitrate variation control
// Impact: 0=unconstrained allows maximum flexibility for quality.
// Default 0 provides optimal quality-bandwidth balance.
// Opus Encoding Parameters
OpusBitrate: 128000,
OpusComplexity: 10,
OpusVBR: 1,
OpusVBRConstraint: 0,
OpusDTX: 0,
// OpusDTX controls discontinuous transmission.
// Used in: Opus encoder for silence detection and transmission
// Impact: Can interfere with system audio monitoring in KVM applications.
// Default 0 (disabled) ensures consistent audio stream.
OpusDTX: 0,
// Audio Parameters - Core audio format configuration
// Used in: Audio processing pipeline for format consistency
// Impact: Controls audio quality, compatibility, and processing requirements
// SampleRate defines audio sampling frequency.
// Used in: Audio capture, processing, and playback throughout pipeline
// Impact: Higher rates improve quality but increase processing and bandwidth.
// Default 48kHz provides professional audio quality with full frequency range.
SampleRate: 48000,
// Channels defines number of audio channels.
// Used in: Audio processing pipeline for channel handling
// Impact: Stereo preserves spatial information but doubles bandwidth.
// Default 2 (stereo) captures full system audio including spatial effects.
Channels: 2,
// FrameSize defines number of samples per audio frame.
// Used in: Audio processing for frame-based operations
// Impact: Larger frames improve efficiency but increase latency.
// Default 960 samples (20ms at 48kHz) balances latency and efficiency.
FrameSize: 960,
// MaxPacketSize defines maximum size for audio packets.
// Used in: Network transmission and buffer allocation
// Impact: Must accommodate compressed frames with overhead.
// Default 4000 bytes prevents fragmentation while allowing quality variations.
// Audio Parameters
SampleRate: 48000,
Channels: 2,
FrameSize: 960,
MaxPacketSize: 4000,
// Audio Quality Bitrates - Preset bitrates for different quality levels
// Used in: Audio quality management and adaptive bitrate control
// Impact: Controls bandwidth usage and audio quality for different scenarios
// AudioQualityLowOutputBitrate defines bitrate for low-quality output audio.
// Used in: Bandwidth-constrained connections and basic audio monitoring
// Impact: Minimizes bandwidth while maintaining acceptable quality.
// Default 32kbps optimized for constrained connections, higher than input.
AudioQualityLowOutputBitrate: 32,
// AudioQualityLowInputBitrate defines bitrate for low-quality input audio.
// Used in: Microphone input in bandwidth-constrained scenarios
// Impact: Reduces bandwidth for microphone audio which is typically simpler.
// Default 16kbps sufficient for basic voice input.
AudioQualityLowInputBitrate: 16,
// AudioQualityMediumOutputBitrate defines bitrate for medium-quality output.
// Used in: Typical KVM scenarios with reasonable network connections
// Impact: Balances bandwidth and quality for most use cases.
// Default 64kbps provides good quality for standard usage.
// Audio Quality Bitrates
AudioQualityLowOutputBitrate: 32,
AudioQualityLowInputBitrate: 16,
AudioQualityMediumOutputBitrate: 64,
// AudioQualityMediumInputBitrate defines bitrate for medium-quality input.
// Used in: Standard microphone input scenarios
// Impact: Provides good voice quality without excessive bandwidth.
// Default 32kbps suitable for clear voice communication.
AudioQualityMediumInputBitrate: 32,
AudioQualityMediumInputBitrate: 32,
// AudioQualityHighOutputBitrate defines bitrate for high-quality output.
// Used in: Professional applications requiring excellent audio fidelity
@ -1689,106 +1611,25 @@ func DefaultAudioConfig() *AudioConfigConstants {
// Audio Quality Channels - Channel configuration for different quality levels
// Used in: Audio processing pipeline for channel handling and bandwidth control
// Impact: Controls spatial audio information and bandwidth requirements
// AudioQualityLowChannels defines channel count for low-quality audio.
// Used in: Basic audio monitoring in bandwidth-constrained scenarios
// Impact: Reduces bandwidth by 50% with acceptable quality trade-off.
// Default 1 (mono) suitable where stereo separation not critical.
AudioQualityLowChannels: 1,
// AudioQualityMediumChannels defines channel count for medium-quality audio.
// Used in: Standard audio scenarios requiring spatial information
// Impact: Preserves spatial audio information essential for modern systems.
// Default 2 (stereo) maintains spatial audio for medium quality.
AudioQualityLowChannels: 1,
AudioQualityMediumChannels: 2,
AudioQualityHighChannels: 2,
AudioQualityUltraChannels: 2,
// AudioQualityHighChannels defines channel count for high-quality audio.
// Used in: High-quality audio scenarios requiring full spatial reproduction
// Impact: Ensures complete spatial audio information for quality scenarios.
// Default 2 (stereo) preserves spatial information for high quality.
AudioQualityHighChannels: 2,
// AudioQualityUltraChannels defines channel count for ultra-quality audio.
// Used in: Ultra-quality scenarios requiring maximum spatial fidelity
// Impact: Provides complete spatial audio reproduction for audiophile use.
// Default 2 (stereo) ensures maximum spatial fidelity for ultra quality.
AudioQualityUltraChannels: 2,
// CGO Audio Constants - Configuration for C interop audio processing
// Used in: CGO audio operations and C library compatibility
// Impact: Controls quality, performance, and compatibility for C-side processing
// CGOOpusBitrate defines bitrate for CGO Opus operations.
// Used in: CGO audio encoding with embedded processing constraints
// Impact: Conservative bitrate reduces processing load while maintaining quality.
// Default 96kbps provides good quality suitable for embedded processing.
CGOOpusBitrate: 96000,
// CGOOpusComplexity defines complexity for CGO Opus operations.
// Used in: CGO audio encoding for CPU load management
// Impact: Lower complexity reduces CPU load while maintaining acceptable quality.
// Default 3 balances quality and real-time processing requirements.
CGOOpusComplexity: 3,
// CGOOpusVBR enables variable bitrate for CGO operations.
// Used in: CGO audio encoding for adaptive bandwidth optimization
// Impact: Allows bitrate adaptation based on content complexity.
// Default 1 (enabled) optimizes bandwidth usage in CGO processing.
CGOOpusVBR: 1,
// CGOOpusVBRConstraint controls VBR constraint for CGO operations.
// Used in: CGO audio encoding for predictable processing load
// Impact: Limits bitrate variations for more predictable embedded performance.
// Default 1 (constrained) ensures predictable processing in embedded environment.
// CGO Audio Constants
CGOOpusBitrate: 96000,
CGOOpusComplexity: 3,
CGOOpusVBR: 1,
CGOOpusVBRConstraint: 1,
CGOOpusSignalType: 3, // OPUS_SIGNAL_MUSIC
CGOOpusBandwidth: 1105, // OPUS_BANDWIDTH_FULLBAND
CGOOpusDTX: 0,
CGOSampleRate: 48000,
CGOChannels: 2,
CGOFrameSize: 960,
CGOMaxPacketSize: 1500,
// CGOOpusSignalType defines signal type for CGO Opus operations.
// Used in: CGO audio encoding for content-optimized processing
// Impact: Optimizes encoding for general audio content types.
// Default 3 (OPUS_SIGNAL_MUSIC) handles system sounds, music, and mixed audio.
CGOOpusSignalType: 3, // OPUS_SIGNAL_MUSIC
// CGOOpusBandwidth defines bandwidth for CGO Opus operations.
// Used in: CGO audio encoding for frequency range control
// Impact: Enables full audio spectrum reproduction up to 20kHz.
// Default 1105 (OPUS_BANDWIDTH_FULLBAND) provides complete spectrum coverage.
CGOOpusBandwidth: 1105, // OPUS_BANDWIDTH_FULLBAND
// CGOOpusDTX controls discontinuous transmission for CGO operations.
// Used in: CGO audio encoding for silence detection control
// Impact: Prevents silence detection interference with system audio monitoring.
// Default 0 (disabled) ensures consistent audio stream.
CGOOpusDTX: 0,
// CGOSampleRate defines sample rate for CGO audio operations.
// Used in: CGO audio processing for format consistency
// Impact: Matches main audio parameters for pipeline consistency.
// Default 48kHz provides professional audio quality and consistency.
CGOSampleRate: 48000,
// CGOChannels defines channel count for CGO audio operations.
// Used in: CGO audio processing for spatial audio handling
// Impact: Maintains spatial audio information throughout CGO pipeline.
// Default 2 (stereo) preserves spatial information in CGO processing.
CGOChannels: 2,
// CGOFrameSize defines frame size for CGO audio operations.
// Used in: CGO audio processing for timing consistency
// Impact: Matches main frame size for consistent timing and efficiency.
// Default 960 samples (20ms at 48kHz) ensures consistent processing timing.
CGOFrameSize: 960,
// CGOMaxPacketSize defines maximum packet size for CGO operations.
// Used in: CGO audio transmission and buffer allocation
// Impact: Accommodates Ethernet MTU while providing sufficient packet space.
// Default 1500 bytes fits Ethernet MTU constraints with compressed audio.
CGOMaxPacketSize: 1500,
// Input IPC Constants - Configuration for microphone input IPC
// Used in: Microphone input processing and IPC communication
// Impact: Controls quality and compatibility for input audio processing
// Input IPC Constants
// InputIPCSampleRate defines sample rate for input IPC operations.
// Used in: Microphone input capture and processing
// Impact: Ensures high-quality input matching system audio output.

View File

@ -135,6 +135,9 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
mp.preallocated = mp.preallocated[:len(mp.preallocated)-1]
mp.mutex.Unlock()
atomic.AddInt64(&mp.hitCount, 1)
// Reset message for reuse
msg.data = msg.data[:0]
msg.msg = InputIPCMessage{}
return msg
}
mp.mutex.Unlock()
@ -143,9 +146,16 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
select {
case msg := <-mp.pool:
atomic.AddInt64(&mp.hitCount, 1)
// Reset message for reuse and ensure proper capacity
msg.data = msg.data[:0]
msg.msg = InputIPCMessage{}
// Ensure data buffer has sufficient capacity
if cap(msg.data) < maxFrameSize {
msg.data = make([]byte, 0, maxFrameSize)
}
return msg
default:
// Pool exhausted, create new message
// Pool exhausted, create new message with exact capacity
atomic.AddInt64(&mp.missCount, 1)
return &OptimizedIPCMessage{
data: make([]byte, 0, maxFrameSize),
@ -155,6 +165,15 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
// Put returns a message to the pool
func (mp *MessagePool) Put(msg *OptimizedIPCMessage) {
if msg == nil {
return
}
// Validate buffer capacity - reject if too small or too large
if cap(msg.data) < maxFrameSize/2 || cap(msg.data) > maxFrameSize*2 {
return // Let GC handle oversized or undersized buffers
}
// Reset the message for reuse
msg.data = msg.data[:0]
msg.msg = InputIPCMessage{}

View File

@ -22,6 +22,7 @@ var (
// ValidateAudioQuality validates audio quality enum values
func ValidateAudioQuality(quality AudioQuality) error {
// Perform validation
switch quality {
case AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra:
return nil
@ -35,7 +36,7 @@ func ValidateFrameData(data []byte) error {
if len(data) == 0 {
return ErrInvalidFrameData
}
// Use config value or fallback to default
// Use config value
maxFrameSize := GetConfig().MaxAudioFrameSize
if len(data) > maxFrameSize {
return ErrInvalidFrameSize
@ -75,14 +76,9 @@ func ValidateBufferSize(size int) error {
// ValidateThreadPriority validates thread priority values
func ValidateThreadPriority(priority int) error {
// Use reasonable defaults if config is not available
minPriority := -20
maxPriority := 99
if config := GetConfig(); config != nil {
minPriority = config.MinNiceValue
maxPriority = config.RTAudioHighPriority
}
if priority < minPriority || priority > maxPriority {
// Use config values
config := GetConfig()
if priority < config.MinNiceValue || priority > config.RTAudioHighPriority {
return ErrInvalidPriority
}
return nil

View File

@ -9,7 +9,7 @@ import (
"github.com/rs/zerolog"
)
// Enhanced validation errors with more specific context
// Validation errors
var (
ErrInvalidFrameLength = errors.New("invalid frame length")
ErrFrameDataCorrupted = errors.New("frame data appears corrupted")
@ -21,6 +21,11 @@ var (
ErrInvalidPointer = errors.New("invalid pointer")
ErrBufferOverflow = errors.New("buffer overflow detected")
ErrInvalidState = errors.New("invalid state")
ErrOperationTimeout = errors.New("operation timeout")
ErrSystemOverload = errors.New("system overload detected")
ErrHardwareFailure = errors.New("hardware failure")
ErrNetworkError = errors.New("network error")
ErrMemoryExhaustion = errors.New("memory exhaustion")
)
// ValidationLevel defines the level of validation to perform
@ -43,13 +48,14 @@ type ValidationConfig struct {
// GetValidationConfig returns the current validation configuration
func GetValidationConfig() ValidationConfig {
configConstants := GetConfig()
// Use direct config access
config := GetConfig()
return ValidationConfig{
Level: ValidationStandard,
EnableRangeChecks: true,
EnableAlignmentCheck: true,
EnableDataIntegrity: false, // Disabled by default for performance
MaxValidationTime: configConstants.MaxValidationTime, // Configurable validation timeout
EnableDataIntegrity: false, // Disabled by default for performance
MaxValidationTime: config.MaxValidationTime, // Configurable validation timeout
}
}
@ -59,10 +65,10 @@ func ValidateAudioFrameFast(data []byte) error {
return ErrInvalidFrameData
}
// Quick bounds check using config constants
maxSize := GetConfig().MaxAudioFrameSize
if len(data) > maxSize {
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), maxSize)
// Quick bounds check using direct config access
config := GetConfig()
if len(data) > config.MaxAudioFrameSize {
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), config.MaxAudioFrameSize)
}
return nil
@ -88,6 +94,7 @@ func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expect
// Range validation
if validationConfig.EnableRangeChecks {
// Use direct config access
config := GetConfig()
if len(data) < config.MinFrameSize {
return fmt.Errorf("%w: frame size %d below minimum %d", ErrInvalidFrameSize, len(data), config.MinFrameSize)
@ -180,15 +187,21 @@ func ValidateAudioConfiguration(config AudioConfig) error {
return fmt.Errorf("quality validation failed: %w", err)
}
// Use direct config access
configConstants := GetConfig()
minOpusBitrate := configConstants.MinOpusBitrate
maxOpusBitrate := configConstants.MaxOpusBitrate
maxChannels := configConstants.MaxChannels
validSampleRates := configConstants.ValidSampleRates
minFrameDuration := configConstants.MinFrameDuration
maxFrameDuration := configConstants.MaxFrameDuration
// Validate bitrate ranges
if config.Bitrate < configConstants.MinOpusBitrate || config.Bitrate > configConstants.MaxOpusBitrate {
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, configConstants.MinOpusBitrate, configConstants.MaxOpusBitrate)
if config.Bitrate < minOpusBitrate || config.Bitrate > maxOpusBitrate {
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, minOpusBitrate, maxOpusBitrate)
}
// Validate sample rate
validSampleRates := configConstants.ValidSampleRates
validSampleRate := false
for _, rate := range validSampleRates {
if config.SampleRate == rate {
@ -201,15 +214,13 @@ func ValidateAudioConfiguration(config AudioConfig) error {
}
// Validate channels
if config.Channels < 1 || config.Channels > configConstants.MaxChannels {
return fmt.Errorf("%w: channels %d outside valid range [1, %d]", ErrInvalidChannels, config.Channels, configConstants.MaxChannels)
if config.Channels < 1 || config.Channels > maxChannels {
return fmt.Errorf("%w: channels %d outside valid range [1, %d]", ErrInvalidChannels, config.Channels, maxChannels)
}
// Validate frame size
minFrameSize := GetConfig().MinFrameDuration
maxFrameSize := GetConfig().MaxFrameDuration
if config.FrameSize < minFrameSize || config.FrameSize > maxFrameSize {
return fmt.Errorf("%w: frame size %v outside valid range [%v, %v]", ErrInvalidConfiguration, config.FrameSize, minFrameSize, maxFrameSize)
if config.FrameSize < minFrameDuration || config.FrameSize > maxFrameDuration {
return fmt.Errorf("%w: frame size %v outside valid range [%v, %v]", ErrInvalidConfiguration, config.FrameSize, minFrameDuration, maxFrameDuration)
}
return nil
@ -380,3 +391,89 @@ func getValidationLogger() *zerolog.Logger {
logger := zerolog.New(nil).With().Timestamp().Logger()
return &logger
}
// ErrorContext provides structured error context for better debugging
type ErrorContext struct {
Component string `json:"component"`
Operation string `json:"operation"`
Timestamp time.Time `json:"timestamp"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
StackTrace []string `json:"stack_trace,omitempty"`
}
// ContextualError wraps an error with additional context
type ContextualError struct {
Err error `json:"error"`
Context ErrorContext `json:"context"`
}
func (ce *ContextualError) Error() string {
return fmt.Sprintf("%s [%s:%s]: %v", ce.Context.Component, ce.Context.Operation, ce.Context.Timestamp.Format(time.RFC3339), ce.Err)
}
func (ce *ContextualError) Unwrap() error {
return ce.Err
}
// NewContextualError creates a new contextual error with metadata
func NewContextualError(err error, component, operation string, metadata map[string]interface{}) *ContextualError {
return &ContextualError{
Err: err,
Context: ErrorContext{
Component: component,
Operation: operation,
Timestamp: time.Now(),
Metadata: metadata,
},
}
}
// WrapWithContext wraps an error with component and operation context
func WrapWithContext(err error, component, operation string) error {
if err == nil {
return nil
}
return NewContextualError(err, component, operation, nil)
}
// WrapWithMetadata wraps an error with additional metadata
func WrapWithMetadata(err error, component, operation string, metadata map[string]interface{}) error {
if err == nil {
return nil
}
return NewContextualError(err, component, operation, metadata)
}
// IsTimeoutError checks if an error is a timeout error
func IsTimeoutError(err error) bool {
return errors.Is(err, ErrOperationTimeout)
}
// IsResourceError checks if an error is related to resource exhaustion
func IsResourceError(err error) bool {
return errors.Is(err, ErrResourceExhaustion) || errors.Is(err, ErrMemoryExhaustion) || errors.Is(err, ErrSystemOverload)
}
// IsHardwareError checks if an error is hardware-related
func IsHardwareError(err error) bool {
return errors.Is(err, ErrHardwareFailure)
}
// IsNetworkError checks if an error is network-related
func IsNetworkError(err error) bool {
return errors.Is(err, ErrNetworkError)
}
// GetErrorSeverity returns the severity level of an error
func GetErrorSeverity(err error) string {
if IsHardwareError(err) {
return "critical"
}
if IsResourceError(err) {
return "high"
}
if IsNetworkError(err) || IsTimeoutError(err) {
return "medium"
}
return "low"
}

View File

@ -192,6 +192,7 @@ func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame {
frame.data = frame.data[:0]
frame.mutex.Unlock()
wasHit = true // Pool hit
atomic.AddInt64(&p.hitCount, 1)
return frame
}

View File

@ -0,0 +1,38 @@
import { cx } from "@/cva.config";
interface AudioConfig {
Quality: number;
Bitrate: number;
SampleRate: number;
Channels: number;
FrameSize: string;
}
interface AudioConfigDisplayProps {
config: AudioConfig;
variant?: 'default' | 'success' | 'info';
className?: string;
}
const variantStyles = {
default: "bg-slate-50 text-slate-600 dark:bg-slate-700 dark:text-slate-400",
success: "bg-green-50 text-green-600 dark:bg-green-900/20 dark:text-green-400",
info: "bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400"
};
export function AudioConfigDisplay({ config, variant = 'default', className }: AudioConfigDisplayProps) {
return (
<div className={cx(
"rounded-md p-2 text-xs",
variantStyles[variant],
className
)}>
<div className="grid grid-cols-2 gap-1">
<span>Sample Rate: {config.SampleRate}Hz</span>
<span>Channels: {config.Channels}</span>
<span>Bitrate: {config.Bitrate}kbps</span>
<span>Frame: {config.FrameSize}</span>
</div>
</div>
);
}

View File

@ -0,0 +1,33 @@
import { cx } from "@/cva.config";
interface AudioMetrics {
frames_dropped: number;
// Add other metrics properties as needed
}
interface AudioStatusIndicatorProps {
metrics?: AudioMetrics;
label: string;
className?: string;
}
export function AudioStatusIndicator({ metrics, label, className }: AudioStatusIndicatorProps) {
const hasIssues = metrics && metrics.frames_dropped > 0;
return (
<div className={cx(
"text-center p-2 bg-slate-50 dark:bg-slate-800 rounded",
className
)}>
<div className={cx(
"font-medium",
hasIssues
? "text-red-600 dark:text-red-400"
: "text-green-600 dark:text-green-400"
)}>
{hasIssues ? "Issues" : "Good"}
</div>
<div className="text-slate-500 dark:text-slate-400">{label}</div>
</div>
);
}

View File

@ -4,6 +4,8 @@ import { LuActivity, LuSignal } from "react-icons/lu";
import { Button } from "@components/Button";
import { AudioLevelMeter } from "@components/AudioLevelMeter";
import { AudioConfigDisplay } from "@components/AudioConfigDisplay";
import { AudioStatusIndicator } from "@components/AudioStatusIndicator";
import { cx } from "@/cva.config";
import { useUiStore } from "@/hooks/stores";
import { useAudioDevices } from "@/hooks/useAudioDevices";
@ -512,14 +514,10 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
</div>
{currentMicrophoneConfig && (
<div className="rounded-md bg-green-50 p-2 text-xs text-green-600 dark:bg-green-900/20 dark:text-green-400">
<div className="grid grid-cols-2 gap-1">
<span>Sample Rate: {currentMicrophoneConfig.SampleRate}Hz</span>
<span>Channels: {currentMicrophoneConfig.Channels}</span>
<span>Bitrate: {currentMicrophoneConfig.Bitrate}kbps</span>
<span>Frame: {currentMicrophoneConfig.FrameSize}</span>
</div>
</div>
<AudioConfigDisplay
config={currentMicrophoneConfig}
variant="success"
/>
)}
</div>
)}
@ -553,14 +551,10 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
</div>
{currentConfig && (
<div className="rounded-md bg-slate-50 p-2 text-xs text-slate-600 dark:bg-slate-700 dark:text-slate-400">
<div className="grid grid-cols-2 gap-1">
<span>Sample Rate: {currentConfig.SampleRate}Hz</span>
<span>Channels: {currentConfig.Channels}</span>
<span>Bitrate: {currentConfig.Bitrate}kbps</span>
<span>Frame: {currentConfig.FrameSize}</span>
</div>
</div>
<AudioConfigDisplay
config={currentConfig}
variant="default"
/>
)}
</div>
@ -575,30 +569,16 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
{metrics ? (
<div className="grid grid-cols-2 gap-3 text-xs">
<div className="text-center p-2 bg-slate-50 dark:bg-slate-800 rounded">
<div className={cx(
"font-medium",
metrics.frames_dropped > 0
? "text-red-600 dark:text-red-400"
: "text-green-600 dark:text-green-400"
)}>
{metrics.frames_dropped > 0 ? "Issues" : "Good"}
</div>
<div className="text-slate-500 dark:text-slate-400">Audio Output</div>
</div>
<AudioStatusIndicator
metrics={metrics}
label="Audio Output"
/>
{micMetrics && (
<div className="text-center p-2 bg-slate-50 dark:bg-slate-800 rounded">
<div className={cx(
"font-medium",
micMetrics.frames_dropped > 0
? "text-red-600 dark:text-red-400"
: "text-green-600 dark:text-green-400"
)}>
{micMetrics.frames_dropped > 0 ? "Issues" : "Good"}
</div>
<div className="text-slate-500 dark:text-slate-400">Microphone</div>
</div>
<AudioStatusIndicator
metrics={micMetrics}
label="Microphone"
/>
)}
</div>
) : (

View File

@ -89,62 +89,17 @@ export const AUDIO_CONFIG = {
SYNC_DEBOUNCE_MS: 1000, // debounce state synchronization
AUDIO_TEST_TIMEOUT: 100, // ms - timeout for audio testing
// Audio Output Quality Bitrates (matching backend config_constants.go)
OUTPUT_QUALITY_BITRATES: {
LOW: 32, // AudioQualityLowOutputBitrate
MEDIUM: 64, // AudioQualityMediumOutputBitrate
HIGH: 128, // AudioQualityHighOutputBitrate
ULTRA: 192, // AudioQualityUltraOutputBitrate
// NOTE: Audio quality presets (bitrates, sample rates, channels, frame sizes)
// are now fetched dynamically from the backend API via audioQualityService
// to eliminate duplication with backend config_constants.go
// Default Quality Labels - will be updated dynamically by audioQualityService
DEFAULT_QUALITY_LABELS: {
0: "Low",
1: "Medium",
2: "High",
3: "Ultra",
} as const,
// Audio Input Quality Bitrates (matching backend config_constants.go)
INPUT_QUALITY_BITRATES: {
LOW: 16, // AudioQualityLowInputBitrate
MEDIUM: 32, // AudioQualityMediumInputBitrate
HIGH: 64, // AudioQualityHighInputBitrate
ULTRA: 96, // AudioQualityUltraInputBitrate
} as const,
// Sample Rates (matching backend config_constants.go)
QUALITY_SAMPLE_RATES: {
LOW: 22050, // AudioQualityLowSampleRate
MEDIUM: 44100, // AudioQualityMediumSampleRate
HIGH: 48000, // Default SampleRate
ULTRA: 48000, // Default SampleRate
} as const,
// Microphone Sample Rates
MIC_QUALITY_SAMPLE_RATES: {
LOW: 16000, // AudioQualityMicLowSampleRate
MEDIUM: 44100, // AudioQualityMediumSampleRate
HIGH: 48000, // Default SampleRate
ULTRA: 48000, // Default SampleRate
} as const,
// Channels (matching backend config_constants.go)
QUALITY_CHANNELS: {
LOW: 1, // AudioQualityLowChannels (mono)
MEDIUM: 2, // AudioQualityMediumChannels (stereo)
HIGH: 2, // AudioQualityHighChannels (stereo)
ULTRA: 2, // AudioQualityUltraChannels (stereo)
} as const,
// Frame Sizes in milliseconds (matching backend config_constants.go)
QUALITY_FRAME_SIZES: {
LOW: 40, // AudioQualityLowFrameSize (40ms)
MEDIUM: 20, // AudioQualityMediumFrameSize (20ms)
HIGH: 20, // AudioQualityHighFrameSize (20ms)
ULTRA: 10, // AudioQualityUltraFrameSize (10ms)
} as const,
// Updated Quality Labels with correct output bitrates
QUALITY_LABELS: {
0: "Low (32 kbps)",
1: "Medium (64 kbps)",
2: "High (128 kbps)",
3: "Ultra (192 kbps)",
} as const,
// Legacy support - keeping for backward compatibility
QUALITY_BITRATES: {
LOW: 32,
MEDIUM: 64,
HIGH: 128,
ULTRA: 192, // Updated to match backend
},
// Audio Analysis
ANALYSIS_FFT_SIZE: 256, // for detailed audio analysis