mirror of
https://github.com/jetkvm/kvm.git
synced 2025-09-16 08:38:14 +00:00
Add atomic fields to AudioConfigCache for validation parameters to enable lock-free access Optimize validation functions to use cached values for common cases Move AudioFrameBatch to separate file and update validation logic
478 lines
16 KiB
Go
478 lines
16 KiB
Go
//go:build cgo || arm
|
|
// +build cgo arm
|
|
|
|
package audio
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Validation errors
|
|
var (
|
|
ErrInvalidAudioQuality = errors.New("invalid audio quality level")
|
|
ErrInvalidFrameSize = errors.New("invalid frame size")
|
|
ErrInvalidFrameData = errors.New("invalid frame data")
|
|
ErrFrameDataEmpty = errors.New("invalid frame data: frame data is empty")
|
|
ErrFrameDataTooLarge = errors.New("invalid frame data: exceeds maximum")
|
|
ErrInvalidBufferSize = errors.New("invalid buffer size")
|
|
ErrInvalidPriority = errors.New("invalid priority value")
|
|
ErrInvalidLatency = errors.New("invalid latency value")
|
|
ErrInvalidConfiguration = errors.New("invalid configuration")
|
|
ErrInvalidSocketConfig = errors.New("invalid socket configuration")
|
|
ErrInvalidMetricsInterval = errors.New("invalid metrics interval")
|
|
ErrInvalidSampleRate = errors.New("invalid sample rate")
|
|
ErrInvalidChannels = errors.New("invalid channels")
|
|
ErrInvalidBitrate = errors.New("invalid bitrate")
|
|
ErrInvalidFrameDuration = errors.New("invalid frame duration")
|
|
ErrInvalidOffset = errors.New("invalid offset")
|
|
ErrInvalidLength = errors.New("invalid length")
|
|
)
|
|
|
|
// ValidateAudioQuality validates audio quality enum values with enhanced checks
|
|
func ValidateAudioQuality(quality AudioQuality) error {
|
|
// Validate enum range
|
|
if quality < AudioQualityLow || quality > AudioQualityUltra {
|
|
return fmt.Errorf("%w: quality value %d outside valid range [%d, %d]",
|
|
ErrInvalidAudioQuality, int(quality), int(AudioQualityLow), int(AudioQualityUltra))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateZeroCopyFrame validates zero-copy audio frame
|
|
func ValidateZeroCopyFrame(frame *ZeroCopyAudioFrame) error {
|
|
if frame == nil {
|
|
return ErrInvalidFrameData
|
|
}
|
|
data := frame.Data()
|
|
if len(data) == 0 {
|
|
return ErrInvalidFrameData
|
|
}
|
|
// Use config value
|
|
maxFrameSize := GetConfig().MaxAudioFrameSize
|
|
if len(data) > maxFrameSize {
|
|
return ErrInvalidFrameSize
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateBufferSize validates buffer size parameters with enhanced boundary checks
|
|
// Optimized to use AudioConfigCache for frequently accessed values
|
|
func ValidateBufferSize(size int) error {
|
|
if size <= 0 {
|
|
return fmt.Errorf("%w: buffer size %d must be positive", ErrInvalidBufferSize, size)
|
|
}
|
|
|
|
// Fast path: Check against cached max frame size
|
|
cache := GetCachedConfig()
|
|
maxFrameSize := int(cache.maxAudioFrameSize.Load())
|
|
|
|
// Most common case: validating a buffer that's sized for audio frames
|
|
if maxFrameSize > 0 && size <= maxFrameSize {
|
|
return nil
|
|
}
|
|
|
|
// Slower path: full validation against SocketMaxBuffer
|
|
config := GetConfig()
|
|
// Use SocketMaxBuffer as the upper limit for general buffer validation
|
|
// This allows for socket buffers while still preventing extremely large allocations
|
|
if size > config.SocketMaxBuffer {
|
|
return fmt.Errorf("%w: buffer size %d exceeds maximum %d",
|
|
ErrInvalidBufferSize, size, config.SocketMaxBuffer)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateThreadPriority validates thread priority values with system limits
|
|
func ValidateThreadPriority(priority int) error {
|
|
const minPriority, maxPriority = -20, 19
|
|
if priority < minPriority || priority > maxPriority {
|
|
return fmt.Errorf("%w: priority %d outside valid range [%d, %d]",
|
|
ErrInvalidPriority, priority, minPriority, maxPriority)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateLatency validates latency duration values with reasonable bounds
|
|
func ValidateLatency(latency time.Duration) error {
|
|
if latency < 0 {
|
|
return fmt.Errorf("%w: latency %v cannot be negative", ErrInvalidLatency, latency)
|
|
}
|
|
config := GetConfig()
|
|
minLatency := time.Millisecond // Minimum reasonable latency
|
|
if latency > 0 && latency < minLatency {
|
|
return fmt.Errorf("%w: latency %v below minimum %v",
|
|
ErrInvalidLatency, latency, minLatency)
|
|
}
|
|
if latency > config.MaxLatency {
|
|
return fmt.Errorf("%w: latency %v exceeds maximum %v",
|
|
ErrInvalidLatency, latency, config.MaxLatency)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateMetricsInterval validates metrics update interval
|
|
func ValidateMetricsInterval(interval time.Duration) error {
|
|
// Use config values
|
|
config := GetConfig()
|
|
minInterval := config.MinMetricsUpdateInterval
|
|
maxInterval := config.MaxMetricsUpdateInterval
|
|
if interval < minInterval {
|
|
return ErrInvalidMetricsInterval
|
|
}
|
|
if interval > maxInterval {
|
|
return ErrInvalidMetricsInterval
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateAdaptiveBufferConfig validates adaptive buffer configuration
|
|
func ValidateAdaptiveBufferConfig(minSize, maxSize, defaultSize int) error {
|
|
if minSize <= 0 || maxSize <= 0 || defaultSize <= 0 {
|
|
return ErrInvalidBufferSize
|
|
}
|
|
if minSize >= maxSize {
|
|
return ErrInvalidBufferSize
|
|
}
|
|
if defaultSize < minSize || defaultSize > maxSize {
|
|
return ErrInvalidBufferSize
|
|
}
|
|
// Validate against global limits
|
|
maxBuffer := GetConfig().SocketMaxBuffer
|
|
if maxSize > maxBuffer {
|
|
return ErrInvalidBufferSize
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateInputIPCConfig validates input IPC configuration
|
|
func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
|
|
// Use config values
|
|
config := GetConfig()
|
|
minSampleRate := config.MinSampleRate
|
|
maxSampleRate := config.MaxSampleRate
|
|
maxChannels := config.MaxChannels
|
|
if sampleRate < minSampleRate || sampleRate > maxSampleRate {
|
|
return ErrInvalidSampleRate
|
|
}
|
|
if channels < 1 || channels > maxChannels {
|
|
return ErrInvalidChannels
|
|
}
|
|
if frameSize <= 0 {
|
|
return ErrInvalidFrameSize
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateOutputIPCConfig validates output IPC configuration
|
|
func ValidateOutputIPCConfig(sampleRate, channels, frameSize int) error {
|
|
// Use config values
|
|
config := GetConfig()
|
|
minSampleRate := config.MinSampleRate
|
|
maxSampleRate := config.MaxSampleRate
|
|
maxChannels := config.MaxChannels
|
|
if sampleRate < minSampleRate || sampleRate > maxSampleRate {
|
|
return ErrInvalidSampleRate
|
|
}
|
|
if channels < 1 || channels > maxChannels {
|
|
return ErrInvalidChannels
|
|
}
|
|
if frameSize <= 0 {
|
|
return ErrInvalidFrameSize
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateLatencyConfig validates latency monitor configuration
|
|
func ValidateLatencyConfig(config LatencyConfig) error {
|
|
if err := ValidateLatency(config.TargetLatency); err != nil {
|
|
return err
|
|
}
|
|
if err := ValidateLatency(config.MaxLatency); err != nil {
|
|
return err
|
|
}
|
|
if config.TargetLatency >= config.MaxLatency {
|
|
return ErrInvalidLatency
|
|
}
|
|
if err := ValidateMetricsInterval(config.OptimizationInterval); err != nil {
|
|
return err
|
|
}
|
|
if config.HistorySize <= 0 {
|
|
return ErrInvalidBufferSize
|
|
}
|
|
if config.JitterThreshold < 0 {
|
|
return ErrInvalidLatency
|
|
}
|
|
if config.AdaptiveThreshold < 0 || config.AdaptiveThreshold > 1.0 {
|
|
return ErrInvalidConfiguration
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateSampleRate validates audio sample rate values
|
|
// Optimized to use AudioConfigCache for frequently accessed values
|
|
func ValidateSampleRate(sampleRate int) error {
|
|
if sampleRate <= 0 {
|
|
return fmt.Errorf("%w: sample rate %d must be positive", ErrInvalidSampleRate, sampleRate)
|
|
}
|
|
|
|
// Fast path: Check against cached sample rate first
|
|
cache := GetCachedConfig()
|
|
cachedRate := int(cache.sampleRate.Load())
|
|
|
|
// Most common case: validating against the current sample rate
|
|
if sampleRate == cachedRate {
|
|
return nil
|
|
}
|
|
|
|
// Slower path: check against all valid rates
|
|
config := GetConfig()
|
|
validRates := config.ValidSampleRates
|
|
for _, rate := range validRates {
|
|
if sampleRate == rate {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("%w: sample rate %d not in supported rates %v",
|
|
ErrInvalidSampleRate, sampleRate, validRates)
|
|
}
|
|
|
|
// ValidateChannelCount validates audio channel count
|
|
// Optimized to use AudioConfigCache for frequently accessed values
|
|
func ValidateChannelCount(channels int) error {
|
|
if channels <= 0 {
|
|
return fmt.Errorf("%w: channel count %d must be positive", ErrInvalidChannels, channels)
|
|
}
|
|
|
|
// Fast path: Check against cached channels first
|
|
cache := GetCachedConfig()
|
|
cachedChannels := int(cache.channels.Load())
|
|
|
|
// Most common case: validating against the current channel count
|
|
if channels == cachedChannels {
|
|
return nil
|
|
}
|
|
|
|
// Check against max channels - still using cache to avoid GetConfig()
|
|
// Note: We don't have maxChannels in the cache yet, so we'll use GetConfig() for now
|
|
config := GetConfig()
|
|
if channels > config.MaxChannels {
|
|
return fmt.Errorf("%w: channel count %d exceeds maximum %d",
|
|
ErrInvalidChannels, channels, config.MaxChannels)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateBitrate validates audio bitrate values (expects kbps)
|
|
// Optimized to use AudioConfigCache for frequently accessed values
|
|
func ValidateBitrate(bitrate int) error {
|
|
if bitrate <= 0 {
|
|
return fmt.Errorf("%w: bitrate %d must be positive", ErrInvalidBitrate, bitrate)
|
|
}
|
|
|
|
// Fast path: Check against cached bitrate values
|
|
cache := GetCachedConfig()
|
|
minBitrate := int(cache.minOpusBitrate.Load())
|
|
maxBitrate := int(cache.maxOpusBitrate.Load())
|
|
|
|
// If we have valid cached values, use them
|
|
if minBitrate > 0 && maxBitrate > 0 {
|
|
// Convert kbps to bps for comparison with config limits
|
|
bitrateInBps := bitrate * 1000
|
|
if bitrateInBps < minBitrate {
|
|
return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps",
|
|
ErrInvalidBitrate, bitrate, bitrateInBps, minBitrate)
|
|
}
|
|
if bitrateInBps > maxBitrate {
|
|
return fmt.Errorf("%w: bitrate %d kbps (%d bps) exceeds maximum %d bps",
|
|
ErrInvalidBitrate, bitrate, bitrateInBps, maxBitrate)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Slower path: full validation with GetConfig()
|
|
config := GetConfig()
|
|
// Convert kbps to bps for comparison with config limits
|
|
bitrateInBps := bitrate * 1000
|
|
if bitrateInBps < config.MinOpusBitrate {
|
|
return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps",
|
|
ErrInvalidBitrate, bitrate, bitrateInBps, config.MinOpusBitrate)
|
|
}
|
|
if bitrateInBps > config.MaxOpusBitrate {
|
|
return fmt.Errorf("%w: bitrate %d kbps (%d bps) exceeds maximum %d bps",
|
|
ErrInvalidBitrate, bitrate, bitrateInBps, config.MaxOpusBitrate)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateFrameDuration validates frame duration values
|
|
// Optimized to use AudioConfigCache for frequently accessed values
|
|
func ValidateFrameDuration(duration time.Duration) error {
|
|
if duration <= 0 {
|
|
return fmt.Errorf("%w: frame duration %v must be positive", ErrInvalidFrameDuration, duration)
|
|
}
|
|
|
|
// Fast path: Check against cached frame size first
|
|
cache := GetCachedConfig()
|
|
|
|
// Convert frameSize (samples) to duration for comparison
|
|
// Note: This calculation should match how frameSize is converted to duration elsewhere
|
|
cachedFrameSize := int(cache.frameSize.Load())
|
|
cachedSampleRate := int(cache.sampleRate.Load())
|
|
|
|
// Only do this calculation if we have valid cached values
|
|
if cachedFrameSize > 0 && cachedSampleRate > 0 {
|
|
cachedDuration := time.Duration(cachedFrameSize) * time.Second / time.Duration(cachedSampleRate)
|
|
|
|
// Most common case: validating against the current frame duration
|
|
if duration == cachedDuration {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Slower path: full validation against min/max
|
|
config := GetConfig()
|
|
if duration < config.MinFrameDuration {
|
|
return fmt.Errorf("%w: frame duration %v below minimum %v",
|
|
ErrInvalidFrameDuration, duration, config.MinFrameDuration)
|
|
}
|
|
if duration > config.MaxFrameDuration {
|
|
return fmt.Errorf("%w: frame duration %v exceeds maximum %v",
|
|
ErrInvalidFrameDuration, duration, config.MaxFrameDuration)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateAudioConfigComplete performs comprehensive audio configuration validation
|
|
// Uses optimized validation functions that leverage AudioConfigCache
|
|
func ValidateAudioConfigComplete(config AudioConfig) error {
|
|
// Fast path: Check if all values match the current cached configuration
|
|
cache := GetCachedConfig()
|
|
cachedSampleRate := int(cache.sampleRate.Load())
|
|
cachedChannels := int(cache.channels.Load())
|
|
cachedBitrate := int(cache.opusBitrate.Load()) / 1000 // Convert from bps to kbps
|
|
cachedFrameSize := int(cache.frameSize.Load())
|
|
|
|
// Only do this calculation if we have valid cached values
|
|
if cachedSampleRate > 0 && cachedChannels > 0 && cachedBitrate > 0 && cachedFrameSize > 0 {
|
|
cachedDuration := time.Duration(cachedFrameSize) * time.Second / time.Duration(cachedSampleRate)
|
|
|
|
// Most common case: validating the current configuration
|
|
if config.SampleRate == cachedSampleRate &&
|
|
config.Channels == cachedChannels &&
|
|
config.Bitrate == cachedBitrate &&
|
|
config.FrameSize == cachedDuration {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Slower path: validate each parameter individually
|
|
if err := ValidateAudioQuality(config.Quality); err != nil {
|
|
return fmt.Errorf("quality validation failed: %w", err)
|
|
}
|
|
if err := ValidateBitrate(config.Bitrate); err != nil {
|
|
return fmt.Errorf("bitrate validation failed: %w", err)
|
|
}
|
|
if err := ValidateSampleRate(config.SampleRate); err != nil {
|
|
return fmt.Errorf("sample rate validation failed: %w", err)
|
|
}
|
|
if err := ValidateChannelCount(config.Channels); err != nil {
|
|
return fmt.Errorf("channel count validation failed: %w", err)
|
|
}
|
|
if err := ValidateFrameDuration(config.FrameSize); err != nil {
|
|
return fmt.Errorf("frame duration validation failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateAudioConfigConstants validates audio configuration constants
|
|
func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
|
|
// Validate that audio quality constants are within valid ranges
|
|
for _, quality := range []AudioQuality{AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra} {
|
|
if err := ValidateAudioQuality(quality); err != nil {
|
|
return fmt.Errorf("invalid audio quality constant %v: %w", quality, err)
|
|
}
|
|
}
|
|
// Validate configuration values if config is provided
|
|
if config != nil {
|
|
if config.MaxFrameSize <= 0 {
|
|
return fmt.Errorf("invalid MaxFrameSize: %d", config.MaxFrameSize)
|
|
}
|
|
if config.SampleRate <= 0 {
|
|
return fmt.Errorf("invalid SampleRate: %d", config.SampleRate)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Note: We're transitioning from individual cached values to using AudioConfigCache
|
|
// for better consistency and reduced maintenance overhead
|
|
|
|
// Global variable for backward compatibility
|
|
var cachedMaxFrameSize int
|
|
|
|
// InitValidationCache initializes cached validation values with actual config
|
|
func InitValidationCache() {
|
|
// Initialize the global cache variable for backward compatibility
|
|
config := GetConfig()
|
|
cachedMaxFrameSize = config.MaxAudioFrameSize
|
|
|
|
// Update the global audio config cache
|
|
GetCachedConfig().Update()
|
|
}
|
|
|
|
// ValidateAudioFrame provides optimized validation for audio frame data
|
|
// This is the primary validation function used in all audio processing paths
|
|
//
|
|
// Performance optimizations:
|
|
// - Uses AudioConfigCache to eliminate GetConfig() call overhead
|
|
// - Single branch condition for optimal CPU pipeline efficiency
|
|
// - Inlined length checks for minimal overhead
|
|
// - Pre-allocated error messages for minimal allocations
|
|
//
|
|
//go:inline
|
|
func ValidateAudioFrame(data []byte) error {
|
|
// Fast path: empty check first to avoid unnecessary cache access
|
|
dataLen := len(data)
|
|
if dataLen == 0 {
|
|
return ErrFrameDataEmpty
|
|
}
|
|
|
|
// Get cached config - this is a pointer access, not a function call
|
|
cache := GetCachedConfig()
|
|
|
|
// Use atomic access to maxAudioFrameSize for lock-free validation
|
|
maxSize := int(cache.maxAudioFrameSize.Load())
|
|
|
|
// If cache not initialized or value is zero, use global cached value or update
|
|
if maxSize == 0 {
|
|
if cachedMaxFrameSize > 0 {
|
|
maxSize = cachedMaxFrameSize
|
|
} else {
|
|
cache.Update()
|
|
maxSize = int(cache.maxAudioFrameSize.Load())
|
|
if maxSize == 0 {
|
|
// Fallback to global config if cache still not initialized
|
|
maxSize = GetConfig().MaxAudioFrameSize
|
|
}
|
|
}
|
|
}
|
|
|
|
// Optimized validation with error message
|
|
if dataLen > maxSize {
|
|
// Use formatted error since we can't guarantee pre-allocated error is available
|
|
return fmt.Errorf("%w: frame size %d exceeds maximum %d bytes",
|
|
ErrFrameDataTooLarge, dataLen, maxSize)
|
|
}
|
|
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 {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%s.%s: %w (metadata: %+v)", component, operation, err, metadata)
|
|
}
|