using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace PowerControl.Helpers { /// /// Controls audio using the Windows CoreAudio API /// from: http://stackoverflow.com/questions/14306048/controling-volume-mixer /// and: http://netcoreaudio.codeplex.com/ /// public static class AudioManager { public static int GetMasterVolume(double roundValue) { return (int)(Math.Round(GetMasterVolume() / roundValue) * roundValue); } #region Master Volume Manipulation /// /// Gets the current master volume in scalar values (percentage) /// /// -1 in case of an error, if successful the value will be between 0 and 100 public static int GetMasterVolume() { IAudioEndpointVolume masterVol = null; try { masterVol = GetMasterVolumeObject(); if (masterVol != null) { float volumeLevel; masterVol.GetMasterVolumeLevelScalar(out volumeLevel); return (int)(volumeLevel * 100); } return -1; } finally { if (masterVol != null) Marshal.ReleaseComObject(masterVol); } } /// /// Gets the mute state of the master volume. /// While the volume can be muted the will still return the pre-muted volume value. /// /// false if not muted, true if volume is muted public static bool GetMasterVolumeMute() { IAudioEndpointVolume masterVol = null; try { masterVol = GetMasterVolumeObject(); if (masterVol == null) return false; bool isMuted; masterVol.GetMute(out isMuted); return isMuted; } finally { if (masterVol != null) Marshal.ReleaseComObject(masterVol); } } /// /// Sets the master volume to a specific level /// /// Value between 0 and 100 indicating the desired scalar value of the volume public static void SetMasterVolume(int newLevel) { IAudioEndpointVolume masterVol = null; try { masterVol = GetMasterVolumeObject(); if (masterVol == null) return; masterVol.SetMasterVolumeLevelScalar((float)newLevel / 100.0f, Guid.Empty); } finally { if (masterVol != null) Marshal.ReleaseComObject(masterVol); } } /// /// Increments or decrements the current volume level by the . /// /// Value between -100 and 100 indicating the desired step amount. Use negative numbers to decrease /// the volume and positive numbers to increase it. /// the new volume level assigned public static float StepMasterVolume(float stepAmount) { IAudioEndpointVolume masterVol = null; try { masterVol = GetMasterVolumeObject(); if (masterVol == null) return -1; float stepAmountScaled = stepAmount / 100; // Get the level float volumeLevel; masterVol.GetMasterVolumeLevelScalar(out volumeLevel); // Calculate the new level float newLevel = volumeLevel + stepAmountScaled; newLevel = Math.Min(1, newLevel); newLevel = Math.Max(0, newLevel); masterVol.SetMasterVolumeLevelScalar(newLevel, Guid.Empty); // Return the new volume level that was set return newLevel * 100; } finally { if (masterVol != null) Marshal.ReleaseComObject(masterVol); } } /// /// Mute or unmute the master volume /// /// true to mute the master volume, false to unmute public static void SetMasterVolumeMute(bool isMuted) { IAudioEndpointVolume masterVol = null; try { masterVol = GetMasterVolumeObject(); if (masterVol == null) return; masterVol.SetMute(isMuted, Guid.Empty); } finally { if (masterVol != null) Marshal.ReleaseComObject(masterVol); } } /// /// Switches between the master volume mute states depending on the current state /// /// the current mute state, true if the volume was muted, false if unmuted public static bool ToggleMasterVolumeMute() { IAudioEndpointVolume masterVol = null; try { masterVol = GetMasterVolumeObject(); if (masterVol == null) return false; bool isMuted; masterVol.GetMute(out isMuted); masterVol.SetMute(!isMuted, Guid.Empty); return !isMuted; } finally { if (masterVol != null) Marshal.ReleaseComObject(masterVol); } } private static IAudioEndpointVolume GetMasterVolumeObject() { IMMDeviceEnumerator deviceEnumerator = null; IMMDevice speakers = null; try { deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers); Guid IID_IAudioEndpointVolume = typeof(IAudioEndpointVolume).GUID; object o; speakers.Activate(ref IID_IAudioEndpointVolume, 0, IntPtr.Zero, out o); IAudioEndpointVolume masterVol = (IAudioEndpointVolume)o; return masterVol; } finally { if (speakers != null) Marshal.ReleaseComObject(speakers); if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator); } } #endregion #region Individual Application Volume Manipulation public static float? GetApplicationVolume(int pid) { ISimpleAudioVolume volume = GetVolumeObject(pid); if (volume == null) return null; float level; volume.GetMasterVolume(out level); Marshal.ReleaseComObject(volume); return level * 100; } public static bool? GetApplicationMute(int pid) { ISimpleAudioVolume volume = GetVolumeObject(pid); if (volume == null) return null; bool mute; volume.GetMute(out mute); Marshal.ReleaseComObject(volume); return mute; } public static void SetApplicationVolume(int pid, float level) { ISimpleAudioVolume volume = GetVolumeObject(pid); if (volume == null) return; Guid guid = Guid.Empty; volume.SetMasterVolume(level / 100, ref guid); Marshal.ReleaseComObject(volume); } public static void SetApplicationMute(int pid, bool mute) { ISimpleAudioVolume volume = GetVolumeObject(pid); if (volume == null) return; Guid guid = Guid.Empty; volume.SetMute(mute, ref guid); Marshal.ReleaseComObject(volume); } private static ISimpleAudioVolume GetVolumeObject(int pid) { IMMDeviceEnumerator deviceEnumerator = null; IAudioSessionEnumerator sessionEnumerator = null; IAudioSessionManager2 mgr = null; IMMDevice speakers = null; try { // get the speakers (1st render + multimedia) device deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers); // activate the session manager. we need the enumerator Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID; object o; speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out o); mgr = (IAudioSessionManager2)o; // enumerate sessions for on this device mgr.GetSessionEnumerator(out sessionEnumerator); int count; sessionEnumerator.GetCount(out count); // search for an audio session with the required process-id ISimpleAudioVolume volumeControl = null; for (int i = 0; i < count; ++i) { IAudioSessionControl2 ctl = null; try { sessionEnumerator.GetSession(i, out ctl); // NOTE: we could also use the app name from ctl.GetDisplayName() int cpid; ctl.GetProcessId(out cpid); if (cpid == pid) { volumeControl = ctl as ISimpleAudioVolume; break; } } finally { if (ctl != null) Marshal.ReleaseComObject(ctl); } } return volumeControl; } finally { if (sessionEnumerator != null) Marshal.ReleaseComObject(sessionEnumerator); if (mgr != null) Marshal.ReleaseComObject(mgr); if (speakers != null) Marshal.ReleaseComObject(speakers); if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator); } } #endregion } #region Abstracted COM interfaces from Windows CoreAudio API [ComImport] [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] internal class MMDeviceEnumerator { } internal enum EDataFlow { eRender, eCapture, eAll, EDataFlow_enum_count } internal enum ERole { eConsole, eMultimedia, eCommunications, ERole_enum_count } [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IMMDeviceEnumerator { int NotImpl1(); [PreserveSig] int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice); // the rest is not implemented } [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IMMDevice { [PreserveSig] int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface); // the rest is not implemented } [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IAudioSessionManager2 { int NotImpl1(); int NotImpl2(); [PreserveSig] int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum); // the rest is not implemented } [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IAudioSessionEnumerator { [PreserveSig] int GetCount(out int SessionCount); [PreserveSig] int GetSession(int SessionCount, out IAudioSessionControl2 Session); } [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ISimpleAudioVolume { [PreserveSig] int SetMasterVolume(float fLevel, ref Guid EventContext); [PreserveSig] int GetMasterVolume(out float pfLevel); [PreserveSig] int SetMute(bool bMute, ref Guid EventContext); [PreserveSig] int GetMute(out bool pbMute); } [Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IAudioSessionControl2 { // IAudioSessionControl [PreserveSig] int NotImpl0(); [PreserveSig] int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); [PreserveSig] int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); [PreserveSig] int GetIconPath([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); [PreserveSig] int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); [PreserveSig] int GetGroupingParam(out Guid pRetVal); [PreserveSig] int SetGroupingParam([MarshalAs(UnmanagedType.LPStruct)] Guid Override, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); [PreserveSig] int NotImpl1(); [PreserveSig] int NotImpl2(); // IAudioSessionControl2 [PreserveSig] int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); [PreserveSig] int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); [PreserveSig] int GetProcessId(out int pRetVal); [PreserveSig] int IsSystemSoundsSession(); [PreserveSig] int SetDuckingPreference(bool optOut); } // http://netcoreaudio.codeplex.com/SourceControl/latest#trunk/Code/CoreAudio/Interfaces/IAudioEndpointVolume.cs [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IAudioEndpointVolume { [PreserveSig] int NotImpl1(); [PreserveSig] int NotImpl2(); /// /// Gets a count of the channels in the audio stream. /// /// The number of channels. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetChannelCount( [Out][MarshalAs(UnmanagedType.U4)] out UInt32 channelCount); /// /// Sets the master volume level of the audio stream, in decibels. /// /// The new master volume level in decibels. /// A user context value that is passed to the notification callback. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int SetMasterVolumeLevel( [In][MarshalAs(UnmanagedType.R4)] float level, [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); /// /// Sets the master volume level, expressed as a normalized, audio-tapered value. /// /// The new master volume level expressed as a normalized value between 0.0 and 1.0. /// A user context value that is passed to the notification callback. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int SetMasterVolumeLevelScalar( [In][MarshalAs(UnmanagedType.R4)] float level, [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); /// /// Gets the master volume level of the audio stream, in decibels. /// /// The volume level in decibels. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetMasterVolumeLevel( [Out][MarshalAs(UnmanagedType.R4)] out float level); /// /// Gets the master volume level, expressed as a normalized, audio-tapered value. /// /// The volume level expressed as a normalized value between 0.0 and 1.0. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetMasterVolumeLevelScalar( [Out][MarshalAs(UnmanagedType.R4)] out float level); /// /// Sets the volume level, in decibels, of the specified channel of the audio stream. /// /// The channel number. /// The new volume level in decibels. /// A user context value that is passed to the notification callback. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int SetChannelVolumeLevel( [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, [In][MarshalAs(UnmanagedType.R4)] float level, [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); /// /// Sets the normalized, audio-tapered volume level of the specified channel in the audio stream. /// /// The channel number. /// The new master volume level expressed as a normalized value between 0.0 and 1.0. /// A user context value that is passed to the notification callback. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int SetChannelVolumeLevelScalar( [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, [In][MarshalAs(UnmanagedType.R4)] float level, [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); /// /// Gets the volume level, in decibels, of the specified channel in the audio stream. /// /// The zero-based channel number. /// The volume level in decibels. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetChannelVolumeLevel( [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, [Out][MarshalAs(UnmanagedType.R4)] out float level); /// /// Gets the normalized, audio-tapered volume level of the specified channel of the audio stream. /// /// The zero-based channel number. /// The volume level expressed as a normalized value between 0.0 and 1.0. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetChannelVolumeLevelScalar( [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, [Out][MarshalAs(UnmanagedType.R4)] out float level); /// /// Sets the muting state of the audio stream. /// /// True to mute the stream, or false to unmute the stream. /// A user context value that is passed to the notification callback. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int SetMute( [In][MarshalAs(UnmanagedType.Bool)] Boolean isMuted, [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); /// /// Gets the muting state of the audio stream. /// /// The muting state. True if the stream is muted, false otherwise. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetMute( [Out][MarshalAs(UnmanagedType.Bool)] out Boolean isMuted); /// /// Gets information about the current step in the volume range. /// /// The current zero-based step index. /// The total number of steps in the volume range. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetVolumeStepInfo( [Out][MarshalAs(UnmanagedType.U4)] out UInt32 step, [Out][MarshalAs(UnmanagedType.U4)] out UInt32 stepCount); /// /// Increases the volume level by one step. /// /// A user context value that is passed to the notification callback. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int VolumeStepUp( [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); /// /// Decreases the volume level by one step. /// /// A user context value that is passed to the notification callback. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int VolumeStepDown( [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); /// /// Queries the audio endpoint device for its hardware-supported functions. /// /// A hardware support mask that indicates the capabilities of the endpoint. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int QueryHardwareSupport( [Out][MarshalAs(UnmanagedType.U4)] out UInt32 hardwareSupportMask); /// /// Gets the volume range of the audio stream, in decibels. /// /// The minimum volume level in decibels. /// The maximum volume level in decibels. /// The volume increment level in decibels. /// An HRESULT code indicating whether the operation passed of failed. [PreserveSig] int GetVolumeRange( [Out][MarshalAs(UnmanagedType.R4)] out float volumeMin, [Out][MarshalAs(UnmanagedType.R4)] out float volumeMax, [Out][MarshalAs(UnmanagedType.R4)] out float volumeStep); } #endregion }