#include-once #include ;ref: ; https://www.autoitscript.com/forum/topic/189854-detect-if-the-audio-output-is-changed/?do=findComment&comment=1363071 ; https://www.autoitscript.com/forum/topic/151474-looking-to-capture-immnotificationclientondevicestatechanged-events/#entry1084193 ; https://www.autoitscript.com/forum/topic/193161-get-audiomicrophone-devices-solved/?do=findComment&comment=1386327 ; https://www.autoitscript.com/forum/topic/196725-set-microphone-for-playback-device-not-a-problemsolved/ Global $g_aAudioSwitched[5] = [0, "", "", "", ""] Global $oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc") Func _ErrFunc($oError) ConsoleWrite("COM Error, ScriptLine(" & $oError.scriptline & ") : Number 0x" & Hex($oError.number, 8) & " - " & $oError.windescription & @CRLF) EndFunc ;==>_ErrFunc 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 $eConsole = 0 Global Const $eMultimedia = 1 Global Const $eCommunications = 2 Global Const $ERole_enum_count = 3 Cmdine_SwitchToDevice() Func Cmdine_SwitchToDevice() Local $n, $i For $n = 1 To $CmdLine[0] If StringInStr($CmdLine[$n], "}.{", 1) Then $i = _SwitchToDevice($CmdLine[$n]) If $i Then Exit 3 Else Exit 4 EndIf EndIf Next EndFunc ;==>Cmdine_SwitchToDevice Func _SwitchToDevice($sId) ; ObjCreateInterface: "Warning: This feature is experimental." Local Static $oPolicyConfig = ObjCreateInterface($sCLSID_CPolicyConfigClient, $sIID_IPolicyConfig, $tagIPolicyConfig) Local $hResult = $oPolicyConfig.SetDefaultEndpoint($sId, $eConsole) If $hResult = $S_OK Then $hResult = $oPolicyConfig.SetDefaultEndpoint($sId, $eCommunications) Return $hResult = $S_OK EndFunc ;==>_SwitchToDevice 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 $sPKEY_Audio_Endpoint_GUID = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E}, 4" Global Const $STGM_READ = 0 Global Const $DEVICE_STATE_ACTIVE = 0x00000001 Global Const $eRender = 0 Global Const $eCapture = 1 Global Const $eAll = 2 Global Const $EDataFlow_enum_count = 3 Func _EnumerateAudioDevices() Local $idDefRender = _GetDefaultEndpoint($eRender) Local $idDefCapture = _GetDefaultEndpoint($eCapture) ;~ Registry Key for Audio Devices Local $sRegAudio = @OSArch = "x64" ? "HKLM64\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio" : "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio" ;~ Create MMDeviceEnumerator object Local $oEnumerator = ObjCreateInterface($sCLSID_MMDeviceEnumerator, $sIID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator) ;~ Get collection out of it Local $pCollection $oEnumerator.EnumAudioEndpoints($eAll, $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[$iCount][5] 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[$i][2] = $sId $aArray[$i][4] = 0 If $sId = $idDefRender Or $sId = $idDefCapture Then $aArray[$i][4] = 1 ;~ Detect Instance ID is Microphone(capture) or Playback(render) $aGuid = StringSplit($sId, ".") $aArray[$i][1] = $aGuid[$aGuid[0]] $aArray[$i][0] = RegRead($sRegAudio & "\Capture\" & $aGuid[$aGuid[0]], "DeviceState") >= 1 ? "Microphone" : "" If $aArray[$i][0] = "" Then $aArray[$i][0] = RegRead($sRegAudio & "\Render\" & $aGuid[$aGuid[0]], "DeviceState") >= 1 ? "Playback" : "" ;~ 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[$i][3] = $sName Next Return $aArray EndFunc ;==>_EnumerateAudioDevices 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 _GetDefaultEndpoint($iType = $eRender) Local $pEndpoint, $oEndpoint Local $pID, $sId, $pProps, $oProps Local $tPKEY_Device_FriendlyName = _WinAPI_PKEYFromString($sPKEY_Device_FriendlyName) Local $oEnumerator = ObjCreateInterface($sCLSID_MMDeviceEnumerator, $sIID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator) $oEnumerator.GetDefaultAudioEndpoint($iType, $DEVICE_STATE_ACTIVE, $pEndpoint) $oEndpoint = ObjCreateInterface($pEndpoint, $IID_IMMDevice, $tagIMMDevice) $oEndpoint.GetId($pID) $sId = DllStructGetData(DllStructCreate("wchar ID[" & _WinAPI_PtrStringLenW($pID) & "]", $pID), "ID") _WinAPI_CoTaskMemFree($pID) Return $sId EndFunc ;==>_GetDefaultEndpoint Global Const $tagIMMNotificationClient = "OnDeviceStateChanged hresult(wstr;dword);" & _ "OnDeviceAdded hresult(wstr);" & _ "OnDeviceRemoved hresult(wstr);" & _ "OnDefaultDeviceChanged hresult(dword;dword;wstr);" & _ "OnPropertyValueChanged hresult(wstr;int64);" ; last param type is improvisation because AutoIt lacks proper type Func _IMMNotificationClient_OnDeviceStateChanged($hResult, $wstr, $dword) #forceref $hResult ;~ ConsoleWrite("_IMMNotificationClient_OnDeviceStateChanged" & @TAB & $wstr & @TAB & $dword & @CRLF) $g_aAudioSwitched[0] = TimerInit() $g_aAudioSwitched[1] = "OnDeviceStateChanged" $g_aAudioSwitched[2] = $wstr $g_aAudioSwitched[3] = $dword $g_aAudioSwitched[4] = "" Return 0 ; S_OK EndFunc ;==>_IMMNotificationClient_OnDeviceStateChanged Func _IMMNotificationClient_OnDeviceAdded($hResult, $wstr) #forceref $hResult ;~ ConsoleWrite("_IMMNotificationClient_OnDeviceAdded" & @TAB & $wstr & @CRLF) $g_aAudioSwitched[0] = TimerInit() $g_aAudioSwitched[1] = "OnDeviceAdded" $g_aAudioSwitched[2] = $wstr $g_aAudioSwitched[3] = "" $g_aAudioSwitched[4] = "" Return 0 ; S_OK EndFunc ;==>_IMMNotificationClient_OnDeviceAdded Func _IMMNotificationClient_OnDeviceRemoved($hResult, $wstr) #forceref $hResult ;~ ConsoleWrite("_IMMNotificationClient_OnDeviceRemoved" & @TAB & $wstr & @CRLF) $g_aAudioSwitched[0] = TimerInit() $g_aAudioSwitched[1] = "OnDeviceRemoved" $g_aAudioSwitched[2] = $wstr $g_aAudioSwitched[3] = "" $g_aAudioSwitched[4] = "" Return 0 ; S_OK EndFunc ;==>_IMMNotificationClient_OnDeviceRemoved Func _IMMNotificationClient_OnDefaultDeviceChanged($hResult, $dword1, $dword2, $wstr) #forceref $hResult ;~ ConsoleWrite("_IMMNotificationClient_OnDefaultDeviceChanged" & @TAB & $dword1 & @TAB & $dword2 & @TAB & $wstr & @CRLF) $g_aAudioSwitched[0] = TimerInit() $g_aAudioSwitched[1] = "OnDefaultDeviceChanged" $g_aAudioSwitched[2] = $dword1 $g_aAudioSwitched[3] = $dword2 $g_aAudioSwitched[4] = $wstr Return 0 ; S_OK EndFunc ;==>_IMMNotificationClient_OnDefaultDeviceChanged Func _IMMNotificationClient_OnPropertyValueChanged($hResult, $wstr, $int64) #forceref $hResult ;~ ConsoleWrite("_IMMNotificationClient_OnPropertyValueChanged" & @TAB & $wstr & @TAB & $int64 & @CRLF) $g_aAudioSwitched[0] = TimerInit() $g_aAudioSwitched[1] = "OnPropertyValueChanged" $g_aAudioSwitched[2] = $wstr $g_aAudioSwitched[3] = $int64 $g_aAudioSwitched[4] = "" Return 0 ; S_OK EndFunc ;==>_IMMNotificationClient_OnPropertyValueChanged Global $t_IMMNotificationClient Global $o_IMMNotificationClient = ObjectFromDtag("_IMMNotificationClient_", $tagIMMNotificationClient, $t_IMMNotificationClient) Global $p_IMMNotificationClient = $o_IMMNotificationClient() Func ObjectFromDtag($sFunctionPrefix, $tagInterface, ByRef $tInterface) Local Const $tagIUnknown = "QueryInterface hresult(ptr;ptr*);" & _ "AddRef dword();" & _ "Release dword();" ; Adding IUnknown methods $tagInterface = $tagIUnknown & $tagInterface Local Const $PTR_SIZE = DllStructGetSize(DllStructCreate("ptr")) ; Below line really simple even though it looks super complex. It's just written weird to fit one line, not to steal your eyes Local $aMethods = StringSplit(StringReplace(StringReplace(StringReplace(StringReplace(StringTrimRight(StringReplace(StringRegExpReplace($tagInterface, "\h*(\w+)\h*(\w+\*?)\h*(\((.*?)\))\h*(;|;*\z)", "$1\|$2;$4" & @LF), ";" & @LF, @LF), 1), "object", "idispatch"), "variant*", "ptr"), "hresult", "long"), "bstr", "ptr"), @LF, 3) Local $iUbound = UBound($aMethods) Local $sMethod, $aSplit, $sNamePart, $aTagPart, $sTagPart, $sRet, $sParams ; Allocation. Read http://msdn.microsoft.com/en-us/library/ms810466.aspx to see why like this (object + methods): $tInterface = DllStructCreate("ptr[" & $iUbound + 1 & "]") If @error Then Return SetError(1, 0, 0) For $i = 0 To $iUbound - 1 $aSplit = StringSplit($aMethods[$i], "|", 2) If UBound($aSplit) <> 2 Then ReDim $aSplit[2] $sNamePart = $aSplit[0] $sTagPart = $aSplit[1] $sMethod = $sFunctionPrefix & $sNamePart $aTagPart = StringSplit($sTagPart, ";", 2) $sRet = $aTagPart[0] $sParams = StringReplace($sTagPart, $sRet, "", 1) $sParams = "ptr" & $sParams DllStructSetData($tInterface, 1, DllCallbackGetPtr(DllCallbackRegister($sMethod, $sRet, $sParams)), $i + 2) ; Freeing is left to AutoIt. Next DllStructSetData($tInterface, 1, DllStructGetPtr($tInterface) + $PTR_SIZE) ; Interface method pointers are actually pointer size away Return ObjCreateInterface(DllStructGetPtr($tInterface), "", $tagInterface, False) ; and first pointer is object pointer that's wrapped EndFunc ;==>ObjectFromDtag Global $o_MMDeviceEnumerator = ObjCreateInterface($sCLSID_MMDeviceEnumerator, $sIID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator) $o_MMDeviceEnumerator.RegisterEndpointNotificationCallback($p_IMMNotificationClient) OnAutoItExitRegister("_UnregisterEndpointNotificationCallback") Func _UnregisterEndpointNotificationCallback() $o_MMDeviceEnumerator.UnregisterEndpointNotificationCallback($p_IMMNotificationClient) EndFunc ;==>_UnregisterEndpointNotificationCallback