Click here to Skip to main content
Click here to Skip to main content

Set Audio Endpoints System Wide in Vista/Windows 7

By , 7 Jan 2011
 

Introduction

This article shows how we can set Audio Endpoints on the same soundcard. While the example here has been specifically written for the onboard Realtek HD Audio chip ALC883, it gives an easy to adapt framework for CPL Applet manipulation. As such, it can be quickly changed to service any CPL Applet.

Update

The Executable has been updated. It now can be minimized to tray. The trayicon has a rightclick popupmenu to set endpoints. LeftClick on the trayicon restores the window. The endpointlist is updated every 60 seconds. The source is purely written for Codegear.

Background

A large part (and the basic idea) is based on AudioConfigManager. Since the writer goes into detail on the basics of it, I'll just present my adaptation for running it on Vista or Windows 7. Windows 7 has the ability to change Endpoints on the fly, which is a major improvement over Vista, and where this application will be the most useful. Still, with the addition of commandline switches, it can also do service with Vista by adding it to a batch file which starts up your audio application.

Using the Code

Whilst the writer showed how to use the Sound Applet to switch between different soundcards under XP, I made a small adaptation to change Audio Endpoints on the same card using Vista/Windows 7.

The basic principle is really simple; we call up the Applet and feed it the desired keystrokes by sending it messages using SendMessage. However, to be able to find out which Endpoint is currently active, and retrieve a list of Endpoints, it needs some inter-process communication.

I changed the function ChangeAudioDevice as found in the original to reflect this.

int ChangeAudioDevice(void)

becomes:

bool ChangeAudioDevice((int cmdSwitch,int Index)

The first parameter passes an int to use in a switch/case to decide which action to take; Index serves as an index of the chosen Audio Endpoint to set. So now we can direct our function to find the correct Endpoint by name, or by index as presented in the Sound Applet.

So look for the proper string or index, check if the Set Default Button is enabled. If it is, this means the device is not presently active, so feed it a message to press it.

So far it is easy going, but to get here, we need to do a whole lot of work to prepare the SoundApplet for interaction running on Vista. We first need to allocate inter-process memory space with the SoundApplet.

To do this, we need to be sure we have proper access rights to the SoundApplet's memory space.

The function returns true on error, false on success:

//find the listview
listView = handAcq->findChildWindow(appletWindow,LIST_VIEW,NULL);
//get the listview thread
GetWindowThreadProcessId(listView,&procID); 
//open the listview process
HANDLE lView=OpenProcess(PROCESS_ALL_ACCESS,false,procID);
if (lView)
{
    // Ensure you have sufficient privileges to the sound applet
    if ( !LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tokenLuid ) )
    {
        errorBox->showMessage("Insufficient Privileges");
        return true;
    }
    tokenPriv.PrivilegeCount = 1;
    tokenPriv.Privileges[0].Luid = tokenLuid;
    tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    tokenHandle=NULL;
    if (!OpenProcessToken(lView,TOKEN_ADJUST_PRIVILEGES,&tokenHandle))
    {
        errorBox->showMessage("Unable to get privilege level");
        return true;
    }
    // Set access token
    if ( !AdjustTokenPrivileges
        (tokenHandle,FALSE,&tokenPriv,sizeof(TOKEN_PRIVILEGES), 
        (PTOKEN_PRIVILEGES) NULL,  (PDWORD) NULL) )
    {
        errorBox->showMessage("Unable to set privilege level");
        return true;
    }

Having come here without errors, we can allocate the inter-process memory:

// Allocate virtual memory for ListView 
LVITEM *_lvi=(LVITEM*)VirtualAllocEx(lView, NULL, lvsize,MEM_COMMIT, PAGE_READWRITE);
// Allocate virtual memory for ListView Item String
char *_item=(char*)VirtualAllocEx(lView, NULL, MAXCHAR, MEM_COMMIT, PAGE_READWRITE);
if (!_item)
{
    errorBox->showMessage("Unable to allocate process memory");
    return true;
}
itemCount=SendMessage(listView,LVM_GETITEMCOUNT, NULL, NULL);
if (!itemCount)
{
    errorBox->showMessage("Change Audio Endpoint - Empty List");
    return true;
}

So now we have the memory, and we've read the Itemcount. Our loop can start:

for (int i=0;i<literal> < </literal>itemCount;i++)
{
    ZeroMemory(&lvi,lvsize);
    if (!i)
    {
        // set the first item in the listview as focused
        lvi.mask=LVIF_STATE;
        lvi.state=LVIS_FOCUSED | LVIS_SELECTED;
        lvi.stateMask=LVIS_FOCUSED | LVIS_SELECTED;
        WriteProcessMemory(lView, _lvi, &lvi, lvsize,NULL);
        SendMessage(listView, LVM_SETITEMSTATE, 
                (WPARAM)lvi.iItem, (LPARAM)_lvi);
        // mess with the keys about to activate the Set Default state
        // no idea why, but it works
        SendMessage(listView, WM_KEYDOWN, VK_DOWN, NULL);
        SendMessage(listView, WM_KEYUP, VK_UP, NULL);
        SendMessage(listView, WM_KEYDOWN, VK_DOWN, NULL);
        SendMessage(listView, WM_KEYUP, VK_UP, NULL);
         // reset the first item in the listview as focused
        lvi.mask=LVIF_STATE;
        lvi.state=LVIS_FOCUSED | LVIS_SELECTED;
        lvi.stateMask=LVIS_FOCUSED | LVIS_SELECTED;
        WriteProcessMemory(lView, _lvi, &lvi, lvsize,NULL);
        SendMessage(listView, LVM_SETITEMSTATE, 
                (WPARAM)lvi.iItem, (LPARAM)_lvi);
    }
    else 
    {
        // descend the list one by one
        SendMessage(listView, WM_KEYDOWN, VK_DOWN, NULL);
        SendMessage(listView, WM_KEYUP, VK_DOWN, NULL);
    }    
    // Commandline switches given?
    if (cmdSwitch>DEFAULT)
    {
        // Get ItemText
        ZeroMemory(&lvi,lvsize);
        lvi.iItem=i;
        lvi.cchTextMax=MAXCHAR;
        lvi.pszText=_item;
        WriteProcessMemory(lView, _lvi, &lvi,lvsize, NULL);
        SendMessage(listView,LVM_GETITEMTEXT, 
                (WPARAM)i, (LPARAM)_lvi);
        ReadProcessMemory(lView, _item, item, MAXCHAR, NULL);
        itemText=item;

To properly leave the loop from the switch statement a flag is used, bool breakFlag. If set, this will cause the loop to end. CString ItemText contains the item text retrieved from the SoundApplet.

switch (cmdSwitch)
{
     case SELECT:
        // Set Selected endpoint as default
        if (i==Index)
        {
            breakFlag=true;
            SendMessage(defaultButton, BM_CLICK, NULL, NULL);
        }
        break;
    case FILL_LIST:
        // check if it's the default endpoint
        if (SetDefaultEndpoint(defaultButton,false))
        strcat_s(item,MAXCHAR,SETDEFAULT);
        // Add ItemText to ListView
        lvEndpoint->Items->Add(item);   
        break;
    case ANALOG:
        if (itemText.Find(AUDIO_DEVICE_1,0)>=0)
        breakFlag=SetDefaultEndpoint(defaultButton,true);
        break;
    case DIGITAL:
        if (itemText.Find(AUDIO_DEVICE_2,0)>=0)
            breakFlag=SetDefaultEndpoint(defaultButton,true);
        break;
}

After this, we do our cleanup, release the memory, and close our handles. This is about the most important change to the original code; the rest of the project contains small changes which are self evident from the source.

Points of Interest

The single most important point of interest I discovered was that the official MSDN docs are written by people with PhDs in writing completely illegible material, surpassing advanced quantum physics in its use of incomprehensible language. Managed/Unmanaged code mixing using C++ is not my forte, so I had to look up a lot. My first thought was to inject a thread into the SoundApplet to manipulate the Applet, but after running afoul on the documentation, I switched to this method. It's quick and dirty, but it does the job.

History

  • 4th May, 2009: Initial version.
  • 14th May, 2009: Article updated.
  • 7th January, 2011: Downloads updated.

License

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

About the Author

Snoepie
Retired
France France
Member
Used to own a software development enterprise from the late 70's to the mid 90's, worked as systems analyst/programmer in that enterprise

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 1memberfaley27 May '11 - 18:36 
link invalid
GeneralRe: My vote of 1memberSnoepie9 Jun '11 - 7:52 
that's up to this board or your side. The links are not my doing. Giving a 1 is absurd.
GeneralRe: My vote of 1mvpOriginalGriff29 Jun '12 - 22:59 
Responding to your message:
"Here is the code, don't mock. Poke tongue | ;-P "
(I can't reply via the normal response as the potential article was closed yesterday, and the message list no longer exists).
 
So add the code to the article, add some explanation of how it works, and resubmit - it should get through this time. remember that the code and all images should be on Codeproject or the article breaks if the remote site is down for whatever reason.
Ideological Purity is no substitute for being able to stick your thumb down a pipe to stop the water

Generalnice postmemberPranay Rana17 Jan '11 - 0:53 
going to utilize you code
My blog:http://pranayamr.blogspot.com/
Programming practice

Do vote my articles December 2010
Best C# article of
Best ASP.NET article of

Best overall artic

GeneralRe: nice postmemberSnoepie17 Jan '11 - 8:20 
I'm honored.Thumbs Up | :thumbsup:
GeneralMy vote of 5membergndnet7 Jan '11 - 10:55 
interesting article, thanks
GeneralRe: My vote of 5memberSnoepie17 Jan '11 - 8:21 
It's a workaround, but it works Big Grin | :-D
QuestionMultiple Active Outputs ?memberkiczek1 Jan '11 - 2:01 
Is there a way to send sound out of my Speaker output and out of hdmi both at the same time ?
 
this program is such a great small piece of software if it ran as a tray application it would be even better!
AnswerRe: Multiple Active Outputs ?memberSnoepie1 Jan '11 - 5:55 
For the tray APP use a trayhandler: (this is for CodeGear)
 
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(TRAY_NOTIFYICON, tagMSG, IconHandler)
END_MESSAGE_MAP(TYourClass)
 
static bool NoTrayIcon;
 
int __fastcall TYourClass::IconHandler(tagMSG &Msg)
{
switch((int)Msg.wParam)
{
case WM_RBUTTONDOWN:
// do something, popupmenu
break;
case WM_LBUTTONDOWN:
Show(); //restore app window;
break;
default:
break;
}
return false;
}
 

void __fastcall TYourClass::SetTrayIcon(HICON anIcon,bool Update) //Update signals to update the current icon TIP message
{
NOTIFYICONDATA nid= {};
if (this->Visible) return; // don't update icon if mainwindow visible
if (!notrayicon)// static boolean toggle, signals delete icon after restoring the window
{
nid.hWnd = this->Handle;
nid.uVersion= NOTIFYICON_VERSION_4;
nid.uID=1;
Shell_NotifyIcon(NIM_DELETE, &nid);
NoTrayIcon=true;
return;
}
 
nid.hWnd = this->Handle;
nid.uVersion= NOTIFYICON_VERSION_4;
nid.uID=1;
if (Update)
nid.uFlags = NIF_TIP | NIF_MESSAGE;
else
nid.uFlags = NIF_ICON | NIF_MESSAGE;
 
nid.uCallbackMessage=TRAY_NOTIFYICON;
if (Update) {
AnsiString tip;
// create your TIP message here
strcpy(nid.szTip,tip.c_str());
} else
nid.hIcon= anIcon;
if (notrayicon) {
Shell_NotifyIcon(NIM_ADD, &nid);
NoTrayIcon=false;
}
else
Shell_NotifyIcon(NIM_MODIFY, &nid);
}
 

Call SetTrayIcon with an TIcon object you loaded into memory. Hide your main window. The NoTrayIcon flag is to signal delete icon.
 
Setting speakers and HDMI (or any multiple endpoint) is only possible on a per program basis. Basically the W7 endpoints are setup so that each app can control a separate endpoint itself programmatically. This little app is actually meant to circumvent that, so it can set the endpoints for all apps at once.
 
If your application has not this capacity built in the output will always go to the default endpoint. Of the top of my head i'd say it should be possible to build a shellprog which redirects the output to another endpoint but that's quite involved.
AnswerRe: Multiple Active Outputs ?memberSnoepie3 Jan '11 - 5:42 
I've added tray to the app. As soon as the site editors are back from as they say 'a much needed holiday' Sleepy | :zzz: it will appear here. Or you can dl the executable direct here: http://dl.dropbox.com/u/1828618/AudioEndpoints.rar
GeneralVERY GOOD~!membercaihaosheng01227 Aug '09 - 22:57 
VERY GOOD~!
GeneralRe: VERY GOOD~!memberSnoepie29 Aug '09 - 0:16 
glad it did the trick for you Smile | :) tnx
GeneralNgen error while complling on VS2008memberkarthiksharmasg24 Aug '09 - 23:25 
Running Ngen...
'"C:\Program Files\AudioConfigManager\Release\RunNgen.bat"' is not recognized as an internal or external command,
operable program or batch file.
Project : error PRJ0019: A tool returned an error code from "Running Ngen..."
 

 
the above error is being displayed while compiling on VS2008..
GeneralRe: Ngen error while complling on VS2008memberSnoepie25 Aug '09 - 0:21 
If you are using the demo source code, this is a straight copy from the original cited in the article with just a code fragment changed by me. It's for demonstration purposes only. All problems concerning its build should be addressed at the original writer.
 
I've converted the project to be no longer NET dependent.
 
Please use the converted source code which is a working sample, it's straight C++. Whilst the code given is written using Codegear, it's a minor translation to VS.
 
As this is fully written by myself i can give support for it.
 
I hope this answers your questions, don't hesitate to ask others.
GeneralRe: Ngen error while complling on VS2008memberkarthiksharmasg25 Aug '09 - 20:55 
Thanks for the replay,
I figured that out, then i decided to move out of .NET to c++, i download and installed CodeGear RAD studio with Delphi C++ builder, i loaded your project and tried to compile but it says some libraries and header file are missing. Any thing else to be added?
 
Missing lib:
lmdstorage10_d10.lib
lmd70_d10.lib
Missing include files:
[BCC32 Error] AudioMain.h(10): E2209 Unable to open include file 'LMDButton.hpp'
[BCC32 Error] AudioMain.h(11): E2209 Unable to open include file 'LMDCustomButton.hpp'
[BCC32 Error] AudioMain.h(12): E2209 Unable to open include file 'LMDCustomScrollBox.hpp'
[BCC32 Error] AudioMain.h(13): E2209 Unable to open include file 'LMDListBox.hpp'
[BCC32 Error] AudioMain.h(14): E2209 Unable to open include file 'LMDCustomComponent.hpp'
[BCC32 Error] AudioMain.h(15): E2209 Unable to open include file 'LMDStarter.hpp'
[BCC32 Error] AudioMain.h(16): E2209 Unable to open include file 'LMDStorBase.hpp'
[BCC32 Error] AudioMain.h(17): E2209 Unable to open include file 'LMDStorFormHook.hpp'
[BCC32 Error] AudioMain.h(18): E2209 Unable to open include file 'LMDStorPropertiesStorage.hpp'
[BCC32 Error] AudioMain.cpp(7): E2209 Unable to open include file 'UserDefines.h'
 
I am sorry if this is mistake from my part, but this is my first experience with Delphi or CodeGear. i searched my system for these files No use.
 
karthik
GeneralRe: Ngen error while complling on VS2008memberkarthiksharmasg25 Aug '09 - 21:56 
ok, i installed LMD2009 that solver the include and lib issues. but can you just put the contents of your "UserDefines.h"
GeneralRe: Ngen error while complling on VS2008 [modified]memberSnoepie25 Aug '09 - 23:07 
oops. Tnx for the info. Thats indeed not obvious from the readme. You can just replace the scroll listbox with the standard windows one. need a slight recoding of the item->add and such but nothing major.
 
userdefines:
 
#define NONE -1
#define MAXITEMCOUNT 50
#define WAIT_COUNT 1000
#define TARGET_SIZE 128
#define APPLET "mmsys.cpl"
#define BUTTON "Button"
#define LIST_VIEW "SysListView32"
 

enum DEVICE_COUNT
{
DEFAULT,
SELECT_LIST,
SELECT_CMD
};
enum APPLET_ENTRIES
{
APPLET_WINDOW_TITLE,
OK_BUTTON,
DEFAULT_BUTTON
};
 
modified on Wednesday, August 26, 2009 5:20 AM

GeneralRe: Ngen error while complling on VS2008memberSnoepie25 Aug '09 - 23:38 
tnx for your input, i've send an update to codeproject reflecting your observations. Sloppy work of me, but never guessed anyone would actually use it as is. Big Grin | :-D
GeneralRe: Ngen error while complling on VS2008memberkarthiksharmasg26 Aug '09 - 2:05 
actually i am trying to extend your work to the microphone also, so asked about all these things as soon as finished the work i ll post here.
 
thanks for the info.
Smile | :)
karthik,
GeneralRe: Ngen error while complling on VS2008memberSnoepie26 Aug '09 - 5:25 
Thumbs Up | :thumbsup: it's not hard you only have to add
//find the SysTabControl32
tabControl = handAcq->findChildWindow(appletWindow,SYS_TAB_CTRL,NULL);
 
and use tabControl to find the proper tab.
GeneralRe: Ngen error while complling on VS2008memberkarthiksharmasg27 Aug '09 - 1:04 
Hi, i ran into a problem here, in Vista for Microphone device instead of Friendly name display, the Microphone will be displayed, if i populate the devices all device name will be Microphone or Midi!!!
So do do know how to read the description of the device that is 2 line of that device like
 
Microphone
XXXXXDevice
working

if we read XXXXXDevice that will be easy.
 
Karthik
GeneralRe: Ngen error while complling on VS2008memberSnoepie27 Aug '09 - 1:31 
can you link me in a screenshot?
GeneralRe: Ngen error while complling on VS2008memberkarthiksharmasg27 Aug '09 - 2:19 
go to http://sites.google.com/site/karthiksharmasg/[^]
and see file "VISTA Audio End point.jpg"
 
Karthik
GeneralRe: Ngen error while complling on VS2008memberkarthiksharmasg27 Aug '09 - 3:12 
That got solved,
Its just a SubItem HAHAHAHA
 
Karthik
GeneralRe: Ngen error while complling on VS2008memberSnoepie27 Aug '09 - 4:51 
i saw the screenshot, wanted to give that answer but well you've got it.... Poke tongue | ;-P

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 7 Jan 2011
Article Copyright 2009 by Snoepie
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid