Support default device in WASAPI driver (#1515)

The WASAPI sound driver now supports a "Default" audio output device:
<img
src="https://github.com/ares-emulator/ares/assets/27514983/fbf358d6-41e7-478d-8a76-fc86790d7774"
width="650" />

This uses the [Automatic Stream
Routing](https://learn.microsoft.com/en-us/windows/win32/coreaudio/automatic-stream-routing)
API that was introduced in Windows 10. A runtime check was added to
ensure that the feature does not appear on older versions of Windows.

I did some limited testing and found the feature to work without issues.

Closes https://github.com/ares-emulator/ares/issues/1514
このコミットが含まれているのは:
John Tur 2024-06-07 05:48:42 -04:00 committed by GitHub
コミット 0a8f25e7d8
この署名に対応する既知のキーがデータベースに存在しません
GPGキーID: B5690EEEBB952194
2個のファイルの変更134行の追加22行の削除

ファイルの表示

@ -6,6 +6,18 @@
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>

ファイルの表示

@ -13,6 +13,51 @@
#define IID_IAudioRenderClient __uuidof(IAudioRenderClient)
#endif
struct ActivateAudioInterfaceHandler : public IActivateAudioInterfaceCompletionHandler {
ActivateAudioInterfaceHandler& self = *this;
HANDLE completionEvent;
ActivateAudioInterfaceHandler() : refCount(1) {
self.completionEvent = CreateEvent(nullptr, false, false, nullptr);
}
~ActivateAudioInterfaceHandler() {
CloseHandle(self.completionEvent);
}
auto __stdcall QueryInterface(REFIID riid, void** ppv) -> HRESULT {
if(riid == IID_IUnknown || riid == IID_IAgileObject || riid == IID_IActivateAudioInterfaceCompletionHandler) {
*ppv = (IActivateAudioInterfaceCompletionHandler*)&self;
} else {
*ppv = NULL;
return E_NOINTERFACE;
}
self.AddRef();
return S_OK;
}
auto __stdcall AddRef() -> ULONG {
return InterlockedIncrement(&self.refCount);
}
auto __stdcall Release() -> ULONG {
if(InterlockedDecrement(&self.refCount) == 0){
delete &self;
return 0;
}
return self.refCount;
}
auto __stdcall ActivateCompleted(IActivateAudioInterfaceAsyncOperation *activateOperation) -> HRESULT {
if(!self.completionEvent) return E_FAIL;
if(!SetEvent(self.completionEvent)) return E_FAIL;
return S_OK;
}
private:
long refCount;
};
struct AudioWASAPI : AudioDriver {
AudioWASAPI& self = *this;
AudioWASAPI(Audio& super) : AudioDriver(super) { construct(); }
@ -31,7 +76,13 @@ struct AudioWASAPI : AudioDriver {
auto driver() -> string override { return "WASAPI"; }
auto ready() -> bool override { return self.isReady; }
auto hasExclusive() -> bool override { return true; }
auto hasExclusive() -> bool override {
if(auto device = self.getDevice()) {
return !(*device).isDefault;
} else {
return false;
}
}
auto hasBlocking() -> bool override { return true; }
auto hasDevices() -> vector<string> override {
@ -93,21 +144,57 @@ private:
struct Device {
string id;
string name;
bool isDefault;
};
vector<Device> devices;
auto getDevice() -> maybe<Device&> {
if(auto index = self.devices.find([&](auto& device) { return device.name == self.device; })) {
return self.devices[*index];
} else {
return nothing;
}
}
using PActivateAudioInterfaceAsync = HRESULT __stdcall(*)(LPCWSTR, REFIID, PROPVARIANT*, IActivateAudioInterfaceCompletionHandler*, IActivateAudioInterfaceAsyncOperation**);
maybe<bool> defaultDeviceSupported;
PActivateAudioInterfaceAsync activateAudioInterfaceAsync;
auto isDefaultDeviceSupported() -> bool {
if(self.defaultDeviceSupported) {
return *self.defaultDeviceSupported;
}
OSVERSIONINFO info = {
.dwOSVersionInfoSize = sizeof(info)
};
if(GetVersionEx(&info) && info.dwBuildNumber >= 14393) {
auto audioLib = LoadLibrary(L"mmdevapi");
self.activateAudioInterfaceAsync = (PActivateAudioInterfaceAsync)GetProcAddress(audioLib, "ActivateAudioInterfaceAsync");
self.defaultDeviceSupported = true;
} else {
self.defaultDeviceSupported = false;
}
return *self.defaultDeviceSupported;
}
auto construct() -> bool {
if(self.isDefaultDeviceSupported()) {
PWSTR defaultDeviceString;
if(StringFromIID(DEVINTERFACE_AUDIO_RENDER, &defaultDeviceString) != S_OK) return false;
Device defaultDevice = {
.id = (const char*)utf8_t(defaultDeviceString),
.name = "Default",
.isDefault = true
};
self.devices.append(defaultDevice);
}
if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&self.enumerator) != S_OK) return false;
IMMDevice* defaultDeviceContext = nullptr;
if(self.enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDeviceContext) != S_OK) return false;
Device defaultDevice;
LPWSTR defaultDeviceString = nullptr;
defaultDeviceContext->GetId(&defaultDeviceString);
defaultDevice.id = (const char*)utf8_t(defaultDeviceString);
CoTaskMemFree(defaultDeviceString);
IMMDeviceCollection* deviceCollection = nullptr;
if(self.enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) != S_OK) return false;
@ -119,6 +206,8 @@ private:
if(deviceCollection->Item(deviceIndex, &deviceContext) != S_OK) continue;
Device device;
device.isDefault = false;
LPWSTR deviceString = nullptr;
deviceContext->GetId(&deviceString);
device.id = (const char*)utf8_t(deviceString);
@ -131,11 +220,7 @@ private:
device.name = (const char*)utf8_t(propVariant.pwszVal);
propertyStore->Release();
if(device.id == defaultDevice.id) {
self.devices.prepend(device);
} else {
self.devices.append(device);
}
self.devices.append(device);
}
deviceCollection->Release();
@ -154,20 +239,35 @@ private:
auto initialize() -> bool {
terminate();
string deviceID;
if(auto index = self.devices.find([&](auto& device) { return device.name == self.device; })) {
deviceID = self.devices[*index].id;
Device selectedDevice;
if(auto device = self.getDevice()) {
selectedDevice = *device;
} else {
return false;
}
utf16_t deviceString(deviceID);
if(self.enumerator->GetDevice(deviceString, &self.audioDevice) != S_OK) return false;
utf16_t deviceString(selectedDevice.id);
if(selectedDevice.isDefault) {
ActivateAudioInterfaceHandler* handler = new ActivateAudioInterfaceHandler;
IActivateAudioInterfaceAsyncOperation* asyncOp;
if(self.activateAudioInterfaceAsync(deviceString, IID_IAudioClient, nullptr, handler, &asyncOp) != S_OK) return false;
WaitForSingleObject(handler->completionEvent, INFINITE);
handler->Release();
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
HRESULT activateResult;
IUnknown* activatedInterface;
if(asyncOp->GetActivateResult(&activateResult, &activatedInterface) != S_OK) return false;
asyncOp->Release();
if(activateResult != S_OK) return false;
self.audioClient = (IAudioClient*)activatedInterface;
} else {
if(self.enumerator->GetDevice(deviceString, &self.audioDevice) != S_OK) return false;
if(self.audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&self.audioClient) != S_OK) return false;
}
WAVEFORMATEXTENSIBLE waveFormat{};
if(self.exclusive) {
if(self.exclusive && !selectedDevice.isDefault) {
IPropertyStore* propertyStore = nullptr;
if(self.audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false;
PROPVARIANT propVariant;