From 9c0aff44897fc122e2f9380e8c96544f28cc798c Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Aug 2025 22:02:22 +0000 Subject: [PATCH] feat(audio): implement audio input supervisor and opus config management add audio input supervisor with opus configuration support update audio quality presets and configuration handling restructure audio subprocess management with environment variables --- cmd/main.go | 1 + internal/audio/api.go | 16 +++++- internal/audio/audio.go | 81 ++++++++++++++++++++++++-- internal/audio/config_constants.go | 86 ++++++++++++++-------------- internal/audio/input_server_main.go | 40 +++++++++++++ internal/audio/input_supervisor.go | 36 ++++++++++-- internal/audio/output_server_main.go | 51 +++++++++++++++++ internal/audio/supervisor.go | 31 +++++++++- main.go | 21 +++++++ 9 files changed, 306 insertions(+), 57 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 35ae413d..3308bcc6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,6 +13,7 @@ func main() { versionJsonPtr := flag.Bool("version-json", false, "print version as json and exit") audioServerPtr := flag.Bool("audio-output-server", false, "Run as audio server subprocess") audioInputServerPtr := flag.Bool("audio-input-server", false, "Run as audio input server subprocess") + flag.Parse() if *versionPtr || *versionJsonPtr { diff --git a/internal/audio/api.go b/internal/audio/api.go index 0444ad93..5d9fe5fa 100644 --- a/internal/audio/api.go +++ b/internal/audio/api.go @@ -8,8 +8,8 @@ import ( ) var ( - // Global audio output supervisor instance globalOutputSupervisor unsafe.Pointer // *AudioOutputSupervisor + globalInputSupervisor unsafe.Pointer // *AudioInputSupervisor ) // isAudioServerProcess detects if we're running as the audio server subprocess @@ -70,3 +70,17 @@ func GetAudioOutputSupervisor() *AudioOutputSupervisor { } return (*AudioOutputSupervisor)(ptr) } + +// SetAudioInputSupervisor sets the global audio input supervisor +func SetAudioInputSupervisor(supervisor *AudioInputSupervisor) { + atomic.StorePointer(&globalInputSupervisor, unsafe.Pointer(supervisor)) +} + +// GetAudioInputSupervisor returns the global audio input supervisor +func GetAudioInputSupervisor() *AudioInputSupervisor { + ptr := atomic.LoadPointer(&globalInputSupervisor) + if ptr == nil { + return nil + } + return (*AudioInputSupervisor)(ptr) +} diff --git a/internal/audio/audio.go b/internal/audio/audio.go index 696772da..e6f46743 100644 --- a/internal/audio/audio.go +++ b/internal/audio/audio.go @@ -167,7 +167,7 @@ func SetAudioQuality(quality AudioQuality) { if config, exists := presets[quality]; exists { currentConfig = config - // Update CGO OPUS encoder parameters based on quality + // Get OPUS encoder parameters based on quality var complexity, vbr, signalType, bandwidth, dtx int switch quality { case AudioQualityLow: @@ -203,11 +203,27 @@ func SetAudioQuality(quality AudioQuality) { dtx = GetConfig().AudioQualityMediumOpusDTX } - // Dynamically update CGO OPUS encoder parameters - // Use current VBR constraint setting from config - vbrConstraint := GetConfig().CGOOpusVBRConstraint - if err := updateOpusEncoderParams(config.Bitrate*1000, complexity, vbr, vbrConstraint, signalType, bandwidth, dtx); err != nil { - logging.GetDefaultLogger().Error().Err(err).Msg("Failed to update OPUS encoder parameters") + // Restart audio output subprocess with new OPUS configuration + if supervisor := GetAudioOutputSupervisor(); supervisor != nil { + logger := logging.GetDefaultLogger().With().Str("component", "audio").Logger() + logger.Info().Int("quality", int(quality)).Msg("restarting audio output subprocess with new quality settings") + + // Set new OPUS configuration + supervisor.SetOpusConfig(config.Bitrate*1000, complexity, vbr, signalType, bandwidth, dtx) + + // Stop current subprocess + supervisor.Stop() + + // Start subprocess with new configuration + if err := supervisor.Start(); err != nil { + logger.Error().Err(err).Msg("failed to restart audio output subprocess") + } + } else { + // Fallback to dynamic update if supervisor is not available + vbrConstraint := GetConfig().CGOOpusVBRConstraint + if err := updateOpusEncoderParams(config.Bitrate*1000, complexity, vbr, vbrConstraint, signalType, bandwidth, dtx); err != nil { + logging.GetDefaultLogger().Error().Err(err).Msg("Failed to update OPUS encoder parameters") + } } } } @@ -230,6 +246,59 @@ func SetMicrophoneQuality(quality AudioQuality) { presets := GetMicrophoneQualityPresets() if config, exists := presets[quality]; exists { currentMicrophoneConfig = config + + // Get OPUS parameters for the selected quality + var complexity, vbr, signalType, bandwidth, dtx int + switch quality { + case AudioQualityLow: + complexity = GetConfig().AudioQualityLowOpusComplexity + vbr = GetConfig().AudioQualityLowOpusVBR + signalType = GetConfig().AudioQualityLowOpusSignalType + bandwidth = GetConfig().AudioQualityLowOpusBandwidth + dtx = GetConfig().AudioQualityLowOpusDTX + case AudioQualityMedium: + complexity = GetConfig().AudioQualityMediumOpusComplexity + vbr = GetConfig().AudioQualityMediumOpusVBR + signalType = GetConfig().AudioQualityMediumOpusSignalType + bandwidth = GetConfig().AudioQualityMediumOpusBandwidth + dtx = GetConfig().AudioQualityMediumOpusDTX + case AudioQualityHigh: + complexity = GetConfig().AudioQualityHighOpusComplexity + vbr = GetConfig().AudioQualityHighOpusVBR + signalType = GetConfig().AudioQualityHighOpusSignalType + bandwidth = GetConfig().AudioQualityHighOpusBandwidth + dtx = GetConfig().AudioQualityHighOpusDTX + case AudioQualityUltra: + complexity = GetConfig().AudioQualityUltraOpusComplexity + vbr = GetConfig().AudioQualityUltraOpusVBR + signalType = GetConfig().AudioQualityUltraOpusSignalType + bandwidth = GetConfig().AudioQualityUltraOpusBandwidth + dtx = GetConfig().AudioQualityUltraOpusDTX + default: + // Use medium quality as fallback + complexity = GetConfig().AudioQualityMediumOpusComplexity + vbr = GetConfig().AudioQualityMediumOpusVBR + signalType = GetConfig().AudioQualityMediumOpusSignalType + bandwidth = GetConfig().AudioQualityMediumOpusBandwidth + dtx = GetConfig().AudioQualityMediumOpusDTX + } + + // Restart audio input subprocess with new OPUS configuration + if supervisor := GetAudioInputSupervisor(); supervisor != nil { + logger := logging.GetDefaultLogger().With().Str("component", "audio").Logger() + logger.Info().Int("quality", int(quality)).Msg("restarting audio input subprocess with new quality settings") + + // Set new OPUS configuration + supervisor.SetOpusConfig(config.Bitrate*1000, complexity, vbr, signalType, bandwidth, dtx) + + // Stop current subprocess + supervisor.Stop() + + // Start subprocess with new configuration + if err := supervisor.Start(); err != nil { + logger.Error().Err(err).Msg("failed to restart audio input subprocess") + } + } } } diff --git a/internal/audio/config_constants.go b/internal/audio/config_constants.go index bcee5669..715bbe2b 100644 --- a/internal/audio/config_constants.go +++ b/internal/audio/config_constants.go @@ -1555,10 +1555,10 @@ func DefaultAudioConfig() *AudioConfigConstants { MaxPacketSize: 4000, // Audio Quality Bitrates - AudioQualityLowOutputBitrate: 32, - AudioQualityLowInputBitrate: 16, - AudioQualityMediumOutputBitrate: 64, - AudioQualityMediumInputBitrate: 32, + AudioQualityLowOutputBitrate: 48, + AudioQualityLowInputBitrate: 24, + AudioQualityMediumOutputBitrate: 80, + AudioQualityMediumInputBitrate: 40, // AudioQualityHighOutputBitrate defines bitrate for high-quality output. // Used in: Professional applications requiring excellent audio fidelity @@ -1573,16 +1573,16 @@ func DefaultAudioConfig() *AudioConfigConstants { AudioQualityHighInputBitrate: 64, // AudioQualityUltraOutputBitrate defines bitrate for ultra-quality output. - // Used in: Audiophile-grade reproduction and high-bandwidth connections - // Impact: Maximum quality but requires significant bandwidth. - // Default 192kbps suitable for high-bandwidth, quality-critical scenarios. - AudioQualityUltraOutputBitrate: 192, + // Used in: High-quality reproduction with optimized resource usage + // Impact: Excellent quality while maintaining system stability. + // Default 160kbps provides excellent audio quality with reduced CPU load. + AudioQualityUltraOutputBitrate: 160, // AudioQualityUltraInputBitrate defines bitrate for ultra-quality input. - // Used in: Professional microphone input requiring maximum quality - // Impact: Provides audiophile-grade voice quality with high bandwidth. - // Default 96kbps ensures maximum voice reproduction quality. - AudioQualityUltraInputBitrate: 96, + // Used in: Professional microphone input with balanced resource usage + // Impact: Provides excellent voice quality while maintaining stability. + // Default 80kbps ensures excellent voice reproduction with reduced CPU load. + AudioQualityUltraInputBitrate: 80, // Audio Quality Sample Rates - Sampling frequencies for different quality levels // Used in: Audio capture, processing, and format negotiation @@ -1590,15 +1590,15 @@ func DefaultAudioConfig() *AudioConfigConstants { // AudioQualityLowSampleRate defines sampling frequency for low-quality audio. // Used in: Bandwidth-constrained scenarios and basic audio requirements - // Impact: Captures frequencies up to 11kHz while minimizing processing load. - // Default 22.05kHz sufficient for speech and basic audio. - AudioQualityLowSampleRate: 22050, + // Impact: Captures frequencies up to 24kHz while maintaining efficiency. + // Default 48kHz provides better quality while maintaining compatibility. + AudioQualityLowSampleRate: 48000, // AudioQualityMediumSampleRate defines sampling frequency for medium-quality audio. - // Used in: Standard audio scenarios requiring CD-quality reproduction - // Impact: Captures full audible range up to 22kHz with balanced processing. - // Default 44.1kHz provides CD-quality standard with excellent balance. - AudioQualityMediumSampleRate: 44100, + // Used in: Standard audio scenarios requiring high-quality reproduction + // Impact: Captures full audible range up to 24kHz with excellent processing. + // Default 48kHz provides professional standard with optimal balance. + AudioQualityMediumSampleRate: 48000, // AudioQualityMicLowSampleRate defines sampling frequency for low-quality microphone. // Used in: Voice/microphone input in bandwidth-constrained scenarios @@ -1612,9 +1612,9 @@ func DefaultAudioConfig() *AudioConfigConstants { // AudioQualityLowFrameSize defines frame duration for low-quality audio. // Used in: Bandwidth-constrained scenarios prioritizing efficiency - // Impact: Reduces processing overhead with acceptable latency increase. - // Default 40ms provides efficiency for constrained scenarios. - AudioQualityLowFrameSize: 40 * time.Millisecond, + // Impact: Balances processing overhead with acceptable latency. + // Default 20ms provides better responsiveness for low-quality scenarios. + AudioQualityLowFrameSize: 20 * time.Millisecond, // AudioQualityMediumFrameSize defines frame duration for medium-quality audio. // Used in: Standard real-time audio applications @@ -1629,14 +1629,14 @@ func DefaultAudioConfig() *AudioConfigConstants { AudioQualityHighFrameSize: 20 * time.Millisecond, // AudioQualityUltraFrameSize defines frame duration for ultra-quality audio. - // Used in: Applications requiring immediate audio feedback - // Impact: Minimizes latency for ultra-responsive audio processing. - // Default 10ms ensures minimal latency for immediate feedback. - AudioQualityUltraFrameSize: 10 * time.Millisecond, + // Used in: Applications requiring excellent quality with balanced performance + // Impact: Balances latency and processing efficiency for stable operation. + // Default 20ms provides excellent quality while reducing CPU load. + AudioQualityUltraFrameSize: 20 * time.Millisecond, // Audio Quality Channels - Channel configuration for different quality levels // Used in: Audio processing pipeline for channel handling and bandwidth control - AudioQualityLowChannels: 1, + AudioQualityLowChannels: 2, AudioQualityMediumChannels: 2, AudioQualityHighChannels: 2, AudioQualityUltraChannels: 2, @@ -1645,32 +1645,32 @@ func DefaultAudioConfig() *AudioConfigConstants { // Used in: Dynamic OPUS encoder configuration based on quality presets // Impact: Controls encoding complexity, VBR, signal type, bandwidth, and DTX - // Low Quality OPUS Parameters - Optimized for bandwidth conservation - AudioQualityLowOpusComplexity: 1, // Low complexity for minimal CPU usage - AudioQualityLowOpusVBR: 0, // CBR for predictable bandwidth - AudioQualityLowOpusSignalType: 3001, // OPUS_SIGNAL_VOICE - AudioQualityLowOpusBandwidth: 1101, // OPUS_BANDWIDTH_NARROWBAND - AudioQualityLowOpusDTX: 1, // Enable DTX for silence suppression + // Low Quality OPUS Parameters - Optimized for bandwidth conservation with better quality + AudioQualityLowOpusComplexity: 3, // Balanced complexity for better quality + AudioQualityLowOpusVBR: 1, // VBR for better quality at same bitrate + AudioQualityLowOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC for better general audio + AudioQualityLowOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND for better frequency range + AudioQualityLowOpusDTX: 0, // Disable DTX for consistent quality - // Medium Quality OPUS Parameters - Balanced performance and quality - AudioQualityMediumOpusComplexity: 5, // Medium complexity for balanced performance - AudioQualityMediumOpusVBR: 1, // VBR for better quality + // Medium Quality OPUS Parameters - Enhanced performance and quality + AudioQualityMediumOpusComplexity: 6, // Higher complexity for better quality + AudioQualityMediumOpusVBR: 1, // VBR for optimal quality AudioQualityMediumOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC - AudioQualityMediumOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND + AudioQualityMediumOpusBandwidth: 1104, // OPUS_BANDWIDTH_SUPERWIDEBAND for better range AudioQualityMediumOpusDTX: 0, // Disable DTX for consistent quality - // High Quality OPUS Parameters - High quality with good performance - AudioQualityHighOpusComplexity: 8, // High complexity for better quality + // High Quality OPUS Parameters - Premium quality with optimized performance + AudioQualityHighOpusComplexity: 9, // Near-maximum complexity for excellent quality AudioQualityHighOpusVBR: 1, // VBR for optimal quality AudioQualityHighOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC - AudioQualityHighOpusBandwidth: 1104, // OPUS_BANDWIDTH_SUPERWIDEBAND + AudioQualityHighOpusBandwidth: 1105, // OPUS_BANDWIDTH_FULLBAND for full frequency range AudioQualityHighOpusDTX: 0, // Disable DTX for consistent quality - // Ultra Quality OPUS Parameters - Maximum quality settings - AudioQualityUltraOpusComplexity: 10, // Maximum complexity for best quality + // Ultra Quality OPUS Parameters - Optimized for high quality with reasonable resource usage + AudioQualityUltraOpusComplexity: 8, // Reduced complexity to prevent CPU overload AudioQualityUltraOpusVBR: 1, // VBR for optimal quality AudioQualityUltraOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC - AudioQualityUltraOpusBandwidth: 1105, // OPUS_BANDWIDTH_FULLBAND + AudioQualityUltraOpusBandwidth: 1104, // OPUS_BANDWIDTH_SUPERWIDEBAND for better stability AudioQualityUltraOpusDTX: 0, // Disable DTX for maximum quality // CGO Audio Constants diff --git a/internal/audio/input_server_main.go b/internal/audio/input_server_main.go index 6846d598..0cc8b11b 100644 --- a/internal/audio/input_server_main.go +++ b/internal/audio/input_server_main.go @@ -4,18 +4,58 @@ import ( "context" "os" "os/signal" + "strconv" "syscall" "time" "github.com/jetkvm/kvm/internal/logging" ) +// getEnvInt reads an integer from environment variable with a default value +func getEnvIntInput(key string, defaultValue int) int { + if value := os.Getenv(key); value != "" { + if intValue, err := strconv.Atoi(value); err == nil { + return intValue + } + } + return defaultValue +} + +// parseOpusConfigInput reads OPUS configuration from environment variables +// with fallback to default config values for input server +func parseOpusConfigInput() (bitrate, complexity, vbr, signalType, bandwidth, dtx int) { + // Read configuration from environment variables with config defaults + bitrate = getEnvIntInput("JETKVM_OPUS_BITRATE", GetConfig().CGOOpusBitrate) + complexity = getEnvIntInput("JETKVM_OPUS_COMPLEXITY", GetConfig().CGOOpusComplexity) + vbr = getEnvIntInput("JETKVM_OPUS_VBR", GetConfig().CGOOpusVBR) + signalType = getEnvIntInput("JETKVM_OPUS_SIGNAL_TYPE", GetConfig().CGOOpusSignalType) + bandwidth = getEnvIntInput("JETKVM_OPUS_BANDWIDTH", GetConfig().CGOOpusBandwidth) + dtx = getEnvIntInput("JETKVM_OPUS_DTX", GetConfig().CGOOpusDTX) + + return bitrate, complexity, vbr, signalType, bandwidth, dtx +} + +// applyOpusConfigInput applies OPUS configuration to the global config for input server +func applyOpusConfigInput(bitrate, complexity, vbr, signalType, bandwidth, dtx int) { + config := GetConfig() + config.CGOOpusBitrate = bitrate + config.CGOOpusComplexity = complexity + config.CGOOpusVBR = vbr + config.CGOOpusSignalType = signalType + config.CGOOpusBandwidth = bandwidth + config.CGOOpusDTX = dtx +} + // RunAudioInputServer runs the audio input server subprocess // This should be called from main() when the subprocess is detected func RunAudioInputServer() error { logger := logging.GetDefaultLogger().With().Str("component", "audio-input-server").Logger() logger.Debug().Msg("audio input server subprocess starting") + // Parse OPUS configuration from environment variables + bitrate, complexity, vbr, signalType, bandwidth, dtx := parseOpusConfigInput() + applyOpusConfigInput(bitrate, complexity, vbr, signalType, bandwidth, dtx) + // Initialize validation cache for optimal performance InitValidationCache() diff --git a/internal/audio/input_supervisor.go b/internal/audio/input_supervisor.go index 50e3eb6c..f75ad307 100644 --- a/internal/audio/input_supervisor.go +++ b/internal/audio/input_supervisor.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/exec" + "strconv" "syscall" "time" ) @@ -12,6 +13,9 @@ import ( type AudioInputSupervisor struct { *BaseSupervisor client *AudioInputClient + + // Environment variables for OPUS configuration + opusEnv []string } // NewAudioInputSupervisor creates a new audio input supervisor @@ -22,6 +26,23 @@ func NewAudioInputSupervisor() *AudioInputSupervisor { } } +// SetOpusConfig sets OPUS configuration parameters as environment variables +// for the audio input subprocess +func (ais *AudioInputSupervisor) SetOpusConfig(bitrate, complexity, vbr, signalType, bandwidth, dtx int) { + ais.mutex.Lock() + defer ais.mutex.Unlock() + + // Store OPUS parameters as environment variables + ais.opusEnv = []string{ + "JETKVM_OPUS_BITRATE=" + strconv.Itoa(bitrate), + "JETKVM_OPUS_COMPLEXITY=" + strconv.Itoa(complexity), + "JETKVM_OPUS_VBR=" + strconv.Itoa(vbr), + "JETKVM_OPUS_SIGNAL_TYPE=" + strconv.Itoa(signalType), + "JETKVM_OPUS_BANDWIDTH=" + strconv.Itoa(bandwidth), + "JETKVM_OPUS_DTX=" + strconv.Itoa(dtx), + } +} + // Start starts the audio input server subprocess func (ais *AudioInputSupervisor) Start() error { ais.mutex.Lock() @@ -40,11 +61,16 @@ func (ais *AudioInputSupervisor) Start() error { return fmt.Errorf("failed to get executable path: %w", err) } + // Build command arguments (only subprocess flag) + args := []string{"--audio-input-server"} + // Create command for audio input server subprocess - cmd := exec.CommandContext(ais.ctx, execPath, "--audio-input-server") - cmd.Env = append(os.Environ(), - "JETKVM_AUDIO_INPUT_IPC=true", // Enable IPC mode - ) + cmd := exec.CommandContext(ais.ctx, execPath, args...) + + // Set environment variables for IPC and OPUS configuration + env := append(os.Environ(), "JETKVM_AUDIO_INPUT_IPC=true") // Enable IPC mode + env = append(env, ais.opusEnv...) // Add OPUS configuration + cmd.Env = env // Set process group to allow clean termination cmd.SysProcAttr = &syscall.SysProcAttr{ @@ -62,7 +88,7 @@ func (ais *AudioInputSupervisor) Start() error { return fmt.Errorf("failed to start audio input server process: %w", err) } - ais.logger.Info().Int("pid", cmd.Process.Pid).Msg("Audio input server subprocess started") + ais.logger.Info().Int("pid", cmd.Process.Pid).Strs("args", args).Strs("opus_env", ais.opusEnv).Msg("Audio input server subprocess started") // Add process to monitoring ais.processMonitor.AddProcess(cmd.Process.Pid, "audio-input-server") diff --git a/internal/audio/output_server_main.go b/internal/audio/output_server_main.go index be9e2e51..32169f97 100644 --- a/internal/audio/output_server_main.go +++ b/internal/audio/output_server_main.go @@ -4,18 +4,69 @@ import ( "context" "os" "os/signal" + "strconv" "syscall" "time" "github.com/jetkvm/kvm/internal/logging" ) +// getEnvInt reads an integer from environment variable with a default value +func getEnvInt(key string, defaultValue int) int { + if value := os.Getenv(key); value != "" { + if intValue, err := strconv.Atoi(value); err == nil { + return intValue + } + } + return defaultValue +} + +// parseOpusConfig reads OPUS configuration from environment variables +// with fallback to default config values +func parseOpusConfig() (bitrate, complexity, vbr, signalType, bandwidth, dtx int) { + // Read configuration from environment variables with config defaults + bitrate = getEnvInt("JETKVM_OPUS_BITRATE", GetConfig().CGOOpusBitrate) + complexity = getEnvInt("JETKVM_OPUS_COMPLEXITY", GetConfig().CGOOpusComplexity) + vbr = getEnvInt("JETKVM_OPUS_VBR", GetConfig().CGOOpusVBR) + signalType = getEnvInt("JETKVM_OPUS_SIGNAL_TYPE", GetConfig().CGOOpusSignalType) + bandwidth = getEnvInt("JETKVM_OPUS_BANDWIDTH", GetConfig().CGOOpusBandwidth) + dtx = getEnvInt("JETKVM_OPUS_DTX", GetConfig().CGOOpusDTX) + + return bitrate, complexity, vbr, signalType, bandwidth, dtx +} + +// applyOpusConfig applies OPUS configuration to the global config +func applyOpusConfig(bitrate, complexity, vbr, signalType, bandwidth, dtx int) { + logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger() + + config := GetConfig() + config.CGOOpusBitrate = bitrate + config.CGOOpusComplexity = complexity + config.CGOOpusVBR = vbr + config.CGOOpusSignalType = signalType + config.CGOOpusBandwidth = bandwidth + config.CGOOpusDTX = dtx + + logger.Info(). + Int("bitrate", bitrate). + Int("complexity", complexity). + Int("vbr", vbr). + Int("signal_type", signalType). + Int("bandwidth", bandwidth). + Int("dtx", dtx). + Msg("applied OPUS configuration") +} + // RunAudioOutputServer runs the audio output server subprocess // This should be called from main() when the subprocess is detected func RunAudioOutputServer() error { logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger() logger.Debug().Msg("audio output server subprocess starting") + // Parse OPUS configuration from environment variables + bitrate, complexity, vbr, signalType, bandwidth, dtx := parseOpusConfig() + applyOpusConfig(bitrate, complexity, vbr, signalType, bandwidth, dtx) + // Initialize validation cache for optimal performance InitValidationCache() diff --git a/internal/audio/supervisor.go b/internal/audio/supervisor.go index a6d91e0f..2d96a7c0 100644 --- a/internal/audio/supervisor.go +++ b/internal/audio/supervisor.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/exec" + "strconv" "sync/atomic" "syscall" "time" @@ -42,6 +43,9 @@ type AudioOutputSupervisor struct { stopChanClosed bool // Track if stopChan is closed processDoneClosed bool // Track if processDone is closed + // Environment variables for OPUS configuration + opusEnv []string + // Callbacks onProcessStart func(pid int) onProcessExit func(pid int, exitCode int, crashed bool) @@ -72,6 +76,23 @@ func (s *AudioOutputSupervisor) SetCallbacks( s.onRestart = onRestart } +// SetOpusConfig sets OPUS configuration parameters as environment variables +// for the audio output subprocess +func (s *AudioOutputSupervisor) SetOpusConfig(bitrate, complexity, vbr, signalType, bandwidth, dtx int) { + s.mutex.Lock() + defer s.mutex.Unlock() + + // Store OPUS parameters as environment variables + s.opusEnv = []string{ + "JETKVM_OPUS_BITRATE=" + strconv.Itoa(bitrate), + "JETKVM_OPUS_COMPLEXITY=" + strconv.Itoa(complexity), + "JETKVM_OPUS_VBR=" + strconv.Itoa(vbr), + "JETKVM_OPUS_SIGNAL_TYPE=" + strconv.Itoa(signalType), + "JETKVM_OPUS_BANDWIDTH=" + strconv.Itoa(bandwidth), + "JETKVM_OPUS_DTX=" + strconv.Itoa(dtx), + } +} + // Start begins supervising the audio output server process func (s *AudioOutputSupervisor) Start() error { if !atomic.CompareAndSwapInt32(&s.running, 0, 1) { @@ -223,18 +244,24 @@ func (s *AudioOutputSupervisor) startProcess() error { s.mutex.Lock() defer s.mutex.Unlock() + // Build command arguments (only subprocess flag) + args := []string{"--audio-output-server"} + // Create new command - s.cmd = exec.CommandContext(s.ctx, execPath, "--audio-output-server") + s.cmd = exec.CommandContext(s.ctx, execPath, args...) s.cmd.Stdout = os.Stdout s.cmd.Stderr = os.Stderr + // Set environment variables for OPUS configuration + s.cmd.Env = append(os.Environ(), s.opusEnv...) + // Start the process if err := s.cmd.Start(); err != nil { return fmt.Errorf("failed to start audio output server process: %w", err) } s.processPID = s.cmd.Process.Pid - s.logger.Info().Int("pid", s.processPID).Msg("audio server process started") + s.logger.Info().Int("pid", s.processPID).Strs("args", args).Strs("opus_env", s.opusEnv).Msg("audio server process started") // Add process to monitoring s.processMonitor.AddProcess(s.processPID, "audio-output-server") diff --git a/main.go b/main.go index a6dc98d2..4f85110d 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,27 @@ func startAudioSubprocess() error { // Set the global supervisor for access from audio package audio.SetAudioOutputSupervisor(audioSupervisor) + // Create and register audio input supervisor + audioInputSupervisor := audio.NewAudioInputSupervisor() + audio.SetAudioInputSupervisor(audioInputSupervisor) + + // Set default OPUS configuration for audio input supervisor (low quality for single-core RV1106) + config := audio.GetConfig() + audioInputSupervisor.SetOpusConfig( + config.AudioQualityLowInputBitrate*1000, // Convert kbps to bps + config.AudioQualityLowOpusComplexity, + config.AudioQualityLowOpusVBR, + config.AudioQualityLowOpusSignalType, + config.AudioQualityLowOpusBandwidth, + config.AudioQualityLowOpusDTX, + ) + + // Start audio input supervisor + if err := audioInputSupervisor.Start(); err != nil { + logger.Error().Err(err).Msg("failed to start audio input supervisor") + // Continue execution as audio input is not critical for basic functionality + } + // Set up callbacks for process lifecycle events audioSupervisor.SetCallbacks( // onProcessStart