Last active
September 8, 2022 20:56
-
-
Save RupertAvery/b1a5297d99f498e44b534b8a5190d9d3 to your computer and use it in GitHub Desktop.
AudioDeviceManager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace MyAudio | |
{ | |
public class AudioDevice | |
{ | |
public string Name { get; set; } | |
public string Direction { get; set; } | |
public bool IsEnabled { get; set; } | |
public string DeviceId { get; set; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using System.Security.Cryptography; | |
namespace MyAudio | |
{ | |
public class AudioDeviceManager : IDisposable | |
{ | |
private IMMDeviceEnumerator pMMDeviceEnumerator; | |
private NotificationClient _notificationClient; | |
private List<AudioDevice> _audioDevices = new List<AudioDevice>(); | |
public IEnumerable<AudioDevice> AudioDevices => _audioDevices; | |
private Dictionary<string, AudioDevice> _audioDeviceLookup = new Dictionary<string, AudioDevice>(); | |
public Action<AudioDevice> DefaultDeviceChanged { get; set; } | |
public Action<AudioDevice> DeviceStateChanged { get; set; } | |
public AudioDeviceManager() | |
{ | |
Guid CLSID_MMDeviceEnumerator = new Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}"); | |
Type MMDeviceEnumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator, true); | |
pMMDeviceEnumerator = (IMMDeviceEnumerator)Activator.CreateInstance(MMDeviceEnumeratorType); | |
_notificationClient = new NotificationClient(); | |
_notificationClient.DefaultDeviceChanged += (sender, args) => | |
{ | |
if(_audioDeviceLookup.TryGetValue(args.DefaultDeviceId, out var device)) | |
{ | |
DefaultDeviceChanged?.Invoke(device); | |
} | |
}; | |
_notificationClient.DeviceStateChanged += (sender, args) => | |
{ | |
if (_audioDeviceLookup.TryGetValue(args.DeviceId, out var device)) | |
{ | |
DeviceStateChanged?.Invoke(device); | |
} | |
}; | |
RegisterNotificationClient(_notificationClient); | |
} | |
public void RegisterNotificationClient([In][MarshalAs(UnmanagedType.Interface)] IMMNotificationClient client) | |
{ | |
var hr = pMMDeviceEnumerator.RegisterEndpointNotificationCallback(client); | |
if (hr != HRESULT.S_OK) | |
{ | |
throw new Exception("Could not register notification client!"); | |
} | |
} | |
public void UnRegisterEndpointNotificationCallback([In][MarshalAs(UnmanagedType.Interface)] IMMNotificationClient client) | |
{ | |
//DeviceEnum declared below | |
var hr = pMMDeviceEnumerator.UnregisterEndpointNotificationCallback(client); | |
if (hr != HRESULT.S_OK) | |
{ | |
throw new Exception("Could not unregister notification client!"); | |
} | |
} | |
public void GetAudioDevices() | |
{ | |
HRESULT hr = HRESULT.E_FAIL; | |
_audioDevices = new List<AudioDevice>(); | |
if (pMMDeviceEnumerator != null) | |
{ | |
IMMDeviceCollection pDeviceCollection = null; | |
hr = pMMDeviceEnumerator.EnumAudioEndpoints(EDataFlow.eAll, | |
Win32.DEVICE_STATE_ACTIVE | Win32.DEVICE_STATE_UNPLUGGED, out pDeviceCollection); | |
if (hr == HRESULT.S_OK) | |
{ | |
uint nDevices = 0; | |
hr = pDeviceCollection.GetCount(out nDevices); | |
for (uint i = 0; i < nDevices; i++) | |
{ | |
IMMDevice pDevice = null; | |
hr = pDeviceCollection.Item(i, out pDevice); | |
if (hr == HRESULT.S_OK) | |
{ | |
int nState = 0; | |
hr = pDevice.GetState(out nState); | |
var isEnabled = false; | |
if (hr == HRESULT.S_OK) | |
{ | |
if (nState == Win32.DEVICE_STATE_ACTIVE) | |
{ | |
isEnabled = true; | |
} | |
else | |
{ | |
isEnabled = false; | |
} | |
} | |
IntPtr nDeviceId; | |
hr = pDevice.GetId(out nDeviceId); | |
string deviceId = null; | |
if (hr == HRESULT.S_OK) | |
{ | |
deviceId = Marshal.PtrToStringUni(nDeviceId); | |
} | |
IPropertyStore pPropertyStore = null; | |
hr = pDevice.OpenPropertyStore(Win32.STGM_READ, out pPropertyStore); | |
if (hr == HRESULT.S_OK) | |
{ | |
string sFriendlyName = null; | |
string sDesc = null; | |
PROPVARIANT pv = new PROPVARIANT(); | |
hr = pPropertyStore.GetValue(ref Win32.PKEY_Device_FriendlyName, out pv); | |
if (hr == HRESULT.S_OK) | |
{ | |
sFriendlyName = Marshal.PtrToStringUni(pv.pwszVal); | |
} | |
hr = pPropertyStore.GetValue(ref Win32.PKEY_Device_DeviceDesc, out pv); | |
if (hr == HRESULT.S_OK) | |
{ | |
sDesc = Marshal.PtrToStringUni(pv.pwszVal); | |
} | |
hr = pPropertyStore.GetValue(ref Win32.PKEY_Device_DeviceDesc, out pv); | |
if (hr == HRESULT.S_OK) | |
{ | |
sDesc = Marshal.PtrToStringUni(pv.pwszVal); | |
} | |
//Console.WriteLine("Device: {0}", sFriendlyName); | |
//IntPtr hGlobal = Marshal.AllocHGlobal(260); | |
//hr = pDevice.GetId(out hGlobal); | |
//string sId = Marshal.PtrToStringUni(hGlobal); | |
//Marshal.FreeHGlobal(hGlobal); | |
//IntPtr pAudioEndpointVolumePtr = IntPtr.Zero; | |
//PROPVARIANT pvActivate = new PROPVARIANT(); | |
//hr = pDevice.Activate(typeof(IAudioEndpointVolume).GUID, 0, ref pvActivate, out pAudioEndpointVolumePtr); | |
//if (hr == HRESULT.S_OK) | |
//{ | |
// IAudioEndpointVolume pAudioEndpointVolume = Marshal.GetObjectForIUnknown(pAudioEndpointVolumePtr) as IAudioEndpointVolume; | |
// float nVolume = 0; | |
// hr = pAudioEndpointVolume.GetMasterVolumeLevelScalar(out nVolume); | |
// System.Diagnostics.Trace.WriteLine("\tMaster Volume : " + nVolume.ToString()); | |
// if (sFriendlyName == "Mixage stéréo (Realtek High Definition Audio)") | |
// { | |
// hr = pAudioEndpointVolume.SetMasterVolumeLevelScalar(0.5f, Guid.Empty); | |
// hr = pAudioEndpointVolume.GetMasterVolumeLevelScalar(out nVolume); | |
// System.Diagnostics.Trace.WriteLine("\tNew Master Volume : " + nVolume.ToString()); | |
// } | |
// Marshal.ReleaseComObject(pAudioEndpointVolume); | |
//} | |
IMMEndpoint pEndpoint = null; | |
pEndpoint = (IMMEndpoint)pDevice; | |
EDataFlow eDirection = EDataFlow.eAll; | |
hr = pEndpoint.GetDataFlow(out eDirection); | |
//System.Diagnostics.Trace.WriteLine("\tDirection : " + eDirection.ToString()); | |
string sDirection = ""; | |
if (eDirection == EDataFlow.eRender) | |
sDirection = "Playback"; | |
else if (eDirection == EDataFlow.eCapture) | |
sDirection = "Recording"; | |
_audioDevices.Add(new AudioDevice() | |
{ | |
Name = sFriendlyName, | |
Direction = sDirection, | |
IsEnabled = isEnabled, | |
DeviceId = deviceId | |
}); | |
Marshal.ReleaseComObject(pPropertyStore); | |
} | |
Marshal.ReleaseComObject(pDevice); | |
} | |
} | |
_audioDeviceLookup = _audioDevices.ToDictionary(d => d.DeviceId); | |
} | |
Marshal.ReleaseComObject(pDeviceCollection); | |
} | |
} | |
public void Dispose() | |
{ | |
UnRegisterEndpointNotificationCallback(_notificationClient); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.InteropServices; | |
namespace MyAudio | |
{ | |
[ComImport] | |
[Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0")] | |
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
public interface IMMNotificationClient | |
{ | |
void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, int dwNewState); | |
void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId); | |
void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId); | |
void OnDefaultDeviceChanged(EDataFlow flow, ERole role, [MarshalAs(UnmanagedType.LPWStr)] string pwstrDefaultDeviceId); | |
void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, ref PROPERTYKEY key); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.ObjectModel; | |
using System.Data; | |
using System.Linq; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Data; | |
namespace MyAudio | |
{ | |
public partial class MainWindow : Window | |
{ | |
private AudioDeviceManager _audioDeviceManager; | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
_audioDeviceManager = new AudioDeviceManager(); | |
_audioDeviceManager.GetAudioDevices(); | |
_audioDeviceManager.DefaultDeviceChanged = device => | |
{ | |
// Doesn't work yet, you might need to do proper binding | |
this.cmb1.SelectedItem = device; | |
}; | |
ListCollectionView lcv = new ListCollectionView(new ObservableCollection<AudioDevice>(_audioDeviceManager.AudioDevices)); | |
lcv.GroupDescriptions.Add(new PropertyGroupDescription("Direction")); | |
this.cmb1.ItemsSource = lcv; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Data; | |
using System.Runtime.InteropServices; | |
namespace MyAudio | |
{ | |
public class DefaultDeviceChangedEventArgs | |
{ | |
public EDataFlow Flow { get; set; } | |
public ERole Role { get; set; } | |
public string DefaultDeviceId { get; set; } | |
} | |
public class DeviceStateChangedEventArgs | |
{ | |
public int NewState { get; set; } | |
public string DeviceId { get; set; } | |
} | |
public class NotificationClient : IMMNotificationClient | |
{ | |
public EventHandler<DefaultDeviceChangedEventArgs> DefaultDeviceChanged { get; set; } | |
public EventHandler<DeviceStateChangedEventArgs> DeviceStateChanged { get; set; } | |
public Action<string> DeviceAdded { get; set; } | |
public void OnDeviceStateChanged(string pwstrDeviceId, int dwNewState) | |
{ | |
DeviceStateChanged.Invoke(this, new DeviceStateChangedEventArgs() { DeviceId = pwstrDeviceId, NewState = dwNewState }); | |
} | |
public void OnDeviceAdded(string pwstrDeviceId) | |
{ | |
} | |
public void OnDeviceRemoved(string pwstrDeviceId) | |
{ | |
} | |
public void OnDefaultDeviceChanged(EDataFlow flow, ERole role, string pwstrDefaultDeviceId) | |
{ | |
DefaultDeviceChanged.Invoke(this, new DefaultDeviceChangedEventArgs() { Flow = flow, Role = role, DefaultDeviceId = pwstrDefaultDeviceId }); | |
} | |
public void OnPropertyValueChanged(string pwstrDeviceId, ref PROPERTYKEY key) | |
{ | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment