On a recent Forum post, I was intrigued by the idea of being able to control the Windows Master Volume Programmatically. In that particular case, the intent was to allow scripting of the volume for user login and logoff.
The result was a rather straightforward program intended for use on the command line for setting and retrieving the current master system volume. I’ve made the source Available on Github and also put up an installer download here.
My initial investigations ended up with code that was able to retrieve the volume, but not set it. The approach taken in that case defined only a single interface method for the interface in question, with a strange “vtable skip” type of method. I’m not sure if that is a special syntax or what, but I ended up looking elsewhere after a few attempts to add the appropriate method to set the system volume.
In my searches, I found a library intended to wrap the Core Audio API to allow it to be accessed from a .NET program, which can be found on CodePlex as well as a (seemingly more recent, based on commit messages) Github.
Once I had that in place, it was merely a case of wiring it all together. Thankfully, since the program is relatively straightforward it is small enough (IMO) to slap it right here for convenience:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Remoting.Channels; using System.Security.AccessControl; using System.Text; using Vannatech.CoreAudio.Enumerations; using Vannatech.CoreAudio.Interfaces; namespace VolumeSlapper { //quick console program slapped together to retrieve/set volume on Windows Vista and later. class Program { private static String HelpText = @" VolumeSlapper Command-Line Volume Utility. v" + Assembly.GetEntryAssembly().GetName().Version.ToString() + @". Syntax: VolumeSlapper (operation) [volumelevel] operation: either get or set. get doesn't require an argument; set requires the volumelevel argument. volumelevel: a value parsable as a floating point value between 0 and 1 to use as the volume level. used only for the set operation. examples: Retrieve current audio level: {0} get set current audio level to 50%: {0} set 0.5 "; static void Main(string[] args) { //we won't bother with fancy argument parsing here, just look at the buggers directly. if(args.Length==0) { ShowHelp(); return; } else if(args.Length>0) { if(String.Compare(args[0],"get",StringComparison.OrdinalIgnoreCase)==0) { //get logic float currvolume = VolumeUtilities.GetMasterVolume(); Console.WriteLine(currvolume); } else if(String.Compare(args[0],"set",StringComparison.OrdinalIgnoreCase)==0) { //set logic. float assignvolume; if(!float.TryParse(args[1],out assignvolume)) { Console.WriteLine("Specified volume level not valid:" + args[1]); Console.WriteLine(); ShowHelp(); return; } else { VolumeUtilities.SetMasterVolume(assignvolume); Console.WriteLine("Volume Level set to " + assignvolume); } } else { Console.WriteLine("unrecognized parameter:" + args[0]); Console.WriteLine(); ShowHelp(); return; } } Console.ReadKey(); } static void ShowHelp() { String sHelpText = String.Format(HelpText, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)); Console.Write(sHelpText); } } public static class VolumeUtilities { public static float GetMasterVolume() { // retrieve audio device... IMMDeviceEnumerator useenumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); IMMDevice speakers; const int eRender = 0; const int eMultimedia = 1; //retrieve the actual endpoint useenumerator.GetDefaultAudioEndpoint(eRender, ERole.eMultimedia, out speakers); object o; //retrieve the actual interface instance to retrieve the volume information from. speakers.Activate(typeof(IAudioEndpointVolume).GUID, 0, IntPtr.Zero, out o); IAudioEndpointVolume aepv = (IAudioEndpointVolume)o; float result; aepv.GetMasterVolumeLevelScalar(out result); Marshal.ReleaseComObject(aepv); Marshal.ReleaseComObject(speakers); Marshal.ReleaseComObject(useenumerator); return result; } public static float SetMasterVolume(float newValue) { // retrieve audio device... IMMDeviceEnumerator useenumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); IMMDevice speakers; const int eRender = 0; const int eMultimedia = 1; //retrieve the actual endpoint useenumerator.GetDefaultAudioEndpoint(eRender, ERole.eMultimedia, out speakers); object o; //retrieve the actual interface instance to retrieve the volume information from. speakers.Activate(typeof(IAudioEndpointVolume).GUID, 0, IntPtr.Zero, out o); IAudioEndpointVolume aepv = (IAudioEndpointVolume)o; float result; int hresult = aepv.GetMasterVolumeLevelScalar(out result); aepv.SetMasterVolumeLevelScalar(newValue,new System.Guid()); Marshal.ReleaseComObject(aepv); Marshal.ReleaseComObject(speakers); Marshal.ReleaseComObject(useenumerator); return result; } [ComImport] [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] private class MMDeviceEnumerator { } } } |
Basically it takes the default EndPoint and changes or sets it’s volume. Nothing too fancy. Ideally, I would clean it up code-wise and also implement additional features- such as manipulating the audio of other programs – which could make it useful for certain scripting uses.
Have something to say about this post? Comment!