using System; using System.Runtime.InteropServices; using DS4Windows.DS4Library.CoreAudio; namespace DS4Windows.DS4Library { public class DS4Audio : IAudioEndpointVolumeCallback { private IAudioEndpointVolume endpointVolume; private static Guid IID_IAudioEndpointVolume = new Guid("5CDF2C82-841E-4546-9722-0CF74078229A"); private static readonly PropertyKey PKEY_Device_FriendlyName = new PropertyKey(new Guid(unchecked((int)0xa45c254e), unchecked((short)0xdf1c), 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), 14); public uint vol; public uint Volume { get { return vol; } } public uint getVolume() { return vol; } private DataFlow instAudioFlags = DataFlow.Render; public void RefreshVolume() { const float HALFPI = (float)Math.PI / 2.0f; float pfLevel = 0; if (endpointVolume != null) endpointVolume.GetMasterVolumeLevelScalar(out pfLevel); if (instAudioFlags == DataFlow.Render) // Use QuadraticEaseOut curve for headphone volume level //vol = pfLevel != 0.0 ? Convert.ToUInt32((80 - 30) * -(pfLevel * (pfLevel - 2.0)) + 30) : 0; // Use SineEaseOut curve for headphone volume level vol = pfLevel != 0.0 ? Convert.ToUInt32((80 - 30) * Math.Sin(pfLevel * HALFPI) + 30) : 0; // Use CubicEaseOut curve for headphone volume level //vol = pfLevel != 0.0 ? Convert.ToUInt32((80 - 30) * (--pfLevel * pfLevel * pfLevel + 1) + 30) : 0; // Use Linear curve for headphone volume level //vol = pfLevel != 0.0 ? Convert.ToUInt32((80 - 30) * pfLevel + 30) : 0; else if (instAudioFlags == DataFlow.Capture) vol = Convert.ToUInt32((60 - 0) * pfLevel + 0); } public void OnNotify(IntPtr pNotify) { RefreshVolume(); } public DS4Audio(DataFlow audioFlags = DataFlow.Render, string searchName = "Wireless Controller") { var audioEnumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator; IMMDeviceCollection audioDevices; audioEnumerator.EnumAudioEndpoints(audioFlags, DeviceState.Active, out audioDevices); int numAudioDevices; Marshal.ThrowExceptionForHR(audioDevices.GetCount(out numAudioDevices)); for (int deviceNumber = 0; deviceNumber < numAudioDevices; ++deviceNumber) { IMMDevice audioDevice; Marshal.ThrowExceptionForHR(audioDevices.Item(deviceNumber, out audioDevice)); string deviceName = GetAudioDeviceName(ref audioDevice); if (deviceName.Contains(searchName)) { object interfacePointer; Marshal.ThrowExceptionForHR(audioDevice.Activate(ref IID_IAudioEndpointVolume, ClsCtx.ALL, IntPtr.Zero, out interfacePointer)); instAudioFlags = audioFlags; endpointVolume = interfacePointer as IAudioEndpointVolume; endpointVolume.RegisterControlChangeNotify(this); } RefreshVolume(); Marshal.ReleaseComObject(audioDevice); } Marshal.ReleaseComObject(audioDevices); Marshal.ReleaseComObject(audioEnumerator); } ~DS4Audio() { if (endpointVolume != null) { endpointVolume.UnregisterControlChangeNotify(this); Marshal.ReleaseComObject(endpointVolume); endpointVolume = null; } } private string GetAudioDeviceName(ref IMMDevice audioDevice) { IPropertyStore propertyStore; Marshal.ThrowExceptionForHR(audioDevice.OpenPropertyStore(StorageAccessMode.Read, out propertyStore)); int numProperties; Marshal.ThrowExceptionForHR(propertyStore.GetCount(out numProperties)); string deviceName = String.Empty; for (int propertyNum = 0; propertyNum < numProperties; ++propertyNum) { PropertyKey propertyKey; Marshal.ThrowExceptionForHR(propertyStore.GetAt(propertyNum, out propertyKey)); if ((propertyKey.formatId == PKEY_Device_FriendlyName.formatId) && (propertyKey.propertyId == PKEY_Device_FriendlyName.propertyId)) { PropVariant propertyValue; Marshal.ThrowExceptionForHR(propertyStore.GetValue(ref propertyKey, out propertyValue)); deviceName = Marshal.PtrToStringUni(propertyValue.pointerValue); break; } } Marshal.ReleaseComObject(propertyStore); return deviceName; } } } namespace DS4Windows.DS4Library.CoreAudio { public enum DataFlow { Render, Capture, All }; [Flags] public enum DeviceState { Active = 0x00000001, Disabled = 0x00000002, NotPresent = 0x00000004, Unplugged = 0x00000008, All = 0x0000000F } enum StorageAccessMode { Read, Write, ReadWrite } [Flags] public enum ClsCtx { INPROC_SERVER = 0x1, INPROC_HANDLER = 0x2, LOCAL_SERVER = 0x4, INPROC_SERVER16 = 0x8, REMOTE_SERVER = 0x10, INPROC_HANDLER16 = 0x20, NO_CODE_DOWNLOAD = 0x400, NO_CUSTOM_MARSHAL = 0x1000, ENABLE_CODE_DOWNLOAD = 0x2000, NO_FAILURE_LOG = 0x4000, DISABLE_AAA = 0x8000, ENABLE_AAA = 0x10000, FROM_DEFAULT_CONTEXT = 0x20000, ACTIVATE_32_BIT_SERVER = 0x40000, ACTIVATE_64_BIT_SERVER = 0x80000, ENABLE_CLOAKING = 0x100000, PS_DLL = unchecked((int)0x80000000), INPROC = INPROC_SERVER | INPROC_HANDLER, SERVER = INPROC_SERVER | LOCAL_SERVER | REMOTE_SERVER, ALL = SERVER | INPROC_HANDLER } public struct PropertyKey { public Guid formatId; public int propertyId; public PropertyKey(Guid formatId, int propertyId) { this.formatId = formatId; this.propertyId = propertyId; } } [StructLayout(LayoutKind.Explicit)] public struct PropVariant { [FieldOffset(0)] private short vt; [FieldOffset(2)] private short wReserved1; [FieldOffset(4)] private short wReserved2; [FieldOffset(6)] private short wReserved3; [FieldOffset(8)] public IntPtr pointerValue; } [Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IPropertyStore { int GetCount(out int propCount); int GetAt(int property, out PropertyKey key); int GetValue(ref PropertyKey key, out PropVariant value); int SetValue(ref PropertyKey key, ref PropVariant value); int Commit(); } [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDevice { int Activate(ref Guid id, ClsCtx clsCtx, IntPtr activationParams, [MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer); int OpenPropertyStore(StorageAccessMode stgmAccess, out IPropertyStore properties); int GetId([MarshalAs(UnmanagedType.LPWStr)] out string id); } [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDeviceCollection { int GetCount(out int numDevices); int Item(int deviceNumber, out IMMDevice device); } [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDeviceEnumerator { int EnumAudioEndpoints(DataFlow dataFlow, DeviceState stateMask, out IMMDeviceCollection devices); } [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { } [Guid("657804FA-D6AD-4496-8A60-352752AF4F89"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IAudioEndpointVolumeCallback { void OnNotify(IntPtr notifyData); }; [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IAudioEndpointVolume { int RegisterControlChangeNotify(IAudioEndpointVolumeCallback pNotify); int UnregisterControlChangeNotify(IAudioEndpointVolumeCallback pNotify); int GetChannelCount(out int pnChannelCount); int SetMasterVolumeLevel(float fLevelDB, ref Guid pguidEventContext); int SetMasterVolumeLevelScalar(float fLevel, ref Guid pguidEventContext); int GetMasterVolumeLevel(out float pfLevelDB); int GetMasterVolumeLevelScalar(out float pfLevel); int SetChannelVolumeLevel(uint nChannel, float fLevelDB, ref Guid pguidEventContext); int SetChannelVolumeLevelScalar(uint nChannel, float fLevel, ref Guid pguidEventContext); int GetChannelVolumeLevel(uint nChannel, out float pfLevelDB); int GetChannelVolumeLevelScalar(uint nChannel, out float pfLevel); int SetMute([MarshalAs(UnmanagedType.Bool)] Boolean bMute, ref Guid pguidEventContext); int GetMute(out bool pbMute); int GetVolumeStepInfo(out uint pnStep, out uint pnStepCount); int VolumeStepUp(ref Guid pguidEventContext); int VolumeStepDown(ref Guid pguidEventContext); int QueryHardwareSupport(out uint pdwHardwareSupportMask); int GetVolumeRange(out float pflVolumeMindB, out float pflVolumeMaxdB, out float pflVolumeIncrementdB); } }