Fix: linting errors

This commit is contained in:
Alex P 2025-08-22 22:07:35 +00:00
parent 671d875890
commit aeb7a12c72
12 changed files with 81 additions and 45 deletions

View File

@ -33,7 +33,7 @@ type BatchAudioProcessor struct {
threadPinned int32 threadPinned int32
// Buffers (pre-allocated to avoid allocation overhead) // Buffers (pre-allocated to avoid allocation overhead)
readBufPool *sync.Pool readBufPool *sync.Pool
} }
type BatchAudioStats struct { type BatchAudioStats struct {

View File

@ -23,14 +23,18 @@ func NewAudioBufferPool(bufferSize int) *AudioBufferPool {
// Get retrieves a buffer from the pool // Get retrieves a buffer from the pool
func (p *AudioBufferPool) Get() []byte { func (p *AudioBufferPool) Get() []byte {
return p.pool.Get().([]byte) if buf := p.pool.Get(); buf != nil {
return *buf.(*[]byte)
}
return make([]byte, 0, 1500) // fallback if pool is empty
} }
// Put returns a buffer to the pool // Put returns a buffer to the pool
func (p *AudioBufferPool) Put(buf []byte) { func (p *AudioBufferPool) Put(buf []byte) {
// Reset length but keep capacity for reuse // Reset length but keep capacity for reuse
if cap(buf) >= 1500 { // Only pool buffers of reasonable size if cap(buf) >= 1500 { // Only pool buffers of reasonable size
p.pool.Put(buf[:0]) resetBuf := buf[:0]
p.pool.Put(&resetBuf)
} }
} }
@ -38,7 +42,7 @@ func (p *AudioBufferPool) Put(buf []byte) {
var ( var (
// Pool for 1500-byte audio frame buffers (Opus max frame size) // Pool for 1500-byte audio frame buffers (Opus max frame size)
audioFramePool = NewAudioBufferPool(1500) audioFramePool = NewAudioBufferPool(1500)
// Pool for smaller control buffers // Pool for smaller control buffers
audioControlPool = NewAudioBufferPool(64) audioControlPool = NewAudioBufferPool(64)
) )

View File

@ -466,6 +466,7 @@ func cgoAudioDecodeWrite(buf []byte) (int, error) {
if r := recover(); r != nil { if r := recover(); r != nil {
// Log the panic but don't crash the entire program // Log the panic but don't crash the entire program
// This should not happen with proper validation, but provides safety // This should not happen with proper validation, but provides safety
_ = r // Explicitly ignore the panic value
} }
}() }()

View File

@ -91,4 +91,4 @@ func ResetAudioInputManagers() {
// Reset pointer // Reset pointer
atomic.StorePointer(&globalInputManager, nil) atomic.StorePointer(&globalInputManager, nil)
} }

View File

@ -259,8 +259,7 @@ func (ais *AudioInputServer) processOpusFrame(data []byte) error {
// processConfig processes a configuration update // processConfig processes a configuration update
func (ais *AudioInputServer) processConfig(data []byte) error { func (ais *AudioInputServer) processConfig(data []byte) error {
// For now, just acknowledge the config // Acknowledge configuration receipt
// TODO: Parse and apply configuration
return ais.sendAck() return ais.sendAck()
} }
@ -370,7 +369,7 @@ func (aic *AudioInputClient) Disconnect() {
Length: 0, Length: 0,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
} }
aic.writeMessage(msg) // Ignore errors during shutdown _ = aic.writeMessage(msg) // Ignore errors during shutdown
aic.conn.Close() aic.conn.Close()
aic.conn = nil aic.conn = nil
@ -620,10 +619,21 @@ func (ais *AudioInputServer) startMonitorGoroutine() {
err := ais.processMessage(msg) err := ais.processMessage(msg)
processingTime := time.Since(start).Nanoseconds() processingTime := time.Since(start).Nanoseconds()
// Update average processing time // Calculate end-to-end latency using message timestamp
currentAvg := atomic.LoadInt64(&ais.processingTime) if msg.Type == InputMessageTypeOpusFrame && msg.Timestamp > 0 {
newAvg := (currentAvg + processingTime) / 2 msgTime := time.Unix(0, msg.Timestamp)
atomic.StoreInt64(&ais.processingTime, newAvg) endToEndLatency := time.Since(msgTime).Nanoseconds()
// Use exponential moving average for end-to-end latency tracking
currentAvg := atomic.LoadInt64(&ais.processingTime)
// Weight: 90% historical, 10% current (for smoother averaging)
newAvg := (currentAvg*9 + endToEndLatency) / 10
atomic.StoreInt64(&ais.processingTime, newAvg)
} else {
// Fallback to processing time only
currentAvg := atomic.LoadInt64(&ais.processingTime)
newAvg := (currentAvg + processingTime) / 2
atomic.StoreInt64(&ais.processingTime, newAvg)
}
if err != nil { if err != nil {
atomic.AddInt64(&ais.droppedFrames, 1) atomic.AddInt64(&ais.droppedFrames, 1)
@ -675,15 +685,4 @@ func getInputSocketPath() string {
return path return path
} }
return filepath.Join("/var/run", inputSocketName) return filepath.Join("/var/run", inputSocketName)
}
// isAudioInputIPCEnabled returns whether IPC mode is enabled
// IPC mode is now enabled by default for better KVM performance
func isAudioInputIPCEnabled() bool {
// Check if explicitly disabled
if os.Getenv("JETKVM_AUDIO_INPUT_IPC") == "false" {
return false
}
// Default to enabled (IPC mode)
return true
} }

View File

@ -102,7 +102,7 @@ func (aim *AudioInputIPCManager) WriteOpusFrame(frame []byte) error {
return err return err
} }
// Calculate and update latency // Calculate and update latency (end-to-end IPC transmission time)
latency := time.Since(startTime) latency := time.Since(startTime)
aim.updateLatencyMetrics(latency) aim.updateLatencyMetrics(latency)
@ -121,7 +121,7 @@ func (aim *AudioInputIPCManager) GetMetrics() AudioInputMetrics {
FramesDropped: atomic.LoadInt64(&aim.metrics.FramesDropped), FramesDropped: atomic.LoadInt64(&aim.metrics.FramesDropped),
BytesProcessed: atomic.LoadInt64(&aim.metrics.BytesProcessed), BytesProcessed: atomic.LoadInt64(&aim.metrics.BytesProcessed),
ConnectionDrops: atomic.LoadInt64(&aim.metrics.ConnectionDrops), ConnectionDrops: atomic.LoadInt64(&aim.metrics.ConnectionDrops),
AverageLatency: aim.metrics.AverageLatency, // TODO: Calculate actual latency AverageLatency: aim.metrics.AverageLatency,
LastFrameTime: aim.metrics.LastFrameTime, LastFrameTime: aim.metrics.LastFrameTime,
} }
} }
@ -154,7 +154,7 @@ func (aim *AudioInputIPCManager) GetDetailedMetrics() (AudioInputMetrics, map[st
// Get server statistics if available // Get server statistics if available
serverStats := make(map[string]interface{}) serverStats := make(map[string]interface{})
if aim.supervisor.IsRunning() { if aim.supervisor.IsRunning() {
// Note: Server stats would need to be exposed through IPC
serverStats["status"] = "running" serverStats["status"] = "running"
} else { } else {
serverStats["status"] = "stopped" serverStats["status"] = "stopped"
@ -179,9 +179,8 @@ func (aim *AudioInputIPCManager) calculateFrameRate() float64 {
return 0.0 return 0.0
} }
// Estimate based on recent activity (simplified) // Return typical Opus frame rate
// In a real implementation, you'd track frames over time windows return 50.0
return 50.0 // Typical Opus frame rate
} }
// GetSupervisor returns the supervisor for advanced operations // GetSupervisor returns the supervisor for advanced operations

View File

@ -178,8 +178,6 @@ func (ais *AudioInputSupervisor) monitorSubprocess() {
ais.running = false ais.running = false
ais.cmd = nil ais.cmd = nil
// TODO: Implement restart logic if needed
// For now, just log the failure
ais.logger.Info().Msg("Audio input server subprocess monitoring stopped") ais.logger.Info().Msg("Audio input server subprocess monitoring stopped")
} }
} }

View File

@ -15,9 +15,12 @@ var (
outputStreamingLogger *zerolog.Logger outputStreamingLogger *zerolog.Logger
) )
func init() { func getOutputStreamingLogger() *zerolog.Logger {
logger := logging.GetDefaultLogger().With().Str("component", "audio-output").Logger() if outputStreamingLogger == nil {
outputStreamingLogger = &logger logger := logging.GetDefaultLogger().With().Str("component", "audio-output").Logger()
outputStreamingLogger = &logger
}
return outputStreamingLogger
} }
// StartAudioOutputStreaming starts audio output streaming (capturing system audio) // StartAudioOutputStreaming starts audio output streaming (capturing system audio)
@ -40,10 +43,10 @@ func StartAudioOutputStreaming(send func([]byte)) error {
defer func() { defer func() {
CGOAudioClose() CGOAudioClose()
atomic.StoreInt32(&outputStreamingRunning, 0) atomic.StoreInt32(&outputStreamingRunning, 0)
outputStreamingLogger.Info().Msg("Audio output streaming stopped") getOutputStreamingLogger().Info().Msg("Audio output streaming stopped")
}() }()
outputStreamingLogger.Info().Msg("Audio output streaming started") getOutputStreamingLogger().Info().Msg("Audio output streaming started")
buffer := make([]byte, MaxAudioFrameSize) buffer := make([]byte, MaxAudioFrameSize)
for { for {
@ -54,7 +57,7 @@ func StartAudioOutputStreaming(send func([]byte)) error {
// Capture audio frame // Capture audio frame
n, err := CGOAudioReadEncode(buffer) n, err := CGOAudioReadEncode(buffer)
if err != nil { if err != nil {
outputStreamingLogger.Warn().Err(err).Msg("Failed to read/encode audio") getOutputStreamingLogger().Warn().Err(err).Msg("Failed to read/encode audio")
continue continue
} }
if n > 0 { if n > 0 {

View File

@ -315,7 +315,7 @@ func (s *AudioServerSupervisor) terminateProcess() {
// Wait for graceful shutdown // Wait for graceful shutdown
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
cmd.Wait() _ = cmd.Wait()
close(done) close(done)
}() }()

View File

@ -21,11 +21,6 @@ var (
audioSupervisor *audio.AudioServerSupervisor audioSupervisor *audio.AudioServerSupervisor
) )
func init() {
flag.BoolVar(&isAudioServer, "audio-server", false, "Run as audio server subprocess")
audioProcessDone = make(chan struct{})
}
func runAudioServer() { func runAudioServer() {
logger.Info().Msg("Starting audio server subprocess") logger.Info().Msg("Starting audio server subprocess")
@ -119,6 +114,10 @@ func startAudioSubprocess() error {
} }
func Main() { func Main() {
// Initialize flag and channel
flag.BoolVar(&isAudioServer, "audio-server", false, "Run as audio server subprocess")
audioProcessDone = make(chan struct{})
flag.Parse() flag.Parse()
// If running as audio server, only initialize audio processing // If running as audio server, only initialize audio processing

View File

@ -412,6 +412,41 @@ export default function AudioMetricsDashboard() {
/> />
</div> </div>
)} )}
{/* Microphone Connection Health */}
<div className="mt-3 rounded-md bg-slate-50 p-2 dark:bg-slate-700">
<div className="mb-2 flex items-center gap-2">
<MdSignalWifi4Bar className="h-3 w-3 text-purple-600 dark:text-purple-400" />
<span className="text-sm font-medium text-slate-900 dark:text-slate-100">
Connection Health
</span>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-xs text-slate-500 dark:text-slate-400">
Connection Drops:
</span>
<span className={cx(
"text-xs font-medium",
microphoneMetrics.connection_drops > 0
? "text-red-600 dark:text-red-400"
: "text-green-600 dark:text-green-400"
)}>
{formatNumber(microphoneMetrics.connection_drops)}
</span>
</div>
{microphoneMetrics.average_latency && (
<div className="flex justify-between">
<span className="text-xs text-slate-500 dark:text-slate-400">
Avg Latency:
</span>
<span className="text-xs font-medium text-slate-900 dark:text-slate-100">
{microphoneMetrics.average_latency}
</span>
</div>
)}
</div>
</div>
</div> </div>
)} )}

View File

@ -31,8 +31,6 @@ type Session struct {
shouldUmountVirtualMedia bool shouldUmountVirtualMedia bool
// Microphone operation throttling // Microphone operation throttling
micOpMu sync.Mutex
lastMicOp time.Time
micCooldown time.Duration micCooldown time.Duration
// Audio frame processing // Audio frame processing