mirror of
https://github.com/jetkvm/kvm.git
synced 2025-09-16 08:38:14 +00:00
This change replaces all instances of GetConfig() function calls with direct access to the Config variable throughout the audio package. The modification improves performance by eliminating function call overhead and simplifies the codebase by removing unnecessary indirection. The commit also includes minor optimizations in validation logic and connection handling, while maintaining all existing functionality. Error handling remains robust with appropriate fallbacks when config values are not available. Additional improvements include: - Enhanced connection health monitoring in UnifiedAudioClient - Optimized validation functions using cached config values - Reduced memory allocations in hot paths - Improved error recovery during quality changes
224 lines
6.2 KiB
Go
224 lines
6.2 KiB
Go
package audio
|
|
|
|
import (
|
|
"fmt"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/jetkvm/kvm/internal/logging"
|
|
)
|
|
|
|
// Component name constant for logging
|
|
const (
|
|
AudioOutputIPCComponent = "audio-output-ipc"
|
|
)
|
|
|
|
// AudioOutputMetrics represents metrics for audio output operations
|
|
type AudioOutputMetrics struct {
|
|
// Atomic int64 field first for proper ARM32 alignment
|
|
FramesReceived int64 `json:"frames_received"` // Total frames received (output-specific)
|
|
|
|
// Embedded struct with atomic fields properly aligned
|
|
BaseAudioMetrics
|
|
}
|
|
|
|
// AudioOutputIPCManager manages audio output using IPC when enabled
|
|
type AudioOutputIPCManager struct {
|
|
*BaseAudioManager
|
|
server *AudioOutputServer
|
|
}
|
|
|
|
// NewAudioOutputIPCManager creates a new IPC-based audio output manager
|
|
func NewAudioOutputIPCManager() *AudioOutputIPCManager {
|
|
return &AudioOutputIPCManager{
|
|
BaseAudioManager: NewBaseAudioManager(logging.GetDefaultLogger().With().Str("component", AudioOutputIPCComponent).Logger()),
|
|
}
|
|
}
|
|
|
|
// Start initializes and starts the audio output IPC manager
|
|
func (aom *AudioOutputIPCManager) Start() error {
|
|
aom.logComponentStart(AudioOutputIPCComponent)
|
|
|
|
// Create and start the IPC server
|
|
server, err := NewAudioOutputServer()
|
|
if err != nil {
|
|
aom.logComponentError(AudioOutputIPCComponent, err, "failed to create IPC server")
|
|
return err
|
|
}
|
|
|
|
if err := server.Start(); err != nil {
|
|
aom.logComponentError(AudioOutputIPCComponent, err, "failed to start IPC server")
|
|
return err
|
|
}
|
|
|
|
aom.server = server
|
|
aom.setRunning(true)
|
|
aom.logComponentStarted(AudioOutputIPCComponent)
|
|
|
|
// Send initial configuration
|
|
config := OutputIPCConfig{
|
|
SampleRate: Config.SampleRate,
|
|
Channels: Config.Channels,
|
|
FrameSize: int(Config.AudioQualityMediumFrameSize.Milliseconds()),
|
|
}
|
|
|
|
if err := aom.SendConfig(config); err != nil {
|
|
aom.logger.Warn().Err(err).Msg("Failed to send initial configuration")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop gracefully shuts down the audio output IPC manager
|
|
func (aom *AudioOutputIPCManager) Stop() {
|
|
aom.logComponentStop(AudioOutputIPCComponent)
|
|
|
|
if aom.server != nil {
|
|
aom.server.Stop()
|
|
aom.server = nil
|
|
}
|
|
|
|
aom.setRunning(false)
|
|
aom.resetMetrics()
|
|
aom.logComponentStopped(AudioOutputIPCComponent)
|
|
}
|
|
|
|
// resetMetrics resets all metrics to zero
|
|
func (aom *AudioOutputIPCManager) resetMetrics() {
|
|
aom.BaseAudioManager.resetMetrics()
|
|
}
|
|
|
|
// WriteOpusFrame sends an Opus frame to the output server
|
|
func (aom *AudioOutputIPCManager) WriteOpusFrame(frame *ZeroCopyAudioFrame) error {
|
|
if !aom.IsRunning() {
|
|
return fmt.Errorf("audio output IPC manager not running")
|
|
}
|
|
|
|
if aom.server == nil {
|
|
return fmt.Errorf("audio output server not initialized")
|
|
}
|
|
|
|
// Validate frame before processing
|
|
if err := ValidateZeroCopyFrame(frame); err != nil {
|
|
aom.logComponentError(AudioOutputIPCComponent, err, "Frame validation failed")
|
|
return fmt.Errorf("output frame validation failed: %w", err)
|
|
}
|
|
|
|
start := time.Now()
|
|
|
|
// Send frame to IPC server
|
|
if err := aom.server.SendFrame(frame.Data()); err != nil {
|
|
aom.recordFrameDropped()
|
|
return err
|
|
}
|
|
|
|
// Update metrics
|
|
processingTime := time.Since(start)
|
|
aom.recordFrameProcessed(frame.Length())
|
|
aom.updateLatency(processingTime)
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteOpusFrameZeroCopy writes an Opus audio frame using zero-copy optimization
|
|
func (aom *AudioOutputIPCManager) WriteOpusFrameZeroCopy(frame *ZeroCopyAudioFrame) error {
|
|
if !aom.IsRunning() {
|
|
return fmt.Errorf("audio output IPC manager not running")
|
|
}
|
|
|
|
if aom.server == nil {
|
|
return fmt.Errorf("audio output server not initialized")
|
|
}
|
|
|
|
start := time.Now()
|
|
|
|
// Extract frame data
|
|
frameData := frame.Data()
|
|
|
|
// Send frame to IPC server (zero-copy not available, use regular send)
|
|
if err := aom.server.SendFrame(frameData); err != nil {
|
|
aom.recordFrameDropped()
|
|
return err
|
|
}
|
|
|
|
// Update metrics
|
|
processingTime := time.Since(start)
|
|
aom.recordFrameProcessed(len(frameData))
|
|
aom.updateLatency(processingTime)
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsReady returns true if the IPC manager is ready to process frames
|
|
func (aom *AudioOutputIPCManager) IsReady() bool {
|
|
return aom.IsRunning() && aom.server != nil
|
|
}
|
|
|
|
// GetMetrics returns current audio output metrics
|
|
func (aom *AudioOutputIPCManager) GetMetrics() AudioOutputMetrics {
|
|
baseMetrics := aom.getBaseMetrics()
|
|
return AudioOutputMetrics{
|
|
FramesReceived: atomic.LoadInt64(&baseMetrics.FramesProcessed), // For output, processed = received
|
|
BaseAudioMetrics: baseMetrics,
|
|
}
|
|
}
|
|
|
|
// GetDetailedMetrics returns detailed metrics including server statistics
|
|
func (aom *AudioOutputIPCManager) GetDetailedMetrics() (AudioOutputMetrics, map[string]interface{}) {
|
|
metrics := aom.GetMetrics()
|
|
detailed := make(map[string]interface{})
|
|
|
|
if aom.server != nil {
|
|
total, dropped, bufferSize := aom.server.GetServerStats()
|
|
detailed["server_total_frames"] = total
|
|
detailed["server_dropped_frames"] = dropped
|
|
detailed["server_buffer_size"] = bufferSize
|
|
detailed["server_frame_rate"] = aom.calculateFrameRate()
|
|
}
|
|
|
|
return metrics, detailed
|
|
}
|
|
|
|
// calculateFrameRate calculates the current frame processing rate
|
|
func (aom *AudioOutputIPCManager) calculateFrameRate() float64 {
|
|
baseMetrics := aom.getBaseMetrics()
|
|
framesProcessed := atomic.LoadInt64(&baseMetrics.FramesProcessed)
|
|
if framesProcessed == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
// Calculate rate based on last frame time
|
|
baseMetrics = aom.getBaseMetrics()
|
|
if baseMetrics.LastFrameTime.IsZero() {
|
|
return 0.0
|
|
}
|
|
|
|
elapsed := time.Since(baseMetrics.LastFrameTime)
|
|
if elapsed.Seconds() == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
return float64(framesProcessed) / elapsed.Seconds()
|
|
}
|
|
|
|
// SendConfig sends configuration to the IPC server
|
|
func (aom *AudioOutputIPCManager) SendConfig(config OutputIPCConfig) error {
|
|
if aom.server == nil {
|
|
return fmt.Errorf("audio output server not initialized")
|
|
}
|
|
|
|
// Validate configuration parameters
|
|
if err := ValidateOutputIPCConfig(config.SampleRate, config.Channels, config.FrameSize); err != nil {
|
|
aom.logger.Error().Err(err).Msg("Configuration validation failed")
|
|
return fmt.Errorf("output configuration validation failed: %w", err)
|
|
}
|
|
|
|
aom.logger.Info().Interface("config", config).Msg("configuration received")
|
|
return nil
|
|
}
|
|
|
|
// GetServer returns the underlying IPC server (for testing)
|
|
func (aom *AudioOutputIPCManager) GetServer() *AudioOutputServer {
|
|
return aom.server
|
|
}
|