Audio 如何检查自动热键脚本中是否连接了声音设备?

Audio 如何检查自动热键脚本中是否连接了声音设备?,audio,autohotkey,device,nircmd,Audio,Autohotkey,Device,Nircmd,我有一个自动热键脚本,可以通过一次按键在多个声音设备之间切换 一切正常,我正在使用nircmd实用程序激活设备(设置为默认设备) Run,Tools\nircmd.exe setdefaultsounddevice“%playback%”,其中%playback%是实际的声音设备名称 所以我的脚本基本上是通过声音面板中的3个设备(耳机、扬声器、电视)循环的 但是,当我的电视关闭(断开连接)时,它仍然会在所有3台设备中循环 我需要的是能够在脚本中检查设备是否已断开连接 我在nircmd中找不到任

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

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

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

所以我的脚本基本上是通过声音面板中的3个设备(耳机、扬声器、电视)循环的

但是,当我的电视关闭(断开连接)时,它仍然会在所有3台设备中循环

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

我在nircmd中找不到任何可以这样做的命令

如果你有任何想法,请告诉我


谢谢。

当然非常可行,但请注意,此答案中的代码非常高级。
这不需要使用任何外部实用程序

因此,我们对这个问题感兴趣。
通过这种方法,我们可以根据一些标准列出所需的音频设备

问题:
我们如何在AHK中使用这种方法?
把它弄得乱七八糟。为此,我们需要它在内存中的地址(指针),因为
DllCall
能够通过地址调用函数/方法


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

CLSID := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
IID := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
pDeviceEnumerator := ComObjCreate(CLSID, IID)

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

首先,我们需要了解,我们想要的方法是接口的第一种方法。
但是因为接口继承自接口的前三个方法实际上是
AddRef
QueryInterface
,和
Release

因此,我们想要的接口方法实际上是接口的第四个方法

好的,我们想得到这个接口的第四个方法的指针。
要做到这一点,我们首先要获取指向接口的指针。vtable包含每个方法的指针。在我们得到vtable的指针之后,我们可以从vtable中得到我们想要的方法的指针

要获取这些指针,我们将使用AHK函数。
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变量。它将包含4个或8个。
存储在vtable中的指针的视觉表示:

vtable                      offset (bytes)
AddRef                      0
QueryInterface              8
Release                     16
EnumAudioEndpoints          24
GetDefaultAudioEndpoint     32
GetDevice                   40
...
所以我们想在偏移量为24字节的位置
NumGet

pEnumAudioEndpoints:=NumGet(vtable+0,3*A\u PtrSize)

(演示如何使用
A_PtrSize
使脚本在32位计算机上也兼容,但您也可以不使用它并指定24位)


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

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

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

所以,第二种方法,简化。为此,我想到的是,列出了未拔出的设备,如果有,您将知道在您的具体案例中,电视是未拔出的。
如果没有,你就会知道电视是插上电源的

好的,我们继续使用这个方法。 它需要三个论点:

  • 数据流

    对于此参数,我们从枚举中指定一个值。我们需要的值是
    eRender
    ,它是枚举的第一个成员,因此0
  • dwStateMask

    为此,我们指定所需的按位标志。我们只需要不插电的设备,所以只需要
    设备\u状态\u不插电
    标志()
  • **pp设备

    这里我们指定一个指向变量的指针,该变量将接收指向结果接口所在的内存地址的指针
  • 现在转到
    DllCall
    ing。调用方法wi的方法
    DllCall(pEnumAudioEndpoints, Ptr, pDeviceEnumerator, UInt, 0, UInt, 0x00000008, PtrP, pDeviceCollection)
    
    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"