mirror of https://github.com/jetkvm/kvm.git
Fixes, Improvements: Fixed stale audio subprocesses when disabling audio from Hardware settings and ensure more consistency between the way in which audio subprocesses are handled when USB Gadget settings are changed
This commit is contained in:
parent
a84f63c0c4
commit
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