Compare commits

..

2 Commits

5 changed files with 49 additions and 166 deletions

View File

@ -51,11 +51,6 @@ func (ais *AudioInputSupervisor) SetOpusConfig(bitrate, complexity, vbr, signalT
// Start begins supervising the audio input server process // Start begins supervising the audio input server process
func (ais *AudioInputSupervisor) Start() error { 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) { if !atomic.CompareAndSwapInt32(&ais.running, 0, 1) {
return fmt.Errorf("audio input supervisor is already running") return fmt.Errorf("audio input supervisor is already running")
} }

View File

@ -10,7 +10,6 @@ import (
var ( var (
globalOutputSupervisor unsafe.Pointer // *AudioOutputSupervisor globalOutputSupervisor unsafe.Pointer // *AudioOutputSupervisor
globalInputSupervisor unsafe.Pointer // *AudioInputSupervisor globalInputSupervisor unsafe.Pointer // *AudioInputSupervisor
usbAudioEnabledFunc func() bool // Callback to check USB audio status
) )
// isAudioServerProcess detects if we're running as the audio server subprocess // isAudioServerProcess detects if we're running as the audio server subprocess
@ -85,16 +84,3 @@ func GetAudioInputSupervisor() *AudioInputSupervisor {
} }
return (*AudioInputSupervisor)(ptr) 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()
}

View File

@ -914,6 +914,11 @@ func validateAudioConfiguration(enabled bool) error {
return nil // Disabling audio is always allowed 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 // Check if ALSA devices are available by attempting to list them
// This is a basic check to ensure the system has audio capabilities // This is a basic check to ensure the system has audio capabilities
if _, err := os.Stat("/proc/asound/cards"); os.IsNotExist(err) { if _, err := os.Stat("/proc/asound/cards"); os.IsNotExist(err) {
@ -960,12 +965,13 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
} }
// Stop global audio input supervisor if active // Stop global audio input supervisor if active
if AudioInputSupervisor != nil && AudioInputSupervisor.IsRunning() { audioInputSupervisor := audio.GetAudioInputSupervisor()
if audioInputSupervisor != nil && audioInputSupervisor.IsRunning() {
logger.Info().Msg("stopping global audio input supervisor") logger.Info().Msg("stopping global audio input supervisor")
AudioInputSupervisor.Stop() audioInputSupervisor.Stop()
// Wait for audio input supervisor to fully stop // Wait for audio input supervisor to fully stop
for i := 0; i < 50; i++ { // Wait up to 5 seconds for i := 0; i < 50; i++ { // Wait up to 5 seconds
if !AudioInputSupervisor.IsRunning() { if !audioInputSupervisor.IsRunning() {
break break
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -974,12 +980,12 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
} }
// Stop audio output supervisor // Stop audio output supervisor
if AudioOutputSupervisor != nil && AudioOutputSupervisor.IsRunning() { if audioSupervisor != nil && audioSupervisor.IsRunning() {
logger.Info().Msg("stopping audio output supervisor") logger.Info().Msg("stopping audio output supervisor")
AudioOutputSupervisor.Stop() audioSupervisor.Stop()
// Wait for audio processes to fully stop before proceeding // Wait for audio processes to fully stop before proceeding
for i := 0; i < 50; i++ { // Wait up to 5 seconds for i := 0; i < 50; i++ { // Wait up to 5 seconds
if !AudioOutputSupervisor.IsRunning() { if !audioSupervisor.IsRunning() {
break break
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -988,8 +994,8 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
} }
logger.Info().Msg("audio processes stopped, proceeding with USB gadget reconfiguration") logger.Info().Msg("audio processes stopped, proceeding with USB gadget reconfiguration")
} else if newAudioEnabled { } else if newAudioEnabled && audioSupervisor != nil && !audioSupervisor.IsRunning() {
// Audio being enabled - supervisors will be created/started after USB reconfiguration // Start audio processes when audio is enabled (after USB reconfiguration)
logger.Info().Msg("audio will be started after USB gadget reconfiguration") logger.Info().Msg("audio will be started after USB gadget reconfiguration")
} }
} }
@ -1004,42 +1010,17 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
} }
// Start audio processes after successful USB reconfiguration if needed // Start audio processes after successful USB reconfiguration if needed
if previousAudioEnabled != newAudioEnabled && newAudioEnabled { if previousAudioEnabled != newAudioEnabled && newAudioEnabled && audioSupervisor != nil {
// Create supervisors if they don't exist (happens when audio was previously disabled) // Ensure supervisor is fully stopped before starting
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 for i := 0; i < 50; i++ { // Wait up to 5 seconds
if !AudioOutputSupervisor.IsRunning() { if !audioSupervisor.IsRunning() {
break break
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
logger.Info().Msg("starting audio processes after USB gadget reconfiguration") logger.Info().Msg("starting audio processes after USB gadget reconfiguration")
if err := AudioOutputSupervisor.Start(); err != nil { if err := audioSupervisor.Start(); err != nil {
logger.Error().Err(err).Msg("failed to start audio output supervisor") logger.Error().Err(err).Msg("failed to start audio supervisor")
// Don't return error here as USB reconfiguration was successful // Don't return error here as USB reconfiguration was successful
} else { } else {
// Broadcast audio device change event to notify WebRTC session // Broadcast audio device change event to notify WebRTC session
@ -1047,18 +1028,6 @@ func rpcSetUsbDevices(usbDevices usbgadget.Devices) error {
broadcaster.BroadcastAudioDeviceChanged(true, "usb_reconfiguration") broadcaster.BroadcastAudioDeviceChanged(true, "usb_reconfiguration")
logger.Info().Msg("broadcasted audio device change event after 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 { } else if previousAudioEnabled != newAudioEnabled {
// Broadcast audio device change event for disabling audio // Broadcast audio device change event for disabling audio
broadcaster := audio.GetAudioEventBroadcaster() broadcaster := audio.GetAudioEventBroadcaster()
@ -1105,12 +1074,13 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
} }
// Stop global audio input supervisor if active // Stop global audio input supervisor if active
if AudioInputSupervisor != nil && AudioInputSupervisor.IsRunning() { audioInputSupervisor := audio.GetAudioInputSupervisor()
if audioInputSupervisor != nil && audioInputSupervisor.IsRunning() {
logger.Info().Msg("stopping global audio input supervisor") logger.Info().Msg("stopping global audio input supervisor")
AudioInputSupervisor.Stop() audioInputSupervisor.Stop()
// Wait for audio input supervisor to fully stop // Wait for audio input supervisor to fully stop
for i := 0; i < 50; i++ { // Wait up to 5 seconds for i := 0; i < 50; i++ { // Wait up to 5 seconds
if !AudioInputSupervisor.IsRunning() { if !audioInputSupervisor.IsRunning() {
break break
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -1119,77 +1089,41 @@ func rpcSetUsbDeviceState(device string, enabled bool) error {
} }
// Stop audio output supervisor // Stop audio output supervisor
if AudioOutputSupervisor != nil && AudioOutputSupervisor.IsRunning() { if audioSupervisor != nil && audioSupervisor.IsRunning() {
logger.Info().Msg("stopping audio output supervisor") logger.Info().Msg("stopping audio output supervisor")
AudioOutputSupervisor.Stop() audioSupervisor.Stop()
// Wait for audio processes to fully stop // Wait for audio processes to fully stop
for i := 0; i < 50; i++ { // Wait up to 5 seconds for i := 0; i < 50; i++ { // Wait up to 5 seconds
if !AudioOutputSupervisor.IsRunning() { if !audioSupervisor.IsRunning() {
break break
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
logger.Info().Msg("audio output supervisor stopped") logger.Info().Msg("audio output supervisor stopped")
} }
} else if enabled { } else if enabled && audioSupervisor != nil {
// Create supervisors if they don't exist (happens when audio was previously disabled) // Ensure supervisor is fully stopped before starting
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 for i := 0; i < 50; i++ { // Wait up to 5 seconds
if !AudioOutputSupervisor.IsRunning() { if !audioSupervisor.IsRunning() {
break break
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
// Start audio processes when audio is enabled // Start audio processes when audio is enabled
logger.Info().Msg("starting audio processes due to audio device being enabled") logger.Info().Msg("starting audio processes due to audio device being enabled")
if err := AudioOutputSupervisor.Start(); err != nil { if err := audioSupervisor.Start(); err != nil {
logger.Error().Err(err).Msg("failed to start audio output supervisor") logger.Error().Err(err).Msg("failed to start audio supervisor")
} else { } else {
// Broadcast audio device change event to notify WebRTC session // Broadcast audio device change event to notify WebRTC session
broadcaster := audio.GetAudioEventBroadcaster() broadcaster := audio.GetAudioEventBroadcaster()
broadcaster.BroadcastAudioDeviceChanged(true, "device_enabled") broadcaster.BroadcastAudioDeviceChanged(true, "device_enabled")
logger.Info().Msg("broadcasted audio device change event after enabling audio device") logger.Info().Msg("broadcasted audio device change event after enabling audio device")
} }
// Always broadcast the audio device change event regardless of enable/disable
// Start audio input supervisor consistently with output broadcaster := audio.GetAudioEventBroadcaster()
if AudioInputSupervisor != nil { broadcaster.BroadcastAudioDeviceChanged(enabled, "device_state_changed")
logger.Info().Msg("starting audio input supervisor due to audio device being enabled") logger.Info().Bool("enabled", enabled).Msg("broadcasted audio device state change event")
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 config.UsbDevices.Audio = enabled
default: default:
return fmt.Errorf("invalid device: %s", device) return fmt.Errorf("invalid device: %s", device)

34
main.go
View File

@ -15,36 +15,30 @@ import (
) )
var ( var (
appCtx context.Context appCtx context.Context
isAudioServer bool isAudioServer bool
audioProcessDone chan struct{} audioProcessDone chan struct{}
AudioOutputSupervisor *audio.AudioOutputSupervisor // Exported for jsonrpc access audioSupervisor *audio.AudioOutputSupervisor
AudioInputSupervisor *audio.AudioInputSupervisor // Exported for jsonrpc access
) )
func startAudioSubprocess() error { func startAudioSubprocess() error {
// Initialize validation cache for optimal performance // Initialize validation cache for optimal performance
audio.InitValidationCache() 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 // Create audio server supervisor
AudioOutputSupervisor = audio.NewAudioOutputSupervisor() audioSupervisor = audio.NewAudioOutputSupervisor()
// Set the global supervisor for access from audio package // Set the global supervisor for access from audio package
audio.SetAudioOutputSupervisor(AudioOutputSupervisor) audio.SetAudioOutputSupervisor(audioSupervisor)
// Create and register audio input supervisor (but don't start it) // Create and register audio input supervisor (but don't start it)
// Audio input will be started on-demand through the UI // Audio input will be started on-demand through the UI
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 default OPUS configuration for audio input supervisor (low quality for single-core RV1106)
audioConfig := audio.Config audioConfig := audio.Config
AudioInputSupervisor.SetOpusConfig( audioInputSupervisor.SetOpusConfig(
audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps audioConfig.AudioQualityLowInputBitrate*1000, // Convert kbps to bps
audioConfig.AudioQualityLowOpusComplexity, audioConfig.AudioQualityLowOpusComplexity,
audioConfig.AudioQualityLowOpusVBR, audioConfig.AudioQualityLowOpusVBR,
@ -57,7 +51,7 @@ func startAudioSubprocess() error {
// when the user activates microphone input through the UI // when the user activates microphone input through the UI
// Set up callbacks for process lifecycle events // Set up callbacks for process lifecycle events
AudioOutputSupervisor.SetCallbacks( audioSupervisor.SetCallbacks(
// onProcessStart // onProcessStart
func(pid int) { func(pid int) {
logger.Info().Int("pid", pid).Msg("audio server process started") logger.Info().Int("pid", pid).Msg("audio server process started")
@ -113,7 +107,7 @@ func startAudioSubprocess() error {
} }
// Start the supervisor // Start the supervisor
if err := AudioOutputSupervisor.Start(); err != nil { if err := audioSupervisor.Start(); err != nil {
return fmt.Errorf("failed to start audio supervisor: %w", err) return fmt.Errorf("failed to start audio supervisor: %w", err)
} }
@ -122,7 +116,7 @@ func startAudioSubprocess() error {
defer close(audioProcessDone) defer close(audioProcessDone)
// Wait for supervisor to stop // Wait for supervisor to stop
for AudioOutputSupervisor.IsRunning() { for audioSupervisor.IsRunning() {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
@ -294,9 +288,9 @@ func Main(audioServer bool, audioInputServer bool) {
// Stop audio subprocess and wait for cleanup // Stop audio subprocess and wait for cleanup
if !isAudioServer { if !isAudioServer {
if AudioOutputSupervisor != nil { if audioSupervisor != nil {
logger.Info().Msg("stopping audio supervisor") logger.Info().Msg("stopping audio supervisor")
AudioOutputSupervisor.Stop() audioSupervisor.Stop()
} }
<-audioProcessDone <-audioProcessDone
} else { } else {

View File

@ -2,7 +2,6 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { useRTCStore, useSettingsStore } from "@/hooks/stores"; import { useRTCStore, useSettingsStore } from "@/hooks/stores";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc"; import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { useAudioEvents, AudioDeviceChangedData } from "@/hooks/useAudioEvents";
import { devLog, devInfo, devWarn, devError, devOnly } from "@/utils/debug"; import { devLog, devInfo, devWarn, devError, devOnly } from "@/utils/debug";
import { AUDIO_CONFIG } from "@/config/constants"; import { AUDIO_CONFIG } from "@/config/constants";
@ -590,31 +589,6 @@ export function useMicrophone() {
}; };
}, []); // No dependencies to prevent re-running }, []); // 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 { return {
isMicrophoneActive, isMicrophoneActive,
isMicrophoneMuted, isMicrophoneMuted,