7

I need to control the volume level of the audio produced by a specific application. I have found Simon's simple & neat answer for a similar question here, but it enumerates audio-generating applications by Display-Name, which for some applications - is empty, and hence cannot be used to detect the target application (whose volume is to be controlled).

Hence, I'm looking for a very similar solution, enumerating audio-playing applications' process IDs, through which I could detect applications' actual names (process names & main window titles).

I'm using VB.Net 2010, for Windows 7.

Community
  • 1
  • 1
Bliss
  • 426
  • 2
  • 5
  • 19

4 Answers4

24

Here is a set of utility C# classes that allow you to get information on audio devices and audio session, including process information. They use the Windows Core Audio Library, and only work on Windows 7 or higher:

// sample program
class Program
{
    static void Main(string[] args)
    {
        // dump all audio devices
        foreach (AudioDevice device in AudioUtilities.GetAllDevices())
        {
            Console.WriteLine(device.FriendlyName);
        }

        // dump all audio sessions
        foreach (AudioSession session in AudioUtilities.GetAllSessions())
        {
            if (session.Process != null)
            {
                // only the one associated with a defined process
                Console.WriteLine(session.Process.ProcessName);
            }
        }
    }
}

// audio utilities
public static class AudioUtilities
{
    private static IAudioSessionManager2 GetAudioSessionManager()
    {
        IMMDevice speakers = GetSpeakers();
        if (speakers == null)
            return null;

        // win7+ only
        object o;
        if (speakers.Activate(typeof(IAudioSessionManager2).GUID, CLSCTX.CLSCTX_ALL, IntPtr.Zero, out o) != 0 || o == null)
            return null;

        return o as IAudioSessionManager2;
    }

    public static AudioDevice GetSpeakersDevice()
    {
        return CreateDevice(GetSpeakers());
    }

    private static AudioDevice CreateDevice(IMMDevice dev)
    {
        if (dev == null)
            return null;

        string id;
        dev.GetId(out id);
        DEVICE_STATE state;
        dev.GetState(out state);
        Dictionary<string, object> properties = new Dictionary<string, object>();
        IPropertyStore store;
        dev.OpenPropertyStore(STGM.STGM_READ, out store);
        if (store != null)
        {
            int propCount;
            store.GetCount(out propCount);
            for (int j = 0; j < propCount; j++)
            {
                PROPERTYKEY pk;
                if (store.GetAt(j, out pk) == 0)
                {
                    PROPVARIANT value = new PROPVARIANT();
                    int hr = store.GetValue(ref pk, ref value);
                    object v = value.GetValue();
                    try
                    {
                        if (value.vt != VARTYPE.VT_BLOB) // for some reason, this fails?
                        {
                            PropVariantClear(ref value);
                        }
                    }
                    catch
                    {
                    }
                    string name = pk.ToString();
                    properties[name] = v;
                }
            }
        }
        return new AudioDevice(id, (AudioDeviceState)state, properties);
    }

    public static IList<AudioDevice> GetAllDevices()
    {
        List<AudioDevice> list = new List<AudioDevice>();
        IMMDeviceEnumerator deviceEnumerator = null;
        try
        {
            deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
        }
        catch
        {
        }
        if (deviceEnumerator == null)
            return list;

        IMMDeviceCollection collection;
        deviceEnumerator.EnumAudioEndpoints(EDataFlow.eAll, DEVICE_STATE.MASK_ALL, out collection);
        if (collection == null)
            return list;

        int count;
        collection.GetCount(out count);
        for (int i = 0; i < count; i++)
        {
            IMMDevice dev;
            collection.Item(i, out dev);
            if (dev != null)
            {
                list.Add(CreateDevice(dev));
            }
        }
        return list;
    }

    private static IMMDevice GetSpeakers()
    {
        // get the speakers (1st render + multimedia) device
        try
        {
            IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
            IMMDevice speakers;
            deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers);
            return speakers;
        }
        catch
        {
            return null;
        }
    }

    public static IList<AudioSession> GetAllSessions()
    {
        List<AudioSession> list = new List<AudioSession>();
        IAudioSessionManager2 mgr = GetAudioSessionManager();
        if (mgr == null)
            return list;

        IAudioSessionEnumerator sessionEnumerator;
        mgr.GetSessionEnumerator(out sessionEnumerator);
        int count;
        sessionEnumerator.GetCount(out count);

        for (int i = 0; i < count; i++)
        {
            IAudioSessionControl ctl;
            sessionEnumerator.GetSession(i, out ctl);
            if (ctl == null)
                continue;

            IAudioSessionControl2 ctl2 = ctl as IAudioSessionControl2;
            if (ctl2 != null)
            {
                list.Add(new AudioSession(ctl2));
            }
        }
        Marshal.ReleaseComObject(sessionEnumerator);
        Marshal.ReleaseComObject(mgr);
        return list;
    }

    public static AudioSession GetProcessSession()
    {
        int id = Process.GetCurrentProcess().Id;
        foreach (AudioSession session in GetAllSessions())
        {
            if (session.ProcessId == id)
                return session;

            session.Dispose();
        }
        return null;
    }

    [DllImport("ole32.dll")]
    private static extern int PropVariantClear(ref PROPVARIANT pvar);

    [ComImport]
    [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
    private class MMDeviceEnumerator
    {
    }

    [Flags]
    private enum CLSCTX
    {
        CLSCTX_INPROC_SERVER = 0x1,
        CLSCTX_INPROC_HANDLER = 0x2,
        CLSCTX_LOCAL_SERVER = 0x4,
        CLSCTX_REMOTE_SERVER = 0x10,
        CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
    }

    private enum STGM
    {
        STGM_READ = 0x00000000,
    }

    private enum EDataFlow
    {
        eRender,
        eCapture,
        eAll,
    }

    private enum ERole
    {
        eConsole,
        eMultimedia,
        eCommunications,
    }

    private enum DEVICE_STATE
    {
        ACTIVE = 0x00000001,
        DISABLED = 0x00000002,
        NOTPRESENT = 0x00000004,
        UNPLUGGED = 0x00000008,
        MASK_ALL = 0x0000000F
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PROPERTYKEY
    {
        public Guid fmtid;
        public int pid;

        public override string ToString()
        {
            return fmtid.ToString("B") + " " + pid;
        }
    }

    // NOTE: we only define what we handle
    [Flags]
    private enum VARTYPE : short
    {
        VT_I4 = 3,
        VT_BOOL = 11,
        VT_UI4 = 19,
        VT_LPWSTR = 31,
        VT_BLOB = 65,
        VT_CLSID = 72,
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PROPVARIANT
    {
        public VARTYPE vt;
        public ushort wReserved1;
        public ushort wReserved2;
        public ushort wReserved3;
        public PROPVARIANTunion union;

        public object GetValue()
        {
            switch (vt)
            {
                case VARTYPE.VT_BOOL:
                    return union.boolVal != 0;

                case VARTYPE.VT_LPWSTR:
                    return Marshal.PtrToStringUni(union.pwszVal);

                case VARTYPE.VT_UI4:
                    return union.lVal;

                case VARTYPE.VT_CLSID:
                    return (Guid)Marshal.PtrToStructure(union.puuid, typeof(Guid));

                default:
                    return vt.ToString() + ":?";
            }
        }
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct PROPVARIANTunion
    {
        [FieldOffset(0)]
        public int lVal;
        [FieldOffset(0)]
        public ulong uhVal;
        [FieldOffset(0)]
        public short boolVal;
        [FieldOffset(0)]
        public IntPtr pwszVal;
        [FieldOffset(0)]
        public IntPtr puuid;
    }

    [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IMMDeviceEnumerator
    {
        [PreserveSig]
        int EnumAudioEndpoints(EDataFlow dataFlow, DEVICE_STATE dwStateMask, out IMMDeviceCollection ppDevices);

        [PreserveSig]
        int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppEndpoint);

        [PreserveSig]
        int GetDevice([MarshalAs(UnmanagedType.LPWStr)] string pwstrId, out IMMDevice ppDevice);

        [PreserveSig]
        int RegisterEndpointNotificationCallback(IMMNotificationClient pClient);

        [PreserveSig]
        int UnregisterEndpointNotificationCallback(IMMNotificationClient pClient);
    }

    [Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IMMNotificationClient
    {
        void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, DEVICE_STATE dwNewState);
        void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId);
        void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId);
        void OnDefaultDeviceChanged(EDataFlow flow, ERole role, string pwstrDefaultDeviceId);
        void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PROPERTYKEY key);
    }

    [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IMMDeviceCollection
    {
        [PreserveSig]
        int GetCount(out int pcDevices);

        [PreserveSig]
        int Item(int nDevice, out IMMDevice ppDevice);
    }

    [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IMMDevice
    {
        [PreserveSig]
        int Activate([MarshalAs(UnmanagedType.LPStruct)] Guid riid, CLSCTX dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);

        [PreserveSig]
        int OpenPropertyStore(STGM stgmAccess, out IPropertyStore ppProperties);

        [PreserveSig]
        int GetId([MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);

        [PreserveSig]
        int GetState(out DEVICE_STATE pdwState);
    }

    [Guid("6f79d558-3e96-4549-a1d1-7d75d2288814"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IPropertyDescription
    {
        [PreserveSig]
        int GetPropertyKey(out PROPERTYKEY pkey);

        [PreserveSig]
        int GetCanonicalName(out IntPtr ppszName);

        [PreserveSig]
        int GetPropertyType(out short pvartype);

        [PreserveSig]
        int GetDisplayName(out IntPtr ppszName);

        // WARNING: the rest is undefined. you *can't* implement it, only use it.
    }

    [Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IPropertyStore
    {
        [PreserveSig]
        int GetCount(out int cProps);

        [PreserveSig]
        int GetAt(int iProp, out PROPERTYKEY pkey);

        [PreserveSig]
        int GetValue(ref PROPERTYKEY key, ref PROPVARIANT pv);

        [PreserveSig]
        int SetValue(ref PROPERTYKEY key, ref PROPVARIANT propvar);

        [PreserveSig]
        int Commit();
    }

    [Guid("BFA971F1-4D5E-40BB-935E-967039BFBEE4"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IAudioSessionManager
    {
        [PreserveSig]
        int GetAudioSessionControl([MarshalAs(UnmanagedType.LPStruct)] Guid AudioSessionGuid, int StreamFlags, out IAudioSessionControl SessionControl);

        [PreserveSig]
        int GetSimpleAudioVolume([MarshalAs(UnmanagedType.LPStruct)] Guid AudioSessionGuid, int StreamFlags, ISimpleAudioVolume AudioVolume);
    }

    [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IAudioSessionManager2
    {
        [PreserveSig]
        int GetAudioSessionControl([MarshalAs(UnmanagedType.LPStruct)] Guid AudioSessionGuid, int StreamFlags, out IAudioSessionControl SessionControl);

        [PreserveSig]
        int GetSimpleAudioVolume([MarshalAs(UnmanagedType.LPStruct)] Guid AudioSessionGuid, int StreamFlags, ISimpleAudioVolume AudioVolume);

        [PreserveSig]
        int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum);

        [PreserveSig]
        int RegisterSessionNotification(IAudioSessionNotification SessionNotification);

        [PreserveSig]
        int UnregisterSessionNotification(IAudioSessionNotification SessionNotification);

        int RegisterDuckNotificationNotImpl();
        int UnregisterDuckNotificationNotImpl();
    }

    [Guid("641DD20B-4D41-49CC-ABA3-174B9477BB08"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IAudioSessionNotification
    {
        void OnSessionCreated(IAudioSessionControl NewSession);
    }

    [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IAudioSessionEnumerator
    {
        [PreserveSig]
        int GetCount(out int SessionCount);

        [PreserveSig]
        int GetSession(int SessionCount, out IAudioSessionControl Session);
    }

    [Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IAudioSessionControl2
    {
        // IAudioSessionControl
        [PreserveSig]
        int GetState(out AudioSessionState pRetVal);

        [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 RegisterAudioSessionNotification(IAudioSessionEvents NewNotifications);

        [PreserveSig]
        int UnregisterAudioSessionNotification(IAudioSessionEvents NewNotifications);

        // 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);
    }

    [Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IAudioSessionControl
    {
        [PreserveSig]
        int GetState(out AudioSessionState pRetVal);

        [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 RegisterAudioSessionNotification(IAudioSessionEvents NewNotifications);

        [PreserveSig]
        int UnregisterAudioSessionNotification(IAudioSessionEvents NewNotifications);
    }

    [Guid("24918ACC-64B3-37C1-8CA9-74A66E9957A8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IAudioSessionEvents
    {
        void OnDisplayNameChanged([MarshalAs(UnmanagedType.LPWStr)] string NewDisplayName, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
        void OnIconPathChanged([MarshalAs(UnmanagedType.LPWStr)] string NewIconPath, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
        void OnSimpleVolumeChanged(float NewVolume, bool NewMute, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
        void OnChannelVolumeChanged(int ChannelCount, IntPtr NewChannelVolumeArray, int ChangedChannel, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
        void OnGroupingParamChanged([MarshalAs(UnmanagedType.LPStruct)] Guid NewGroupingParam, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
        void OnStateChanged(AudioSessionState NewState);
        void OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason);
    }

    [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface ISimpleAudioVolume
    {
        [PreserveSig]
        int SetMasterVolume(float fLevel, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);

        [PreserveSig]
        int GetMasterVolume(out float pfLevel);

        [PreserveSig]
        int SetMute(bool bMute, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);

        [PreserveSig]
        int GetMute(out bool pbMute);
    }
}

public sealed class AudioSession : IDisposable
{
    private AudioUtilities.IAudioSessionControl2 _ctl;
    private Process _process;

    internal AudioSession(AudioUtilities.IAudioSessionControl2 ctl)
    {
        _ctl = ctl;
    }

    public Process Process
    {
        get
        {
            if (_process == null && ProcessId != 0)
            {
                try
                {
                    _process = Process.GetProcessById(ProcessId);
                }
                catch
                {
                    // do nothing
                }
            }
            return _process;
        }
    }

    public int ProcessId
    {
        get
        {
            CheckDisposed();
            int i;
            _ctl.GetProcessId(out i);
            return i;
        }
    }

    public string Identifier
    {
        get
        {
            CheckDisposed();
            string s;
            _ctl.GetSessionIdentifier(out s);
            return s;
        }
    }

    public string InstanceIdentifier
    {
        get
        {
            CheckDisposed();
            string s;
            _ctl.GetSessionInstanceIdentifier(out s);
            return s;
        }
    }

    public AudioSessionState State
    {
        get
        {
            CheckDisposed();
            AudioSessionState s;
            _ctl.GetState(out s);
            return s;
        }
    }

    public Guid GroupingParam
    {
        get
        {
            CheckDisposed();
            Guid g;
            _ctl.GetGroupingParam(out g);
            return g;
        }
        set
        {
            CheckDisposed();
            _ctl.SetGroupingParam(value, Guid.Empty);
        }
    }

    public string DisplayName
    {
        get
        {
            CheckDisposed();
            string s;
            _ctl.GetDisplayName(out s);
            return s;
        }
        set
        {
            CheckDisposed();
            string s;
            _ctl.GetDisplayName(out s);
            if (s != value)
            {
                _ctl.SetDisplayName(value, Guid.Empty);
            }
        }
    }

    public string IconPath
    {
        get
        {
            CheckDisposed();
            string s;
            _ctl.GetIconPath(out s);
            return s;
        }
        set
        {
            CheckDisposed();
            string s;
            _ctl.GetIconPath(out s);
            if (s != value)
            {
                _ctl.SetIconPath(value, Guid.Empty);
            }
        }
    }

    private void CheckDisposed()
    {
        if (_ctl == null)
            throw new ObjectDisposedException("Control");
    }

    public override string ToString()
    {
        string s = DisplayName;
        if (!string.IsNullOrEmpty(s))
            return "DisplayName: " + s;

        if (Process != null)
            return "Process: " + Process.ProcessName;

        return "Pid: " + ProcessId;
    }

    public void Dispose()
    {
        if (_ctl != null)
        {
            Marshal.ReleaseComObject(_ctl);
            _ctl = null;
        }
    }
}

public sealed class AudioDevice
{
    internal AudioDevice(string id, AudioDeviceState state, IDictionary<string, object> properties)
    {
        Id = id;
        State = state;
        Properties = properties;
    }

    public string Id { get; private set; }
    public AudioDeviceState State { get; private set; }
    public IDictionary<string, object> Properties { get; private set; }

    public string Description
    {
        get
        {
            const string PKEY_Device_DeviceDesc = "{a45c254e-df1c-4efd-8020-67d146a850e0} 2";
            object value;
            Properties.TryGetValue(PKEY_Device_DeviceDesc, out value);
            return string.Format("{0}", value);
        }
    }

    public string ContainerId
    {
        get
        {
            const string PKEY_Devices_ContainerId = "{8c7ed206-3f8a-4827-b3ab-ae9e1faefc6c} 2";
            object value;
            Properties.TryGetValue(PKEY_Devices_ContainerId, out value);
            return string.Format("{0}", value);
        }
    }

    public string EnumeratorName
    {
        get
        {
            const string PKEY_Device_EnumeratorName = "{a45c254e-df1c-4efd-8020-67d146a850e0} 24";
            object value;
            Properties.TryGetValue(PKEY_Device_EnumeratorName, out value);
            return string.Format("{0}", value);
        }
    }

    public string InterfaceFriendlyName
    {
        get
        {
            const string DEVPKEY_DeviceInterface_FriendlyName = "{026e516e-b814-414b-83cd-856d6fef4822} 2";
            object value;
            Properties.TryGetValue(DEVPKEY_DeviceInterface_FriendlyName, out value);
            return string.Format("{0}", value);
        }
    }

    public string FriendlyName
    {
        get
        {
            const string DEVPKEY_Device_FriendlyName = "{a45c254e-df1c-4efd-8020-67d146a850e0} 14";
            object value;
            Properties.TryGetValue(DEVPKEY_Device_FriendlyName, out value);
            return string.Format("{0}", value);
        }
    }

    public string IconPath
    {
        get
        {
            const string DEVPKEY_DeviceClass_IconPath = "{259abffc-50a7-47ce-af08-68c9a7d73366} 12";
            object value;
            Properties.TryGetValue(DEVPKEY_DeviceClass_IconPath, out value);
            return string.Format("{0}", value);
        }
    }

    public override string ToString()
    {
        return FriendlyName;
    }
}

public enum AudioSessionState
{
    Inactive = 0,
    Active = 1,
    Expired = 2
}

public enum AudioDeviceState
{
    Active = 0x1,
    Disabled = 0x2,
    NotPresent = 0x4,
    Unplugged = 0x8,
}

public enum AudioSessionDisconnectReason
{
    DisconnectReasonDeviceRemoval = 0,
    DisconnectReasonServerShutdown = 1,
    DisconnectReasonFormatChanged = 2,
    DisconnectReasonSessionLogoff = 3,
    DisconnectReasonSessionDisconnected = 4,
    DisconnectReasonExclusiveModeOverride = 5
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
15

I took Simon's answer from this question and the other question, and boiled it down to answer the question: How to change the volume of a program only given its process ID.

This example sets the volume of Spotify to 50%.

static class Program {
  [DllImport("user32.dll")]
  public static extern IntPtr FindWindow(string strClassName, string strWindowName);

  [DllImport("user32.dll", SetLastError = true)]
  public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

  static void Main() {
    var pID = 0;
    foreach (var process in Process.GetProcesses()) {
      if (process.ProcessName == "Spotify" && !String.IsNullOrEmpty(process.MainWindowTitle))
        pID = process.Id;
    }

    if (pID == 0)
      return;

    VolumeMixer.SetApplicationVolume(pID, 50f);
  }
}

Here is the VolumeMixer helper class:

public class VolumeMixer {
  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) {
    // get the speakers (1st render + multimedia) device
    IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
    IMMDevice speakers;
    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);
    IAudioSessionManager2 mgr = (IAudioSessionManager2)o;

    // enumerate sessions for on this device
    IAudioSessionEnumerator sessionEnumerator;
    mgr.GetSessionEnumerator(out sessionEnumerator);
    int count;
    sessionEnumerator.GetCount(out count);

    // search for an audio session with the required name
    // NOTE: we could also use the process id instead of the app name (with IAudioSessionControl2)
    ISimpleAudioVolume volumeControl = null;
    for (int i = 0; i < count; i++) {
      IAudioSessionControl2 ctl;
      sessionEnumerator.GetSession(i, out ctl);
      int cpid;
      ctl.GetProcessId(out cpid);

      if (cpid == pid) {
        volumeControl = ctl as ISimpleAudioVolume;
        break;
      }
      Marshal.ReleaseComObject(ctl);
    }
    Marshal.ReleaseComObject(sessionEnumerator);
    Marshal.ReleaseComObject(mgr);
    Marshal.ReleaseComObject(speakers);
    Marshal.ReleaseComObject(deviceEnumerator);
    return volumeControl;
  }
}

[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);
}
Anders Carstensen
  • 2,949
  • 23
  • 23
  • How can I find a name for a different application? Is there a way to list them? – Aleksander Lipka Nov 23 '19 at 13:15
  • 1
    @AleksanderLipka I have updated my answer, so it loops through all processes and looks for Spotify. You can use the code to list all processes. – Anders Carstensen Nov 23 '19 at 14:20
  • Upon running `GetApplicationVolume`, I got a `Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'MMDeviceEnumerator' to type '(Namespace).MMDeviceEnumerator'.` error. Changing the lines `IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());` to `MMDeviceEnumeratorFactory.CreateInstance()` from [this answer](https://stackoverflow.com/a/31931235/) fixed this error. – Gannon Prudhomme May 08 '20 at 19:18
1

Spotify's main window is no longer called SpotifyMainWindow but Chrome_WidgetWin_0 for some reason.

So change

var hWnd = FindWindow("SpotifyMainWindow", "Spotify");

to

var hWnd = FindWindow("Chrome_WidgetWin_0", "");

Otherwise the volume won't change if a song is playing.

LarsTech
  • 80,625
  • 14
  • 153
  • 225
Grim
  • 51
  • 5
0

First Of All, Download NAudio File From Any Browser

Then Add The DLL Into Reference

Then Add ComboBox, TrackBar And Label In The Form

Then Copy The Code Of Either VBExpress Or C#

VB.NET:

Imports NAudio, NAudio.CoreAudioApi
Public Class Form1
    Public MDE As New MMDeviceEnumerator
    Public MD As MMDevice = MDE.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)
    Public sessions = MD.AudioSessionManager.Sessions
    Public IndexVal As Integer = 0

    Public Sub Form1_Load() Handles MyBase.Load
        For C As Integer = 0 To sessions.Count
            ComboBox1.Items.Add(sessions.Item(C).GetProcessId)

        Next

        ComboBox1.SelectedIndex = IndexVal
    End Sub

    Public Sub TrackBar1_Scroll() Handles TrackBar1.Scroll
        sessions.Item(ComboBox1.SelectedIndex).SimpleAudioVolume.Volume = TrackBar1.Value / 100
    End Sub

End Class

CSharp (C#):

using NAudio;
using NAudio.CoreAudioApi;
using System;
public namespace WindowsApplicationForm1{
    public class Form1{
        public MMDeviceEnumerator MDE = new MMDeviceEnumerator();
        public MMDevice MD = MDE.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
        var sessions = MD.AudioSessionManager.Sessions;
        public int IndexVal;
        public void Form1_Load(Object sender, EventArgs e){
            trackBar1.Scroll += new System.EventHandler(TrackBar1_Scroll);

            for(int x; x < sessions.Count(); x++){
               comboBox1.Items.Add(sessions.Item(x));
            }
            comboBox1.SelectedIndex = IndexVal;
        }
        public void TrackBar1_Scroll(Object Sender, EventArgs e){

            sessions.Item(comboBox1.SelectedIndex).SimpleAudioVolume.Volume = trackBar1.Value / 100;
        }




    }
}

If You Want To See Process ID Of Any App, Go To Task Manager And See Whether "PID" Is Checked Or Not

To Check Effect Of Volume, Open Volume Mixer And See Whether It's Changing Or Not