Click here to Skip to main content
Click here to Skip to main content

Vista Core Audio API Master Volume Control

, 3 May 2012
Rate this:
Please Sign up or sign in to vote.
A managed wrapper for accessing the Vista Core Audio API
Screenshot - CoreAudioVolume.png

Introduction

Windows Vista features a completely new set of user-mode audio components that provide per application volume control. All legacy APIs such as the waveXxx and mixerXxx functions and DirectSound have been rebuilt on top of these new components so that without any code changes all 'old' applications support this new audio API. This is a great thing except when your application is reading or querying the master volume settings of the operating system because all of a sudden, you are no longer controlling the master volume of the operating system but only of your own application.

Core Audio API

The new API is COM based, and split into four sub APIs which roughly do the following:

  • MMDevice API - This API allows enumeration and instancing of the available audio devices in the system.
  • WASAPI - This API allows playback and recording of audio streams.
  • DeviceTopology API - This API allows access to hardware features such as bass, treble, and auto gain control.
  • EndpointVolume API - This API allows access to the Volume and Peak meters.

The MMDevice and EndpointVolume APIs are needed to control the master volume and mute settings, while the APIs themselves are a huge improvement since the legacy functions lack a nice managed interface, making working with them somehow an unpleasant experience in C#. Writing COM interop code is not for the novice and is error-prone, hence creating a wrapper to do all the plumbing for us seems a good idea.

Using the Code

You always start by creating the enumerator class, allowing you to find the device that you want, or just settle for the default device.

MMDeviceEnumerator devEnum = new MMDeviceEnumerator();
MMDevice defaultDevice = 
  devEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);

After you have a reference to an MMDevice object, toggling Mute is as easy as:

defaultDevice.AudioEndpointVolume.Mute = 
             !defaultDevice.AudioEndpointVolume.Mute;

Or getting the volume of the left channel:

Console.WriteLine("Left Volume : {0}", 
      defaultDevice.AudioEndpointVolume.Channels[0].VolumeLevelScalar);

Since the wrapper implements the complete EndpointVolume API, we can also get more advanced things such as peak meters for all channels:

Console.WriteLine("Master Peak : {0}", 
      defaultDevice.AudioMeterInformation.MasterPeakValue);
Console.WriteLine("Left   Peak : {0}", 
      defaultDevice.AudioMeterInformation.PeakValues[0]);
Console.WriteLine("Right  Peak : {0}", 
      defaultDevice.AudioMeterInformation.PeakValues[1]);

If you just want to be informed when somebody changes the volume settings, you can subscribe to the OnVolumeNotification event like this:

defaultDevice.AudioEndpointVolume.OnVolumeNotification += new 
    AudioEndpointVolumeNotificationDelegate(
       AudioEndpointVolume_OnVolumeNotification);
.
.
void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data)
{
    Console.WriteLine("New Volume {0}", data.MasterVolume);
    Console.WriteLine("Muted      {0}", data.Muted);
}

Points of interest

The documentation of the volume notification states it must be non-blocking. The client should never wait on a synchronization object during an event callback. Keep that in mind when write event handlers.

Although Vista does per-application audio settings, Microsoft has not open up the APIs to enumerate Sessions. So it will not be possible to make a sndvol.exe-like application.

Right now, only the MMDevice and EndpointVolume APIs are implemented; I hope to add the WASAPI in a future update.

History

  • 2007.04.23 - Initial article
  • 2010.05.17 - Updated source and sample

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Ray M.
Web Developer
Netherlands Netherlands
No Biography provided

Comments and Discussions

 
QuestionIs it possible to monitor an audio jack? PinmemberVekool_Coder1-Sep-13 2:03 
Question“The calling thread must be STA" Pinmemberankit_sam3-Jul-13 14:19 
QuestionHow can i use this in VB.net to get the "AudioMeterInformation.MasterPeakValue" Pinmember3AgL3 DeeJay26-Apr-13 12:09 
AnswerRe: How can i use this in VB.net to get the "AudioMeterInformation.MasterPeakValue" Pinmember3AgL3 DeeJay27-Apr-13 9:03 
GeneralRe: How can i use this in VB.net to get the "AudioMeterInformation.MasterPeakValue" PinmemberMember 1017462928-Aug-13 6:27 
GeneralRe: How can i use this in VB.net to get the "AudioMeterInformation.MasterPeakValue" Pinmember3AgL3 DeeJay28-Aug-13 9:22 
QuestionFollowing Code Cannot Run In Background Pinmemberq1139600966-Apr-13 14:07 
GeneralMy vote of 5 PinmemberSergio Andrés Gutiérrez Rojas29-Mar-13 13:46 
Questionhi Pinmembernmuhammad15-Mar-13 22:23 
GeneralMy vote of 5 Pinmemberbachtkb11-Feb-13 17:49 
GeneralMy vote of 3 Pinmemberjason.leex15-Jan-13 16:01 
GeneralMy vote of 5 PinmemberEdo Tzumer8-Jan-13 20:25 
QuestionIs Good Pinmemberisisbrand7-Jan-13 3:02 
GeneralMy vote of 5 Pinmembersir-West27-Dec-12 23:37 
QuestionWhy the files i downloaded is broken? PinmemberLoveIn_LIn21-Nov-12 15:23 
QuestionHow can I select the app in "CoreAudioConsoleTest" PinmemberMember 94111563-Nov-12 8:16 
QuestionPeak meter level for a capture endpoint always 0.0 unless Microsoft recording panel is open Pinmemberunpocoloco2-Nov-12 15:36 
QuestionHow to implement IMMNotificationClient Pinmemberkhoonking26-Sep-12 23:29 
AnswerRe: How to implement IMMNotificationClient Pinmemberimaginejta6-Jan-13 3:07 
GeneralRe: How to implement IMMNotificationClient PinmemberVekool_Coder1-Sep-13 1:55 
GeneralMy vote of 4 PinmemberBurak Tunçbilek30-Aug-12 12:13 
Question64/32 compatibility PinmemberErik Friesen24-Aug-12 11:13 
GeneralMy vote of 4 PinmemberBurak Tunçbilek31-Jul-12 11:51 
QuestionBug in PropertyStore Pinmemberkore_sar19-Jul-12 22:20 
GeneralMy vote of 5 Pinmemberyuzsshuihan11-Jun-12 20:48 
QuestionTHANK YOU!! Pinmemberphate86712-May-12 5:39 
Questiondoesnt load in vs2005 as claimed Pinmemberwilco_sg2-May-12 16:39 
AnswerRe: doesnt load in vs2005 as claimed PinmemberRay M.2-May-12 22:56 
SuggestionMy new code for switching the default audio device PinmemberMadMidi12-Mar-12 17:59 
I missed the "SetDefaultEndpoint" method in this library.
I needed it for a little app which starts over system hotkey, runs TeamSpeak3-Client and switches the default audio device.
So I can switch the directX game sound from "7.1-Speakers SB X-Fi" to "5.1-Headset Realtek-OnBoard" while playing online games with one single key on my gaming keyboard.
 
I added a new interface file "IPolicyConfig.cs":
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
 
namespace CoreAudioApi.Interfaces
{
    [Guid("f8679f50-850a-41cf-9c72-430f290290c8"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IPolicyConfig
    {
        [PreserveSig]
        int GetMixFormat(string pszDeviceName, IntPtr ppFormat);
 
        [PreserveSig]
        int GetDeviceFormat(string pszDeviceName, bool bDefault, IntPtr ppFormat);
 
        [PreserveSig]
        int ResetDeviceFormat(string pszDeviceName);
 
        [PreserveSig]
        int SetDeviceFormat(string pszDeviceName, IntPtr pEndpointFormat, IntPtr MixFormat);
 
        [PreserveSig]
        int GetProcessingPeriod(string pszDeviceName, bool bDefault, IntPtr pmftDefaultPeriod, IntPtr pmftMinimumPeriod);
 
        [PreserveSig]
        int SetProcessingPeriod(string pszDeviceName, IntPtr pmftPeriod);
 
        [PreserveSig]
        int GetShareMode(string pszDeviceName, IntPtr pMode);
 
        [PreserveSig]
        int SetShareMode(string pszDeviceName, IntPtr mode);
 
        [PreserveSig]
        int GetPropertyValue(string pszDeviceName, bool bFxStore, IntPtr key, IntPtr pv);
 
        [PreserveSig]
        int SetPropertyValue(string pszDeviceName, bool bFxStore, IntPtr key, IntPtr pv);
 
        [PreserveSig]
        int SetDefaultEndpoint(string pszDeviceName, ERole role);
 
        [PreserveSig]
        int SetEndpointVisibility(string pszDeviceName, bool bVisible);        
    }
}
and a new class file "PolicyConfigClient.cs":
using System;
using System.Collections.Generic;
using System.Text;
using CoreAudioApi.Interfaces;
using System.Runtime.InteropServices;
 
namespace CoreAudioApi
{
    [ComImport, Guid("870af99c-171d-4f9e-af0d-e63df40c2bc9")]
    internal class _PolicyConfigClient
    {
    }
    
    public class PolicyConfigClient
    {
        private IPolicyConfig _PolicyConfig = new _PolicyConfigClient() as IPolicyConfig;
 
        public void SetDefaultEndpoint(string devID, ERole eRole)
        {
            Marshal.ThrowExceptionForHR(_PolicyConfig.SetDefaultEndpoint(devID, eRole));
        }
    }
}
 

As an example, a little console tool which is useful in scripts:
using System;
using System.Collections.Generic;
using System.Text;
using CoreAudioApi;
using System.Diagnostics;
 
namespace CoreAudioConsoleTool
{
    class Program
    {
        static private void DisplayUsage()
        {
            Console.WriteLine(@"
Usage:
 
{0} [index | -default | -d | -help | -h]
 
(no parameter)  Lists audio devices with index and friendly name
index           Sets default audio device by given list index
-default | -d   Lists default audio device with index and friendly name
-help | -h      Displays usage\n",
                System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
        }
 
        static void Main(string[] args)
        {
            if (args.Length > 1)
            {
                Console.WriteLine("Wrong argument count !");
                DisplayUsage();
                return;
            }
 
            MMDeviceEnumerator DevEnum = new MMDeviceEnumerator();
            MMDeviceCollection devices = DevEnum.EnumerateAudioEndPoints(EDataFlow.eRender, EDeviceState.DEVICE_STATE_ACTIVE);
 
            if (args.Length == 0)
            {
                for (int i = 0; i < devices.Count; i++)
                    Console.WriteLine("{0} : \"{1}\"", i, devices[i].FriendlyName);
 
                return;
            }
 
            string param = args[0].ToLower();
            
            switch (param)
            {
                case "?":
                case "/?":
                case "-?":
                case "h":
                case "/h":
                case "-h":
                case "help":
                case "/help":
                case "-help":
                    DisplayUsage();
                    return;
            }
 
            MMDevice DefaultDevice = DevEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
            
            switch (param)
            {
                case "-d":
                case "-default":
                    for (int i = 0; i < devices.Count; i++)
                    {
                        if (devices[i].ID == DefaultDevice.ID)
                        {
                            Console.WriteLine("{0} : \"{1}\"", i, devices[i].FriendlyName);
                            return;
                        }
                    }
                    return;
            }
 
            int index;
 
            try
            {
                index = int.Parse(param);
            }
            catch (Exception e)
            {
                Console.WriteLine("Invalid int value in index argument !");
                Console.WriteLine(e.Message);
                DisplayUsage();
                return;
            }
 
            if (index < 0 || index >= devices.Count)
            {
                Console.WriteLine("Invalid device index !");
                DisplayUsage();
                return;
            }
 
            if (devices[index].ID == DefaultDevice.ID)
                return;
 
            PolicyConfigClient client = new PolicyConfigClient();
 
            client.SetDefaultEndpoint(devices[index].ID, ERole.eCommunications);
            client.SetDefaultEndpoint(devices[index].ID, ERole.eMultimedia);
 
        } // main()

    } // class program

} //namespace
 

For this example please have a look at my FriendlyName-fix in my prior post.
 
(All Code tested with VS2010 & Win7Pro x64 only.)
GeneralRe: My new code for switching the default audio device Pinmemberpetervanekeren22-Jan-13 3:23 
BugBug Report and Solution: FriendlyName gives wrong result [modified] PinmemberMadMidi12-Mar-12 0:18 
GeneralMy vote of 5 Pinmemberfairyredfox4-Mar-12 22:02 
GeneralMy vote of 5 PinmemberSteve McLenithan22-Feb-12 11:50 
GeneralMy vote of 5 Pinmemberqzcreativeit13-Feb-12 5:29 
Questionseriously? Pinmemberqzcreativeit13-Feb-12 5:28 
GeneralMy vote of 5 PinmemberDr Bob22-Jan-12 11:27 
QuestionSimple Volume Control Pinmembergauravkale22-Dec-11 5:35 
Questioncore PinmemberSpricer16-Dec-11 22:21 
Questioncore PinmemberSpricer16-Dec-11 22:13 
AnswerRe: core PinmemberRay M.16-Dec-11 22:14 
AnswerRe: core PinmemberMember 869768727-May-12 13:10 
Questioncore PinmemberSpricer16-Dec-11 22:05 
AnswerRe: core PinmemberRay M.16-Dec-11 22:11 
Questioncoreaudio api PinmemberSpricer16-Dec-11 14:52 
AnswerRe: coreaudio api PinmemberRay M.16-Dec-11 16:33 
QuestionDifference between VB and C#???? [modified] PinmemberLeo de Blank12-Sep-11 6:06 
AnswerRe: Difference between VB and C#???? PinmemberRay M.12-Sep-11 6:19 
GeneralRe: Difference between VB and C#???? PinmemberLeo de Blank12-Sep-11 9:17 
GeneralRe: Difference between VB and C#???? PinmemberRay M.12-Sep-11 9:35 
GeneralRe: Difference between VB and C#???? PinmemberLeo de Blank12-Sep-11 9:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 3 May 2012
Article Copyright 2007 by Ray M.
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid