如何在AutoHotkey脚本中检查声音设备是否连接? [英] How to check if sound device connected in AutoHotkey script?
问题描述
我有一次击键即可在多个声音设备之间切换的AutoHotkey脚本。
一切正常,我正在使用nircmd实用程序激活设备(设置为默认设备)
Run, Tools
ircmd.exe setdefaultsounddevice "%playback%"
,其中%playback%
是实际的声音设备名称。
所以我的脚本基本上遍历了我在Sound Panel中拥有的3个设备(耳机、扬声器、电视)。
但是,当我的电视关闭(断开)时,它仍然在所有3个设备上循环。
我需要的是能够在我的脚本中检查设备是否已断开连接。
我在nircmd中找不到任何可以执行此操作的命令。
如果您有什么想法,请告诉我。
谢谢。
推荐答案
确实非常可行,但请注意,此答案中的代码非常高级。
这不需要使用任何外部实用程序。
所以我们对EnumAudioEndpoints
method of the IMMDeviceEnumerator
interface感兴趣。
使用此方法,我们可以根据某些条件列出所需的音频设备。
问题:
我们如何在AHK中使用这种方法?
DllCall
。为此,我们需要它在内存中的地址(指针),因为DllCall
能够通过地址调用函数/方法。
我们从获取
IMMDeviceEnumerator
接口的指针开始。为此,我们需要它的CLSID,在本例中还需要它的IID。我是通过谷歌搜索找到的。
然后我们使用AHK的
ComObjCreate
功能(更复杂的是,我们正在使用ComObjects)正如AHK文档所指定的,我们确实从函数中获得了一个指针而不是一个对象,因为我们指定了一个IID。
CLSID := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
IID := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
pDeviceEnumerator := ComObjCreate(CLSID, IID)
现在我们有了指向接口pDeviceEnumerator
的指针,我们需要一个指向该接口的EnumAudioEndpoints
方法的指针。
这就是我们的下一个问题。
首先我们需要了解,我们所需的方法是接口的第一个方法。
但是因为接口继承自IUnknown
,所以接口的前三个方法实际上是AddRef
、QueryInterface
和Release
。
因此,我们所需的接口方法实际上是接口的第四个方法。
确定,所以我们要获取指向此接口的第四个方法的指针。
为此,我们首先希望获得指向接口的virtual table的指针。Vtable包含每个方法的指针。在我们将指针指向vtable之后,我们就可以从该vtable中获得所需方法的指针。
要获取这些指针,我们将使用AHK的NumGet
函数。
Vtable通常位于ComObject的开头(偏移量0),因此让NumGet
我们的pDeviceEnumerator
指针在偏移量0处到达vtable:
vtable := NumGet(pDeviceEnumerator+0)
+0
被指定,因此AHK不会将变量pDeviceEnumerator
视为ByRef变量,而是在内存中所需的地址进行操作。
我们省略了第二个和第三个参数来使用缺省值,偏移量0(这正是我们想要的),而且UPtr类型对我们来说也很好。
现在我们有了vtable的内存地址,让我们最终获得EnumAudioEndpoints
方法的指针。
现在请记住它是vtable(偏移量3)中的第一个(但实际上是第四个)方法。
因此,我们希望获得内存中的地址,该地址是vtable内存地址的3个方法的偏移量。
现在记住vtable是如何包含指针的,所以我们要将内存中3个指针的大小向前移动。指针的大小在32位计算机上为4字节,在64位计算机上为8字节。
因此,当我们的程序在现代台式计算机上运行时,可以很有把握地说,现在指针的大小总是8个字节。我们还可以利用内置的AHK变量A_PtrSize
。它将包含4或8。
存储在vtable中的指针的可视化表示:
vtable offset (bytes)
AddRef 0
QueryInterface 8
Release 16
EnumAudioEndpoints 24
GetDefaultAudioEndpoint 32
GetDevice 40
...
因此我们希望NumGet
偏移量为24字节:
pEnumAudioEndpoints := NumGet(vtable+0, 3*A_PtrSize)
(演示如何使用A_PtrSize
使您的脚本也与32位计算机兼容,但您也可以不这样做并指定24)
好了,现在我们终于有了指向IMMDeviceEnumerator::EnumAudioEndpoints
方法的指针,这意味着我们终于可以使用它了。
所以下一个问题是,我们如何使用它?
首先,我们需要决定如何使用它。我可以想出两种我们想要使用它的方式。
第一个是列出所有插入的活动设备,并使用它们做所需的事情,完全放弃使用nircmd;第二个是稍微简化一下,只适用于您的特定情况。
我将为您演示第二种方法,如果您想要进行适当的实现,您可以自己尝试实现第一种方法。如果你遇到问题,你当然可以寻求帮助。所以,第二种方法,简化。为此,我想到的是列出未拔下的设备,如果有,您就会知道您的特定情况电视是在什么情况下拔下的。
如果没有,你就会知道电视已经插上电源了。
好的,那么继续使用该方法。 它需要三个参数:
dataFlow
对于此参数,我们从EDataFlow
枚举中指定一个值。我们需要的值是eRender
,它是枚举的第一个成员,因此0。dwStateMask
为此,我们指定所需的按位标志。我们只想要拔下的设备,所以我们只需要DEVICE_STATE_UNPLUGGED
标志(0x00000008
)。**ppDevices
在这里,我们指定一个指向变量的指针,该变量将接收指向产生的IMMDeviceCollection
接口所在的内存地址的指针。
,现在转到DllCall
。使用DllCall
调用方法的方式更加神奇,您甚至很难在文档中找到它,但它在某种程度上是存在的。
方法是在实例上调用的,因此对于DllCall
的第一个参数,我们将传递存储在pEnumAudioEndpoints
中的方法的指针,对于第二个参数,我们希望传递我们所作用的对象(接口的实例)的指针,我们已经将其存储在pDeviceEnumerator
中。
在此之后,我们通常将参数传递给该方法。
DllCall(pEnumAudioEndpoints, Ptr, pDeviceEnumerator, UInt, 0, UInt, 0x00000008, PtrP, pDeviceCollection)
DllCall
的语法是Type后跟参数。首先,我们传递一个指针ptr。
然后我们传递两个非负数,类型为UInt的无符号整数就可以了。
然后我们传递一个PtrP来传递变量
pDeviceCollection
的指针。您会注意到,该变量甚至从未声明过,但这很好,AHK是一种非常宽容的语言,因此它会自动为我们创建变量。
好的,现在
DllCall
已全部完成,我们有一个指向结果IMMDeviceCollection
界面的指针。
您将注意到该接口包含两个方法,GetCount
和Item
。对于我为您演示的简化方法,我们对
GetCount
方法感兴趣。因此,我们将再次获得该接口的vtable的地址:
vtable := NumGet(pDeviceCollection+0)
同样,我们对接口的第一个(但实际上是第四个)方法(偏移量3)感兴趣:
pGetCount := NumGet(vtable+0, 3*A_PtrSize)
然后我们已经可以使用该方法,所以让我们再次DllCall
。
这一次我们操作的对象是IMMDeviceCollection
,并且我们将其指针存储在pDeviceCollection
变量中。
这些函数只需要一个参数*pcDevices
,即指向一个变量的指针,该变量将接收我们的设备集合上的设备数量。
DllCall(pGetCount, Ptr, pDeviceCollection, UIntP, DeviceCount)
我们开始了,简化的方法都完成了。
我们已成功接收到已拔下但已启用的音频播放设备的数量。
现在,当我们知道我们已经完成了ComObject时,我们应该发布它们(正如文档所指定的)。这不一定是100%必需的,但绝对是很好的实践,所以让我们发布ComObject:
ObjRelease(pDeviceEnumerator)
ObjRelease(pDeviceCollection)
以下是简化方式的完整示例脚本:
#NoEnv ;unquoted types in DllCall don't hinder performance
CLSID := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
IID := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
pDeviceEnumerator := ComObjCreate(CLSID, IID)
vtable := NumGet(pDeviceEnumerator+0)
pEnumAudioEndpoints := NumGet(vtable+0, 3*A_PtrSize)
DllCall(pEnumAudioEndpoints, Ptr, pDeviceEnumerator, UInt, 0, UInt, 0x00000008, PtrP, pDeviceCollection)
vtable := NumGet(pDeviceCollection+0)
pGetCount := NumGet(vtable+0, 3*A_PtrSize)
DllCall(pGetCount, Ptr, pDeviceCollection, UIntP, DeviceCount)
ObjRelease(pDeviceEnumerator)
ObjRelease(pDeviceCollection)
if (DeviceCount = 0)
MsgBox, % "No unplugged, but enabled, devices found`nI'll assume my TV is plugged in and I have three audio devices enabled"
else if (DeviceCount = 1)
MsgBox, % "One unplugged, but enabled, device found`nI'll assume my TV is unplugged and I have only two audio devices enabled"
else
MsgBox, % "There are " DeviceCount "unplugged audio devices"
如果这看起来非常复杂/困难,那是因为事实确实如此。
我要说,这几乎和AHKDllCall
ING一样复杂。
但是,当你不使用为你做所有酷的事情的外部实用程序时,事情就是这样的。
如果您决定实现我提到的处理音频设备的适当解决方案,this可能是一个很好的库,您可以使用或参考。我自己没有用过,所以不能说那里的一些东西是不是过时了。
这是莱西科斯自己做的。
这篇关于如何在AutoHotkey脚本中检查声音设备是否连接?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!