2121#include < avrt.h>
2222#include < RTWorkQ.h>
2323#include < wrl/implements.h>
24+ #include < devicetopology.h>
2425
2526using 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
294300WASAPISource::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
551622void 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+
633806ComPtr<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