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

Set Audio Endpoints System Wide in Vista/Windows 7

, 7 Jan 2011
Rate this:
Please Sign up or sign in to vote.
An article on how to set system wide audio endpoints programmatically.

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
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

Comments and Discussions

 
GeneralMy vote of 1 Pinmemberfaley27-May-11 18:36 
GeneralRe: My vote of 1 PinmemberSnoepie9-Jun-11 7:52 
GeneralRe: My vote of 1 PinmvpOriginalGriff29-Jun-12 22:59 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 7 Jan 2011
Article Copyright 2009 by Snoepie
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid