Saturday, July 30, 2011

Windows 7 Default Audio Device Changer

There are a number of posts on the internet regarding programmatically switching the default audio device in Windows Vista and Windows 7. I recently encountered this issues as well. I had purchased a USB headset.

The issue started from there. I did not want to have to plug and unplug the headset every time I wanted to use it, nor did I want to go to the Control Panel and manually change the default playback and recording devices.

Therefore, I started searching online to find a method. It appears that Microsoft does not support this in any way in Windows Vista and 7. I did discover that one of the program managers for Windows might pass this issue along to the developers for consideration, so we may not be out of luck in the next version.

The searches ended up revealing a number of methods to perform the change. I took a number of the suggestions, code snippets, helper DLLs, and came up with a solution of my own. All the source websites are credited in the list below and I am very grateful for their detailed groundwork.

The main points are as follows:
  • Enumerate a list of all rendering (output) and capture (input) audio devices in the system via the registry for easiest access
  • Use the unsupported Microsoft interface IPolicyConfig to actually make the switch
  • Develop a command-line application that can perform all the default audio device switching with a minimum of intervention and callable from AutoHotkey
This forum post on AutoHotkey is what got me started. However, as astutely observed by many others before me, Windows 7 did not support most of the non-UI methods that were described in this post. I first landed on Dave Amenta's site, which contained a full-functional application to do exactly what I wanted, minus the recording devices. Dave definitely has one of the best samples on the usage of IPolicyConfig I could find and I highly recommend his site for additional details.

I continued digging and eventually found an excellent set of references in this Microsoft forum post. In this post, I am deeply indebted to ghoster_e, albain, and EreTIk. albain got me started with his excellent reference on reading the writing the necessary registry entries to get and set the default audio device. However, this method did not notify any running applications that the default changed, necessitating restarting that application for it to recognize the change.

ghoster_e had some great information on the unsupported IPolicyConfig COM interface, along with some snippets of code. EreTIk also maintains a very useful website that includes this full C++ header file for inclusion in a C++ project.

This almost got me to where I needed to be. However, as I was writing this entire project in C#, I did not want to have to make the necessary COM conversions to use those IPolicyConfig interfaces. I searched some more and found Ray Molenkamp's CoreAudio project. This was missing the IPolicyConfig interfaces, but was almost feature-complete.

One more search landed me at MixerProNET; a full .NET-based library (CoreAudio.dll), with all the necessary functions to set the default audio device (both playback and recording) via the IPolicyConfig COM interface that sends out notifications to running applications notifying them of the change.

Once I had this, I was at last in position to finish off my project. I made the last tweaks, setup the necessary command-line parameters for the front-end command-line application and started testing. To my great and lasting enjoyment, all the tests worked. I now had the ability to setup AutoHotkey to trap any necessary keys and fire off my application with parameters indicating which devices I wanted to use as default.

I've included a reference of the application below and will be posting it in a few days.

AudioChanger.exe

Allows a command-line method of switching system default audio devices. Tested and found working on Windows 7 x64. Should also work on Windows 7 x86. In both cases, this program must be run as an administrator or with UAC disabled.

Parameters:

        /?                      Brings up this help message
        /list                   Display a list of currently enabled audio devices and the defaults
        /cro                    Returns the ID of the current default output audio device
        /cri                    Returns the ID of the current default input audio device
        /so {guid}              Sets the default output audio device based on a GUID
        /so devicename busname  Sets the default output audio device based on the device
                                name and device bus name (like Speakers, Creative SB X-Fi)
        /si {guid}              Sets the default input audio device based on a GUID
        /si devicename busname  Sets the default input audio device based on the device
                                name and device bus name (like Speakers, Creative SB X-Fi)
        /so devicename busname [devicename] [busname] ...       Switches between default output audio devices in the list of pairs; once it reaches the last one, it cycles back to the first item
        /si devicename busname [devicename] [busname] ...       Switches between default input audio devices in the list of pairs; once it reaches the last one, it cycles back to the first item



Credits
This project could not have been realized without the work of all those I've already mentioned in this post. However, to ensure that credit be given where credit is due, I've recapped all the people who've provided the groundwork (in no particular order):

14 comments:

  1. Great job, is your project open to share? if so, how could we get it?

    ReplyDelete
  2. Deliang,

    I'm glad you find this useful. I uploaded the source and the application to my GitHub repository, https://github.com/aifdsc/AudioChanger. Feel free to suggest any improvements that you can think of.

    Thanks!

    Stephan

    ReplyDelete
  3. Stephan,

    Thanks for sharing. It is compiled successfully. But when I run it, the following error is shown up:
    =======================================
    System.NullReferenceException: Object reference not set to an instance of an object.
    at AudioEndpointManager.getAvailableAudioDevices(String regKey, Boolean includeNonEnabledStates) in C:\ESysAudioChang
    er\AudioChanger\Audio.cs:line 152
    at AudioEndpointManager.RefreshAvailableAudioDevices() in C:\ESysAudioChanger\AudioChanger\Audio.cs:line 156
    at AudioEndpointManager..ctor() in C:\ESysAudioChanger\AudioChanger\Audio.cs:line 94
    at AudioChanger.Program.Main(String[] args) in C:\ESysAudioChanger\AudioChanger\Program.cs:line 75
    ========================================

    Do I need to do some presettings before using this utility?
    I saw:
    "SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render";
    "SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Capture";
    in your code, so do we need to do some registration settings first?

    thank you for help.

    Deliang

    ReplyDelete
  4. Deliang,

    Normally, you should not have to do anything else with the program, but one thing does come to mind. That registry setting that you pointed out is, by default, protected by Windows 7 and Windows Vista.

    If you're running with UAC turned on, that could be the source of the issue. I experienced best results with turning UAC off; otherwise this utility occasionally had issues with that protected registry key.

    One other thing that I had to do for this to work on Windows 8 Beta, was to go the Registry Editor, navigate to HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices, right-click on the MMDevices entry, click Permissions, and add the Administrators group with full control. That resolved any remaining permission issues with that registry key.

    **NOTE**: this does involve editing the registry, to please make sure to take any necessary precautions and backup beforehand :)

    Let me know if this doesn't work for you and provide any assistance that I can.

    Thanks!

    Stephan

    ReplyDelete
    Replies
    1. Stephan,

      I turned off UAC, but I run into the following issue when I try to set another input device as default one.

      Do you have any clue?

      thanks for help.

      -D.

      ==================================
      C:\AudioChanger\bin\debug\AnyCPU>AudioChanger.exe /si 762cf124-1deb-4443-bf18-dce1b0c2942a
      System.NullReferenceException: Object reference not set to an instance of an object.
      at AudioEndpointManager.setDefaultAudioDevice(String regKey, String deviceId, String deviceIdPrefix) in C:\AudioChanger\Audio.cs:line 188
      at AudioEndpointManager.SetDefaultInputDevice(String deviceId) in C:\AudioChanger\Audio.cs:line 197
      at AudioChanger.Program.Main(String[] args) in C:\AudioChanger\Program.cs:line 112

      C:\AudioChanger\bin\debug\AnyCPU>AudioChanger.exe /si {762cf124-1deb-4443-bf18-dce1b0c2942a}
      System.Security.SecurityException: Requested registry access is not allowed.
      at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
      at AudioEndpointManager.setDefaultAudioDevice(String regKey, String deviceId, String deviceIdPrefix) in C:\AudioChanger\Audio.cs:line 188
      at AudioEndpointManager.SetDefaultInputDevice(String deviceId) in C:\AudioChanger\Audio.cs:line 197
      at AudioChanger.Program.Main(String[] args) in C:\AudioChanger\Program.cs:line 112
      The Zone of the assembly that failed was:
      MyComputer

      Delete
  5. Deliang,

    My apologies for the delay in answering. I believe that this is caused by the insufficient registry permissions, so you may need to apply the second fix I mentioned in my last post on editing the registry. I wasn't able to find too much else on this when I first created the utility, and the registry edit seemed to be the only way to allow the tool to change those protected registry entries.

    If that still doesn't work, let me know and I'll see what else I can find.

    Cheers!

    Stephan

    ReplyDelete
  6. with uac turned off and the required access permissions this works great on win7 64 bit. However on win7 32 bit, even with UAC off and the registry permissions correctly set, the program generates an error, about a memory access violation.

    I tracked it down to these lines of code. After commenting them out, it still seems to work correctly. Not sure how or why :)

    public class CPolicyConfigClient
    {
    private IPolicyConfig _policyConfigClient = (new _CPolicyConfigClient() as IPolicyConfig);

    public int SetDefaultDevice(string deviceID)
    {
    //this._policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eConsole);
    //this._policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eMultimedia);
    //this._policyConfigClient.SetDefaultEndpoint(deviceID, ERole.eCommunications);
    return 0;
    }
    }

    ReplyDelete
  7. by the way, I forgot to mention, I am talking about when setting the default input device with /si

    ReplyDelete
  8. jitterjames: these are some very interesting findings. I confess I had not tried on a 32-bit setup, so I might be off-base here. However, I do recall running into an issue on 64-bit computers where I had to compile without the AnyCPU setting. I believe, in my reference research, that x86 (32-bit) versions of the IPolicyConfig code required compiling with x86 explicitly specified, rather than AnyCPU. Something about how .NET compiles causes issues when using that code unless the bitness is perfectly matched.

    If you're willing to try, I would be curious to know if uncommenting those lines you commented and recompiling with x86 instead of AnyCPU resolves the issue as well.

    Thanks!

    ReplyDelete
  9. sorry about the delay in responding. I did not know you had responded. I will look and see. thanks. I *think* I already tried that, but maybe not.

    ReplyDelete
  10. OK, I tried it and it makes no difference. Maybe what you are thinking about is this: compiling for x86 and then running on a 64 bit machine will not work, because it ends up getting the registry keys wrong (something to do with wow64). This is too bad because I have a program VoxCommando which I would like to add this code to. Because my program uses TTS I am forced to compile it for x86. That means that I am forced to call this as a commandline param.

    the weird thing is that it seems to set the device correctly. It shows up in the windows device selection window immediately, without having to close and reopen it.

    ReplyDelete
  11. aifdsc:

    I'm working on developing a script that does something very similar to yours, and I have just overcome most of the major hurdles to getting it working.

    In reading your code, it looks like the Role:# value has something to do with which device is the default...could you perhaps explain to me how you were able to determine which device was default in the registry, and how to change the device?

    Thanks,
    Overkill

    ReplyDelete
  12. Overkill,

    Certainly. It has been a bit, so I will review my notes to ensure that I'm giving you the correct information and I will post the answer here. It may be a day or two, but I'll be back in touch soon.

    Thanks!

    aifdsc

    ReplyDelete
    Replies
    1. Dear Stephan,
      I am trying to use your AudioChanger program to control my audio device on my Win 7 x64 PC.
      I have downloaded the following .exe file
      AudioChanger / AudioChanger / obj / x64 / Debug / AudioChanger.exe
      When I execute it I have the following message regardless of the /parameter I am using

      D:\Users\Regis\Downloads>audiochanger /?
      System.NullReferenceException: Object reference not set to an instance of an object.
      at AudioEndpoint.BytesToDate(Byte[] bytes)
      at AudioEndpoint.GetAudioInputDevices()
      at AudioChanger.Program.Main(String[] args)
      AMD High Definition Audio Device
      High Definition Audio Device
      High Definition Audio Device

      Default output device: Digital Audio (S/PDIF)
      Default input device:
      Press any key to continue . . .

      Could you let me what I am doing wrong?

      Thank you in advance

      RĂ©gis

      Delete