mirror of https://github.com/jetkvm/kvm.git
Compare commits
12 Commits
dcce0fefb7
...
70ef7193fd
| Author | SHA1 | Date |
|---|---|---|
|
|
70ef7193fd | |
|
|
35b5dbd034 | |
|
|
7dc57bcdf3 | |
|
|
05b347fe74 | |
|
|
e989cad633 | |
|
|
fc38830af1 | |
|
|
76b80da157 | |
|
|
01719e01dd | |
|
|
753c613708 | |
|
|
f6dd605ea6 | |
|
|
680607e82e | |
|
|
6c6a1def28 |
|
|
@ -15,14 +15,9 @@ func ensureAudioControlService() *audio.AudioControlService {
|
|||
sessionProvider := &SessionProviderImpl{}
|
||||
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(
|
||||
func() *audio.AudioControlService { return audioControlService },
|
||||
func() audio.AudioConfig { return audioControlService.GetCurrentAudioQuality() },
|
||||
func(quality audio.AudioQuality) error {
|
||||
audioControlService.SetAudioQuality(quality)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
return audioControlService
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,58 +26,17 @@ type AudioConfigConstants struct {
|
|||
FrameSize int // Samples per audio frame (default: 960 for 20ms at 48kHz)
|
||||
MaxPacketSize int // Maximum encoded packet size in bytes (default: 4000)
|
||||
|
||||
// Audio Quality Bitrates (kbps)
|
||||
AudioQualityLowOutputBitrate int // Low-quality output bitrate (default: 32)
|
||||
AudioQualityLowInputBitrate int // Low-quality input bitrate (default: 16)
|
||||
AudioQualityMediumOutputBitrate int // Medium-quality output bitrate (default: 64)
|
||||
AudioQualityMediumInputBitrate int // Medium-quality input bitrate (default: 32)
|
||||
AudioQualityHighOutputBitrate int // High-quality output bitrate (default: 128)
|
||||
AudioQualityHighInputBitrate int // High-quality input bitrate (default: 64)
|
||||
AudioQualityUltraOutputBitrate int // Ultra-quality output bitrate (default: 192)
|
||||
AudioQualityUltraInputBitrate int // Ultra-quality input bitrate (default: 96)
|
||||
// Optimal Audio Configuration (S16_LE @ 48kHz stereo from HDMI)
|
||||
// Single optimized setting - no quality presets needed
|
||||
OptimalOutputBitrate int // Output bitrate: 96 kbps (optimal for stereo @ 48kHz)
|
||||
OptimalInputBitrate int // Input bitrate: 48 kbps (optimal for mono mic @ 48kHz)
|
||||
|
||||
// Audio Quality Sample Rates (Hz)
|
||||
AudioQualityLowSampleRate int // Low-quality sample rate (default: 22050)
|
||||
AudioQualityMediumSampleRate int // Medium-quality sample rate (default: 44100)
|
||||
AudioQualityMicLowSampleRate int // Low-quality microphone sample rate (default: 16000)
|
||||
|
||||
// Audio Quality Frame Sizes
|
||||
AudioQualityLowFrameSize time.Duration // Low-quality frame duration (default: 40ms)
|
||||
AudioQualityMediumFrameSize time.Duration // Medium-quality frame duration (default: 20ms)
|
||||
AudioQualityHighFrameSize time.Duration // High-quality frame duration (default: 20ms)
|
||||
|
||||
AudioQualityUltraFrameSize time.Duration // Ultra-quality frame duration (default: 10ms)
|
||||
|
||||
// Audio Quality Channels
|
||||
AudioQualityLowChannels int // Low-quality channel count (default: 1)
|
||||
AudioQualityMediumChannels int // Medium-quality channel count (default: 2)
|
||||
AudioQualityHighChannels int // High-quality channel count (default: 2)
|
||||
AudioQualityUltraChannels int // Ultra-quality channel count (default: 2)
|
||||
|
||||
// Audio Quality OPUS Encoder Parameters
|
||||
AudioQualityLowOpusComplexity int // Low-quality OPUS complexity (default: 1)
|
||||
AudioQualityLowOpusVBR int // Low-quality OPUS VBR setting (default: 0)
|
||||
AudioQualityLowOpusSignalType int // Low-quality OPUS signal type (default: 3001)
|
||||
AudioQualityLowOpusBandwidth int // Low-quality OPUS bandwidth (default: 1101)
|
||||
AudioQualityLowOpusDTX int // Low-quality OPUS DTX setting (default: 1)
|
||||
|
||||
AudioQualityMediumOpusComplexity int // Medium-quality OPUS complexity (default: 5)
|
||||
AudioQualityMediumOpusVBR int // Medium-quality OPUS VBR setting (default: 1)
|
||||
AudioQualityMediumOpusSignalType int // Medium-quality OPUS signal type (default: 3002)
|
||||
AudioQualityMediumOpusBandwidth int // Medium-quality OPUS bandwidth (default: 1103)
|
||||
AudioQualityMediumOpusDTX int // Medium-quality OPUS DTX setting (default: 0)
|
||||
|
||||
AudioQualityHighOpusComplexity int // High-quality OPUS complexity (default: 8)
|
||||
AudioQualityHighOpusVBR int // High-quality OPUS VBR setting (default: 1)
|
||||
AudioQualityHighOpusSignalType int // High-quality OPUS signal type (default: 3002)
|
||||
AudioQualityHighOpusBandwidth int // High-quality OPUS bandwidth (default: 1104)
|
||||
AudioQualityHighOpusDTX int // High-quality OPUS DTX setting (default: 0)
|
||||
|
||||
AudioQualityUltraOpusComplexity int // Ultra-quality OPUS complexity (default: 10)
|
||||
AudioQualityUltraOpusVBR int // Ultra-quality OPUS VBR setting (default: 1)
|
||||
AudioQualityUltraOpusSignalType int // Ultra-quality OPUS signal type (default: 3002)
|
||||
AudioQualityUltraOpusBandwidth int // Ultra-quality OPUS bandwidth (default: 1105)
|
||||
AudioQualityUltraOpusDTX int // Ultra-quality OPUS DTX setting (default: 0)
|
||||
// Optimal OPUS Encoder Parameters (minimal CPU usage)
|
||||
OptimalOpusComplexity int // Complexity: 1 (minimal CPU ~0.5%)
|
||||
OptimalOpusVBR int // VBR: enabled for efficiency
|
||||
OptimalOpusSignalType int // Signal: OPUS_SIGNAL_MUSIC (3002)
|
||||
OptimalOpusBandwidth int // Bandwidth: WIDEBAND (1103 = native 48kHz)
|
||||
OptimalOpusDTX int // DTX: disabled for continuous audio
|
||||
|
||||
// CGO Audio Constants
|
||||
CGOOpusBitrate int // Native Opus encoder bitrate in bps (default: 96000)
|
||||
|
|
@ -315,66 +274,27 @@ func DefaultAudioConfig() *AudioConfigConstants {
|
|||
FrameSize: 960,
|
||||
MaxPacketSize: 4000,
|
||||
|
||||
AudioQualityLowOutputBitrate: 32,
|
||||
AudioQualityLowInputBitrate: 16,
|
||||
AudioQualityMediumOutputBitrate: 48,
|
||||
AudioQualityMediumInputBitrate: 24,
|
||||
AudioQualityHighOutputBitrate: 64,
|
||||
AudioQualityHighInputBitrate: 32,
|
||||
AudioQualityUltraOutputBitrate: 96,
|
||||
AudioQualityUltraInputBitrate: 48,
|
||||
AudioQualityLowSampleRate: 48000,
|
||||
AudioQualityMediumSampleRate: 48000,
|
||||
AudioQualityMicLowSampleRate: 16000,
|
||||
AudioQualityLowFrameSize: 20 * time.Millisecond,
|
||||
AudioQualityMediumFrameSize: 20 * time.Millisecond,
|
||||
AudioQualityHighFrameSize: 20 * time.Millisecond,
|
||||
// Optimal Audio Configuration (single setting for all use cases)
|
||||
OptimalOutputBitrate: 96, // 96 kbps for stereo @ 48kHz
|
||||
OptimalInputBitrate: 48, // 48 kbps for mono mic @ 48kHz
|
||||
OptimalOpusComplexity: 1, // Complexity 1: minimal CPU (~0.5%)
|
||||
OptimalOpusVBR: 1, // VBR enabled for efficiency
|
||||
OptimalOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC
|
||||
OptimalOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND (native 48kHz)
|
||||
OptimalOpusDTX: 0, // DTX disabled for continuous audio
|
||||
|
||||
AudioQualityUltraFrameSize: 20 * time.Millisecond, // Ultra-quality frame duration
|
||||
|
||||
// Audio Quality Channels
|
||||
AudioQualityLowChannels: 1, // Mono for low quality
|
||||
AudioQualityMediumChannels: 2, // Stereo for medium quality
|
||||
AudioQualityHighChannels: 2, // Stereo for high quality
|
||||
AudioQualityUltraChannels: 2, // Stereo for ultra quality
|
||||
|
||||
// Audio Quality OPUS Parameters
|
||||
AudioQualityLowOpusComplexity: 0, // Low complexity
|
||||
AudioQualityLowOpusVBR: 1, // VBR enabled
|
||||
AudioQualityLowOpusSignalType: 3001, // OPUS_SIGNAL_VOICE
|
||||
AudioQualityLowOpusBandwidth: 1101, // OPUS_BANDWIDTH_NARROWBAND
|
||||
AudioQualityLowOpusDTX: 1, // DTX enabled
|
||||
|
||||
AudioQualityMediumOpusComplexity: 1, // Low complexity
|
||||
AudioQualityMediumOpusVBR: 1, // VBR enabled
|
||||
AudioQualityMediumOpusSignalType: 3001, // OPUS_SIGNAL_VOICE
|
||||
AudioQualityMediumOpusBandwidth: 1102, // OPUS_BANDWIDTH_MEDIUMBAND
|
||||
AudioQualityMediumOpusDTX: 1, // DTX enabled
|
||||
|
||||
AudioQualityHighOpusComplexity: 2, // Medium complexity
|
||||
AudioQualityHighOpusVBR: 1, // VBR enabled
|
||||
AudioQualityHighOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC
|
||||
AudioQualityHighOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND
|
||||
AudioQualityHighOpusDTX: 0, // DTX disabled
|
||||
|
||||
AudioQualityUltraOpusComplexity: 3, // Higher complexity
|
||||
AudioQualityUltraOpusVBR: 1, // VBR enabled
|
||||
AudioQualityUltraOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC
|
||||
AudioQualityUltraOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND
|
||||
AudioQualityUltraOpusDTX: 0, // DTX disabled
|
||||
|
||||
// CGO Audio Constants - Optimized for RV1106 native audio processing
|
||||
CGOOpusBitrate: 64000, // Reduced for RV1106 efficiency
|
||||
CGOOpusComplexity: 2, // Minimal complexity for RV1106
|
||||
CGOOpusVBR: 1,
|
||||
CGOOpusVBRConstraint: 1,
|
||||
CGOOpusSignalType: 3002, // OPUS_SIGNAL_MUSIC
|
||||
CGOOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND for RV1106
|
||||
CGOOpusDTX: 0,
|
||||
CGOSampleRate: 48000,
|
||||
CGOChannels: 2,
|
||||
CGOFrameSize: 960,
|
||||
CGOMaxPacketSize: 1200, // Reduced for RV1106 memory efficiency
|
||||
// CGO Audio Constants - Optimized for S16_LE @ 48kHz with minimal CPU
|
||||
CGOOpusBitrate: 96000, // 96 kbps optimal for stereo @ 48kHz
|
||||
CGOOpusComplexity: 1, // Complexity 1: minimal CPU (~0.5% on RV1106)
|
||||
CGOOpusVBR: 1, // VBR enabled for efficiency
|
||||
CGOOpusVBRConstraint: 1, // Constrained VBR for predictable bitrate
|
||||
CGOOpusSignalType: -1000, // OPUS_AUTO (automatic voice/music detection)
|
||||
CGOOpusBandwidth: 1103, // OPUS_BANDWIDTH_WIDEBAND (native 48kHz, no resampling)
|
||||
CGOOpusDTX: 0, // DTX disabled for continuous audio
|
||||
CGOSampleRate: 48000, // 48 kHz native HDMI sample rate
|
||||
CGOChannels: 2, // Stereo
|
||||
CGOFrameSize: 960, // 20ms frames at 48kHz
|
||||
CGOMaxPacketSize: 1500, // Standard Ethernet MTU
|
||||
|
||||
// Input IPC Constants
|
||||
InputIPCSampleRate: 48000, // Input IPC sample rate (48kHz)
|
||||
|
|
|
|||
|
|
@ -236,19 +236,19 @@ func (s *AudioControlService) GetMicrophoneStatus() map[string]interface{} {
|
|||
}
|
||||
}
|
||||
|
||||
// SetAudioQuality sets the audio output quality
|
||||
func (s *AudioControlService) SetAudioQuality(quality AudioQuality) {
|
||||
SetAudioQuality(quality)
|
||||
// SetAudioQuality is deprecated - audio quality is now fixed at optimal settings
|
||||
func (s *AudioControlService) SetAudioQuality(quality int) {
|
||||
// No-op: quality is fixed at optimal configuration
|
||||
}
|
||||
|
||||
// GetAudioQualityPresets returns available audio quality presets
|
||||
func (s *AudioControlService) GetAudioQualityPresets() map[AudioQuality]AudioConfig {
|
||||
return GetAudioQualityPresets()
|
||||
// GetAudioQualityPresets is deprecated - returns empty map
|
||||
func (s *AudioControlService) GetAudioQualityPresets() map[int]AudioConfig {
|
||||
return map[int]AudioConfig{}
|
||||
}
|
||||
|
||||
// GetMicrophoneQualityPresets returns available microphone quality presets
|
||||
func (s *AudioControlService) GetMicrophoneQualityPresets() map[AudioQuality]AudioConfig {
|
||||
return GetMicrophoneQualityPresets()
|
||||
// GetMicrophoneQualityPresets is deprecated - returns empty map
|
||||
func (s *AudioControlService) GetMicrophoneQualityPresets() map[int]AudioConfig {
|
||||
return map[int]AudioConfig{}
|
||||
}
|
||||
|
||||
// GetCurrentAudioQuality returns the current audio quality configuration
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
// Validation errors
|
||||
var (
|
||||
ErrInvalidAudioQuality = errors.New("invalid audio quality level")
|
||||
ErrInvalidFrameSize = errors.New("invalid frame size")
|
||||
ErrInvalidFrameData = errors.New("invalid frame data")
|
||||
ErrFrameDataEmpty = errors.New("invalid frame data: frame data is empty")
|
||||
|
|
@ -30,13 +29,9 @@ var (
|
|||
ErrInvalidLength = errors.New("invalid length")
|
||||
)
|
||||
|
||||
// ValidateAudioQuality validates audio quality enum values with enhanced checks
|
||||
func ValidateAudioQuality(quality AudioQuality) error {
|
||||
// Validate enum range
|
||||
if quality < AudioQualityLow || quality > AudioQualityUltra {
|
||||
return fmt.Errorf("%w: quality value %d outside valid range [%d, %d]",
|
||||
ErrInvalidAudioQuality, int(quality), int(AudioQualityLow), int(AudioQualityUltra))
|
||||
}
|
||||
// ValidateAudioQuality is deprecated - quality is now fixed at optimal settings
|
||||
func ValidateAudioQuality(quality int) error {
|
||||
// Quality validation removed - using fixed optimal configuration
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -316,9 +311,6 @@ func ValidateAudioConfigComplete(config AudioConfig) error {
|
|||
}
|
||||
|
||||
// Slower path: validate each parameter individually
|
||||
if err := ValidateAudioQuality(config.Quality); err != nil {
|
||||
return fmt.Errorf("quality validation failed: %w", err)
|
||||
}
|
||||
if err := ValidateBitrate(config.Bitrate); err != nil {
|
||||
return fmt.Errorf("bitrate validation failed: %w", err)
|
||||
}
|
||||
|
|
@ -336,12 +328,7 @@ func ValidateAudioConfigComplete(config AudioConfig) error {
|
|||
|
||||
// ValidateAudioConfigConstants validates audio configuration constants
|
||||
func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
|
||||
// Validate that audio quality constants are within valid ranges
|
||||
for _, quality := range []AudioQuality{AudioQualityLow, AudioQualityMedium, AudioQualityHigh, AudioQualityUltra} {
|
||||
if err := ValidateAudioQuality(quality); err != nil {
|
||||
return fmt.Errorf("invalid audio quality constant %v: %w", quality, err)
|
||||
}
|
||||
}
|
||||
// Quality validation removed - using fixed optimal configuration
|
||||
// Validate configuration values if config is provided
|
||||
if config != nil {
|
||||
if Config.MaxFrameSize <= 0 {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func (aom *AudioOutputIPCManager) Start() error {
|
|||
config := UnifiedIPCConfig{
|
||||
SampleRate: Config.SampleRate,
|
||||
Channels: Config.Channels,
|
||||
FrameSize: int(Config.AudioQualityMediumFrameSize.Milliseconds()),
|
||||
FrameSize: 20, // Fixed 20ms frame size for optimal audio
|
||||
}
|
||||
|
||||
if err := aom.SendConfig(config); err != nil {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
// Key components: output/input pipelines with Opus codec, buffer management,
|
||||
// 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.
|
||||
//
|
||||
// # Performance Characteristics
|
||||
|
|
@ -14,13 +14,12 @@
|
|||
// Designed for embedded ARM systems with limited resources:
|
||||
// - Sub-50ms end-to-end latency under normal conditions
|
||||
// - Memory usage scales with buffer configuration
|
||||
// - CPU usage optimized through zero-copy operations
|
||||
// - Network bandwidth adapts to quality settings
|
||||
// - CPU usage optimized through zero-copy operations and complexity=1 Opus
|
||||
// - Fixed optimal configuration (96 kbps output, 48 kbps input)
|
||||
//
|
||||
// # Usage Example
|
||||
//
|
||||
// config := GetAudioConfig()
|
||||
// SetAudioQuality(AudioQualityHigh)
|
||||
//
|
||||
// // Audio output will automatically start when frames are received
|
||||
package audio
|
||||
|
|
@ -29,8 +28,6 @@ import (
|
|||
"errors"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/logging"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -42,23 +39,13 @@ func GetMaxAudioFrameSize() int {
|
|||
return Config.MaxAudioFrameSize
|
||||
}
|
||||
|
||||
// AudioQuality represents different audio quality presets
|
||||
type AudioQuality int
|
||||
|
||||
const (
|
||||
AudioQualityLow AudioQuality = iota
|
||||
AudioQualityMedium
|
||||
AudioQualityHigh
|
||||
AudioQualityUltra
|
||||
)
|
||||
|
||||
// AudioConfig holds configuration for audio processing
|
||||
// AudioConfig holds the optimal audio configuration
|
||||
// All settings are fixed for S16_LE @ 48kHz HDMI audio
|
||||
type AudioConfig struct {
|
||||
Quality AudioQuality
|
||||
Bitrate int // kbps
|
||||
SampleRate int // Hz
|
||||
Channels int
|
||||
FrameSize time.Duration // ms
|
||||
Bitrate int // kbps (96 for output, 48 for input)
|
||||
SampleRate int // Hz (always 48000)
|
||||
Channels int // 2 for output (stereo), 1 for input (mono)
|
||||
FrameSize time.Duration // ms (always 20ms)
|
||||
}
|
||||
|
||||
// AudioMetrics tracks audio performance metrics
|
||||
|
|
@ -72,195 +59,29 @@ type AudioMetrics struct {
|
|||
}
|
||||
|
||||
var (
|
||||
// Optimal configuration for audio output (HDMI → client)
|
||||
currentConfig = AudioConfig{
|
||||
Quality: AudioQualityMedium,
|
||||
Bitrate: Config.AudioQualityMediumOutputBitrate,
|
||||
Bitrate: Config.OptimalOutputBitrate,
|
||||
SampleRate: Config.SampleRate,
|
||||
Channels: Config.Channels,
|
||||
FrameSize: Config.AudioQualityMediumFrameSize,
|
||||
FrameSize: 20 * time.Millisecond,
|
||||
}
|
||||
// Optimal configuration for microphone input (client → target)
|
||||
currentMicrophoneConfig = AudioConfig{
|
||||
Quality: AudioQualityMedium,
|
||||
Bitrate: Config.AudioQualityMediumInputBitrate,
|
||||
Bitrate: Config.OptimalInputBitrate,
|
||||
SampleRate: Config.SampleRate,
|
||||
Channels: 1,
|
||||
FrameSize: Config.AudioQualityMediumFrameSize,
|
||||
FrameSize: 20 * time.Millisecond,
|
||||
}
|
||||
metrics AudioMetrics
|
||||
)
|
||||
|
||||
// qualityPresets defines the base quality configurations
|
||||
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
|
||||
// GetAudioConfig returns the current optimal audio configuration
|
||||
func GetAudioConfig() AudioConfig {
|
||||
return currentConfig
|
||||
}
|
||||
|
||||
// GetMicrophoneConfig returns the current microphone configuration
|
||||
// GetMicrophoneConfig returns the current optimal microphone configuration
|
||||
func GetMicrophoneConfig() AudioConfig {
|
||||
return currentMicrophoneConfig
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,22 +7,14 @@ import (
|
|||
// RPC wrapper functions for audio control
|
||||
// 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 (
|
||||
getAudioControlServiceFunc func() *AudioControlService
|
||||
getAudioQualityFunc func() AudioConfig
|
||||
setAudioQualityFunc func(AudioQuality) error
|
||||
)
|
||||
|
||||
// SetRPCCallbacks sets the callback functions for RPC operations
|
||||
func SetRPCCallbacks(
|
||||
getService func() *AudioControlService,
|
||||
getQuality func() AudioConfig,
|
||||
setQuality func(AudioQuality) error,
|
||||
) {
|
||||
// SetRPCCallbacks sets the callback function for RPC operations
|
||||
func SetRPCCallbacks(getService func() *AudioControlService) {
|
||||
getAudioControlServiceFunc = getService
|
||||
getAudioQualityFunc = getQuality
|
||||
setAudioQualityFunc = setQuality
|
||||
}
|
||||
|
||||
// RPCAudioMute handles audio mute/unmute RPC requests
|
||||
|
|
@ -37,30 +29,11 @@ func RPCAudioMute(muted bool) error {
|
|||
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) {
|
||||
if getAudioQualityFunc == nil || setAudioQualityFunc == nil {
|
||||
return nil, fmt.Errorf("audio quality functions not available")
|
||||
}
|
||||
|
||||
// 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
|
||||
// Quality is now fixed - return current optimal configuration
|
||||
currentConfig := GetAudioConfig()
|
||||
return map[string]any{"config": currentConfig}, nil
|
||||
}
|
||||
|
||||
|
|
@ -100,21 +73,15 @@ func RPCAudioStatus() (map[string]interface{}, error) {
|
|||
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) {
|
||||
if getAudioControlServiceFunc == nil || getAudioQualityFunc == nil {
|
||||
return nil, fmt.Errorf("audio control service not available")
|
||||
}
|
||||
service := getAudioControlServiceFunc()
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("audio control service not initialized")
|
||||
}
|
||||
|
||||
presets := service.GetAudioQualityPresets()
|
||||
current := getAudioQualityFunc()
|
||||
// Return single optimal configuration as both preset and current
|
||||
current := GetAudioConfig()
|
||||
|
||||
// Return empty presets map (UI will handle this gracefully)
|
||||
return map[string]any{
|
||||
"presets": presets,
|
||||
"presets": map[string]any{},
|
||||
"current": current,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
14
main.go
14
main.go
|
|
@ -36,15 +36,15 @@ func startAudioSubprocess() error {
|
|||
audioInputSupervisor := audio.NewAudioInputSupervisor()
|
||||
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
|
||||
audioInputSupervisor.SetOpusConfig(
|
||||
audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
|
||||
audioConfig.AudioQualityLowOpusComplexity,
|
||||
audioConfig.AudioQualityLowOpusVBR,
|
||||
audioConfig.AudioQualityLowOpusSignalType,
|
||||
audioConfig.AudioQualityLowOpusBandwidth,
|
||||
audioConfig.AudioQualityLowOpusDTX,
|
||||
audioConfig.OptimalInputBitrate*1000, // Convert kbps to bps (48 kbps)
|
||||
audioConfig.OptimalOpusComplexity, // Complexity 1 for minimal CPU
|
||||
audioConfig.OptimalOpusVBR, // VBR enabled
|
||||
audioConfig.OptimalOpusSignalType, // MUSIC signal type
|
||||
audioConfig.OptimalOpusBandwidth, // WIDEBAND for 48kHz
|
||||
audioConfig.OptimalOpusDTX, // DTX disabled
|
||||
)
|
||||
|
||||
// Note: Audio input supervisor is NOT started here - it will be started on-demand
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { useAudioEvents } from "@/hooks/useAudioEvents";
|
|||
import { useJsonRpc, JsonRpcResponse } from "@/hooks/useJsonRpc";
|
||||
import { useRTCStore } from "@/hooks/stores";
|
||||
import notifications from "@/notifications";
|
||||
import audioQualityService from "@/services/audioQualityService";
|
||||
|
||||
// Type for microphone error
|
||||
interface MicrophoneError {
|
||||
|
|
@ -69,11 +68,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
|||
const { send } = useJsonRpc();
|
||||
|
||||
// Initialize audio quality service with RPC for cloud compatibility
|
||||
useEffect(() => {
|
||||
if (send) {
|
||||
audioQualityService.setRpcSend(send);
|
||||
}
|
||||
}, [send]);
|
||||
// Audio quality service removed - using fixed optimal configuration
|
||||
|
||||
// WebSocket-only implementation - no fallback polling
|
||||
|
||||
|
|
@ -131,12 +126,24 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
|||
|
||||
const loadAudioConfigurations = async () => {
|
||||
try {
|
||||
// Use centralized audio quality service
|
||||
const { audio } = await audioQualityService.loadAllConfigurations();
|
||||
// Load audio configuration directly via RPC
|
||||
if (!send) return;
|
||||
|
||||
if (audio) {
|
||||
setCurrentConfig(audio.current);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
send("audioStatus", {}, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
reject(new Error(resp.error.message));
|
||||
} else if ("result" in resp && resp.result) {
|
||||
const result = resp.result as any;
|
||||
if (result.config) {
|
||||
setCurrentConfig(result.config);
|
||||
}
|
||||
resolve();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setConfigsLoaded(true);
|
||||
} catch {
|
||||
|
|
@ -180,34 +187,7 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
|||
}
|
||||
};
|
||||
|
||||
const handleQualityChange = async (quality: number) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
// Quality change handler removed - quality is now fixed at optimal settings
|
||||
|
||||
const handleToggleMicrophoneEnable = async () => {
|
||||
const now = Date.now();
|
||||
|
|
@ -447,45 +427,24 @@ export default function AudioControlPopover({ microphone }: AudioControlPopoverP
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{/* Quality Settings */}
|
||||
<div className="space-y-3">
|
||||
{/* Audio Quality Info (fixed optimal configuration) */}
|
||||
{currentConfig && (
|
||||
<div className="space-y-2 rounded-md bg-slate-50 p-3 dark:bg-slate-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<MdGraphicEq className="h-4 w-4 text-slate-600 dark:text-slate-400" />
|
||||
<span className="font-medium text-slate-900 dark:text-slate-100">
|
||||
Audio Output Quality
|
||||
Audio Configuration
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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 className="text-sm text-slate-600 dark:text-slate-400">
|
||||
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>
|
||||
|
||||
{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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -89,17 +89,8 @@ export const AUDIO_CONFIG = {
|
|||
SYNC_DEBOUNCE_MS: 1000, // debounce state synchronization
|
||||
AUDIO_TEST_TIMEOUT: 100, // ms - timeout for audio testing
|
||||
|
||||
// NOTE: Audio quality presets (bitrates, sample rates, channels, frame sizes)
|
||||
// are now fetched dynamically from the backend API via audioQualityService
|
||||
// to eliminate duplication with backend config_constants.go
|
||||
|
||||
// Default Quality Labels - will be updated dynamically by audioQualityService
|
||||
DEFAULT_QUALITY_LABELS: {
|
||||
0: "Low",
|
||||
1: "Medium",
|
||||
2: "High",
|
||||
3: "Ultra",
|
||||
} as const,
|
||||
// Audio quality is fixed at optimal settings (96 kbps @ 48kHz stereo)
|
||||
// No quality presets needed - single optimal configuration for all use cases
|
||||
|
||||
// Audio Analysis
|
||||
ANALYSIS_FFT_SIZE: 256, // for detailed audio analysis
|
||||
|
|
|
|||
|
|
@ -1,146 +0,0 @@
|
|||
import { JsonRpcResponse } from '@/hooks/useJsonRpc';
|
||||
|
||||
interface AudioConfig {
|
||||
Quality: number;
|
||||
Bitrate: number;
|
||||
SampleRate: number;
|
||||
Channels: number;
|
||||
FrameSize: string;
|
||||
}
|
||||
|
||||
type QualityPresets = Record<number, AudioConfig>;
|
||||
|
||||
interface AudioQualityResponse {
|
||||
current: AudioConfig;
|
||||
presets: QualityPresets;
|
||||
}
|
||||
|
||||
type RpcSendFunction = (method: string, params: Record<string, unknown>, callback: (resp: JsonRpcResponse) => void) => void;
|
||||
|
||||
class AudioQualityService {
|
||||
private audioPresets: QualityPresets | null = null;
|
||||
private microphonePresets: QualityPresets | null = null;
|
||||
private qualityLabels: Record<number, string> = {
|
||||
0: 'Low',
|
||||
1: 'Medium',
|
||||
2: 'High',
|
||||
3: 'Ultra'
|
||||
};
|
||||
private rpcSend: RpcSendFunction | null = null;
|
||||
|
||||
/**
|
||||
* Set RPC send function for cloud compatibility
|
||||
*/
|
||||
setRpcSend(rpcSend: RpcSendFunction): void {
|
||||
this.rpcSend = rpcSend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch audio quality presets using RPC (cloud-compatible)
|
||||
*/
|
||||
async fetchAudioQualityPresets(): Promise<AudioQualityResponse | null> {
|
||||
if (!this.rpcSend) {
|
||||
console.error('RPC not available for audio quality presets');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await new Promise<AudioQualityResponse | null>((resolve) => {
|
||||
this.rpcSend!("audioQualityPresets", {}, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
console.error('RPC audio quality presets failed:', resp.error);
|
||||
resolve(null);
|
||||
} else if ("result" in resp) {
|
||||
const data = resp.result as AudioQualityResponse;
|
||||
this.audioPresets = data.presets;
|
||||
this.updateQualityLabels(data.presets);
|
||||
resolve(data);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch audio quality presets:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update quality labels with actual bitrates from presets
|
||||
*/
|
||||
private updateQualityLabels(presets: QualityPresets): void {
|
||||
const newQualityLabels: Record<number, string> = {};
|
||||
Object.entries(presets).forEach(([qualityNum, preset]) => {
|
||||
const quality = parseInt(qualityNum);
|
||||
const qualityNames = ['Low', 'Medium', 'High', 'Ultra'];
|
||||
const name = qualityNames[quality] || `Quality ${quality}`;
|
||||
newQualityLabels[quality] = `${name} (${preset.Bitrate}kbps)`;
|
||||
});
|
||||
this.qualityLabels = newQualityLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quality labels with bitrates
|
||||
*/
|
||||
getQualityLabels(): Record<number, string> {
|
||||
return this.qualityLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached audio presets
|
||||
*/
|
||||
getAudioPresets(): QualityPresets | null {
|
||||
return this.audioPresets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached microphone presets
|
||||
*/
|
||||
getMicrophonePresets(): QualityPresets | null {
|
||||
return this.microphonePresets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set audio quality using RPC (cloud-compatible)
|
||||
*/
|
||||
async setAudioQuality(quality: number): Promise<boolean> {
|
||||
if (!this.rpcSend) {
|
||||
console.error('RPC not available for audio quality change');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return await new Promise<boolean>((resolve) => {
|
||||
this.rpcSend!("audioQuality", { quality }, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
console.error('RPC audio quality change failed:', resp.error);
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to set audio quality:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load both audio and microphone configurations
|
||||
*/
|
||||
async loadAllConfigurations(): Promise<{
|
||||
audio: AudioQualityResponse | null;
|
||||
}> {
|
||||
const [audio ] = await Promise.all([
|
||||
this.fetchAudioQualityPresets(),
|
||||
]);
|
||||
|
||||
return { audio };
|
||||
}
|
||||
}
|
||||
|
||||
// Export a singleton instance
|
||||
export const audioQualityService = new AudioQualityService();
|
||||
export default audioQualityService;
|
||||
Loading…
Reference in New Issue