mirror of https://github.com/jetkvm/kvm.git
289 lines
9.8 KiB
Go
289 lines
9.8 KiB
Go
// Package audio provides a comprehensive real-time audio processing system for JetKVM.
|
|
//
|
|
// # Architecture Overview
|
|
//
|
|
// The audio package implements a multi-component architecture designed for low-latency,
|
|
// high-quality audio streaming in embedded ARM environments. The system consists of:
|
|
//
|
|
// - Audio Output Pipeline: Receives compressed audio frames, decodes via Opus, and
|
|
// outputs to ALSA-compatible audio devices
|
|
// - Audio Input Pipeline: Captures microphone input, encodes via Opus, and streams
|
|
// to connected clients
|
|
// - Adaptive Buffer Management: Dynamically adjusts buffer sizes based on system
|
|
// load and latency requirements
|
|
// - Zero-Copy Frame Pool: Minimizes memory allocations through frame reuse
|
|
// - IPC Communication: Unix domain sockets for inter-process communication
|
|
// - Process Supervision: Automatic restart and health monitoring of audio subprocesses
|
|
//
|
|
// # Key Components
|
|
//
|
|
// ## Buffer Pool System (buffer_pool.go)
|
|
// Implements a two-tier buffer pool with separate pools for audio frames and control
|
|
// messages. Uses sync.Pool for efficient memory reuse and tracks allocation statistics.
|
|
//
|
|
// ## Zero-Copy Frame Management (zero_copy.go)
|
|
// Provides reference-counted audio frames that can be shared between components
|
|
// without copying data. Includes automatic cleanup and pool-based allocation.
|
|
//
|
|
// ## Adaptive Buffering Algorithm (adaptive_buffer.go)
|
|
// Dynamically adjusts buffer sizes based on:
|
|
// - System CPU and memory usage
|
|
// - Audio latency measurements
|
|
// - Frame drop rates
|
|
// - Network conditions
|
|
//
|
|
// The algorithm uses exponential smoothing and configurable thresholds to balance
|
|
// latency and stability. Buffer sizes are adjusted in discrete steps to prevent
|
|
// oscillation.
|
|
//
|
|
// ## Latency Monitoring (latency_monitor.go)
|
|
// Tracks end-to-end audio latency using high-resolution timestamps. Implements
|
|
// adaptive optimization that adjusts system parameters when latency exceeds
|
|
// configured thresholds.
|
|
//
|
|
// ## Process Supervision (supervisor.go)
|
|
// Manages audio subprocess lifecycle with automatic restart capabilities.
|
|
// Monitors process health and implements exponential backoff for restart attempts.
|
|
//
|
|
// # Quality Levels
|
|
//
|
|
// The system supports four quality presets optimized for different use cases:
|
|
// - Low: 32kbps output, 16kbps input - minimal bandwidth, voice-optimized
|
|
// - Medium: 96kbps output, 64kbps input - balanced quality and bandwidth
|
|
// - High: 192kbps output, 128kbps input - high quality for music
|
|
// - Ultra: 320kbps output, 256kbps input - maximum quality
|
|
//
|
|
// # Configuration System
|
|
//
|
|
// All configuration is centralized in config_constants.go, allowing runtime
|
|
// tuning of performance parameters. Key configuration areas include:
|
|
// - Opus codec parameters (bitrate, complexity, VBR settings)
|
|
// - Buffer sizes and pool configurations
|
|
// - Latency thresholds and optimization parameters
|
|
// - Process monitoring and restart policies
|
|
//
|
|
// # Thread Safety
|
|
//
|
|
// All public APIs are thread-safe. Internal synchronization uses:
|
|
// - atomic operations for performance counters
|
|
// - sync.RWMutex for configuration updates
|
|
// - sync.Pool for buffer management
|
|
// - channel-based communication for IPC
|
|
//
|
|
// # Error Handling
|
|
//
|
|
// The system implements comprehensive error handling with:
|
|
// - Graceful degradation on component failures
|
|
// - Automatic retry with exponential backoff
|
|
// - Detailed error context for debugging
|
|
// - Metrics collection for monitoring
|
|
//
|
|
// # Performance Characteristics
|
|
//
|
|
// Designed for embedded ARM systems with limited resources:
|
|
// - Sub-50ms end-to-end latency under normal conditions
|
|
// - Memory usage scales with buffer configuration
|
|
// - CPU usage optimized through zero-copy operations
|
|
// - Network bandwidth adapts to quality settings
|
|
//
|
|
// # Usage Example
|
|
//
|
|
// config := GetAudioConfig()
|
|
// SetAudioQuality(AudioQualityHigh)
|
|
//
|
|
// // Audio output will automatically start when frames are received
|
|
// metrics := GetAudioMetrics()
|
|
// fmt.Printf("Latency: %v, Frames: %d\n", metrics.AverageLatency, metrics.FramesReceived)
|
|
package audio
|
|
|
|
import (
|
|
"errors"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrAudioAlreadyRunning = errors.New("audio already running")
|
|
)
|
|
|
|
// MaxAudioFrameSize is now retrieved from centralized config
|
|
func GetMaxAudioFrameSize() int {
|
|
return GetConfig().MaxAudioFrameSize
|
|
}
|
|
|
|
// AudioQuality represents different audio quality presets
|
|
type AudioQuality int
|
|
|
|
const (
|
|
AudioQualityLow AudioQuality = iota
|
|
AudioQualityMedium
|
|
AudioQualityHigh
|
|
AudioQualityUltra
|
|
)
|
|
|
|
// AudioConfig holds configuration for audio processing
|
|
type AudioConfig struct {
|
|
Quality AudioQuality
|
|
Bitrate int // kbps
|
|
SampleRate int // Hz
|
|
Channels int
|
|
FrameSize time.Duration // ms
|
|
}
|
|
|
|
// AudioMetrics tracks audio performance metrics
|
|
type AudioMetrics struct {
|
|
FramesReceived int64
|
|
FramesDropped int64
|
|
BytesProcessed int64
|
|
ConnectionDrops int64
|
|
LastFrameTime time.Time
|
|
AverageLatency time.Duration
|
|
}
|
|
|
|
var (
|
|
currentConfig = AudioConfig{
|
|
Quality: AudioQualityMedium,
|
|
Bitrate: GetConfig().AudioQualityMediumOutputBitrate,
|
|
SampleRate: GetConfig().SampleRate,
|
|
Channels: GetConfig().Channels,
|
|
FrameSize: GetConfig().AudioQualityMediumFrameSize,
|
|
}
|
|
currentMicrophoneConfig = AudioConfig{
|
|
Quality: AudioQualityMedium,
|
|
Bitrate: GetConfig().AudioQualityMediumInputBitrate,
|
|
SampleRate: GetConfig().SampleRate,
|
|
Channels: 1,
|
|
FrameSize: GetConfig().AudioQualityMediumFrameSize,
|
|
}
|
|
metrics AudioMetrics
|
|
)
|
|
|
|
// qualityPresets defines the base quality configurations
|
|
var qualityPresets = map[AudioQuality]struct {
|
|
outputBitrate, inputBitrate int
|
|
sampleRate, channels int
|
|
frameSize time.Duration
|
|
}{
|
|
AudioQualityLow: {
|
|
outputBitrate: GetConfig().AudioQualityLowOutputBitrate, inputBitrate: GetConfig().AudioQualityLowInputBitrate,
|
|
sampleRate: GetConfig().AudioQualityLowSampleRate, channels: GetConfig().AudioQualityLowChannels,
|
|
frameSize: GetConfig().AudioQualityLowFrameSize,
|
|
},
|
|
AudioQualityMedium: {
|
|
outputBitrate: GetConfig().AudioQualityMediumOutputBitrate, inputBitrate: GetConfig().AudioQualityMediumInputBitrate,
|
|
sampleRate: GetConfig().AudioQualityMediumSampleRate, channels: GetConfig().AudioQualityMediumChannels,
|
|
frameSize: GetConfig().AudioQualityMediumFrameSize,
|
|
},
|
|
AudioQualityHigh: {
|
|
outputBitrate: GetConfig().AudioQualityHighOutputBitrate, inputBitrate: GetConfig().AudioQualityHighInputBitrate,
|
|
sampleRate: GetConfig().SampleRate, channels: GetConfig().AudioQualityHighChannels,
|
|
frameSize: GetConfig().AudioQualityHighFrameSize,
|
|
},
|
|
AudioQualityUltra: {
|
|
outputBitrate: GetConfig().AudioQualityUltraOutputBitrate, inputBitrate: GetConfig().AudioQualityUltraInputBitrate,
|
|
sampleRate: GetConfig().SampleRate, channels: GetConfig().AudioQualityUltraChannels,
|
|
frameSize: GetConfig().AudioQualityUltraFrameSize,
|
|
},
|
|
}
|
|
|
|
// GetAudioQualityPresets returns predefined quality configurations for audio output
|
|
func GetAudioQualityPresets() map[AudioQuality]AudioConfig {
|
|
result := make(map[AudioQuality]AudioConfig)
|
|
for quality, preset := range qualityPresets {
|
|
result[quality] = AudioConfig{
|
|
Quality: quality,
|
|
Bitrate: preset.outputBitrate,
|
|
SampleRate: preset.sampleRate,
|
|
Channels: preset.channels,
|
|
FrameSize: preset.frameSize,
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetMicrophoneQualityPresets returns predefined quality configurations for microphone input
|
|
func GetMicrophoneQualityPresets() map[AudioQuality]AudioConfig {
|
|
result := make(map[AudioQuality]AudioConfig)
|
|
for quality, preset := range qualityPresets {
|
|
result[quality] = AudioConfig{
|
|
Quality: quality,
|
|
Bitrate: preset.inputBitrate,
|
|
SampleRate: func() int {
|
|
if quality == AudioQualityLow {
|
|
return GetConfig().AudioQualityMicLowSampleRate
|
|
}
|
|
return preset.sampleRate
|
|
}(),
|
|
Channels: 1, // Microphone is always mono
|
|
FrameSize: preset.frameSize,
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SetAudioQuality updates the current audio quality configuration
|
|
func SetAudioQuality(quality AudioQuality) {
|
|
presets := GetAudioQualityPresets()
|
|
if config, exists := presets[quality]; exists {
|
|
currentConfig = config
|
|
}
|
|
}
|
|
|
|
// GetAudioConfig returns the current audio configuration
|
|
func GetAudioConfig() AudioConfig {
|
|
return currentConfig
|
|
}
|
|
|
|
// SetMicrophoneQuality updates the current microphone quality configuration
|
|
func SetMicrophoneQuality(quality AudioQuality) {
|
|
presets := GetMicrophoneQualityPresets()
|
|
if config, exists := presets[quality]; exists {
|
|
currentMicrophoneConfig = config
|
|
}
|
|
}
|
|
|
|
// GetMicrophoneConfig returns the current microphone configuration
|
|
func GetMicrophoneConfig() AudioConfig {
|
|
return currentMicrophoneConfig
|
|
}
|
|
|
|
// GetAudioMetrics returns current audio metrics
|
|
func GetAudioMetrics() AudioMetrics {
|
|
// Get base metrics
|
|
framesReceived := atomic.LoadInt64(&metrics.FramesReceived)
|
|
framesDropped := atomic.LoadInt64(&metrics.FramesDropped)
|
|
|
|
// If audio relay is running, use relay stats instead
|
|
if IsAudioRelayRunning() {
|
|
relayReceived, relayDropped := GetAudioRelayStats()
|
|
framesReceived = relayReceived
|
|
framesDropped = relayDropped
|
|
}
|
|
|
|
return AudioMetrics{
|
|
FramesReceived: framesReceived,
|
|
FramesDropped: framesDropped,
|
|
BytesProcessed: atomic.LoadInt64(&metrics.BytesProcessed),
|
|
LastFrameTime: metrics.LastFrameTime,
|
|
ConnectionDrops: atomic.LoadInt64(&metrics.ConnectionDrops),
|
|
AverageLatency: metrics.AverageLatency,
|
|
}
|
|
}
|
|
|
|
// RecordFrameReceived increments the frames received counter
|
|
func RecordFrameReceived(bytes int) {
|
|
atomic.AddInt64(&metrics.FramesReceived, 1)
|
|
atomic.AddInt64(&metrics.BytesProcessed, int64(bytes))
|
|
metrics.LastFrameTime = time.Now()
|
|
}
|
|
|
|
// RecordFrameDropped increments the frames dropped counter
|
|
func RecordFrameDropped() {
|
|
atomic.AddInt64(&metrics.FramesDropped, 1)
|
|
}
|
|
|
|
// RecordConnectionDrop increments the connection drops counter
|
|
func RecordConnectionDrop() {
|
|
atomic.AddInt64(&metrics.ConnectionDrops, 1)
|
|
}
|