如何以编程方式启用侧音/麦克风pass-thru

时间:2017-04-10 16:10:34

标签: java c++ windows audio com

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

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

An example of what I mean here

(请注意,这与'收听此设备'功能不同,会产生相当糟糕的延迟)

我一直在使用Core Audio API的MSDN示例,并且能够查询设备并获取其频道,音量级别,静音设置等,但麦克风级别静音/取消静音不会似乎甚至可以从核心音频apis访问。

我的问题是:有没有办法以编程方式连接USB耳机的麦克风级别/静音设置?

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

1 个答案:

答案 0 :(得分:2)

解决此问题的关键是向后走设备拓扑树,直到找到负责设置侧音静音属性的部分。所以在我的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调用它,因此在任何传输的开始和结束时