如何检查声音设备是否已在AutoHotkey脚本中连接?

问题描述 投票:0回答:1

我有AutoHotkey脚本,可以通过一次按键在多个声音设备之间切换。

一切正常,我正在使用nircmd实用程序激活设备(设置为默认设备)

[Run, Tools\nircmd.exe setdefaultsounddevice "%playback%",其中%playback%是实际的声音设备名称。

所以我的脚本基本上遍历了声音面板中的3种设备(耳机,扬声器,电视)。

但是,当我的电视关闭(断开连接)时,它仍会在所有3个设备之间循环播放。

enter image description here

我需要能够检查脚本中的设备是否断开连接。

我在nircmd中找不到任何可以执行此操作的命令。

[如果您有任何想法请告诉我。

谢谢。

audio autohotkey device nircmd
1个回答
0
投票

当然非常可行,但是要警告,此答案中的代码相当高级。这没有使用任何外部实用程序。

所以我们对EnumAudioEndpoints method of the IMMDeviceEnumerator interface感兴趣。通过这种方法,我们可以根据一些标准列出所需的音频设备。

问题:我们如何在AHK中使用这种方法?通过EnumAudioEndpoints。为此,我们需要在内存(指针)中提供其地址,因为IMMDeviceEnumerator可以按地址调用函数/方法。


因此,我们首先获取DllCall接口的指针。为此,我们需要它的DllCall,在这种情况下还需要它的DllCall。我通过Google搜索找到了它们。然后,我们利用AHK的IMMDeviceEnumerator函数(并使它更加复杂,是的,我们正在使用IMMDeviceEnumerator)正如AHK文档所指定的那样,我们确实从函数中获得了指针而不是对象,因为我们指定了IID。

CLSID

现在我们有了指向接口IID的指针,我们需要一个指向接口ComObjCreate方法的指针。这就是我们的下一个问题。

首先,我们需要了解,我们想要的方法是接口的第一个方法。但是

因为接口是从ComObjCreate继承的,所以接口的前三个方法实际上是ComObjectsCLSID := "{BCDE0395-E52F-467C-8E3D-C4579291692E}" IID := "{A95664D2-9614-4F35-A746-DE8DB63617E6}" pDeviceEnumerator := ComObjCreate(CLSID, IID) pDeviceEnumerator。因此,我们期望的接口方法实际上是接口的[[fourth方法。好,所以我们想要获得指向该接口的第4个方法的指针。为此,我们首先要获取指向接口的EnumAudioEndpoints的指针。 vtable包含每个方法的指针。在有了vtable指针之后,我们可以从该vtable中获得所需方法的指针。

要获取这些指针,我们将使用AHK的EnumAudioEndpoints函数。vtable通常位于ComObject的开头(偏移量为0),因此让IUnknown我们的偏移量为0的IUnknown指针可以到达vtable:AddRef

QueryInterface被指定,因此AHK不会将变量Release视为ByRef变量,而是在内存中所需的地址处进行操作。而且我们省略了第二个和第三个参数以使用默认值,偏移量0(这正是我们想要的),并且UPtr类型对我们来说也很好。

现在我们有了vtable的内存地址,最后让我们获取virtual table方法的指针。现在,记住它是vtable中的第一个(实际上是

fourth

)方法(偏移量3)。因此,我们要获取内存中的地址,该地址与vtable的内存地址相比偏移了3个方法。
现在记住vtable如何包含指针,所以我们想

forward

内存中3个指针的大小。指针的大小在32位计算机上为4个字节,在64位计算机上为8个字节。因此可以肯定地说,当我们的程序在现代台式计算机上运行时,指针的大小始终为8个字节。我们还可以使用内置的AHK变量NumGet。它将包含4或8。可视化表示存储在vtable中的指针:NumGet
所以我们想在偏移量为24个字节的情况下NumGetpDeviceEnumerator(演示了vtable := NumGet(pDeviceEnumerator+0)的用法也使您的脚本在32位计算机上兼容,但是您也可以不使用它并指定24))


好吧,,现在我们终于有了指向+0方法的指针,这意味着我们终于可以使用它了。

所以下一个问题是,我们如何使用它?首先,我们需要决定如何使用它。我可以想到两种使用方式。第一个是列出所有活动的和插入的设备,并使用它们进行所需的操作,并完全放弃使用nircmd,第二个是进行一些简化,仅适用于您的特定情况。

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

因此,第二种方法是简化。为此,我想到了列出未插拔的设备,如果有,您将知道在[[您的特定情况中,电视已拔出。如果没有,您将知道电视已插入。

确定,请继续使用该方法。它需要三个参数:

pDeviceEnumerator对于此参数,我们从EnumAudioEndpoints枚举中指定一个值。我们的期望值为A_PtrSize,它是枚举的第一个成员,因此

    0

  • A_PtrSize为此,我们指定所需的按位标志。我们只需要拔下设备,因此只需要vtable offset (bytes) AddRef 0 QueryInterface 8 Release 16 EnumAudioEndpoints 24 GetDefaultAudioEndpoint 32 GetDevice 40 ... 标志(NumGet)就可以了。pEnumAudioEndpoints := NumGet(vtable+0, 3*A_PtrSize)在这里,我们指定一个指向变量的指针,该变量将接收指向结果A_PtrSize接口所在的内存地址的指针。
  • 现在进入IMMDeviceEnumerator::EnumAudioEndpoints ing。用IMMDeviceEnumerator::EnumAudioEndpoints调用方法的方式甚至更是AHK的魔力,即使在文档中您也几乎找不到它,但这确实存在。方法是在实例上调用的,因此对于dataFlow的第一个参数,我们将传递存储在EDataFlow中的方法的指针,而对于第二个参数,则要传递对象的指针(我们正在处理的接口实例),并将其存储在EDataFlow中。之后,我们通常将参数传递给该方法。
  • eRender
  • dwStateMask的语法为Type,后跟参数。首先,我们传递一个指针Ptr。然后我们传递两个非负数,类型为无符号整数UInt会很好。然后,我们传递一个DEVICE_STATE_UNPLUGGED以传递变量0x00000008的指针。您会注意到,甚至从未声明过该变量,但这很好,AHK是一种宽容的语言,因此它将自动为我们创建该变量。

    好,现在0x00000008已经完成,并且我们有一个指向结果**ppDevices接口的指针。您会注意到该界面如何包含两种方法,IMMDeviceCollectionIMMDeviceCollection。为了简化起见,我们对DllCall方法感兴趣。因此,我们再次获取该接口的vtable的地址:DllCall再一次,我们对接口的第一个(但实际上是第四个)方法(偏移量3)感兴趣:DllCall


    然后我们已经可以使用该方法,因此再次让pEnumAudioEndpoints。这次我们要处理的对象是pDeviceEnumerator,并且其指针存储在DllCall(pEnumAudioEndpoints, Ptr, pDeviceEnumerator, UInt, 0, UInt, 0x00000008, PtrP, pDeviceCollection) 变量中。

    函数只需要一个参数DllCall,它是一个指向变量的指针,该变量将接收设备集合中存在的设备数量。PtrP

    然后我们走了,简化的方法就完成了。我们成功收到了未拔出但已启用的音频播放设备的数量。现在,当我们知道我们已经完成ComObjects时,就应该释放它们(如文档所指定)。这不一定是100%必需的,但绝对是个好习惯,因此让我们发布ComObjects:


    pDeviceCollection

    这是简化方法的完整示例脚本:
    DllCall

    如果这看起来非常复杂/困难,那是因为它确实是。我想说,这几乎和AHKIMMDeviceCollectioning一样复杂。但是,当您不使用为您完成所有出色工作的外部实用程序时,就是这种方式。

    如果您决定实施适当的解决方案来处理我所谈论的音频设备,则IMMDeviceCollection可能是一个不错的库,您可以使用它,也可以从中获取参考。我自己没有使用过,所以不能说那里的东西已经过时了。它是由Lexikos亲自制造的。
    © www.soinside.com 2019 - 2024. All rights reserved.