Skip to content

Commit fb53304

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

File tree

3 files changed

+267
-2
lines changed

3 files changed

+267
-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: 233 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)
@@ -546,6 +554,12 @@ void WASAPISource::Update(obs_data_t *settings)
546554

547555
if (restart)
548556
SetEvent(restartSignal);
557+
558+
IMMDevice *device = GetMMDeviceById(params.isDefaultDevice, params.device_id, sourceType == SourceType::Input);
559+
WASAPISource::DeviceTopologyTraversal(device, nullptr, settings, true);
560+
if (device) {
561+
device->Release();
562+
}
549563
}
550564

551565
void WASAPISource::OnWindowChanged(obs_data_t *settings)
@@ -630,6 +644,201 @@ static DWORD GetSpeakerChannelMask(speaker_layout layout)
630644
return (DWORD)layout;
631645
}
632646

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

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

@@ -1473,7 +1704,7 @@ static obs_properties_t *GetWASAPIPropertiesInput(void *)
14731704
}
14741705

14751706
obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming"));
1476-
1707+
obs_property_set_modified_callback(device_prop, DeviceSelectionChanged);
14771708
return props;
14781709
}
14791710

0 commit comments

Comments
 (0)