mirror of https://github.com/jetkvm/kvm.git
355 lines
11 KiB
Go
355 lines
11 KiB
Go
//go:build cgo || arm
|
|
// +build cgo arm
|
|
|
|
package audio
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Validation errors
|
|
var (
|
|
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")
|
|
|
|
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 is deprecated - quality is now fixed at optimal settings
|
|
func ValidateAudioQuality(quality int) error {
|
|
// Quality validation removed - using fixed optimal configuration
|
|
return nil
|
|
}
|
|
|
|
// ValidateZeroCopyFrame validates zero-copy audio frame
|
|
// Optimized to use cached max frame size
|
|
func ValidateZeroCopyFrame(frame *ZeroCopyAudioFrame) error {
|
|
if frame == nil {
|
|
return ErrInvalidFrameData
|
|
}
|
|
data := frame.Data()
|
|
if len(data) == 0 {
|
|
return ErrInvalidFrameData
|
|
}
|
|
|
|
// Fast path: use cached max frame size
|
|
maxFrameSize := cachedMaxFrameSize
|
|
if maxFrameSize == 0 {
|
|
// Fallback: get from cache
|
|
cache := Config
|
|
maxFrameSize = cache.MaxAudioFrameSize
|
|
if maxFrameSize == 0 {
|
|
// Last resort: use default
|
|
maxFrameSize = cache.MaxAudioFrameSize
|
|
}
|
|
// Cache globally for next calls
|
|
cachedMaxFrameSize = maxFrameSize
|
|
}
|
|
|
|
if len(data) > maxFrameSize {
|
|
return ErrInvalidFrameSize
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateBufferSize validates buffer size parameters with enhanced boundary checks
|
|
// Optimized for minimal overhead in hotpath
|
|
func ValidateBufferSize(size int) error {
|
|
if size <= 0 {
|
|
return fmt.Errorf("%w: buffer size %d must be positive", ErrInvalidBufferSize, size)
|
|
}
|
|
// Single boundary check using pre-cached value
|
|
if size > Config.SocketMaxBuffer {
|
|
return fmt.Errorf("%w: buffer size %d exceeds maximum %d",
|
|
ErrInvalidBufferSize, size, Config.SocketMaxBuffer)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateLatency validates latency duration values with reasonable bounds
|
|
// Optimized to use AudioConfigCache for frequently accessed values
|
|
func ValidateLatency(latency time.Duration) error {
|
|
if latency < 0 {
|
|
return fmt.Errorf("%w: latency %v cannot be negative", ErrInvalidLatency, latency)
|
|
}
|
|
|
|
// Fast path: check against cached max latency
|
|
cache := Config
|
|
maxLatency := time.Duration(cache.MaxLatency)
|
|
|
|
// If we have a valid cached value, use it
|
|
if maxLatency > 0 {
|
|
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 > maxLatency {
|
|
return fmt.Errorf("%w: latency %v exceeds maximum %v",
|
|
ErrInvalidLatency, latency, maxLatency)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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
|
|
// Optimized to use AudioConfigCache for frequently accessed values
|
|
func ValidateMetricsInterval(interval time.Duration) error {
|
|
// Fast path: check against cached values
|
|
cache := Config
|
|
minInterval := time.Duration(cache.MinMetricsUpdateInterval)
|
|
maxInterval := time.Duration(cache.MaxMetricsUpdateInterval)
|
|
|
|
// If we have valid cached values, use them
|
|
if minInterval > 0 && maxInterval > 0 {
|
|
if interval < minInterval {
|
|
return fmt.Errorf("%w: interval %v below minimum %v",
|
|
ErrInvalidMetricsInterval, interval, minInterval)
|
|
}
|
|
if interval > maxInterval {
|
|
return fmt.Errorf("%w: interval %v exceeds maximum %v",
|
|
ErrInvalidMetricsInterval, interval, maxInterval)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
minInterval = Config.MinMetricsUpdateInterval
|
|
maxInterval = Config.MaxMetricsUpdateInterval
|
|
if interval < minInterval {
|
|
return ErrInvalidMetricsInterval
|
|
}
|
|
if interval > maxInterval {
|
|
return ErrInvalidMetricsInterval
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateInputIPCConfig validates input IPC configuration
|
|
func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
|
|
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 {
|
|
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
|
|
}
|
|
|
|
// ValidateSampleRate validates audio sample rate values
|
|
// Optimized for minimal overhead in hotpath
|
|
func ValidateSampleRate(sampleRate int) error {
|
|
if sampleRate <= 0 {
|
|
return fmt.Errorf("%w: sample rate %d must be positive", ErrInvalidSampleRate, sampleRate)
|
|
}
|
|
// Direct validation against valid rates
|
|
for _, rate := range Config.ValidSampleRates {
|
|
if sampleRate == rate {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("%w: sample rate %d not in valid rates %v",
|
|
ErrInvalidSampleRate, sampleRate, Config.ValidSampleRates)
|
|
}
|
|
|
|
// ValidateChannelCount validates audio channel count
|
|
// Optimized for minimal overhead in hotpath
|
|
func ValidateChannelCount(channels int) error {
|
|
if channels <= 0 {
|
|
return fmt.Errorf("%w: channel count %d must be positive", ErrInvalidChannels, channels)
|
|
}
|
|
// Direct boundary check
|
|
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 for minimal overhead in hotpath
|
|
func ValidateBitrate(bitrate int) error {
|
|
if bitrate <= 0 {
|
|
return fmt.Errorf("%w: bitrate %d must be positive", ErrInvalidBitrate, bitrate)
|
|
}
|
|
// Direct boundary check with single conversion
|
|
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 := Config
|
|
|
|
// Convert frameSize (samples) to duration for comparison
|
|
cachedFrameSize := cache.FrameSize
|
|
cachedSampleRate := cache.SampleRate
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// Fast path: Check against cached min/max frame duration
|
|
cachedMinDuration := time.Duration(cache.MinFrameDuration)
|
|
cachedMaxDuration := time.Duration(cache.MaxFrameDuration)
|
|
|
|
if cachedMinDuration > 0 && cachedMaxDuration > 0 {
|
|
if duration < cachedMinDuration {
|
|
return fmt.Errorf("%w: frame duration %v below minimum %v",
|
|
ErrInvalidFrameDuration, duration, cachedMinDuration)
|
|
}
|
|
if duration > cachedMaxDuration {
|
|
return fmt.Errorf("%w: frame duration %v exceeds maximum %v",
|
|
ErrInvalidFrameDuration, duration, cachedMaxDuration)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Slow path: Use current config values
|
|
updatedMinDuration := time.Duration(cache.MinFrameDuration)
|
|
updatedMaxDuration := time.Duration(cache.MaxFrameDuration)
|
|
|
|
if duration < updatedMinDuration {
|
|
return fmt.Errorf("%w: frame duration %v below minimum %v",
|
|
ErrInvalidFrameDuration, duration, updatedMinDuration)
|
|
}
|
|
if duration > updatedMaxDuration {
|
|
return fmt.Errorf("%w: frame duration %v exceeds maximum %v",
|
|
ErrInvalidFrameDuration, duration, updatedMaxDuration)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateAudioConfigConstants validates audio configuration constants
|
|
func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
|
|
// Quality validation removed - using fixed optimal configuration
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
cachedMaxFrameSize = Config.MaxAudioFrameSize
|
|
|
|
// Initialize the global audio config cache
|
|
cachedMaxFrameSize = Config.MaxAudioFrameSize
|
|
}
|
|
|
|
// ValidateAudioFrame validates audio frame data with cached max size for performance
|
|
//
|
|
//go:inline
|
|
func ValidateAudioFrame(data []byte) error {
|
|
// Fast path: check length against cached max size in single operation
|
|
dataLen := len(data)
|
|
if dataLen == 0 {
|
|
return ErrFrameDataEmpty
|
|
}
|
|
|
|
// Use global cached value for fastest access - updated during initialization
|
|
maxSize := cachedMaxFrameSize
|
|
if maxSize == 0 {
|
|
// Fallback: get from cache only if global cache not initialized
|
|
cache := Config
|
|
maxSize = cache.MaxAudioFrameSize
|
|
if maxSize == 0 {
|
|
// Last resort: get fresh value
|
|
maxSize = cache.MaxAudioFrameSize
|
|
}
|
|
// Cache the value globally for next calls
|
|
cachedMaxFrameSize = maxSize
|
|
}
|
|
|
|
// Single comparison for validation
|
|
if dataLen > maxSize {
|
|
return ErrFrameDataTooLarge
|
|
}
|
|
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)
|
|
}
|