对于我目前的项目,我实现了一个C ++本机库,我将通过JNA访问,这个项目是一个低延迟的通信模拟器。在传输时需要启用侧音,以模仿模拟器所基于的硬件。 当然,JAVA声音难以实现接近零延迟(最好我们可以得到~120ms),为了保持可理解性,我们需要侧音的延迟接近零。幸运的是,在Windows中,有一种方法可以听取USB耳机的麦克风,从而产生完美的侧音。
音频属性 - >播放 - >耳机耳机 - >属性 - >水平
An example of what I mean here
(请注意,这与'收听此设备'功能不同,会产生相当糟糕的延迟)
我一直在使用Core Audio API的MSDN示例,并且能够查询设备并获取其频道,音量级别,静音设置等,但麦克风级别静音/取消静音不会似乎甚至可以从核心音频apis访问。
我的问题是:有没有办法以编程方式连接USB耳机的麦克风级别/静音设置?
我们的模拟器是标准化的,所以我们不必担心支持各种耳机(目前只有2个)。
答案 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调用它,因此在任何传输的开始和结束时