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
このコミットが含まれているのは:
コミット
0a8f25e7d8
|
@ -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;
|
||||
|
|
読み込み中…
新しいイシューから参照