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
This commit is contained in:
Alex P 2025-08-28 22:02:22 +00:00
parent 0d4176cf98
commit 9c0aff4489
9 changed files with 306 additions and 57 deletions

View File

@ -13,6 +13,7 @@ func main() {
versionJsonPtr := flag.Bool("version-json", false, "print version as json and exit") versionJsonPtr := flag.Bool("version-json", false, "print version as json and exit")
audioServerPtr := flag.Bool("audio-output-server", false, "Run as audio server subprocess") 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") audioInputServerPtr := flag.Bool("audio-input-server", false, "Run as audio input server subprocess")
flag.Parse() flag.Parse()
if *versionPtr || *versionJsonPtr { if *versionPtr || *versionJsonPtr {

View File

@ -8,8 +8,8 @@ import (
) )
var ( var (
// Global audio output supervisor instance
globalOutputSupervisor unsafe.Pointer // *AudioOutputSupervisor globalOutputSupervisor unsafe.Pointer // *AudioOutputSupervisor
globalInputSupervisor unsafe.Pointer // *AudioInputSupervisor
) )
// isAudioServerProcess detects if we're running as the audio server subprocess // isAudioServerProcess detects if we're running as the audio server subprocess
@ -70,3 +70,17 @@ func GetAudioOutputSupervisor() *AudioOutputSupervisor {
} }
return (*AudioOutputSupervisor)(ptr) 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)
}

View File

@ -167,7 +167,7 @@ func SetAudioQuality(quality AudioQuality) {
if config, exists := presets[quality]; exists { if config, exists := presets[quality]; exists {
currentConfig = config currentConfig = config
// Update CGO OPUS encoder parameters based on quality // Get OPUS encoder parameters based on quality
var complexity, vbr, signalType, bandwidth, dtx int var complexity, vbr, signalType, bandwidth, dtx int
switch quality { switch quality {
case AudioQualityLow: case AudioQualityLow:
@ -203,14 +203,30 @@ func SetAudioQuality(quality AudioQuality) {
dtx = GetConfig().AudioQualityMediumOpusDTX dtx = GetConfig().AudioQualityMediumOpusDTX
} }
// Dynamically update CGO OPUS encoder parameters // Restart audio output subprocess with new OPUS configuration
// Use current VBR constraint setting from config 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 vbrConstraint := GetConfig().CGOOpusVBRConstraint
if err := updateOpusEncoderParams(config.Bitrate*1000, complexity, vbr, vbrConstraint, signalType, bandwidth, dtx); err != nil { 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") logging.GetDefaultLogger().Error().Err(err).Msg("Failed to update OPUS encoder parameters")
} }
} }
} }
}
// GetAudioConfig returns the current audio configuration // GetAudioConfig returns the current audio configuration
func GetAudioConfig() AudioConfig { func GetAudioConfig() AudioConfig {
@ -230,6 +246,59 @@ func SetMicrophoneQuality(quality AudioQuality) {
presets := GetMicrophoneQualityPresets() presets := GetMicrophoneQualityPresets()
if config, exists := presets[quality]; exists { if config, exists := presets[quality]; exists {
currentMicrophoneConfig = config 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")
}
}
} }
} }

View File

@ -1555,10 +1555,10 @@ func DefaultAudioConfig() *AudioConfigConstants {
MaxPacketSize: 4000, MaxPacketSize: 4000,
// Audio Quality Bitrates // Audio Quality Bitrates
AudioQualityLowOutputBitrate: 32, AudioQualityLowOutputBitrate: 48,
AudioQualityLowInputBitrate: 16, AudioQualityLowInputBitrate: 24,
AudioQualityMediumOutputBitrate: 64, AudioQualityMediumOutputBitrate: 80,
AudioQualityMediumInputBitrate: 32, AudioQualityMediumInputBitrate: 40,
// AudioQualityHighOutputBitrate defines bitrate for high-quality output. // AudioQualityHighOutputBitrate defines bitrate for high-quality output.
// Used in: Professional applications requiring excellent audio fidelity // Used in: Professional applications requiring excellent audio fidelity
@ -1573,16 +1573,16 @@ func DefaultAudioConfig() *AudioConfigConstants {
AudioQualityHighInputBitrate: 64, AudioQualityHighInputBitrate: 64,
// AudioQualityUltraOutputBitrate defines bitrate for ultra-quality output. // AudioQualityUltraOutputBitrate defines bitrate for ultra-quality output.
// Used in: Audiophile-grade reproduction and high-bandwidth connections // Used in: High-quality reproduction with optimized resource usage
// Impact: Maximum quality but requires significant bandwidth. // Impact: Excellent quality while maintaining system stability.
// Default 192kbps suitable for high-bandwidth, quality-critical scenarios. // Default 160kbps provides excellent audio quality with reduced CPU load.
AudioQualityUltraOutputBitrate: 192, AudioQualityUltraOutputBitrate: 160,
// AudioQualityUltraInputBitrate defines bitrate for ultra-quality input. // AudioQualityUltraInputBitrate defines bitrate for ultra-quality input.
// Used in: Professional microphone input requiring maximum quality // Used in: Professional microphone input with balanced resource usage
// Impact: Provides audiophile-grade voice quality with high bandwidth. // Impact: Provides excellent voice quality while maintaining stability.
// Default 96kbps ensures maximum voice reproduction quality. // Default 80kbps ensures excellent voice reproduction with reduced CPU load.
AudioQualityUltraInputBitrate: 96, AudioQualityUltraInputBitrate: 80,
// Audio Quality Sample Rates - Sampling frequencies for different quality levels // Audio Quality Sample Rates - Sampling frequencies for different quality levels
// Used in: Audio capture, processing, and format negotiation // Used in: Audio capture, processing, and format negotiation
@ -1590,15 +1590,15 @@ func DefaultAudioConfig() *AudioConfigConstants {
// AudioQualityLowSampleRate defines sampling frequency for low-quality audio. // AudioQualityLowSampleRate defines sampling frequency for low-quality audio.
// Used in: Bandwidth-constrained scenarios and basic audio requirements // Used in: Bandwidth-constrained scenarios and basic audio requirements
// Impact: Captures frequencies up to 11kHz while minimizing processing load. // Impact: Captures frequencies up to 24kHz while maintaining efficiency.
// Default 22.05kHz sufficient for speech and basic audio. // Default 48kHz provides better quality while maintaining compatibility.
AudioQualityLowSampleRate: 22050, AudioQualityLowSampleRate: 48000,
// AudioQualityMediumSampleRate defines sampling frequency for medium-quality audio. // AudioQualityMediumSampleRate defines sampling frequency for medium-quality audio.
// Used in: Standard audio scenarios requiring CD-quality reproduction // Used in: Standard audio scenarios requiring high-quality reproduction
// Impact: Captures full audible range up to 22kHz with balanced processing. // Impact: Captures full audible range up to 24kHz with excellent processing.
// Default 44.1kHz provides CD-quality standard with excellent balance. // Default 48kHz provides professional standard with optimal balance.
AudioQualityMediumSampleRate: 44100, AudioQualityMediumSampleRate: 48000,
// AudioQualityMicLowSampleRate defines sampling frequency for low-quality microphone. // AudioQualityMicLowSampleRate defines sampling frequency for low-quality microphone.
// Used in: Voice/microphone input in bandwidth-constrained scenarios // Used in: Voice/microphone input in bandwidth-constrained scenarios
@ -1612,9 +1612,9 @@ func DefaultAudioConfig() *AudioConfigConstants {
// AudioQualityLowFrameSize defines frame duration for low-quality audio. // AudioQualityLowFrameSize defines frame duration for low-quality audio.
// Used in: Bandwidth-constrained scenarios prioritizing efficiency // Used in: Bandwidth-constrained scenarios prioritizing efficiency
// Impact: Reduces processing overhead with acceptable latency increase. // Impact: Balances processing overhead with acceptable latency.
// Default 40ms provides efficiency for constrained scenarios. // Default 20ms provides better responsiveness for low-quality scenarios.
AudioQualityLowFrameSize: 40 * time.Millisecond, AudioQualityLowFrameSize: 20 * time.Millisecond,
// AudioQualityMediumFrameSize defines frame duration for medium-quality audio. // AudioQualityMediumFrameSize defines frame duration for medium-quality audio.
// Used in: Standard real-time audio applications // Used in: Standard real-time audio applications
@ -1629,14 +1629,14 @@ func DefaultAudioConfig() *AudioConfigConstants {
AudioQualityHighFrameSize: 20 * time.Millisecond, AudioQualityHighFrameSize: 20 * time.Millisecond,
// AudioQualityUltraFrameSize defines frame duration for ultra-quality audio. // AudioQualityUltraFrameSize defines frame duration for ultra-quality audio.
// Used in: Applications requiring immediate audio feedback // Used in: Applications requiring excellent quality with balanced performance
// Impact: Minimizes latency for ultra-responsive audio processing. // Impact: Balances latency and processing efficiency for stable operation.
// Default 10ms ensures minimal latency for immediate feedback. // Default 20ms provides excellent quality while reducing CPU load.
AudioQualityUltraFrameSize: 10 * time.Millisecond, AudioQualityUltraFrameSize: 20 * time.Millisecond,
// Audio Quality Channels - Channel configuration for different quality levels // Audio Quality Channels - Channel configuration for different quality levels
// Used in: Audio processing pipeline for channel handling and bandwidth control // Used in: Audio processing pipeline for channel handling and bandwidth control
AudioQualityLowChannels: 1, AudioQualityLowChannels: 2,
AudioQualityMediumChannels: 2, AudioQualityMediumChannels: 2,
AudioQualityHighChannels: 2, AudioQualityHighChannels: 2,
AudioQualityUltraChannels: 2, AudioQualityUltraChannels: 2,
@ -1645,32 +1645,32 @@ func DefaultAudioConfig() *AudioConfigConstants {
// Used in: Dynamic OPUS encoder configuration based on quality presets // Used in: Dynamic OPUS encoder configuration based on quality presets
// Impact: Controls encoding complexity, VBR, signal type, bandwidth, and DTX // Impact: Controls encoding complexity, VBR, signal type, bandwidth, and DTX
// Low Quality OPUS Parameters - Optimized for bandwidth conservation // Low Quality OPUS Parameters - Optimized for bandwidth conservation with better quality
AudioQualityLowOpusComplexity: 1, // Low complexity for minimal CPU usage AudioQualityLowOpusComplexity: 3, // Balanced complexity for better quality
AudioQualityLowOpusVBR: 0, // CBR for predictable bandwidth AudioQualityLowOpusVBR: 1, // VBR for better quality at same bitrate
AudioQualityLowOpusSignalType: 3001, // OPUS_SIGNAL_VOICE AudioQualityLowOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC for better general audio
AudioQualityLowOpusBandwidth: 1101, // OPUS_BANDWIDTH_NARROWBAND AudioQualityLowOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND for better frequency range
AudioQualityLowOpusDTX: 1, // Enable DTX for silence suppression AudioQualityLowOpusDTX: 0, // Disable DTX for consistent quality
// Medium Quality OPUS Parameters - Balanced performance and quality // Medium Quality OPUS Parameters - Enhanced performance and quality
AudioQualityMediumOpusComplexity: 5, // Medium complexity for balanced performance AudioQualityMediumOpusComplexity: 6, // Higher complexity for better quality
AudioQualityMediumOpusVBR: 1, // VBR for better quality AudioQualityMediumOpusVBR: 1, // VBR for optimal quality
AudioQualityMediumOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC 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 AudioQualityMediumOpusDTX: 0, // Disable DTX for consistent quality
// High Quality OPUS Parameters - High quality with good performance // High Quality OPUS Parameters - Premium quality with optimized performance
AudioQualityHighOpusComplexity: 8, // High complexity for better quality AudioQualityHighOpusComplexity: 9, // Near-maximum complexity for excellent quality
AudioQualityHighOpusVBR: 1, // VBR for optimal quality AudioQualityHighOpusVBR: 1, // VBR for optimal quality
AudioQualityHighOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC 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 AudioQualityHighOpusDTX: 0, // Disable DTX for consistent quality
// Ultra Quality OPUS Parameters - Maximum quality settings // Ultra Quality OPUS Parameters - Optimized for high quality with reasonable resource usage
AudioQualityUltraOpusComplexity: 10, // Maximum complexity for best quality AudioQualityUltraOpusComplexity: 8, // Reduced complexity to prevent CPU overload
AudioQualityUltraOpusVBR: 1, // VBR for optimal quality AudioQualityUltraOpusVBR: 1, // VBR for optimal quality
AudioQualityUltraOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC 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 AudioQualityUltraOpusDTX: 0, // Disable DTX for maximum quality
// CGO Audio Constants // CGO Audio Constants

View File

@ -4,18 +4,58 @@ import (
"context" "context"
"os" "os"
"os/signal" "os/signal"
"strconv"
"syscall" "syscall"
"time" "time"
"github.com/jetkvm/kvm/internal/logging" "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 // RunAudioInputServer runs the audio input server subprocess
// This should be called from main() when the subprocess is detected // This should be called from main() when the subprocess is detected
func RunAudioInputServer() error { func RunAudioInputServer() error {
logger := logging.GetDefaultLogger().With().Str("component", "audio-input-server").Logger() logger := logging.GetDefaultLogger().With().Str("component", "audio-input-server").Logger()
logger.Debug().Msg("audio input server subprocess starting") 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 // Initialize validation cache for optimal performance
InitValidationCache() InitValidationCache()

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strconv"
"syscall" "syscall"
"time" "time"
) )
@ -12,6 +13,9 @@ import (
type AudioInputSupervisor struct { type AudioInputSupervisor struct {
*BaseSupervisor *BaseSupervisor
client *AudioInputClient client *AudioInputClient
// Environment variables for OPUS configuration
opusEnv []string
} }
// NewAudioInputSupervisor creates a new audio input supervisor // 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 // Start starts the audio input server subprocess
func (ais *AudioInputSupervisor) Start() error { func (ais *AudioInputSupervisor) Start() error {
ais.mutex.Lock() ais.mutex.Lock()
@ -40,11 +61,16 @@ func (ais *AudioInputSupervisor) Start() error {
return fmt.Errorf("failed to get executable path: %w", err) 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 // Create command for audio input server subprocess
cmd := exec.CommandContext(ais.ctx, execPath, "--audio-input-server") cmd := exec.CommandContext(ais.ctx, execPath, args...)
cmd.Env = append(os.Environ(),
"JETKVM_AUDIO_INPUT_IPC=true", // Enable IPC mode // 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 // Set process group to allow clean termination
cmd.SysProcAttr = &syscall.SysProcAttr{ 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) 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 // Add process to monitoring
ais.processMonitor.AddProcess(cmd.Process.Pid, "audio-input-server") ais.processMonitor.AddProcess(cmd.Process.Pid, "audio-input-server")

View File

@ -4,18 +4,69 @@ import (
"context" "context"
"os" "os"
"os/signal" "os/signal"
"strconv"
"syscall" "syscall"
"time" "time"
"github.com/jetkvm/kvm/internal/logging" "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 // RunAudioOutputServer runs the audio output server subprocess
// This should be called from main() when the subprocess is detected // This should be called from main() when the subprocess is detected
func RunAudioOutputServer() error { func RunAudioOutputServer() error {
logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger() logger := logging.GetDefaultLogger().With().Str("component", "audio-output-server").Logger()
logger.Debug().Msg("audio output server subprocess starting") 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 // Initialize validation cache for optimal performance
InitValidationCache() InitValidationCache()

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strconv"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
@ -42,6 +43,9 @@ type AudioOutputSupervisor struct {
stopChanClosed bool // Track if stopChan is closed stopChanClosed bool // Track if stopChan is closed
processDoneClosed bool // Track if processDone is closed processDoneClosed bool // Track if processDone is closed
// Environment variables for OPUS configuration
opusEnv []string
// Callbacks // Callbacks
onProcessStart func(pid int) onProcessStart func(pid int)
onProcessExit func(pid int, exitCode int, crashed bool) onProcessExit func(pid int, exitCode int, crashed bool)
@ -72,6 +76,23 @@ func (s *AudioOutputSupervisor) SetCallbacks(
s.onRestart = onRestart 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 // Start begins supervising the audio output server process
func (s *AudioOutputSupervisor) Start() error { func (s *AudioOutputSupervisor) Start() error {
if !atomic.CompareAndSwapInt32(&s.running, 0, 1) { if !atomic.CompareAndSwapInt32(&s.running, 0, 1) {
@ -223,18 +244,24 @@ func (s *AudioOutputSupervisor) startProcess() error {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
// Build command arguments (only subprocess flag)
args := []string{"--audio-output-server"}
// Create new command // 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.Stdout = os.Stdout
s.cmd.Stderr = os.Stderr s.cmd.Stderr = os.Stderr
// Set environment variables for OPUS configuration
s.cmd.Env = append(os.Environ(), s.opusEnv...)
// Start the process // Start the process
if err := s.cmd.Start(); err != nil { if err := s.cmd.Start(); err != nil {
return fmt.Errorf("failed to start audio output server process: %w", err) return fmt.Errorf("failed to start audio output server process: %w", err)
} }
s.processPID = s.cmd.Process.Pid 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 // Add process to monitoring
s.processMonitor.AddProcess(s.processPID, "audio-output-server") s.processMonitor.AddProcess(s.processPID, "audio-output-server")

21
main.go
View File

@ -44,6 +44,27 @@ func startAudioSubprocess() error {
// Set the global supervisor for access from audio package // Set the global supervisor for access from audio package
audio.SetAudioOutputSupervisor(audioSupervisor) 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 // Set up callbacks for process lifecycle events
audioSupervisor.SetCallbacks( audioSupervisor.SetCallbacks(
// onProcessStart // onProcessStart