mirror of https://github.com/jetkvm/kvm.git
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:
parent
cb20956445
commit
6355dd87be
|
@ -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,
|
// Supports four quality presets (Low/Medium/High/Ultra) with configurable bitrates.
|
||||||
// high-quality audio streaming in embedded ARM environments. The system consists of:
|
// All APIs are thread-safe with comprehensive error handling and metrics collection.
|
||||||
//
|
|
||||||
// - 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
|
|
||||||
//
|
//
|
||||||
// # Performance Characteristics
|
// # Performance Characteristics
|
||||||
//
|
//
|
||||||
|
|
|
@ -33,23 +33,34 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
|
||||||
bufferSize = GetConfig().AudioFramePoolSize
|
bufferSize = GetConfig().AudioFramePoolSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-allocate 20% of max pool size for immediate availability
|
// Optimize preallocation based on buffer size to reduce memory footprint
|
||||||
preallocSize := GetConfig().PreallocPercentage
|
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)
|
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++ {
|
for i := 0; i < preallocSize; i++ {
|
||||||
|
// Use exact buffer size to prevent over-allocation
|
||||||
buf := make([]byte, 0, bufferSize)
|
buf := make([]byte, 0, bufferSize)
|
||||||
preallocated = append(preallocated, &buf)
|
preallocated = append(preallocated, &buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AudioBufferPool{
|
return &AudioBufferPool{
|
||||||
bufferSize: bufferSize,
|
bufferSize: bufferSize,
|
||||||
maxPoolSize: GetConfig().MaxPoolSize, // Limit pool size to prevent excessive memory usage
|
maxPoolSize: GetConfig().MaxPoolSize,
|
||||||
preallocated: preallocated,
|
preallocated: preallocated,
|
||||||
preallocSize: preallocSize,
|
preallocSize: preallocSize,
|
||||||
pool: sync.Pool{
|
pool: sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
|
// Allocate exact size to minimize memory waste
|
||||||
buf := make([]byte, 0, bufferSize)
|
buf := make([]byte, 0, bufferSize)
|
||||||
return &buf
|
return &buf
|
||||||
},
|
},
|
||||||
|
@ -59,41 +70,52 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
|
||||||
|
|
||||||
func (p *AudioBufferPool) Get() []byte {
|
func (p *AudioBufferPool) Get() []byte {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
wasHit := false
|
||||||
defer func() {
|
defer func() {
|
||||||
latency := time.Since(start)
|
latency := time.Since(start)
|
||||||
// Record metrics for frame pool (assuming this is the main usage)
|
// Record metrics for frame pool (assuming this is the main usage)
|
||||||
if p.bufferSize >= GetConfig().AudioFramePoolSize {
|
if p.bufferSize >= GetConfig().AudioFramePoolSize {
|
||||||
GetGranularMetricsCollector().RecordFramePoolGet(latency, atomic.LoadInt64(&p.hitCount) > 0)
|
GetGranularMetricsCollector().RecordFramePoolGet(latency, wasHit)
|
||||||
} else {
|
} 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()
|
p.mutex.Lock()
|
||||||
if len(p.preallocated) > 0 {
|
if len(p.preallocated) > 0 {
|
||||||
buf := p.preallocated[len(p.preallocated)-1]
|
lastIdx := len(p.preallocated) - 1
|
||||||
p.preallocated = p.preallocated[:len(p.preallocated)-1]
|
buf := p.preallocated[lastIdx]
|
||||||
|
p.preallocated = p.preallocated[:lastIdx]
|
||||||
p.mutex.Unlock()
|
p.mutex.Unlock()
|
||||||
|
|
||||||
|
// Update hit counter
|
||||||
atomic.AddInt64(&p.hitCount, 1)
|
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()
|
p.mutex.Unlock()
|
||||||
|
|
||||||
// Try sync.Pool next
|
// Try sync.Pool next
|
||||||
if buf := p.pool.Get(); buf != nil {
|
if poolBuf := p.pool.Get(); poolBuf != nil {
|
||||||
bufPtr := buf.(*[]byte)
|
buf := poolBuf.(*[]byte)
|
||||||
// Update pool size counter when retrieving from pool
|
// Update hit counter
|
||||||
p.mutex.Lock()
|
|
||||||
if p.currentSize > 0 {
|
|
||||||
p.currentSize--
|
|
||||||
}
|
|
||||||
p.mutex.Unlock()
|
|
||||||
atomic.AddInt64(&p.hitCount, 1)
|
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)
|
atomic.AddInt64(&p.missCount, 1)
|
||||||
return make([]byte, 0, p.bufferSize)
|
return make([]byte, 0, p.bufferSize)
|
||||||
}
|
}
|
||||||
|
@ -110,11 +132,13 @@ func (p *AudioBufferPool) Put(buf []byte) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if cap(buf) < p.bufferSize {
|
// Validate buffer capacity - reject buffers that are too small or too large
|
||||||
return // Buffer too small, don't pool it
|
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]
|
resetBuf := buf[:0]
|
||||||
|
|
||||||
// First try to return to pre-allocated pool for fastest reuse
|
// First try to return to pre-allocated pool for fastest reuse
|
||||||
|
@ -127,10 +151,7 @@ func (p *AudioBufferPool) Put(buf []byte) {
|
||||||
p.mutex.Unlock()
|
p.mutex.Unlock()
|
||||||
|
|
||||||
// Check sync.Pool size limit to prevent excessive memory usage
|
// Check sync.Pool size limit to prevent excessive memory usage
|
||||||
p.mutex.RLock()
|
currentSize := atomic.LoadInt64(&p.currentSize)
|
||||||
currentSize := p.currentSize
|
|
||||||
p.mutex.RUnlock()
|
|
||||||
|
|
||||||
if currentSize >= int64(p.maxPoolSize) {
|
if currentSize >= int64(p.maxPoolSize) {
|
||||||
return // Pool is full, let GC handle this buffer
|
return // Pool is full, let GC handle this buffer
|
||||||
}
|
}
|
||||||
|
@ -138,10 +159,8 @@ func (p *AudioBufferPool) Put(buf []byte) {
|
||||||
// Return to sync.Pool
|
// Return to sync.Pool
|
||||||
p.pool.Put(&resetBuf)
|
p.pool.Put(&resetBuf)
|
||||||
|
|
||||||
// Update pool size counter
|
// Update pool size counter atomically
|
||||||
p.mutex.Lock()
|
atomic.AddInt64(&p.currentSize, 1)
|
||||||
p.currentSize++
|
|
||||||
p.mutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -236,31 +236,9 @@ int jetkvm_audio_init() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// jetkvm_audio_read_encode reads one audio frame from ALSA, encodes it with Opus, and handles errors.
|
// jetkvm_audio_read_encode captures audio from ALSA, encodes with Opus, and handles errors.
|
||||||
//
|
// Implements robust error recovery for buffer underruns and device suspension.
|
||||||
// This function implements a robust audio capture pipeline with the following features:
|
// Returns: >0 (bytes written), -1 (init error), -2 (unrecoverable error)
|
||||||
// - 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
|
|
||||||
int jetkvm_audio_read_encode(void *opus_buf) {
|
int jetkvm_audio_read_encode(void *opus_buf) {
|
||||||
short pcm_buffer[1920]; // max 2ch*960
|
short pcm_buffer[1920]; // max 2ch*960
|
||||||
unsigned char *out = (unsigned char*)opus_buf;
|
unsigned char *out = (unsigned char*)opus_buf;
|
||||||
|
@ -610,29 +588,59 @@ var (
|
||||||
errInvalidBufferPtr = errors.New("invalid buffer pointer")
|
errInvalidBufferPtr = errors.New("invalid buffer pointer")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error creation functions with context
|
// Error creation functions with enhanced context
|
||||||
func newBufferTooSmallError(actual, required int) error {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
func cgoAudioInit() error {
|
||||||
|
|
|
@ -1513,105 +1513,27 @@ type AudioConfigConstants struct {
|
||||||
// real-time audio requirements, and extensive testing for optimal performance.
|
// real-time audio requirements, and extensive testing for optimal performance.
|
||||||
func DefaultAudioConfig() *AudioConfigConstants {
|
func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
return &AudioConfigConstants{
|
return &AudioConfigConstants{
|
||||||
// Audio Quality Presets - Core audio frame and packet size configuration
|
// Audio Quality Presets
|
||||||
// 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.
|
|
||||||
MaxAudioFrameSize: 4096,
|
MaxAudioFrameSize: 4096,
|
||||||
|
|
||||||
// Opus Encoding Parameters - Configuration for Opus audio codec
|
// Opus Encoding Parameters
|
||||||
// Used in: Audio encoding/decoding pipeline for quality control
|
OpusBitrate: 128000,
|
||||||
// Impact: Controls audio quality, bandwidth usage, and encoding performance
|
OpusComplexity: 10,
|
||||||
|
OpusVBR: 1,
|
||||||
// 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.
|
|
||||||
OpusVBRConstraint: 0,
|
OpusVBRConstraint: 0,
|
||||||
|
OpusDTX: 0,
|
||||||
|
|
||||||
// OpusDTX controls discontinuous transmission.
|
// Audio Parameters
|
||||||
// Used in: Opus encoder for silence detection and transmission
|
SampleRate: 48000,
|
||||||
// Impact: Can interfere with system audio monitoring in KVM applications.
|
Channels: 2,
|
||||||
// Default 0 (disabled) ensures consistent audio stream.
|
FrameSize: 960,
|
||||||
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.
|
|
||||||
MaxPacketSize: 4000,
|
MaxPacketSize: 4000,
|
||||||
|
|
||||||
// Audio Quality Bitrates - Preset bitrates for different quality levels
|
// Audio Quality Bitrates
|
||||||
// Used in: Audio quality management and adaptive bitrate control
|
AudioQualityLowOutputBitrate: 32,
|
||||||
// Impact: Controls bandwidth usage and audio quality for different scenarios
|
AudioQualityLowInputBitrate: 16,
|
||||||
|
|
||||||
// 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.
|
|
||||||
AudioQualityMediumOutputBitrate: 64,
|
AudioQualityMediumOutputBitrate: 64,
|
||||||
|
AudioQualityMediumInputBitrate: 32,
|
||||||
// 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,
|
|
||||||
|
|
||||||
// AudioQualityHighOutputBitrate defines bitrate for high-quality output.
|
// AudioQualityHighOutputBitrate defines bitrate for high-quality output.
|
||||||
// Used in: Professional applications requiring excellent audio fidelity
|
// Used in: Professional applications requiring excellent audio fidelity
|
||||||
|
@ -1689,106 +1611,25 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
|
|
||||||
// Audio Quality Channels - Channel configuration for different quality levels
|
// Audio Quality Channels - Channel configuration for different quality levels
|
||||||
// Used in: Audio processing pipeline for channel handling and bandwidth control
|
// Used in: Audio processing pipeline for channel handling and bandwidth control
|
||||||
// Impact: Controls spatial audio information and bandwidth requirements
|
AudioQualityLowChannels: 1,
|
||||||
|
|
||||||
// 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.
|
|
||||||
AudioQualityMediumChannels: 2,
|
AudioQualityMediumChannels: 2,
|
||||||
|
AudioQualityHighChannels: 2,
|
||||||
|
AudioQualityUltraChannels: 2,
|
||||||
|
|
||||||
// AudioQualityHighChannels defines channel count for high-quality audio.
|
// CGO Audio Constants
|
||||||
// Used in: High-quality audio scenarios requiring full spatial reproduction
|
CGOOpusBitrate: 96000,
|
||||||
// Impact: Ensures complete spatial audio information for quality scenarios.
|
CGOOpusComplexity: 3,
|
||||||
// Default 2 (stereo) preserves spatial information for high quality.
|
CGOOpusVBR: 1,
|
||||||
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.
|
|
||||||
CGOOpusVBRConstraint: 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.
|
// Input IPC Constants
|
||||||
// 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
|
|
||||||
|
|
||||||
// InputIPCSampleRate defines sample rate for input IPC operations.
|
// InputIPCSampleRate defines sample rate for input IPC operations.
|
||||||
// Used in: Microphone input capture and processing
|
// Used in: Microphone input capture and processing
|
||||||
// Impact: Ensures high-quality input matching system audio output.
|
// Impact: Ensures high-quality input matching system audio output.
|
||||||
|
|
|
@ -135,6 +135,9 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
|
||||||
mp.preallocated = mp.preallocated[:len(mp.preallocated)-1]
|
mp.preallocated = mp.preallocated[:len(mp.preallocated)-1]
|
||||||
mp.mutex.Unlock()
|
mp.mutex.Unlock()
|
||||||
atomic.AddInt64(&mp.hitCount, 1)
|
atomic.AddInt64(&mp.hitCount, 1)
|
||||||
|
// Reset message for reuse
|
||||||
|
msg.data = msg.data[:0]
|
||||||
|
msg.msg = InputIPCMessage{}
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
mp.mutex.Unlock()
|
mp.mutex.Unlock()
|
||||||
|
@ -143,9 +146,16 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
|
||||||
select {
|
select {
|
||||||
case msg := <-mp.pool:
|
case msg := <-mp.pool:
|
||||||
atomic.AddInt64(&mp.hitCount, 1)
|
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
|
return msg
|
||||||
default:
|
default:
|
||||||
// Pool exhausted, create new message
|
// Pool exhausted, create new message with exact capacity
|
||||||
atomic.AddInt64(&mp.missCount, 1)
|
atomic.AddInt64(&mp.missCount, 1)
|
||||||
return &OptimizedIPCMessage{
|
return &OptimizedIPCMessage{
|
||||||
data: make([]byte, 0, maxFrameSize),
|
data: make([]byte, 0, maxFrameSize),
|
||||||
|
@ -155,6 +165,15 @@ func (mp *MessagePool) Get() *OptimizedIPCMessage {
|
||||||
|
|
||||||
// Put returns a message to the pool
|
// Put returns a message to the pool
|
||||||
func (mp *MessagePool) Put(msg *OptimizedIPCMessage) {
|
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
|
// Reset the message for reuse
|
||||||
msg.data = msg.data[:0]
|
msg.data = msg.data[:0]
|
||||||
msg.msg = InputIPCMessage{}
|
msg.msg = InputIPCMessage{}
|
||||||
|
|
|
@ -22,6 +22,7 @@ var (
|
||||||
|
|
||||||
// ValidateAudioQuality validates audio quality enum values
|
// ValidateAudioQuality validates audio quality enum values
|
||||||
func ValidateAudioQuality(quality AudioQuality) error {
|
func ValidateAudioQuality(quality AudioQuality) error {
|
||||||
|
// Perform validation
|
||||||
switch quality {
|
switch quality {
|
||||||
case AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra:
|
case AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra:
|
||||||
return nil
|
return nil
|
||||||
|
@ -35,7 +36,7 @@ func ValidateFrameData(data []byte) error {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return ErrInvalidFrameData
|
return ErrInvalidFrameData
|
||||||
}
|
}
|
||||||
// Use config value or fallback to default
|
// Use config value
|
||||||
maxFrameSize := GetConfig().MaxAudioFrameSize
|
maxFrameSize := GetConfig().MaxAudioFrameSize
|
||||||
if len(data) > maxFrameSize {
|
if len(data) > maxFrameSize {
|
||||||
return ErrInvalidFrameSize
|
return ErrInvalidFrameSize
|
||||||
|
@ -75,14 +76,9 @@ func ValidateBufferSize(size int) error {
|
||||||
|
|
||||||
// ValidateThreadPriority validates thread priority values
|
// ValidateThreadPriority validates thread priority values
|
||||||
func ValidateThreadPriority(priority int) error {
|
func ValidateThreadPriority(priority int) error {
|
||||||
// Use reasonable defaults if config is not available
|
// Use config values
|
||||||
minPriority := -20
|
config := GetConfig()
|
||||||
maxPriority := 99
|
if priority < config.MinNiceValue || priority > config.RTAudioHighPriority {
|
||||||
if config := GetConfig(); config != nil {
|
|
||||||
minPriority = config.MinNiceValue
|
|
||||||
maxPriority = config.RTAudioHighPriority
|
|
||||||
}
|
|
||||||
if priority < minPriority || priority > maxPriority {
|
|
||||||
return ErrInvalidPriority
|
return ErrInvalidPriority
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enhanced validation errors with more specific context
|
// Validation errors
|
||||||
var (
|
var (
|
||||||
ErrInvalidFrameLength = errors.New("invalid frame length")
|
ErrInvalidFrameLength = errors.New("invalid frame length")
|
||||||
ErrFrameDataCorrupted = errors.New("frame data appears corrupted")
|
ErrFrameDataCorrupted = errors.New("frame data appears corrupted")
|
||||||
|
@ -21,6 +21,11 @@ var (
|
||||||
ErrInvalidPointer = errors.New("invalid pointer")
|
ErrInvalidPointer = errors.New("invalid pointer")
|
||||||
ErrBufferOverflow = errors.New("buffer overflow detected")
|
ErrBufferOverflow = errors.New("buffer overflow detected")
|
||||||
ErrInvalidState = errors.New("invalid state")
|
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
|
// ValidationLevel defines the level of validation to perform
|
||||||
|
@ -43,13 +48,14 @@ type ValidationConfig struct {
|
||||||
|
|
||||||
// GetValidationConfig returns the current validation configuration
|
// GetValidationConfig returns the current validation configuration
|
||||||
func GetValidationConfig() ValidationConfig {
|
func GetValidationConfig() ValidationConfig {
|
||||||
configConstants := GetConfig()
|
// Use direct config access
|
||||||
|
config := GetConfig()
|
||||||
return ValidationConfig{
|
return ValidationConfig{
|
||||||
Level: ValidationStandard,
|
Level: ValidationStandard,
|
||||||
EnableRangeChecks: true,
|
EnableRangeChecks: true,
|
||||||
EnableAlignmentCheck: true,
|
EnableAlignmentCheck: true,
|
||||||
EnableDataIntegrity: false, // Disabled by default for performance
|
EnableDataIntegrity: false, // Disabled by default for performance
|
||||||
MaxValidationTime: configConstants.MaxValidationTime, // Configurable validation timeout
|
MaxValidationTime: config.MaxValidationTime, // Configurable validation timeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +65,10 @@ func ValidateAudioFrameFast(data []byte) error {
|
||||||
return ErrInvalidFrameData
|
return ErrInvalidFrameData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick bounds check using config constants
|
// Quick bounds check using direct config access
|
||||||
maxSize := GetConfig().MaxAudioFrameSize
|
config := GetConfig()
|
||||||
if len(data) > maxSize {
|
if len(data) > config.MaxAudioFrameSize {
|
||||||
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), maxSize)
|
return fmt.Errorf("%w: frame size %d exceeds maximum %d", ErrInvalidFrameSize, len(data), config.MaxAudioFrameSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -88,6 +94,7 @@ func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expect
|
||||||
|
|
||||||
// Range validation
|
// Range validation
|
||||||
if validationConfig.EnableRangeChecks {
|
if validationConfig.EnableRangeChecks {
|
||||||
|
// Use direct config access
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
if len(data) < config.MinFrameSize {
|
if len(data) < config.MinFrameSize {
|
||||||
return fmt.Errorf("%w: frame size %d below minimum %d", ErrInvalidFrameSize, 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)
|
return fmt.Errorf("quality validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use direct config access
|
||||||
configConstants := GetConfig()
|
configConstants := GetConfig()
|
||||||
|
minOpusBitrate := configConstants.MinOpusBitrate
|
||||||
|
maxOpusBitrate := configConstants.MaxOpusBitrate
|
||||||
|
maxChannels := configConstants.MaxChannels
|
||||||
|
validSampleRates := configConstants.ValidSampleRates
|
||||||
|
minFrameDuration := configConstants.MinFrameDuration
|
||||||
|
maxFrameDuration := configConstants.MaxFrameDuration
|
||||||
|
|
||||||
// Validate bitrate ranges
|
// Validate bitrate ranges
|
||||||
if config.Bitrate < configConstants.MinOpusBitrate || config.Bitrate > configConstants.MaxOpusBitrate {
|
if config.Bitrate < minOpusBitrate || config.Bitrate > maxOpusBitrate {
|
||||||
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, configConstants.MinOpusBitrate, configConstants.MaxOpusBitrate)
|
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, minOpusBitrate, maxOpusBitrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate sample rate
|
// Validate sample rate
|
||||||
validSampleRates := configConstants.ValidSampleRates
|
|
||||||
validSampleRate := false
|
validSampleRate := false
|
||||||
for _, rate := range validSampleRates {
|
for _, rate := range validSampleRates {
|
||||||
if config.SampleRate == rate {
|
if config.SampleRate == rate {
|
||||||
|
@ -201,15 +214,13 @@ func ValidateAudioConfiguration(config AudioConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate channels
|
// Validate channels
|
||||||
if config.Channels < 1 || 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, configConstants.MaxChannels)
|
return fmt.Errorf("%w: channels %d outside valid range [1, %d]", ErrInvalidChannels, config.Channels, maxChannels)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate frame size
|
// Validate frame size
|
||||||
minFrameSize := GetConfig().MinFrameDuration
|
if config.FrameSize < minFrameDuration || config.FrameSize > maxFrameDuration {
|
||||||
maxFrameSize := GetConfig().MaxFrameDuration
|
return fmt.Errorf("%w: frame size %v outside valid range [%v, %v]", ErrInvalidConfiguration, config.FrameSize, minFrameDuration, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -380,3 +391,89 @@ func getValidationLogger() *zerolog.Logger {
|
||||||
logger := zerolog.New(nil).With().Timestamp().Logger()
|
logger := zerolog.New(nil).With().Timestamp().Logger()
|
||||||
return &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"
|
||||||
|
}
|
||||||
|
|
|
@ -192,6 +192,7 @@ func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame {
|
||||||
frame.data = frame.data[:0]
|
frame.data = frame.data[:0]
|
||||||
frame.mutex.Unlock()
|
frame.mutex.Unlock()
|
||||||
|
|
||||||
|
wasHit = true // Pool hit
|
||||||
atomic.AddInt64(&p.hitCount, 1)
|
atomic.AddInt64(&p.hitCount, 1)
|
||||||
return frame
|
return frame
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import { LuActivity, LuSignal } from "react-icons/lu";
|
||||||
|
|
||||||
import { Button } from "@components/Button";
|
import { Button } from "@components/Button";
|
||||||
import { AudioLevelMeter } from "@components/AudioLevelMeter";
|
import { AudioLevelMeter } from "@components/AudioLevelMeter";
|
||||||
|
import { AudioConfigDisplay } from "@components/AudioConfigDisplay";
|
||||||
|
import { AudioStatusIndicator } from "@components/AudioStatusIndicator";
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
import { useUiStore } from "@/hooks/stores";
|
import { useUiStore } from "@/hooks/stores";
|
||||||
import { useAudioDevices } from "@/hooks/useAudioDevices";
|
import { useAudioDevices } from "@/hooks/useAudioDevices";
|
||||||
|
@ -512,14 +514,10 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentMicrophoneConfig && (
|
{currentMicrophoneConfig && (
|
||||||
<div className="rounded-md bg-green-50 p-2 text-xs text-green-600 dark:bg-green-900/20 dark:text-green-400">
|
<AudioConfigDisplay
|
||||||
<div className="grid grid-cols-2 gap-1">
|
config={currentMicrophoneConfig}
|
||||||
<span>Sample Rate: {currentMicrophoneConfig.SampleRate}Hz</span>
|
variant="success"
|
||||||
<span>Channels: {currentMicrophoneConfig.Channels}</span>
|
/>
|
||||||
<span>Bitrate: {currentMicrophoneConfig.Bitrate}kbps</span>
|
|
||||||
<span>Frame: {currentMicrophoneConfig.FrameSize}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -553,14 +551,10 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentConfig && (
|
{currentConfig && (
|
||||||
<div className="rounded-md bg-slate-50 p-2 text-xs text-slate-600 dark:bg-slate-700 dark:text-slate-400">
|
<AudioConfigDisplay
|
||||||
<div className="grid grid-cols-2 gap-1">
|
config={currentConfig}
|
||||||
<span>Sample Rate: {currentConfig.SampleRate}Hz</span>
|
variant="default"
|
||||||
<span>Channels: {currentConfig.Channels}</span>
|
/>
|
||||||
<span>Bitrate: {currentConfig.Bitrate}kbps</span>
|
|
||||||
<span>Frame: {currentConfig.FrameSize}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -575,30 +569,16 @@ export default function AudioControlPopover({ microphone, open }: AudioControlPo
|
||||||
|
|
||||||
{metrics ? (
|
{metrics ? (
|
||||||
<div className="grid grid-cols-2 gap-3 text-xs">
|
<div className="grid grid-cols-2 gap-3 text-xs">
|
||||||
<div className="text-center p-2 bg-slate-50 dark:bg-slate-800 rounded">
|
<AudioStatusIndicator
|
||||||
<div className={cx(
|
metrics={metrics}
|
||||||
"font-medium",
|
label="Audio Output"
|
||||||
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>
|
|
||||||
|
|
||||||
{micMetrics && (
|
{micMetrics && (
|
||||||
<div className="text-center p-2 bg-slate-50 dark:bg-slate-800 rounded">
|
<AudioStatusIndicator
|
||||||
<div className={cx(
|
metrics={micMetrics}
|
||||||
"font-medium",
|
label="Microphone"
|
||||||
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>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -89,62 +89,17 @@ export const AUDIO_CONFIG = {
|
||||||
SYNC_DEBOUNCE_MS: 1000, // debounce state synchronization
|
SYNC_DEBOUNCE_MS: 1000, // debounce state synchronization
|
||||||
AUDIO_TEST_TIMEOUT: 100, // ms - timeout for audio testing
|
AUDIO_TEST_TIMEOUT: 100, // ms - timeout for audio testing
|
||||||
|
|
||||||
// Audio Output Quality Bitrates (matching backend config_constants.go)
|
// NOTE: Audio quality presets (bitrates, sample rates, channels, frame sizes)
|
||||||
OUTPUT_QUALITY_BITRATES: {
|
// are now fetched dynamically from the backend API via audioQualityService
|
||||||
LOW: 32, // AudioQualityLowOutputBitrate
|
// to eliminate duplication with backend config_constants.go
|
||||||
MEDIUM: 64, // AudioQualityMediumOutputBitrate
|
|
||||||
HIGH: 128, // AudioQualityHighOutputBitrate
|
// Default Quality Labels - will be updated dynamically by audioQualityService
|
||||||
ULTRA: 192, // AudioQualityUltraOutputBitrate
|
DEFAULT_QUALITY_LABELS: {
|
||||||
|
0: "Low",
|
||||||
|
1: "Medium",
|
||||||
|
2: "High",
|
||||||
|
3: "Ultra",
|
||||||
} as const,
|
} 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
|
// Audio Analysis
|
||||||
ANALYSIS_FFT_SIZE: 256, // for detailed audio analysis
|
ANALYSIS_FFT_SIZE: 256, // for detailed audio analysis
|
||||||
|
|
Loading…
Reference in New Issue