Advertisement
pk3456

Change audio device depending on active monitor

Nov 16th, 2022 (edited)
237
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #Persistent
  2.  
  3. ; change this
  4. ; ===============================================================
  5. ; monitor number of PC
  6. global PC_Monitor := 1
  7. ; audio device name of PC
  8. global PC_Audio := "Headphones (F16-R)"
  9.  
  10. ; monitor number of TV
  11. global TV_Monitor := 2
  12. ; audio device name of TV
  13. global TV_Audio := "Speakers (Realtek High Definition Audio)"
  14. ; ===============================================================
  15.  
  16. ; set up a winevent hook to watch for window change events to window move events
  17. ActiveWindowAudioChange() {
  18.     static hook := DllCall("SetWinEventHook"
  19.                             , "UInt", 0x0003 ; EVENT_SYSTEM_FOREGROUND
  20.                             , "UInt", 0x000B ; EVENT_SYSTEM_MOVESIZEEND
  21.                             , "Ptr", 0
  22.                             , "Ptr", RegisterCallback("ActiveWindowAudioChangeCB", "F")
  23.                             , "Int", 0
  24.                             , "Int", 0
  25.                             , "Int", 0x2)
  26. }
  27.  
  28. Return
  29. ; End of Auto-Execute section
  30.  
  31. ; Press F1 to check audio device names
  32. F1::
  33.     devices := Audio_EnumEndpoints()
  34.     out := "Devices:`n"
  35.     for DeviceName, DeviceID in devices {
  36.         out .= "[" A_Index "] " DeviceName "`n"
  37.     }
  38.     msgbox % out
  39. Return
  40.  
  41. ActiveWindowAudioChangeCB(hWinEventHook, event, hwnd, idObject, idChild) {
  42.     static CurrentAudioDevice := "" ; remember current audio device
  43.     if (idObject|idChild != 0)
  44.         return
  45.  
  46.     MonitorNumber := GetMonitorNumber()
  47.  
  48.     if !Audio_GetProgramPeakVolume(hwnd)
  49.         return
  50.  
  51.     ; change to PC_Audio if window is active in PC_Monitor
  52.     if (MonitorNumber = PC_Monitor && CurrentAudioDevice != PC_Audio) {
  53.         DevID := Audio_GetDeviceID(Audio_EnumEndpoints(), PC_Audio)
  54.         Audio_SetDefaultEndpoint(DevID)
  55.         CurrentAudioDevice := PC_Audio
  56.     }
  57.  
  58.     ; change to TV_Audio if window is active in TV_Monitor
  59.     if (MonitorNumber = TV_Monitor && CurrentAudioDevice != TV_Audio) {
  60.         DevID := Audio_GetDeviceID(Audio_EnumEndpoints(), TV_Audio)
  61.         Audio_SetDefaultEndpoint(DevID)
  62.         CurrentAudioDevice := TV_Audio
  63.     }
  64. }
  65.  
  66. ; some code borrowed from here: https://www.autohotkey.com/boards/viewtopic.php?p=46349#p46349
  67. GetMonitorNumber() {
  68.     static MONITOR_DEFAULTTONEAREST := 0x00000002
  69.     hMonitor := DllCall("User32\MonitorFromWindow", "Ptr", WinExist("A"), "UInt", MONITOR_DEFAULTTONEAREST)
  70.     NumPut(VarSetCapacity(MONITORINFOEX, 40 + (32 << !!A_IsUnicode)), MONITORINFOEX, 0, "uint")
  71.     if (DllCall("user32\GetMonitorInfo", "ptr", hMonitor, "ptr", &MONITORINFOEX))
  72.     {
  73.         MonitorName     := StrGet(&MONITORINFOEX + 40, 32)
  74.         MonitorNumber   := RegExReplace(MonitorName, ".*(\d+)$", "$1")
  75.         return MonitorNumber
  76.     }
  77.     return false
  78. }
  79.  
  80. ; https://www.autohotkey.com/boards/viewtopic.php?p=388086#p388086
  81. Audio_EnumEndpoints() {
  82.     ; http://www.daveamenta.com/2011-05/programmatically-or-command-line-change-the-default-sound-playback-device-in-windows-7/
  83.     Devices := {}
  84.     IMMDeviceEnumerator := ComObjCreate("{BCDE0395-E52F-467C-8E3D-C4579291692E}", "{A95664D2-9614-4F35-A746-DE8DB63617E6}")
  85.  
  86.     ; IMMDeviceEnumerator::EnumAudioEndpoints
  87.     ; eRender = 0, eCapture, eAll
  88.     ; 0x1 = DEVICE_STATE_ACTIVE
  89.     DllCall(NumGet(NumGet(IMMDeviceEnumerator+0)+3*A_PtrSize), "UPtr", IMMDeviceEnumerator, "UInt", 0, "UInt", 0x1, "UPtrP", IMMDeviceCollection, "UInt")
  90.     ObjRelease(IMMDeviceEnumerator)
  91.  
  92.     ; IMMDeviceCollection::GetCount
  93.     DllCall(NumGet(NumGet(IMMDeviceCollection+0)+3*A_PtrSize), "UPtr", IMMDeviceCollection, "UIntP", Count, "UInt")
  94.     Loop % (Count)
  95.     {
  96.         ; IMMDeviceCollection::Item
  97.         DllCall(NumGet(NumGet(IMMDeviceCollection+0)+4*A_PtrSize), "UPtr", IMMDeviceCollection, "UInt", A_Index-1, "UPtrP", IMMDevice, "UInt")
  98.  
  99.         ; IMMDevice::GetId
  100.         DllCall(NumGet(NumGet(IMMDevice+0)+5*A_PtrSize), "UPtr", IMMDevice, "UPtrP", pBuffer, "UInt")
  101.         DeviceID := StrGet(pBuffer, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "UPtr", pBuffer)
  102.  
  103.         ; IMMDevice::OpenPropertyStore
  104.         ; 0x0 = STGM_READ
  105.         DllCall(NumGet(NumGet(IMMDevice+0)+4*A_PtrSize), "UPtr", IMMDevice, "UInt", 0x0, "UPtrP", IPropertyStore, "UInt")
  106.         ObjRelease(IMMDevice)
  107.  
  108.         ; IPropertyStore::GetValue
  109.         VarSetCapacity(PROPVARIANT, A_PtrSize == 4 ? 16 : 24)
  110.         VarSetCapacity(PROPERTYKEY, 20)
  111.         DllCall("Ole32.dll\CLSIDFromString", "Str", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "UPtr", &PROPERTYKEY)
  112.         NumPut(14, &PROPERTYKEY + 16, "UInt")
  113.         DllCall(NumGet(NumGet(IPropertyStore+0)+5*A_PtrSize), "UPtr", IPropertyStore, "UPtr", &PROPERTYKEY, "UPtr", &PROPVARIANT, "UInt")
  114.         DeviceName := StrGet(NumGet(&PROPVARIANT + 8), "UTF-16")    ; LPWSTR PROPVARIANT.pwszVal
  115.         DllCall("Ole32.dll\CoTaskMemFree", "UPtr", NumGet(&PROPVARIANT + 8))    ; LPWSTR PROPVARIANT.pwszVal
  116.         ObjRelease(IPropertyStore)
  117.  
  118.         ObjRawSet(Devices, DeviceName, DeviceID)
  119.     }
  120.     ObjRelease(IMMDeviceCollection)
  121.     return Devices
  122. }
  123.  
  124. Audio_SetDefaultEndpoint(DeviceID)
  125. {
  126.     IPolicyConfig := ComObjCreate("{870af99c-171d-4f9e-af0d-e63df40c2bc9}", "{F8679F50-850A-41CF-9C72-430F290290C8}")
  127.     DllCall(NumGet(NumGet(IPolicyConfig+0)+13*A_PtrSize), "UPtr", IPolicyConfig, "UPtr", &DeviceID, "UInt", 0, "UInt")
  128.     ObjRelease(IPolicyConfig)
  129. }
  130.  
  131. Audio_GetDeviceID(Devices, Name)
  132. {
  133.     For DeviceName, DeviceID in Devices
  134.         If (InStr(DeviceName, Name))
  135.             Return DeviceID
  136. }
  137.  
  138. Audio_GetProgramPeakVolume(hwnd:="") {
  139.     WinGet, PID, PID, % "ahk_id " hwnd
  140.     ProcessName := ProcessGetName(PID)
  141.  
  142.     ISAVs := []
  143.     static IID_IASM2 := "{77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F}"
  144.     , IID_IASC2 := "{BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D}"
  145.     , IID_ISAV := "{87CE5498-68D6-44E5-9215-6DA47EF883D8}"
  146.     , IID_IAudioMeterInformation := "{C02216F6-8C67-4B5B-9D00-D008E73E0064}"
  147.  
  148.     ; Activate the session manager of the given device
  149.     pIMMD := VA_GetDevice()
  150.     VA_IMMDevice_Activate(pIMMD, IID_IASM2, 0, 0, pIASM2)
  151.     ObjRelease(pIMMD)
  152.  
  153.     ; Enumerate sessions for on this device
  154.     VA_IAudioSessionManager2_GetSessionEnumerator(pIASM2, pIASE)
  155.     ObjRelease(pIASM2)
  156.  
  157.     ; Search for audio sessions with a matching process ID or Name
  158.     VA_IAudioSessionEnumerator_GetCount(pIASE, Count)
  159.     Loop, % Count
  160.     {
  161.         ; Get this session's IAudioSessionControl2 via its IAudioSessionControl
  162.         VA_IAudioSessionEnumerator_GetSession(pIASE, A_Index-1, pIASC)
  163.         pIASC2 := ComObjQuery(pIASC, IID_IASC2)
  164.         ObjRelease(pIASC)
  165.  
  166.         VA_IAudioSessionControl2_GetProcessID(pIASC2, PID)
  167.         if (ProcessGetName(PID) = ProcessName) {
  168.             ; get IAudioMeterInformation from IAudioSessionControl
  169.             ; AudioMeterInformation = {C02216F6-8C67-4B5B-9D00-D008E73E0064}
  170.             AudioMeter := ComObjQuery(pIASC2, IID_IAudioMeterInformation)
  171.             VA_IAudioMeterInformation_GetPeakValue(AudioMeter, vPeakValue)
  172.             ObjRelease(AudioMeter)
  173.         }
  174.         ObjRelease(pIASC2)
  175.     }
  176.     ; Release the IAudioSessionEnumerator
  177.     ObjRelease(pIASE)
  178.     return vPeakValue
  179. }
  180.  
  181. ProcessGetName(PID) {
  182.     hProcess := DllCall("OpenProcess"
  183.     , "UInt", 0x1000 ; DWORD dwDesiredAccess (PROCESS_QUERY_LIMITED_INFORMATION)
  184.     , "UInt", False  ; BOOL  bInheritHandle
  185.     , "UInt", PID    ; DWORD dwProcessId
  186.     , "UPtr")
  187.     dwSize := VarSetCapacity(strExeName, 512 * A_IsUnicode, 0) // A_IsUnicode
  188.     DllCall("QueryFullProcessImageName"
  189.     , "UPtr", hProcess  ; HANDLE hProcess
  190.     , "UInt", 0         ; DWORD  dwFlags
  191.     , "Str", strExeName ; LPSTR  lpExeName
  192.     , "UInt*", dwSize   ; PDWORD lpdwSize
  193.     , "UInt")
  194.     DllCall("CloseHandle", "UPtr", hProcess, "UInt")
  195.     SplitPath, strExeName, strExeName
  196.     return strExeName
  197. }
  198.  
  199. ; =====================================================================================
  200. ; functions used from VA.ahk
  201. ; remove functions below if you already have VA.ahk
  202. ; =====================================================================================
  203.  
  204. ; device_desc = device_id
  205. ;               | ( friendly_name | 'playback' | 'capture' ) [ ':' index ]
  206. VA_GetDevice(device_desc="playback")
  207. {
  208.     static CLSID_MMDeviceEnumerator := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
  209.         , IID_IMMDeviceEnumerator := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
  210.     if !(deviceEnumerator := ComObjCreate(CLSID_MMDeviceEnumerator, IID_IMMDeviceEnumerator))
  211.         return 0
  212.  
  213.     device := 0
  214.  
  215.     if VA_IMMDeviceEnumerator_GetDevice(deviceEnumerator, device_desc, device) = 0
  216.         goto VA_GetDevice_Return
  217.  
  218.     if device_desc is integer
  219.     {
  220.         m2 := device_desc
  221.         if m2 >= 4096 ; Probably a device pointer, passed here indirectly via VA_GetAudioMeter or such.
  222.         {
  223.             ObjAddRef(device := m2)
  224.             goto VA_GetDevice_Return
  225.         }
  226.     }
  227.     else
  228.         RegExMatch(device_desc, "(.*?)\s*(?::(\d+))?$", m)
  229.  
  230.     if m1 in playback,p
  231.         m1 := "", flow := 0 ; eRender
  232.     else if m1 in capture,c
  233.         m1 := "", flow := 1 ; eCapture
  234.     else if (m1 . m2) = ""  ; no name or number specified
  235.         m1 := "", flow := 0 ; eRender (default)
  236.     else
  237.         flow := 2 ; eAll
  238.  
  239.     if (m1 . m2) = ""   ; no name or number (maybe "playback" or "capture")
  240.     {
  241.         VA_IMMDeviceEnumerator_GetDefaultAudioEndpoint(deviceEnumerator, flow, 0, device)
  242.         goto VA_GetDevice_Return
  243.     }
  244.  
  245.     VA_IMMDeviceEnumerator_EnumAudioEndpoints(deviceEnumerator, flow, 1, devices)
  246.  
  247.     if m1 =
  248.     {
  249.         VA_IMMDeviceCollection_Item(devices, m2-1, device)
  250.         goto VA_GetDevice_Return
  251.     }
  252.  
  253.     VA_IMMDeviceCollection_GetCount(devices, count)
  254.     index := 0
  255.     Loop % count
  256.         if VA_IMMDeviceCollection_Item(devices, A_Index-1, device) = 0
  257.             if InStr(VA_GetDeviceName(device), m1) && (m2 = "" || ++index = m2)
  258.                 goto VA_GetDevice_Return
  259.             else
  260.                 ObjRelease(device), device:=0
  261.  
  262. VA_GetDevice_Return:
  263.    ObjRelease(deviceEnumerator)
  264.     if devices
  265.         ObjRelease(devices)
  266.  
  267.     return device ; may be 0
  268. }
  269.  
  270. VA_IMMDevice_Activate(this, iid, ClsCtx, ActivationParams, ByRef Interface) {
  271.     return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "ptr", VA_GUID(iid), "uint", ClsCtx, "uint", ActivationParams, "ptr*", Interface)
  272. }
  273.  
  274. VA_IAudioSessionManager2_GetSessionEnumerator(this, ByRef SessionEnum) {
  275.     return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "ptr*", SessionEnum)
  276. }
  277.  
  278. VA_IAudioSessionEnumerator_GetCount(this, ByRef SessionCount) {
  279.     return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int*", SessionCount)
  280. }
  281.  
  282. VA_IAudioSessionEnumerator_GetSession(this, SessionCount, ByRef Session) {
  283.     return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "int", SessionCount, "ptr*", Session)
  284. }
  285.  
  286. VA_IAudioSessionControl2_GetProcessId(this, ByRef pid) {
  287.     return DllCall(NumGet(NumGet(this+0)+14*A_PtrSize), "ptr", this, "uint*", pid)
  288. }
  289.  
  290. VA_IAudioMeterInformation_GetPeakValue(this, ByRef Peak) {
  291.     return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "float*", Peak)
  292. }
  293.  
  294. VA_IMMDeviceEnumerator_GetDevice(this, id, ByRef Device) {
  295.     return DllCall(NumGet(NumGet(this+0)+5*A_PtrSize), "ptr", this, "wstr", id, "ptr*", Device)
  296. }
  297.  
  298. VA_IMMDeviceEnumerator_GetDefaultAudioEndpoint(this, DataFlow, Role, ByRef Endpoint) {
  299.     return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "int", DataFlow, "int", Role, "ptr*", Endpoint)
  300. }
  301.  
  302. VA_IMMDeviceEnumerator_EnumAudioEndpoints(this, DataFlow, StateMask, ByRef Devices) {
  303.     return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "int", DataFlow, "uint", StateMask, "ptr*", Devices)
  304. }
  305.  
  306. VA_IMMDeviceCollection_Item(this, Index, ByRef Device) {
  307.     return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint", Index, "ptr*", Device)
  308. }
  309.  
  310. VA_IMMDeviceCollection_GetCount(this, ByRef Count) {
  311.     return DllCall(NumGet(NumGet(this+0)+3*A_PtrSize), "ptr", this, "uint*", Count)
  312. }
  313.  
  314. VA_GetDeviceName(device)
  315. {
  316.     static PKEY_Device_FriendlyName
  317.     if !VarSetCapacity(PKEY_Device_FriendlyName)
  318.         VarSetCapacity(PKEY_Device_FriendlyName, 20)
  319.         ,VA_GUID(PKEY_Device_FriendlyName :="{A45C254E-DF1C-4EFD-8020-67D146A850E0}")
  320.         ,NumPut(14, PKEY_Device_FriendlyName, 16)
  321.     VarSetCapacity(prop, 16)
  322.     VA_IMMDevice_OpenPropertyStore(device, 0, store)
  323.     ; store->GetValue(.., [out] prop)
  324.     DllCall(NumGet(NumGet(store+0)+5*A_PtrSize), "ptr", store, "ptr", &PKEY_Device_FriendlyName, "ptr", &prop)
  325.     ObjRelease(store)
  326.     VA_WStrOut(deviceName := NumGet(prop,8))
  327.     return deviceName
  328. }
  329.  
  330. VA_GUID(ByRef guid_out, guid_in="%guid_out%") {
  331.     if (guid_in == "%guid_out%")
  332.         guid_in :=   guid_out
  333.     if  guid_in is integer
  334.         return guid_in
  335.     VarSetCapacity(guid_out, 16, 0)
  336.     DllCall("ole32\CLSIDFromString", "wstr", guid_in, "ptr", &guid_out)
  337.     return &guid_out
  338. }
  339.  
  340. VA_IMMDevice_OpenPropertyStore(this, Access, ByRef Properties) {
  341.     return DllCall(NumGet(NumGet(this+0)+4*A_PtrSize), "ptr", this, "uint", Access, "ptr*", Properties)
  342. }
  343.  
  344. VA_WStrOut(ByRef str) {
  345.     str := StrGet(ptr := str, "UTF-16")
  346.     DllCall("ole32\CoTaskMemFree", "ptr", ptr)
  347. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement