diff --git a/Desktop/Application/MaxMix/MaxMix.csproj b/Desktop/Application/MaxMix/MaxMix.csproj index 25d45f52..ae5aef01 100644 --- a/Desktop/Application/MaxMix/MaxMix.csproj +++ b/Desktop/Application/MaxMix/MaxMix.csproj @@ -111,6 +111,7 @@ + diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs b/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs index 03714645..3d03fc69 100644 --- a/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs +++ b/Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs @@ -6,7 +6,9 @@ namespace MaxMix.Services.Audio { + public delegate void AudioEndpointDelegate(object sender, string displayName, int volume, bool isMuted); + public delegate void AudioEndpointVolumeDelegate(object sender, int volume, bool isMuted); public delegate void AudioSessionDelegate(object sender, int pid, string displayName, int volume, bool isMuted); - public delegate void AudioSessionVolumeDelegate(object sender, int pid, int volume, bool isMuted); public delegate void AudioSessionNameDelegate(object sender, int pid, string displayName); + public delegate void AudioSessionVolumeDelegate(object sender, int pid, int volume, bool isMuted); } diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioEndpointVolumeWrapper.cs b/Desktop/Application/MaxMix/Services/Audio/AudioEndpointVolumeWrapper.cs new file mode 100644 index 00000000..3762112b --- /dev/null +++ b/Desktop/Application/MaxMix/Services/Audio/AudioEndpointVolumeWrapper.cs @@ -0,0 +1,103 @@ +using CSCore.CoreAudioAPI; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaxMix.Services.Audio +{ + /// + /// Provides a facade with a simpler interface over the AudioSessionControl + /// CSCore class. + /// + public class AudioEndpointVolumeWrapper : IDisposable + { + #region Constructor + public AudioEndpointVolumeWrapper(AudioEndpointVolume endpointVolume) + { + _endpointVolume = endpointVolume; + + _callback = new AudioEndpointVolumeCallback(); + _callback.NotifyRecived += OnEndpointNotifyReceived; + _endpointVolume.RegisterControlChangeNotify(_callback); + + _isNotifyEnabled = true; + } + #endregion + + #region Events + /// + /// Raised when the volume of this session has changed. + /// + public event EventHandler VolumeChanged; + #endregion + + #region Fields + private AudioEndpointVolume _endpointVolume; + private AudioEndpointVolumeCallback _callback; + private bool _isNotifyEnabled; + #endregion + + #region Properties + /// + /// The display name of the process that created this session. + /// + public string DisplayName + { + get => "Master"; + } + + /// + /// Current volume of this session (0-100). + /// + public int Volume + { + get => (int)(_endpointVolume.MasterVolumeLevelScalar * 100); + set + { + _isNotifyEnabled = false; + _endpointVolume.MasterVolumeLevelScalar = value / 100f; + } + } + + /// + /// Current mute state of this session. + /// + public bool IsMuted + { + get => _endpointVolume.IsMuted; + set + { + _isNotifyEnabled = false; + _endpointVolume.IsMuted = value; + } + } + #endregion + + #region Event Handlers + private void OnEndpointNotifyReceived(object sender, AudioEndpointVolumeCallbackEventArgs e) + { + if (!_isNotifyEnabled) + { + _isNotifyEnabled = true; + return; + } + + VolumeChanged?.Invoke(this, e); + } + #endregion + + #region IDisposable + public void Dispose() + { + if (_callback != null) + _endpointVolume.UnregisterControlChangeNotify(_callback); + + _endpointVolume = null; + _callback = null; + } + #endregion + } +} diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs b/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs index eb92a30b..ab7b83a3 100644 --- a/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs +++ b/Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs @@ -26,10 +26,21 @@ public AudioSessionService() #region Fields private readonly SynchronizationContext _synchronizationContext; private AudioSessionManager2 _sessionManager; + private AudioEndpointVolumeWrapper _endpointVolumeWrapper; private IDictionary _wrappers; #endregion #region Events + /// + /// Raised when the volume for an active session has changed. + /// + public event AudioEndpointDelegate EndpointCreated; + + /// + /// Raised when the volume for an active session has changed. + /// + public event AudioEndpointVolumeDelegate EndpointVolumeChanged; + /// /// Raised when a new audio session has been created. /// @@ -53,7 +64,7 @@ public AudioSessionService() public void Start() { _wrappers = new Dictionary(); - new Thread(() => InitializeEvents()).Start(); + new Thread(() => InitializeWrappers()).Start(); } /// @@ -67,6 +78,11 @@ public void Stop() _sessionManager.Dispose(); } + if(_endpointVolumeWrapper != null) + { + _endpointVolumeWrapper.Dispose(); + } + if (_wrappers != null) { foreach (var wrapper in _wrappers.Values) @@ -77,39 +93,53 @@ public void Stop() } /// - /// Sets the volume of an audio session. + /// Sets the volume of the endpoint (master volume). /// - /// The process Id of the target session. /// The desired volume. - public void SetVolume(int pid, int volume) + /// The mute state of the endpoint. + public void SetEndpointVolume(int volume, bool isMuted) { - if (!_wrappers.ContainsKey(pid)) - return; - - _wrappers[pid].Volume = volume; + _endpointVolumeWrapper.Volume = volume; + _endpointVolumeWrapper.IsMuted = isMuted; } /// - /// Sets the mute state of an audio session. + /// Sets the volume of an audio session. /// /// The process Id of the target session. - /// The mute state. - public void SetMute(int pid, bool isMuted) + /// The desired volume. + public void SetSessionVolume(int pid, int volume, bool isMuted) { if (!_wrappers.ContainsKey(pid)) return; + _wrappers[pid].Volume = volume; _wrappers[pid].IsMuted = isMuted; } #endregion #region Private Methods - private void InitializeEvents() + private void InitializeWrappers() { - _sessionManager = GetDefaultAudioSessionManager(DataFlow.Render); - _sessionManager.SessionCreated += OnSessionCreated; + AudioEndpointVolume endpointVolume; + GetDefaultEndpointObjects(DataFlow.Render, out _sessionManager, out endpointVolume); - using (var sessionEnumerator = _sessionManager.GetSessionEnumerator()) + InitializeEndpointWrapper(endpointVolume); + InitializeSessionWrappers(_sessionManager); + } + + private void InitializeEndpointWrapper(AudioEndpointVolume endpointVolume) + { + _endpointVolumeWrapper = new AudioEndpointVolumeWrapper(endpointVolume); + _endpointVolumeWrapper.VolumeChanged += OnEndpointVolumeChanged; + RaiseEndpointCreated(_endpointVolumeWrapper.DisplayName, _endpointVolumeWrapper.Volume, _endpointVolumeWrapper.IsMuted); + } + + private void InitializeSessionWrappers(AudioSessionManager2 sessionManager) + { + sessionManager.SessionCreated += OnSessionCreated; + + using (var sessionEnumerator = sessionManager.GetSessionEnumerator()) foreach (var session in sessionEnumerator) { var session2 = session.QueryInterface(); @@ -141,18 +171,26 @@ private void UnregisterSession(AudioSessionWrapper wrapper) wrapper.Dispose(); } - private AudioSessionManager2 GetDefaultAudioSessionManager(DataFlow dataFlow) + private void GetDefaultEndpointObjects(DataFlow dataFlow, out AudioSessionManager2 sessionManager, out AudioEndpointVolume endpointVolume) { - AudioSessionManager2 sessionManager; using (var enumerator = new MMDeviceEnumerator()) + { using (var device = enumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia)) + { sessionManager = AudioSessionManager2.FromMMDevice(device); - - return sessionManager; + endpointVolume = AudioEndpointVolume.FromDevice(device); + } + } } #endregion #region Event Handlers + private void OnEndpointVolumeChanged(object sender, AudioEndpointVolumeCallbackEventArgs e) + { + var wrapper = (AudioEndpointVolumeWrapper)sender; + RaiseEndpointVolumeChanged(wrapper.Volume, wrapper.IsMuted); + } + private void OnSessionCreated(object sender, SessionCreatedEventArgs e) { var session = e.NewSession; @@ -175,6 +213,22 @@ private void OnSimpleVolumeChanged(object sender, AudioSessionSimpleVolumeChange #endregion #region Event Dispatchers + private void RaiseEndpointCreated(string displayName, int volume, bool isMuted) + { + if (SynchronizationContext.Current != _synchronizationContext) + _synchronizationContext.Post(o => EndpointCreated?.Invoke(this, displayName, volume, isMuted), null); + else + EndpointCreated.Invoke(this, displayName, volume, isMuted); + } + + private void RaiseEndpointVolumeChanged(int volume, bool isMuted) + { + if (SynchronizationContext.Current != _synchronizationContext) + _synchronizationContext.Post(o => EndpointVolumeChanged?.Invoke(this, volume, isMuted), null); + else + EndpointVolumeChanged?.Invoke(this, volume, isMuted); + } + private void RaiseSessionCreated(int pid, string displayName, int volume, bool isMuted) { if (SynchronizationContext.Current != _synchronizationContext) diff --git a/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs b/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs index f57ac389..d27a9204 100644 --- a/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs +++ b/Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs @@ -115,14 +115,18 @@ public int Volume public bool IsMuted { get => _simpleAudio.IsMuted; - set => _simpleAudio.IsMuted = value; + set + { + _isNotifyEnabled = false; + _simpleAudio.IsMuted = value; + } } #endregion #region Event Handlers private void OnSimpleVolumeChanged(object sender, AudioSessionSimpleVolumeChangedEventArgs e) { - if(!_isNotifyEnabled) + if (!_isNotifyEnabled) { _isNotifyEnabled = true; return; @@ -133,7 +137,7 @@ private void OnSimpleVolumeChanged(object sender, AudioSessionSimpleVolumeChange private void OnStateChanged(object sender, AudioSessionStateChangedEventArgs e) { - if(e.NewState == AudioSessionState.AudioSessionStateExpired) + if (e.NewState == AudioSessionState.AudioSessionStateExpired) { SessionDisconnected?.Invoke(this, new AudioSessionDisconnectedEventArgs(AudioSessionDisconnectReason.DisconnectReasonSessionDisconnected)); } @@ -154,7 +158,7 @@ public void Dispose() _session2 = null; _simpleAudio = null; _events = null; - } + } #endregion } } diff --git a/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs b/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs index 0116b73f..ccb1f7fd 100644 --- a/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs +++ b/Desktop/Application/MaxMix/Services/Audio/IAudioSessionService.cs @@ -11,9 +11,11 @@ internal interface IAudioSessionService { void Start(); void Stop(); - void SetVolume(int pid, int volume); - void SetMute(int pid, bool isMuted); + void SetEndpointVolume(int volume, bool isMuted); + void SetSessionVolume(int pid, int volume, bool isMuted); + event AudioEndpointDelegate EndpointCreated; + event AudioEndpointVolumeDelegate EndpointVolumeChanged; event AudioSessionDelegate SessionCreated; event EventHandler SessionRemoved; event AudioSessionVolumeDelegate SessionVolumeChanged; diff --git a/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs b/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs index b82b8ba0..a6db3e03 100644 --- a/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs +++ b/Desktop/Application/MaxMix/Services/Communication/CommunicationService.cs @@ -25,7 +25,7 @@ public CommunicationService(ISerializationService serializationService) #region Consts private const int _checkPortInterval = 500; - private const int _timeout = 100; + private const int _timeout = 1000; #endregion #region Fields @@ -95,6 +95,12 @@ public void Send(IMessage message) { var message_ = _serializationService.Serialize(message); _serialPort.Write(message_, 0, message_.Length); + + // TODO: Temporary hack to prevent messages being sent too quickly. + // When the application is first run or the device is first connected, if there are + // multiple audio sessions active, the device does only receive the first message. + // Serial communication on the device side needs to be made more robust. + Thread.Sleep(50); } catch(Exception e) { diff --git a/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs b/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs index ff39475e..193ba4fb 100644 --- a/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs +++ b/Desktop/Application/MaxMix/ViewModels/MainViewModel.cs @@ -36,6 +36,8 @@ public MainViewModel() _serializationService.RegisterType(5); _audioSessionService = new AudioSessionService(); + _audioSessionService.EndpointCreated += OnAudioEndpointCreated; + _audioSessionService.EndpointVolumeChanged += OnAudioEndpointVolumeChanged; _audioSessionService.SessionCreated += OnAudioSessionCreated; _audioSessionService.SessionRemoved += OnAudioSessionRemoved; _audioSessionService.SessionVolumeChanged += OnAudioSessionVolumeChanged; @@ -180,6 +182,18 @@ private void RaiseExitRequested() #endregion #region EventHandlers + private void OnAudioEndpointCreated(object sender, string displayName, int volume, bool isMuted) + { + var message = new MessageAddSession(int.MinValue, displayName, volume, isMuted); + _communicationService.Send(message); + } + + private void OnAudioEndpointVolumeChanged(object sender, int volume, bool isMuted) + { + var message = new MessageUpdateVolumeSession(int.MinValue, volume, isMuted); + _communicationService.Send(message); + } + private void OnAudioSessionCreated(object sender, int pid, string displayName, int volume, bool isMuted) { var message = new MessageAddSession(pid, displayName, volume, isMuted); @@ -217,8 +231,11 @@ private void OnMessageReceived(object sender, IMessage message) if (message.GetType() == typeof(MessageUpdateVolumeSession)) { var message_ = message as MessageUpdateVolumeSession; - _audioSessionService.SetVolume(message_.Id, message_.Volume); - _audioSessionService.SetMute(message_.Id, message_.IsMuted); + + if(message_.Id == int.MinValue) + _audioSessionService.SetEndpointVolume(message_.Volume, message_.IsMuted); + else + _audioSessionService.SetSessionVolume(message_.Id, message_.Volume, message_.IsMuted); } } diff --git a/Embedded/MaxMix/MaxMix.ino b/Embedded/MaxMix/MaxMix.ino index 0a646a6c..52234c14 100644 --- a/Embedded/MaxMix/MaxMix.ino +++ b/Embedded/MaxMix/MaxMix.ino @@ -162,9 +162,7 @@ void loop() //--------------------------------------------------------- void ClearReceive() { - receiveIndex = 0; - ClearBuffer(receiveBuffer, RECEIVE_BUFFER_SIZE); - ClearBuffer(decodeBuffer, RECEIVE_BUFFER_SIZE); + receiveIndex = 0; } //--------------------------------------------------------- @@ -172,8 +170,6 @@ void ClearReceive() void ClearSend() { sendIndex = 0; - ClearBuffer(sendBuffer, SEND_BUFFER_SIZE); - ClearBuffer(encodeBuffer, SEND_BUFFER_SIZE); } //---------------------------------------------------------