mirror of https://github.com/jetkvm/kvm.git
style(audio): fix formatting and add missing newlines
- Fix indentation in test files and supervisor code - Add missing newlines at end of files - Clean up documentation formatting - Fix buffer pool pointer return type
This commit is contained in:
parent
6a68e23d12
commit
e8d12bae4b
|
@ -18,17 +18,17 @@ import (
|
||||||
// uses multiple factors to make decisions:
|
// uses multiple factors to make decisions:
|
||||||
//
|
//
|
||||||
// 1. System Load Monitoring:
|
// 1. System Load Monitoring:
|
||||||
// - CPU usage: High CPU load increases buffer sizes to prevent underruns
|
// - CPU usage: High CPU load increases buffer sizes to prevent underruns
|
||||||
// - Memory usage: High memory pressure reduces buffer sizes to conserve RAM
|
// - Memory usage: High memory pressure reduces buffer sizes to conserve RAM
|
||||||
//
|
//
|
||||||
// 2. Latency Tracking:
|
// 2. Latency Tracking:
|
||||||
// - Target latency: Optimal latency for the current quality setting
|
// - Target latency: Optimal latency for the current quality setting
|
||||||
// - Max latency: Hard limit beyond which buffers are aggressively reduced
|
// - Max latency: Hard limit beyond which buffers are aggressively reduced
|
||||||
//
|
//
|
||||||
// 3. Adaptation Strategy:
|
// 3. Adaptation Strategy:
|
||||||
// - Exponential smoothing: Prevents oscillation and provides stable adjustments
|
// - Exponential smoothing: Prevents oscillation and provides stable adjustments
|
||||||
// - Discrete steps: Buffer sizes change in fixed increments to avoid instability
|
// - Discrete steps: Buffer sizes change in fixed increments to avoid instability
|
||||||
// - Hysteresis: Different thresholds for increasing vs decreasing buffer sizes
|
// - Hysteresis: Different thresholds for increasing vs decreasing buffer sizes
|
||||||
//
|
//
|
||||||
// The algorithm is specifically tuned for embedded ARM systems with limited resources,
|
// The algorithm is specifically tuned for embedded ARM systems with limited resources,
|
||||||
// prioritizing stability over absolute minimum latency.
|
// prioritizing stability over absolute minimum latency.
|
||||||
|
@ -182,20 +182,23 @@ func (abm *AdaptiveBufferManager) adaptationLoop() {
|
||||||
//
|
//
|
||||||
// Mathematical Model:
|
// Mathematical Model:
|
||||||
// 1. Factor Calculation:
|
// 1. Factor Calculation:
|
||||||
// - CPU Factor: Sigmoid function that increases buffer size under high CPU load
|
|
||||||
// - Memory Factor: Inverse relationship that decreases buffer size under memory pressure
|
|
||||||
// - Latency Factor: Exponential decay that aggressively reduces buffers when latency exceeds targets
|
|
||||||
//
|
//
|
||||||
// 2. Combined Factor:
|
// - CPU Factor: Sigmoid function that increases buffer size under high CPU load
|
||||||
// Combined = (CPU_factor * Memory_factor * Latency_factor)
|
|
||||||
// This multiplicative approach ensures any single critical factor can override others
|
|
||||||
//
|
//
|
||||||
// 3. Exponential Smoothing:
|
// - Memory Factor: Inverse relationship that decreases buffer size under memory pressure
|
||||||
// New_size = Current_size + smoothing_factor * (Target_size - Current_size)
|
|
||||||
// This prevents rapid oscillations and provides stable convergence
|
|
||||||
//
|
//
|
||||||
// 4. Discrete Quantization:
|
// - Latency Factor: Exponential decay that aggressively reduces buffers when latency exceeds targets
|
||||||
// Final sizes are rounded to frame boundaries and clamped to configured limits
|
//
|
||||||
|
// 2. Combined Factor:
|
||||||
|
// Combined = (CPU_factor * Memory_factor * Latency_factor)
|
||||||
|
// This multiplicative approach ensures any single critical factor can override others
|
||||||
|
//
|
||||||
|
// 3. Exponential Smoothing:
|
||||||
|
// New_size = Current_size + smoothing_factor * (Target_size - Current_size)
|
||||||
|
// This prevents rapid oscillations and provides stable convergence
|
||||||
|
//
|
||||||
|
// 4. Discrete Quantization:
|
||||||
|
// Final sizes are rounded to frame boundaries and clamped to configured limits
|
||||||
//
|
//
|
||||||
// The algorithm runs periodically and only applies changes when the adaptation interval
|
// The algorithm runs periodically and only applies changes when the adaptation interval
|
||||||
// has elapsed, preventing excessive adjustments that could destabilize the audio pipeline.
|
// has elapsed, preventing excessive adjustments that could destabilize the audio pipeline.
|
||||||
|
|
|
@ -40,7 +40,8 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
|
||||||
preallocSize: preallocSize,
|
preallocSize: preallocSize,
|
||||||
pool: sync.Pool{
|
pool: sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
return make([]byte, 0, bufferSize)
|
buf := make([]byte, 0, bufferSize)
|
||||||
|
return &buf
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,4 +274,4 @@ func BenchmarkAudioInputIPCManager(b *testing.B) {
|
||||||
manager.GetMetrics()
|
manager.GetMetrics()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,4 +238,4 @@ func BenchmarkAudioInputManager(b *testing.B) {
|
||||||
_ = manager.IsReady()
|
_ = manager.IsReady()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -452,7 +452,7 @@ func (c *AudioOutputClient) ReceiveFrame() ([]byte, error) {
|
||||||
return nil, fmt.Errorf("failed to read frame data: %w", err)
|
return nil, fmt.Errorf("failed to read frame data: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Caller is responsible for returning frame to pool via PutAudioFrameBuffer()
|
// Note: Caller is responsible for returning frame to pool via PutAudioFrameBuffer()
|
||||||
|
|
||||||
atomic.AddInt64(&c.totalFrames, 1)
|
atomic.AddInt64(&c.totalFrames, 1)
|
||||||
|
|
|
@ -78,8 +78,8 @@ const (
|
||||||
AudioOutputIPCComponent = "audio-output-ipc"
|
AudioOutputIPCComponent = "audio-output-ipc"
|
||||||
|
|
||||||
// Common component names
|
// Common component names
|
||||||
AudioRelayComponent = "audio-relay"
|
AudioRelayComponent = "audio-relay"
|
||||||
AudioEventsComponent = "audio-events"
|
AudioEventsComponent = "audio-events"
|
||||||
AudioMetricsComponent = "audio-metrics"
|
AudioMetricsComponent = "audio-metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -117,4 +117,4 @@ type AudioStreamerInterface interface {
|
||||||
Start() error
|
Start() error
|
||||||
Stop()
|
Stop()
|
||||||
GetStats() (processed, dropped int64, avgProcessingTime time.Duration)
|
GetStats() (processed, dropped int64, avgProcessingTime time.Duration)
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,4 +174,4 @@ func (aom *AudioOutputManager) LogPerformanceStats() {
|
||||||
// GetStreamer returns the streamer for advanced operations
|
// GetStreamer returns the streamer for advanced operations
|
||||||
func (aom *AudioOutputManager) GetStreamer() *AudioOutputStreamer {
|
func (aom *AudioOutputManager) GetStreamer() *AudioOutputStreamer {
|
||||||
return aom.streamer
|
return aom.streamer
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,4 +274,4 @@ func BenchmarkAudioOutputManager(b *testing.B) {
|
||||||
manager.GetMetrics()
|
manager.GetMetrics()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,7 +202,7 @@ func (s *AudioOutputStreamer) processingLoop() {
|
||||||
// Process frame and return buffer to pool after processing
|
// Process frame and return buffer to pool after processing
|
||||||
func() {
|
func() {
|
||||||
defer s.bufferPool.Put(frameData)
|
defer s.bufferPool.Put(frameData)
|
||||||
|
|
||||||
if _, err := s.client.ReceiveFrame(); err != nil {
|
if _, err := s.client.ReceiveFrame(); err != nil {
|
||||||
if s.client.IsConnected() {
|
if s.client.IsConnected() {
|
||||||
getOutputStreamingLogger().Warn().Err(err).Msg("Error reading audio frame from output server")
|
getOutputStreamingLogger().Warn().Err(err).Msg("Error reading audio frame from output server")
|
||||||
|
|
|
@ -338,4 +338,4 @@ func BenchmarkAudioOutputStreamer(b *testing.B) {
|
||||||
streamer.ReportLatency(10 * time.Millisecond)
|
streamer.ReportLatency(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,9 @@ type AudioOutputSupervisor struct {
|
||||||
lastExitTime time.Time
|
lastExitTime time.Time
|
||||||
|
|
||||||
// Channels for coordination
|
// Channels for coordination
|
||||||
processDone chan struct{}
|
processDone chan struct{}
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
stopChanClosed bool // Track if stopChan is closed
|
stopChanClosed bool // Track if stopChan is closed
|
||||||
processDoneClosed bool // Track if processDone is closed
|
processDoneClosed bool // Track if processDone is closed
|
||||||
|
|
||||||
// Process monitoring
|
// Process monitoring
|
||||||
|
@ -107,7 +107,7 @@ func (s *AudioOutputSupervisor) Start() error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
s.processDone = make(chan struct{})
|
s.processDone = make(chan struct{})
|
||||||
s.stopChan = make(chan struct{})
|
s.stopChan = make(chan struct{})
|
||||||
s.stopChanClosed = false // Reset channel closed flag
|
s.stopChanClosed = false // Reset channel closed flag
|
||||||
s.processDoneClosed = false // Reset channel closed flag
|
s.processDoneClosed = false // Reset channel closed flag
|
||||||
// Recreate context as well since it might have been cancelled
|
// Recreate context as well since it might have been cancelled
|
||||||
s.ctx, s.cancel = context.WithCancel(context.Background())
|
s.ctx, s.cancel = context.WithCancel(context.Background())
|
||||||
|
@ -197,7 +197,7 @@ func (s *AudioOutputSupervisor) GetProcessMetrics() *ProcessMetrics {
|
||||||
return &metric
|
return &metric
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return default metrics if process not found in monitor
|
// Return default metrics if process not found in monitor
|
||||||
return &ProcessMetrics{
|
return &ProcessMetrics{
|
||||||
PID: pid,
|
PID: pid,
|
||||||
|
|
|
@ -109,9 +109,9 @@ func TestAudioOutputSupervisorConcurrentOperations(t *testing.T) {
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
_ = supervisor.GetProcessMetrics()
|
_ = supervisor.GetProcessMetrics()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test concurrent status checks
|
// Test concurrent status checks
|
||||||
|
@ -214,4 +214,4 @@ func BenchmarkAudioOutputSupervisor(b *testing.B) {
|
||||||
_ = supervisor.IsRunning()
|
_ = supervisor.IsRunning()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,25 +11,25 @@ import (
|
||||||
|
|
||||||
// Enhanced validation errors with more specific context
|
// Enhanced validation errors with more specific context
|
||||||
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")
|
||||||
ErrBufferAlignment = errors.New("buffer alignment invalid")
|
ErrBufferAlignment = errors.New("buffer alignment invalid")
|
||||||
ErrInvalidSampleFormat = errors.New("invalid sample format")
|
ErrInvalidSampleFormat = errors.New("invalid sample format")
|
||||||
ErrInvalidTimestamp = errors.New("invalid timestamp")
|
ErrInvalidTimestamp = errors.New("invalid timestamp")
|
||||||
ErrConfigurationMismatch = errors.New("configuration mismatch")
|
ErrConfigurationMismatch = errors.New("configuration mismatch")
|
||||||
ErrResourceExhaustion = errors.New("resource exhaustion detected")
|
ErrResourceExhaustion = errors.New("resource exhaustion detected")
|
||||||
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")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidationLevel defines the level of validation to perform
|
// ValidationLevel defines the level of validation to perform
|
||||||
type ValidationLevel int
|
type ValidationLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ValidationMinimal ValidationLevel = iota // Only critical safety checks
|
ValidationMinimal ValidationLevel = iota // Only critical safety checks
|
||||||
ValidationStandard // Standard validation for production
|
ValidationStandard // Standard validation for production
|
||||||
ValidationStrict // Comprehensive validation for debugging
|
ValidationStrict // Comprehensive validation for debugging
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidationConfig controls validation behavior
|
// ValidationConfig controls validation behavior
|
||||||
|
@ -47,7 +47,7 @@ func GetValidationConfig() 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: 5 * time.Second, // Default validation timeout
|
MaxValidationTime: 5 * time.Second, // Default validation timeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,13 +57,13 @@ func ValidateAudioFrameFast(data []byte) error {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return ErrInvalidFrameData
|
return ErrInvalidFrameData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick bounds check using config constants
|
// Quick bounds check using config constants
|
||||||
maxSize := GetConfig().MaxAudioFrameSize
|
maxSize := GetConfig().MaxAudioFrameSize
|
||||||
if len(data) > maxSize {
|
if len(data) > maxSize {
|
||||||
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), maxSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ func ValidateAudioFrameFast(data []byte) error {
|
||||||
func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expectedChannels int) error {
|
func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expectedChannels int) error {
|
||||||
validationConfig := GetValidationConfig()
|
validationConfig := GetValidationConfig()
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Timeout protection for validation
|
// Timeout protection for validation
|
||||||
defer func() {
|
defer func() {
|
||||||
if time.Since(start) > validationConfig.MaxValidationTime {
|
if time.Since(start) > validationConfig.MaxValidationTime {
|
||||||
|
@ -79,12 +79,12 @@ func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expect
|
||||||
getValidationLogger().Warn().Dur("duration", time.Since(start)).Msg("validation timeout exceeded")
|
getValidationLogger().Warn().Dur("duration", time.Since(start)).Msg("validation timeout exceeded")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Basic validation first
|
// Basic validation first
|
||||||
if err := ValidateAudioFrameFast(data); err != nil {
|
if err := ValidateAudioFrameFast(data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range validation
|
// Range validation
|
||||||
if validationConfig.EnableRangeChecks {
|
if validationConfig.EnableRangeChecks {
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
|
@ -92,7 +92,7 @@ func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expect
|
||||||
if len(data) < minFrameSize {
|
if len(data) < minFrameSize {
|
||||||
return fmt.Errorf("%w: frame size %d below minimum %d", ErrInvalidFrameSize, len(data), minFrameSize)
|
return fmt.Errorf("%w: frame size %d below minimum %d", ErrInvalidFrameSize, len(data), minFrameSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate frame length matches expected sample format
|
// Validate frame length matches expected sample format
|
||||||
expectedFrameSize := (expectedSampleRate * expectedChannels * 2) / 1000 * int(config.AudioQualityMediumFrameSize/time.Millisecond)
|
expectedFrameSize := (expectedSampleRate * expectedChannels * 2) / 1000 * int(config.AudioQualityMediumFrameSize/time.Millisecond)
|
||||||
tolerance := 512 // Frame size tolerance in bytes
|
tolerance := 512 // Frame size tolerance in bytes
|
||||||
|
@ -100,21 +100,21 @@ func ValidateAudioFrameComprehensive(data []byte, expectedSampleRate int, expect
|
||||||
return fmt.Errorf("%w: frame size %d doesn't match expected %d (±%d)", ErrInvalidFrameLength, len(data), expectedFrameSize, tolerance)
|
return fmt.Errorf("%w: frame size %d doesn't match expected %d (±%d)", ErrInvalidFrameLength, len(data), expectedFrameSize, tolerance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alignment validation for ARM32 compatibility
|
// Alignment validation for ARM32 compatibility
|
||||||
if validationConfig.EnableAlignmentCheck {
|
if validationConfig.EnableAlignmentCheck {
|
||||||
if uintptr(unsafe.Pointer(&data[0]))%4 != 0 {
|
if uintptr(unsafe.Pointer(&data[0]))%4 != 0 {
|
||||||
return fmt.Errorf("%w: buffer not 4-byte aligned for ARM32", ErrBufferAlignment)
|
return fmt.Errorf("%w: buffer not 4-byte aligned for ARM32", ErrBufferAlignment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data integrity checks (expensive, only for debugging)
|
// Data integrity checks (expensive, only for debugging)
|
||||||
if validationConfig.EnableDataIntegrity && validationConfig.Level == ValidationStrict {
|
if validationConfig.EnableDataIntegrity && validationConfig.Level == ValidationStrict {
|
||||||
if err := validateAudioDataIntegrity(data, expectedChannels); err != nil {
|
if err := validateAudioDataIntegrity(data, expectedChannels); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,26 +123,26 @@ func ValidateZeroCopyFrameEnhanced(frame *ZeroCopyAudioFrame) error {
|
||||||
if frame == nil {
|
if frame == nil {
|
||||||
return fmt.Errorf("%w: frame is nil", ErrInvalidPointer)
|
return fmt.Errorf("%w: frame is nil", ErrInvalidPointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check reference count validity
|
// Check reference count validity
|
||||||
frame.mutex.RLock()
|
frame.mutex.RLock()
|
||||||
refCount := frame.refCount
|
refCount := frame.refCount
|
||||||
length := frame.length
|
length := frame.length
|
||||||
capacity := frame.capacity
|
capacity := frame.capacity
|
||||||
frame.mutex.RUnlock()
|
frame.mutex.RUnlock()
|
||||||
|
|
||||||
if refCount <= 0 {
|
if refCount <= 0 {
|
||||||
return fmt.Errorf("%w: invalid reference count %d", ErrInvalidState, refCount)
|
return fmt.Errorf("%w: invalid reference count %d", ErrInvalidState, refCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if length < 0 || capacity < 0 {
|
if length < 0 || capacity < 0 {
|
||||||
return fmt.Errorf("%w: negative length (%d) or capacity (%d)", ErrInvalidState, length, capacity)
|
return fmt.Errorf("%w: negative length (%d) or capacity (%d)", ErrInvalidState, length, capacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
if length > capacity {
|
if length > capacity {
|
||||||
return fmt.Errorf("%w: length %d exceeds capacity %d", ErrBufferOverflow, length, capacity)
|
return fmt.Errorf("%w: length %d exceeds capacity %d", ErrBufferOverflow, length, capacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the underlying data
|
// Validate the underlying data
|
||||||
data := frame.Data()
|
data := frame.Data()
|
||||||
return ValidateAudioFrameFast(data)
|
return ValidateAudioFrameFast(data)
|
||||||
|
@ -153,25 +153,25 @@ func ValidateBufferBounds(buffer []byte, offset, length int) error {
|
||||||
if buffer == nil {
|
if buffer == nil {
|
||||||
return fmt.Errorf("%w: buffer is nil", ErrInvalidPointer)
|
return fmt.Errorf("%w: buffer is nil", ErrInvalidPointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
return fmt.Errorf("%w: negative offset %d", ErrInvalidState, offset)
|
return fmt.Errorf("%w: negative offset %d", ErrInvalidState, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if length < 0 {
|
if length < 0 {
|
||||||
return fmt.Errorf("%w: negative length %d", ErrInvalidState, length)
|
return fmt.Errorf("%w: negative length %d", ErrInvalidState, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for integer overflow
|
// Check for integer overflow
|
||||||
if offset > len(buffer) {
|
if offset > len(buffer) {
|
||||||
return fmt.Errorf("%w: offset %d exceeds buffer length %d", ErrBufferOverflow, offset, len(buffer))
|
return fmt.Errorf("%w: offset %d exceeds buffer length %d", ErrBufferOverflow, offset, len(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safe addition check for overflow
|
// Safe addition check for overflow
|
||||||
if offset+length < offset || offset+length > len(buffer) {
|
if offset+length < offset || offset+length > len(buffer) {
|
||||||
return fmt.Errorf("%w: range [%d:%d] exceeds buffer length %d", ErrBufferOverflow, offset, offset+length, len(buffer))
|
return fmt.Errorf("%w: range [%d:%d] exceeds buffer length %d", ErrBufferOverflow, offset, offset+length, len(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,16 +180,16 @@ func ValidateAudioConfiguration(config AudioConfig) error {
|
||||||
if err := ValidateAudioQuality(config.Quality); err != nil {
|
if err := ValidateAudioQuality(config.Quality); err != nil {
|
||||||
return fmt.Errorf("quality validation failed: %w", err)
|
return fmt.Errorf("quality validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configConstants := GetConfig()
|
configConstants := GetConfig()
|
||||||
|
|
||||||
// Validate bitrate ranges
|
// Validate bitrate ranges
|
||||||
minBitrate := 6000 // Minimum Opus bitrate
|
minBitrate := 6000 // Minimum Opus bitrate
|
||||||
maxBitrate := 510000 // Maximum Opus bitrate
|
maxBitrate := 510000 // Maximum Opus bitrate
|
||||||
if config.Bitrate < minBitrate || config.Bitrate > maxBitrate {
|
if config.Bitrate < minBitrate || config.Bitrate > maxBitrate {
|
||||||
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, minBitrate, maxBitrate)
|
return fmt.Errorf("%w: bitrate %d outside valid range [%d, %d]", ErrInvalidConfiguration, config.Bitrate, minBitrate, maxBitrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate sample rate
|
// Validate sample rate
|
||||||
validSampleRates := []int{8000, 12000, 16000, 24000, 48000}
|
validSampleRates := []int{8000, 12000, 16000, 24000, 48000}
|
||||||
validSampleRate := false
|
validSampleRate := false
|
||||||
|
@ -202,38 +202,38 @@ func ValidateAudioConfiguration(config AudioConfig) error {
|
||||||
if !validSampleRate {
|
if !validSampleRate {
|
||||||
return fmt.Errorf("%w: sample rate %d not in supported rates %v", ErrInvalidSampleRate, config.SampleRate, validSampleRates)
|
return fmt.Errorf("%w: sample rate %d not in supported rates %v", ErrInvalidSampleRate, config.SampleRate, validSampleRates)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate channels
|
// Validate channels
|
||||||
if config.Channels < 1 || config.Channels > configConstants.MaxChannels {
|
if config.Channels < 1 || config.Channels > configConstants.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, configConstants.MaxChannels)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate frame size
|
// Validate frame size
|
||||||
minFrameSize := 10 * time.Millisecond // Minimum frame duration
|
minFrameSize := 10 * time.Millisecond // Minimum frame duration
|
||||||
maxFrameSize := 100 * time.Millisecond // Maximum frame duration
|
maxFrameSize := 100 * time.Millisecond // Maximum frame duration
|
||||||
if config.FrameSize < minFrameSize || config.FrameSize > maxFrameSize {
|
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 fmt.Errorf("%w: frame size %v outside valid range [%v, %v]", ErrInvalidConfiguration, config.FrameSize, minFrameSize, maxFrameSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateResourceLimits checks if system resources are within acceptable limits
|
// ValidateResourceLimits checks if system resources are within acceptable limits
|
||||||
func ValidateResourceLimits() error {
|
func ValidateResourceLimits() error {
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
|
|
||||||
// Check buffer pool sizes
|
// Check buffer pool sizes
|
||||||
framePoolStats := GetAudioBufferPoolStats()
|
framePoolStats := GetAudioBufferPoolStats()
|
||||||
if framePoolStats.FramePoolSize > int64(config.MaxPoolSize*2) {
|
if framePoolStats.FramePoolSize > int64(config.MaxPoolSize*2) {
|
||||||
return fmt.Errorf("%w: frame pool size %d exceeds safe limit %d", ErrResourceExhaustion, framePoolStats.FramePoolSize, config.MaxPoolSize*2)
|
return fmt.Errorf("%w: frame pool size %d exceeds safe limit %d", ErrResourceExhaustion, framePoolStats.FramePoolSize, config.MaxPoolSize*2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check zero-copy pool allocation count
|
// Check zero-copy pool allocation count
|
||||||
zeroCopyStats := GetGlobalZeroCopyPoolStats()
|
zeroCopyStats := GetGlobalZeroCopyPoolStats()
|
||||||
if zeroCopyStats.AllocationCount > int64(config.MaxPoolSize*3) {
|
if zeroCopyStats.AllocationCount > int64(config.MaxPoolSize*3) {
|
||||||
return fmt.Errorf("%w: zero-copy allocations %d exceed safe limit %d", ErrResourceExhaustion, zeroCopyStats.AllocationCount, config.MaxPoolSize*3)
|
return fmt.Errorf("%w: zero-copy allocations %d exceed safe limit %d", ErrResourceExhaustion, zeroCopyStats.AllocationCount, config.MaxPoolSize*3)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,34 +242,35 @@ func validateAudioDataIntegrity(data []byte, channels int) error {
|
||||||
if len(data)%2 != 0 {
|
if len(data)%2 != 0 {
|
||||||
return fmt.Errorf("%w: odd number of bytes for 16-bit samples", ErrInvalidSampleFormat)
|
return fmt.Errorf("%w: odd number of bytes for 16-bit samples", ErrInvalidSampleFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data)%(channels*2) != 0 {
|
if len(data)%(channels*2) != 0 {
|
||||||
return fmt.Errorf("%w: data length %d not aligned to channel count %d", ErrInvalidSampleFormat, len(data), channels)
|
return fmt.Errorf("%w: data length %d not aligned to channel count %d", ErrInvalidSampleFormat, len(data), channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for obvious corruption patterns (all zeros, all max values)
|
// Check for obvious corruption patterns (all zeros, all max values)
|
||||||
sampleCount := len(data) / 2
|
sampleCount := len(data) / 2
|
||||||
zeroCount := 0
|
zeroCount := 0
|
||||||
maxCount := 0
|
maxCount := 0
|
||||||
|
|
||||||
for i := 0; i < len(data); i += 2 {
|
for i := 0; i < len(data); i += 2 {
|
||||||
sample := int16(data[i]) | int16(data[i+1])<<8
|
sample := int16(data[i]) | int16(data[i+1])<<8
|
||||||
if sample == 0 {
|
switch sample {
|
||||||
|
case 0:
|
||||||
zeroCount++
|
zeroCount++
|
||||||
} else if sample == 32767 || sample == -32768 {
|
case 32767, -32768:
|
||||||
maxCount++
|
maxCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag suspicious patterns
|
// Flag suspicious patterns
|
||||||
if zeroCount > sampleCount*9/10 {
|
if zeroCount > sampleCount*9/10 {
|
||||||
return fmt.Errorf("%w: %d%% zero samples suggests silence or corruption", ErrFrameDataCorrupted, (zeroCount*100)/sampleCount)
|
return fmt.Errorf("%w: %d%% zero samples suggests silence or corruption", ErrFrameDataCorrupted, (zeroCount*100)/sampleCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if maxCount > sampleCount/10 {
|
if maxCount > sampleCount/10 {
|
||||||
return fmt.Errorf("%w: %d%% max-value samples suggests clipping or corruption", ErrFrameDataCorrupted, (maxCount*100)/sampleCount)
|
return fmt.Errorf("%w: %d%% max-value samples suggests clipping or corruption", ErrFrameDataCorrupted, (maxCount*100)/sampleCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,4 +287,4 @@ func getValidationLogger() *zerolog.Logger {
|
||||||
// Return a basic logger for validation
|
// Return a basic logger for validation
|
||||||
logger := zerolog.New(nil).With().Timestamp().Logger()
|
logger := zerolog.New(nil).With().Timestamp().Logger()
|
||||||
return &logger
|
return &logger
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,25 +13,27 @@ import (
|
||||||
// allocations and memory copying in the audio pipeline:
|
// allocations and memory copying in the audio pipeline:
|
||||||
//
|
//
|
||||||
// Key Features:
|
// Key Features:
|
||||||
// 1. Reference Counting: Multiple components can safely share the same frame data
|
|
||||||
// without copying. The frame is automatically returned to the pool when the last
|
|
||||||
// reference is released.
|
|
||||||
//
|
//
|
||||||
// 2. Thread Safety: All operations are protected by RWMutex, allowing concurrent
|
// 1. Reference Counting: Multiple components can safely share the same frame data
|
||||||
// reads while ensuring exclusive access for modifications.
|
// without copying. The frame is automatically returned to the pool when the last
|
||||||
|
// reference is released.
|
||||||
//
|
//
|
||||||
// 3. Pool Integration: Frames are automatically managed by ZeroCopyFramePool,
|
// 2. Thread Safety: All operations are protected by RWMutex, allowing concurrent
|
||||||
// enabling efficient reuse and preventing memory fragmentation.
|
// reads while ensuring exclusive access for modifications.
|
||||||
//
|
//
|
||||||
// 4. Unsafe Pointer Access: For performance-critical CGO operations, direct
|
// 3. Pool Integration: Frames are automatically managed by ZeroCopyFramePool,
|
||||||
// memory access is provided while maintaining safety through reference counting.
|
// enabling efficient reuse and preventing memory fragmentation.
|
||||||
|
//
|
||||||
|
// 4. Unsafe Pointer Access: For performance-critical CGO operations, direct
|
||||||
|
// memory access is provided while maintaining safety through reference counting.
|
||||||
//
|
//
|
||||||
// Usage Pattern:
|
// Usage Pattern:
|
||||||
// frame := pool.Get() // Acquire frame (refCount = 1)
|
//
|
||||||
// frame.AddRef() // Share with another component (refCount = 2)
|
// frame := pool.Get() // Acquire frame (refCount = 1)
|
||||||
// data := frame.Data() // Access data safely
|
// frame.AddRef() // Share with another component (refCount = 2)
|
||||||
// frame.Release() // Release reference (refCount = 1)
|
// data := frame.Data() // Access data safely
|
||||||
// frame.Release() // Final release, returns to pool (refCount = 0)
|
// frame.Release() // Release reference (refCount = 1)
|
||||||
|
// frame.Release() // Final release, returns to pool (refCount = 0)
|
||||||
//
|
//
|
||||||
// Memory Safety:
|
// Memory Safety:
|
||||||
// - Frames cannot be modified while shared (refCount > 1)
|
// - Frames cannot be modified while shared (refCount > 1)
|
||||||
|
@ -52,23 +54,26 @@ type ZeroCopyAudioFrame struct {
|
||||||
// real-time audio processing with minimal allocation overhead:
|
// real-time audio processing with minimal allocation overhead:
|
||||||
//
|
//
|
||||||
// Tier 1 - Pre-allocated Frames:
|
// Tier 1 - Pre-allocated Frames:
|
||||||
// A small number of frames are pre-allocated at startup and kept ready
|
//
|
||||||
// for immediate use. This provides the fastest possible allocation for
|
// A small number of frames are pre-allocated at startup and kept ready
|
||||||
// the most common case and eliminates allocation latency spikes.
|
// for immediate use. This provides the fastest possible allocation for
|
||||||
|
// the most common case and eliminates allocation latency spikes.
|
||||||
//
|
//
|
||||||
// Tier 2 - sync.Pool Cache:
|
// Tier 2 - sync.Pool Cache:
|
||||||
// The standard Go sync.Pool provides efficient reuse of frames with
|
//
|
||||||
// automatic garbage collection integration. Frames are automatically
|
// The standard Go sync.Pool provides efficient reuse of frames with
|
||||||
// returned here when memory pressure is low.
|
// automatic garbage collection integration. Frames are automatically
|
||||||
|
// returned here when memory pressure is low.
|
||||||
//
|
//
|
||||||
// Tier 3 - Memory Guard:
|
// Tier 3 - Memory Guard:
|
||||||
// A configurable limit prevents excessive memory usage by limiting
|
//
|
||||||
// the total number of allocated frames. When the limit is reached,
|
// A configurable limit prevents excessive memory usage by limiting
|
||||||
// allocation requests are denied to prevent OOM conditions.
|
// the total number of allocated frames. When the limit is reached,
|
||||||
|
// allocation requests are denied to prevent OOM conditions.
|
||||||
//
|
//
|
||||||
// Performance Characteristics:
|
// Performance Characteristics:
|
||||||
// - Pre-allocated tier: ~10ns allocation time
|
// - Pre-allocated tier: ~10ns allocation time
|
||||||
// - sync.Pool tier: ~50ns allocation time
|
// - sync.Pool tier: ~50ns allocation time
|
||||||
// - Memory guard: Prevents unbounded growth
|
// - Memory guard: Prevents unbounded growth
|
||||||
// - Metrics tracking: Hit/miss rates for optimization
|
// - Metrics tracking: Hit/miss rates for optimization
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue