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/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/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/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/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/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..a070a0a 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; @@ -98,12 +99,17 @@ 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; + // 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/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..2d31094 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; } @@ -643,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) { 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.