From 04793dc757c27deb73bd5ba59ac094555780e6e0 Mon Sep 17 00:00:00 2001 From: Mack Straight Date: Sat, 3 Oct 2026 14:36:01 -0400 Subject: [PATCH 1/3] test: check that endpoints pick up sample rate and channel changes Issue #2: after the host's sample rate changed, the audio engine kept the endpoint's old format until a reboot. Adds format-* scenarios to the issues group that run the same endpoint IDs at 48 kHz, 96 kHz, 48 kHz again and then in mono, and SarTest --expect-format, which fails streams whose engine format doesn't match --rate and --channels. Streams also record their engine sample rate. The channel step uses mono because Windows gives SAR endpoints with more than two channels a stereo shared-mode format even when they are new (seen with fresh IDs at four and eight channels), so a four-channel step can't distinguish a stale format from that. Co-Authored-By: Claude Opus 5.5 --- SarTest/SarTest.cpp | 8 ++++++++ SarTest/wasapi.cpp | 12 ++++++++++++ SarTest/wasapi.h | 5 +++++ tools/test/Invoke-SarTest.ps1 | 14 ++++++++++++++ tools/test/README.md | 4 +++- 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/SarTest/SarTest.cpp b/SarTest/SarTest.cpp index ee49d78..fcc8032 100644 --- a/SarTest/SarTest.cpp +++ b/SarTest/SarTest.cpp @@ -79,6 +79,8 @@ int usage() "Other options: --results --phase-timeout S --no-sarasio-log\n" " --keep-config (host/run: do not rewrite default.json)\n" " --app-routing (turn on application routing in default.json)\n" + " --expect-format (fail streams whose engine format isn't --rate\n" + " and --channels)\n" " --registered (use the COM-registered SarAsio instead of a path)\n" " --wait S --wait-gone S --restart-delay MS --rate HZ\n" " --min-valid RATIO --max-discontinuities N\n" @@ -132,6 +134,12 @@ WasapiOptions wasapiOptions(const Args& args, const EndpointLayout& layout) options.settleSeconds = args.getDouble(L"settle", 2.0); options.maxReopens = args.getInt(L"max-reopens", 20); options.maxTransitionFrames = args.getInt(L"max-transition", 4800); + + if (args.has(L"expect-format")) { + options.expectedRate = (unsigned long)args.getDouble(L"rate", 48000.0); + options.expectedChannels = layout.channels; + } + return options; } diff --git a/SarTest/wasapi.cpp b/SarTest/wasapi.cpp index 1429dad..cd99e67 100644 --- a/SarTest/wasapi.cpp +++ b/SarTest/wasapi.cpp @@ -408,6 +408,7 @@ class Stream _kind = classifyFormat(mix, &_stats.format); _stats.channels = mix->nChannels; + _stats.sampleRate = mix->nSamplesPerSec; _rate = mix->nSamplesPerSec ? (double)mix->nSamplesPerSec : 48000.0; if (_kind == SampleKind::Unsupported) { @@ -1076,6 +1077,7 @@ std::string StreamStats::toJson() const o.setString("name", name) .setString("direction", isCapture ? "capture" : "render") + .setInt("sampleRate", sampleRate) .setBool("started", started) .setBool("deviceInvalidated", deviceInvalidated) .setString("error", lastError == S_OK ? "" : hresultText(lastError)) @@ -1148,6 +1150,16 @@ static void evaluate(StreamStats *s, const WasapiOptions& options) return; } + if ((options.expectedRate && s->sampleRate != options.expectedRate) || + (options.expectedChannels && s->channels != options.expectedChannels)) { + char buf[128]; + + sprintf_s(buf, "engine format is %lu Hz, %d ch; expected %lu Hz, %d ch", + s->sampleRate, s->channels, options.expectedRate, options.expectedChannels); + s->failure = buf; + return; + } + if (!s->errorStage.empty()) { s->failure = s->errorStage; return; diff --git a/SarTest/wasapi.h b/SarTest/wasapi.h index 2b163c8..b2fd7cc 100644 --- a/SarTest/wasapi.h +++ b/SarTest/wasapi.h @@ -36,6 +36,7 @@ struct StreamStats std::string errorStage; std::string format; int channels = 0; + unsigned long sampleRate = 0; long long framesProcessed = 0; long long silentFrames = 0; long long startupSilentFrames = 0; @@ -104,6 +105,10 @@ struct WasapiOptions double settleSeconds = 2.0; int maxReopens = 20; long long maxTransitionFrames = 4800; + // Fail streams whose engine format differs from these (0: don't check), + // e.g. when Windows kept an endpoint's old format after a change. + unsigned long expectedRate = 0; + int expectedChannels = 0; }; struct WasapiResult diff --git a/tools/test/Invoke-SarTest.ps1 b/tools/test/Invoke-SarTest.ps1 index 613248f..09a8d56 100644 --- a/tools/test/Invoke-SarTest.ps1 +++ b/tools/test/Invoke-SarTest.ps1 @@ -352,6 +352,20 @@ if ($Scenarios -contains 'issues') { $summary.scenarios += Invoke-Scenario 'long-endpoint-name' ( @('host', '--endpoints', 1, '--duration', 2, '--id-prefix', 'SarTest-long', '--prefix', ('SarTestLongEndpointName' * 4)) + $common) -Timeout 120 + # Issue #2: after the host's sample rate or channel count changes, the + # audio engine kept the endpoint's old format. The driver encodes the + # format in each endpoint's interface name, so the same endpoint IDs in a + # new format are new endpoints to Windows. Same IDs at 48 kHz, 96 kHz, + # 48 kHz again, then mono; each must stream with the engine format + # matching. (Mono, because Windows gives SAR endpoints with more than two + # channels a stereo shared-mode format, so four channels can't be told + # apart from a stale two.) + $fmt = @('run', '--endpoints', 2, '--iterations', 1, '--duration', 3, '--prefix', 'SarTestFormat', + '--expect-format', '--sarasio', "`"$sarAsio`"") + $summary.scenarios += Invoke-Scenario 'format-48k' ($fmt + @('--rate', 48000, '--channels', 2)) + $summary.scenarios += Invoke-Scenario 'format-96k' ($fmt + @('--rate', 96000, '--channels', 2)) + $summary.scenarios += Invoke-Scenario 'format-48k-again' ($fmt + @('--rate', 48000, '--channels', 2)) + $summary.scenarios += Invoke-Scenario 'format-mono' ($fmt + @('--rate', 48000, '--channels', 1)) # Application routing on: from then on the driver's registry filter sees # every registry value query on the machine. With SarAsio's COM classes # registered, the harness's own WASAPI client also goes through SarAsio's diff --git a/tools/test/README.md b/tools/test/README.md index de59cb1..bd0c276 100644 --- a/tools/test/README.md +++ b/tools/test/README.md @@ -109,7 +109,9 @@ configured and checks that it still offers a stereo input and output. then gives it a new ID, and checks that the new ID shows the new name; the results record what Windows shows for the reused ID. `long-endpoint-name` runs a host whose endpoint names are longer than the driver's 63-character -limit. `app-routing` runs a loopback with application routing on and +limit. The `format-*` scenarios (#2) run the same endpoint IDs at 48 kHz, 96 kHz, +48 kHz again and then in mono, and fail if a stream's engine format doesn't +match (`--expect-format`). `app-routing` runs a loopback with application routing on and SarAsio's COM classes registered, which turns on the driver's registry filter. From cbcda7a9ce7763c9dda0130f9f37ec191d6827ec Mon Sep 17 00:00:00 2001 From: Mack Straight Date: Sat, 3 Oct 2026 14:48:54 -0400 Subject: [PATCH 2/3] Require Windows 10 Windows 7 and 8 have been out of support for years, the x64 driver already targets Windows 10 (its catalog is generated for 10_X64 only), and nothing tests anything older. Drop what was left for them: - The driver always encodes the format into endpoint IDs; the bare-ID path for Windows before 10 is gone. - The driver's remaining Win32/ARM/ARM64 configurations target Windows 10, and SarAsio and the installer actions build with _WIN32_WINNT for Windows 10. - README: Windows 10 or later, and no Windows 7 installation note. Co-Authored-By: Claude Opus 5.5 --- README.md | 8 +----- SarAsio/targetver.h | 2 +- SarInstallerActions/targetver.h | 2 +- .../SynchronousAudioRouter.vcxproj | 8 +++--- SynchronousAudioRouter/control.cpp | 28 ++++++------------- 5 files changed, 16 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index c3e2aa7..9d7eead 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Discord server at [https://discord.gg/9rwFdMW](https://discord.gg/9rwFdMW). ## System Requirements -* Windows 7 or later. If you are running a current version of Windows 10, Secure Boot is not supported. +* Windows 10 or later. Secure Boot is not supported. * An audio interface which supports ASIO. If your hardware doesn't come with a native ASIO driver, you may be able to use ASIO4ALL instead. * Digital audio workstation software. SAR is mainly tested using [REAPER](http://www.reaper.fm/). @@ -84,12 +84,6 @@ ASIO driver is closed. If you're using REAPER, make sure "Close audio device when stopped and application is inactive" in the Audio preferences is disabled. -## Windows 7 Installation Note - -Make sure you have Windows update [KB3033929](https://technet.microsoft.com/en-us/library/security/3033929.aspx) -installed prior to installing SAR, otherwise you will receive an error about -an unsigned driver. - ## Unsigned prereleased drivers note Prereleases of SAR are unsigned. That means that it is required to enable diff --git a/SarAsio/targetver.h b/SarAsio/targetver.h index 45badd6..3b09799 100644 --- a/SarAsio/targetver.h +++ b/SarAsio/targetver.h @@ -6,6 +6,6 @@ // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. #include -#define _WIN32_WINNT 0x0601 +#define _WIN32_WINNT 0x0A00 // Windows 10 #include diff --git a/SarInstallerActions/targetver.h b/SarInstallerActions/targetver.h index 9195471..c04ba81 100644 --- a/SarInstallerActions/targetver.h +++ b/SarInstallerActions/targetver.h @@ -2,6 +2,6 @@ #include -#define _WIN32_WINNT 0x0601 +#define _WIN32_WINNT 0x0A00 // Windows 10 #include diff --git a/SynchronousAudioRouter/SynchronousAudioRouter.vcxproj b/SynchronousAudioRouter/SynchronousAudioRouter.vcxproj index 66ae1c7..fdb7352 100644 --- a/SynchronousAudioRouter/SynchronousAudioRouter.vcxproj +++ b/SynchronousAudioRouter/SynchronousAudioRouter.vcxproj @@ -50,10 +50,10 @@ WindowsKernelModeDriver10.0 Driver WDM - Windows7 + Windows10 - Windows7 + Windows10 false WindowsKernelModeDriver10.0 Driver @@ -81,7 +81,7 @@ WDM - Windows7 + Windows10 false WindowsKernelModeDriver10.0 Driver @@ -95,7 +95,7 @@ WDM - Windows7 + Windows10 false WindowsKernelModeDriver10.0 Driver diff --git a/SynchronousAudioRouter/control.cpp b/SynchronousAudioRouter/control.cpp index 00695f8..c5d9e20 100644 --- a/SynchronousAudioRouter/control.cpp +++ b/SynchronousAudioRouter/control.cpp @@ -405,7 +405,6 @@ NTSTATUS SarCreateEndpoint( NTSTATUS status = STATUS_SUCCESS; PKSDEVICE ksDevice = KsGetDeviceForDeviceObject(device); BOOLEAN deviceNameAllocated = FALSE, deviceIdAllocated = FALSE; - RTL_OSVERSIONINFOW versionInfo = {}; SarEndpoint *endpoint; if (request->type != SAR_ENDPOINT_TYPE_RECORDING && @@ -472,17 +471,15 @@ NTSTATUS SarCreateEndpoint( deviceIdAllocated = TRUE; - // Windows 10 introduces a 'format cache' which at this time appears to - // not fully invalidate itself when KSEVENT_PINCAPS_FORMATCHANGE is - // sent. Work around this by encoding information about the sample rate, - // channel count and sample resolution into the endpoint ID. This means - // that WASAPI endpoints won't be stable across changes to these - // parameters on Windows 10 and e.g. applications that store them as - // configuration may lose track of them, but that seems better than "it - // fully stops working until you reinstall the driver". - RtlGetVersion(&versionInfo); - - if (versionInfo.dwMajorVersion >= 10) { + // Windows 10's audio engine caches each endpoint's format and doesn't + // fully invalidate that cache when KSEVENT_PINCAPS_FORMATCHANGE is sent. + // Encode the sample rate, channel count and sample resolution into the + // endpoint ID instead, so an endpoint in a new format is a new endpoint to + // Windows. WASAPI endpoints then aren't stable across changes to these + // parameters, and e.g. applications that store them as configuration may + // lose track of them, but that seems better than "it fully stops working + // until you reinstall the driver". + { DECLARE_UNICODE_STRING_SIZE(deviceIdBuffer, 256); RtlUnicodeStringPrintf(&deviceIdBuffer, @@ -491,13 +488,6 @@ NTSTATUS SarCreateEndpoint( status = SarStringDuplicate( &endpoint->deviceIdMangled, &deviceIdBuffer); - if (!NT_SUCCESS(status)) { - goto err_out; - } - } else { - status = SarStringDuplicate( - &endpoint->deviceIdMangled, &endpoint->deviceId); - if (!NT_SUCCESS(status)) { goto err_out; } From 6e48a900fc9eaa22dd85656b401c46a98d2b0281 Mon Sep 17 00:00:00 2001 From: Mack Straight Date: Sat, 3 Oct 2026 14:48:15 -0400 Subject: [PATCH 3/3] Remove the format change broadcast Whenever any audio endpoint became active, SarAsio had the driver send KSEVENT_PINCAPS_FORMATCHANGE to every SAR filter on its next tick, and the audio engine invalidated every stream open on a SAR endpoint. Its check for SAR endpoints passes for any endpoint (IPropertyStore returns S_OK and VT_EMPTY for a missing key), so starting a host invalidated its own fresh streams, and so did every endpoint Windows rebuilt and any other device appearing. The event was added for #2, so the audio engine would re-read the format of an endpoint recreated at a different sample rate. It wasn't enough on Windows 10, whose format cache ignored it, so the driver also puts the channel count, sample rate and sample size into each endpoint's KS reference string there, which makes such an endpoint a new one to Windows. With Windows 10 required, the event does nothing useful. Remove it, SarAsio's endpoint notification client that only existed to trigger it, the driver's SAR_SEND_FORMAT_CHANGE_EVENT IOCTL, and SarConfigure's -u switch that sent it by hand. On the test VM (16 endpoint pairs, three start/stop iterations), the loopback run saw 34 to 116 stream invalidations per run with the broadcast (3 runs) and none without it (8 runs). The format-* checks (same endpoint IDs at 48 kHz, 96 kHz and 48 kHz again) pass with and without it. Co-Authored-By: Claude Opus 5.5 --- SarAsio/sarclient.cpp | 141 ----------------------------- SarAsio/sarclient.h | 43 --------- SarConfigure/SarConfigure.cpp | 90 +++--------------- SarTest/wasapi.h | 7 +- SynchronousAudioRouter/control.cpp | 52 ----------- SynchronousAudioRouter/entry.cpp | 3 - SynchronousAudioRouter/sar.h | 6 +- 7 files changed, 16 insertions(+), 326 deletions(-) diff --git a/SarAsio/sarclient.cpp b/SarAsio/sarclient.cpp index 6e2bbc8..4b23cf8 100644 --- a/SarAsio/sarclient.cpp +++ b/SarAsio/sarclient.cpp @@ -43,13 +43,6 @@ void SarClient::tick(long bufferIndex) if (!_registers) return; - if (_updateSampleRateOnTick.exchange(false)) { - DWORD dummy; - - DeviceIoControl(_device, SAR_SEND_FORMAT_CHANGE_EVENT, - nullptr, 0, nullptr, 0, &dummy, nullptr); - } - // for each endpoint // read isActive, generation and buffer offset/size/position // if offset/size invalid, skip endpoint (fill asio buffers with 0) @@ -187,12 +180,6 @@ bool SarClient::start() return false; } - if (!openMmNotificationClient()) { - LOG(ERROR) << "Couldn't open MMDevice notification client"; - stop(); - return false; - } - if (!setBufferLayout()) { LOG(ERROR) << "Couldn't set layout"; stop(); @@ -214,21 +201,6 @@ bool SarClient::start() void SarClient::stop() { - if (_mmNotificationClientRegistered) { - _mmEnumerator->UnregisterEndpointNotificationCallback( - _mmNotificationClient); - _mmNotificationClientRegistered = false; - } - - if (_mmNotificationClient) { - _mmNotificationClient->Release(); - _mmNotificationClient = nullptr; - } - - if (_mmEnumerator) { - _mmEnumerator = nullptr; - } - if (_device != INVALID_HANDLE_VALUE) { _registersLock.lock(); CancelIoEx(_device, nullptr); @@ -327,34 +299,6 @@ bool SarClient::openControlDevice() return true; } -bool SarClient::openMmNotificationClient() -{ - if (FAILED(CoCreateInstance( - __uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, - __uuidof(IMMDeviceEnumerator), (LPVOID *)&_mmEnumerator))) { - - return false; - } - - if (FAILED(CComObject::CreateInstance( - &_mmNotificationClient))) { - - return false; - } - - _mmNotificationClient->AddRef(); - _mmNotificationClient->setClient(shared_from_this()); - - if (FAILED(_mmEnumerator->RegisterEndpointNotificationCallback( - _mmNotificationClient))) { - - return false; - } - - _mmNotificationClientRegistered = true; - return true; -} - bool SarClient::setBufferLayout() { SarSetBufferLayoutRequest request = {}; @@ -580,89 +524,4 @@ void SarClient::mux( } } -HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDeviceStateChanged( - _In_ LPCWSTR pwstrDeviceId, - _In_ DWORD dwNewState) -{ - // When a SAR endpoint is re-activated after its initial creation, its - // supported sample rate may be different. To force the audio engine to - // notice the possible format change, we listen for device state change - // events and tell the kernel mode driver to broadcast a - // KSEVENT_PINCAPS_FORMATCHANGE event, which causes the audio engine to - // re-query the pin capabilities. This isn't needed for newly added - // endpoints or non-SAR endpoints, so we filter out those events. - if (dwNewState != DEVICE_STATE_ACTIVE) { - return S_OK; - } - - if (auto client = _client.lock()) { - do { - CComPtr mmEnumerator; - CComPtr device; - CComPtr ps; - PROPVARIANT pvalue = {}; - - // This seems a bit shady, but MSDN's device events example - // initializes COM the same way. It's not clear what the ownership - // of the thread that delivers the IMMNotificationClient events is. - CoInitialize(NULL); - - if (FAILED(CoCreateInstance( - __uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, - __uuidof(IMMDeviceEnumerator), (LPVOID *)&mmEnumerator))) { - - break; - } - - if (FAILED(mmEnumerator->GetDevice(pwstrDeviceId, &device))) { - break; - } - - if (FAILED(device->OpenPropertyStore(STGM_READ, &ps))) { - break; - } - - if (FAILED(ps->GetValue( - PKEY_SynchronousAudioRouter_EndpointId, &pvalue))) { - - break; - } - - client->updateSampleRateOnTick(); - PropVariantClear(&pvalue); - } while(false); - - CoUninitialize(); - } - - return S_OK; -} - -HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDeviceAdded( - _In_ LPCWSTR pwstrDeviceId) -{ - return S_OK; -} - -HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDeviceRemoved( - _In_ LPCWSTR pwstrDeviceId) -{ - return S_OK; -} - -HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnDefaultDeviceChanged( - _In_ EDataFlow flow, - _In_ ERole role, - _In_ LPCWSTR pwstrDefaultDeviceId) -{ - return S_OK; -} - -HRESULT STDMETHODCALLTYPE SarClient::NotificationClient::OnPropertyValueChanged( - _In_ LPCWSTR pwstrDeviceId, - _In_ const PROPERTYKEY key) -{ - return S_OK; -} - } // namespace Sar diff --git a/SarAsio/sarclient.h b/SarAsio/sarclient.h index fd9d2e6..8d69027 100644 --- a/SarAsio/sarclient.h +++ b/SarAsio/sarclient.h @@ -39,10 +39,6 @@ struct SarClient: public std::enable_shared_from_this void tick(long bufferIndex); bool start(); void stop(); - void updateSampleRateOnTick() - { - _updateSampleRateOnTick = true; - } private: struct NotificationHandle @@ -64,42 +60,7 @@ struct SarClient: public std::enable_shared_from_this SarHandleQueueResponse responses[32]; }; - struct ATL_NO_VTABLE NotificationClient: - public CComObjectRootEx, - public IMMNotificationClient - { - BEGIN_COM_MAP(NotificationClient) - COM_INTERFACE_ENTRY(IMMNotificationClient) - END_COM_MAP() - - DECLARE_NO_REGISTRY() - - void setClient(std::weak_ptr client) - { - _client = client; - } - - virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged( - _In_ LPCWSTR pwstrDeviceId, - _In_ DWORD dwNewState) override; - virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded( - _In_ LPCWSTR pwstrDeviceId) override; - virtual HRESULT STDMETHODCALLTYPE OnDeviceRemoved( - _In_ LPCWSTR pwstrDeviceId) override; - virtual HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged( - _In_ EDataFlow flow, - _In_ ERole role, - _In_ LPCWSTR pwstrDefaultDeviceId) override; - virtual HRESULT STDMETHODCALLTYPE OnPropertyValueChanged( - _In_ LPCWSTR pwstrDeviceId, - _In_ const PROPERTYKEY key) override; - - private: - std::weak_ptr _client; - }; - bool openControlDevice(); - bool openMmNotificationClient(); bool setBufferLayout(); bool createEndpoints(); bool enableRegistryFilter(); @@ -127,10 +88,6 @@ struct SarClient: public std::enable_shared_from_this volatile SarEndpointRegisters *_registers; HandleQueueCompletion _handleQueueCompletion; bool _handleQueueStarted; - CComPtr _mmEnumerator; - CComObject *_mmNotificationClient = nullptr; - bool _mmNotificationClientRegistered = false; - std::atomic _updateSampleRateOnTick = false; std::mutex _registersLock; }; diff --git a/SarConfigure/SarConfigure.cpp b/SarConfigure/SarConfigure.cpp index c7e7534..2a31d84 100644 --- a/SarConfigure/SarConfigure.cpp +++ b/SarConfigure/SarConfigure.cpp @@ -59,89 +59,21 @@ static int openConfigurationDialog(Sar::AsioDriver& driver) { } } -static bool broadcastPinFormatChange() { - HDEVINFO devinfo; - SP_DEVICE_INTERFACE_DATA interfaceData; - PSP_DEVICE_INTERFACE_DETAIL_DATA interfaceDetail; - DWORD requiredSize; - HANDLE _device; - DWORD dummy; - - interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - devinfo = SetupDiGetClassDevs( - &GUID_DEVINTERFACE_SYNCHRONOUSAUDIOROUTER, nullptr, nullptr, - DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); - - if (devinfo == INVALID_HANDLE_VALUE) { - return false; - } - - if (!SetupDiEnumDeviceInterfaces(devinfo, NULL, - &GUID_DEVINTERFACE_SYNCHRONOUSAUDIOROUTER, 0, &interfaceData)) { - - return false; - } - - SetLastError(0); - SetupDiGetDeviceInterfaceDetail( - devinfo, &interfaceData, nullptr, 0, &requiredSize, nullptr); - - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - return false; - } - - interfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredSize); - interfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); - - if (!SetupDiGetDeviceInterfaceDetail( - devinfo, &interfaceData, interfaceDetail, - requiredSize, nullptr, nullptr)) { - - free(interfaceDetail); - return false; - } - - _device = CreateFile(interfaceDetail->DevicePath, - GENERIC_ALL, 0, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr); - - if (_device == INVALID_HANDLE_VALUE) { - free(interfaceDetail); - return false; - } - - free(interfaceDetail); - - DeviceIoControl(_device, SAR_SEND_FORMAT_CHANGE_EVENT, - nullptr, 0, nullptr, 0, &dummy, nullptr); - printf("Sent SAR_SEND_FORMAT_CHANGE_EVENT\n"); - - CloseHandle(_device); - - return true; -} - int main(int argc, char *argv[]) { + CoInitialize(NULL); - if (argc >= 2 && !strcmp(argv[1], "-u")) { - broadcastPinFormatChange(); - } - else { - CoInitialize(NULL); - - std::vector asioDrivers = Sar::InstalledAsioDrivers(); + std::vector asioDrivers = Sar::InstalledAsioDrivers(); - printf("\n"); - for (size_t i = 0; i < asioDrivers.size(); i++) { - printf("Checking driver: %s == %s\n", asioDrivers[i].clsid.c_str(), CLSID_STR_SynchronousAudioRouter); - if (asioDrivers[i].clsid == CLSID_STR_SynchronousAudioRouter) { - Sar::AsioDriver& driver = asioDrivers[i]; - printf("Found SAR ASIO driver: %s\n", driver.name.c_str()); - return openConfigurationDialog(driver); - } + printf("\n"); + for (size_t i = 0; i < asioDrivers.size(); i++) { + printf("Checking driver: %s == %s\n", asioDrivers[i].clsid.c_str(), CLSID_STR_SynchronousAudioRouter); + if (asioDrivers[i].clsid == CLSID_STR_SynchronousAudioRouter) { + Sar::AsioDriver& driver = asioDrivers[i]; + printf("Found SAR ASIO driver: %s\n", driver.name.c_str()); + return openConfigurationDialog(driver); } - - CoUninitialize(); } + + CoUninitialize(); } diff --git a/SarTest/wasapi.h b/SarTest/wasapi.h index b2fd7cc..a070a0a 100644 --- a/SarTest/wasapi.h +++ b/SarTest/wasapi.h @@ -99,9 +99,10 @@ struct WasapiOptions bool expectInvalidation = false; double minValidRatio = 0.5; long long maxDiscontinuities = -1; // -1: report only - // SarAsio broadcasts a format change whenever one of its endpoints - // becomes active, which invalidates open streams: wait before streaming, - // and reopen invalidated streams like a well-behaved client would. + // Streams opened just as a host's endpoints appear can be invalidated + // (SarAsio used to broadcast a format change on every endpoint + // activation): wait before streaming, and reopen invalidated streams like + // a well-behaved client would. double settleSeconds = 2.0; int maxReopens = 20; long long maxTransitionFrames = 4800; diff --git a/SynchronousAudioRouter/control.cpp b/SynchronousAudioRouter/control.cpp index c5d9e20..2d31094 100644 --- a/SynchronousAudioRouter/control.cpp +++ b/SynchronousAudioRouter/control.cpp @@ -633,55 +633,3 @@ VOID SarOrphanEndpoint(SarEndpoint *endpoint) SAR_TRACE("Endpoint orphaned but not deleted: %p, refs: %d", endpoint, endpoint->refs); } } - -NTSTATUS SarSendFormatChangeEvent(PDEVICE_OBJECT deviceObject, SarDriverExtension *extension) -{ - PVOID restartKey = nullptr; - PKSDEVICE ksDevice = KsGetDeviceForDeviceObject(deviceObject); - - // Always acquire ksDevice before extension->mutex, else deadlocks can occurs with a concurrent SarKsFilterCreate for example - KsAcquireDevice(ksDevice); - - KeEnterCriticalRegion(); - ExAcquireFastMutexUnsafe(&extension->mutex); - - FOR_EACH_GENERIC( - &extension->controlContextTable, SarTableEntry, - tableEntry, restartKey) { - - SarControlContext *controlContext = - (SarControlContext *)tableEntry->value; - - ExAcquireFastMutexUnsafe(&controlContext->mutex); - - PLIST_ENTRY entry = controlContext->endpointList.Flink; - - while (entry != &controlContext->endpointList) { - SarEndpoint *endpoint = - CONTAINING_RECORD(entry, SarEndpoint, listEntry); - - entry = entry->Flink; - - for (PKSFILTER filter = - KsFilterFactoryGetFirstChildFilter(endpoint->filterFactory); - filter; - filter = KsFilterGetNextSiblingFilter(filter)) { - - KsFilterGenerateEvents(filter, - &KSEVENTSETID_PinCapsChange, - KSEVENT_PINCAPS_FORMATCHANGE, - 0, nullptr, - nullptr, nullptr); - } - } - - ExReleaseFastMutexUnsafe(&controlContext->mutex); - } - - ExReleaseFastMutexUnsafe(&extension->mutex); - KeLeaveCriticalRegion(); - - KsReleaseDevice(ksDevice); - - return STATUS_SUCCESS; -} diff --git a/SynchronousAudioRouter/entry.cpp b/SynchronousAudioRouter/entry.cpp index 6a0e6ee..5421212 100644 --- a/SynchronousAudioRouter/entry.cpp +++ b/SynchronousAudioRouter/entry.cpp @@ -697,9 +697,6 @@ NTSTATUS SarIrpDeviceControl(PDEVICE_OBJECT deviceObject, PIRP irp) KeLeaveCriticalRegion(); break; } - case SAR_SEND_FORMAT_CHANGE_EVENT: - ntStatus = SarSendFormatChangeEvent(deviceObject, extension); - break; default: SAR_ERROR("Unknown ioctl %lu", ioControlCode); break; diff --git a/SynchronousAudioRouter/sar.h b/SynchronousAudioRouter/sar.h index f24fc92..16f2c63 100644 --- a/SynchronousAudioRouter/sar.h +++ b/SynchronousAudioRouter/sar.h @@ -60,8 +60,7 @@ DEFINE_GUID(GUID_DEVINTERFACE_SYNCHRONOUSAUDIOROUTER, FILE_DEVICE_UNKNOWN, 3, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) #define SAR_START_REGISTRY_FILTER CTL_CODE( \ FILE_DEVICE_UNKNOWN, 4, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA) -#define SAR_SEND_FORMAT_CHANGE_EVENT CTL_CODE( \ - FILE_DEVICE_UNKNOWN, 5, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA) +// 5 was SAR_SEND_FORMAT_CHANGE_EVENT, removed with pre-Windows 10 support. // SarNdis ioctls #define SARNDIS_IOCTL_CODE(i) CTL_CODE( \ @@ -320,9 +319,6 @@ NTSTATUS SarCreateEndpoint( SarCreateEndpointRequest *request); VOID SarOrphanEndpoint(SarEndpoint *endpoint); VOID SarDeleteEndpoint(SarEndpoint *endpoint); -NTSTATUS SarSendFormatChangeEvent( - PDEVICE_OBJECT deviceObject, - SarDriverExtension *extension); FORCEINLINE VOID SarRetainEndpoint(SarEndpoint *endpoint) {