mirror of https://github.com/jetkvm/kvm.git
refactor(audio): move hardcoded values to config for better flexibility
- Replace hardcoded values with configurable parameters in audio components - Add new config fields for adaptive buffer sizes and frame pool settings - Implement memory guard in ZeroCopyFramePool to prevent excessive allocations
This commit is contained in:
parent
35a666ed31
commit
9e343b3cc7
|
@ -37,9 +37,9 @@ type AdaptiveBufferConfig struct {
|
||||||
func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig {
|
func DefaultAdaptiveBufferConfig() AdaptiveBufferConfig {
|
||||||
return AdaptiveBufferConfig{
|
return AdaptiveBufferConfig{
|
||||||
// Conservative buffer sizes for 256MB RAM constraint
|
// Conservative buffer sizes for 256MB RAM constraint
|
||||||
MinBufferSize: 3, // Minimum 3 frames (slightly higher for stability)
|
MinBufferSize: GetConfig().AdaptiveMinBufferSize,
|
||||||
MaxBufferSize: 20, // Maximum 20 frames (increased for high load scenarios)
|
MaxBufferSize: GetConfig().AdaptiveMaxBufferSize,
|
||||||
DefaultBufferSize: 6, // Default 6 frames (increased for better stability)
|
DefaultBufferSize: GetConfig().AdaptiveDefaultBufferSize,
|
||||||
|
|
||||||
// CPU thresholds optimized for single-core ARM Cortex A7 under load
|
// CPU thresholds optimized for single-core ARM Cortex A7 under load
|
||||||
LowCPUThreshold: GetConfig().LowCPUThreshold * 100, // Below 20% CPU
|
LowCPUThreshold: GetConfig().LowCPUThreshold * 100, // Below 20% CPU
|
||||||
|
|
|
@ -698,6 +698,28 @@ type AudioConfigConstants struct {
|
||||||
OutputSizeThreshold int
|
OutputSizeThreshold int
|
||||||
TargetLevel float64
|
TargetLevel float64
|
||||||
|
|
||||||
|
// Adaptive Buffer Configuration - Controls dynamic buffer sizing for optimal performance
|
||||||
|
// Used in: adaptive_buffer.go for dynamic buffer management
|
||||||
|
// Impact: Controls buffer size adaptation based on system load and latency
|
||||||
|
|
||||||
|
// AdaptiveMinBufferSize defines minimum buffer size in frames for adaptive buffering.
|
||||||
|
// Used in: adaptive_buffer.go DefaultAdaptiveBufferConfig()
|
||||||
|
// Impact: Lower values reduce latency but may cause underruns under high load.
|
||||||
|
// Default 3 frames provides stability while maintaining low latency.
|
||||||
|
AdaptiveMinBufferSize int
|
||||||
|
|
||||||
|
// AdaptiveMaxBufferSize defines maximum buffer size in frames for adaptive buffering.
|
||||||
|
// Used in: adaptive_buffer.go DefaultAdaptiveBufferConfig()
|
||||||
|
// Impact: Higher values handle load spikes but increase maximum latency.
|
||||||
|
// Default 20 frames accommodates high load scenarios without excessive latency.
|
||||||
|
AdaptiveMaxBufferSize int
|
||||||
|
|
||||||
|
// AdaptiveDefaultBufferSize defines default buffer size in frames for adaptive buffering.
|
||||||
|
// Used in: adaptive_buffer.go DefaultAdaptiveBufferConfig()
|
||||||
|
// Impact: Starting point for buffer adaptation, affects initial latency.
|
||||||
|
// Default 6 frames balances initial latency with adaptation headroom.
|
||||||
|
AdaptiveDefaultBufferSize int
|
||||||
|
|
||||||
// Priority Scheduling
|
// Priority Scheduling
|
||||||
AudioHighPriority int
|
AudioHighPriority int
|
||||||
AudioMediumPriority int
|
AudioMediumPriority int
|
||||||
|
@ -1159,6 +1181,11 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
||||||
HighMemoryThreshold: 0.75,
|
HighMemoryThreshold: 0.75,
|
||||||
TargetLatency: 20 * time.Millisecond,
|
TargetLatency: 20 * time.Millisecond,
|
||||||
|
|
||||||
|
// Adaptive Buffer Size Configuration
|
||||||
|
AdaptiveMinBufferSize: 3, // Minimum 3 frames for stability
|
||||||
|
AdaptiveMaxBufferSize: 20, // Maximum 20 frames for high load
|
||||||
|
AdaptiveDefaultBufferSize: 6, // Default 6 frames for balanced performance
|
||||||
|
|
||||||
// Adaptive Optimizer Configuration
|
// Adaptive Optimizer Configuration
|
||||||
CooldownPeriod: 30 * time.Second,
|
CooldownPeriod: 30 * time.Second,
|
||||||
RollbackThreshold: 300 * time.Millisecond,
|
RollbackThreshold: 300 * time.Millisecond,
|
||||||
|
|
|
@ -19,11 +19,11 @@ import (
|
||||||
var (
|
var (
|
||||||
inputMagicNumber uint32 = GetConfig().InputMagicNumber // "JKMI" (JetKVM Microphone Input)
|
inputMagicNumber uint32 = GetConfig().InputMagicNumber // "JKMI" (JetKVM Microphone Input)
|
||||||
inputSocketName = "audio_input.sock"
|
inputSocketName = "audio_input.sock"
|
||||||
writeTimeout = 15 * time.Millisecond // Non-blocking write timeout (increased for high load)
|
writeTimeout = GetConfig().WriteTimeout // Non-blocking write timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
headerSize = 17 // Fixed header size: 4+1+4+8 bytes
|
headerSize = 17 // Fixed header size: 4+1+4+8 bytes - matches GetConfig().HeaderSize
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -503,10 +503,12 @@ func (aic *AudioInputClient) Connect() error {
|
||||||
aic.running = true
|
aic.running = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Exponential backoff starting at 50ms
|
// Exponential backoff starting from config
|
||||||
delay := time.Duration(50*(1<<uint(i/3))) * time.Millisecond
|
backoffStart := GetConfig().BackoffStart
|
||||||
if delay > 500*time.Millisecond {
|
delay := time.Duration(backoffStart.Nanoseconds()*(1<<uint(i/3))) * time.Nanosecond
|
||||||
delay = 500 * time.Millisecond
|
maxDelay := GetConfig().MaxRetryDelay
|
||||||
|
if delay > maxDelay {
|
||||||
|
delay = maxDelay
|
||||||
}
|
}
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,10 +422,12 @@ func (c *AudioClient) Connect() error {
|
||||||
c.running = true
|
c.running = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Exponential backoff starting at 50ms
|
// Exponential backoff starting from config
|
||||||
delay := time.Duration(50*(1<<uint(i/3))) * time.Millisecond
|
backoffStart := GetConfig().BackoffStart
|
||||||
if delay > 400*time.Millisecond {
|
delay := time.Duration(backoffStart.Nanoseconds()*(1<<uint(i/3))) * time.Nanosecond
|
||||||
delay = 400 * time.Millisecond
|
maxDelay := GetConfig().MaxRetryDelay
|
||||||
|
if delay > maxDelay {
|
||||||
|
delay = maxDelay
|
||||||
}
|
}
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ func (r *AudioRelay) relayLoop() {
|
||||||
defer r.wg.Done()
|
defer r.wg.Done()
|
||||||
r.logger.Debug().Msg("Audio relay loop started")
|
r.logger.Debug().Msg("Audio relay loop started")
|
||||||
|
|
||||||
const maxConsecutiveErrors = 10
|
var maxConsecutiveErrors = GetConfig().MaxConsecutiveErrors
|
||||||
consecutiveErrors := 0
|
consecutiveErrors := 0
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
|
@ -23,6 +23,7 @@ type ZeroCopyFramePool struct {
|
||||||
counter int64 // Frame counter (atomic)
|
counter int64 // Frame counter (atomic)
|
||||||
hitCount int64 // Pool hit counter (atomic)
|
hitCount int64 // Pool hit counter (atomic)
|
||||||
missCount int64 // Pool miss counter (atomic)
|
missCount int64 // Pool miss counter (atomic)
|
||||||
|
allocationCount int64 // Total allocations counter (atomic)
|
||||||
|
|
||||||
// Other fields
|
// Other fields
|
||||||
pool sync.Pool
|
pool sync.Pool
|
||||||
|
@ -36,13 +37,23 @@ type ZeroCopyFramePool struct {
|
||||||
|
|
||||||
// NewZeroCopyFramePool creates a new zero-copy frame pool
|
// NewZeroCopyFramePool creates a new zero-copy frame pool
|
||||||
func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool {
|
func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool {
|
||||||
// Pre-allocate 15 frames for immediate availability
|
// Pre-allocate frames for immediate availability
|
||||||
preallocSize := 15
|
preallocSizeBytes := GetConfig().PreallocSize
|
||||||
maxPoolSize := 50 // Limit total pool size
|
maxPoolSize := GetConfig().MaxPoolSize // Limit total pool size
|
||||||
preallocated := make([]*ZeroCopyAudioFrame, 0, preallocSize)
|
|
||||||
|
// Calculate number of frames based on memory budget, not frame count
|
||||||
|
preallocFrameCount := preallocSizeBytes / maxFrameSize
|
||||||
|
if preallocFrameCount > maxPoolSize {
|
||||||
|
preallocFrameCount = maxPoolSize
|
||||||
|
}
|
||||||
|
if preallocFrameCount < 1 {
|
||||||
|
preallocFrameCount = 1 // Always preallocate at least one frame
|
||||||
|
}
|
||||||
|
|
||||||
|
preallocated := make([]*ZeroCopyAudioFrame, 0, preallocFrameCount)
|
||||||
|
|
||||||
// Pre-allocate frames to reduce initial allocation overhead
|
// Pre-allocate frames to reduce initial allocation overhead
|
||||||
for i := 0; i < preallocSize; i++ {
|
for i := 0; i < preallocFrameCount; i++ {
|
||||||
frame := &ZeroCopyAudioFrame{
|
frame := &ZeroCopyAudioFrame{
|
||||||
data: make([]byte, 0, maxFrameSize),
|
data: make([]byte, 0, maxFrameSize),
|
||||||
capacity: maxFrameSize,
|
capacity: maxFrameSize,
|
||||||
|
@ -54,7 +65,7 @@ func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool {
|
||||||
return &ZeroCopyFramePool{
|
return &ZeroCopyFramePool{
|
||||||
maxSize: maxFrameSize,
|
maxSize: maxFrameSize,
|
||||||
preallocated: preallocated,
|
preallocated: preallocated,
|
||||||
preallocSize: preallocSize,
|
preallocSize: preallocFrameCount,
|
||||||
maxPoolSize: maxPoolSize,
|
maxPoolSize: maxPoolSize,
|
||||||
pool: sync.Pool{
|
pool: sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
|
@ -70,6 +81,20 @@ func NewZeroCopyFramePool(maxFrameSize int) *ZeroCopyFramePool {
|
||||||
|
|
||||||
// Get retrieves a zero-copy frame from the pool
|
// Get retrieves a zero-copy frame from the pool
|
||||||
func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame {
|
func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame {
|
||||||
|
// Memory guard: Track allocation count to prevent excessive memory usage
|
||||||
|
allocationCount := atomic.LoadInt64(&p.allocationCount)
|
||||||
|
if allocationCount > int64(p.maxPoolSize*2) {
|
||||||
|
// If we've allocated too many frames, force pool reuse
|
||||||
|
atomic.AddInt64(&p.missCount, 1)
|
||||||
|
frame := p.pool.Get().(*ZeroCopyAudioFrame)
|
||||||
|
frame.mutex.Lock()
|
||||||
|
frame.refCount = 1
|
||||||
|
frame.length = 0
|
||||||
|
frame.data = frame.data[:0]
|
||||||
|
frame.mutex.Unlock()
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
// First try pre-allocated frames for fastest access
|
// First try pre-allocated frames for fastest access
|
||||||
p.mutex.Lock()
|
p.mutex.Lock()
|
||||||
if len(p.preallocated) > 0 {
|
if len(p.preallocated) > 0 {
|
||||||
|
@ -88,7 +113,8 @@ func (p *ZeroCopyFramePool) Get() *ZeroCopyAudioFrame {
|
||||||
}
|
}
|
||||||
p.mutex.Unlock()
|
p.mutex.Unlock()
|
||||||
|
|
||||||
// Try sync.Pool next
|
// Try sync.Pool next and track allocation
|
||||||
|
atomic.AddInt64(&p.allocationCount, 1)
|
||||||
frame := p.pool.Get().(*ZeroCopyAudioFrame)
|
frame := p.pool.Get().(*ZeroCopyAudioFrame)
|
||||||
frame.mutex.Lock()
|
frame.mutex.Lock()
|
||||||
frame.refCount = 1
|
frame.refCount = 1
|
||||||
|
@ -230,6 +256,7 @@ func (p *ZeroCopyFramePool) GetZeroCopyPoolStats() ZeroCopyFramePoolStats {
|
||||||
|
|
||||||
hitCount := atomic.LoadInt64(&p.hitCount)
|
hitCount := atomic.LoadInt64(&p.hitCount)
|
||||||
missCount := atomic.LoadInt64(&p.missCount)
|
missCount := atomic.LoadInt64(&p.missCount)
|
||||||
|
allocationCount := atomic.LoadInt64(&p.allocationCount)
|
||||||
totalRequests := hitCount + missCount
|
totalRequests := hitCount + missCount
|
||||||
|
|
||||||
var hitRate float64
|
var hitRate float64
|
||||||
|
@ -245,6 +272,7 @@ func (p *ZeroCopyFramePool) GetZeroCopyPoolStats() ZeroCopyFramePoolStats {
|
||||||
PreallocatedMax: int64(p.preallocSize),
|
PreallocatedMax: int64(p.preallocSize),
|
||||||
HitCount: hitCount,
|
HitCount: hitCount,
|
||||||
MissCount: missCount,
|
MissCount: missCount,
|
||||||
|
AllocationCount: allocationCount,
|
||||||
HitRate: hitRate,
|
HitRate: hitRate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,6 +286,7 @@ type ZeroCopyFramePoolStats struct {
|
||||||
PreallocatedMax int64
|
PreallocatedMax int64
|
||||||
HitCount int64
|
HitCount int64
|
||||||
MissCount int64
|
MissCount int64
|
||||||
|
AllocationCount int64
|
||||||
HitRate float64 // Percentage
|
HitRate float64 // Percentage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue