如何以编程方式启用侧音/麦克风直通

Posted

技术标签:

【中文标题】如何以编程方式启用侧音/麦克风直通【英文标题】:How to enable sidetone/microphone pass-thru programmatically 【发布时间】:2017-04-10 16:10:33 【问题描述】:

对于我当前的项目,我正在用 C++ 实现一个本机库,我将通过 JNA 访问该库,这个项目是一个低延迟通信模拟器。为了模仿模拟器所基于的硬件,需要在传输时启用侧音。 当然,事实证明 JAVA 声音很难达到接近零的延迟(我们能得到的最好是约 120 毫秒),为了保持可理解性,我们需要侧音的延迟接近于零。幸运的是,在 Windows 中似乎有一种方法可以收听 USB 耳机的麦克风,从而产生完美的侧音。

音频属性->播放->耳机->属性->电平

An example of what I mean here

(请注意,这与“收听此设备”功能不同,后者会产生非常糟糕的延迟)

我一直在使用 Core Audio API 的 MSDN 示例,并且能够查询设备并获取它们的通道、音量、静音设置等,但似乎无法访问麦克风级别的静音/取消静音甚至来自核心音频 api。

我的问题是:有没有办法以编程方式与 USB 耳机的麦克风电平/静音设置进行交互?

我们的模拟器是标准化的,因此我们不必担心支持各种耳机(目前有 2 个)。

【问题讨论】:

我将尝试使用 IDeviceTopology 来查看该功能是否可用 - 如果可行,我会向您报告。 我很幸运,使用了 MSDN 中发布的一个示例,该示例显示了通过设备拓扑树“向后走”的方法,这使我能够搜索一个静音节点,该节点有 getter/setter 方法。虽然不是我找到的原始链接 this *** answer 有我引用的 walkTreeBackwardsFromPart() 函数。 【参考方案1】:

解决这个问题的关键是向后遍历设备拓扑树,直到找到负责设置侧音静音属性的部分。因此,在我的 CPP 项目中,我使用了几种方法共同确定我在拓扑树中的位置,以寻找 SuperMix 部分。

SuperMix 似乎是侧音的通用名称,并且至少被我们支持的两款耳机使用。两个耳机的树是相同的,您的里程可能会有所不同。这就是前面提到的 WalkTreeBackwardsFromPart 示例的输出结果(参见 this answer)

Part Name: SuperMix
    Part Name: Volume
        Part Name: Mute

这是我修改后的 WalkTreeBackwardsFromPart 版本,它出于所有意图和目的仅检查我们当前正在查看的部分是否是 SuperMix 并且该部分的直接子节点是否是卷节点,这是为了防止不正确分配,因为我发现对于我们的耳机,通常会有两个称为 SuperMix 的节点,唯一的区别是我们想要的那个有一个音量节点子节点。

HRESULT Sidetone::WalkTreeBackwardsFromPart(IPart *part) 

    HRESULT hr;

    if (wcscmp(this->getPartName(part), L"SuperMix") == 0 && this->treePeek(part, L"Volume"))

        this->superMix = part;

        IPart** superMixChildren = this->getChildParts(part);
        int nSuperMixChildren = sizeof(superMixChildren) / sizeof(superMixChildren[0]);
        if (nSuperMixChildren > 0)

            for (int i = 0; i < nSuperMixChildren; i++)

                if (wcscmp(this->getPartName(superMixChildren[i]), L"Volume") == 0)

                    this->volumeNode = this->getIPartAsIAudioVolumeLevel(superMixChildren[i]);
                    if (this->volumeNode != NULL)

                        IPart** volumeNodeChildren = this->getChildParts(superMixChildren[i]);
                        int nVolumeNodeChildren = sizeof(volumeNodeChildren) / sizeof(volumeNodeChildren[0]);
                        if (nVolumeNodeChildren > 0)

                            for (int j = 0; j < nVolumeNodeChildren; j++)

                                if (wcscmp(this->getPartName(volumeNodeChildren[j]), L"Mute") == 0)

                                    this->muteNode = this->getIPartAsIAudioMute(volumeNodeChildren[j]);
                                    break;
                                
                            
                        
                    
                    break;
                
            
        
        delete[] superMixChildren;


        this->muteNode; // = someotherfunc();
        this->superMixFound = true;
        return S_OK;

     else if(superMixFound == false)

        IPartsList *pIncomingParts = NULL;
        hr = part->EnumPartsIncoming(&pIncomingParts);
        if (E_NOTFOUND == hr) 
            // not an error... we've just reached the end of the path
            //printf("%S - No incoming parts at this part: 0x%08x\n", this->MSGIDENTIFIER, hr);
            return S_OK;
        
        if (FAILED(hr)) 
            printf("%S - Couldn't enum incoming parts: hr = 0x%08x\n", this->MSGIDENTIFIER, hr);
            return hr;
        
        UINT nParts = 0;
        hr = pIncomingParts->GetCount(&nParts);
        if (FAILED(hr)) 
            printf("%S - Couldn't get count of incoming parts: hr = 0x%08x\n", this->MSGIDENTIFIER, hr);
            pIncomingParts->Release();
            return hr;
        

        // walk the tree on each incoming part recursively
        for (UINT n = 0; n < nParts; n++) 
            IPart *pIncomingPart = NULL;
            hr = pIncomingParts->GetPart(n, &pIncomingPart);
            if (FAILED(hr)) 
                printf("%S - Couldn't get part #%u (0-based) of %u (1-basedSmile hr = 0x%08x\n", this->MSGIDENTIFIER, n, nParts, hr);
                pIncomingParts->Release();
                return hr;
            

            hr = WalkTreeBackwardsFromPart(pIncomingPart);
            if (FAILED(hr)) 
                printf("%S - Couldn't walk tree on part #%u (0-based) of %u (1-basedSmile hr = 0x%08x\n", this->MSGIDENTIFIER, n, nParts, hr);
                pIncomingPart->Release();
                pIncomingParts->Release();
                return hr;
            
            pIncomingPart->Release();
        

        pIncomingParts->Release();
    

    return S_OK;

Sidetone::superMixFound 是一个布尔成员,用于快速中断我们的递归循环并阻止我们进一步遍历设备拓扑树(浪费时间)。

Sidetone::getPartName() 是一个简单的可重用方法,用于返回零件名称的宽字符串数组。

Sidetone::treePeek() 如果指定部件的子项包含名称作为第二个参数指定的部件,则返回 true。

Sidetone::getChildParts() 为给定部件的每个子元素返回一个指针数组。

在弄清楚这一点之后,只需将 setMute 方法暴露给 dllmain.cpp 并在我们需要激活/停用侧音时通过 JNA 调用它,因此在任何传输的开始和结束时。

【讨论】:

以上是关于如何以编程方式启用侧音/麦克风直通的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式关闭 VoiceProcessingIO AGC

在 OSX 中以编程方式更改麦克风增益

以编程方式在android中的软输入键盘上禁用语音到文本按钮(麦克风)

如何以编程方式请求媒体权限?

是否可以以编程方式在 Android 中收听扬声器输出?

如何以编程方式在 iphone 中执行应用程序音频的内部录制