mirror of https://github.com/jetkvm/kvm.git
[WIP] Updates: simplify audio system
This commit is contained in:
parent
6c6a1def28
commit
680607e82e
|
@ -15,14 +15,9 @@ func ensureAudioControlService() *audio.AudioControlService {
|
||||||
sessionProvider := &SessionProviderImpl{}
|
sessionProvider := &SessionProviderImpl{}
|
||||||
audioControlService = audio.NewAudioControlService(sessionProvider, logger)
|
audioControlService = audio.NewAudioControlService(sessionProvider, logger)
|
||||||
|
|
||||||
// Set up RPC callback functions for the audio package
|
// Set up RPC callback function for the audio package
|
||||||
audio.SetRPCCallbacks(
|
audio.SetRPCCallbacks(
|
||||||
func() *audio.AudioControlService { return audioControlService },
|
func() *audio.AudioControlService { return audioControlService },
|
||||||
func() audio.AudioConfig { return audioControlService.GetCurrentAudioQuality() },
|
|
||||||
func(quality audio.AudioQuality) error {
|
|
||||||
audioControlService.SetAudioQuality(quality)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return audioControlService
|
return audioControlService
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// Key components: output/input pipelines with Opus codec, buffer management,
|
// Key components: output/input pipelines with Opus codec, buffer management,
|
||||||
// zero-copy frame pools, IPC communication, and process supervision.
|
// zero-copy frame pools, IPC communication, and process supervision.
|
||||||
//
|
//
|
||||||
// Supports four quality presets (Low/Medium/High/Ultra) with configurable bitrates.
|
// Optimized for S16_LE @ 48kHz stereo HDMI audio with minimal CPU usage.
|
||||||
// All APIs are thread-safe with comprehensive error handling and metrics collection.
|
// All APIs are thread-safe with comprehensive error handling and metrics collection.
|
||||||
//
|
//
|
||||||
// # Performance Characteristics
|
// # Performance Characteristics
|
||||||
|
@ -14,13 +14,12 @@
|
||||||
// Designed for embedded ARM systems with limited resources:
|
// Designed for embedded ARM systems with limited resources:
|
||||||
// - Sub-50ms end-to-end latency under normal conditions
|
// - Sub-50ms end-to-end latency under normal conditions
|
||||||
// - Memory usage scales with buffer configuration
|
// - Memory usage scales with buffer configuration
|
||||||
// - CPU usage optimized through zero-copy operations
|
// - CPU usage optimized through zero-copy operations and complexity=1 Opus
|
||||||
// - Network bandwidth adapts to quality settings
|
// - Fixed optimal configuration (96 kbps output, 48 kbps input)
|
||||||
//
|
//
|
||||||
// # Usage Example
|
// # Usage Example
|
||||||
//
|
//
|
||||||
// config := GetAudioConfig()
|
// config := GetAudioConfig()
|
||||||
// SetAudioQuality(AudioQualityHigh)
|
|
||||||
//
|
//
|
||||||
// // Audio output will automatically start when frames are received
|
// // Audio output will automatically start when frames are received
|
||||||
package audio
|
package audio
|
||||||
|
@ -42,23 +41,13 @@ func GetMaxAudioFrameSize() int {
|
||||||
return Config.MaxAudioFrameSize
|
return Config.MaxAudioFrameSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioQuality represents different audio quality presets
|
// AudioConfig holds the optimal audio configuration
|
||||||
type AudioQuality int
|
// All settings are fixed for S16_LE @ 48kHz HDMI audio
|
||||||
|
|
||||||
const (
|
|
||||||
AudioQualityLow AudioQuality = iota
|
|
||||||
AudioQualityMedium
|
|
||||||
AudioQualityHigh
|
|
||||||
AudioQualityUltra
|
|
||||||
)
|
|
||||||
|
|
||||||
// AudioConfig holds configuration for audio processing
|
|
||||||
type AudioConfig struct {
|
type AudioConfig struct {
|
||||||
Quality AudioQuality
|
Bitrate int // kbps (96 for output, 48 for input)
|
||||||
Bitrate int // kbps
|
SampleRate int // Hz (always 48000)
|
||||||
SampleRate int // Hz
|
Channels int // 2 for output (stereo), 1 for input (mono)
|
||||||
Channels int
|
FrameSize time.Duration // ms (always 20ms)
|
||||||
FrameSize time.Duration // ms
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioMetrics tracks audio performance metrics
|
// AudioMetrics tracks audio performance metrics
|
||||||
|
@ -72,195 +61,29 @@ type AudioMetrics struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// Optimal configuration for audio output (HDMI → client)
|
||||||
currentConfig = AudioConfig{
|
currentConfig = AudioConfig{
|
||||||
Quality: AudioQualityMedium,
|
Bitrate: Config.OptimalOutputBitrate,
|
||||||
Bitrate: Config.AudioQualityMediumOutputBitrate,
|
|
||||||
SampleRate: Config.SampleRate,
|
SampleRate: Config.SampleRate,
|
||||||
Channels: Config.Channels,
|
Channels: Config.Channels,
|
||||||
FrameSize: Config.AudioQualityMediumFrameSize,
|
FrameSize: 20 * time.Millisecond,
|
||||||
}
|
}
|
||||||
|
// Optimal configuration for microphone input (client → target)
|
||||||
currentMicrophoneConfig = AudioConfig{
|
currentMicrophoneConfig = AudioConfig{
|
||||||
Quality: AudioQualityMedium,
|
Bitrate: Config.OptimalInputBitrate,
|
||||||
Bitrate: Config.AudioQualityMediumInputBitrate,
|
|
||||||
SampleRate: Config.SampleRate,
|
SampleRate: Config.SampleRate,
|
||||||
Channels: 1,
|
Channels: 1,
|
||||||
FrameSize: Config.AudioQualityMediumFrameSize,
|
FrameSize: 20 * time.Millisecond,
|
||||||
}
|
}
|
||||||
metrics AudioMetrics
|
metrics AudioMetrics
|
||||||
)
|
)
|
||||||
|
|
||||||
// qualityPresets defines the base quality configurations
|
// GetAudioConfig returns the current optimal audio configuration
|
||||||
var qualityPresets = map[AudioQuality]struct {
|
|
||||||
outputBitrate, inputBitrate int
|
|
||||||
sampleRate, channels int
|
|
||||||
frameSize time.Duration
|
|
||||||
}{
|
|
||||||
AudioQualityLow: {
|
|
||||||
outputBitrate: Config.AudioQualityLowOutputBitrate, inputBitrate: Config.AudioQualityLowInputBitrate,
|
|
||||||
sampleRate: Config.AudioQualityLowSampleRate, channels: Config.AudioQualityLowChannels,
|
|
||||||
frameSize: Config.AudioQualityLowFrameSize,
|
|
||||||
},
|
|
||||||
AudioQualityMedium: {
|
|
||||||
outputBitrate: Config.AudioQualityMediumOutputBitrate, inputBitrate: Config.AudioQualityMediumInputBitrate,
|
|
||||||
sampleRate: Config.AudioQualityMediumSampleRate, channels: Config.AudioQualityMediumChannels,
|
|
||||||
frameSize: Config.AudioQualityMediumFrameSize,
|
|
||||||
},
|
|
||||||
AudioQualityHigh: {
|
|
||||||
outputBitrate: Config.AudioQualityHighOutputBitrate, inputBitrate: Config.AudioQualityHighInputBitrate,
|
|
||||||
sampleRate: Config.SampleRate, channels: Config.AudioQualityHighChannels,
|
|
||||||
frameSize: Config.AudioQualityHighFrameSize,
|
|
||||||
},
|
|
||||||
AudioQualityUltra: {
|
|
||||||
outputBitrate: Config.AudioQualityUltraOutputBitrate, inputBitrate: Config.AudioQualityUltraInputBitrate,
|
|
||||||
sampleRate: Config.SampleRate, channels: Config.AudioQualityUltraChannels,
|
|
||||||
frameSize: Config.AudioQualityUltraFrameSize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAudioQualityPresets returns predefined quality configurations for audio output
|
|
||||||
func GetAudioQualityPresets() map[AudioQuality]AudioConfig {
|
|
||||||
result := make(map[AudioQuality]AudioConfig)
|
|
||||||
for quality, preset := range qualityPresets {
|
|
||||||
config := AudioConfig{
|
|
||||||
Quality: quality,
|
|
||||||
Bitrate: preset.outputBitrate,
|
|
||||||
SampleRate: preset.sampleRate,
|
|
||||||
Channels: preset.channels,
|
|
||||||
FrameSize: preset.frameSize,
|
|
||||||
}
|
|
||||||
result[quality] = config
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMicrophoneQualityPresets returns predefined quality configurations for microphone input
|
|
||||||
func GetMicrophoneQualityPresets() map[AudioQuality]AudioConfig {
|
|
||||||
result := make(map[AudioQuality]AudioConfig)
|
|
||||||
for quality, preset := range qualityPresets {
|
|
||||||
config := AudioConfig{
|
|
||||||
Quality: quality,
|
|
||||||
Bitrate: preset.inputBitrate,
|
|
||||||
SampleRate: func() int {
|
|
||||||
if quality == AudioQualityLow {
|
|
||||||
return Config.AudioQualityMicLowSampleRate
|
|
||||||
}
|
|
||||||
return preset.sampleRate
|
|
||||||
}(),
|
|
||||||
Channels: 1, // Microphone is always mono
|
|
||||||
FrameSize: preset.frameSize,
|
|
||||||
}
|
|
||||||
result[quality] = config
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAudioQuality updates the current audio quality configuration
|
|
||||||
func SetAudioQuality(quality AudioQuality) {
|
|
||||||
// Validate audio quality parameter
|
|
||||||
if err := ValidateAudioQuality(quality); err != nil {
|
|
||||||
// Log validation error but don't fail - maintain backward compatibility
|
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "audio").Logger()
|
|
||||||
logger.Warn().Err(err).Int("quality", int(quality)).Msg("invalid audio quality, using current config")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
presets := GetAudioQualityPresets()
|
|
||||||
if config, exists := presets[quality]; exists {
|
|
||||||
currentConfig = config
|
|
||||||
|
|
||||||
// Get OPUS encoder parameters based on quality
|
|
||||||
var complexity, vbr, signalType, bandwidth, dtx int
|
|
||||||
switch quality {
|
|
||||||
case AudioQualityLow:
|
|
||||||
complexity = Config.AudioQualityLowOpusComplexity
|
|
||||||
vbr = Config.AudioQualityLowOpusVBR
|
|
||||||
signalType = Config.AudioQualityLowOpusSignalType
|
|
||||||
bandwidth = Config.AudioQualityLowOpusBandwidth
|
|
||||||
dtx = Config.AudioQualityLowOpusDTX
|
|
||||||
case AudioQualityMedium:
|
|
||||||
complexity = Config.AudioQualityMediumOpusComplexity
|
|
||||||
vbr = Config.AudioQualityMediumOpusVBR
|
|
||||||
signalType = Config.AudioQualityMediumOpusSignalType
|
|
||||||
bandwidth = Config.AudioQualityMediumOpusBandwidth
|
|
||||||
dtx = Config.AudioQualityMediumOpusDTX
|
|
||||||
case AudioQualityHigh:
|
|
||||||
complexity = Config.AudioQualityHighOpusComplexity
|
|
||||||
vbr = Config.AudioQualityHighOpusVBR
|
|
||||||
signalType = Config.AudioQualityHighOpusSignalType
|
|
||||||
bandwidth = Config.AudioQualityHighOpusBandwidth
|
|
||||||
dtx = Config.AudioQualityHighOpusDTX
|
|
||||||
case AudioQualityUltra:
|
|
||||||
complexity = Config.AudioQualityUltraOpusComplexity
|
|
||||||
vbr = Config.AudioQualityUltraOpusVBR
|
|
||||||
signalType = Config.AudioQualityUltraOpusSignalType
|
|
||||||
bandwidth = Config.AudioQualityUltraOpusBandwidth
|
|
||||||
dtx = Config.AudioQualityUltraOpusDTX
|
|
||||||
default:
|
|
||||||
// Use medium quality as fallback
|
|
||||||
complexity = Config.AudioQualityMediumOpusComplexity
|
|
||||||
vbr = Config.AudioQualityMediumOpusVBR
|
|
||||||
signalType = Config.AudioQualityMediumOpusSignalType
|
|
||||||
bandwidth = Config.AudioQualityMediumOpusBandwidth
|
|
||||||
dtx = Config.AudioQualityMediumOpusDTX
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update audio output subprocess configuration dynamically without restart
|
|
||||||
logger := logging.GetDefaultLogger().With().Str("component", "audio").Logger()
|
|
||||||
logger.Info().Int("quality", int(quality)).Msg("updating audio output quality settings dynamically")
|
|
||||||
|
|
||||||
// Set new OPUS configuration for future restarts
|
|
||||||
if supervisor := GetAudioOutputSupervisor(); supervisor != nil {
|
|
||||||
supervisor.SetOpusConfig(config.Bitrate*1000, complexity, vbr, signalType, bandwidth, dtx)
|
|
||||||
|
|
||||||
// Send dynamic configuration update to running subprocess via IPC
|
|
||||||
if supervisor.IsConnected() {
|
|
||||||
// Convert AudioConfig to UnifiedIPCOpusConfig with complete Opus parameters
|
|
||||||
opusConfig := UnifiedIPCOpusConfig{
|
|
||||||
SampleRate: config.SampleRate,
|
|
||||||
Channels: config.Channels,
|
|
||||||
FrameSize: int(config.FrameSize.Milliseconds() * int64(config.SampleRate) / 1000), // Convert ms to samples
|
|
||||||
Bitrate: config.Bitrate * 1000, // Convert kbps to bps
|
|
||||||
Complexity: complexity,
|
|
||||||
VBR: vbr,
|
|
||||||
SignalType: signalType,
|
|
||||||
Bandwidth: bandwidth,
|
|
||||||
DTX: dtx,
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info().Interface("opusConfig", opusConfig).Msg("sending Opus configuration to audio output subprocess")
|
|
||||||
if err := supervisor.SendOpusConfig(opusConfig); err != nil {
|
|
||||||
logger.Warn().Err(err).Msg("failed to send dynamic Opus config update via IPC, falling back to subprocess restart")
|
|
||||||
// Fallback to subprocess restart if IPC update fails
|
|
||||||
supervisor.Stop()
|
|
||||||
if err := supervisor.Start(); err != nil {
|
|
||||||
logger.Error().Err(err).Msg("failed to restart audio output subprocess after IPC update failure")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Info().Msg("audio output quality updated dynamically via IPC")
|
|
||||||
|
|
||||||
// Reset audio output stats after config update
|
|
||||||
go func() {
|
|
||||||
time.Sleep(Config.QualityChangeSettleDelay) // Wait for quality change to settle
|
|
||||||
// Reset audio input server stats to clear persistent warnings
|
|
||||||
ResetGlobalAudioInputServerStats()
|
|
||||||
// Attempt recovery if there are still issues
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
RecoverGlobalAudioInputServer()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Info().Bool("supervisor_running", supervisor.IsRunning()).Msg("audio output subprocess not connected, configuration will apply on next start")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAudioConfig returns the current audio configuration
|
|
||||||
func GetAudioConfig() AudioConfig {
|
func GetAudioConfig() AudioConfig {
|
||||||
return currentConfig
|
return currentConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMicrophoneConfig returns the current microphone configuration
|
// GetMicrophoneConfig returns the current optimal microphone configuration
|
||||||
func GetMicrophoneConfig() AudioConfig {
|
func GetMicrophoneConfig() AudioConfig {
|
||||||
return currentMicrophoneConfig
|
return currentMicrophoneConfig
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,22 +7,14 @@ import (
|
||||||
// RPC wrapper functions for audio control
|
// RPC wrapper functions for audio control
|
||||||
// These functions bridge the RPC layer to the AudioControlService
|
// These functions bridge the RPC layer to the AudioControlService
|
||||||
|
|
||||||
// These variables will be set by the main package to provide access to the global service
|
// This variable will be set by the main package to provide access to the global service
|
||||||
var (
|
var (
|
||||||
getAudioControlServiceFunc func() *AudioControlService
|
getAudioControlServiceFunc func() *AudioControlService
|
||||||
getAudioQualityFunc func() AudioConfig
|
|
||||||
setAudioQualityFunc func(AudioQuality) error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetRPCCallbacks sets the callback functions for RPC operations
|
// SetRPCCallbacks sets the callback function for RPC operations
|
||||||
func SetRPCCallbacks(
|
func SetRPCCallbacks(getService func() *AudioControlService) {
|
||||||
getService func() *AudioControlService,
|
|
||||||
getQuality func() AudioConfig,
|
|
||||||
setQuality func(AudioQuality) error,
|
|
||||||
) {
|
|
||||||
getAudioControlServiceFunc = getService
|
getAudioControlServiceFunc = getService
|
||||||
getAudioQualityFunc = getQuality
|
|
||||||
setAudioQualityFunc = setQuality
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPCAudioMute handles audio mute/unmute RPC requests
|
// RPCAudioMute handles audio mute/unmute RPC requests
|
||||||
|
@ -37,30 +29,11 @@ func RPCAudioMute(muted bool) error {
|
||||||
return service.MuteAudio(muted)
|
return service.MuteAudio(muted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPCAudioQuality handles audio quality change RPC requests
|
// RPCAudioQuality is deprecated - quality is now fixed at optimal settings
|
||||||
|
// Returns current config for backward compatibility
|
||||||
func RPCAudioQuality(quality int) (map[string]any, error) {
|
func RPCAudioQuality(quality int) (map[string]any, error) {
|
||||||
if getAudioQualityFunc == nil || setAudioQualityFunc == nil {
|
// Quality is now fixed - return current optimal configuration
|
||||||
return nil, fmt.Errorf("audio quality functions not available")
|
currentConfig := GetAudioConfig()
|
||||||
}
|
|
||||||
|
|
||||||
// Convert int to AudioQuality type
|
|
||||||
audioQuality := AudioQuality(quality)
|
|
||||||
|
|
||||||
// Get current audio quality configuration
|
|
||||||
currentConfig := getAudioQualityFunc()
|
|
||||||
|
|
||||||
// Set new quality if different
|
|
||||||
if currentConfig.Quality != audioQuality {
|
|
||||||
err := setAudioQualityFunc(audioQuality)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to set audio quality: %w", err)
|
|
||||||
}
|
|
||||||
// Get updated config after setting
|
|
||||||
newConfig := getAudioQualityFunc()
|
|
||||||
return map[string]any{"config": newConfig}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return current config if no change needed
|
|
||||||
return map[string]any{"config": currentConfig}, nil
|
return map[string]any{"config": currentConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,21 +73,15 @@ func RPCAudioStatus() (map[string]interface{}, error) {
|
||||||
return service.GetAudioStatus(), nil
|
return service.GetAudioStatus(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPCAudioQualityPresets handles audio quality presets RPC requests (read-only)
|
// RPCAudioQualityPresets is deprecated - returns single optimal configuration
|
||||||
|
// Kept for backward compatibility with UI
|
||||||
func RPCAudioQualityPresets() (map[string]any, error) {
|
func RPCAudioQualityPresets() (map[string]any, error) {
|
||||||
if getAudioControlServiceFunc == nil || getAudioQualityFunc == nil {
|
// Return single optimal configuration as both preset and current
|
||||||
return nil, fmt.Errorf("audio control service not available")
|
current := GetAudioConfig()
|
||||||
}
|
|
||||||
service := getAudioControlServiceFunc()
|
|
||||||
if service == nil {
|
|
||||||
return nil, fmt.Errorf("audio control service not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
presets := service.GetAudioQualityPresets()
|
|
||||||
current := getAudioQualityFunc()
|
|
||||||
|
|
||||||
|
// Return empty presets map (UI will handle this gracefully)
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"presets": presets,
|
"presets": map[string]any{},
|
||||||
"current": current,
|
"current": current,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
14
main.go
14
main.go
|
@ -36,15 +36,15 @@ func startAudioSubprocess() error {
|
||||||
audioInputSupervisor := audio.NewAudioInputSupervisor()
|
audioInputSupervisor := audio.NewAudioInputSupervisor()
|
||||||
audio.SetAudioInputSupervisor(audioInputSupervisor)
|
audio.SetAudioInputSupervisor(audioInputSupervisor)
|
||||||
|
|
||||||
// Set default OPUS configuration for audio input supervisor (low quality for single-core RV1106)
|
// Set optimal OPUS configuration for audio input supervisor (48 kbps mono mic)
|
||||||
audioConfig := audio.Config
|
audioConfig := audio.Config
|
||||||
audioInputSupervisor.SetOpusConfig(
|
audioInputSupervisor.SetOpusConfig(
|
||||||
audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
|
audioConfig.OptimalInputBitrate*1000, // Convert kbps to bps (48 kbps)
|
||||||
audioConfig.AudioQualityLowOpusComplexity,
|
audioConfig.OptimalOpusComplexity, // Complexity 1 for minimal CPU
|
||||||
audioConfig.AudioQualityLowOpusVBR,
|
audioConfig.OptimalOpusVBR, // VBR enabled
|
||||||
audioConfig.AudioQualityLowOpusSignalType,
|
audioConfig.OptimalOpusSignalType, // MUSIC signal type
|
||||||
audioConfig.AudioQualityLowOpusBandwidth,
|
audioConfig.OptimalOpusBandwidth, // WIDEBAND for 48kHz
|
||||||
audioConfig.AudioQualityLowOpusDTX,
|
audioConfig.OptimalOpusDTX, // DTX disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
// Note: Audio input supervisor is NOT started here - it will be started on-demand
|
// Note: Audio input supervisor is NOT started here - it will be started on-demand
|
||||||
|
|
|
@ -180,34 +180,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQualityChange = async (quality: number) => {
|
// Quality change handler removed - quality is now fixed at optimal settings
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
// Use RPC for device communication - works for both local and cloud
|
|
||||||
if (rpcDataChannel?.readyState !== "open") {
|
|
||||||
throw new Error("Device connection not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
send("audioQuality", { quality }, (resp: JsonRpcResponse) => {
|
|
||||||
if ("error" in resp) {
|
|
||||||
reject(new Error(resp.error.message));
|
|
||||||
} else {
|
|
||||||
// Update local state with response
|
|
||||||
if ("result" in resp && resp.result && typeof resp.result === 'object' && 'config' in resp.result) {
|
|
||||||
setCurrentConfig(resp.result.config as AudioConfig);
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : "Failed to change audio quality";
|
|
||||||
notifications.error(errorMessage);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleMicrophoneEnable = async () => {
|
const handleToggleMicrophoneEnable = async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -447,41 +420,23 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quality Settings */}
|
{/* Audio Quality Info (fixed optimal configuration) */}
|
||||||
<div className="space-y-3">
|
{currentConfig && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="space-y-2 rounded-md bg-slate-50 p-3 dark:bg-slate-800">
|
||||||
<MdGraphicEq className="h-4 w-4 text-slate-600 dark:text-slate-400" />
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium text-slate-900 dark:text-slate-100">
|
<MdGraphicEq className="h-4 w-4 text-slate-600 dark:text-slate-400" />
|
||||||
Audio Output Quality
|
<span className="font-medium text-slate-900 dark:text-slate-100">
|
||||||
</span>
|
Audio Configuration
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
{Object.entries(audioQualityService.getQualityLabels()).map(([quality, label]) => (
|
|
||||||
<button
|
|
||||||
key={quality}
|
|
||||||
onClick={() => handleQualityChange(parseInt(quality))}
|
|
||||||
disabled={isLoading}
|
|
||||||
className={cx(
|
|
||||||
"rounded-md border px-3 py-2 text-sm font-medium transition-colors",
|
|
||||||
currentConfig?.Quality === parseInt(quality)
|
|
||||||
? "border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-300"
|
|
||||||
: "border-slate-200 bg-white text-slate-700 hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-700 dark:text-slate-300 dark:hover:bg-slate-600",
|
|
||||||
isLoading && "opacity-50 cursor-not-allowed"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{currentConfig && (
|
|
||||||
<div className="text-xs text-slate-600 dark:text-slate-400 mt-2">
|
|
||||||
Bitrate: {currentConfig.Bitrate}kbps |
|
|
||||||
Sample Rate: {currentConfig.SampleRate}Hz
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
</div>
|
Optimized for S16_LE @ 48kHz stereo HDMI audio
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-slate-500 dark:text-slate-500">
|
||||||
|
Bitrate: {currentConfig.Bitrate} kbps | Sample Rate: {currentConfig.SampleRate} Hz | Channels: {currentConfig.Channels}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue