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:
Alex P 2025-08-26 16:49:41 +00:00
parent 6a68e23d12
commit e8d12bae4b
14 changed files with 123 additions and 113 deletions

View File

@ -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.

View File

@ -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
}, },
}, },
} }

View File

@ -274,4 +274,4 @@ func BenchmarkAudioInputIPCManager(b *testing.B) {
manager.GetMetrics() manager.GetMetrics()
} }
}) })
} }

View File

@ -238,4 +238,4 @@ func BenchmarkAudioInputManager(b *testing.B) {
_ = manager.IsReady() _ = manager.IsReady()
} }
}) })
} }

View File

@ -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)

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -274,4 +274,4 @@ func BenchmarkAudioOutputManager(b *testing.B) {
manager.GetMetrics() manager.GetMetrics()
} }
}) })
} }

View File

@ -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")

View File

@ -338,4 +338,4 @@ func BenchmarkAudioOutputStreamer(b *testing.B) {
streamer.ReportLatency(10 * time.Millisecond) streamer.ReportLatency(10 * time.Millisecond)
} }
}) })
} }

View File

@ -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,

View File

@ -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()
} }
}) })
} }

View File

@ -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
} }

View File

@ -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
// //