Click here to Skip to main content
15,861,172 members
Articles / Programming Languages / C++/CLI
Article

Changing your Windows audio device programmatically using VC++

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
23 Dec 2008CPOL6 min read 131.9K   2.9K   31   18
How to launch and manipulate applets programmatically.

Introduction

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.

Background

Before going further, a few things should be stated:

  1. Precisely because Microsoft did not want you to do this, this solution is by definition a kludge, a workaround.
  2. 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).
  3. 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.
  4. 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:

C++
#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:

C++
Process::Start("rundll32.exe","shell32.dll,Control_RunDLL mmsys.cpl");

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:

C++
Process::Start("rundll32.exe","shell32.dll,Control_RunDLL mmsys.cpl,,2");

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:

C++
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 findChildWindow():

C++
applyButton = findChildWindow(appletWindow,BUTTON,APPLY_BUTTON);

BUTTON is defined in ACMDefines.h as:

C++
#define BUTTON _T("Button")

and APPLY_BUTTON is defined as:

C++
#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:

C++
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:

C++
SendMessage(tabControl,WM_KEYDOWN,VK_CONTROL,0); //ctrl key down
SendMessage(tabControl,WM_KEYDOWN,VK_RIGHT,0); //right arrow key down
SendMessage(tabControl,WM_KEYUP,VK_RIGHT,0); //right arrow key up
SendMessage(tabControl,WM_KEYDOWN,VK_RIGHT,0); //right arrow key down
SendMessage(tabControl,WM_KEYUP,VK_RIGHT,0); //right arrow key up
SendMessage(tabControl,WM_KEYUP,VK_CONTROL,0); //ctrl key up

Once we have the audio tab showing, we look for the handle of the audio device selection we want (AUDIO_DEVICE_1):

C++
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:

C++
SendMessage(comboBox,CB_SELECTSTRING,(WPARAM)NONE,(LPARAM)&AUDIO_DEVICE_2);

Then, we message the "Apply" button to apply the change:

C++
SendMessage(applyButton,BM_CLICK,0,0); //apply the change

and close the applet window:

C++
SendMessage(appletWindow,WM_CLOSE,0,0); //close the applet

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:

C++
protected: bool launchApplication()
{
    HINSTANCE returnCode;
    returnCode = ShellExecute((HWND)errorBox->getWindowHandle(), //window
                "open", //verb - action
                "C:\\Program Files\\SomeProgram.exe", //program file specification
                "", //parameters (none)
                "C:\\Program Files", //directory where opened
                SW_SHOWMINNOACTIVE); //window minimized, no focus
    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.

History

  • 23 Dec 2008: Updated source code.

License

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


Written By
Software Developer (Senior)
United States United States
Brian Odlum is a retired software engineer who spent twenty years in the industry. He learned more than a dozen programming languages and worked with all of them in a variety of programming environments and operating systems.

He now considers himself a serious composer of computer music, dabbles in video game level development, and likes to spend at least three months of every year living in a foreign country.

Comments and Discussions

 
QuestionHas anyone tried this in VS 2017? Pin
Member 1511269325-Mar-21 5:04
Member 1511269325-Mar-21 5:04 
QuestionLaunching an applet (.cpl) from managed code Pin
Member 1369250322-Feb-18 23:11
Member 1369250322-Feb-18 23:11 
Question5.1 Rechanneling System Pin
Etara Cheong9-Aug-13 8:51
Etara Cheong9-Aug-13 8:51 
GeneralProblems Selecting "Sound Recording" Combobox Pin
53rg10026-Apr-10 8:40
53rg10026-Apr-10 8:40 
GeneralRef. difficulties in using Pin
FlavioAR12-Apr-10 14:25
FlavioAR12-Apr-10 14:25 
QuestionRef. difficulties in using Pin
FlavioAR12-Apr-10 14:04
FlavioAR12-Apr-10 14:04 
GeneralWindows 7 Pin
arunsankarm8-Dec-09 1:13
arunsankarm8-Dec-09 1:13 
GeneralSolution for vista Pin
ummarbhutta10-Sep-09 1:05
ummarbhutta10-Sep-09 1:05 
Generalinclude files Pin
opt1cs9821-Jun-09 16:01
opt1cs9821-Jun-09 16:01 
Answeradaptation Pin
Snoepie26-Apr-09 20:29
Snoepie26-Apr-09 20:29 
QuestionIs this an easier way to change the default audio device? Pin
Randor 17-Dec-08 9:30
professional Randor 17-Dec-08 9:30 
AnswerRe: Is this an easier way to change the default audio device? Pin
odlumb17-Dec-08 11:12
odlumb17-Dec-08 11:12 
AnswerRe: Is this an easier way to change the default audio device? Pin
Jim Crafton17-Dec-08 16:48
Jim Crafton17-Dec-08 16:48 
GeneralRe: Is this an easier way to change the default audio device? Pin
Randor 18-Dec-08 1:42
professional Randor 18-Dec-08 1:42 
AnswerRe: Is this an easier way to change the default audio device? Pin
odlumb18-Dec-08 5:34
odlumb18-Dec-08 5:34 
To reiterate - the value of my article is not really changing the default audio device. That was simply a vehicle for creating an example, a particular one that I know many people have searched for on the internet.

If you want to launch a control panel applet and manipulate it programatically, you will confront the problem of "how do I acquire the handles to all those controls?" Especially from managed code, this is not always obvious or easy. What my example does is offer an abstracted/generalized mechanism for acquiring those handles.

Everything below the method findChildWindow() can be used "as is" to acquire handles to any control in any windowed application. The programmer only needs to supply the type and label of the target control (gleaned from Spy++ or some other snooping program). To demonstrate the use of findChildWindow(), I choose the problem of changing the default audio device.

Upon review, I realize that the method findChildWindow() ought to be a method of class "CallbackFunctions", not of class "DeviceSwitcher". D'Oh! | :doh: It took me about one minute to make this change in the code. If I can figure out how to re-upload the code example, I will do so.

There are three kinds of people - those who can count, and those who can't!

JokeRe: Is this an easier way to change the default audio device? Pin
chaau15-Apr-09 18:15
chaau15-Apr-09 18:15 
Generalhmmm.... Pin
MrBobIsMean17-Dec-08 8:24
MrBobIsMean17-Dec-08 8:24 
AnswerRe: hmmm.... Pin
odlumb17-Dec-08 9:17
odlumb17-Dec-08 9:17 

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

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