Skip to content

Commit 2efbbaf

Browse files
author
hongqingwan
committed
win-wasapi: Add microphone volume adjustment feature
1 parent 0b12296 commit 2efbbaf

File tree

3 files changed

+269
-2
lines changed

3 files changed

+269
-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: 235 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,13 @@ class WASAPISource {
289290
obs_weak_source_release(reroute_target);
290291
reroute_target = obs_source_get_weak_source(target);
291292
}
293+
294+
static void DeviceTopologyTraversal(IMMDevice *device, obs_properties_t *props, obs_data_t *settings,
295+
bool modifyBySettins);
296+
static void DeviceTopologyTraversal(IConnector *connect, obs_properties_t *props, obs_data_t *settings,
297+
bool modifyBySettins);
298+
static void DeviceTopologyTraversal(IPart *part, obs_properties_t *props, obs_data_t *settings,
299+
bool modifyBySettins);
292300
};
293301

294302
WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, SourceType type)
@@ -541,11 +549,19 @@ void WASAPISource::Update(obs_data_t *settings)
541549
(title != params.title) || (executable != params.executable))
542550
: (device_id.compare(params.device_id) != 0);
543551

552+
std::string deviceId = params.device_id;
553+
bool isDefault = params.isDefaultDevice;
544554
UpdateSettings(std::move(params));
545555
LogSettings();
546556

547557
if (restart)
548558
SetEvent(restartSignal);
559+
560+
IMMDevice *device = GetMMDeviceById(isDefault, deviceId, sourceType == SourceType::Input);
561+
WASAPISource::DeviceTopologyTraversal(device, nullptr, settings, true);
562+
if (device) {
563+
device->Release();
564+
}
549565
}
550566

551567
void WASAPISource::OnWindowChanged(obs_data_t *settings)
@@ -630,6 +646,201 @@ static DWORD GetSpeakerChannelMask(speaker_layout layout)
630646
return (DWORD)layout;
631647
}
632648

649+
void WASAPISource::DeviceTopologyTraversal(IPart *part, obs_properties_t *props, obs_data_t *settings,
650+
bool modifyBySettins)
651+
{
652+
if (part == nullptr) {
653+
return;
654+
}
655+
IPartsList *partList = nullptr;
656+
part->EnumPartsOutgoing(&partList);
657+
if (partList == nullptr) {
658+
return;
659+
}
660+
IPart *pPartNext = NULL;
661+
partList->GetPart(0, &pPartNext);
662+
if (pPartNext == nullptr) {
663+
partList->Release();
664+
return;
665+
}
666+
PartType partType;
667+
HRESULT hr = pPartNext->GetPartType(&partType);
668+
if (FAILED(hr)) {
669+
partList->Release();
670+
pPartNext->Release();
671+
return;
672+
}
673+
674+
if (partType == PartType::Subunit) {
675+
IKsJackDescription **Jack = nullptr;
676+
hr = pPartNext->Activate(CLSCTX_INPROC_SERVER, __uuidof(IKsJackDescription), (void **)&Jack);
677+
GUID SubType = {};
678+
pPartNext->GetSubType(&SubType);
679+
LPWSTR name = nullptr;
680+
pPartNext->GetName(&name);
681+
if (SubType == KSNODETYPE_LOUDNESS) {
682+
// TODO
683+
} else if (SubType == KSNODETYPE_ADC) {
684+
// TODO
685+
} else if (SubType == KSNODETYPE_VOLUME) {
686+
IAudioVolumeLevel *audioVolumeLevel = nullptr;
687+
pPartNext->Activate(CLSCTX_ALL, __uuidof(IAudioVolumeLevel), (void **)&audioVolumeLevel);
688+
if (audioVolumeLevel) {
689+
float pfMinLevelDB = 0.0f;
690+
float pfMaxLevelDB = 0.0f;
691+
float pfStepping = 0.0f;
692+
hr = audioVolumeLevel->GetLevelRange(0, &pfMinLevelDB, &pfMaxLevelDB, &pfStepping);
693+
if (SUCCEEDED(hr)) {
694+
size_t len = wcslen(name);
695+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
696+
std::string utf8_name;
697+
utf8_name.resize(size);
698+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
699+
if (modifyBySettins) {
700+
float pfLevelDB =
701+
(float)obs_data_get_double(settings, utf8_name.c_str());
702+
audioVolumeLevel->SetLevel(0, pfLevelDB, nullptr);
703+
} else {
704+
float pfLevelDB = 0.0f;
705+
audioVolumeLevel->GetLevel(0, &pfLevelDB);
706+
obs_property_t *p = obs_properties_add_float_slider(
707+
props, utf8_name.c_str(), utf8_name.c_str(), pfMinLevelDB,
708+
pfMaxLevelDB, pfStepping);
709+
obs_data_set_double(settings, utf8_name.c_str(), pfLevelDB);
710+
}
711+
}
712+
audioVolumeLevel->Release();
713+
}
714+
} else if (SubType == KSNODETYPE_MUTE) {
715+
IAudioMute *audioMute = nullptr;
716+
pPartNext->Activate(CLSCTX_ALL, __uuidof(IAudioMute), (void **)&audioMute);
717+
if (audioMute != nullptr) {
718+
size_t len = wcslen(name);
719+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
720+
std::string utf8_name;
721+
utf8_name.resize(size);
722+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
723+
if (modifyBySettins) {
724+
bool mute = obs_data_get_bool(settings, utf8_name.c_str());
725+
audioMute->SetMute(mute, nullptr);
726+
} else {
727+
obs_property_t *p =
728+
obs_properties_add_bool(props, utf8_name.c_str(), utf8_name.c_str());
729+
BOOL isMute = FALSE;
730+
audioMute->GetMute(&isMute);
731+
obs_data_set_bool(settings, utf8_name.c_str(), !!isMute);
732+
}
733+
audioMute->Release();
734+
}
735+
} else if (SubType == KSNODETYPE_AGC) {
736+
IAudioAutoGainControl *audioAutoGainControl = nullptr;
737+
pPartNext->Activate(CLSCTX_ALL, __uuidof(IAudioAutoGainControl),
738+
(void **)&audioAutoGainControl);
739+
740+
if (audioAutoGainControl != nullptr) {
741+
size_t len = wcslen(name);
742+
size_t size = os_wcs_to_utf8(name, len, nullptr, 0) + 1;
743+
std::string utf8_name;
744+
utf8_name.resize(size);
745+
os_wcs_to_utf8(name, len, &utf8_name[0], size);
746+
if (modifyBySettins) {
747+
bool enable = obs_data_get_bool(settings, utf8_name.c_str());
748+
audioAutoGainControl->SetEnabled(enable, nullptr);
749+
} else {
750+
obs_property_t *p =
751+
obs_properties_add_bool(props, utf8_name.c_str(), utf8_name.c_str());
752+
BOOL isEnabled = FALSE;
753+
audioAutoGainControl->GetEnabled(&isEnabled);
754+
obs_data_set_bool(settings, utf8_name.c_str(), !!isEnabled);
755+
}
756+
audioAutoGainControl->Release();
757+
}
758+
} else if (SubType == KSNODETYPE_TONE) {
759+
// TODO
760+
}
761+
762+
if (!name) {
763+
CoTaskMemFree(name);
764+
}
765+
766+
DeviceTopologyTraversal(pPartNext, props, settings, modifyBySettins);
767+
pPartNext->Release();
768+
}
769+
770+
if (partType == PartType::Connector) {
771+
IConnector *connect = nullptr;
772+
hr = pPartNext->QueryInterface(__uuidof(IConnector), (void **)&connect);
773+
DeviceTopologyTraversal(connect, props, settings, modifyBySettins);
774+
}
775+
}
776+
777+
void WASAPISource::DeviceTopologyTraversal(IConnector *connect, obs_properties_t *props, obs_data_t *settings,
778+
bool modifyBySettins)
779+
{
780+
if (connect == nullptr) {
781+
return;
782+
}
783+
784+
BOOL IsConnected = FALSE;
785+
HRESULT hr = connect->IsConnected(&IsConnected);
786+
if (FAILED(hr)) {
787+
return;
788+
}
789+
790+
if (IsConnected == FALSE) {
791+
ConnectorType connType;
792+
hr = connect->GetType(&connType);
793+
794+
if (FAILED(hr)) {
795+
return;
796+
}
797+
if (connType == Software_IO) {
798+
return;
799+
}
800+
}
801+
802+
IConnector *connectedTo = nullptr;
803+
hr = connect->GetConnectedTo(&connectedTo);
804+
if (FAILED(hr) || connectedTo == nullptr) {
805+
return;
806+
}
807+
808+
IPart *Part = nullptr;
809+
810+
connectedTo->QueryInterface(__uuidof(IPart), (void **)&Part);
811+
DeviceTopologyTraversal(Part, props, settings, modifyBySettins);
812+
if (Part != nullptr) {
813+
Part->Release();
814+
}
815+
816+
connectedTo->Release();
817+
}
818+
819+
void WASAPISource::DeviceTopologyTraversal(IMMDevice *device, obs_properties_t *props, obs_data_t *settings,
820+
bool modifyBySettins)
821+
{
822+
if (device == nullptr) {
823+
return;
824+
}
825+
IDeviceTopology *pDeviceTopology = nullptr;
826+
HRESULT hr = device->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, nullptr, (void **)&pDeviceTopology);
827+
if (FAILED(hr))
828+
return;
829+
UINT nConnectorCount = 0;
830+
hr = pDeviceTopology->GetConnectorCount(&nConnectorCount);
831+
if (FAILED(hr))
832+
return;
833+
for (UINT i = 0; i < nConnectorCount; i++) {
834+
IConnector *pConnector = nullptr;
835+
pDeviceTopology->GetConnector(i, &pConnector);
836+
DeviceTopologyTraversal(pConnector, props, settings, modifyBySettins);
837+
if (pConnector) {
838+
pConnector->Release();
839+
}
840+
}
841+
pDeviceTopology->Release();
842+
}
843+
633844
ComPtr<IAudioClient> WASAPISource::InitClient(IMMDevice *device, SourceType type, DWORD process_id,
634845
PFN_ActivateAudioInterfaceAsync activate_audio_interface_async,
635846
speaker_layout &speakers, audio_format &format, uint32_t &samples_per_sec)
@@ -707,6 +918,7 @@ ComPtr<IAudioClient> WASAPISource::InitClient(IMMDevice *device, SourceType type
707918
DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
708919
if (type != SourceType::Input)
709920
flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
921+
710922
res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, BUFFER_TIME_100NS, 0, pFormat, nullptr);
711923
if (FAILED(res))
712924
throw HRError("Failed to initialize audio client", res);
@@ -1454,8 +1666,29 @@ static bool UpdateWASAPIMethod(obs_properties_t *props, obs_property_t *, obs_da
14541666
return true;
14551667
}
14561668

1457-
static obs_properties_t *GetWASAPIPropertiesInput(void *)
1669+
static bool DeviceSelectionChanged(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
14581670
{
1671+
string id = obs_data_get_string(settings, OPT_DEVICE_ID);
1672+
obs_property_t *property = obs_properties_first(props);
1673+
1674+
while (property) {
1675+
std::string name = obs_property_name(property);
1676+
obs_property_next(&property);
1677+
if (name != OPT_DEVICE_ID && name != OPT_USE_DEVICE_TIMING) {
1678+
obs_properties_remove_by_name(props, name.c_str());
1679+
}
1680+
}
1681+
IMMDevice *device = GetMMDeviceById(id == "default" ? true : false, id, true);
1682+
WASAPISource::DeviceTopologyTraversal(device, props, settings, false);
1683+
if (device) {
1684+
device->Release();
1685+
}
1686+
return true;
1687+
}
1688+
1689+
static obs_properties_t *GetWASAPIPropertiesInput(void *data)
1690+
{
1691+
WASAPISource *source = (WASAPISource *)data;
14591692
obs_properties_t *props = obs_properties_create();
14601693
vector<AudioDeviceInfo> devices;
14611694

@@ -1473,7 +1706,7 @@ static obs_properties_t *GetWASAPIPropertiesInput(void *)
14731706
}
14741707

14751708
obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming"));
1476-
1709+
obs_property_set_modified_callback(device_prop, DeviceSelectionChanged);
14771710
return props;
14781711
}
14791712

0 commit comments

Comments
 (0)