mirror of https://github.com/jetkvm/kvm.git
Compare commits
2 Commits
ef4b5572be
...
704d807fe9
| Author | SHA1 | Date |
|---|---|---|
|
|
704d807fe9 | |
|
|
7ce1520c2f |
|
|
@ -51,6 +51,11 @@ func (ais *AudioInputSupervisor) SetOpusConfig(bitrate, complexity, vbr, signalT
|
|||
|
||||
// Start begins supervising the audio input server process
|
||||
func (ais *AudioInputSupervisor) Start() error {
|
||||
// Check if USB audio is enabled before starting
|
||||
if !IsUsbAudioEnabled() {
|
||||
return fmt.Errorf("USB audio device is disabled - cannot start audio input supervisor")
|
||||
}
|
||||
|
||||
if !atomic.CompareAndSwapInt32(&ais.running, 0, 1) {
|
||||
return fmt.Errorf("audio input supervisor is already running")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
var (
|
||||
globalOutputSupervisor unsafe.Pointer // *AudioOutputSupervisor
|
||||
globalInputSupervisor unsafe.Pointer // *AudioInputSupervisor
|
||||
usbAudioEnabledFunc func() bool // Callback to check USB audio status
|
||||
)
|
||||
|
||||
// isAudioServerProcess detects if we're running as the audio server subprocess
|
||||
|
|
@ -84,3 +85,16 @@ func GetAudioInputSupervisor() *AudioInputSupervisor {
|
|||
}
|
||||
return (*AudioInputSupervisor)(ptr)
|
||||
}
|
||||
|
||||
// SetUsbAudioEnabledCallback sets the callback function to check USB audio status
|
||||
func SetUsbAudioEnabledCallback(callback func() bool) {
|
||||
usbAudioEnabledFunc = callback
|
||||
}
|
||||
|
||||
// IsUsbAudioEnabled checks if USB audio device is enabled via the callback
|
||||
func IsUsbAudioEnabled() bool {
|
||||
if usbAudioEnabledFunc == nil {
|
||||
return false // Default to disabled if callback not set
|
||||
}
|
||||
return usbAudioEnabledFunc()
|
||||
}
|
||||
|
|
|
|||
150
jsonrpc.go
150
jsonrpc.go
|
|
@ -914,11 +914,6 @@ func validateAudioConfiguration(enabled bool) error {
|
|||
return nil // Disabling audio is always allowed
|
||||
}
|
||||
|
||||
// Check if audio supervisor is available
|
||||
if audioSupervisor == nil {
|
||||
return fmt.Errorf("audio supervisor not initialized - audio functionality not available")
|
||||
}
|
||||
|
||||
// Check if ALSA devices are available by attempting to list them
|
||||
// This is a basic check to ensure the system has audio capabilities
|
||||
if _, err := os.Stat("/proc/asound/cards"); os.IsNotExist(err) {
|
||||
|
|
@ -964,13 +959,27 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
|
|||
logger.Info().Msg("audio input manager stopped")
|
||||
}
|
||||
|
||||
// Stop global audio input supervisor if active
|
||||
if AudioInputSupervisor != nil && AudioInputSupervisor.IsRunning() {
|
||||
logger.Info().Msg("stopping global audio input supervisor")
|
||||
AudioInputSupervisor.Stop()
|
||||
// Wait for audio input supervisor to fully stop
|
||||
for i := 0; i < 50; i++ { // Wait up to 5 seconds
|
||||
if !AudioInputSupervisor.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
logger.Info().Msg("global audio input supervisor stopped")
|
||||
}
|
||||
|
||||
// Stop audio output supervisor
|
||||
if audioSupervisor != nil && audioSupervisor.IsRunning() {
|
||||
if AudioOutputSupervisor != nil && AudioOutputSupervisor.IsRunning() {
|
||||
logger.Info().Msg("stopping audio output supervisor")
|
||||
audioSupervisor.Stop()
|
||||
AudioOutputSupervisor.Stop()
|
||||
// Wait for audio processes to fully stop before proceeding
|
||||
for i := 0; i < 50; i++ { // Wait up to 5 seconds
|
||||
if !audioSupervisor.IsRunning() {
|
||||
if !AudioOutputSupervisor.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
|
@ -979,8 +988,8 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
|
|||
}
|
||||
|
||||
logger.Info().Msg("audio processes stopped, proceeding with USB gadget reconfiguration")
|
||||
} else if newAudioEnabled && audioSupervisor != nil && !audioSupervisor.IsRunning() {
|
||||
// Start audio processes when audio is enabled (after USB reconfiguration)
|
||||
} else if newAudioEnabled {
|
||||
// Audio being enabled - supervisors will be created/started after USB reconfiguration
|
||||
logger.Info().Msg("audio will be started after USB gadget reconfiguration")
|
||||
}
|
||||
}
|
||||
|
|
@ -995,17 +1004,42 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
|
|||
}
|
||||
|
||||
// Start audio processes after successful USB reconfiguration if needed
|
||||
if previousAudioEnabled != newAudioEnabled && newAudioEnabled && audioSupervisor != nil {
|
||||
// Ensure supervisor is fully stopped before starting
|
||||
if previousAudioEnabled != newAudioEnabled && newAudioEnabled {
|
||||
// Create supervisors if they don't exist (happens when audio was previously disabled)
|
||||
if AudioOutputSupervisor == nil {
|
||||
logger.Info().Msg("creating audio output supervisor for USB audio enablement")
|
||||
AudioOutputSupervisor = audio.NewAudioOutputSupervisor()
|
||||
audio.SetAudioOutputSupervisor(AudioOutputSupervisor)
|
||||
}
|
||||
|
||||
// Create audio input supervisor if it doesn't exist
|
||||
if AudioInputSupervisor == nil {
|
||||
logger.Info().Msg("creating audio input supervisor for USB audio enablement")
|
||||
AudioInputSupervisor = audio.NewAudioInputSupervisor()
|
||||
audio.SetAudioInputSupervisor(AudioInputSupervisor)
|
||||
|
||||
// Set default OPUS configuration for audio input supervisor
|
||||
audioConfig := audio.Config
|
||||
AudioInputSupervisor.SetOpusConfig(
|
||||
audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
|
||||
audioConfig.AudioQualityLowOpusComplexity,
|
||||
audioConfig.AudioQualityLowOpusVBR,
|
||||
audioConfig.AudioQualityLowOpusSignalType,
|
||||
audioConfig.AudioQualityLowOpusBandwidth,
|
||||
audioConfig.AudioQualityLowOpusDTX,
|
||||
)
|
||||
}
|
||||
|
||||
// Ensure audio output supervisor is fully stopped before starting
|
||||
for i := 0; i < 50; i++ { // Wait up to 5 seconds
|
||||
if !audioSupervisor.IsRunning() {
|
||||
if !AudioOutputSupervisor.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
logger.Info().Msg("starting audio processes after USB gadget reconfiguration")
|
||||
if err := audioSupervisor.Start(); err != nil {
|
||||
logger.Error().Err(err).Msg("failed to start audio supervisor")
|
||||
if err := AudioOutputSupervisor.Start(); err != nil {
|
||||
logger.Error().Err(err).Msg("failed to start audio output supervisor")
|
||||
// Don't return error here as USB reconfiguration was successful
|
||||
} else {
|
||||
// Broadcast audio device change event to notify WebRTC session
|
||||
|
|
@ -1013,6 +1047,18 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
|
|||
broadcaster.BroadcastAudioDeviceChanged(true, "usb_reconfiguration")
|
||||
logger.Info().Msg("broadcasted audio device change event after USB reconfiguration")
|
||||
}
|
||||
|
||||
// Start audio input supervisor if microphone restoration is enabled
|
||||
// This makes audio input consistent with audio output - both auto-start when USB audio enabled
|
||||
if AudioInputSupervisor != nil {
|
||||
logger.Info().Msg("starting audio input supervisor after USB gadget reconfiguration")
|
||||
if err := AudioInputSupervisor.Start(); err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to start audio input supervisor - will be available on-demand")
|
||||
// This is not a critical failure - audio input can be started on-demand later
|
||||
} else {
|
||||
logger.Info().Msg("audio input supervisor started successfully after USB reconfiguration")
|
||||
}
|
||||
}
|
||||
} else if previousAudioEnabled != newAudioEnabled {
|
||||
// Broadcast audio device change event for disabling audio
|
||||
broadcaster := audio.GetAudioEventBroadcaster()
|
||||
|
|
@ -1058,42 +1104,92 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
|
|||
logger.Info().Msg("audio input manager stopped")
|
||||
}
|
||||
|
||||
// Stop global audio input supervisor if active
|
||||
if AudioInputSupervisor != nil && AudioInputSupervisor.IsRunning() {
|
||||
logger.Info().Msg("stopping global audio input supervisor")
|
||||
AudioInputSupervisor.Stop()
|
||||
// Wait for audio input supervisor to fully stop
|
||||
for i := 0; i < 50; i++ { // Wait up to 5 seconds
|
||||
if !AudioInputSupervisor.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
logger.Info().Msg("global audio input supervisor stopped")
|
||||
}
|
||||
|
||||
// Stop audio output supervisor
|
||||
if audioSupervisor != nil && audioSupervisor.IsRunning() {
|
||||
if AudioOutputSupervisor != nil && AudioOutputSupervisor.IsRunning() {
|
||||
logger.Info().Msg("stopping audio output supervisor")
|
||||
audioSupervisor.Stop()
|
||||
AudioOutputSupervisor.Stop()
|
||||
// Wait for audio processes to fully stop
|
||||
for i := 0; i < 50; i++ { // Wait up to 5 seconds
|
||||
if !audioSupervisor.IsRunning() {
|
||||
if !AudioOutputSupervisor.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
logger.Info().Msg("audio output supervisor stopped")
|
||||
}
|
||||
} else if enabled && audioSupervisor != nil {
|
||||
// Ensure supervisor is fully stopped before starting
|
||||
} else if enabled {
|
||||
// Create supervisors if they don't exist (happens when audio was previously disabled)
|
||||
if AudioOutputSupervisor == nil {
|
||||
logger.Info().Msg("creating audio output supervisor for audio device enablement")
|
||||
AudioOutputSupervisor = audio.NewAudioOutputSupervisor()
|
||||
audio.SetAudioOutputSupervisor(AudioOutputSupervisor)
|
||||
}
|
||||
|
||||
// Create audio input supervisor if it doesn't exist
|
||||
if AudioInputSupervisor == nil {
|
||||
logger.Info().Msg("creating audio input supervisor for audio device enablement")
|
||||
AudioInputSupervisor = audio.NewAudioInputSupervisor()
|
||||
audio.SetAudioInputSupervisor(AudioInputSupervisor)
|
||||
|
||||
// Set default OPUS configuration for audio input supervisor
|
||||
audioConfig := audio.Config
|
||||
AudioInputSupervisor.SetOpusConfig(
|
||||
audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
|
||||
audioConfig.AudioQualityLowOpusComplexity,
|
||||
audioConfig.AudioQualityLowOpusVBR,
|
||||
audioConfig.AudioQualityLowOpusSignalType,
|
||||
audioConfig.AudioQualityLowOpusBandwidth,
|
||||
audioConfig.AudioQualityLowOpusDTX,
|
||||
)
|
||||
}
|
||||
|
||||
// Ensure audio output supervisor is fully stopped before starting
|
||||
for i := 0; i < 50; i++ { // Wait up to 5 seconds
|
||||
if !audioSupervisor.IsRunning() {
|
||||
if !AudioOutputSupervisor.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
// Start audio processes when audio is enabled
|
||||
logger.Info().Msg("starting audio processes due to audio device being enabled")
|
||||
if err := audioSupervisor.Start(); err != nil {
|
||||
logger.Error().Err(err).Msg("failed to start audio supervisor")
|
||||
if err := AudioOutputSupervisor.Start(); err != nil {
|
||||
logger.Error().Err(err).Msg("failed to start audio output supervisor")
|
||||
} else {
|
||||
// Broadcast audio device change event to notify WebRTC session
|
||||
broadcaster := audio.GetAudioEventBroadcaster()
|
||||
broadcaster.BroadcastAudioDeviceChanged(true, "device_enabled")
|
||||
logger.Info().Msg("broadcasted audio device change event after enabling audio device")
|
||||
}
|
||||
// Always broadcast the audio device change event regardless of enable/disable
|
||||
broadcaster := audio.GetAudioEventBroadcaster()
|
||||
broadcaster.BroadcastAudioDeviceChanged(enabled, "device_state_changed")
|
||||
logger.Info().Bool("enabled", enabled).Msg("broadcasted audio device state change event")
|
||||
|
||||
// Start audio input supervisor consistently with output
|
||||
if AudioInputSupervisor != nil {
|
||||
logger.Info().Msg("starting audio input supervisor due to audio device being enabled")
|
||||
if err := AudioInputSupervisor.Start(); err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to start audio input supervisor - will be available on-demand")
|
||||
} else {
|
||||
logger.Info().Msg("audio input supervisor started successfully after enabling audio device")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always broadcast the audio device change event regardless of enable/disable
|
||||
broadcaster := audio.GetAudioEventBroadcaster()
|
||||
broadcaster.BroadcastAudioDeviceChanged(enabled, "device_state_changed")
|
||||
logger.Info().Bool("enabled", enabled).Msg("broadcasted audio device state change event")
|
||||
config.UsbDevices.Audio = enabled
|
||||
default:
|
||||
return fmt.Errorf("invalid device: %s", device)
|
||||
|
|
|
|||
34
main.go
34
main.go
|
|
@ -15,30 +15,36 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
appCtx context.Context
|
||||
isAudioServer bool
|
||||
audioProcessDone chan struct{}
|
||||
audioSupervisor *audio.AudioOutputSupervisor
|
||||
appCtx context.Context
|
||||
isAudioServer bool
|
||||
audioProcessDone chan struct{}
|
||||
AudioOutputSupervisor *audio.AudioOutputSupervisor // Exported for jsonrpc access
|
||||
AudioInputSupervisor *audio.AudioInputSupervisor // Exported for jsonrpc access
|
||||
)
|
||||
|
||||
func startAudioSubprocess() error {
|
||||
// Initialize validation cache for optimal performance
|
||||
audio.InitValidationCache()
|
||||
|
||||
// Set up USB audio status callback for the audio package
|
||||
audio.SetUsbAudioEnabledCallback(func() bool {
|
||||
return config.UsbDevices != nil && config.UsbDevices.Audio
|
||||
})
|
||||
|
||||
// Create audio server supervisor
|
||||
audioSupervisor = audio.NewAudioOutputSupervisor()
|
||||
AudioOutputSupervisor = audio.NewAudioOutputSupervisor()
|
||||
|
||||
// Set the global supervisor for access from audio package
|
||||
audio.SetAudioOutputSupervisor(audioSupervisor)
|
||||
audio.SetAudioOutputSupervisor(AudioOutputSupervisor)
|
||||
|
||||
// Create and register audio input supervisor (but don't start it)
|
||||
// Audio input will be started on-demand through the UI
|
||||
audioInputSupervisor := audio.NewAudioInputSupervisor()
|
||||
audio.SetAudioInputSupervisor(audioInputSupervisor)
|
||||
AudioInputSupervisor = audio.NewAudioInputSupervisor()
|
||||
audio.SetAudioInputSupervisor(AudioInputSupervisor)
|
||||
|
||||
// Set default OPUS configuration for audio input supervisor (low quality for single-core RV1106)
|
||||
audioConfig := audio.Config
|
||||
audioInputSupervisor.SetOpusConfig(
|
||||
AudioInputSupervisor.SetOpusConfig(
|
||||
audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
|
||||
audioConfig.AudioQualityLowOpusComplexity,
|
||||
audioConfig.AudioQualityLowOpusVBR,
|
||||
|
|
@ -51,7 +57,7 @@ func startAudioSubprocess() error {
|
|||
// when the user activates microphone input through the UI
|
||||
|
||||
// Set up callbacks for process lifecycle events
|
||||
audioSupervisor.SetCallbacks(
|
||||
AudioOutputSupervisor.SetCallbacks(
|
||||
// onProcessStart
|
||||
func(pid int) {
|
||||
logger.Info().Int("pid", pid).Msg("audio server process started")
|
||||
|
|
@ -107,7 +113,7 @@ func startAudioSubprocess() error {
|
|||
}
|
||||
|
||||
// Start the supervisor
|
||||
if err := audioSupervisor.Start(); err != nil {
|
||||
if err := AudioOutputSupervisor.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start audio supervisor: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +122,7 @@ func startAudioSubprocess() error {
|
|||
defer close(audioProcessDone)
|
||||
|
||||
// Wait for supervisor to stop
|
||||
for audioSupervisor.IsRunning() {
|
||||
for AudioOutputSupervisor.IsRunning() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
|
|
@ -288,9 +294,9 @@ func Main(audioServer bool, audioInputServer bool) {
|
|||
|
||||
// Stop audio subprocess and wait for cleanup
|
||||
if !isAudioServer {
|
||||
if audioSupervisor != nil {
|
||||
if AudioOutputSupervisor != nil {
|
||||
logger.Info().Msg("stopping audio supervisor")
|
||||
audioSupervisor.Stop()
|
||||
AudioOutputSupervisor.Stop()
|
||||
}
|
||||
<-audioProcessDone
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||
|
||||
import { useRTCStore, useSettingsStore } from "@/hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { useAudioEvents, AudioDeviceChangedData } from "@/hooks/useAudioEvents";
|
||||
import { devLog, devInfo, devWarn, devError, devOnly } from "@/utils/debug";
|
||||
import { AUDIO_CONFIG } from "@/config/constants";
|
||||
|
||||
|
|
@ -589,6 +590,31 @@ export function useMicrophone() {
|
|||
};
|
||||
}, []); // No dependencies to prevent re-running
|
||||
|
||||
// Handle audio device changes (USB audio enable/disable)
|
||||
const handleAudioDeviceChanged = useCallback((data: AudioDeviceChangedData) => {
|
||||
// When USB audio is re-enabled and user previously had microphone enabled, restore it
|
||||
if (data.enabled && data.reason === "usb_reconfiguration" && microphoneWasEnabled && !isMicrophoneActive && peerConnection) {
|
||||
devInfo("USB audio re-enabled and microphone was previously enabled - attempting to restore");
|
||||
startMicrophone().then((result) => {
|
||||
if (result.success) {
|
||||
devInfo("Microphone successfully restored after USB audio re-enable");
|
||||
} else {
|
||||
devWarn("Failed to restore microphone after USB audio re-enable:", result.error);
|
||||
}
|
||||
}).catch((error) => {
|
||||
devWarn("Error restoring microphone after USB audio re-enable:", error);
|
||||
});
|
||||
}
|
||||
// When USB audio is disabled, clear the microphone enabled flag if it was set
|
||||
else if (!data.enabled && data.reason === "usb_reconfiguration" && microphoneWasEnabled) {
|
||||
devInfo("USB audio disabled - clearing microphone enabled flag");
|
||||
setMicrophoneWasEnabled(false);
|
||||
}
|
||||
}, [microphoneWasEnabled, isMicrophoneActive, peerConnection, startMicrophone, setMicrophoneWasEnabled]);
|
||||
|
||||
// Subscribe to audio device change events
|
||||
useAudioEvents(handleAudioDeviceChanged);
|
||||
|
||||
return {
|
||||
isMicrophoneActive,
|
||||
isMicrophoneMuted,
|
||||
|
|
|
|||
Loading…
Reference in New Issue