Skip to content

Commit

Permalink
parallel com execution
Browse files Browse the repository at this point in the history
using marhsalling of pointers in up to 10 COM Threads to achieve all com interop

this is a massive change, that shouldn't break anything
  • Loading branch information
xenolightning committed Sep 26, 2016
1 parent 0305869 commit e939c69
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 145 deletions.
2 changes: 1 addition & 1 deletion AudioSwitcher.AudioApi.CoreAudio.Tests/DeviceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public async Task Device_Set_Default_Communications_Async()
}
}

[Fact]
[Fact(Skip = "The order is not guranteed, so skipping for now")]
public async Task Device_Set_Default_Communications_Async_Returns_In_Order()
{
using (var controller = CreateTestController())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: CollectionBehavior(DisableTestParallelization = true, MaxParallelThreads = 1)]
[assembly: CollectionBehavior(DisableTestParallelization = true)]
54 changes: 32 additions & 22 deletions AudioSwitcher.AudioApi.CoreAudio/CoreAudioController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using AudioSwitcher.AudioApi.CoreAudio.Interfaces;
using AudioSwitcher.AudioApi.CoreAudio.Threading;
Expand All @@ -16,26 +17,33 @@ public sealed class CoreAudioController : AudioController<CoreAudioDevice>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private HashSet<CoreAudioDevice> _deviceCache = new HashSet<CoreAudioDevice>();
private IMultimediaDeviceEnumerator _innerEnumerator;
private volatile IntPtr _innerEnumeratorPtr;
private readonly ThreadLocal<IMultimediaDeviceEnumerator> _innerEnumerator;
private SystemEventNotifcationClient _systemEvents;

private IMultimediaDeviceEnumerator InnerEnumerator => _innerEnumerator.Value;

public CoreAudioController()
{
// ReSharper disable once SuspiciousTypeConversion.Global
var innerEnumerator = ComObjectFactory.GetDeviceEnumerator();
_innerEnumeratorPtr = Marshal.GetIUnknownForObject(innerEnumerator);

if (innerEnumerator == null)
throw new InvalidComObjectException("No Device Enumerator");

_innerEnumerator = new ThreadLocal<IMultimediaDeviceEnumerator>(() => Marshal.GetUniqueObjectForIUnknown(_innerEnumeratorPtr) as IMultimediaDeviceEnumerator, true);

ComThread.Invoke(() =>
{
// ReSharper disable once SuspiciousTypeConversion.Global
_innerEnumerator = ComObjectFactory.GetDeviceEnumerator();
_systemEvents = new SystemEventNotifcationClient(_innerEnumerator);

if (_innerEnumerator == null)
return;
_systemEvents = new SystemEventNotifcationClient(() => InnerEnumerator);

_systemEvents.DeviceAdded.Subscribe(x => OnDeviceAdded(x.DeviceId));
_systemEvents.DeviceRemoved.Subscribe(x => OnDeviceRemoved(x.DeviceId));

_deviceCache = new HashSet<CoreAudioDevice>();
IMultimediaDeviceCollection collection;
_innerEnumerator.EnumAudioEndpoints(EDataFlow.All, EDeviceState.All, out collection);
InnerEnumerator.EnumAudioEndpoints(EDataFlow.All, EDeviceState.All, out collection);

using (var coll = new MultimediaDeviceCollection(collection))
{
Expand Down Expand Up @@ -70,24 +78,26 @@ private void OnDeviceRemoved(string deviceId)

protected override void Dispose(bool disposing)
{
if (_systemEvents != null)
ComThread.BeginInvoke(() =>
{
_systemEvents.Dispose();
_systemEvents?.Dispose();
_systemEvents = null;
}

foreach (var device in _deviceCache)
})
.ContinueWith(x =>
{
device.Dispose();
}
foreach (var device in _deviceCache)
{
device.Dispose();
}

_deviceCache?.Clear();
_lock?.Dispose();
_innerEnumerator = null;
_deviceCache?.Clear();
_lock?.Dispose();
_innerEnumerator?.Dispose();

base.Dispose(disposing);
base.Dispose(disposing);

GC.SuppressFinalize(this);
GC.SuppressFinalize(this);
});
}

public override CoreAudioDevice GetDevice(Guid id, DeviceState state)
Expand Down Expand Up @@ -132,7 +142,7 @@ private CoreAudioDevice GetOrAddDeviceFromRealId(string deviceId)
return ComThread.Invoke(() =>
{
IMultimediaDevice mDevice;
_innerEnumerator.GetDevice(deviceId, out mDevice);
InnerEnumerator.GetDevice(deviceId, out mDevice);

if (mDevice == null)
return null;
Expand Down Expand Up @@ -214,7 +224,7 @@ private static bool DeviceIsValid(IMultimediaDevice device)
internal string GetDefaultDeviceId(DeviceType deviceType, Role role)
{
IMultimediaDevice dev;
_innerEnumerator.GetDefaultAudioEndpoint(deviceType.AsEDataFlow(), role.AsERole(), out dev);
InnerEnumerator.GetDefaultAudioEndpoint(deviceType.AsEDataFlow(), role.AsERole(), out dev);
if (dev == null)
return null;

Expand Down
8 changes: 4 additions & 4 deletions AudioSwitcher.AudioApi.CoreAudio/CoreAudioDevice.Internal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private void GetPropertyInformation(IMultimediaDevice device)
_properties.TryLoadFrom(device);
}

private void LoadAudioMeterInformation(IMultimediaDevice device)
private void LoadAudioMeterInformation(Func<IMultimediaDevice> device)
{
//This should be all on the COM thread to avoid any
//weird lookups on the result COM object not on an STA Thread
Expand All @@ -106,7 +106,7 @@ private void LoadAudioMeterInformation(IMultimediaDevice device)
{
var clsGuid = new Guid(ComInterfaceIds.AUDIO_METER_INFORMATION_IID);
object result;
ex = Marshal.GetExceptionForHR(device.Activate(ref clsGuid, ClassContext.Inproc, IntPtr.Zero, out result));
ex = Marshal.GetExceptionForHR(device().Activate(ref clsGuid, ClassContext.Inproc, IntPtr.Zero, out result));
_audioMeterInformation = result as IAudioMeterInformation;
}
catch (Exception e)
Expand All @@ -118,7 +118,7 @@ private void LoadAudioMeterInformation(IMultimediaDevice device)
ClearAudioMeterInformation();
}

private void LoadAudioEndpointVolume(IMultimediaDevice device)
private void LoadAudioEndpointVolume(Func<IMultimediaDevice> device)
{
//Don't even bother looking up volume for disconnected devices
if (!State.HasFlag(DeviceState.Active))
Expand All @@ -138,7 +138,7 @@ private void LoadAudioEndpointVolume(IMultimediaDevice device)
try
{
var clsGuid = new Guid(ComInterfaceIds.AUDIO_ENDPOINT_VOLUME_IID);
ex = Marshal.GetExceptionForHR(device.Activate(ref clsGuid, ClassContext.Inproc, IntPtr.Zero, out result));
ex = Marshal.GetExceptionForHR(Device.Activate(ref clsGuid, ClassContext.Inproc, IntPtr.Zero, out result));
}
catch (Exception e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private void ClearAudioSession()
_sessionController = null;
}

private void LoadAudioSessionController(IMultimediaDevice device)
private void LoadAudioSessionController(Func<IMultimediaDevice> device)
{
if (_sessionController?.IsValueCreated == true)
return;
Expand All @@ -33,7 +33,7 @@ private void LoadAudioSessionController(IMultimediaDevice device)
{
var clsGuid = new Guid(ComInterfaceIds.AUDIO_SESSION_MANAGER2_IID);
object result;
Marshal.GetExceptionForHR(device.Activate(ref clsGuid, ClassContext.Inproc, IntPtr.Zero, out result));
Marshal.GetExceptionForHR(device().Activate(ref clsGuid, ClassContext.Inproc, IntPtr.Zero, out result));

//This is scoped into the managed object, so disposal is taken care of there.
var audioSessionManager = result as IAudioSessionManager2;
Expand Down
67 changes: 34 additions & 33 deletions AudioSwitcher.AudioApi.CoreAudio/CoreAudioDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public sealed partial class CoreAudioDevice : Device
private readonly AsyncManualResetEvent _defaultCommResetEvent = new AsyncManualResetEvent(false);

private EDataFlow _dataFlow;
private IMultimediaDevice _device;
private ThreadLocal<IMultimediaDevice> _device;
private readonly CoreAudioController _controller;
private Guid? _id;
private double _volume;
Expand All @@ -47,15 +47,18 @@ public sealed partial class CoreAudioDevice : Device
private volatile bool _isUpdatingPeakValue;
private volatile bool _isDefaultDevice;
private volatile bool _isDefaultCommDevice;
private volatile IntPtr _devicePtr;

private IMultimediaDevice Device
{
get
{
//ComThread.Assert();

if (_isDisposed)
throw new ObjectDisposedException("COM Device Disposed");

return _device;
return _device.Value;
}
}

Expand Down Expand Up @@ -169,17 +172,19 @@ internal CoreAudioDevice(IMultimediaDevice device, CoreAudioController controlle
{
ComThread.Assert();

_device = device;
_devicePtr = Marshal.GetIUnknownForObject(device);
_device = new ThreadLocal<IMultimediaDevice>(() => Marshal.GetUniqueObjectForIUnknown(_devicePtr) as IMultimediaDevice, true);

_controller = controller;

if (device == null)
throw new ArgumentNullException(nameof(device));

LoadProperties(device);
LoadProperties();

ReloadAudioMeterInformation(device);
ReloadAudioEndpointVolume(device);
ReloadAudioSessionController(device);
ReloadAudioMeterInformation();
ReloadAudioEndpointVolume();
ReloadAudioSessionController();

controller.SystemEvents.DeviceStateChanged
.When(x => String.Equals(x.DeviceId, RealId, StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -251,15 +256,18 @@ public override bool HasCapability<TCapability>()

public override TCapability GetCapability<TCapability>()
{
if (_sessionController?.Value is TCapability)
return (TCapability)(_sessionController?.Value as IDeviceCapability);
return ComThread.Invoke(() =>
{
if (_sessionController?.Value is TCapability)
return (TCapability)(_sessionController?.Value as IDeviceCapability);

return default(TCapability);
return default(TCapability);
});
}

public override IEnumerable<IDeviceCapability> GetAllCapabilities()
{
yield return _sessionController?.Value;
yield return ComThread.Invoke(() => _sessionController?.Value);
}

public override bool Mute(bool mute)
Expand Down Expand Up @@ -491,20 +499,18 @@ private void OnDefaultChanged(string deviceId, ERole deviceRole)
OnDefaultChanged();
}

private void LoadProperties(IMultimediaDevice device)
private void LoadProperties()
{
ComThread.Assert();

//Load values
Marshal.ThrowExceptionForHR(device.GetId(out _realId));
Marshal.ThrowExceptionForHR(device.GetState(out _state));
Marshal.ThrowExceptionForHR(Device.GetId(out _realId));
Marshal.ThrowExceptionForHR(Device.GetState(out _state));

// ReSharper disable once SuspiciousTypeConversion.Global
var ep = device as IMultimediaEndpoint;
var ep = Device as IMultimediaEndpoint;
if (ep != null)
ep.GetDataFlow(out _dataFlow);

GetPropertyInformation(device);
GetPropertyInformation(Device);

//load the initial default state. Have to query using device id because this device is not cached until after creation
_isDefaultCommDevice = _controller.GetDefaultDeviceId(DeviceType, Role.Communications) == RealId;
Expand All @@ -514,54 +520,49 @@ private void LoadProperties(IMultimediaDevice device)

private void OnPropertyChanged(PropertyKey propertyKey)
{
ComThread.BeginInvoke(() =>
{
LoadProperties(Device);
})
.ContinueWith(x =>
ComThread.BeginInvoke(LoadProperties).ContinueWith(x =>
{
//Ignore the properties we don't care about
if (!PropertykeyToPropertyMap.ContainsKey(propertyKey))
return;

foreach (var propName in PropertykeyToPropertyMap[propertyKey])
OnPropertyChanged(propName);

});
}

private void OnStateChanged(EDeviceState deviceState)
{
_state = deviceState;

ReloadAudioEndpointVolume(Device);
ReloadAudioMeterInformation(Device);
ReloadAudioSessionController(Device);
ReloadAudioEndpointVolume();
ReloadAudioMeterInformation();
ReloadAudioSessionController();

OnStateChanged(deviceState.AsDeviceState());
}

private void ReloadAudioMeterInformation(IMultimediaDevice device)
private void ReloadAudioMeterInformation()
{
ComThread.Invoke(() =>
{
LoadAudioMeterInformation(device);
LoadAudioMeterInformation(() => Device);
});
}

private void ReloadAudioSessionController(IMultimediaDevice device)
private void ReloadAudioSessionController()
{
ComThread.Invoke(() =>
{
LoadAudioSessionController(device);
LoadAudioSessionController(() => Device);
});
}

private void ReloadAudioEndpointVolume(IMultimediaDevice device)
private void ReloadAudioEndpointVolume()
{
ComThread.Invoke(() =>
{
LoadAudioEndpointVolume(device);
LoadAudioEndpointVolume(() => Device);

if (AudioEndpointVolume != null)
AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification;
Expand Down
Loading

0 comments on commit e939c69

Please sign in to comment.