Jump to content

SSD - Set Sound Device [Updated 2017-Sep-16]


KaFu
 Share

Recommended Posts

Of course I had that bookmarked :)...

ObjCreaeInterface is almost finished.

If you want to impress that girl from the grocery store you do something like this:

#AutoIt3Wrapper_Version=beta
#AutoIt3Wrapper_AU3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6

#NoTrayIcon

Opt("TrayMenuMode", 1)

#include <WinAPI.au3>
#include <Constants.au3>

Global Const $sCLSID_MMDeviceEnumerator = "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
Global Const $sIID_IMMDeviceEnumerator = "{A95664D2-9614-4F35-A746-DE8DB63617E6}"

Global Const $tagIMMDeviceEnumerator = "EnumAudioEndpoints hresult(dword;dword;ptr*);" & _
"GetDefaultAudioEndpoint hresult(dword;dword;ptr*);" & _
"GetDevice hresult(wstr;ptr*);" & _
"RegisterEndpointNotificationCallback hresult(ptr);" & _
"UnregisterEndpointNotificationCallback hresult(ptr);"

Global Const $sCLSID_CPolicyConfigClient = "{870af99c-171d-4f9e-af0d-e63df40c2bc9}"
Global Const $sIID_IPolicyConfig = "{f8679f50-850a-41cf-9c72-430f290290c8}"

Global Const $tagIPolicyConfig = "GetMixFormat hresult(wstr;ptr*);" & _
"GetDeviceFormat hresult(wstr;int;ptr*);" & _
"ResetDeviceFormat hresult(wstr);" & _
"SetDeviceFormat hresult(wstr;ptr;ptr);" & _
"GetProcessingPeriod hresult(wstr;int;int64*;int64*);" & _
"SetProcessingPeriod hresult(wstr;int64*);" & _
"GetShareMode hresult(wstr;ptr);" & _
"SetShareMode hresult(wstr;ptr);" & _
"GetPropertyValue hresult(wstr;struct;variant*);" & _
"SetPropertyValue hresult(wstr;struct;variant*);" & _
"SetDefaultEndpoint hresult(wstr;int);" & _
"SetEndpointVisibility hresult(wstr;int);"

Global Const $IID_IMMDevice = "{D666063F-1587-4E43-81F1-B948E807363F}"
Global Const $tagIMMDevice = "Activate hresult(ptr;dword;variant*;ptr*);" & _
"OpenPropertyStore hresult(dword;ptr*);" & _
"GetId hresult(ptr*);" & _
"GetState hresult(dword*);"

Global Const $IID_IMMDeviceCollection = "{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}"
Global Const $tagIMMDeviceCollection = "GetCount hresult(uint*);" & _
"Item hresult(uint;ptr*)"

Global Const $IID_IPropertyStore = "{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}"
Global Const $tagIPropertyStore = "GetCount hresult(dword*);" & _
"GetAt hresult(dword;ptr*);" & _
"GetValue hresult(ptr;variant*);" & _
"SetValue hresult(ptr;variant);" & _
"Commit hresult();"

Global Const $sPKEY_Device_FriendlyName = "{a45c254e-df1c-4efd-8020-67d146a850e0} 14"

Global Const $STGM_READ = 0
Global Const $DEVICE_STATE_ACTIVE = 0x00000001

Global Const $S_OK = 0
Global Const $E_INVALIDARG = 0x80070057

Global Const $eRender = 0
Global Const $eCapture = 1
Global Const $eAll = 2
Global Const $EDataFlow_enum_count = 3

Global Const $eConsole = 0
Global Const $eMultimedia = 1
Global Const $eCommunications = 2
Global Const $ERole_enum_count = 3
;==========================================================

If @OSVersion <> "WIN_7" Then Exit ; God knows what $tagIPolicyConfig is for other systems

; First generae array filled wih devices data
Global $aArray = _EnumerateDevices()

; Then create UI
For $i = 0 To UBound($aArray, 2) - 2
$aArray[2][$i] = TrayCreateItem($aArray[1][$i])
Next

TrayCreateItem("")
Global $iExit = TrayCreateItem("Exit")
TrayItemSetState($iExit, $TRAY_DEFAULT)


TraySetState()
Global $iMsg

While 1
$iMsg = TrayGetMsg()
Switch $iMsg
Case $iExit
Exit
Case Else
If $iMsg > 0 Then
For $i = 0 To UBound($aArray, 2) - 1
TrayItemSetState($aArray[2][$i], $TRAY_UNCHECKED)
If $iMsg = $aArray[2][$i] Then
ConsoleWrite("Switching to: " & $aArray[1][$i] & @CRLF)
_SwitchToDevice($aArray[0][$i])
TrayItemSetState($iMsg, $TRAY_CHECKED)
EndIf
Next
EndIf
EndSwitch
WEnd

; THE END



Func _EnumerateDevices()
; Error monitoring
Local $oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")
    #forceref $oErrorHandler

    ; Creae MMDeviceEnumerator object
Local $oEnumerator = ObjCreateInterface($sCLSID_MMDeviceEnumerator, $sIID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator)

    ; Get collection out of it
Local $pCollection
$oEnumerator.EnumAudioEndpoints($eRender, $DEVICE_STATE_ACTIVE, $pCollection)
    ; Turn it into object
Local $oCollection = ObjCreateInterface($pCollection, $IID_IMMDeviceCollection, $tagIMMDeviceCollection)

    ; Check the number of devices in collection
Local $iCount
$oCollection.GetCount($iCount)

; Array for out
Local $aArray[3][$iCount + 1]

Local $pEndpoint, $oEndpoint
Local $pID, $sId, $pProps, $oProps
Local $sName
Local $tPKEY_Device_FriendlyName = _WinAPI_PKEYFromString($sPKEY_Device_FriendlyName)

    ; Actual enumeration
For $i = 0 To $iCount - 1
; Item at "i" index is audio endpoint device
$oCollection.Item($i, $pEndpoint)
$oEndpoint = ObjCreateInterface($pEndpoint, $IID_IMMDevice, $tagIMMDevice)

; Collect ID
$oEndpoint.GetId($pID)
$sId = DllStructGetData(DllStructCreate("wchar ID[" & _WinAPI_PtrStringLenW($pID) & "]", $pID), "ID")
_WinAPI_CoTaskMemFree($pID)
$aArray[0][$i] = $sId

; PropertyStore stores properties, lol
$oEndpoint.OpenPropertyStore($STGM_READ, $pProps)
$oProps = ObjCreateInterface($pProps, $IID_IPropertyStore, $tagIPropertyStore)

; Collect FriendlyName property
$sName = 0
$oProps.GetValue(DllStructGetPtr($tPKEY_Device_FriendlyName), $sName)
$aArray[1][$i] = $sName
Next

Return $aArray
EndFunc ;==>_EnumerateDevices

Func _SwitchToDevice($sId)
Local $oPolicyConfig = ObjCreateInterface($sCLSID_CPolicyConfigClient, $sIID_IPolicyConfig, $tagIPolicyConfig)

Local $hResult
$hResult = $oPolicyConfig.SetDefaultEndpoint($sId, $eConsole)
If $hResult = $S_OK Then $hResult = $oPolicyConfig.SetDefaultEndpoint($sId, $eCommunications)

Return $hResult = $S_OK
EndFunc ;==>_SwitchToDevice


Func _WinAPI_PKEYFromString($sPKEY, $pID = Default)
Local $tPKEY = DllStructCreate("byte GUID[16]; dword PID;")
DllCall("propsys.dll", "long", "PSPropertyKeyFromString", "wstr", $sPKEY, "ptr", DllStructGetPtr($tPKEY))
If @error Then ; alternative for unsupported systems
Local $tGUID = _WinAPI_GUIDFromString($sPKEY)
If Not @error Then
DllStructSetData($tPKEY, "GUID", DllStructGetData(DllStructCreate("byte Data[16]", DllStructGetPtr($tGUID)), 1))
EndIf
DllStructSetData($tPKEY, "PID", Number(StringRegExpReplace($sPKEY, ".*?}", "")))
EndIf
If $pID <> Default Then DllStructSetData($tPKEY, "PID", $pID)
Return $tPKEY
EndFunc ;==>_WinAPI_PKEYFromString

Func _WinAPI_PtrStringLenW($pString)
Local $aCall = DllCall("kernel32.dll", "dword", "lstrlenW", "ptr", $pString)
If @error Then Return SetError(1, 0, 0)
Return $aCall[0]
EndFunc ;==>_WinAPI_PtrStringLenW

Func _WinAPI_CoTaskMemFree($pMem)
DllCall("ole32.dll", "none", "CoTaskMemFree", "ptr", $pMem)
If @error Then Return SetError(1, 0, False)
Return True
EndFunc ;==>_WinAPI_CoTaskMemFree

Func _ErrFunc($oError)
ConsoleWrite("COM Error, ScriptLine(" & $oError.scriptline & ") : Number 0x" & Hex($oError.number, 8) & " - " & $oError.windescription & @CRLF)
EndFunc ;==>_ErrFunc

...Then you could explain to her that code tags here tend to ruin the formatting of your code in case she decides you are sponge worthy, to put it that way.

edit: It's in the tray

Edited by KaFu
Link to comment
Share on other sites

This is a fantastic utility, especially since it's written in my favorite scripting language. Thank you!!!

I was curious why you didn't hide the Control Panel when changing the audio device.. then, after I tried to add code to hide it, I realized that it's practically impossible to hide it. :)

I found other posts where other people tried to do the same thing but couldn't completely hide the Windows Control panel gui. If anyone is able to figure out a good way to do this, please let me know.

Glad you like it :). I saw your comment on my homepage (but as I'm on holiday :bye: I hadn't the time to approve it yet). See the two posts before this, trancexx will for sure come up with a direct COM solution.

Additionally I have some more snippets flying around, was thinking about releasing a v2 sometimes in the future. My current Beta opens the Control Panel on a separate Desktop (see CreateDesktop) and thus is invisible to the user (imho also a great general concept to automate applications "invisible"!). Also I wanted to add option to differentiate between sound, communication and recording devices... well, as SMF just had a new release, maybe I'll give SSD v2 a try :)...

Edit: Just saw that I've posted an experimental v1.2 using the CreateDesktop function in the MVP section, here's the Beta for open testing:

[see two posts below]

Edited by KaFu
Link to comment
Share on other sites

  • 4 weeks later...

So trancexx's code works really well (no offense KaFu!), does anyone know how to *Get* the current default Audio Endpoint using the IPolicyConfig code?

Link to comment
Share on other sites

^^ IMMDeviceEnumerator has GetDefaultAudioEndpoint method. You do something like this:

$oEnumerator.GetDefaultAudioEndpoint($eConsole, $eRender, $pEndpoint)
;...

...Presumably you want to mark checked the tray item with default endpoint in advance. Just compare IDs and check the one that matches.

edit: The code works correctly on Windows 8 too.

Edited by trancexx

♡♡♡

.

eMyvnE

Link to comment
Share on other sites

Thank you trancexx! I just found that code as well, I'll give it a try. And yes, that's what I plan to do, but I was thinking I'd also customize the tray icon to match the device icon; might be a neat effect.

Link to comment
Share on other sites

So I got all of that working (I can post my code later if anyone is interested... and by "my" code I mean trancexx's code with some minor alterations of my own), and then I got to thinking: "It'd be cool to take this a step further and have the script automatically update the list of audio endpoints when something is removed/added/etc."

Trancexx, if you don't mind another question, do you know how to setup autoit to capture the OnDeviceStateChanged? I've read through the msdn info, as well as an example they have, but it's frankly over my head.

I can use GUIRegisterMsg($WM_DEVICECHANGE, 'WM_DEVICECHANGE') to detect when a change occurs, which works, but I've had difficulty getting it filtered down to just what I want, and I'm loathe to have the script firing over and over for stuff it shouldn't care about. What do you think?

Link to comment
Share on other sites

Well, nevermind for now; I ended up just using the WM_DEVICECHANGE and watched for changes in the count of the following device states:

$DEVICE_STATE_ACTIVE

$DEVICE_STATE_DISABLED
$DEVICE_STATE_NOTPRESENT
$DEVICE_STATE_UNPLUGGED

Strange/frustrating part is that a bluetooth speaker connecting/disconnecting is not picked up by the GUIRegisterMsg($WM_DEVICECHANGE...). Back to searching...

Link to comment
Share on other sites

  • 4 months later...
Link to comment
Share on other sites

  • 1 year later...

2015-Sep-17, Changelog v1.3 > v2

  • Win10 compatibility
  • Change recording devices
  • Change audio or communication device separately
  • Merge two separate output and recording device shortcuts
  • New GUI layout
  • and many bugfixes

Source and Executable are available at http://www.funk.eu

Best Regards
Updated first Post...

Enjoy :)...

Link to comment
Share on other sites

2016-Aug-30, Changelog v2 > v3

  • added Win 10 anniversary update compatibility and removes ini-file save to AppDataDir, portable install to program directory only.

Source and Executable are available at http://www.funk.eu

Best Regards
Updated first Post...

Enjoy :)...

Link to comment
Share on other sites

Thanks for this.  I used to use a widget called Win7AudioSwitcher and it was nice but I lost it when I moved to Win10.

I just now usually go into the control panel and switch up stuff, but I'll give this a shot.

3x Sound Cards, a USB DAC, a Mixer, and a AV Receiver so I have a ton of audio devices and depending if I am doing voice over work, playing instruments, or just watching youtube I use a different device for them all....

Edited by ViciousXUSMC
Link to comment
Share on other sites

  • 2 months later...
Link to comment
Share on other sites

2017-Sep-16, Changelog v3 > v4

  • Ported to Autoit 3.3.14.2
  • Minor improvements and bug fixes
  • Identified Win10-1703 issue, after the Update Windows sometimes "cleans" the entries in the sound control panel, initially blocking the "Sound" dialog until manually confirmed. Opening the "Sound" control panel manually one time fixes this topic.

Source and Executable are available at http://www.funk.eu

Best Regards
Updated first Post...

Enjoy :)...

Edited by KaFu
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...