perf(audio): replace frame validation with ultra-fast version

Use ValidateAudioFrameUltraFast in critical audio paths to reduce processing overhead
Reduce minimum frame size to 1 byte to allow smaller frames
This commit is contained in:
Alex P 2025-08-27 22:44:34 +00:00
parent e3e7b898b5
commit 25363cef90
8 changed files with 31 additions and 67 deletions

View File

@ -2388,7 +2388,7 @@ func DefaultAudioConfig() *AudioConfigConstants {
// Validation Configuration
MaxValidationTime: 5 * time.Second, // 5s maximum validation timeout
MinFrameSize: 64, // 64 bytes minimum frame size
MinFrameSize: 1, // 1 byte minimum frame size (allow small frames)
FrameSizeTolerance: 512, // 512 bytes frame size tolerance
// Device Health Monitoring Configuration

View File

@ -83,8 +83,8 @@ func (aim *AudioInputManager) WriteOpusFrame(frame []byte) error {
return nil // Not running, silently drop
}
// Validate frame before processing
if err := ValidateFrameData(frame); err != nil {
// Use ultra-fast validation for critical audio path
if err := ValidateAudioFrameUltraFast(frame); err != nil {
aim.logComponentError(AudioInputManagerComponent, err, "Frame validation failed")
return fmt.Errorf("input frame validation failed: %w", err)
}

View File

@ -477,8 +477,8 @@ func (ais *AudioInputServer) processOpusFrame(data []byte) error {
return nil // Empty frame, ignore
}
// Validate frame data before processing
if err := ValidateFrameData(data); err != nil {
// Use ultra-fast validation for critical audio path
if err := ValidateAudioFrameUltraFast(data); err != nil {
logger := logging.GetDefaultLogger().With().Str("component", AudioInputServerComponent).Logger()
logger.Error().Err(err).Msg("Frame validation failed")
return fmt.Errorf("input frame validation failed: %w", err)
@ -635,7 +635,7 @@ func (aic *AudioInputClient) SendFrame(frame []byte) error {
}
// Validate frame data before sending
if err := ValidateFrameData(frame); err != nil {
if err := ValidateAudioFrameUltraFast(frame); err != nil {
logger := logging.GetDefaultLogger().With().Str("component", AudioInputClientComponent).Logger()
logger.Error().Err(err).Msg("Frame validation failed")
return fmt.Errorf("input frame validation failed: %w", err)

View File

@ -103,7 +103,7 @@ func (aim *AudioInputIPCManager) WriteOpusFrame(frame []byte) error {
}
// Validate frame data
if err := ValidateFrameData(frame); err != nil {
if err := ValidateAudioFrameUltraFast(frame); err != nil {
atomic.AddInt64(&aim.metrics.FramesDropped, 1)
aim.logger.Debug().Err(err).Msg("invalid frame data")
return err

View File

@ -260,8 +260,8 @@ func (s *AudioOutputServer) Close() error {
}
func (s *AudioOutputServer) SendFrame(frame []byte) error {
// Comprehensive frame validation
if err := ValidateFrameData(frame); err != nil {
// Use ultra-fast validation for critical audio path
if err := ValidateAudioFrameUltraFast(frame); err != nil {
logger := logging.GetDefaultLogger().With().Str("component", AudioOutputServerComponent).Logger()
logger.Error().Err(err).Msg("Frame validation failed")
return fmt.Errorf("output frame validation failed: %w", err)

View File

@ -170,8 +170,8 @@ func (r *AudioRelay) relayLoop() {
// forwardToWebRTC forwards a frame to the WebRTC audio track
func (r *AudioRelay) forwardToWebRTC(frame []byte) error {
// Validate frame data before processing
if err := ValidateFrameData(frame); err != nil {
// Use ultra-fast validation for critical audio path
if err := ValidateAudioFrameUltraFast(frame); err != nil {
r.incrementDropped()
r.logger.Debug().Err(err).Msg("invalid frame data in relay")
return err

View File

@ -35,34 +35,6 @@ func ValidateAudioQuality(quality AudioQuality) error {
return nil
}
// ValidateFrameData validates audio frame data with comprehensive boundary checks
func ValidateFrameData(data []byte) error {
if data == nil {
return fmt.Errorf("%w: frame data is nil", ErrInvalidFrameData)
}
if len(data) == 0 {
return fmt.Errorf("%w: frame data is empty", ErrInvalidFrameData)
}
config := GetConfig()
// Check minimum frame size
if len(data) < config.MinFrameSize {
return fmt.Errorf("%w: frame size %d below minimum %d",
ErrInvalidFrameSize, len(data), config.MinFrameSize)
}
// Check maximum frame size
if len(data) > config.MaxAudioFrameSize {
return fmt.Errorf("%w: frame size %d exceeds maximum %d",
ErrInvalidFrameSize, len(data), config.MaxAudioFrameSize)
}
// Validate frame alignment for audio samples (must be even for 16-bit samples)
if len(data)%2 != 0 {
return fmt.Errorf("%w: frame size %d not aligned for 16-bit samples",
ErrInvalidFrameSize, len(data))
}
return nil
}
// ValidateZeroCopyFrame validates zero-copy audio frame
func ValidateZeroCopyFrame(frame *ZeroCopyAudioFrame) error {
if frame == nil {
@ -327,6 +299,8 @@ func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
}
// ValidateAudioFrameFast performs fast validation of audio frame data
// ValidateAudioFrameFast provides minimal validation for critical audio processing paths
// This function is optimized for performance and only checks essential safety bounds
func ValidateAudioFrameFast(data []byte) error {
if len(data) == 0 {
return fmt.Errorf("%w: frame data is empty", ErrInvalidFrameData)
@ -338,6 +312,17 @@ func ValidateAudioFrameFast(data []byte) error {
return nil
}
// ValidateAudioFrameUltraFast provides zero-overhead validation for ultra-critical paths
// This function only checks for nil/empty data and maximum size to prevent buffer overruns
// Use this in hot audio processing loops where every microsecond matters
func ValidateAudioFrameUltraFast(data []byte) error {
// Only check for catastrophic failures that could crash the system
if len(data) == 0 || len(data) > 8192 { // Hard-coded 8KB safety limit
return ErrInvalidFrameData
}
return nil
}
// WrapWithMetadata wraps error with metadata for enhanced validation context
func WrapWithMetadata(err error, component, operation string, metadata map[string]interface{}) error {
if err == nil {

View File

@ -62,46 +62,25 @@ func testAudioQualityValidation(t *testing.T) {
}
}
// testFrameDataValidation tests frame data validation with various edge cases
// testFrameDataValidation tests frame data validation with various edge cases using modern validation
func testFrameDataValidation(t *testing.T) {
config := GetConfig()
// Test nil data
err := ValidateFrameData(nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame data is nil")
// Test empty data
err = ValidateFrameData([]byte{})
err := ValidateAudioFrameFast([]byte{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "frame data is empty")
// Test data below minimum size
if config.MinFrameSize > 0 {
smallData := make([]byte, config.MinFrameSize-1)
err = ValidateFrameData(smallData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "below minimum")
}
// Test data above maximum size
largeData := make([]byte, config.MaxAudioFrameSize+1)
err = ValidateFrameData(largeData)
err = ValidateAudioFrameFast(largeData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "exceeds maximum")
// Test unaligned data (odd number of bytes for 16-bit samples)
oddData := make([]byte, 1001) // Odd number
if len(oddData) >= config.MinFrameSize {
err = ValidateFrameData(oddData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not aligned")
}
// Test valid aligned data
validData := make([]byte, 1000) // Even number, within bounds
if len(validData) >= config.MinFrameSize && len(validData) <= config.MaxAudioFrameSize {
err = ValidateFrameData(validData)
// Test valid data
validData := make([]byte, 1000) // Within bounds
if len(validData) <= config.MaxAudioFrameSize {
err = ValidateAudioFrameFast(validData)
assert.NoError(t, err)
}
}