Skip to content

Commit

Permalink
Merge pull request #63 from t3knomanzer/mode-master-volume
Browse files Browse the repository at this point in the history
Mode master volume
  • Loading branch information
t3knomanzer authored Jul 16, 2020
2 parents 84033e2 + dcd9276 commit bf80e61
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 34 deletions.
1 change: 1 addition & 0 deletions Desktop/Application/MaxMix/MaxMix.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
</Compile>
<Compile Include="Services\Audio\AudioCommon.cs" />
<Compile Include="Services\Audio\AudioSessionService.cs" />
<Compile Include="Services\Audio\AudioEndpointVolumeWrapper.cs" />
<Compile Include="Services\Audio\AudioSessionWrapper.cs" />
<Compile Include="Services\Audio\IAudioSessionService.cs" />
<Compile Include="Services\Communication\CobsSerializationService .cs" />
Expand Down
4 changes: 3 additions & 1 deletion Desktop/Application/MaxMix/Services/Audio/AudioCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Provides a facade with a simpler interface over the AudioSessionControl
/// CSCore class.
/// </summary>
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
/// <summary>
/// Raised when the volume of this session has changed.
/// </summary>
public event EventHandler<AudioEndpointVolumeCallbackEventArgs> VolumeChanged;
#endregion

#region Fields
private AudioEndpointVolume _endpointVolume;
private AudioEndpointVolumeCallback _callback;
private bool _isNotifyEnabled;
#endregion

#region Properties
/// <summary>
/// The display name of the process that created this session.
/// </summary>
public string DisplayName
{
get => "Master";
}

/// <summary>
/// Current volume of this session (0-100).
/// </summary>
public int Volume
{
get => (int)(_endpointVolume.MasterVolumeLevelScalar * 100);
set
{
_isNotifyEnabled = false;
_endpointVolume.MasterVolumeLevelScalar = value / 100f;
}
}

/// <summary>
/// Current mute state of this session.
/// </summary>
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
}
}
92 changes: 73 additions & 19 deletions Desktop/Application/MaxMix/Services/Audio/AudioSessionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,21 @@ public AudioSessionService()
#region Fields
private readonly SynchronizationContext _synchronizationContext;
private AudioSessionManager2 _sessionManager;
private AudioEndpointVolumeWrapper _endpointVolumeWrapper;
private IDictionary<int, AudioSessionWrapper> _wrappers;
#endregion

#region Events
/// <summary>
/// Raised when the volume for an active session has changed.
/// </summary>
public event AudioEndpointDelegate EndpointCreated;

/// <summary>
/// Raised when the volume for an active session has changed.
/// </summary>
public event AudioEndpointVolumeDelegate EndpointVolumeChanged;

/// <summary>
/// Raised when a new audio session has been created.
/// </summary>
Expand All @@ -53,7 +64,7 @@ public AudioSessionService()
public void Start()
{
_wrappers = new Dictionary<int, AudioSessionWrapper>();
new Thread(() => InitializeEvents()).Start();
new Thread(() => InitializeWrappers()).Start();
}

/// <summary>
Expand All @@ -67,6 +78,11 @@ public void Stop()
_sessionManager.Dispose();
}

if(_endpointVolumeWrapper != null)
{
_endpointVolumeWrapper.Dispose();
}

if (_wrappers != null)
{
foreach (var wrapper in _wrappers.Values)
Expand All @@ -77,39 +93,53 @@ public void Stop()
}

/// <summary>
/// Sets the volume of an audio session.
/// Sets the volume of the endpoint (master volume).
/// </summary>
/// <param name="pid">The process Id of the target session.</param>
/// <param name="volume">The desired volume.</param>
public void SetVolume(int pid, int volume)
/// <param name="isMuted">The mute state of the endpoint.</param>
public void SetEndpointVolume(int volume, bool isMuted)
{
if (!_wrappers.ContainsKey(pid))
return;

_wrappers[pid].Volume = volume;
_endpointVolumeWrapper.Volume = volume;
_endpointVolumeWrapper.IsMuted = isMuted;
}

/// <summary>
/// Sets the mute state of an audio session.
/// Sets the volume of an audio session.
/// </summary>
/// <param name="pid">The process Id of the target session.</param>
/// <param name="isMuted">The mute state.</param>
public void SetMute(int pid, bool isMuted)
/// <param name="volume">The desired volume.</param>
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<AudioSessionControl2>();
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand Down
12 changes: 8 additions & 4 deletions Desktop/Application/MaxMix/Services/Audio/AudioSessionWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
}
Expand All @@ -154,7 +158,7 @@ public void Dispose()
_session2 = null;
_simpleAudio = null;
_events = null;
}
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> SessionRemoved;
event AudioSessionVolumeDelegate SessionVolumeChanged;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand Down
Loading

0 comments on commit bf80e61

Please sign in to comment.