如何在AutoHotkey脚本中检查声音设备是否连接? [英] How to check if sound device connected in AutoHotkey script?

查看:0
本文介绍了如何在AutoHotkey脚本中检查声音设备是否连接?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一次击键即可在多个声音设备之间切换的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,所以接口的前三个方法实际上是AddRefQueryInterfaceRelease
因此,我们所需的接口方法实际上是接口的第四个方法。

确定,所以我们要获取指向此接口的第四个方法的指针。
为此,我们首先希望获得指向接口的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;第二个是稍微简化一下,只适用于您的特定情况。

我将为您演示第二种方法,如果您想要进行适当的实现,您可以自己尝试实现第一种方法。如果你遇到问题,你当然可以寻求帮助。

所以,第二种方法,简化。为此,我想到的是列出未拔下的设备,如果有,您就会知道您的特定情况电视是在什么情况下拔下的。
如果没有,你就会知道电视已经插上电源了。

好的,那么继续使用该方法。 它需要三个参数:

  1. dataFlow
    对于此参数,我们从EDataFlow枚举中指定一个值。我们需要的值是eRender,它是枚举的第一个成员,因此0
  2. dwStateMask
    为此,我们指定所需的按位标志。我们只想要拔下的设备,所以我们只需要DEVICE_STATE_UNPLUGGED标志(0x00000008)。
  3. **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界面的指针。 您将注意到该接口包含两个方法,GetCountItem
对于我为您演示的简化方法,我们对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"

如果这看起来非常复杂/困难,那是因为事实确实如此。
我要说,这几乎和AHKDllCallING一样复杂。
但是,当你不使用为你做所有酷的事情的外部实用程序时,事情就是这样的。

如果您决定实现我提到的处理音频设备的适当解决方案,this可能是一个很好的库,您可以使用或参考。我自己没有用过,所以不能说那里的一些东西是不是过时了。
这是莱西科斯自己做的。

这篇关于如何在AutoHotkey脚本中检查声音设备是否连接?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆