mirror of https://github.com/jetkvm/kvm.git
Fix: linting errors
This commit is contained in:
parent
671d875890
commit
aeb7a12c72
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -91,4 +91,4 @@ func ResetAudioInputManagers() {
|
||||||
|
|
||||||
// Reset pointer
|
// Reset pointer
|
||||||
atomic.StorePointer(&globalInputManager, nil)
|
atomic.StorePointer(&globalInputManager, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
9
main.go
9
main.go
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue