Skip to content

Commit cf57067

Browse files
author
hongqingwan
committed
win-wasapi: Add microphone volume adjustment feature
1 parent 189ed7c commit cf57067

File tree

3 files changed

+302
-2
lines changed

3 files changed

+302
-2
lines changed

plugins/win-wasapi/enum-wasapi.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "enum-wasapi.hpp"
22

33
#include <util/base.h>
4+
#include <util/dstr.h>
45
#include <util/platform.h>
56
#include <util/windows/HRError.hpp>
67
#include <util/windows/ComPtr.hpp>
@@ -90,3 +91,35 @@ void GetWASAPIAudioDevices(vector<AudioDeviceInfo> &devices, bool input)
9091
blog(LOG_WARNING, "[GetWASAPIAudioDevices] %s: %lX", error.str, error.hr);
9192
}
9293
}
94+
95+
IMMDevice *GetMMDeviceById(bool isDefaultDevice, const std::string &device_id, bool input)
96+
{
97+
ComPtr<IMMDeviceEnumerator> enumerator;
98+
ComPtr<IMMDevice> device;
99+
100+
HRESULT res = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
101+
(void **)enumerator.Assign());
102+
if (FAILED(res))
103+
return nullptr;
104+
105+
if (isDefaultDevice) {
106+
HRESULT res = enumerator->GetDefaultAudioEndpoint(input ? eCapture : eRender,
107+
input ? eCommunications : eConsole, device.Assign());
108+
if (FAILED(res))
109+
return nullptr;
110+
} else {
111+
wchar_t *w_id;
112+
os_utf8_to_wcs_ptr(device_id.c_str(), device_id.size(), &w_id);
113+
if (!w_id)
114+
throw "Failed to widen device id string";
115+
116+
const HRESULT res = enumerator->GetDevice(w_id, device.Assign());
117+
118+
bfree(w_id);
119+
120+
if (FAILED(res))
121+
return nullptr;
122+
}
123+
124+
return device.Detach();
125+
}

plugins/win-wasapi/enum-wasapi.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ struct AudioDeviceInfo {
3939

4040
std::string GetDeviceName(IMMDevice *device);
4141
void GetWASAPIAudioDevices(std::vector<AudioDeviceInfo> &devices, bool input);
42+
IMMDevice *GetMMDeviceById(bool isDefaultDevice, const std::string &device_id, bool input);

plugins/win-wasapi/win-wasapi.cpp

Lines changed: 268 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <avrt.h>
2222
#include <RTWorkQ.h>
2323
#include <wrl/implements.h>
24+
#include <devicetopology.h>
2425

2526
using namespace std;
2627

@@ -289,6 +290,11 @@ class WASAPISource {
289290
obs_weak_source_release(reroute_target);
290291
reroute_target = obs_source_get_weak_source(target);
291292
}
293+
294+
template<typename F, typename... Fs> static void DeviceTopologyTraversal(IMMDevice *device, F &&f, Fs &&...fs);
295+
template<typename F, typename... Fs>
296+
static void DeviceTopologyTraversal(IConnector *connect, F &&f, Fs &&...fs);
297+
template<typename F, typename... Fs> static void DeviceTopologyTraversal(IPart *part, F &&f, Fs &&...fs);
292298
};
293299

294300
WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, SourceType type)
@@ -541,11 +547,76 @@ void WASAPISource::Update(obs_data_t *settings)
541547
(title != params.title) || (executable != params.executable))
542548
: (device_id.compare(params.device_id) != 0);
543549

550+
std::string deviceId = params.device_id;
551+
bool isDefault = params.isDefaultDevice;
544552
UpdateSettings(std::move(params));
545553
LogSettings();
546554

547555
if (restart)
548556
SetEvent(restartSignal);
557+
558+
IMMDevice *device = GetMMDeviceById(isDefault, deviceId, sourceType == SourceType::Input);
559+
WASAPISource::DeviceTopologyTraversal(device, [settings](IPart *part) {
560+
GUID SubType = {};
561+
part->GetSubType(&SubType);
562+
LPWSTR name = nullptr;
563+
part->GetName(&name);
564+
if (SubType == KSNODETYPE_VOLUME) {
565+
IAudioVolumeLevel *audioVolumeLevel = nullptr;
566+
part->Activate(CLSCTX_ALL, __uuidof(IAudioVolumeLevel), (void **)&audioVolumeLevel);
567+
if (audioVolumeLevel) {
568+
float pfMinLevelDB = 0.0f;
569+
float pfMaxLevelDB = 0.0f;
570+
float pfStepping = 0.0f;
571+
HRESULT hr =
572+
audioVolumeLevel->GetLevelRange(0, &pfMinLevelDB, &pfMaxLevelDB, &pfStepping);
573+
if (SUCCEEDED(hr)) {
574+
size_t len = wcslen(name);
575+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
576+
std::string utf8_name;
577+
utf8_name.resize(size);
578+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
579+
float pfLevelDB = (float)obs_data_get_double(settings, utf8_name.c_str());
580+
audioVolumeLevel->SetLevel(0, pfLevelDB, nullptr);
581+
}
582+
audioVolumeLevel->Release();
583+
}
584+
} else if (SubType == KSNODETYPE_MUTE) {
585+
IAudioMute *audioMute = nullptr;
586+
part->Activate(CLSCTX_ALL, __uuidof(IAudioMute), (void **)&audioMute);
587+
if (audioMute != nullptr) {
588+
size_t len = wcslen(name);
589+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
590+
std::string utf8_name;
591+
utf8_name.resize(size);
592+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
593+
bool mute = obs_data_get_bool(settings, utf8_name.c_str());
594+
audioMute->SetMute(mute, nullptr);
595+
audioMute->Release();
596+
}
597+
} else if (SubType == KSNODETYPE_AGC) {
598+
IAudioAutoGainControl *audioAutoGainControl = nullptr;
599+
part->Activate(CLSCTX_ALL, __uuidof(IAudioAutoGainControl), (void **)&audioAutoGainControl);
600+
601+
if (audioAutoGainControl != nullptr) {
602+
size_t len = wcslen(name);
603+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
604+
std::string utf8_name;
605+
utf8_name.resize(size);
606+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
607+
bool enable = obs_data_get_bool(settings, utf8_name.c_str());
608+
audioAutoGainControl->SetEnabled(enable, nullptr);
609+
audioAutoGainControl->Release();
610+
}
611+
}
612+
613+
if (!name) {
614+
CoTaskMemFree(name);
615+
}
616+
});
617+
if (device) {
618+
device->Release();
619+
}
549620
}
550621

551622
void WASAPISource::OnWindowChanged(obs_data_t *settings)
@@ -630,6 +701,108 @@ static DWORD GetSpeakerChannelMask(speaker_layout layout)
630701
return (DWORD)layout;
631702
}
632703

704+
template<typename F, typename... Fs> void WASAPISource::DeviceTopologyTraversal(IPart *part, F &&f, Fs &&...fs)
705+
{
706+
if (part == nullptr) {
707+
return;
708+
}
709+
IPartsList *partList = nullptr;
710+
part->EnumPartsOutgoing(&partList);
711+
if (partList == nullptr) {
712+
return;
713+
}
714+
IPart *pPartNext = NULL;
715+
partList->GetPart(0, &pPartNext);
716+
if (pPartNext == nullptr) {
717+
partList->Release();
718+
return;
719+
}
720+
PartType partType;
721+
HRESULT hr = pPartNext->GetPartType(&partType);
722+
if (FAILED(hr)) {
723+
partList->Release();
724+
pPartNext->Release();
725+
return;
726+
}
727+
728+
if (partType == PartType::Subunit) {
729+
std::bind(std::forward<F>(f), pPartNext, std::forward<Fs>(fs)...)();
730+
DeviceTopologyTraversal(pPartNext, f, fs...);
731+
pPartNext->Release();
732+
}
733+
734+
if (partType == PartType::Connector) {
735+
IConnector *connect = nullptr;
736+
hr = pPartNext->QueryInterface(__uuidof(IConnector), (void **)&connect);
737+
DeviceTopologyTraversal(connect, f, fs...);
738+
}
739+
}
740+
741+
template<typename F, typename... Fs> void WASAPISource::DeviceTopologyTraversal(IConnector *connect, F &&f, Fs &&...fs)
742+
{
743+
if (connect == nullptr) {
744+
return;
745+
}
746+
747+
BOOL IsConnected = FALSE;
748+
HRESULT hr = connect->IsConnected(&IsConnected);
749+
if (FAILED(hr)) {
750+
return;
751+
}
752+
753+
if (IsConnected == FALSE) {
754+
ConnectorType connType;
755+
hr = connect->GetType(&connType);
756+
757+
if (FAILED(hr)) {
758+
return;
759+
}
760+
if (connType == Software_IO) {
761+
return;
762+
}
763+
}
764+
765+
IConnector *connectedTo = nullptr;
766+
hr = connect->GetConnectedTo(&connectedTo);
767+
if (FAILED(hr) || connectedTo == nullptr) {
768+
return;
769+
}
770+
771+
IPart *Part = nullptr;
772+
773+
connectedTo->QueryInterface(__uuidof(IPart), (void **)&Part);
774+
DeviceTopologyTraversal(Part, f, fs...);
775+
if (Part != nullptr) {
776+
Part->Release();
777+
}
778+
779+
connectedTo->Release();
780+
}
781+
782+
template<typename F, typename... Fs> void WASAPISource::DeviceTopologyTraversal(IMMDevice *device, F &&f, Fs &&...fs)
783+
{
784+
if (device == nullptr) {
785+
return;
786+
}
787+
IDeviceTopology *pDeviceTopology = nullptr;
788+
HRESULT hr = device->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, nullptr, (void **)&pDeviceTopology);
789+
if (FAILED(hr))
790+
return;
791+
UINT nConnectorCount = 0;
792+
hr = pDeviceTopology->GetConnectorCount(&nConnectorCount);
793+
if (FAILED(hr))
794+
return;
795+
for (UINT i = 0; i < nConnectorCount; i++) {
796+
IConnector *pConnector = nullptr;
797+
pDeviceTopology->GetConnector(i, &pConnector);
798+
DeviceTopologyTraversal(pConnector, f, fs...);
799+
if (pConnector) {
800+
pConnector->Release();
801+
}
802+
}
803+
pDeviceTopology->Release();
804+
}
805+
633806
ComPtr<IAudioClient> WASAPISource::InitClient(IMMDevice *device, SourceType type, DWORD process_id,
634807
PFN_ActivateAudioInterfaceAsync activate_audio_interface_async,
635808
speaker_layout &speakers, audio_format &format, uint32_t &samples_per_sec)
@@ -707,6 +880,7 @@ ComPtr<IAudioClient> WASAPISource::InitClient(IMMDevice *device, SourceType type
707880
DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
708881
if (type != SourceType::Input)
709882
flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
883+
710884
res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, BUFFER_TIME_100NS, 0, pFormat, nullptr);
711885
if (FAILED(res))
712886
throw HRError("Failed to initialize audio client", res);
@@ -1454,8 +1628,100 @@ static bool UpdateWASAPIMethod(obs_properties_t *props, obs_property_t *, obs_da
14541628
return true;
14551629
}
14561630

1457-
static obs_properties_t *GetWASAPIPropertiesInput(void *)
1631+
static bool DeviceSelectionChanged(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
1632+
{
1633+
string id = obs_data_get_string(settings, OPT_DEVICE_ID);
1634+
obs_property_t *property = obs_properties_first(props);
1635+
1636+
while (property) {
1637+
std::string name = obs_property_name(property);
1638+
obs_property_next(&property);
1639+
if (name != OPT_DEVICE_ID && name != OPT_USE_DEVICE_TIMING) {
1640+
obs_properties_remove_by_name(props, name.c_str());
1641+
}
1642+
}
1643+
IMMDevice *device = GetMMDeviceById(id == "default" ? true : false, id, true);
1644+
WASAPISource::DeviceTopologyTraversal(device, [props, settings](IPart *part) {
1645+
GUID SubType = {};
1646+
part->GetSubType(&SubType);
1647+
LPWSTR name = nullptr;
1648+
part->GetName(&name);
1649+
if (SubType == KSNODETYPE_VOLUME) {
1650+
IAudioVolumeLevel *audioVolumeLevel = nullptr;
1651+
part->Activate(CLSCTX_ALL, __uuidof(IAudioVolumeLevel), (void **)&audioVolumeLevel);
1652+
if (audioVolumeLevel) {
1653+
float pfMinLevelDB = 0.0f;
1654+
float pfMaxLevelDB = 0.0f;
1655+
float pfStepping = 0.0f;
1656+
HRESULT hr =
1657+
audioVolumeLevel->GetLevelRange(0, &pfMinLevelDB, &pfMaxLevelDB, &pfStepping);
1658+
if (SUCCEEDED(hr)) {
1659+
size_t len = wcslen(name);
1660+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
1661+
std::string utf8_name;
1662+
utf8_name.resize(size);
1663+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
1664+
float pfLevelDB = 0.0f;
1665+
audioVolumeLevel->GetLevel(0, &pfLevelDB);
1666+
obs_property_t *p = obs_properties_add_float_slider(props, utf8_name.c_str(),
1667+
utf8_name.c_str(),
1668+
pfMinLevelDB, pfMaxLevelDB,
1669+
pfStepping);
1670+
obs_data_set_double(settings, utf8_name.c_str(), pfLevelDB);
1671+
}
1672+
audioVolumeLevel->Release();
1673+
}
1674+
} else if (SubType == KSNODETYPE_MUTE) {
1675+
IAudioMute *audioMute = nullptr;
1676+
part->Activate(CLSCTX_ALL, __uuidof(IAudioMute), (void **)&audioMute);
1677+
if (audioMute != nullptr) {
1678+
size_t len = wcslen(name);
1679+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
1680+
std::string utf8_name;
1681+
utf8_name.resize(size);
1682+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
1683+
1684+
obs_property_t *p =
1685+
obs_properties_add_bool(props, utf8_name.c_str(), utf8_name.c_str());
1686+
BOOL isMute = FALSE;
1687+
audioMute->GetMute(&isMute);
1688+
obs_data_set_bool(settings, utf8_name.c_str(), !!isMute);
1689+
1690+
audioMute->Release();
1691+
}
1692+
} else if (SubType == KSNODETYPE_AGC) {
1693+
IAudioAutoGainControl *audioAutoGainControl = nullptr;
1694+
part->Activate(CLSCTX_ALL, __uuidof(IAudioAutoGainControl), (void **)&audioAutoGainControl);
1695+
1696+
if (audioAutoGainControl != nullptr) {
1697+
size_t len = wcslen(name);
1698+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
1699+
std::string utf8_name;
1700+
utf8_name.resize(size);
1701+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
1702+
1703+
obs_property_t *p =
1704+
obs_properties_add_bool(props, utf8_name.c_str(), utf8_name.c_str());
1705+
BOOL isEnabled = FALSE;
1706+
audioAutoGainControl->GetEnabled(&isEnabled);
1707+
obs_data_set_bool(settings, utf8_name.c_str(), !!isEnabled);
1708+
audioAutoGainControl->Release();
1709+
}
1710+
}
1711+
1712+
if (!name) {
1713+
CoTaskMemFree(name);
1714+
}
1715+
});
1716+
if (device) {
1717+
device->Release();
1718+
}
1719+
return true;
1720+
}
1721+
1722+
static obs_properties_t *GetWASAPIPropertiesInput(void *data)
14581723
{
1724+
WASAPISource *source = (WASAPISource *)data;
14591725
obs_properties_t *props = obs_properties_create();
14601726
vector<AudioDeviceInfo> devices;
14611727

@@ -1473,7 +1739,7 @@ static obs_properties_t *GetWASAPIPropertiesInput(void *)
14731739
}
14741740

14751741
obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming"));
1476-
1742+
obs_property_set_modified_callback(device_prop, DeviceSelectionChanged);
14771743
return props;
14781744
}
14791745

0 commit comments

Comments
 (0)