Have you ever wanted to switch the active audio output device (sound playback) on your Windows computer programmatically? Have you noticed that Microsoft (by design) has made it almost impossible to do this? Well, here is a solution to that problem. This is a Managed C++ solution (.NET), but the method used here can be adapted easily to other languages.
Before going further, a few things should be stated:
- Precisely because Microsoft did not want you to do this, this solution is by definition a kludge, a workaround.
- Any third party application which outputs to the sound playback audio device, such as the Windows Media Player, has to be re-launched after the audio device has changed, because they usually consult the Registry and configure themselves only once at application launch time. Your own applications don't have to mimic this limitation (wink).
- I have only tested this on XP. I'm not aware of any reason why the solution will not work under Vista, but verifying the solution's operability under Vista is left as an exercise for the reader.
- Because we are switching between audio devices, and the actual audio devices available on your machine are almost certainly different from my machine, you must supply a bit of machine dependent information to get the solution to work on your own computer.
OK, having those four caveats out of the way, here is the general idea. We programmatically launch the "Sounds and Audio Devices Properties" applet and manipulates the panel using the messaging system. The panel actually appears on the screen briefly, but that's not a problem. Changing the audio device this way is better (in my opinion) than directly modifying the Registry, which is always error prone because it's never exactly clear which Registry keys are involved.
This general method can, of course, be extended to manipulate any applet or application, it's not limited to just the "Sound and Audio Devices Properties" applet. The solution is small enough that its adaptation to other applets (.cpl) and applications (.exe) should be obvious.
Using the Code
Let's get started. The first thing you need to do is determine which two audio devices you want to switch between. Launch the "Sounds and Audio Devices Properties" applet from the Control Panel, go to the "Audio" tab, and click on the "Sound playback" combobox. A drop down list appears showing the audio output devices available on your machine for sound playback. Write down the two devices you desire to manipulate (exactly, all capitalization and punctuation must be accurate).
On my machine, I selected "Realtek HD Audio output" and "CreamWare Play/Rec 1".
At the top of the file ACMDefines.h, replace my selections with your own:
#define AUDIO_DEVICE_1 _T("Realtek HD Audio output")
#define AUDIO_DEVICE_2 _T("CreamWare Play/Rec 1")
Now, build the solution, and it should work on your machine.
OK, let's explain what's going on.
Launching an applet (.cpl) from managed code is easy enough, it's a one-liner:
The applet "mmsys.cpl" is located in the system32 directory with all the other applets found in the Control Panel, nothing surprising there. I deliberately did not launch the applet showing the "Audio" tab immediately, in order to demonstrate in the example code how we can select tabs programmatically. But, we could change the above line to:
The result would be that the applet window would appear with the "Audio" tab already selected, so the process of selecting the desired tab would not have to be done in the code. But, if you desire to utilize my solution as a jig for adapting it to your own specific needs, it's better to show all the steps involved.
Once the applet has successfully launched, we obtain a handle to the applet window:
appletWindow = FindWindow(NULL,APPLET_WINDOW_TITLE);
From there, it's simply a question of sending messages to the applet emulating the keyboard activity. The concept is simple enough. The difficulty is getting the handles to the various controls in the applet window. First, we need to snoop around in the applet window with Spy++ (inside Visual Studio), to discover the types and labels of the controls we wish to manipulate. So, launch the applet, open Spy++, and look for the applet window. You should see something like the following:
Window xxxxxxxx "Sounds and Audio Devices Properties" #xxxxx (Dialog)
Click on the "+" to the left of this entry to expand the list of child windows. One of the child entries is 'Window xxxxxxxx "&Apply" Button'. There are two important pieces of information here. One, it's a control of type
BUTTON. Two, the button's text (or label) is "&Apply" (note the ampersand which doesn't actually appear in the GUI).
In the example code, we obtain the handle to this control (and all subsequent controls) with a call to
applyButton = findChildWindow(appletWindow,BUTTON,APPLY_BUTTON);
BUTTON is defined in ACMDefines.h as:
#define BUTTON _T("Button")
APPLY_BUTTON is defined as:
#define APPLY_BUTTON _T("&Apply")
OK, so now we have the handle to the "Apply" button. The next thing we need to do is select he "Audio" tab. In Spy++, you will see an entry like:
Window xxxxxxxx '"' SysTabControl32
The type is
SysTabControl, but it has no label. The fact that it has no label isn't a problem, because there is only one control of type "
SysTabControl32" in the applet window. We obtain the handle to this control the same way we obtained the handle to the "Apply" button:
tabControl = findChildWindow(appletWindow,SYS_TAB_CTRL,NULL);
Once we have the handle, we emulate the keyboard activity (ctrl-up/down/right/left) to select the "Audio" tab:
SendMessage(tabControl,WM_KEYDOWN,VK_CONTROL,0); SendMessage(tabControl,WM_KEYDOWN,VK_RIGHT,0); SendMessage(tabControl,WM_KEYUP,VK_RIGHT,0); SendMessage(tabControl,WM_KEYDOWN,VK_RIGHT,0); SendMessage(tabControl,WM_KEYUP,VK_RIGHT,0); SendMessage(tabControl,WM_KEYUP,VK_CONTROL,0);
Once we have the audio tab showing, we look for the handle of the audio device selection we want (
comboBox = findChildWindow(appletWindow,COMBO_BOX,AUDIO_DEVICE_1);
If the handle isn't found, we look for the handle to
AUDIO_DEVICE_2. An assumption is made in the example code that one of these two audio devices will always be selected, and hence will always be the selected item in the combo box. If your machine has more than two audio devices, and neither of the two devices you have selected is the default selected item, then you must first look for the handle to the default selected item and from there select one of the two devices of interest. The example code doesn't do this, it assumes one of the two devices is already selected.
Once we have the handle to the currently selected item in the combobox, we simply send it a message to change to the new audio device:
Then, we message the "Apply" button to apply the change:
and close the applet window:
Points of Interest
If you wish to launch a regular application (.exe) and manipulate it using emulated keystrokes, there exists several methods to do this. Here is just one possibility:
protected: bool launchApplication()
returnCode = ShellExecute((HWND)errorBox->getWindowHandle(), "open", "C:\\Program Files\\SomeProgram.exe", "", "C:\\Program Files", SW_SHOWMINNOACTIVE); return((returnCode > (HINSTANCE)32 && returnCode !=
(HINSTANCE SE_ERR_NOASSOC) ? true : false);
Only the launching method is different for .exe files, everything else is the same as for .cpl files.
As you can see, the concept is extremely simple. The value of this coding example is the compact and generic method it demonstrates for acquiring the handles to the various controls in the applet from Managed C++.
Clearly, this example is specific to a particular task and applet. For that reason, I have not tried to reduce everything to its most abstract state, but rather just presented a clear example of a method which can be used as a guide to design your own specific solutions to similar problems.
- 23 Dec 2008: Updated source code.