mirror of https://github.com/jetkvm/kvm.git
153 lines
4.5 KiB
Go
153 lines
4.5 KiB
Go
//go:build cgo
|
|
// +build cgo
|
|
|
|
// Package audio provides real-time audio processing for JetKVM with low-latency streaming.
|
|
//
|
|
// Key components: output/input pipelines with Opus codec, buffer management,
|
|
// zero-copy frame pools, IPC communication, and process supervision.
|
|
//
|
|
// Optimized for S16_LE @ 48kHz stereo HDMI audio with minimal CPU usage.
|
|
// All APIs are thread-safe with comprehensive error handling and metrics collection.
|
|
//
|
|
// # 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 and complexity=1 Opus
|
|
// - Fixed optimal configuration (96 kbps output, 48 kbps input)
|
|
//
|
|
// # Usage Example
|
|
//
|
|
// config := GetAudioConfig()
|
|
//
|
|
// // Audio output will automatically start when frames are received
|
|
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 Config.MaxAudioFrameSize
|
|
}
|
|
|
|
// AudioConfig holds the optimal audio configuration
|
|
// All settings are fixed for S16_LE @ 48kHz HDMI audio
|
|
type AudioConfig struct {
|
|
Bitrate int // kbps (96 for output, 48 for input)
|
|
SampleRate int // Hz (always 48000)
|
|
Channels int // 2 for output (stereo), 1 for input (mono)
|
|
FrameSize time.Duration // ms (always 20ms)
|
|
}
|
|
|
|
// AudioMetrics tracks audio performance metrics
|
|
type AudioMetrics struct {
|
|
FramesReceived uint64
|
|
FramesDropped uint64
|
|
BytesProcessed uint64
|
|
ConnectionDrops uint64
|
|
LastFrameTime time.Time
|
|
AverageLatency time.Duration
|
|
}
|
|
|
|
var (
|
|
// Optimal configuration for audio output (HDMI → client)
|
|
currentConfig = AudioConfig{
|
|
Bitrate: Config.OptimalOutputBitrate,
|
|
SampleRate: Config.SampleRate,
|
|
Channels: Config.Channels,
|
|
FrameSize: 20 * time.Millisecond,
|
|
}
|
|
// Optimal configuration for microphone input (client → target)
|
|
currentMicrophoneConfig = AudioConfig{
|
|
Bitrate: Config.OptimalInputBitrate,
|
|
SampleRate: Config.SampleRate,
|
|
Channels: 1,
|
|
FrameSize: 20 * time.Millisecond,
|
|
}
|
|
metrics AudioMetrics
|
|
)
|
|
|
|
// GetAudioConfig returns the current optimal audio configuration
|
|
func GetAudioConfig() AudioConfig {
|
|
return currentConfig
|
|
}
|
|
|
|
// GetMicrophoneConfig returns the current optimal microphone configuration
|
|
func GetMicrophoneConfig() AudioConfig {
|
|
return currentMicrophoneConfig
|
|
}
|
|
|
|
// GetGlobalAudioMetrics returns the current global audio metrics
|
|
func GetGlobalAudioMetrics() AudioMetrics {
|
|
return metrics
|
|
}
|
|
|
|
// Batched metrics to reduce atomic operations frequency
|
|
var (
|
|
batchedFramesReceived uint64
|
|
batchedBytesProcessed uint64
|
|
batchedFramesDropped uint64
|
|
batchedConnectionDrops uint64
|
|
|
|
lastFlushTime int64 // Unix timestamp in nanoseconds
|
|
)
|
|
|
|
// RecordFrameReceived increments the frames received counter with batched updates
|
|
func RecordFrameReceived(bytes int) {
|
|
// Use local batching to reduce atomic operations frequency
|
|
atomic.AddUint64(&batchedBytesProcessed, uint64(bytes))
|
|
|
|
// Update timestamp immediately for accurate tracking
|
|
metrics.LastFrameTime = time.Now()
|
|
}
|
|
|
|
// RecordFrameDropped increments the frames dropped counter with batched updates
|
|
func RecordFrameDropped() {
|
|
atomic.AddUint64(&batchedFramesDropped, 1)
|
|
}
|
|
|
|
// RecordConnectionDrop increments the connection drops counter with batched updates
|
|
func RecordConnectionDrop() {
|
|
atomic.AddUint64(&batchedConnectionDrops, 1)
|
|
}
|
|
|
|
// flushBatchedMetrics flushes accumulated metrics to the main counters
|
|
func flushBatchedMetrics() {
|
|
// Atomically move batched metrics to main metrics
|
|
framesReceived := atomic.SwapUint64(&batchedFramesReceived, 0)
|
|
bytesProcessed := atomic.SwapUint64(&batchedBytesProcessed, 0)
|
|
framesDropped := atomic.SwapUint64(&batchedFramesDropped, 0)
|
|
connectionDrops := atomic.SwapUint64(&batchedConnectionDrops, 0)
|
|
|
|
// Update main metrics if we have any batched data
|
|
if framesReceived > 0 {
|
|
atomic.AddUint64(&metrics.FramesReceived, framesReceived)
|
|
}
|
|
if bytesProcessed > 0 {
|
|
atomic.AddUint64(&metrics.BytesProcessed, bytesProcessed)
|
|
}
|
|
if framesDropped > 0 {
|
|
atomic.AddUint64(&metrics.FramesDropped, framesDropped)
|
|
}
|
|
if connectionDrops > 0 {
|
|
atomic.AddUint64(&metrics.ConnectionDrops, connectionDrops)
|
|
}
|
|
|
|
// Update last flush time
|
|
atomic.StoreInt64(&lastFlushTime, time.Now().UnixNano())
|
|
}
|
|
|
|
// FlushPendingMetrics forces a flush of all batched metrics
|
|
func FlushPendingMetrics() {
|
|
flushBatchedMetrics()
|
|
}
|