如何以编程方式启用侧音/麦克风直通
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 调用它,因此在任何传输的开始和结束时。
【讨论】:
以上是关于如何以编程方式启用侧音/麦克风直通的主要内容,如果未能解决你的问题,请参考以下文章