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,
|
||||
// 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
|
||||
//
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 { 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>
|
||||
) : (
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue