Programmatically (or Command Line) change the default sound playback device in Windows 7

May 31, 2011 by
 

This post sums up the information found here.

Microsoft kept the API which changes the default sound playback device closed, for good reason. Drivers and control applet applications from different manufactures would mostly certainly end up ‘fighting’ over the default device, which would be terribly confusing and effectively makes the hardware incompatible. There are legitimate reasons for one to wish to change the default playback device from the command line or programmatically, but the needs are generally isolated to a single user or a group of users looking for something highly specialized. A friend of mine needed just this kind of solution for switching from headphones to speakers (without clicking a few times and without opening new windows).

I’ve simply wrapped the information in the aforementioned MSDN thread into a console application. The application uses undocumented API; while it works on Windows Vista and Windows 7, is it very likely to become broken with the next version of Windows. This isn’t something you can rely on.

Here is the undocumented and private COM interface which is used by mmsys.cpl to interact with the Audio service (credit goes to EreTIk obviously):

// ----------------------------------------------------------------------------
// PolicyConfig.h
// Undocumented COM-interface IPolicyConfig.
// Use for set default audio render endpoint
// @author EreTIk
// ----------------------------------------------------------------------------
 
#pragma once
 
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8")
IPolicyConfig;
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9")
CPolicyConfigClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigClient
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
//
// interface IPolicyConfig
// {f8679f50-850a-41cf-9c72-430f290290c8}
//
// Query interface:
// CComPtr[IPolicyConfig] PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient));
//
// @compatible: Windows 7 and Later
// ----------------------------------------------------------------------------
interface IPolicyConfig : public IUnknown
{
public:
 
    virtual HRESULT GetMixFormat(
        PCWSTR,
        WAVEFORMATEX **
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
        PCWSTR,
        INT,
        WAVEFORMATEX **
    );
 
    virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
        PCWSTR
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
        PCWSTR,
        WAVEFORMATEX *,
        WAVEFORMATEX *
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
        PCWSTR,
        INT,
        PINT64,
        PINT64
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
        PCWSTR,
        PINT64
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
        __in PCWSTR wszDeviceId,
        __in ERole eRole
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
        PCWSTR,
        INT
    );
};
 
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620")
IPolicyConfigVista;
class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862")
CPolicyConfigVistaClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigVistaClient
// {294935CE-F637-4E7C-A41B-AB255460B862}
//
// interface IPolicyConfigVista
// {568b9108-44bf-40b4-9006-86afe5b5a620}
//
// Query interface:
// CComPtr[IPolicyConfigVista] PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
//
// @compatible: Windows Vista and Later
// ----------------------------------------------------------------------------
interface IPolicyConfigVista : public IUnknown
{
public:
 
    virtual HRESULT GetMixFormat(
        PCWSTR,
        WAVEFORMATEX **
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
        PCWSTR,
        INT,
        WAVEFORMATEX **
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
        PCWSTR,
        WAVEFORMATEX *,
        WAVEFORMATEX *
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
        PCWSTR,
        INT,
        PINT64,
        PINT64
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
        PCWSTR,
        PINT64
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE GetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE SetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
        __in PCWSTR wszDeviceId,
        __in ERole eRole
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
        PCWSTR,
        INT
    );  // not available on Windows 7, use method from IPolicyConfig
};

We simply enumerate the devices and choose one via the command line:

#include "stdio.h"
#include "wchar.h"
#include "tchar.h"
#include "windows.h"
#include "Mmdeviceapi.h"
#include "PolicyConfig.h"
#include "Propidl.h"
#include "Functiondiscoverykeys_devpkey.h"
 
HRESULT SetDefaultAudioPlaybackDevice(LPCWSTR devID)
{
	IPolicyConfigVista *pPolicyConfig;
	ERole reserved = eConsole;
 
    HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient),
		NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID *)&pPolicyConfig);
	if (SUCCEEDED(hr))
	{
		hr = pPolicyConfig->SetDefaultEndpoint(devID, reserved);
		pPolicyConfig->Release();
	}
	return hr;
}
 
// EndPointController.exe [NewDefaultDeviceID]
int _tmain(int argc, _TCHAR* argv[])
{
	// read the command line option, -1 indicates list devices.
	int option = -1;
	if (argc == 2) option = atoi((char*)argv[1]);
 
	HRESULT hr = CoInitialize(NULL);
	if (SUCCEEDED(hr))
	{
		IMMDeviceEnumerator *pEnum = NULL;
		// Create a multimedia device enumerator.
		hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
			CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnum);
		if (SUCCEEDED(hr))
		{
			IMMDeviceCollection *pDevices;
			// Enumerate the output devices.
			hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices);
			if (SUCCEEDED(hr))
			{
				UINT count;
				pDevices->GetCount(&count);
				if (SUCCEEDED(hr))
				{
					for (int i = 0; i < count; i++)
					{
						IMMDevice *pDevice;
						hr = pDevices->Item(i, &pDevice);
						if (SUCCEEDED(hr))
						{
							LPWSTR wstrID = NULL;
							hr = pDevice->GetId(&wstrID);
							if (SUCCEEDED(hr))
							{
								IPropertyStore *pStore;
								hr = pDevice->OpenPropertyStore(STGM_READ, &pStore);
								if (SUCCEEDED(hr))
								{
									PROPVARIANT friendlyName;
									PropVariantInit(&friendlyName);
									hr = pStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
									if (SUCCEEDED(hr))
									{
										// if no options, print the device
										// otherwise, find the selected device and set it to be default
										if (option == -1) printf("Audio Device %d: %ws\n",i, friendlyName.pwszVal);
										if (i == option) SetDefaultAudioPlaybackDevice(wstrID);
										PropVariantClear(&friendlyName);
									}
									pStore->Release();
								}
							}
							pDevice->Release();
						}
					}
				}
				pDevices->Release();
			}
			pEnum->Release();
		}
	}
	return hr;
}

You can download the application and source project. (MIT License)

My friend followed up with some .NET code to wrap it.

Comments

16 Comments on Programmatically (or Command Line) change the default sound playback device in Windows 7

    [...] change the default audio playback device, on the fly… a good friend of mine wrote a great article and subsequent code example (in Win32 C++) that compiles down to a very handy console-based [...]

  1. Peter Parys on Thu, 16th Jun 2011 5:51 PM
  2. great job!

    little suggestion: if you’d use “cout” instead of “printf” to display the selection of audio devices in the console window it would be possible to parse the output of endpointcontroller within batch-scripts, i.e. it displays through standard output. although “printf” displays the device list, it is not possible to work with the results in the command line, e.g.:

    endpointcontroller.exe | find “Realtek”

    This would usually display only devices with “Realtek” in their name if endpointcontroller uses “cout” for output. Using “printf” the pipe doesn’t work.

    many thanks for your consideration!

  3. Kevin on Sun, 10th Jul 2011 8:51 PM
  4. This is a great application. It is one of those extremely niche programs that very few people would need. These king of things are usually very hard to find. I’m very glad I found this.

  5. Wayne on Sun, 7th Aug 2011 10:08 AM
  6. Hi Dave
    I’ve been looking for months to be able to change the default audio device programmatically. I was using sendkey commands to do the job but it could get interrupted.

    I’m new to C++ and I only got experience in VB.Net and VBscript.

    I downloaded your project and the program works like a dream, but I don’t really know how to do anything with code in the project, so I’ll ask you…
    When I change the default audio device, the already selected default audio device becomes default communication device, but if I change back the first audio device then I becomes default and no devices are communication device.

    Is there a way to change the same device to communication and default audio device?
    I can see if this is alot of work, and if it is then it doesn’t matter…

    Thx anyway=)

  7. WarNov on Mon, 26th Sep 2011 11:30 PM
  8. Tested it in Windows 8. Works perfectly.

  9. bmd on Mon, 17th Oct 2011 6:35 PM
  10. This is really helpful thanks!

  11. Stefan on Mon, 7th Nov 2011 7:20 AM
  12. Wow – this REALLY helped me with my “Homecontrol”-Project. Thank you!

    Maybe this helps someone else when trying to use the .NET-wrapper:
    My Windows 7 (x64) buffers stdout – so the output is always empty when using Process.Start(). Adding fflush(stdout) after the printf helps if you’s like to read the output in managed code.

  13. Chris on Thu, 29th Dec 2011 10:12 AM
  14. This is exactly what I was looking for and it works great! Would it be possible to expand this application to also be able to switch the communication device?

  15. Anthony Collins on Sun, 1st Jan 2012 11:15 PM
  16. Fantastic, just what I needed when I added my Microsoft Headset to my system. I just changed the hotkeys on my Microsoft keyboard to run your program with the right parameter.

    Well done – thanks for sharing!

  17. Will C on Tue, 31st Jan 2012 1:26 AM
  18. EXACTLY WHAT I NEEDED!!

  19. Dude on Thu, 16th Feb 2012 4:43 AM
  20. You rock!

  21. Devon Lehman on Wed, 28th Mar 2012 7:19 AM
  22. Awesome! I was trying to find something similar to powercfg that would let me change my primary sound driver from the command line! I currently have a quick-n-dirty Autohotkey script to open the Sound control panel window and change it, but this is a much better solution. Now I just need to figure out how to identify sound device IDs

  23. soof on Fri, 20th Apr 2012 4:09 AM
  24. perfect

  25. Dan Stevens on Fri, 20th Apr 2012 6:38 AM
  26. Hi Dave

    Thanks for writing this code letting us use your code under the terms of the MIT license. I’ve added the code to a GitHub repository: https://github.com/IAmAI/AudioEndPointController

    I intend to make some improvements to the code, which I hope you won’t mind.

  27. Dude on Sat, 21st Apr 2012 9:10 AM
  28. Hi Dave
    I wrapped some eye candy code around yours and gave you credit,
    http://heilnizar.deviantart.com/art/PlaybackDevice-Audio-Output-Switch-285591834
    I just wish it was possible to detect which device is currently active.

    Thanks

  29. James on Thu, 26th Apr 2012 5:36 AM
  30. This is awesome. Like others here, I have been searching for this for quite some time. I have two questions about possible improvements.

    1) could the code all us the option to select output device by name instead of ID# since the IDs can change as you add and remove other audio devices. The ideal would be if we could pass a string as a parameter and the code would check to see if the friendly device name .Contains(ourstring)

    2) Is there any chance that the same approach could be taken to allow us to select the default input (recording) device as well? That would be brilliant.

    thanks so much for sharing this with us!