Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

How do I change the master volume in Windows Vista

How do I change the master volume in Windows Vista

  • Comments 32

It's actually easier in Vista than it was in XP.  For Vista, we recognized that one of the key customer scenarios was going to be setting the master volume, and since we'd removed the old mechanism that was used to set the volume, we knew we had to provide an easier mechanism for Vista.

Just for grins,  I threw together a tiny app that demonstrates it.  To save space, all error checking was removed.

#include <stdio.h>
#include <windows.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>

void Usage()
{
  printf("Usage: \n");
  printf(" SetVolume [Reports the current volume]\n");
  printf(" SetVolume -d <new volume in decibels> [Sets the current default render device volume to the new volume]\n");
  printf(" SetVolume -f <new volume as an amplitude scalar> [Sets the current default render device volume to the new volume]\n");

}
int _tmain(int argc, _TCHAR* argv[])
{
  HRESULT hr;
  bool decibels = false;
  bool scalar = false;
  double newVolume;
  if (argc != 3 && argc != 1)
  {
    Usage();
    return -1;
  }
  if (argc == 3)
  {
    if (argv[1][0] == '-')
    {
      if (argv[1][1] == 'f')
      {
        scalar = true;
      }
      else if (argv[1][1] == 'd')
      {
        decibels = true;
      }
    }
    else
    {
      Usage();
      return -1;
    }

    newVolume = _tstof(argv[2]);
  }

  // -------------------------
  CoInitialize(NULL);
  IMMDeviceEnumerator *deviceEnumerator = NULL;
  hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
  IMMDevice *defaultDevice = NULL;

  hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
  deviceEnumerator->Release();
  deviceEnumerator = NULL;

  IAudioEndpointVolume *endpointVolume = NULL;
  hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
  defaultDevice->Release();
  defaultDevice = NULL; 

  // -------------------------
  float currentVolume = 0;
  endpointVolume->GetMasterVolumeLevel(&currentVolume);
  printf("Current volume in dB is: %f\n", currentVolume);

  hr = endpointVolume->GetMasterVolumeLevelScalar(&currentVolume);
  printf("Current volume as a scalar is: %f\n", currentVolume);
  if (decibels)
  {
    hr = endpointVolume->SetMasterVolumeLevel((float)newVolume, NULL);
  }
  else if (scalar)
  {
    hr = endpointVolume->SetMasterVolumeLevelScalar((float)newVolume, NULL);
  }
  endpointVolume->Release();

  CoUninitialize();
  return 0;
}

This program has essentially 3 parts.  The first parses the command line, the second retrieves an endpoint volume interface on the default endpoint, the third retrieves the current volume and sets the volume.

I'm going to ignore the first part, it's the same junk you'll see in any CS 101 class. 

The second part instantiates an MMDeviceEnumerator object which implements the IMMDeviceEnumerator interface.  The IMMDeviceEnumerator interface is the gateway object to the new audio subsystem - it can be used to enumerate audio endpoints and retrieve information about the various endpoints.  In this case, I'm only interested in the GetDefaultAudioEndpoint method, it returns an IMMDevice object that points to the current endpoint.

Again, there are a bunch of things I can do with an IMMDevice object, but I'm only really interested in the "Activate" method.  The idea is that each MMDevice object supports lots of different interfaces, you "Activate" the interface to access the functionality associated with that object.  Again, in this case, I'm only interested in the IAudioEndpointVolume interface - there are other interfaces, like IDeviceTopology, and IAudioClient that can be activated from the endpoint.

The IAudioEndpointVolume interface is where the good stuff lives, right now I'm only interested in four methods, which retrieve (and set) the current endpoint volume in either decibels or as a scalar value. 

The decibels version of the IAudioEndointVolume interface instructs the driver to set the desired master volume (input or output) to the decibel value specified, it's intended to be used for applications that want to have exact control over the output dB value of the audio solution.

The scalar version is a bit more complicated.  It's intended for use in applications that have volume sliders, and provides a linear volume taper (represented as a floating point value between 0.0 and 1.0).  In other words, the perceived volume when you set the scalar version of the API to .5 is twice as loud as when set to .25 and is half as loud as when set to 1.0.

  • I'm confused how 13 lines of non-error checked code is "easy".

    Surely it wouldn't have killed anyone to have a nice API such as: SetDefaultMasterVolume(byte volume);

    ...or am I completely missing the point?? :)

  • Thanks Larry.  I'll have to send a link to this to the people at Griffin so they can fix my PowerMate.  I loved this volume knob on XP.  On Vista it only changes the volume for itself which is somewhat less than useful.

  • Anonymous: You've clearly never tried to do this on XP :).

    On XP, you need to first retrieve the default wave output device (or voice communications app), then you need to open a mixer handle that corresponds to that object.

    You next need to enumerate over the mixer destination lines associated with that mixer.  For each destination, you need to enumerate the controls on that destination looking for a master volume control.

    There are some shortcuts that will make the search for master volume easier, but even so.  Once you've retrieved the master volume control, you retrieve the volume using mixerGetControlDetails and set the volue using mixerSetControlDetails.

    I've written the code, it's a TON more than 13 lines.

    As to why there isn't a trivial API like SetDefaultMasterVolume, it's a trade-off.  You can have a bazilion APIs each of which wraps 13 lines of code or a couple of dozen APIs and push the 13 lines of code to the app.

  • This is great, but why in Vista is the pass through from mic to speakers no longer available. In XP I could just "unmute" the microphone, then I would hear the mic through the speakers...no option for that in Vista...or am I missing something?

  • David: Those are two unrelated issues.  The functionality you're describing was provided by your audio driver, on Vista, the same functionality exists - go to the playback devices, select your default playback device, and look at the "levels" tab - there should be a "Mic" or "Line In" slider, unmute that and it should work.

  • and that's the easy way????????? Then we must have different views on the word "EASY"

  • bw, IMHO, it's easy.  Especially compared to the amount of work it was to do it on every version of Windows before Vista.

  • Easy enough indeed.

    I'd like to ask whether these functions will be ported back to at least WinXP? Or if I have a program that'll run in both WinXP and Vista, I have to keep both code path?

  • I don't know, Larry. As annoying as the old mixer functions were, they were at least straightforward to call from languages other than C++ and (classic) VB. I'm not sure adding yet another COM server is the way to make this sort of thing "easy." Other examples are rampant throughout the shell, including the new Task Dialogs and common file dialogs.

    Off topic: Since MCI is presumably kaput now, what's the officially sanctioned version of "set door open" and "set door closed?"

  • bw: It's as easy as it should be, in my opinion. There's really only ONE application that I want controlling the master volume (and that's the little speaker icon in the notification area). Of course, they need to provide an API for "special cases" like MCE as noted before, but unless you are one of these "special cases" I don't want you touching my master volume control anyway.

  • Can you do this in vbscript? (in other words, does this have proper automation interfaces?)

    I guess the it would be

    GetObject("something with MMDeviceEnumerator").GetDefaultAudioEndpoint.Activate("something with AudioEndpointVolume").GetMasterVolumeLevel

  • Jonathan, unfortunately no :(  A managed interop layer for these APIs didn't make the bar for Vista :(

    Cheong, sorry, no - they rely on a TON of infrastructure in Vista, which does stink.

    Bob, MCI isn't kaput, it's still the best way of opening and closing the CD tray door (I've not yet found a better one (short of issuing the raw IOCTLs)).

  • Heh, when I first read the article title in my RSS reader, I thouhg you meant changing the default boot device :-)

    Re: MCI & CD tray operations - Surely there must be a generic API for ejecting devices? How else would you eject USB drives, etc? (Closing the device is another matter, but the API should be almost identical)

  • Having had to write code to activate and change the volume on line-in, this is definitely MUCH easier than mucking through the source lines, destination lines, and control types in the mixer API.

    I agree with Dean Harding's comment, though -- apps generally should not be mucking with the global playback volume, or really any volume settings, unless they're providing UI for the user to do so. It's annoying to have a game automatically shove MIDI to maximum volume because that was the fix the programmers came up with for no-music bugs.

  • New and easy is good, and I'm sure this is a wonderful improvement. And people do often complain that Microsoft introduces new APIs without removing the old crud, thus seemingly causing endless problems. There's a little known guy called Mr Chen who spends a great deal of time defending backwards compatability.

    But I do have a question that I would have assumed to be obvious: if the old way actually has been removed, will applications that did it the "old way" on XP (you know, that archaic and irrelevant old thing that noone uses any more) have problems running on Vista?

    And if the old way is still there, doesn't that mean VBscript can still do things that way?  And the phrase "and since we'd removed the old mechanism that was used to set the volume" would mean something other than what it seems to mean.

Page 1 of 3 (32 items) 123