Skip to content

Commit e5904fb

Browse files
committed
Implement broadcast of KSEVENT_PINCAPS_FORMATCHANGE. Fixes #2
1 parent 5736d99 commit e5904fb

7 files changed

Lines changed: 84 additions & 36 deletions

File tree

SarAsio/mmwrapper.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@ typedef HRESULT STDAPICALLTYPE DllGetClassObjectFn(
2626

2727
#define MMDEVAPI_PATH "%SystemRoot%\\System32\\MMDevApi.dll"
2828

29-
#undef DEFINE_PROPERTYKEY
30-
#define DEFINE_PROPERTYKEY(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8,pid) \
31-
EXTERN_C const PROPERTYKEY name = {\
32-
{ l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid }
33-
34-
DEFINE_PROPERTYKEY(PKEY_SynchronousAudioRouter_EndpointId,
35-
0xf4b15b6f, 0x8c3f, 0x48b6, 0xa1, 0x15, 0x42, 0xfd, 0xe1, 0x9e, 0xf0, 0x5b,
36-
0);
29+
const PROPERTYKEY PKEY_SynchronousAudioRouter_EndpointId =
30+
{
31+
{ 0xf4b15b6f, 0x8c3f, 0x48b6,
32+
{ 0xa1, 0x15, 0x42, 0xfd, 0xe1, 0x9e, 0xf0, 0x5b } },
33+
0
34+
};
3735

3836
SarMMDeviceEnumerator::SarMMDeviceEnumerator()
3937
{
@@ -159,7 +157,7 @@ HRESULT STDMETHODCALLTYPE SarMMDeviceEnumerator::GetDefaultAudioEndpoint(
159157
for (UINT i = 0; i < deviceCount && !foundDevice; ++i) {
160158
CComPtr<IMMDevice> device;
161159
CComPtr<IPropertyStore> ps;
162-
PROPVARIANT pvalue;
160+
PROPVARIANT pvalue = {};
163161

164162
if (!SUCCEEDED(devices->Item(i, &device))) {
165163
continue;

SarAsio/mmwrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
namespace Sar {
2323

24+
extern const PROPERTYKEY PKEY_SynchronousAudioRouter_EndpointId;
25+
2426
struct ATL_NO_VTABLE SarMMDeviceEnumerator:
2527
public CComObjectRootEx<CComMultiThreadModel>,
2628
public CComCoClass<SarMMDeviceEnumerator, &__uuidof(MMDeviceEnumerator)>,

SarAsio/sarclient.cpp

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// along with SynchronousAudioRouter. If not, see <http://www.gnu.org/licenses/>.
1616

1717
#include "stdafx.h"
18+
#include "mmwrapper.h"
1819
#include "sarclient.h"
1920
#include "utility.h"
2021

@@ -35,6 +36,13 @@ void SarClient::tick(long bufferIndex)
3536
ATLASSERT(bufferIndex == 0 || bufferIndex == 1);
3637
bool hasUpdatedNotificationHandles = false;
3738

39+
if (_updateSampleRateOnTick.exchange(false)) {
40+
DWORD dummy;
41+
42+
DeviceIoControl(_device, SAR_SEND_FORMAT_CHANGE_EVENT,
43+
nullptr, 0, nullptr, 0, &dummy, nullptr);
44+
}
45+
3846
// for each endpoint
3947
// read isActive, generation and buffer offset/size/position
4048
// if offset/size invalid, skip endpoint (fill asio buffers with 0)
@@ -304,6 +312,7 @@ bool SarClient::openMmNotificationClient()
304312
}
305313

306314
_mmNotificationClient->AddRef();
315+
_mmNotificationClient->setClient(shared_from_this());
307316

308317
if (FAILED(_mmEnumerator->RegisterEndpointNotificationCallback(
309318
_mmNotificationClient))) {
@@ -522,31 +531,68 @@ HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDeviceStateChanged(
522531
_In_ LPCWSTR pwstrDeviceId,
523532
_In_ DWORD dwNewState)
524533
{
525-
std::ostringstream os;
534+
// When a SAR endpoint is re-activated after its initial creation, its
535+
// supported sample rate may be different. To force the audio engine to
536+
// notice the possible format change, we listen for device state change
537+
// events and tell the kernel mode driver to broadcast a
538+
// KSEVENT_PINCAPS_FORMATCHANGE event, which causes the audio engine to
539+
// re-query the pin capabilities. This isn't needed for newly added
540+
// endpoints or non-SAR endpoints, so we filter out those events.
541+
if (dwNewState != DEVICE_STATE_ACTIVE) {
542+
return S_OK;
543+
}
544+
545+
do {
546+
if (auto client = _client.lock()) {
547+
CComPtr<IMMDeviceEnumerator> mmEnumerator;
548+
CComPtr<IMMDevice> device;
549+
CComPtr<IPropertyStore> ps;
550+
PROPVARIANT pvalue = {};
551+
552+
// This seems a bit shady, but MSDN's device events example
553+
// initializes COM the same way. It's not clear what the ownership
554+
// of the thread that delivers the IMMNotificationClient events is.
555+
CoInitialize(NULL);
556+
557+
if (FAILED(CoCreateInstance(
558+
__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
559+
__uuidof(IMMDeviceEnumerator), (LPVOID *)&mmEnumerator))) {
560+
561+
break;
562+
}
563+
564+
if (FAILED(mmEnumerator->GetDevice(pwstrDeviceId, &device))) {
565+
break;
566+
}
526567

527-
os << "OnDeviceStateChanged(" << TCHARToUTF8(pwstrDeviceId)
528-
<< ", " << dwNewState << ")";
529-
OutputDebugStringA(os.str().c_str());
568+
if (FAILED(device->OpenPropertyStore(STGM_READ, &ps))) {
569+
break;
570+
}
571+
572+
if (FAILED(ps->GetValue(
573+
PKEY_SynchronousAudioRouter_EndpointId, &pvalue))) {
574+
575+
break;
576+
}
577+
578+
client->updateSampleRateOnTick();
579+
PropVariantClear(&pvalue);
580+
}
581+
} while(false);
582+
583+
CoUninitialize();
530584
return S_OK;
531585
}
532586

533587
HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDeviceAdded(
534588
_In_ LPCWSTR pwstrDeviceId)
535589
{
536-
std::ostringstream os;
537-
538-
os << "OnDeviceAdded(" << TCHARToUTF8(pwstrDeviceId) << ")";
539-
OutputDebugStringA(os.str().c_str());
540590
return S_OK;
541591
}
542592

543593
HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDeviceRemoved(
544594
_In_ LPCWSTR pwstrDeviceId)
545595
{
546-
std::ostringstream os;
547-
548-
os << "OnDeviceRemoved(" << TCHARToUTF8(pwstrDeviceId) << ")";
549-
OutputDebugStringA(os.str().c_str());
550596
return S_OK;
551597
}
552598

@@ -555,25 +601,13 @@ HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDefaultDeviceChanged(
555601
_In_ ERole role,
556602
_In_ LPCWSTR pwstrDefaultDeviceId)
557603
{
558-
std::ostringstream os;
559-
560-
os << "OnDefaultDeviceChanged(" << flow << ", " << role << ", "
561-
<< TCHARToUTF8(pwstrDefaultDeviceId) << ")";
562-
OutputDebugStringA(os.str().c_str());
563604
return S_OK;
564605
}
565606

566607
HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnPropertyValueChanged(
567608
_In_ LPCWSTR pwstrDeviceId,
568609
_In_ const PROPERTYKEY key)
569610
{
570-
std::ostringstream os;
571-
CComBSTR bstr(key.fmtid);
572-
std::wstring wstr(bstr);
573-
574-
os << "OnPropertyValueChanged(" << TCHARToUTF8(pwstrDeviceId)
575-
<< ", " << TCHARToUTF8(wstr.c_str()) << ")";
576-
OutputDebugStringA(os.str().c_str());
577611
return S_OK;
578612
}
579613

SarAsio/sarclient.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,18 @@ struct BufferConfig
3030
std::vector<std::vector<void *>> asioBuffers;
3131
};
3232

33-
struct SarClient
33+
struct SarClient: public std::enable_shared_from_this<SarClient>
3434
{
3535
SarClient(
3636
const DriverConfig& driverConfig,
3737
const BufferConfig& bufferConfig);
3838
void tick(long bufferIndex);
3939
bool start();
4040
void stop();
41+
void updateSampleRateOnTick()
42+
{
43+
_updateSampleRateOnTick = true;
44+
}
4145

4246
private:
4347
struct NotificationHandle
@@ -69,6 +73,11 @@ struct SarClient
6973

7074
DECLARE_NO_REGISTRY()
7175

76+
void setClient(std::weak_ptr<SarClient> client)
77+
{
78+
_client = client;
79+
}
80+
7281
virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
7382
_In_ LPCWSTR pwstrDeviceId,
7483
_In_ DWORD dwNewState) override;
@@ -83,6 +92,9 @@ struct SarClient
8392
virtual HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
8493
_In_ LPCWSTR pwstrDeviceId,
8594
_In_ const PROPERTYKEY key) override;
95+
96+
private:
97+
std::weak_ptr<SarClient> _client;
8698
};
8799

88100
bool openControlDevice();
@@ -117,6 +129,7 @@ struct SarClient
117129
CComPtr<IMMDeviceEnumerator> _mmEnumerator;
118130
CComObject<NotificationClient> *_mmNotificationClient = nullptr;
119131
bool _mmNotificationClientRegistered = false;
132+
std::atomic<bool> _updateSampleRateOnTick = false;
120133
};
121134

122135
} // namespace Sar

SarAsio/stdafx.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <atlcom.h>
3737
#include <atlstr.h>
3838

39+
#include <atomic>
3940
#include <codecvt>
4041
#include <cstddef>
4142
#include <cstdint>

SarAsio/wrapper.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ AsioStatus SarAsioWrapper::start()
7474
return AsioStatus::NotPresent;
7575
}
7676

77-
_sar = std::make_unique<SarClient>(_config, _bufferConfig);
77+
_sar = std::make_shared<SarClient>(_config, _bufferConfig);
7878

7979
if (!_sar->start()) {
8080
OutputDebugString(_T("Failed to start SAR"));

SarAsio/wrapper.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ struct ATL_NO_VTABLE SarAsioWrapper:
9898
HWND _hwnd;
9999
DriverConfig _config;
100100
BufferConfig _bufferConfig;
101-
std::unique_ptr<SarClient> _sar;
101+
std::shared_ptr<SarClient> _sar;
102102
CComPtr<IASIO> _innerDriver;
103103
std::vector<VirtualChannel> _virtualInputs;
104104
std::vector<VirtualChannel> _virtualOutputs;

0 commit comments

Comments
 (0)